/*******************************************************************************
* Copyright 2009, 2010 Innovation Gate GmbH. All Rights Reserved.
*
* This file is part of the OpenWGA server platform.
*
* OpenWGA is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* In addition, a special exception is granted by the copyright holders
* of OpenWGA called "OpenWGA plugin exception". You should have received
* a copy of this exception along with OpenWGA in file COPYING.
* If not, see <http://www.openwga.com/gpl-plugin-exception>.
*
* OpenWGA is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with OpenWGA in file COPYING.
* If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
package de.innovationgate.wgpublisher.webtml;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import de.innovationgate.utils.WGUtils;
import de.innovationgate.webgate.api.WGAPIException;
import de.innovationgate.webgate.api.WGAuthorisationException;
import de.innovationgate.webgate.api.WGBackendException;
import de.innovationgate.webgate.api.WGCachedResultSet;
import de.innovationgate.webgate.api.WGContent;
import de.innovationgate.webgate.api.WGContentKey;
import de.innovationgate.webgate.api.WGContentList;
import de.innovationgate.webgate.api.WGDatabase;
import de.innovationgate.webgate.api.WGException;
import de.innovationgate.webgate.api.WGInvalidDatabaseException;
import de.innovationgate.webgate.api.WGLanguage;
import de.innovationgate.webgate.api.WGQueryException;
import de.innovationgate.webgate.api.WGReportingResultSetCore;
import de.innovationgate.webgate.api.WGResultSet;
import de.innovationgate.webgate.api.WGStandardResultSet;
import de.innovationgate.webgate.api.WGStructEntry;
import de.innovationgate.webgate.api.WGStructEntryList;
import de.innovationgate.webgate.api.WGUnavailableException;
import de.innovationgate.wga.modules.options.OptionConversionException;
import de.innovationgate.wgpublisher.WGACore;
import de.innovationgate.wgpublisher.expressions.ExpressionEngine;
import de.innovationgate.wgpublisher.expressions.ExpressionEngineFactory;
import de.innovationgate.wgpublisher.expressions.ExpressionResult;
import de.innovationgate.wgpublisher.lang.LanguageBehaviour;
import de.innovationgate.wgpublisher.lang.LanguageBehaviourTools;
import de.innovationgate.wgpublisher.lang.WebTMLLanguageChooser;
import de.innovationgate.wgpublisher.lucene.LuceneManager;
import de.innovationgate.wgpublisher.webtml.utils.ResultSetTagStatus;
import de.innovationgate.wgpublisher.webtml.utils.TMLException;
public class Query extends Base {
public class QueryData {
private String _query;
private String _type;
public String getQuery() {
return _query;
}
public void setQuery(String query) {
_query = query;
}
public String getType() {
return _type;
}
public void setType(String type) {
_type = type;
}
}
public static final String TAGINFO_FULLQUERY = "fullquery";
public static final String TAGINFO_UNSPECIFICQUERY = "unspecificquery";
public static final String SESSION_ATTRIBUTE_SIMPLIFIED_LUCENEQUERY = "de.innovationgate.wgpublisher.webtml.Query.SIMPLIFIED_LUCENE_QUERY";
public static final String TAGINFO_ERROR = "error";
public static final String TAGINFO_CACHEUSED = "cacheused";
private static final Object TAGINFO_EXECUTIONTIME = "executiontime";
public static final Object TAGINFO_TOTALPROCESSINGTIME = "totalprocessingtime";
private String name;
private String type;
private String db;
private String alllanguages;
private String includecurrent;
private String highlight;
private String enhance;
private String max;
private String cache;
private String role;
private String returnField;
private String options;
public static class Status extends BaseTagStatus implements TMLParameterReceiver {
private String fullQuery;
private boolean unspecificQuery;
private boolean usedCache;
private WGContent firstContent = null;
private boolean firstContentRetrieved = false;
private HashMap queryParameters;
private Throwable error = null;
private WGResultSet _resultSet;
private String _role;
private void retrieveFirstContent() {
try {
if (firstContentRetrieved) {
return;
}
WGContent tryContent = null;
for (int offset = 0; offset < _resultSet.results(); offset++) {
try {
List contentList = _resultSet.getContentList(offset+1, 1);
if (contentList.size() >= 1) {
tryContent = (WGContent) contentList.get(0);
if (tryContent.mayBePublished(tmlContext.isbrowserinterface(), _role)) {
firstContent = tryContent;
break;
}
}
}
catch (WGAPIException e) {
tmlContext.addwarning("Exception extracting first result from query. Exception: '" + e.getClass().getName() + "' message: '" + e.getMessage() + "'.");
log.error("Exception extracting first result from query", e);
}
}
firstContentRetrieved = true;
}
catch (Exception e) {
tmlContext.addwarning("Exception extracting first result from query. Exception: '" + e.getClass().getName() + "' message: '" + e.getMessage() + "'.");
log.error("Exception extracting first result from query", e);
}
}
public WGContent getFirstContent() {
retrieveFirstContent();
return firstContent;
}
@Override
public Object getTagInfo(String name) throws WGAPIException {
if (name.equals(TAGINFO_FULLQUERY)) {
return this.fullQuery;
}
else if (name.equals(TAGINFO_UNSPECIFICQUERY)) {
return Boolean.valueOf(this.unspecificQuery);
}
else if (name.equals(TAGINFO_ERROR)) {
return this.error;
}
else if (name.equals(TAGINFO_CACHEUSED)) {
return new Boolean(this.usedCache);
}
else if (name.equals(TAGINFO_EXECUTIONTIME)) {
if (_resultSet != null) {
return _resultSet.getExecutionTime();
}
else {
return 0;
}
}
if (name.equals("count")) {
if (_resultSet != null) {
try {
return _resultSet.results();
}
catch (WGBackendException e) {
tmlContext.getlog().error("Exception retrieving result size for query", e);
}
}
return 0;
}
return super.getTagInfo(name);
}
public void addParam(String name, Object value, String type) {
queryParameters.put(name.toLowerCase(), value);
}
}
@Override
public BaseTagStatus createTagStatus() {
return new Status();
}
public String getReturn() {
return this.getTagAttributeValue("return", returnField, null);
}
public void setReturn(String returnField) {
returnField = returnField;
}
public void tmlEndTag() throws WGAPIException, TMLException {
Status status = (Status) getStatus();
this.setResultOutput(false);
// Get the db
WGDatabase db = null;
if ( (this.getDb() == null) || this.getDb().trim().startsWith("*") || this.getDb().indexOf(",") != -1){
db = this.getTMLContext().getdocument().getDatabase();
}
else {
try {
db = (WGDatabase) this.openContentDB(getTMLContext().resolveDBKey(this.getDb().toLowerCase()));
}
catch (WGUnavailableException e) {
this.addWarning("Database '" + getDb() + "' is currently unavailable");
status.error = e;
return;
}
catch (WGException e) {
this.addWarning(e.getMessage());
status.error = e;
return;
}
if (db == null) {
this.addWarning("Could not find database to query: " + this.getDb(), true);
status.error = new WGInvalidDatabaseException("Could not find database to query: " + this.getDb());
return;
}
if (db.isSessionOpen() == false) {
this.addWarning("User cannot open database " + this.getDb(), true);
status.error = new WGAuthorisationException("User cannot open database " + this.getDb());
return;
}
}
java.util.Map parameters = buildParameters(db);
// Get queryData
QueryData queryData = getQueryData(db);
if (queryData == null) {
return;
}
status._resultSet = null;
status._role = getRole();
if (queryData.getType().startsWith("xp:")) {
status._resultSet = executeExpressionQuery(getTMLContext().content(), queryData.getType().substring(3), queryData.getQuery(), parameters);
}
else if (queryData.getType().equals("lucene")) {
status._resultSet = executeLuceneQuery(db, queryData, parameters);
}
else {
status._resultSet = executeDBQuery(db, queryData.getQuery(), queryData.getType(), parameters);
}
if (status._resultSet != null) {
// If max results set, enforce this setting by telling the resultset to return this results limit
// This is done for queries that do not limit results themselves
Integer maxResults = (Integer) parameters.get(WGDatabase.QUERYOPTION_MAXRESULTS);
if (maxResults != null) {
status._resultSet.limitResults(maxResults.intValue());
}
ResultSetTagStatus parent = (ResultSetTagStatus) getStatus().getAncestorTag(ResultSetTagStatus.class);
if (parent != null) {
String language = (String) (parameters.containsKey(WGDatabase.QUERYOPTION_ONLYLANGUAGE) ? parameters.get(WGDatabase.QUERYOPTION_ONLYLANGUAGE) : ResultSetTagStatus.MULTILANGUAGE_RESULT);
parent.addResultSet(status._resultSet, language);
}
// Extract the first result for direct access (only neccessary if the query tag is addressable via ID or if return attribute is used)
if (getReturn() != null && status._resultSet.results() >= 1) {
status.retrieveFirstContent();
String returnItem = this.getReturn();
if (returnItem != null) {
if (status.firstContent != null && status.firstContent.hasItem(returnItem)) {
this.setResult(status.firstContent.getItemValue(returnItem));
this.setResultOutput(true);
}
}
}
}
else {
this.addWarning("Query had no result. type: " + this.getType() + " - query: " + this.getResult(), false);
return;
}
}
private java.util.HashMap buildParameters(WGDatabase db) throws WGAPIException, TMLException {
Status status = (Status) getStatus();
java.util.HashMap parameters = new java.util.HashMap();
WebTMLLanguageChooser chooser = new WebTMLLanguageChooser(db, getTMLContext());
// Include current behaviour
if (this.stringToBoolean(this.getIncludecurrent()) == false && !getTMLContext().content().isTemporary()) {
parameters.put(WGDatabase.QUERYOPTION_EXCLUDEDOCUMENT, this.getTMLContext().content());
}
// Role
parameters.put(WGDatabase.QUERYOPTION_ROLE, this.getRole());
// Behaviour regarding unpublished documents
if (this.stringToBoolean(this.getOnlypublished())) {
parameters.put(WGDatabase.QUERYOPTION_ENHANCE, new Boolean(true));
parameters.put(WGDatabase.QUERYOPTION_ONLYRELEASED, "");
}
else {
parameters.put(WGDatabase.QUERYOPTION_ENHANCE, new Boolean(false));
}
// Language behaviour
if (this.stringToBoolean(this.getAlllanguages()) != true) {
List<WGLanguage> langs = chooser.getQueryLanguages(db);
if (langs.size() == 0) {
throw new TMLException("No allowed query languages for database '" + db.getDbReference() + "' from context " + getTMLContext().getpath(), true);
}
parameters.put(WGDatabase.QUERYOPTION_ONLYLANGUAGE, langs.get(0).getName()); // Compatibility with query types not supporting multiple languages
parameters.put(WGDatabase.QUERYOPTION_LANGUAGES, langs);
}
//Set MaxResults
int maxResults = this.getMaxResults(db);
if (maxResults != 0) {
parameters.put(WGDatabase.QUERYOPTION_MAXRESULTS, new Integer(maxResults));
}
if (this.stringToBoolean(this.getCache()) == true) {
parameters.put(WGDatabase.QUERYOPTION_CACHERESULT, new Boolean(true));
}
// Eventually add native options
String options = getOptions();
if (options != null) {
parameters.put(WGDatabase.QUERYOPTION_NATIVEOPTIONS, options);
}
// Add LuceneOption SearchScope
// Default SearchScope == DB;
String searchScope = LuceneManager.SEARCHSCOPE_DB;
String strDb = getDb();
if (strDb != null) {
if (strDb.trim().equals("*")) {
searchScope = LuceneManager.SEARCHSCOPE_DOMAIN;
}
if (strDb.trim().equals("**")) {
searchScope = LuceneManager.SEARCHSCOPE_WGA;
}
if (strDb.trim().indexOf(",") != -1) {
searchScope = LuceneManager.SEARCHSCOPE_DB_LIST;
parameters.put(LuceneManager.QUERYOPTION_SEARCHDBKEYS, strDb);
}
}
parameters.put(LuceneManager.QUERYOPTION_SEARCHSCOPE, searchScope);
// Query parameters
parameters.put(WGDatabase.QUERYOPTION_QUERY_PARAMETERS, status.queryParameters);
return parameters;
}
/**
* determine max query results
* @return int maxQueryResults
*/
private int getMaxResults(WGDatabase db) {
// Determine max query results default
int defaultMaxResults = WGACore.DEFAULT_QUERY_MAXRESULTS;
if (db != null) {
defaultMaxResults = ((Integer) getCore().readPublisherOptionOrDefault(db, WGACore.DBATTRIB_MAXQUERYRESULTS)).intValue();
}
// Try to find max results for this query
String maxStr = this.getMax();
return stringToInteger(maxStr, defaultMaxResults);
}
private QueryData getQueryData(WGDatabase db) {
QueryData queryData = new QueryData();
queryData.setQuery(this.getResultString().trim());
queryData.setType(this.getType());
return queryData;
}
/**
* @param db2
* @param query
* @return
*/
private WGResultSet executeLuceneQuery(WGDatabase db, QueryData queryData, Map parameters) {
Status status = (Status) getStatus();
LuceneManager manager = getCore().getLuceneManager();
if (manager == null) {
addWarning("Lucene fulltext index is disabled");
return null;
}
WGResultSet resultSet;
try {
resultSet = manager.search(db, queryData.getQuery(), parameters, (HttpServletRequest) getPageContext().getRequest());
}
catch (WGQueryException e) {
this.addWarning("Error executing query: " + e.getError() + "{Query: " + e.getQuery() + "}", true);
status.error = e;
resultSet = null;
}
status.fullQuery = (String) parameters.get(WGDatabase.QUERYOPTION_RETURNQUERY);
Boolean specq = (Boolean) parameters.get(LuceneManager.TAGINFO_UNSPECIFICQUERY);
if (specq != null) {
status.unspecificQuery = specq.booleanValue();
} else {
status.unspecificQuery = false;
}
// if highlighting enabled store simplified lucene query in session
if (stringToBoolean(getHighlight())) {
((HttpServletRequest)getPageContext().getRequest()).getSession().setAttribute(SESSION_ATTRIBUTE_SIMPLIFIED_LUCENEQUERY, parameters.get(LuceneManager.TAGINFO_SIMPLIFIEDQUERY));
}
return resultSet;
}
private WGResultSet executeDBQuery(WGDatabase db, String query, String queryType, Map parameters) {
Status status = (Status) getStatus();
if (db == null) {
addWarning("This query type does not support multi database queries");
return null;
}
// Execute query
WGResultSet resultSet;
try {
resultSet = db.query(queryType, query, parameters);
}
catch (WGQueryException exc) {
this.addWarning("Error executing query: " + exc.getError() + "{Query: " + exc.getQuery() + "}", true);
status.error = exc;
resultSet = null;
}
catch (WGAPIException e) {
this.addWarning("Error executing query: " + e.getMessage(), true);
status.error = e;
resultSet = null;
}
status.fullQuery = (String) parameters.get(WGDatabase.QUERYOPTION_RETURNQUERY);
if (parameters.containsKey(WGDatabase.QUERYOPTION_USEDCACHE)) {
status.usedCache = true;
}
return resultSet;
}
/**
* Gets the db
*
* @return Returns a String
*/
public String getDb() {
return this.getTagAttributeValue("db", db, null);
}
/**
* Sets the db
*
* @param db
* The db to set
*/
public void setDb(String db) {
this.db = db;
}
/**
* Gets the type
*
* @return Returns a String
*/
public String getType() {
return this.getTagAttributeValue("type", type, this.getTMLContext().content().getDatabase().getAttribute(WGACore.DBATTRIB_QUERY_DEFAULT).toString());
}
/**
* Sets the type
*
* @param type
* The type to set
*/
public void setType(String type) {
this.type = type;
}
/**
* Gets the alllanguages
*
* @return Returns a String
*/
public String getAlllanguages() {
return this.getTagAttributeValue("alllanguages", alllanguages, "false");
}
/**
* Sets the alllanguages
*
* @param alllanguages
* The alllanguages to set
*/
public void setAlllanguages(String alllanguages) {
this.alllanguages = alllanguages;
}
/**
* Gets the max
*
* @return Returns a String
*/
public String getMax() {
return this.getTagAttributeValue("max", max, null);
}
/**
* Sets the max
*
* @param max
* The max to set
*/
public void setMax(String max) {
this.max = max;
}
/**
* Gets the enhance
*
* @return Returns a String
*/
public String getOnlypublished() {
return this.getTagAttributeValue("onlypublished", enhance, "true");
}
/**
* Sets the enhance
*
* @param enhance
* The enhance to set
*/
public void setOnlypublished(String enhance) {
this.enhance = enhance;
}
/**
* Gets the name
*
* @return Returns a String
*/
public String getName() {
return name;
}
/**
* Sets the name
*
* @param name
* The name to set
*/
public void setName(String name) {
this.name = name;
}
/* (non-Javadoc)
* @see de.innovationgate.wgpublisher.webtml.QueryParameterReceiver#addParam(java.lang.String, java.lang.Object)
*/
/**
* @see Base#tmlStartTag()
*/
public void tmlStartTag() throws TMLException {
Status status = (Status) getStatus();
status.queryParameters = new HashMap();
status.firstContent = null;
status.firstContentRetrieved = false;
status.error = null;
status.usedCache = false;
Collection.Status collectionTag = (Collection.Status) getStatus().getAncestorTag(Collection.class);
if (collectionTag != null) {
status.queryParameters.putAll(collectionTag.getQueryParameters());
}
// Put default query parameters
try {
WGContent content = getTMLContext().content();
Map<String, Object> defaultParameters = buildDefaultQueryParams(content);
for (Map.Entry<String,Object> param : defaultParameters.entrySet()) {
WGUtils.setDefaultProperty(status.queryParameters, param.getKey(), param.getValue());
}
}
catch (Throwable e) {
getCore().getLog().error("Error adding default query parameters", e);
addWarning("Error adding default query parameters: " + e.getClass().getName() + " - " + e.getMessage());
}
}
public static Map<String, Object> buildDefaultQueryParams(WGContent content) throws WGAPIException {
Map<String,Object> defaultParameters = new HashMap<String,Object>();
if (!content.isTemporary()) {
defaultParameters.put("content", content);
defaultParameters.put("key", content.getContentKey(true).toString());
defaultParameters.put("language", content.getLanguage().getName());
defaultParameters.put("structkey", String.valueOf(content.getContentKey(true).getStructKey()));
}
return defaultParameters;
}
public static String makeQueryParameterName(String relationName) {
StringBuilder paramName = new StringBuilder();
char c;
for (int i=0; i < relationName.length() ; i++) {
c = relationName.charAt(i);
if (Character.isDigit(c) || Character.isLetter(c) || c == '_') {
paramName.append(c);
}
else if (c == '-' || c == ' ') {
paramName.append("_");
}
}
return paramName.toString();
}
/**
* Returns the cache.
*
* @return String
*/
public String getCache() {
return this.getTagAttributeValue("cache", cache, "false");
}
/**
* Sets the cache.
*
* @param cache
* The cache to set
*/
public void setCache(String cache) {
this.cache = cache;
}
/**
* Returns the role.
*
* @return String
*/
public String getRole() {
return this.getTagAttributeValue("role", role, WGContent.DISPLAYTYPE_SEARCH);
}
/**
* Sets the role.
*
* @param role
* The role to set
*/
public void setRole(String role) {
this.role = role;
}
/**
* Returns the includecurrent.
*
* @return String
*/
public String getIncludecurrent() {
return this.getTagAttributeValue("includecurrent", includecurrent, "false");
}
/**
* Sets the includecurrent.
*
* @param includecurrent
* The includecurrent to set
*/
public void setIncludecurrent(String includecurrent) {
this.includecurrent = includecurrent;
}
/**
* @return
*/
public String getOptions() {
return this.getTagAttributeValue("options", options, null);
}
/**
* @param string
*/
public void setOptions(String string) {
options = string;
}
private WGResultSet executeExpressionQuery(WGContent content, String language, String expr, Map parameters) throws WGAPIException {
if (content.isDummy()) {
addWarning("Cannot execute an expression query when a dummy document is in context", true);
return null;
}
String scope = "children";
boolean deep = true;
// Parse native options
String nativeOptionsString = getOptions();
if (nativeOptionsString != null) {
List nativeOptions = WGUtils.deserializeCollection(nativeOptionsString, ",", true);
if (nativeOptions.contains("flat")) {
deep = false;
}
if (nativeOptions.contains("siblings")) {
scope = "siblings";
}
}
ExpressionEngine engine = ExpressionEngineFactory.getEngine(language);
if (engine == null) {
this.addWarning("Unknown expression language: language", true);
return null;
}
WGStructEntryList structsToSearch;
if (scope.equals("siblings")) {
structsToSearch = content.getStructEntry().getSiblingEntries();
}
else {
structsToSearch = content.getStructEntry().getChildEntries();
}
int maxDocs = 0;
String maxString = getMax();
if (maxString != null) {
try {
maxDocs = new Integer(maxString).intValue();
}
catch (NumberFormatException e) {
addWarning("Cannot parse maxdocs as a number: " + getMax(), false);
}
}
String prefLanguage = getTMLContext().getpreferredlanguage();
if (prefLanguage == null) {
prefLanguage = content.getLanguage().getName();
}
String role = null;
if (!parameters.containsKey(WGDatabase.QUERYOPTION_ENHANCE) || parameters.get(WGDatabase.QUERYOPTION_ENHANCE).equals(new Boolean(true))) {
role = (String) (parameters.containsKey(WGDatabase.QUERYOPTION_ROLE) ? parameters.get(WGDatabase.QUERYOPTION_ROLE) : "search");
}
List<WGContentKey> results = recurseExpressionQuery(engine, structsToSearch, prefLanguage, expr, deep, maxDocs, role);
WGCachedResultSet cachedSet = new WGCachedResultSet(results);
return new WGStandardResultSet(content.getDatabase(), cachedSet, parameters);
}
/**
* @param engine
* @param content
* @param expr
* @param deep
* @throws WGAPIException
*/
private List<WGContentKey> recurseExpressionQuery(ExpressionEngine engine, WGStructEntryList entryList, String language, String expr, boolean deep, int maxDocs, String role) throws WGAPIException {
Iterator childEntries = entryList.iterator();
WGStructEntry childEntry;
WGContent childContent;
List<WGContentKey> results = new ArrayList<WGContentKey>();
while (childEntries.hasNext()) {
childEntry = (WGStructEntry) childEntries.next();
childContent = childEntry.getReleasedContent(language);
if (childContent != null) {
if (role == null || childContent.isVisibleFor(role)) {
ExpressionResult result = engine.evaluateExpression(expr, getTMLContextForDocument(childContent), ExpressionEngine.TYPE_EXPRESSION, null);
if (result.isTrue()) {
results.add(childContent.getContentKey());
if (maxDocs != 0 && results.size() >= maxDocs) {
break;
}
}
if (result.isError()) {
addWarning("Error executing expression: " + result.getException().getClass().getName() + " - " + result.getException().getMessage());
}
}
}
if (deep) {
results.addAll(recurseExpressionQuery(engine, childEntry.getChildEntries(), language, expr, deep, maxDocs, role));
if (maxDocs != 0 && results.size() >= maxDocs) {
break;
}
}
}
return results;
}
public void setUnspecificQuery(boolean unspecificQuery) {
Status status = (Status) getStatus();
status.unspecificQuery = unspecificQuery;
}
public String getHighlight() {
return this.getTagAttributeValue("hightlight", highlight, "false");
}
public void setHighlight(String highlight) {
this.highlight = highlight;
}
}