001/* 002 * Copyright 2018 Anyware Services 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.ametys.cms.search.query; 017 018import java.util.ArrayList; 019import java.util.Collection; 020import java.util.Collections; 021import java.util.HashSet; 022import java.util.List; 023import java.util.Map; 024import java.util.Objects; 025import java.util.Set; 026 027import org.apache.solr.client.solrj.util.ClientUtils; 028 029import org.ametys.cms.content.ContentHelper; 030import org.ametys.cms.content.referencetable.HierarchicalReferenceTablesHelper; 031import org.ametys.cms.content.referencetable.search.ParentContentSearchField; 032import org.ametys.cms.repository.Content; 033import org.ametys.plugins.repository.AmetysObjectResolver; 034 035/** 036 * Represents a {@link Query} testing a content field. 037 */ 038public class ContentQuery extends AbstractFieldQuery 039{ 040 /** The value to test. */ 041 protected Object _value; 042 /** The operator */ 043 protected Operator _operator; 044 /** The resolver to resolve the results. */ 045 protected AmetysObjectResolver _resolver; 046 /** The extension point. */ 047 protected ContentHelper _contentHelper; 048 /** <code>true</code> if the query should be executed with an AndQuery. */ 049 protected boolean _isAndMultipleOperand; 050 /** The helper to resolve the reference tables. */ 051 protected HierarchicalReferenceTablesHelper _refTableHelper; 052 /** <code>true</code> if the query should be executed by resolving the children. */ 053 protected boolean _resolveChildren; 054 055 /** 056 * Build a content query. 057 * @param fieldPath the field's path 058 * @param operator the operator 059 * @param value the value 060 * @param resolver the resolver 061 * @param contentHelper the content helper 062 */ 063 public ContentQuery(String fieldPath, Operator operator, Object value, AmetysObjectResolver resolver, ContentHelper contentHelper) 064 { 065 066 this(fieldPath, operator, value, resolver, contentHelper, false); 067 } 068 069 /** 070 * Build a content query by testing the operand. 071 * @param fieldPath the field's path 072 * @param operator the operator 073 * @param value the value 074 * @param resolver the resolver 075 * @param contentHelper the content helper 076 * @param isAndMultipleOperand 'true' to execute an AndQuery, OrQuery otherwise 077 */ 078 public ContentQuery(String fieldPath, Operator operator, Object value, AmetysObjectResolver resolver, ContentHelper contentHelper, boolean isAndMultipleOperand) 079 { 080 this(fieldPath, operator, value, resolver, contentHelper, null, false, isAndMultipleOperand); 081 } 082 083 /** 084 * Build a content query by testing if the ancestors should be resolved. 085 * @param fieldPath the field's path 086 * @param operator the operator 087 * @param value the value 088 * @param resolver the resolver 089 * @param contentHelper the content helper 090 * @param refTableHelper The helper to resolve the ancestors 091 * @param resolveChildren 'true' to execute an OrQuery on the ancestors and itself, 'false' will execute a join request 092 */ 093 public ContentQuery(String fieldPath, Operator operator, Object value, AmetysObjectResolver resolver, ContentHelper contentHelper, HierarchicalReferenceTablesHelper refTableHelper, boolean resolveChildren) 094 { 095 this(fieldPath, operator, value, resolver, contentHelper, refTableHelper, resolveChildren, false); 096 } 097 098 /** 099 * Build a content query by testing the operand and if the ancestors should be resolved. 100 * @param fieldPath the field's path 101 * @param operator The operator. Only {@link Query.Operator#EQ} and {@link Query.Operator#NE} are allowed. 102 * @param value the value 103 * @param resolver the resolver 104 * @param contentHelper the content helper 105 * @param refTableHelper The helper to resolve the ancestors 106 * @param resolveChildren 'true' to execute an OrQuery on the ancestors and itself, 'false' will execute a join request 107 * @param isAndMultipleOperand 'true' to execute an AndQuery, OrQuery otherwise 108 */ 109 public ContentQuery(String fieldPath, Operator operator, Object value, AmetysObjectResolver resolver, ContentHelper contentHelper, HierarchicalReferenceTablesHelper refTableHelper, boolean resolveChildren, boolean isAndMultipleOperand) 110 { 111 super(fieldPath); 112 if (Operator.EQ != operator && Operator.NE != operator && Operator.EXISTS != operator) 113 { 114 throw new IllegalArgumentException("Test operator '" + operator + "' can't be used for a content query."); 115 } 116 117 this._value = value; 118 this._operator = operator; 119 this._resolver = resolver; 120 this._contentHelper = contentHelper; 121 this._refTableHelper = refTableHelper; 122 this._resolveChildren = resolveChildren; 123 this._isAndMultipleOperand = isAndMultipleOperand; 124 } 125 126 127 /** 128 * Get the value. 129 * @return the value 130 */ 131 public Object getValue() 132 { 133 return this._value; 134 } 135 136 @SuppressWarnings("unchecked") 137 public String build() throws QuerySyntaxException 138 { 139 if (_operator == Operator.EXISTS) 140 { 141 StringBuilder query = new StringBuilder(); 142 query.append(_fieldPath).append("_s:").append(QueryHelper.EXISTS_VALUE); 143 return query.toString(); 144 } 145 146 if (_value instanceof Map<?, ?>) 147 { 148 Map<String, Object> mapValues = (Map<String, Object>) this._value; 149 Object values = mapValues.get("value"); 150 boolean autoposting = (boolean) mapValues.get("autoposting"); 151 if (autoposting) 152 { 153 if (_resolveChildren) 154 { 155 Query query = null; 156 if (values instanceof Collection<?>) 157 { 158 query = _getChildrenOrSelfQuery((Collection<String>) values, _operator); 159 } 160 else 161 { 162 query = _getChildrenOrSelfQuery(Collections.singletonList((String) values), _operator); 163 } 164 165 return query.build(); 166 } 167 else 168 { 169 Query subQuery = null; 170 if (values instanceof Collection<?>) 171 { 172 subQuery = _createRefParentsQuery((Collection<String>) values, _operator); 173 } 174 else 175 { 176 subQuery = _createRefParentsQuery(Collections.singletonList((String) values), _operator); 177 } 178 179 Query joinQuery = new JoinQuery(subQuery, _fieldPath); 180 return joinQuery.build(); 181 } 182 183 } 184 else 185 { 186 Query query = _createQueryString(values, _operator); 187 return query.build(); 188 } 189 } 190 else 191 { 192 Query query = _createQueryString(_value, _operator); 193 return query.build(); 194 } 195 } 196 197 private Query _getChildrenOrSelfQuery(Collection<String> values, Operator operator) 198 { 199 List<Query> queries = new ArrayList<>(); 200 201 if (_isAndMultipleOperand) 202 { 203 for (String value: values) 204 { 205 Set<String> childrenAndSelf = _getChildrenOrSelf(value); 206 207 List<Query> subQueries = new ArrayList<>(); 208 for (String id : childrenAndSelf) 209 { 210 subQueries.add(_createQueryString(id, operator)); 211 } 212 213 queries.add(new OrQuery(subQueries)); 214 } 215 216 return new AndQuery(queries); 217 } 218 else 219 { 220 Set<String> allChildrenAndSelf = new HashSet<>(); 221 for (String value: values) 222 { 223 allChildrenAndSelf.addAll(_getChildrenOrSelf(value)); 224 } 225 226 List<Query> subQueries = new ArrayList<>(); 227 for (String id : allChildrenAndSelf) 228 { 229 subQueries.add(_createQueryString(id, operator)); 230 } 231 232 return new OrQuery(subQueries); 233 } 234 } 235 236 private Set<String> _getChildrenOrSelf(String id) 237 { 238 Content content = _resolver.resolveById(id); 239 240 Set<String> childrenOrSelf = new HashSet<>(); 241 childrenOrSelf.add(id); 242 if (_contentHelper.isReferenceTable(content) && _refTableHelper.isHierarchical(content)) 243 { 244 childrenOrSelf.addAll(_refTableHelper.getAllChildren(content)); 245 } 246 247 return childrenOrSelf; 248 } 249 250 private Query _createQueryString(Object value, Operator operator) 251 { 252 String fieldPath = getFieldPath(); 253 254 if (value instanceof Collection<?>) 255 { 256 @SuppressWarnings("unchecked") 257 List<String> stringValues = (List<String>) value; 258 259 List<Query> queries = new ArrayList<>(); 260 for (String val : stringValues) 261 { 262 queries.add(new StringQuery(fieldPath, operator, val, null)); 263 } 264 265 return _isAndMultipleOperand ? new AndQuery(queries) : new OrQuery(queries); 266 } 267 else 268 { 269 return new StringQuery(fieldPath, operator, (String) value, null); 270 } 271 } 272 273 /** 274 * Create a subQuery in order to create the join query 275 * @param values the values to add in the query 276 * @param operator the operator 277 * @return The non-null query 278 */ 279 private Query _createRefParentsQuery(Collection<String> values, Operator operator) 280 { 281 List<Query> queries = new ArrayList<>(); 282 for (String val: values) 283 { 284 queries.add(() -> 285 { 286 StringBuilder q = new StringBuilder(); 287 if (Operator.NE == operator) 288 { 289 NotQuery.appendNegation(q).append('('); 290 } 291 q.append(ParentContentSearchField.NAME).append(":").append(ClientUtils.escapeQueryChars(val)); 292 if (Operator.NE == operator) 293 { 294 q.append(')'); 295 } 296 return q.toString(); 297 }); 298 } 299 300 if (queries.size() == 1) 301 { 302 return queries.get(0); 303 } 304 else 305 { 306 return _isAndMultipleOperand ? new AndQuery(queries) : new OrQuery(queries); 307 } 308 } 309 310 @Override 311 public int hashCode() 312 { 313 final int prime = 31; 314 int result = super.hashCode(); 315 result = prime * result + Objects.hash(_isAndMultipleOperand, _operator, _resolveChildren, _value); 316 return result; 317 } 318 319 @Override 320 public boolean equals(Object obj) 321 { 322 if (this == obj) 323 { 324 return true; 325 } 326 if (!super.equals(obj)) 327 { 328 return false; 329 } 330 if (getClass() != obj.getClass()) 331 { 332 return false; 333 } 334 ContentQuery other = (ContentQuery) obj; 335 return _isAndMultipleOperand == other._isAndMultipleOperand && _operator == other._operator && _resolveChildren == other._resolveChildren && Objects.equals(_value, other._value); 336 } 337}