Java tutorial
/* * Copyright (C) 2005-2012 BetaCONCEPT Limited * * This file is part of Astroboa. * * Astroboa 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. * * Astroboa 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 Astroboa. If not, see <http://www.gnu.org/licenses/>. */ package org.betaconceptframework.astroboa.model.factory; import java.io.InputStream; import java.util.List; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringUtils; import org.betaconceptframework.astroboa.api.model.ContentObject; import org.betaconceptframework.astroboa.api.model.ObjectReferenceProperty; import org.betaconceptframework.astroboa.api.model.SimpleCmsProperty; import org.betaconceptframework.astroboa.api.model.Taxonomy; import org.betaconceptframework.astroboa.api.model.Topic; import org.betaconceptframework.astroboa.api.model.exception.CmsException; import org.betaconceptframework.astroboa.api.model.query.Condition; import org.betaconceptframework.astroboa.api.model.query.QueryOperator; import org.betaconceptframework.astroboa.api.model.query.criteria.ConditionalCriterion; import org.betaconceptframework.astroboa.api.model.query.criteria.ContentObjectCriteria; import org.betaconceptframework.astroboa.api.model.query.criteria.ContentObjectReferenceCriterion; import org.betaconceptframework.astroboa.api.model.query.criteria.Criterion; import org.betaconceptframework.astroboa.api.model.query.criteria.LocalizationCriterion; import org.betaconceptframework.astroboa.api.model.query.criteria.SimpleCriterion; import org.betaconceptframework.astroboa.api.model.query.criteria.SimpleCriterion.CaseMatching; import org.betaconceptframework.astroboa.api.model.query.criteria.TopicCriteria; import org.betaconceptframework.astroboa.api.model.query.criteria.TopicReferenceCriterion; import org.betaconceptframework.astroboa.model.impl.item.CmsBuiltInItem; import org.betaconceptframework.astroboa.model.impl.query.criteria.ConditionalCriterionImpl; import org.betaconceptframework.astroboa.model.impl.query.criteria.ContentObjectReferenceCritetionImpl; import org.betaconceptframework.astroboa.model.impl.query.criteria.CriterionUtils; import org.betaconceptframework.astroboa.model.impl.query.criteria.LocalizationCriterionImpl; import org.betaconceptframework.astroboa.model.impl.query.criteria.NotCriterion; import org.betaconceptframework.astroboa.model.impl.query.criteria.RangeCriterion; import org.betaconceptframework.astroboa.model.impl.query.criteria.SimpleCriterionImpl; import org.betaconceptframework.astroboa.model.impl.query.criteria.TopicReferenceCriterionImpl; import org.betaconceptframework.astroboa.model.impl.query.parser.CriterionParser; import org.betaconceptframework.astroboa.util.CmsConstants; /** * Provides convenient methods for creating simple criteria for most common * operators. * * <p> * Use of this factory is preferred as it offers the developer all necessary * methods to create fast any criterion for any operator by just providing only * the necessary information. For example, an equals criterion for property * <code>title</code> could be created by the following two ways: * </p> * * <pre> * * // 1. The hard way * Criterion titleCriterion = CriterionFactory.newSimpleCriterion(); * titleCriterion.setProperty("title"); * titleCriterion.setOperator(QueryOperator.EQUALS); * titleCriterion.addValue("MySearchExpression"); * * //2. The easy way * Criterion titleCriterion = CriterionFactory.equals("title", "MySearchExpression"); * </pre> * * @author Gregory Chomatas (gchomatas@betaconcept.com) * @author Savvas Triantafyllou (striantafyllou@betaconcept.com) * */ public class CriterionFactory { CriterionFactory() { //Use private static constructor so that it cannot be instantiated } /** * Creates a simple criterion. * * <p> * This method creates an empty criterion. It should be used only when * this factory does not provide an alternative method for * a criterion to be created. * </p> * * @return An empty criterion. */ public static SimpleCriterion newSimpleCriterion() { return new SimpleCriterionImpl(); } /** * Associates two criteria with the {@link Condition#AND and} expression. * * @param leftHandSide * Left hand side of association. * @param rightHandSide * Right hand side of association. * @return An {@link ConditionalCriterion ANDed} conditional criterion. */ public static Criterion and(Criterion leftHandSide, Criterion rightHandSide) { return newConditionalCriterion(leftHandSide, rightHandSide, Condition.AND); } /** * Creates a "between" restriction for property. * * <p> * It is equivalent to a {@link #lessThanOrEquals(String, Object)} criterion * and a {@link #greaterThanOrEquals(String, Object)} criterion, associated with * logical expression {@link Condition#AND}. * </p> * * <p> * Note that multi valued simple properties, * will match a criterion if at least ONE of their * values satisfies the criterion. For example if there is a multi valued property called * <code>amounts</code> and has values <code>8, 11</code> and * the following criterion is set <code>between("amounts", "9","10")</code>, this property * will match the specified criterion because there is at least one value greater than <code>9</code> * and less than <code>10</code>. * </p> * * <p> * In general, range criteria on multi valued simple properties should be * avoided if possible as there is high possibility that "unwanted" results * may be included to the overall outcome. The result in the above example from user's perspective, * is wrong as property's values are not ALL between <code>9</code> and * <code>10</code> where as from JCR's perspective, is correct as there is at least * one value which is less than <code>9</code> and at least one value which is * greater than <code>10</code>. * </p> * * @param propertyPath * Same semantics as {@link SimpleCriterion#setProperty(String)}. * @param lowerLimit * Lower constraint value. * @param upperLimit * Upper constraint value. * @return A <code>lessThanOrEquals AND greaterThanOrEquals</code> constraint. */ public static Criterion between(String propertyPath, Object lowerLimit, Object upperLimit) { return createRangeCriterion(propertyPath, lowerLimit, upperLimit); } /** * Creates an "equals" restriction for property. * * <p> * Note that multi valued simple properties, * will match a criterion if at least ONE of their * values satisfies the criterion. For example if there is a multi valued property called * <code>amounts</code> and has values <code>23.4, 30.5</code> and * the following criterion is set <code>equals("amounts", "26")</code>, this property will match * the specified criterion. * </p> * * <p> * In cases where this criterion represents a reference criterion then value should be prefixed as follows : * * If criterion's property path corresponds to a property whose type is a reference to a * content object then value should be prefixed with {@link CmsConstants#CONTENT_OBJECT_REFERENCE_CRITERION_VALUE_PREFIX}. * If criterion's property path corresponds to a property whose type is a reference to a * topic then value should be prefixed with {@link CmsConstants#TOPIC_REFERENCE_CRITERION_VALUE_PREFIX}. * </p> * * @param propertyPath * Same semantics as {@link SimpleCriterion#setProperty(String)}. * @param value * Constraint value. * @return A <code>not equals</code> constraint. */ public static Criterion equals(String propertyPath, Object value) { return createSimpleCriterion(propertyPath, value, QueryOperator.EQUALS); } /** * Creates an "equals" restriction for property but for a list of values. * Example : * * <pre>equals("index", Condition.OR, Arrays.asList(1, 2))</pre> * <p> * will constrain query results to entities whose <code>index</code> * property has value <code>1</code> or <code>2</code>. * </p> * * <p> * Note that multi valued simple properties, * will match a criterion if at least ONE of their * values satisfies the criterion. For example if there is a multi valued property called * <code>amounts</code> and has values <code>23.4, 30.5</code> and * the following criterion is set <code>equals("amounts", Condition.OR, Arrays.asList(1, 2))</code>, this property * will not match the specified criterion. * </p> * * <p> * In cases where this criterion represents a reference criterion then values should be prefixed as follows : * * If criterion's property path corresponds to a property whose type is a reference to a * content object then each value should be prefixed with {@link CmsConstants#CONTENT_OBJECT_REFERENCE_CRITERION_VALUE_PREFIX}. * If criterion's property path corresponds to a property whose type is a reference to a * topic then each values should be prefixed with {@link CmsConstants#TOPIC_REFERENCE_CRITERION_VALUE_PREFIX}. * </p> * * @param propertyPath * Same semantics as {@link SimpleCriterion#setProperty(String)}. * @param internalCondition * {@link Condition#AND} or {@link Condition#OR}. * @param values * Constraint values. * * @return An <code>equals</code> constraint. */ public static Criterion equals(String propertyPath, Condition internalCondition, List values) { return createSimpleCriterion(propertyPath, values, internalCondition, QueryOperator.EQUALS); } /** * Same semantics with {@link #equals(String, Object)} but ignores case. Examples : * * <pre>equals("name", "bar")</pre> * <p> * will match all properties named <code>name</code> and have value(s) : * <code>Bar</code>, <code>BAR</code>, etc. * </p> * * @param propertyPath * Same semantics as {@link SimpleCriterion#setProperty(String)}. * @param value * Constraint value. * * @return An <code>equals</code> constraint. */ public static Criterion equalsCaseInsensitive(String propertyPath, Object value) { return CriterionUtils.createSimpleCriterion(propertyPath, value, QueryOperator.EQUALS, CaseMatching.LOWER_CASE); } /** * Creates a "greater than" restriction for property. * * <p> * Note that multi valued simple properties * will match a criterion if at least ONE of their * values satisfies the criterion. For example if there is a multi valued property called * <code>amounts</code> and has values <code>23.4, 30.5</code> and * the following criterion is set <code>greaterThan("amounts", "25")</code>, this property will match * the specified criterion. * </p> * * @param propertyPath * Same semantics as {@link SimpleCriterion#setProperty(String)}. * @param value * Constraint value. * * @return A <code>greater than</code> constraint. */ public static Criterion greaterThan(String propertyPath, Object value) { return createSimpleCriterion(propertyPath, value, QueryOperator.GREATER); } /** * Creates a "greater than or equals" restriction for property. * * <p> * Note that multi valued simple properties, * will match a criterion if at least ONE of their * values satisfies the criterion. For example if there is a multi valued property called * <code>amounts</code> and has values <code>23.4, 30.5</code> and * the following criterion is set <code>greaterThanOrEquals("amounts", "23.4")</code>, this property will match * the specified criterion. * </p> * @param propertyPath * Same semantics as {@link SimpleCriterion#setProperty(String)}. * @param value * Constraint value. * * @return A <code>greater than or equals</code> constraint. */ public static Criterion greaterThanOrEquals(String propertyPath, Object value) { return createSimpleCriterion(propertyPath, value, QueryOperator.GREATER_EQUAL); } /** * Creates a restriction for property existence. For example, * * <p> * <code>isNotNull("title")</code> will constrain query results to entities * whose <code>title</code> property exists. * * @param propertyPath * Same semantics as {@link SimpleCriterion#setProperty(String)}. * * @return A <code>not null</code> constraint. */ public static Criterion isNotNull(String propertyPath) { return createSimpleCriterion(propertyPath, null, null, QueryOperator.IS_NOT_NULL); } /** * Creates a restriction for property nonexistence. Examples : * * <p> * <code>isNull("title")</code> will constrain query results to entities * whose <code>title</code> property does not exist. * * @param propertyPath * Same semantics as {@link SimpleCriterion#setProperty(String)}. * * @return A <code>null</code> constraint. */ public static Criterion isNull(String propertyPath) { return createSimpleCriterion(propertyPath, null, null, QueryOperator.IS_NULL); } /** * Creates a "less than" restriction for property. * * <p> * Note that multi valued simple properties, * will match a criterion if at least ONE of their * values satisfies the criterion. For example if there is a multi valued property called * <code>amounts</code> and has values <code>23.4, 30.5</code> and * the following criterion is set <code>lessThan("amounts", "26")</code>, this property will match * the specified criterion. * </p> * * @param propertyPath * Same semantics as {@link SimpleCriterion#setProperty(String)}. * @param value * Constraint value. * * @return A <code>less than</code> constraint. */ public static Criterion lessThan(String propertyPath, Object value) { return createSimpleCriterion(propertyPath, value, QueryOperator.LESS); } /** * Creates a "less than or equals" restriction for property. * * <p> * Note that multi valued simple properties, * will match a criterion if at least ONE of their * values satisfies the criterion. For example if there is a multi valued property called * <code>amounts</code> and has values <code>23.4, 30.5</code> and * the following criterion is set <code>lessThanOrEquals("amounts", "26")</code>, this property will match * the specified criterion. * </p> * * @param propertyPath * Same semantics as {@link SimpleCriterion#setProperty(String)}. * @param value * Constraint value. * * @return A <code>less than</code> constraint. */ public static Criterion lessThanOrEquals(String propertyPath, Object value) { return createSimpleCriterion(propertyPath, value, QueryOperator.LESS_EQUAL); } /** * Like criterion is based on LIKE in SQL. * * <p> * Use character <code>%</code> before and/or * after value to search for any string of zero or more characters or * character <code>_</code> for any single character. Examples : * </p> * <ul> * <li> <code>like('title', '%val')</code> * <p> * will match all * <code>title</code> properties whose values end with the string * <code>val</code> * </p> * <li> <code>like('title', 'val%')</code> * <p> * will match all * <code>title</code> properties whose values start with the string * <code>val</code> * </p> * <li> <code>like('title', '%val%')</code> * <p> * will match all * <code>title</code> properties whose values contain with the substring * <code>val</code> * </p> * </ul> * * Use of this criterion is case sensitive. * * @param propertyPath * Same semantics as {@link SimpleCriterion#setProperty(String)}. * @param searchExpression * Search value. * @return A <code>like</code> constraint. */ public static Criterion like(String propertyPath, String searchExpression) { return createSimpleCriterion(propertyPath, searchExpression, QueryOperator.LIKE); } /** * Same semantics with {@link #like(String, String)} but ignores case. Examples : * * <pre>like("name", "bar")</pre> * <p> * will match all properties named <code>name</code> and have value(s) like: * <code>Bar</code>, <code>BAR</code>, etc. * </p> * * @param propertyPath * Same semantics as {@link SimpleCriterion#setProperty(String)}. * @param value * Constraint value. * * @return A <code>like</code> constraint with no case sensitivity */ public static Criterion likeCaseInsensitive(String propertyPath, String value) { return CriterionUtils.createSimpleCriterion(propertyPath, value, QueryOperator.LIKE, CaseMatching.LOWER_CASE); } /** * Same semantics with {@link #like(String, String)} but for multiple search * expressions and a condition. * * <p> * Example : <code>like('title', Arrays.asList("Larry", "Moe"), Condition.OR )</code> * will match all <code>title</code> properties whose values contain <code>Larry</code> * or <code>Moe</code>. * </p> * * @param propertyPath * Same semantics as {@link SimpleCriterion#setProperty(String)}. * @param searchExpressions * Search values. * @param internalCondition * Default value is {@link Condition#AND}. * @return A <code>like</code> constraint. */ public static Criterion like(String propertyPath, List<String> searchExpressions, Condition internalCondition) { return createSimpleCriterion(propertyPath, searchExpressions, internalCondition, QueryOperator.LIKE); } /** * Associates two criteria with the {@link Condition#OR or} expression. * * @param leftHandSide * Left hand side of association. * @param rightHandSide * Right hand side of association. * @return An {@link ConditionalCriterion ORed} conditional criterion. */ public static Criterion or(Criterion leftHandSide, Criterion rightHandSide) { return newConditionalCriterion(leftHandSide, rightHandSide, Condition.OR); } public static Criterion createSimpleCriterion(String propertyPath, List values, Condition internalCondition, QueryOperator operator) { //Special case if (values != null && operator != null && (operator == QueryOperator.EQUALS || operator == QueryOperator.NOT_EQUALS)) { for (Object value : values) { if (topicReferenceCriterionMustBeCreated(value)) { return newTopicReferenceCriterion(propertyPath, values, internalCondition, operator, ((String) value).endsWith(CmsConstants.INCLUDE_CHILDREN_EXPRESSION)); } } } return CriterionUtils.createSimpleCriterion(propertyPath, values, internalCondition, operator, CaseMatching.NO_CASE); } public static Criterion createSimpleCriterion(String propertyPath, Object value, QueryOperator operator) { //Special case if (operator != null && (operator == QueryOperator.EQUALS || operator == QueryOperator.NOT_EQUALS) && topicReferenceCriterionMustBeCreated(value)) { return newTopicReferenceCriterion(propertyPath, (String) value, operator, ((String) value).endsWith(CmsConstants.INCLUDE_CHILDREN_EXPRESSION)); } return CriterionUtils.createSimpleCriterion(propertyPath, value, operator, CaseMatching.NO_CASE); } private static boolean topicReferenceCriterionMustBeCreated(Object value) { return value != null && value instanceof String && (((String) value).startsWith(CmsConstants.TOPIC_REFERENCE_CRITERION_VALUE_PREFIX) || ((String) value).endsWith(CmsConstants.INCLUDE_CHILDREN_EXPRESSION)); } private static RangeCriterion createRangeCriterion(String propertyPath, Object lowerLimit, Object upperLimit) { RangeCriterion rangeCriteria = new RangeCriterion(); rangeCriteria.setProperty(propertyPath); rangeCriteria.setLowerLimit(lowerLimit); rangeCriteria.setUpperLimit(upperLimit); return rangeCriteria; } private static ConditionalCriterion newConditionalCriterion(Criterion leftHandSide, Criterion rightHandSide, Condition condition) { ConditionalCriterion conditionalCriterion = new ConditionalCriterionImpl(); conditionalCriterion.setCondition(condition); conditionalCriterion.setLeftHandSide(leftHandSide); conditionalCriterion.setRightHandSide(rightHandSide); return conditionalCriterion; } /** * Creates a range criterion between two values. * * @return An empty criterion. */ public static RangeCriterion newRangeCriterion(String property, Object lowerLimit, Object upperLimit) { RangeCriterion rangeCriterion = new RangeCriterion(); rangeCriterion.setProperty(property); rangeCriterion.setLowerLimit(lowerLimit); rangeCriterion.setUpperLimit(upperLimit); return rangeCriterion; } /** * Creates a "notEquals" restriction for property. * * <p> * Note that multi valued simple properties, * will match a criterion if at least ONE of their * values satisfies the criterion. For example if there is a multi valued property called * <code>amounts</code> and has values <code>23.4, 30.5</code> and * the following criterion is set <code>notEquals("amounts", "26")</code>, this property will match * the specified criterion. * </p> * <p> * In cases where this criterion represents a reference criterion then value should be prefixed as follows : * * If criterion's property path corresponds to a property whose type is a reference to a * content object then value should be prefixed with {@link CmsConstants#CONTENT_OBJECT_REFERENCE_CRITERION_VALUE_PREFIX}. * If criterion's property path corresponds to a property whose type is a reference to a * topic then value should be prefixed with {@link CmsConstants#TOPIC_REFERENCE_CRITERION_VALUE_PREFIX}. * </p> * @param propertyPath * Same semantics as {@link SimpleCriterion#setProperty(String)}. * @param value * Constraint value. * * @return A <code>not equals</code> constraint. */ public static Criterion notEquals(String propertyPath, Object value) { return createSimpleCriterion(propertyPath, value, QueryOperator.NOT_EQUALS); } /** * Creates a "not equals" restriction for property for a list of values. * Example : * * <pre>notEquals("index", Condition.AND, Arrays.asList(1, 2)</pre> * <p> * will constrain query results to entities whose <code>index</code> * property does not have values <code>1</code> and <code>2</code>. * </p> * * <p> * Note that multi valued simple properties, * will match a criterion if at least ONE of their * values satisfies the criterion. For example if there is a multi valued property called * <code>amounts</code> and has values <code>23.4, 30.5</code> and * the following criterion is set <code>notEquals("amounts", Condition.AND, Arrays.asList(1, 2))</code>, this property will match * the specified criterion. * </p> * * <p> * In cases where this criterion represents a reference criterion then values should be prefixed as follows : * * If criterion's property path corresponds to a property whose type is a reference to a * content object then each value should be prefixed with {@link CmsConstants#CONTENT_OBJECT_REFERENCE_CRITERION_VALUE_PREFIX}. * If criterion's property path corresponds to a property whose type is a reference to a * topic then each value should be prefixed with {@link CmsConstants#TOPIC_REFERENCE_CRITERION_VALUE_PREFIX}. * </p> * @param propertyPath * Same semantics as {@link SimpleCriterion#setProperty(String)}. * @param internalCondition * {@link Condition#AND} or {@link Condition#OR}. * @param values * Constraint values. * * @return A <code>not equals</code> constraint. */ public static Criterion notEquals(String propertyPath, Condition internalCondition, List values) { return createSimpleCriterion(propertyPath, values, internalCondition, QueryOperator.NOT_EQUALS); } /** * Creates a localization criterion. * * @return A constraint about localized labels for an entity. */ public static LocalizationCriterion newLocalizationCriterion() { return new LocalizationCriterionImpl(); } /** * Create a new criterion which negates the provided criterion. * * @param criterion * @return */ public static Criterion not(Criterion criterion) { return new NotCriterion(criterion); } /** * Create a {@link Criterion criterion} representing all query restrictions contained in the * provided expression and add it to provided {@link ContentObjectCriteria}. * * <p> * This method parses any string which contains query restrictions of the form * <code>propertyPath operator value</code> combined together with one or more * condition operators ({@link Condition#AND AND}, {@link Condition#OR OR}) with <code>AND</code> * having higher precedence than <code>OR</code>.It also supports * the use of parenthesis in order to define a different precedence among the * restrictions. * </p> * * <p> * <code>propertyPath</code> has the same semantics as {@link SimpleCriterion#setProperty(String)}, * except that it does not support index information, that is <code>profile.title[1]</code> is not * acceptable. * </p> * * <p> * For convenience , there are several reserved property paths: * <ul> * <li><b>objectType</b>, which refers to built in property {@link CmsBuiltInItem#ContentObjectTypeName}</li> * <li><b>textSearched</b>, which enabled full text search by calling {@link ContentObjectCriteria#addFullTextSearchCriterion(String)}</li> * </ul> * </p> * * <p> * <code>operator</code> can be one of * <ul> * <li>{@link QueryOperator#EQUALS =}</li> * <li>{@link QueryOperator#NOT_EQUALS !=}</li> * <li>{@link QueryOperator#LESS <}</li> * <li>{@link QueryOperator#LESS_EQUAL <=}</li> * <li>{@link QueryOperator#GREATER >}</li> * <li>{@link QueryOperator#GREATER_EQUAL >=}</li> * <li>{@link QueryOperator#IS_NOT_NULL IS_NOT_NULL}</li> * <li>{@link QueryOperator#IS_NULL IS_NULL}</li> * <li>{@link QueryOperator#CONTAINS CONTAINS}</li> * <li>{@link QueryOperator#LIKE LIKE} but using <code>%%</code></li> * </ul> * </p> * * <p> * Finally <code>value</code> can be any string literal between single (') or double quotes("). * In case you enclose value inside single quotes, then you may use double quotes within the search expression, * as long as these quotes are not place right after the first single quote or right before the last one.<br/> * * For Boolean values accepted entries are <code>true, TRUE, false, FALSE</code>.<br/> * * In cases where operator is <code>CONTAINS</code> ({@link QueryOperator#LIKE}) then value * can contain special character '*' as described in {@link #like(String, String)}. * Also, in cases where operator is <code>%%</code> ({@link QueryOperator#LIKE}) then value * can contain special character '%' as described in {@link #like(String, String)}. * * As far as date values concern, ISO8601 international standard representation is used. That means * that date values must follow the pattern * <pre> * yyyy-MM-ddTHH:mm:ss.SSSTZD * </pre> * * where * <pre> * yyyy represent four-digit year * MM represent two-digit month starting with 01 for January * dd represent two-digit day of month. First day is 01, last is 31 * T is a single character denoting that Time will follow * HH represent two digits of hour starting from 00 till 23 * mm represent two digits of minute starting from 00 till 59 * ss represent two digits of second starting from 00 till 59 * SSS represent three digits of milliseconds starting from 000 till 999 * TZD represent time zone designator, Z for UTC or an offset from UTC * in the form of +hh:mm or -hh:mm * </pre> * * For user convenience, date value contain only date information, that is yyyy-MM-dd * or may have date time information without milliseconds, that is yyyy-MM-ddTHH:mm.ssTZD * or may have no time zone as well, that is yyyy-MM-ddTHH:mm.ss. For all of these formats * default values will apply to the element missing. * </p> * * <p> * Here some several examples of valid expressions * <ul> * <li><code> (title="true") </code></li> * <li><code> (title="false") </code></li> * <li><code> (title="TRUE") </code></li> * <li><code> (title="FALSE") </code></li> * <li><code> (title="news") </code></li> * <li><code> (title!="news") </code></li> * <li><code> (title CONTAINS "news*") </code></li> * <li><code> (profile CONTAINS "news*") </code>, support for complex properties as well. This restriction instructs Astroboa to search all values of the child properties * of "profile" property.</li> * <li><code> (startDate="2009-04-14T19:21:51.000+03:00") </code></li> * <li><code> (startDate="2009-04-14T19:21:51+03:00") </code></li> * <li><code> (startDate="2009-04-14T19:21:51Z") </code></li> * <li><code> (startDate="2009-04-14T19:21:51-01:00") </code></li> * <li><code> (startDate="2009-04-14")</code></li> * <li><code> (title%%"news") </code></li> * * <li><code> (title%%"news%") </code></li> * <li><code> (title="ne'ws") </code></li> * <li><code> (title='ne"ws') </code></li> * <li><code> title IS_NOT_NULL </code></li> * <li><code> title IS_NULL </code></li> * <li><code> title IS_NULL AND profile.subject="news" </code></li> * <li><code> (title IS_NULL) AND (profile.subject="news") </code></li> * <li><code> ( (title IS_NULL) AND (profile.subject="news") ) </code></li> * <li><code> ( title IS_NULL AND profile.subject="news" ) </code></li> * <li><code> ( (title IS_NULL) AND profile.subject="news" ) </code></li> * <li><code> title="news" AND profile.subject="news" </code></li> * <li><code> (title="news") AND (profile.subject="news") </code></li> * <li><code> ( (title="news") AND (profile.subject="news") ) </code></li> * <li><code> ( title="news" AND profile.subject="news" ) </code></li> * <li><code> ( (title="news") AND profile.subject="news" ) </code></li> * <li><code> title="news" AND profile.subject="news" AND profile.title.en="article" </code></li> * <li><code> (title="news") AND (profile.subject="news") AND (profile.title.en="article") </code></li> * <li><code> ( (title="news") AND (profile.subject="news") AND (profile.title.en="article") ) </code></li> * <li><code> ( title="news" AND profile.subject="news" AND profile.title.en="article" ) </code></li> * <li><code> ( (title="news" AND profile.subject="news") AND profile.title.en="article" ) </code></li> * <li><code> ( (title="news" AND (profile.subject="news")) AND profile.title.en="article" ) </code></li> * <li><code> title="news" OR profile.subject="news" </code></li> * <li><code> (title="news") OR (profile.subject="news") </code></li> * <li><code> ( (title="news") OR (profile.subject="news") ) </code></li> * <li><code> ( title="news" OR profile.subject="news" ) </code></li> * <li><code> ( (title="news") OR profile.subject="news" ) </code></li> * <li><code> title="news" OR profile.subject="news" OR profile.title.en="article" </code></li> * <li><code> (title="news") OR (profile.subject="news") OR (profile.title.en="article") </code></li> * <li><code> ( (title="news") OR (profile.subject="news") OR (profile.title.en="article") ) </code></li> * <li><code> ( title="news" OR profile.subject="news" OR profile.title.en="article" ) </code></li> * <li><code> ( (title="news" OR profile.subject="news") OR profile.title.en="article" ) </code></li> * <li><code> ( (title="news" OR (profile.subject="news")) OR profile.title.en="article" ) </code></li> * <li><code> title="news" AND profile.subject="news" OR profile.title.en="article" </code></li> * <li><code> (title="news" AND profile.subject="news") OR profile.title.en="article" </code></li> *</ul> * </p> * * <p> * Here some several examples of INVALID expressions * <ul> * <li><code> (title="ne'ws'") </code></li> * <li><code> (title='"news') </code></li> * </ul> * </p> * * * @param expression A string representing query restrictions * @param contentObjectCriteria Criteria where the constructed criterion will be adjusted * @return {@link Criterion Criterion} representing the provided expression */ public static void parse(String expression, ContentObjectCriteria contentObjectCriteria) { if (StringUtils.isBlank(expression)) { return; } InputStream expressionStream = null; try { expressionStream = IOUtils.toInputStream(expression); CriterionParser criterionParser = new CriterionParser(expressionStream); criterionParser.parseExpressionAndAppendCriteria(contentObjectCriteria); } catch (Exception e) { throw new CmsException("Expression " + expression, e); } finally { IOUtils.closeQuietly(expressionStream); } } /** * Criterion used to match all content objects related to <code>topicIds</code> via property <code>propertyPath</code>. * * This is a useful method when searching for {@link ContentObject contentObjects} which refer to a specific * {@link Topic} and their property path is unknown or does not matter. This criterion will create several constraints, * one for each property of type {@link Topic} defined in content model which accepts topics belonging to * the same {@link Taxonomy taxonomy} with provided <code>topicId</code> or accepts topics from * any {@link Taxonomy taxonomy}. * * * * @param propertyPath * Same semantics as {@link SimpleCriterion#setProperty(String)} * but it is restricted to {@link SimpleCmsProperty simple cms properties}. * @param topicIdOrName * Topic id or name to match * @param operator * {@link QueryOperator}. Mainly, {@link QueryOperator#equals(Object)} * @param includeSubTopics * Whether to look for content objects which refers to any of topic children as well as topic itself * * @return Criterion object ready to be used inside and ORed or ANDed criterion or to be added directly to * {@link ContentObjectCriteria} */ public static Criterion newTopicReferenceCriterion(String propertyPath, String topicIdOrName, QueryOperator operator, boolean includeSubTopics) { TopicReferenceCriterion topicReferenceCriterion = new TopicReferenceCriterionImpl(); if (includeSubTopics) { topicReferenceCriterion.expandCriterionToIncludeSubTopics(); } topicReferenceCriterion.setProperty(propertyPath); topicReferenceCriterion.setOperator(operator); processValueAndAddItToCriterion(topicIdOrName, topicReferenceCriterion, CmsConstants.TOPIC_REFERENCE_CRITERION_VALUE_PREFIX); return topicReferenceCriterion; } /** * Criterion used to match all content objects related to <code>topicIds</code> via property <code>propertyPath</code>. * * This is a useful method when searching for {@link ContentObject contentObjects} which refer to a specific * {@link Topic}. In cases where property is unknown or does not matter, this criterion will create several constraints, * one for each property of type {@link Topic} defined in content model which accepts topics belonging to * the same {@link Taxonomy taxonomy} with provided <code>topicId</code> or accepts topics from * any {@link Taxonomy taxonomy}. * @param propertyPath * Same semantics as {@link SimpleCriterion#setProperty(String)} * but it is restricted to {@link SimpleCmsProperty simple cms properties}. * @param topicsOrTopicIdsOrTopicNames * List of topic Ids to match * @param internalCondition * Whether to look for properties which contain ALL provided <code>topicIds</code> {@link Condition#AND} or * they contain either of the provided <code>topicIds<code> * @param operator * {@link QueryOperator}. Mainly, {@link QueryOperator#equals(Object)} * @param includeSubTopics * Whether to look for content objects which refers to any of topic children as well as topic itself * * @return Criterion object ready to be used inside and ORed or ANDed criterion or to be added directly to * {@link ContentObjectCriteria} */ public static Criterion newTopicReferenceCriterion(String propertyPath, List topicsOrTopicIdsOrTopicNames, Condition internalCondition, QueryOperator operator, boolean includeSubTopics) { TopicReferenceCriterion topicReferenceCriterion = new TopicReferenceCriterionImpl(); if (includeSubTopics) { topicReferenceCriterion.expandCriterionToIncludeSubTopics(); } topicReferenceCriterion.setProperty(propertyPath); topicReferenceCriterion.setInternalCondition(internalCondition); topicReferenceCriterion.setOperator(operator); if (topicsOrTopicIdsOrTopicNames != null) { for (Object topicsOrTopicIdsOrTopicName : topicsOrTopicIdsOrTopicNames) { if (topicsOrTopicIdsOrTopicName != null) { if (topicsOrTopicIdsOrTopicName instanceof Topic) { topicReferenceCriterion.addTopicAsAValue((Topic) topicsOrTopicIdsOrTopicName); } else if (topicsOrTopicIdsOrTopicName instanceof String) { processValueAndAddItToCriterion((String) topicsOrTopicIdsOrTopicName, topicReferenceCriterion, CmsConstants.TOPIC_REFERENCE_CRITERION_VALUE_PREFIX); } else { topicReferenceCriterion.addValue(topicsOrTopicIdsOrTopicName); } } } } return topicReferenceCriterion; } /** * Criterion used to match all content objects related to <code>topic</code> via property <code>propertyPath</code>. * * This is a useful method when searching for {@link ContentObject contentObjects} which refer to a specific * {@link Topic} and their property path is unknown or does not matter. This criterion will create several constraints, * one for each property of type {@link Topic} defined in content model which accepts topics belonging to * the same {@link Taxonomy taxonomy} with provided <code>topicId</code> or accepts topics from * any {@link Taxonomy taxonomy}. * * * * @param propertyPath * Same semantics as {@link SimpleCriterion#setProperty(String)} * but it is restricted to {@link SimpleCmsProperty simple cms properties}. * @param topic * Topic to match * @param operator * {@link QueryOperator}. Mainly, {@link QueryOperator#equals(Object)} * @param includeSubTopics * Whether to look for content objects which refers to any of topic children as well as topic itself * * @return Criterion object ready to be used inside and ORed or ANDed criterion or to be added directly to * {@link ContentObjectCriteria} */ public static Criterion newTopicReferenceCriterion(String propertyPath, Topic topic, QueryOperator operator, boolean includeSubTopics) { TopicReferenceCriterion topicReferenceCriterion = new TopicReferenceCriterionImpl(); if (includeSubTopics) { topicReferenceCriterion.expandCriterionToIncludeSubTopics(); } topicReferenceCriterion.setProperty(propertyPath); topicReferenceCriterion.setOperator(operator); topicReferenceCriterion.addTopicAsAValue(topic); return topicReferenceCriterion; } /** * Criterion used to match all content objects related to <code>topics</code> via property <code>propertyPath</code>. * * This is a useful method when searching for {@link ContentObject contentObjects} which refer to a specific * {@link Topic}. In cases where property is unknown or does not matter, this criterion will create several constraints, * one for each property of type {@link Topic} defined in content model which accepts topics belonging to * the same {@link Taxonomy taxonomy} with provided <code>topicId</code> or accepts topics from * any {@link Taxonomy taxonomy}. * @param propertyPath * Same semantics as {@link SimpleCriterion#setProperty(String)} * but it is restricted to {@link SimpleCmsProperty simple cms properties}. * @param topics * List of topics to match * @param internalCondition * Whether to look for properties which contain ALL provided <code>topicIds</code> {@link Condition#AND} or * they contain either of the provided <code>topicIds<code> * @param operator * {@link QueryOperator}. Mainly, {@link QueryOperator#equals(Object)} * @param includeSubTopics * Whether to look for content objects which refers to any of topic children as well as topic itself * * @return Criterion object ready to be used inside and ORed or ANDed criterion or to be added directly to * {@link ContentObjectCriteria} */ public static Criterion newTopicReferenceListCriterion(String propertyPath, List<Topic> topics, Condition internalCondition, QueryOperator operator, boolean includeSubTopics) { TopicReferenceCriterion topicReferenceCriterion = new TopicReferenceCriterionImpl(); if (includeSubTopics) { topicReferenceCriterion.expandCriterionToIncludeSubTopics(); } topicReferenceCriterion.setProperty(propertyPath); topicReferenceCriterion.setInternalCondition(internalCondition); topicReferenceCriterion.setOperator(operator); topicReferenceCriterion.addTopicsAsValues(topics); return topicReferenceCriterion; } /** * Criterion used to match all content object properties of type {@link ContentObject} ( {@link ObjectReferenceProperty}) which * refer to a {@link ContentObject}. * * This is a useful method when searching for {@link ContentObject contentObjects} which refer to a specific * {@link ContentObject}. * * @param propertyPath * Same semantics as {@link SimpleCriterion#setProperty(String)} * but it is restricted to {@link SimpleCmsProperty simple cms properties}. * @param contentObjectsOrContentObjectIdsOrContentObjectSystemNames * List of content objects whose identifiers or system names will be used in criterion values or * list of content object identifiers or content object system names * @param internalCondition * Whether to look for properties which refer to ALL provided <code>contentObjectReferences</code> {@link Condition#AND} or * they refer to any of the provided <code>contentObjectReferences<code> * @param operator * {@link QueryOperator}. Mainly, {@link QueryOperator#equals(Object)} * @return Criterion object ready to be used inside and ORed or ANDed criterion or to be added directly to * {@link ContentObjectCriteria} */ public static Criterion newContentObjectReferenceCriterion(String propertyPath, List contentObjectsOrContentObjectIdsOrContentObjectSystemNames, Condition internalCondition, QueryOperator operator) { ContentObjectReferenceCriterion contentObjectReferenceCriterion = new ContentObjectReferenceCritetionImpl(); contentObjectReferenceCriterion.setProperty(propertyPath); contentObjectReferenceCriterion.setOperator(operator); contentObjectReferenceCriterion.setInternalCondition(internalCondition); if (contentObjectsOrContentObjectIdsOrContentObjectSystemNames != null) { for (Object contentObjectsOrContentObjectIdsOrContentObjectSystemName : contentObjectsOrContentObjectIdsOrContentObjectSystemNames) { if (contentObjectsOrContentObjectIdsOrContentObjectSystemName != null) { if (contentObjectsOrContentObjectIdsOrContentObjectSystemName instanceof ContentObject) { contentObjectReferenceCriterion.addContentObjectAsAValue( (ContentObject) contentObjectsOrContentObjectIdsOrContentObjectSystemName); } else if (contentObjectsOrContentObjectIdsOrContentObjectSystemName instanceof String) { processValueAndAddItToCriterion( (String) contentObjectsOrContentObjectIdsOrContentObjectSystemName, contentObjectReferenceCriterion, CmsConstants.CONTENT_OBJECT_REFERENCE_CRITERION_VALUE_PREFIX); } else { contentObjectReferenceCriterion .addValue(contentObjectsOrContentObjectIdsOrContentObjectSystemName); } } } } return contentObjectReferenceCriterion; } /** * Criterion used to match all content object properties of type {@link ContentObject} ( {@link ObjectReferenceProperty}) which * refer to a {@link ContentObject}. * * This is a useful method when searching for {@link ContentObject contentObjects} which refer to a specific * {@link ContentObject}. * * @param propertyPath * Same semantics as {@link SimpleCriterion#setProperty(String)} * but it is restricted to {@link SimpleCmsProperty simple cms properties}. * @param contentObjectReference * Content object whose identifier or system name will be used in criterion values * @param operator * {@link QueryOperator}. Mainly, {@link QueryOperator#equals(Object)} * @return Criterion object ready to be used inside and ORed or ANDed criterion or to be added directly to * {@link ContentObjectCriteria} */ public static Criterion newContentObjectReferenceCriterion(String propertyPath, ContentObject contentObjectReference, QueryOperator operator) { ContentObjectReferenceCriterion contentObjectReferenceCriterion = new ContentObjectReferenceCritetionImpl(); contentObjectReferenceCriterion.setProperty(propertyPath); contentObjectReferenceCriterion.addContentObjectAsAValue(contentObjectReference); contentObjectReferenceCriterion.setOperator(operator); return contentObjectReferenceCriterion; } /** * Criterion used to match all content object properties of type {@link ContentObject} ( {@link ObjectReferenceProperty}) which * refer to a {@link ContentObject}. * * This is a useful method when searching for {@link ContentObject contentObjects} which refer to a specific * {@link ContentObject}. * * @param propertyPath * Same semantics as {@link SimpleCriterion#setProperty(String)} * but it is restricted to {@link SimpleCmsProperty simple cms properties}. * @param contentObjectReferenceIdOrSystemName * Content object identifier or system name * @param operator * {@link QueryOperator}. Mainly, {@link QueryOperator#equals(Object)} * * @return Criterion object ready to be used inside and ORed or ANDed criterion or to be added directly to * {@link ContentObjectCriteria} */ public static Criterion newContentObjectReferenceCriterion(String propertyPath, String contentObjectReferenceIdOrSystemName, QueryOperator operator) { ContentObjectReferenceCriterion contentObjectReferenceCriterion = new ContentObjectReferenceCritetionImpl(); contentObjectReferenceCriterion.setProperty(propertyPath); contentObjectReferenceCriterion.setOperator(operator); processValueAndAddItToCriterion(contentObjectReferenceIdOrSystemName, contentObjectReferenceCriterion, CmsConstants.CONTENT_OBJECT_REFERENCE_CRITERION_VALUE_PREFIX); return contentObjectReferenceCriterion; } private static void processValueAndAddItToCriterion(String referenceIdOrSystemName, SimpleCriterion referenceCriterion, String expectedPrefix) { if (referenceIdOrSystemName != null && referenceCriterion != null) { //1. Reference Id or system name may end with /* // This is taken into account only when criterion // corresponds to a Topic Reference if (referenceCriterion instanceof TopicReferenceCriterion && referenceIdOrSystemName.endsWith(CmsConstants.INCLUDE_CHILDREN_EXPRESSION)) { ((TopicReferenceCriterion) referenceCriterion).expandCriterionToIncludeSubTopics(); //Remove suffix and continue processing referenceIdOrSystemName = StringUtils.substringBeforeLast(referenceIdOrSystemName, CmsConstants.INCLUDE_CHILDREN_EXPRESSION); } //2. In all other cases, add value as is if (!referenceIdOrSystemName.startsWith(expectedPrefix)) { if (CmsConstants.UUIDPattern.matcher(referenceIdOrSystemName).matches()) { referenceCriterion.addValue(referenceIdOrSystemName); } //3. If value represents a system name , add the proper prefix else { referenceCriterion.addValue(expectedPrefix + referenceIdOrSystemName); } } else { referenceCriterion.addValue(referenceIdOrSystemName); } } } /** * Create a {@link Criterion criterion} representing all query restrictions contained in the * provided expression and add it to provided {@link TopicCriteria}. * * <p> * This method parses any string which contains query restrictions of the form * <code>propertyPath operator value</code> combined together with one or more * condition operators ({@link Condition#AND AND}, {@link Condition#OR OR}) with <code>AND</code> * having higher precedence than <code>OR</code>.It also supports * the use of parenthesis in order to define a different precedence among the * restrictions. * </p> * * <p> * Users can search topic by name, by label in a locale or in any locale, by its ancestor's name, * by its ancestor's label or by its taxonomy's name. Search by taxonomy's label will be supported * in the next release. * </p> * * <p> * Therefore <code>propertyPath</code> can have one of the following values : * * <ul> * <li><code>id</code> represents the id of a topic</li> * <li><code>name</code> represents the name of a topic</li> * <li><code>label</code> represents a label of a topic in any locale</li> * <li><code>label.ANY_LOCALE</code> where ANY_LOCALE, can be replaced with "en", "el", etc and represents a label of a topic in a specific locale</li> * <li><code>ancestor.id</code> represents the id of a topic's ancestor</li> * <li><code>ancestor.name</code> represents the name of a topic's ancestor</li> * <li><code>ancestor.label</code> represents a label of a topic's ancestor in any locale</li> * <li><code>ancestor.label.ANY_LOCALE</code> where ANY_LOCALE, can be replaced with "en", "el", etc and represents a label of a topic's ancestor in a specific locale</li> * <li><code>taxonomy</code> represents the name of a topic's taxonomy</li> * </ul> * </p> * * <p> * <code>operator</code> can be one of * <ul> * <li>{@link QueryOperator#EQUALS =}</li> * <li>{@link QueryOperator#NOT_EQUALS !=}</li> * <li>{@link QueryOperator#CONTAINS CONTAINS}</li> * <li>{@link QueryOperator#LIKE LIKE} but using <code>%%</code></li> * </ul> * * All other operators are ignored. * </p> * * <p> * Finally <code>value</code> can be any string literal between single (') or double quotes("). * In case you enclose value inside single quotes, then you may use double quotes within the search expression, * as long as these quotes are not place right after the first single quote or right before the last one.<br/> * * In cases where operator is <code>CONTAINS</code> ({@link QueryOperator#LIKE}) then value * can contain special character '*' as described in {@link #like(String, String)}. * Also, in cases where operator is <code>%%</code> ({@link QueryOperator#LIKE}) then value * can contain special character '%' as described in {@link #like(String, String)}. * * <p> * Here some several examples of valid expressions * <ul> * <li><code> (name="sports") </code></li> * <li><code> (name!="sports") </code></li> * <li><code> (name%%"news") </code></li> * <li><code> (name%%"news%") </code></li> * <li><code> (name="ne'ws") </code></li> * <li><code> (name='ne"ws') </code></li> * <li><code> name="sports" AND ancestor.name="news" </code></li> * <li><code> (name="sports") AND (ancestor.name="news") </code></li> * <li><code> ( (name="sports") AND (ancestor.name="news") ) </code></li> * <li><code> ( name="sports" AND ancestor.name="news" ) </code></li> * <li><code> ( (name="sports") AND ancestor.name="news" ) </code></li> * <li><code> name="sport" AND ancestor.label CONTAINS "ne*" </code></li> * <li><code> name="sport" AND ancestor.label.en CONTAINS "ne*" </code></li> * <li><code> name CONTAINS "sport" AND ancestor.label.en CONTAINS "ne*" </code></li> * <li><code> label CONTAINS "sport*" AND ancestor.label.en CONTAINS "ne*" </code></li> * <li><code> taxonomy="sport" AND label CONTAINS "sport*" AND ancestor.label.en CONTAINS "ne*" </code></li> *</ul> * </p> * * <p> * Here some several examples of INVALID expressions * <ul> * <li><code> (name="ne'ws'") </code></li> * <li><code> (name='"news') </code></li> * </ul> * </p> * * * @param expression A string representing query restrictions * @param contentObjectCriteria Criteria where the constructed criterion will be adjusted * @return {@link Criterion Criterion} representing the provided expression */ public static void parse(String expression, TopicCriteria topicCriteria) { if (StringUtils.isBlank(expression)) { return; } InputStream expressionStream = null; try { expressionStream = IOUtils.toInputStream(expression); CriterionParser criterionParser = new CriterionParser(expressionStream); criterionParser.parseExpressionAndAppendTopicCriteria(topicCriteria); } catch (Exception e) { throw new CmsException("Expression " + expression, e); } finally { IOUtils.closeQuietly(expressionStream); } } /** * Contains criterion enables the use of full-text search. * * <p> * All types * of properties are searched including binary types. The following rules * apply : * </p> * * <p> * <ul> * <li>Search expression can contain one or more terms separated by whitespace.A * term can be a single word or a phrase delimited by double quotes ("). * <li>Search expression for all terms should contain whitespace (implicit AND) between * terms and for either of terms should contain OR. * <li>Search expression can contain both AND-ed and OR-ed terms with AND having higher * precedence. * <li>Any term prefixed with - (minus sign) will not be included in the * results. * <li>Any term containing '*' character will result in matching values * containing any string of zero or more characters in place of '*'. Note * this behavior is disabled if term is a phrase. * </ul> * </p> * * <p> * Search expression examples : * * <ul> * <li><code>contains('title', 'java jdk')</code> * <p> * Matches all values containing <code>java</code> AND <code>jdk</code>. * </p> * * <li><code>contains('title', '\"java jdk\"')</code> * <p> * Matches all values containing the phrase <code>java jdk</code>. * </p> * * <li><code>contains('title', 'java OR jdk')</code> * <p> * Matches all values containing <code>java</code> OR <code>jdk</code>. * </p> * * <li><code>contains('title', '\"java jdk\" OR 1.5')</code> * <p> * Matches all values which contain phrase <code>java jdk</code> OR * <code>1.5</code>. * </p> * * <li><code>contains('title', '-\"java jdk\" 1.5')</code> * <p> * Matches all values which DO NOT contain phrase <code>java jdk</code> * and contain <code>1.5</code>. * </p> * * <li><code>contains('title', 'java* 1.5')</code> * <p> * Matches all values which contain strings that start with * <code>java</code> and also contain string <code>1.5</code>. * </p> * * <li><code>contains('title', '*java* 1.5')</code> * <p> * Matches all values which contain string <code>java</code> and also * contain string <code>1.5</code>. * </p> * * </ul> * </p> * * @param propertyPath * Same semantics as {@link SimpleCriterion#setProperty(String)} * but it is restricted to {@link SimpleCmsProperty simple cms properties}. * @param searchExpression * Search value. * @return A <code>contains</code> constraint. */ public static Criterion contains(String propertyPath, String searchExpression) { return createSimpleCriterion(propertyPath, searchExpression, QueryOperator.CONTAINS); } }