Files
alfresco-community-repo/source/java/org/alfresco/web/bean/search/SearchContext.java
Alan Davis 36680a6542 Merged HEAD-BUG-FIX (5.1/Cloud) to HEAD (5.1/Cloud)
99981: Merged 5.0.N (5.0.2) to HEAD-BUG-FIX (5.1/Cloud) (PARTIAL MERGE)
      99482: Merged DEV to 5.0.N (5.0.1)
         99198 : MNT-13545: JavaDoc : Inconsistencies between the Java doc and the actual code
            - Changed Javadoc typo in project alfresco-jlan
         99413 : MNT-13545: JavaDoc : Inconsistencies between the Java doc and the actual code
            - Changed Javadoc parameters inconsistence in project alfresco-jlan
         99205 : MNT-13545: JavaDoc : Inconsistencies between the Java doc and the actual code
            - Changed Javadoc parameters inconsistence in project core
         99415 : MNT-13545: JavaDoc : Inconsistencies between the Java doc and the actual code
            - Changed Javadoc parameters inconsistence in project data-model
         99227 : MNT-13545: JavaDoc : Inconsistencies between the Java doc and the actual code
            - Changed Javadoc parameters inconsistence in project file-transfer-receiver
         99416 : MNT-13545: JavaDoc : Inconsistencies between the Java doc and the actual code
            - Changed Javadoc parameters inconsistence in project legacy-lucene
         99417 : MNT-13545: JavaDoc : Inconsistencies between the Java doc and the actual code
            - Changed Javadoc parameters inconsistence in project qa-share
         99418 : MNT-13545: JavaDoc : Inconsistencies between the Java doc and the actual code
            - Changed Javadoc parameters inconsistence in project remote-api
         99427 : MNT-13545: JavaDoc : Inconsistencies between the Java doc and the actual code
            - Changed Javadoc in project Repository, letters S..Z
         99433 : MNT-13545: JavaDoc : Inconsistencies between the Java doc and the actual code
            - Changed Javadoc in project Repository, letters A..R
         99421 : MNT-13545: JavaDoc : Inconsistencies between the Java doc and the actual code
            - Changed Javadoc parameters inconsistence in project share-po
         99247 : MNT-13545: JavaDoc : Inconsistencies between the Java doc and the actual code
            - Changed Javadoc typo in project slingshot
         99248 : MNT-13545: JavaDoc : Inconsistencies between the Java doc and the actual code
            - Changed Javadoc parameters inconsistence in project slingshot
         99424 : MNT-13545: JavaDoc : Inconsistencies between the Java doc and the actual code
            - Changed Javadoc parameters inconsistence in project solr
         99426 : MNT-13545: JavaDoc : Inconsistencies between the Java doc and the actual code
            - Changed Javadoc parameters inconsistence in project solr4
         99253 : MNT-13545: JavaDoc : Inconsistencies between the Java doc and the actual code
            - Changed Javadoc parameters inconsistence in project solr-client
         99259 : MNT-13545: JavaDoc : Inconsistencies between the Java doc and the actual code
            - Changed Javadoc parameters inconsistence in project web-client
         99260 : MNT-13545: JavaDoc : Inconsistencies between the Java doc and the actual code
            - Changed Javadoc parameters inconsistence in project web-framework-commons


git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@100501 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
2015-03-27 23:00:01 +00:00

1085 lines
38 KiB
Java

/*
* Copyright (C) 2005-2012 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.web.bean.search;
import java.io.Serializable;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import javax.faces.context.FacesContext;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.model.ContentModel;
import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.Path;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
import org.alfresco.util.ISO9075;
import org.alfresco.util.SearchLanguageConversion;
import org.alfresco.web.app.Application;
import org.alfresco.web.bean.repository.Repository;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.lucene.queryParser.QueryParser;
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.SAXReader;
import org.dom4j.io.XMLWriter;
/**
* Holds the context required to build a search query and can return the populated query.
* <p>
* Builds a lucene format search string from each of the supplied attributes and terms.
* Can be serialized to and from XML format for saving and restoring of previous searches.
*
* @author Kevin Roast
*/
public class SearchContext implements Serializable
{
private static final long serialVersionUID = 6730844584074229969L;
/** XML serialization elements */
private static final String ELEMENT_VALUE = "value";
private static final String ELEMENT_FIXED_VALUES = "fixed-values";
private static final String ELEMENT_INCLUSIVE = "inclusive";
private static final String ELEMENT_UPPER = "upper";
private static final String ELEMENT_LOWER = "lower";
private static final String ELEMENT_RANGE = "range";
private static final String ELEMENT_RANGES = "ranges";
private static final String ELEMENT_NAME = "name";
private static final String ELEMENT_ATTRIBUTE = "attribute";
private static final String ELEMENT_ATTRIBUTES = "attributes";
private static final String ELEMENT_MIMETYPE = "mimetype";
private static final String ELEMENT_CONTENT_TYPE = "content-type";
private static final String ELEMENT_FOLDER_TYPE = "folder-type";
private static final String ELEMENT_CATEGORY = "category";
private static final String ELEMENT_CATEGORIES = "categories";
private static final String ELEMENT_LOCATION = "location";
private static final String ELEMENT_MODE = "mode";
private static final String ELEMENT_TEXT = "text";
private static final String ELEMENT_SEARCH = "search";
private static final String ELEMENT_QUERY = "query";
/** advanced search term operators */
private static final char OP_WILDCARD = '*';
private static final char OP_AND = '+';
private static final char OP_NOT = '-';
private static final String STR_OP_WILDCARD = "" + OP_WILDCARD;
/** Search mode constants */
public final static int SEARCH_ALL = 0;
public final static int SEARCH_FILE_NAMES_CONTENTS = 1;
public final static int SEARCH_FILE_NAMES = 2;
public final static int SEARCH_SPACE_NAMES = 3;
/** the search text string */
private String text = "";
/** mode for the search */
private int mode = SearchContext.SEARCH_ALL;
/** folder XPath location for the search */
private String location = null;
/** categories to add to the search */
private String[] categories = new String[0];
/** folder type to restrict search against */
private String folderType = null;
/** content type to restrict search against */
private String contentType = null;
/** content mimetype to restrict search against */
private String mimeType = null;
/** any extra simple query attributes to add to the search */
protected List<QName> simpleSearchAdditionalAttrs = new ArrayList<QName>(4);
/** any extra query attributes to add to the search */
private Map<QName, String> queryAttributes = new HashMap<QName, String>(5, 1.0f);
/** any additional range attribute to add to the search */
private Map<QName, RangeProperties> rangeAttributes = new HashMap<QName, RangeProperties>(5, 1.0f);
/** any additional fixed value attributes to add to the search, such as boolean or noderef */
private Map<QName, String> queryFixedValues = new HashMap<QName, String>(5, 1.0f);
/** set true to force the use of AND between text terms */
private boolean forceAndTerms = false;
/** logger */
private static Log logger = LogFactory.getLog(SearchContext.class);
/**
* Build the search query string based on the current search context members.
*
* @param minimum small possible textual string used for a match
* this does not effect fixed values searches (e.g. boolean, int values) or date ranges
*
* @return prepared search query string
*/
public String buildQuery(int minimum)
{
String query;
boolean validQuery = false;
// the QName for the well known "name" attribute
String nameAttr = Repository.escapeQName(QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, ELEMENT_NAME));
StringBuilder plBuf = new StringBuilder(500).append("(");
StringBuilder mnBuf = new StringBuilder(500).append("-(");
// match against content text
String text = this.text.trim();
boolean appendText = mode == SEARCH_FILE_NAMES_CONTENTS || mode == SEARCH_ALL;
if (text.length() != 0 && text.length() >= minimum)
{
if (text.indexOf(' ') == -1 && text.charAt(0) != '"')
{
// check for existance of a special operator
boolean operatorAND = (text.charAt(0) == OP_AND);
boolean operatorNOT = (text.charAt(0) == OP_NOT);
// strip operator from term if one was found
if (operatorAND || operatorNOT)
{
text = text.substring(1);
}
if (text.length() != 0)
{
if (operatorNOT)
{
processSearchTextAttribute(nameAttr, text, appendText, mnBuf);
processSearchAdditionalAttributes(text, mnBuf, this.simpleSearchAdditionalAttrs);
}
else
{
if (plBuf.length() > 1)
{
plBuf.append(' ');
}
if (operatorAND)
{
plBuf.append(OP_AND);
}
plBuf.append('(');
processSearchTextAttribute(nameAttr, text, appendText, plBuf);
processSearchAdditionalAttributes(text, plBuf, this.simpleSearchAdditionalAttrs);
plBuf.append(')');
}
}
}
else
{
// multiple word search
if (text.charAt(0) == '"' && text.charAt(text.length() - 1) == '"')
{
// as a single quoted phrase
String quotedSafeText = '"' + SearchLanguageConversion.escapeLuceneQuery(text.substring(1, text.length() - 1)) + '"';
if (appendText)
{
plBuf.append("TEXT:").append(quotedSafeText);
}
plBuf.append(" @").append(nameAttr).append(":").append(quotedSafeText);
for (QName qname : this.simpleSearchAdditionalAttrs)
{
plBuf.append(" @").append(
Repository.escapeQName(qname)).append(":").append(quotedSafeText);
}
}
else
{
// as individual search terms
StringTokenizer t = new StringTokenizer(text, " ");
int tokenCount = t.countTokens();
for (int i=0; i<tokenCount; i++)
{
String term = t.nextToken();
// check for existance of a special operator
boolean operatorAND = (term.charAt(0) == OP_AND);
boolean operatorNOT = (term.charAt(0) == OP_NOT);
// strip operator from term if one was found
if (operatorAND || operatorNOT)
{
term = term.substring(1);
}
// special case for AND all terms if set (apply after operator character removed)
// note that we can't force AND if NOT operator has been set
if (operatorNOT == false)
{
operatorAND = operatorAND | this.forceAndTerms;
}
if (term.length() != 0)
{
if (operatorNOT)
{
processSearchTextAttribute(nameAttr, term, appendText, mnBuf);
if (mode == SearchContext.SEARCH_ALL)
{
processSearchAdditionalAttributes(term, mnBuf, this.simpleSearchAdditionalAttrs);
}
}
else
{
if (plBuf.length() > 0)
{
plBuf.append(' ');
}
if (operatorAND)
{
plBuf.append(OP_AND);
}
plBuf.append('(');
processSearchTextAttribute(nameAttr, term, appendText, plBuf);
if (mode == SearchContext.SEARCH_ALL)
{
processSearchAdditionalAttributes(term, plBuf, this.simpleSearchAdditionalAttrs);
}
plBuf.append(')');
}
}
}
}
}
plBuf.append(')');
mnBuf.append(')');
validQuery = true;
}
// match a specific PATH for space location or categories
StringBuilder pathQuery = null;
if (location != null || (categories != null && categories.length !=0))
{
pathQuery = new StringBuilder(128);
if (location != null)
{
pathQuery.append(" PATH:\"").append(location).append("\" ");
if (categories != null && categories.length != 0)
{
pathQuery.append("AND (");
}
}
if (categories != null && categories.length != 0)
{
for (int i=0; i<categories.length; i++)
{
pathQuery.append(" PATH:\"").append(categories[i]).append("\" ");
}
if (location != null)
{
pathQuery.append(") ");
}
}
}
// match any extra query attribute values specified
StringBuilder attributeQuery = null;
if (queryAttributes.size() != 0)
{
attributeQuery = new StringBuilder(queryAttributes.size() << 6);
for (QName qname : queryAttributes.keySet())
{
String value = queryAttributes.get(qname).trim();
if (value.length() >= minimum)
{
processSearchAttribute(qname, value, attributeQuery);
}
}
// handle the case where we did not add any attributes due to minimum length restrictions
if (attributeQuery.length() == 0)
{
attributeQuery = null;
}
}
// match any extra fixed value attributes specified
if (queryFixedValues.size() != 0)
{
if (attributeQuery == null)
{
attributeQuery = new StringBuilder(queryFixedValues.size() << 6);
}
for (QName qname : queryFixedValues.keySet())
{
String escapedName = Repository.escapeQName(qname);
String value = queryFixedValues.get(qname);
attributeQuery.append(" +@").append(escapedName)
.append(":\"").append(SearchLanguageConversion.escapeLuceneQuery(value)).append('"');
}
}
// range attributes are a special case also
if (rangeAttributes.size() != 0)
{
if (attributeQuery == null)
{
attributeQuery = new StringBuilder(rangeAttributes.size() << 6);
}
for (QName qname : rangeAttributes.keySet())
{
String escapedName = Repository.escapeQName(qname);
RangeProperties rp = rangeAttributes.get(qname);
String value1 = SearchLanguageConversion.escapeLuceneQuery(rp.lower);
String value2 = SearchLanguageConversion.escapeLuceneQuery(rp.upper);
attributeQuery.append(" +@").append(escapedName)
.append(":").append(rp.inclusive ? "[" : "{").append(value1)
.append(" TO ").append(value2).append(rp.inclusive ? "]" : "}");
}
}
// mimetype is a special case - it is indexed as a special attribute it comes from the combined
// ContentData attribute of cm:content - ContentData string cannot be searched directly
if (mimeType != null && mimeType.length() != 0)
{
if (attributeQuery == null)
{
attributeQuery = new StringBuilder(64);
}
String escapedName = Repository.escapeQName(QName.createQName(ContentModel.PROP_CONTENT + ".mimetype"));
attributeQuery.append(" +@").append(escapedName)
.append(":").append(mimeType);
}
// match against appropriate content type
String fileTypeQuery;
if (contentType != null)
{
fileTypeQuery = " TYPE:\"" + contentType + "\" ";
}
else
{
// default to cm:content
fileTypeQuery = " TYPE:\"{" + NamespaceService.CONTENT_MODEL_1_0_URI + "}content\" ";
}
// match against appropriate folder type
String folderTypeQuery;
if (folderType != null)
{
folderTypeQuery = " TYPE:\"" + folderType + "\" ";
}
else
{
folderTypeQuery = " TYPE:\"{" + NamespaceService.CONTENT_MODEL_1_0_URI + "}folder\" ";
}
if (text.length() != 0 && text.length() >= minimum)
{
StringBuilder buf = new StringBuilder(128);
if (plBuf.length() > 2)
{
buf.append(plBuf);
if (mnBuf.length() > 3)
{
buf.append(" AND ");
}
}
if (mnBuf.length() > 3)
{
buf.append(mnBuf);
}
// text query for name and/or full text specified
switch (mode)
{
case SearchContext.SEARCH_ALL:
query = '(' + fileTypeQuery + " AND " + '(' + buf + ')' + ')' +
' ' +
'(' + folderTypeQuery + " AND " + '(' + buf + "))";
break;
case SearchContext.SEARCH_FILE_NAMES:
case SearchContext.SEARCH_FILE_NAMES_CONTENTS:
query = fileTypeQuery + " AND " + '(' + buf + ')';
break;
case SearchContext.SEARCH_SPACE_NAMES:
query = folderTypeQuery + " AND " + buf;
break;
default:
throw new IllegalStateException("Unknown search mode specified: " + mode);
}
}
else
{
// no text query specified - must be an attribute/value query only
switch (mode)
{
case SearchContext.SEARCH_ALL:
query = '(' + fileTypeQuery + ' ' + folderTypeQuery + ')';
break;
case SearchContext.SEARCH_FILE_NAMES:
case SearchContext.SEARCH_FILE_NAMES_CONTENTS:
query = fileTypeQuery;
break;
case SearchContext.SEARCH_SPACE_NAMES:
query = folderTypeQuery;
break;
default:
throw new IllegalStateException("Unknown search mode specified: " + mode);
}
}
// match entire query against any additional attributes specified
if (attributeQuery != null)
{
query = attributeQuery + " AND (" + query + ')';
}
// match entire query against any specified paths
if (pathQuery != null)
{
query = "(" + pathQuery + ") AND (" + query + ')';
}
query = "(" + query + ") AND NOT ASPECT:\"sys:hidden\" ";
// check that we have a query worth executing - if we have no attributes, paths or text/name search
// then we'll only have a search against files/type TYPE which does nothing by itself!
validQuery = validQuery | (attributeQuery != null) | (pathQuery != null);
if (validQuery == false)
{
query = null;
}
if (logger.isDebugEnabled())
logger.debug("Query:\r\n" + query);
return query;
}
/**
* Build the lucene search terms required for the specified attribute and append to a buffer.
* Supports text values with a wildcard '*' character as the prefix and/or the suffix.
*
* @param qname QName of the attribute
* @param value Non-null value of the attribute
* @param buf Buffer to append lucene terms to
*/
private static void processSearchAttribute(QName qname, String value, StringBuilder buf)
{
buf.append(" +@").append(Repository.escapeQName(qname)).append(":\"")
.append(SearchContext.escape(value)).append("\" ");
}
/**
* Build the lucene search terms required for the specified attribute and append to multiple buffers.
* Supports text values with a wildcard '*' character as the prefix and/or the suffix.
*
* @param qname QName.toString() of the attribute
* @param value Non-null value of the attribute
* @param appendText Attribute search buffer to append lucene terms to
* @param mnBuf Text search buffer to append lucene terms to
*/
private static void processSearchTextAttribute(String qname, String value, boolean appendText, StringBuilder mnBuf)
{
mnBuf.append('@').append(qname).append(":\"")
.append(SearchContext.escape(value)).append('"');
if (appendText)
{
mnBuf.append(" TEXT:\"").append(SearchContext.escape(value)).append("\" ");
}
}
private static void processSearchAdditionalAttributes(String value, StringBuilder buf,
List<QName> simpleSearchAdditionalAttrs)
{
for (QName qname : simpleSearchAdditionalAttrs)
{
buf.append(" @").append(Repository.escapeQName(qname)).append(":\"").append(SearchContext.escape(value))
.append("\" ");
}
}
/**
* Returns a String where those characters that QueryParser
* expects to be escaped are escaped by a preceding <code>\</code>.
* '*' and '?' are not escaped.
*/
private static String escape(String s)
{
StringBuffer sb = new StringBuffer(s.length() + 4);
for (int i = 0; i < s.length(); i++)
{
char c = s.charAt(i);
if (c == '\\' || c == '+' || c == '-' || c == '!' || c == '(' || c == ')' || c == ':' ||
c == '^' || c == '[' || c == ']' || c == '\"' || c == '{' || c == '}' || c == '~')
{
sb.append('\\');
}
sb.append(c);
}
return sb.toString();
}
/**
* Generate a search XPATH pointing to the specified node, optionally return an XPATH
* that includes the child nodes.
*
* @param ref Of the node to generate path too
* @param children Whether to include children of the node
*
* @return the path
*/
public static String getPathFromSpaceRef(NodeRef ref, boolean children)
{
FacesContext context = FacesContext.getCurrentInstance();
Path path = Repository.getServiceRegistry(context).getNodeService().getPath(ref);
NamespaceService ns = Repository.getServiceRegistry(context).getNamespaceService();
StringBuilder buf = new StringBuilder(64);
for (int i=0; i<path.size(); i++)
{
String elementString = "";
Path.Element element = path.get(i);
if (element instanceof Path.ChildAssocElement)
{
ChildAssociationRef elementRef = ((Path.ChildAssocElement)element).getRef();
if (elementRef.getParentRef() != null)
{
Collection<String> prefixes = ns.getPrefixes(elementRef.getQName().getNamespaceURI());
if (prefixes.size() >0)
{
elementString = '/' + (String)prefixes.iterator().next() + ':' + ISO9075.encode(elementRef.getQName().getLocalName());
}
}
}
buf.append(elementString);
}
if (children == true)
{
// append syntax to get all children of the path
buf.append("//*");
}
else
{
// append syntax to just represent the path, not the children
buf.append("/*");
}
return buf.toString();
}
/**
* @return Returns the categories to use for the search
*/
public String[] getCategories()
{
return this.categories;
}
/**
* @param categories The categories to set as a list of search XPATHs
*/
public void setCategories(String[] categories)
{
if (categories != null)
{
this.categories = categories;
}
}
/**
* @return Returns the node XPath to search in or null for all.
*/
public String getLocation()
{
return this.location;
}
/**
* @param location The node XPATH to search from or null for all..
*/
public void setLocation(String location)
{
this.location = location;
}
/**
* @return Returns the mode to use during the search (see constants)
*/
public int getMode()
{
return this.mode;
}
/**
* @param mode The mode to use during the search (see constants)
*/
public void setMode(int mode)
{
this.mode = mode;
}
public String getSearchText()
{
if (this.text != null && this.text.trim().length() > 0)
{
return getText();
}
StringBuilder res = new StringBuilder();
for (QName attrName : this.queryAttributes.keySet())
{
if (ContentModel.PROP_TITLE.isMatch(attrName))
{
res.append(res.length() > 0 ? ", " : "");
res.append(Application.getMessage(FacesContext.getCurrentInstance(), "title"));
res.append(": ");
res.append(this.queryAttributes.get(attrName));
}
else if (ContentModel.PROP_DESCRIPTION.isMatch(attrName))
{
res.append(res.length() > 0 ? ", " : "");
res.append(Application.getMessage(FacesContext.getCurrentInstance(), "description"));
res.append(": ");
res.append(this.queryAttributes.get(attrName));
}
else if (ContentModel.PROP_AUTHOR.isMatch(attrName))
{
res.append(res.length() > 0 ? ", " : "");
res.append(Application.getMessage(FacesContext.getCurrentInstance(), "author"));
res.append(": ");
res.append(this.queryAttributes.get(attrName));
}
else
{
res.append(res.length() > 0 ? ", " : "");
res.append(this.queryAttributes.get(attrName));
}
}
return res.toString();
}
/**
* @return Returns the search text string.
*/
public String getText()
{
return this.text;
}
/**
* @param text The search text string.
*/
public void setText(String text)
{
this.text = text;
}
/**
* @return Returns the contentType.
*/
public String getContentType()
{
return this.contentType;
}
/**
* @param contentType The content type to restrict attribute search against.
*/
public void setContentType(String contentType)
{
this.contentType = contentType;
}
/**
* @return Returns the folderType.
*/
public String getFolderType()
{
return this.folderType;
}
/**
* @param folderType The folder type to restrict attribute search against.
*/
public void setFolderType(String folderType)
{
this.folderType = folderType;
}
/**
* @return Returns the mimeType.
*/
public String getMimeType()
{
return this.mimeType;
}
/**
* @param mimeType The mimeType to set.
*/
public void setMimeType(String mimeType)
{
this.mimeType = mimeType;
}
/**
* Add an additional attribute to search against for simple searches
*
* @param qname QName of the attribute to search against
*/
public void addSimpleAttributeQuery(QName qname)
{
this.simpleSearchAdditionalAttrs.add(qname);
}
/**
* Sets the additional attribute to search against for simple searches.
*
* @param attrs The list of attributes to search against
*/
public void setSimpleSearchAdditionalAttributes(List<QName> attrs)
{
if (attrs != null)
{
this.simpleSearchAdditionalAttrs = attrs;
}
}
/**
* Add an additional attribute to search against
*
* @param qname QName of the attribute to search against
* @param value Value of the attribute to use
*/
public void addAttributeQuery(QName qname, String value)
{
this.queryAttributes.put(qname, value);
}
public String getAttributeQuery(QName qname)
{
return this.queryAttributes.get(qname);
}
/**
* Add an additional range attribute to search against
*
* @param qname QName of the attribute to search against
* @param lower Lower value for range
* @param upper Upper value for range
* @param inclusive True for inclusive within the range, false otherwise
*/
public void addRangeQuery(QName qname, String lower, String upper, boolean inclusive)
{
this.rangeAttributes.put(qname, new RangeProperties(qname, lower, upper, inclusive));
}
public RangeProperties getRangeProperty(QName qname)
{
return this.rangeAttributes.get(qname);
}
/**
* Add an additional fixed value attribute to search against
*
* @param qname QName of the attribute to search against
* @param value Fixed value of the attribute to use
*/
public void addFixedValueQuery(QName qname, String value)
{
this.queryFixedValues.put(qname, value);
}
public String getFixedValueQuery(QName qname)
{
return this.queryFixedValues.get(qname);
}
/**
* @return Returns if AND is forced between text terms. False (OR terms) is the default.
*/
public boolean getForceAndTerms()
{
return this.forceAndTerms;
}
/**
* @param forceAndTerms Set true to force AND between text terms. Otherwise OR is the default.
*/
public void setForceAndTerms(boolean forceAndTerms)
{
this.forceAndTerms = forceAndTerms;
}
/**
* @return this SearchContext as XML
*
* Example:
* <code>
* <?xml version="1.0" encoding="UTF-8"?>
* <search>
* <text>CDATA</text>
* <mode>int</mode>
* <location>XPath</location>
* <categories>
* <category>XPath</category>
* </categories>
* <content-type>String</content-type>
* <folder-type>String</folder-type>
* <mimetype>String</mimetype>
* <attributes>
* <attribute name="String">String</attribute>
* </attributes>
* <ranges>
* <range name="String">
* <lower>String</lower>
* <upper>String</upper>
* <inclusive>boolean</inclusive>
* </range>
* </ranges>
* <fixed-values>
* <value name="String">String</value>
* </fixed-values>
* <query>CDATA</query>
* </search>
* </code>
*/
public String toXML()
{
try
{
NamespaceService ns = Repository.getServiceRegistry(FacesContext.getCurrentInstance()).getNamespaceService();
Document doc = DocumentHelper.createDocument();
Element root = doc.addElement(ELEMENT_SEARCH);
root.addElement(ELEMENT_TEXT).addCDATA(this.text);
root.addElement(ELEMENT_MODE).addText(Integer.toString(this.mode));
if (this.location != null)
{
root.addElement(ELEMENT_LOCATION).addText(this.location);
}
Element categories = root.addElement(ELEMENT_CATEGORIES);
for (String path : this.categories)
{
categories.addElement(ELEMENT_CATEGORY).addText(path);
}
if (this.contentType != null)
{
root.addElement(ELEMENT_CONTENT_TYPE).addText(this.contentType);
}
if (this.folderType != null)
{
root.addElement(ELEMENT_FOLDER_TYPE).addText(this.folderType);
}
if (this.mimeType != null && this.mimeType.length() != 0)
{
root.addElement(ELEMENT_MIMETYPE).addText(this.mimeType);
}
Element attributes = root.addElement(ELEMENT_ATTRIBUTES);
for (QName attrName : this.queryAttributes.keySet())
{
attributes.addElement(ELEMENT_ATTRIBUTE)
.addAttribute(ELEMENT_NAME, attrName.toPrefixString(ns))
.addCDATA(this.queryAttributes.get(attrName));
}
Element ranges = root.addElement(ELEMENT_RANGES);
for (QName rangeName : this.rangeAttributes.keySet())
{
RangeProperties rangeProps = this.rangeAttributes.get(rangeName);
Element range = ranges.addElement(ELEMENT_RANGE);
range.addAttribute(ELEMENT_NAME, rangeName.toPrefixString(ns));
range.addElement(ELEMENT_LOWER).addText(rangeProps.lower);
range.addElement(ELEMENT_UPPER).addText(rangeProps.upper);
range.addElement(ELEMENT_INCLUSIVE).addText(Boolean.toString(rangeProps.inclusive));
}
Element values = root.addElement(ELEMENT_FIXED_VALUES);
for (QName valueName : this.queryFixedValues.keySet())
{
values.addElement(ELEMENT_VALUE)
.addAttribute(ELEMENT_NAME, valueName.toPrefixString(ns))
.addCDATA(this.queryFixedValues.get(valueName));
}
// outputing the full lucene query may be useful for some situations
Element query = root.addElement(ELEMENT_QUERY);
String queryString = buildQuery(0);
if (queryString != null)
{
query.addCDATA(queryString);
}
StringWriter out = new StringWriter(1024);
XMLWriter writer = new XMLWriter(OutputFormat.createPrettyPrint());
writer.setWriter(out);
writer.write(doc);
return out.toString();
}
catch (Throwable err)
{
throw new AlfrescoRuntimeException("Failed to export SearchContext to XML.", err);
}
}
/**
* Restore a SearchContext from an XML definition
*
* @param xml XML format SearchContext @see #toXML()
*/
public SearchContext fromXML(String xml)
{
try
{
NamespaceService ns = Repository.getServiceRegistry(FacesContext.getCurrentInstance()).getNamespaceService();
// get the root element
SAXReader reader = new SAXReader();
Document document = reader.read(new StringReader(xml));
Element rootElement = document.getRootElement();
Element textElement = rootElement.element(ELEMENT_TEXT);
if (textElement != null)
{
this.text = textElement.getText();
}
Element modeElement = rootElement.element(ELEMENT_MODE);
if (modeElement != null)
{
this.mode = Integer.parseInt(modeElement.getText());
}
Element locationElement = rootElement.element(ELEMENT_LOCATION);
if (locationElement != null)
{
this.location = locationElement.getText();
}
Element categoriesElement = rootElement.element(ELEMENT_CATEGORIES);
if (categoriesElement != null)
{
List<String> categories = new ArrayList<String>(4);
for (Iterator i=categoriesElement.elementIterator(ELEMENT_CATEGORY); i.hasNext(); /**/)
{
Element categoryElement = (Element)i.next();
categories.add(categoryElement.getText());
}
this.categories = categories.toArray(this.categories);
}
Element contentTypeElement = rootElement.element(ELEMENT_CONTENT_TYPE);
if (contentTypeElement != null)
{
this.contentType = contentTypeElement.getText();
}
Element folderTypeElement = rootElement.element(ELEMENT_FOLDER_TYPE);
if (folderTypeElement != null)
{
this.folderType = folderTypeElement.getText();
}
Element mimetypeElement = rootElement.element(ELEMENT_MIMETYPE);
if (mimetypeElement != null)
{
this.mimeType = mimetypeElement.getText();
}
Element attributesElement = rootElement.element(ELEMENT_ATTRIBUTES);
if (attributesElement != null)
{
for (Iterator i=attributesElement.elementIterator(ELEMENT_ATTRIBUTE); i.hasNext(); /**/)
{
Element attrElement = (Element)i.next();
QName qname = QName.createQName(attrElement.attributeValue(ELEMENT_NAME), ns);
addAttributeQuery(qname, attrElement.getText());
}
}
Element rangesElement = rootElement.element(ELEMENT_RANGES);
if (rangesElement != null)
{
for (Iterator i=rangesElement.elementIterator(ELEMENT_RANGE); i.hasNext(); /**/)
{
Element rangeElement = (Element)i.next();
Element lowerElement = rangeElement.element(ELEMENT_LOWER);
Element upperElement = rangeElement.element(ELEMENT_UPPER);
Element incElement = rangeElement.element(ELEMENT_INCLUSIVE);
if (lowerElement != null && upperElement != null && incElement != null)
{
QName qname = QName.createQName(rangeElement.attributeValue(ELEMENT_NAME), ns);
addRangeQuery(qname,
lowerElement.getText(), upperElement.getText(),
Boolean.parseBoolean(incElement.getText()));
}
}
}
Element valuesElement = rootElement.element(ELEMENT_FIXED_VALUES);
if (valuesElement != null)
{
for (Iterator i=valuesElement.elementIterator(ELEMENT_VALUE); i.hasNext(); /**/)
{
Element valueElement = (Element)i.next();
QName qname = QName.createQName(valueElement.attributeValue(ELEMENT_NAME), ns);
addFixedValueQuery(qname, valueElement.getText());
}
}
}
catch (Throwable err)
{
throw new AlfrescoRuntimeException("Failed to import SearchContext from XML.", err);
}
return this;
}
/**
* Simple wrapper class for range query attribute properties
*/
static class RangeProperties implements Serializable
{
private static final long serialVersionUID = 5627339191207625169L;
QName qname;
String lower;
String upper;
boolean inclusive;
RangeProperties(QName qname, String lower, String upper, boolean inclusive)
{
this.qname = qname;
this.lower = lower;
this.upper = upper;
this.inclusive = inclusive;
}
}
}