mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-08-07 17:49:17 +00:00
27600: ALF-8522: MyBatis: Performance: ensure <association> elements are properly keyed 27608: RINF 09 / RINF 10: DB-based paged query for get children (DocLib & CMIS) - update FileFolder paging API - update GetChildren canned query sorting (to return sort props prior to batched pre-load) 27614: RINF 09 / RINF 10: DB-based paged query for get children (DocLib & CMIS) - add missing file (and fix my SmartSVN ignore unversioned list ;-) - follow-on to r27608 27621: RINF 11: Extended generalized CannedQueryDAO to support common selects - MyBatis resultset termination included - RINF 11: Integrate existing CannedQueryDAO (ALF-8417) - RINF 11: Canned queries (ALF-7167) 27623: RINF 09 / RINF 10: DB-based paged query for get children (DocLib & CMIS) - if total count not requested then can cut-off permission checks (based on max items) and instead set "hasMore" flag (if applicable) - now used by Share DocLib folder tree listing (on LHS) - see ALF-6570 27636: RINF 09 / RINF 10: DB-based getChildren - improvements to treenode (push down sort by name, get subfolders with max 1, remove redundant max check) - re: ALF-6570 - add default child assoc sort (if no sort requested) - minor tweak to getChildren CQ trace logging 27641: RINF 11: Extended generalized CannedQueryDAO - add temporary MyBatis workarounds for ResultHandler-based queries --> support unlimited queries for nested results maps (MyBatis #129) --> clear cache to return results on subsequent calls (MyBatis #58 ...) 27642: RINF 16: Ordered associations: Added NodeService.setAssociations - All reordering, deleting and adding node by NodeService impl. - ALF-7405 (ALF-7404 RINF 16: Peer association enhancements) 27643: ALF-8183 SVC 01: I18N List of values constraint. Added ListOfValuesConstraint.getDisplayLabel(constraintAllowableValue) method which uses the usual I18NUtil methods to retrieve a localised display-label for the LoV constraint values. These can be added to resource bundles in the usual way using a key as follows: listconstraint.test_list1.ABC=ABC display where "listconstraint." is a fixed prefix. "test_list1" is the shortform QName for the constraint with colon replaced by underscore. "ABC" is one of the allowed values for the named constraint. The test case changes & the associated model & property changes should make things clear. 27644: Test cases for ChannelService.createChannel and ChannelService.getChannels 27645: ALF-8183. I18N LIST constraint. This check-in ensures that requests for a LIST.value display label for: a non-existent value returns null an unlocalised value returns the allowableValue String from the model (backwards compatibility). 27651: Fixing compile errors caused by moving NodeLocator classes. 27652: RINF 09 / RINF 10: DB-based getChildren CQ - replace direct use of "sqlSessionTemplate" with "cannedQueryDAO"(RINF 11) 27657: RINF 09: GetChildren CQ - temp fix for cm:modified (when qname does not exist) - TODO review 27666: Support for ALF-8594: RSOLR 039: SOLR sorting - sorting for all single valued types 27671: Ignore virtual tomcat build 27673: Projects core, datamodel and solr now explicitly list their library dependencies and no longer include the 3rd party project 27681: Added pub:channelInfo aspect tows:webasset and ws:section. Also ws:website now extends pub:DeliveryChannel. Added new behaviours to section and website to ensure the channel info is populated. 27682: Added example SOLR config dir and instructions on how to set up - simple dev set up only subject to lots of change 27685: Cleanup: Removed non-javadoc; Removed unused methods; Added @override 27689: Update SOLR test instance dependencies 27692: ALF-8183 SVC 01: I18N Lost of values constraint. Added code to the FormService's PropertyFieldProcessor so that the allowedValues for a ListOfValuesConstraint have both the allowed value and its localised display label, if there is one. Added English property values for the bpm:allowedStatus constraint and also for the LIST constraint in the FDK. 27693: WCM-QS ML work - Have the webscript return more details on the node and its parents, and begin to use this in the UI to allow you to create as well as manage translations 27695: Workaround/fix for ALF-5747. Incompatibilities with OpenOffice 3.3 It looks like OOo 3.3.0 shipped with a missing component. The ConfigurationRegistry, which is used to remotely query the OOo version, was not implemented in 3.3.0 and therefore gives an ugly (and unavoidable) exception on OOo startup. There's not much we can do here, beyond getting users to upgrade their OOo install. I've added a catch clause that detects this problem and emits a slightly friendlier warning to the log. 27720: ALF-8532: WPUB: F148: Foundation API: Request that a node be published to the live environment 27727: Removed duplicates 27734: WCM-QS ML Updates to handle marking a node as an initial translation, and a start on creating translations 27735: Fixed generics warning 27736: Added PermissionService.getReaders(List<Long> aclIds) - Support for ALF-8335: RSOLR 013: DAOs and remote APIs for ACL changesets (ALF-7072) - Also cleaned up empty javadoc 27739: Implemented NodeFilters and NodeFinders for AbstractChannelType and ChannelServiceImpl. 27741: RINF 09: GetChildren CQ - fix sorting - with unit tests (for default Share DocLib fields) - partial ALF-8638 - ALF-8584 (follow-on for r27657) 27746: Start on linking the WCM-QS ML ui and the forms service for creating the translation 27747: Added --INCLUDE: directive to SQL scripts to allow importing of DB-specific snippets - ALF-8498: SVC 06: Property holding comment count for a node 27748: Web scripts for ACLs and ACL readers - Unit tests at all levels (Webscripts, Service and DAO) - Tweaks to the DB queries to support index-based sorting - Support for ALF-8335: RSOLR 013: DAOs and remote APIs for ACL changesets (ALF-7072) 27753: Fixed enum naming 27754: Undo changes to ChannelServiceImplIntegratedTest made in 27739 that broke the build 27762: Part I: ALF-7992: RSOLR 024: Locale support for query: SOLR - restructure and split localisation from generic query parsing - Lucene impl migrated and tested - No SOLR impl yet - Fix CMIS QueyTest - Fix references for LuceneQueryParser mostly to AbstractLuceneQueryParser 27763: Initial implementation of ALF-8498. SVC 06: Property holding comment count for a node. This check-in adds a rollup property to hold the commentCount (fm:commentCount under fm:discussable). It adds registered behaviours for fm:post deletion/creation that will decrement/increment the commentCount for the discussable ancestor node. I've also added a new test class to test this rollup property. There was no existing test class at this level and indeed there is no CommentService or DiscussionService. Instead the REST API of comment.put.desc.xml uses the ScriptNodeService to create the correct node structure. Therefore the test code has had to do the same thing. 27766: Slight reimpl of ALF-8498. Moved the rollup property onto its own aspect. So now we have fm:discussable, as before, for generic discussions and posts (including Explorer comments). And we have fm:commentsRollup which is only for rolling up Share comments. 27777: WCM QS ML Forms config and related tweaks to support having newly created nodes made multilingual where required 27781: Add repository project dependency for test context and debug - fixes model issue with SOLRAPIClientTest 27782: SOLR - move back to dynamic catch all field which seems to be working -> simpler build and model management 27810: ALF-8405 SiteAspect needs to handle child types of Site as well as Site itself 27821: ALF-7070: Fixed SOLRSerializer to handle residual properties (not in dictionary) 27828: RINF 11: Canned Queries (CQ) - update API post review . - add underlying pageRequest/pageResult for single page (or max items if skip is 0) - add hasMoreItems (ALF-8470) and update unit tests - return total count range - accurate count (if lower=upper), approximation (lower<upper) or more than (lower known, upper unknown) - update option to request total count with max (if cutoff/trimmed then returns unknown upper count) 27829: RINF 09: GetChildren CQ - paging support for FileFolderService list - update impl wrt CQ API changes (follow-on to r27828) - add factory method to return CQ with common params & update FileFolderService - update CMIS getChildren (AlrescoCmisService) - update scripting layer (ScriptNode, ScriptPagingNodes) - update Share DocLib (including doclist & treenode) - ALF-8641 - add proto (UI subject to review) to demo Share DocLib requesting max total count (eg. "Showing items 1 - 50 of 1000++") 27837: Moving the pdfbox, fontbox, jempbox libs from 1.3.1 to 1.5.0, which adds various bugfixes. 27838: Web scripts for ACLs and ACL readers - Simplification of paging API for ACLs - Support for ALF-8335: RSOLR 013: DAOs and remote APIs for ACL changesets (ALF-7072) 27839: SOLR ACL tracking client code and tests - Support for ALF-8335: RSOLR 013: ACL changesets client APIs (ALF-8463) 27844: Build fix. Making SiteServiceImplTest more tolerant of preexisting sites. 27845: Build fix. Making SiteServiceTest more tolerant of preexisting sites in databse. 27846: Build fix relating to ALF-8183. An extra test constraint requires an increment to a assertion expectedValue in this test. 27849: ALF-8532: WPUB: F148: Foundation API: Request that a node be published to the live environment - Added more necessary interfaces and framework code 27850: RINF 09: GetChildren CQ - update sorting unit test + fixes - nulls sort 'low' - fix multi sort props 27858: ALF-8532: WPUB: F148: Foundation API: Request that a node be published to the live environment - Initial cut of persisting publishing events (not tested yet) 27861: WCM QS ML webscript work - fix some issues, and start on webscript unit test Also updates a failing test with details of why it's failing (switch from JSON to XML some time ago) 27864: Fix context minimal tests - web publishing context is already included from the high level context, and shouldn't be in the core one 27867: ALF-8184: SVC 02: Encode and decode of forms itemId should be done in the REST layer and not in each individual form processor. 27868: Sample Adobe Illustrator files from Linton, for use in testing future metadata/transformer support 27869: Fixed ChannelServiceImplTest failures. 27870: Created JBPM process definition to publish scheduled Publishing Events. 27872: Sample Adobe Illustrator 3 file (PS not PDF based) from Linton, for use in testing future metadata/transformer support 27874: OpenCMIS update 27875: Change the Tika auto transformer to register aliases of mime types, as well as the canonical ones, for when Alfresco uses the alias 27876: RINF 11: Permission checking in canned queries (ALF-8419) 27877: RINF 39: Optimise GetChildren CQ for unsorted maxItems (ALF-8576) 27878: RINF 09: Update FileFolderService - all list methods should use GetChildren CQ (ALF-8733) 27882: ALF-8532: WPUB: F148: Foundation API: Request that a node be published to the live environment - Unit test for the publishing package serializer/deserializer 27885: Change order of publishing context so it comes after transfer service context. 27886: Fix for ALF-7992: RSOLR 024: Locale support for query: SOLR - d:mltext, d:text. d:content - phrase, term, prefix, wild, ranges, fuzzy etc - localised tokenisation - consistent tokenisation for cross language support ( phrase, term, prefix, wild, ranges, fuzzy etc) - CMIS related stuff to be tested later 27897: Minor tweak to prevent repeated gets of the default locale when writing properties 27900: WCM QS ML webscript tests 27905: Fixed PostgreSQL HeartbeatTest: WorkflowDeployer was checking for read-only server too late 27906: Remove unused imports 27907: Used common SQL snippets for DB2 node inserts. git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@28319 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
1035 lines
36 KiB
Java
1035 lines
36 KiB
Java
/*
|
|
* Copyright (C) 2005-2010 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.repo.search.impl.lucene.AbstractLuceneQueryParser;
|
|
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.web.bean.repository.Repository;
|
|
import org.apache.commons.logging.Log;
|
|
import org.apache.commons.logging.LogFactory;
|
|
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));
|
|
|
|
// match against content text
|
|
String text = this.text.trim();
|
|
|
|
StringBuilder fullTextBuf = new StringBuilder(64);
|
|
StringBuilder nameAttrBuf = new StringBuilder(128);
|
|
StringBuilder additionalAttrsBuf = new StringBuilder(128);
|
|
|
|
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)
|
|
{
|
|
// prepend NOT operator if supplied
|
|
if (operatorNOT)
|
|
{
|
|
fullTextBuf.append(OP_NOT);
|
|
nameAttrBuf.append(OP_NOT);
|
|
}
|
|
|
|
processSearchTextAttribute(nameAttr, text, nameAttrBuf, fullTextBuf);
|
|
for (QName qname : this.simpleSearchAdditionalAttrs)
|
|
{
|
|
processSearchAttribute(qname, text, additionalAttrsBuf, false, operatorNOT);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// multiple word search
|
|
if (text.charAt(0) == '"' && text.charAt(text.length() - 1) == '"')
|
|
{
|
|
// as a single quoted phrase
|
|
String quotedSafeText = '"' + text.substring(1, text.length() - 1) + '"';
|
|
fullTextBuf.append("TEXT:").append(quotedSafeText);
|
|
nameAttrBuf.append("@").append(nameAttr).append(":").append(quotedSafeText);
|
|
for (QName qname : this.simpleSearchAdditionalAttrs)
|
|
{
|
|
additionalAttrsBuf.append(" @").append(
|
|
Repository.escapeQName(qname)).append(":").append(quotedSafeText);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// as individual search terms
|
|
StringTokenizer t = new StringTokenizer(text, " ");
|
|
|
|
fullTextBuf.append('(');
|
|
nameAttrBuf.append('(');
|
|
additionalAttrsBuf.append('(');
|
|
|
|
int termCount = 0;
|
|
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)
|
|
{
|
|
// prepend NOT operator if supplied
|
|
if (operatorNOT)
|
|
{
|
|
fullTextBuf.append(OP_NOT);
|
|
nameAttrBuf.append(OP_NOT);
|
|
}
|
|
|
|
// prepend AND operator if supplied
|
|
if (operatorAND)
|
|
{
|
|
fullTextBuf.append(OP_AND);
|
|
nameAttrBuf.append(OP_AND);
|
|
}
|
|
|
|
processSearchTextAttribute(nameAttr, term, nameAttrBuf, fullTextBuf);
|
|
for (QName qname : this.simpleSearchAdditionalAttrs)
|
|
{
|
|
processSearchAttribute(qname, term, additionalAttrsBuf, operatorAND, operatorNOT);
|
|
}
|
|
|
|
fullTextBuf.append(' ');
|
|
nameAttrBuf.append(' ');
|
|
additionalAttrsBuf.append(' ');
|
|
|
|
termCount++;
|
|
}
|
|
}
|
|
fullTextBuf.append(')');
|
|
nameAttrBuf.append(')');
|
|
additionalAttrsBuf.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(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 = AbstractLuceneQueryParser.escape(rp.lower);
|
|
String value2 = AbstractLuceneQueryParser.escape(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\" ";
|
|
}
|
|
|
|
String fullTextQuery = fullTextBuf.toString();
|
|
String nameAttrQuery = nameAttrBuf.toString();
|
|
String additionalAttrsQuery =
|
|
(this.simpleSearchAdditionalAttrs.size() != 0) ? additionalAttrsBuf.toString() : "";
|
|
|
|
if (text.length() != 0 && text.length() >= minimum)
|
|
{
|
|
// text query for name and/or full text specified
|
|
switch (mode)
|
|
{
|
|
case SearchContext.SEARCH_ALL:
|
|
query = '(' + fileTypeQuery + " AND " + '(' + nameAttrQuery + ' ' + additionalAttrsQuery + ' ' + fullTextQuery + ')' + ')' +
|
|
' ' +
|
|
'(' + folderTypeQuery + " AND " + '(' + nameAttrQuery + ' ' + additionalAttrsQuery + "))";
|
|
break;
|
|
|
|
case SearchContext.SEARCH_FILE_NAMES:
|
|
query = fileTypeQuery + " AND " + nameAttrQuery;
|
|
break;
|
|
|
|
case SearchContext.SEARCH_FILE_NAMES_CONTENTS:
|
|
query = fileTypeQuery + " AND " + '(' + nameAttrQuery + ' ' + fullTextQuery + ')';
|
|
break;
|
|
|
|
case SearchContext.SEARCH_SPACE_NAMES:
|
|
query = folderTypeQuery + " AND " + nameAttrQuery;
|
|
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 + ')';
|
|
}
|
|
|
|
// 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)
|
|
{
|
|
processSearchAttribute(qname, value, buf, true, false);
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
* @param andOp If true apply the '+' AND operator as the prefix to the attribute term
|
|
* @param notOp If true apply the '-' NOT operator as the prefix to the attribute term
|
|
*/
|
|
private static void processSearchAttribute(QName qname, String value, StringBuilder buf, boolean andOp, boolean notOp)
|
|
{
|
|
if (andOp) buf.append('+');
|
|
else if (notOp) buf.append('-');
|
|
buf.append('@').append(Repository.escapeQName(qname)).append(":\"")
|
|
.append(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 attrBuf Attribute search buffer to append lucene terms to
|
|
* @param textBuf Text search buffer to append lucene terms to
|
|
*/
|
|
private static void processSearchTextAttribute(String qname, String value, StringBuilder attrBuf, StringBuilder textBuf)
|
|
{
|
|
textBuf.append("TEXT:\"").append(value).append('"');
|
|
attrBuf.append('@').append(qname).append(":\"")
|
|
.append(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 id 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 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;
|
|
}
|
|
|
|
/**
|
|
* @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
|
|
* @param value Value of the attribute to use
|
|
*/
|
|
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;
|
|
}
|
|
}
|
|
}
|