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) 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 (_value instanceof Map<?, ?>) 140 { 141 Map<String, Object> mapValues = (Map<String, Object>) this._value; 142 Object values = mapValues.get("value"); 143 boolean autoposting = (boolean) mapValues.get("autoposting"); 144 if (autoposting) 145 { 146 if (_resolveChildren) 147 { 148 Query query = null; 149 if (values instanceof Collection<?>) 150 { 151 query = _getChildrenOrSelfQuery((Collection<String>) values, _operator); 152 } 153 else 154 { 155 query = _getChildrenOrSelfQuery(Collections.singletonList((String) values), _operator); 156 } 157 158 return query.build(); 159 } 160 else 161 { 162 Query subQuery = null; 163 if (values instanceof Collection<?>) 164 { 165 subQuery = _createRefParentsQuery((Collection<String>) values, _operator); 166 } 167 else 168 { 169 subQuery = _createRefParentsQuery(Collections.singletonList((String) values), _operator); 170 } 171 172 Query joinQuery = new JoinQuery(subQuery, _fieldPath); 173 return joinQuery.build(); 174 } 175 176 } 177 else 178 { 179 Query query = _createQueryString(values, _operator); 180 return query.build(); 181 } 182 } 183 else 184 { 185 Query query = _createQueryString(_value, _operator); 186 return query.build(); 187 } 188 } 189 190 private Query _getChildrenOrSelfQuery(Collection<String> values, Operator operator) 191 { 192 List<Query> queries = new ArrayList<>(); 193 194 if (_isAndMultipleOperand) 195 { 196 for (String value: values) 197 { 198 Set<String> childrenAndSelf = _getChildrenOrSelf(value); 199 200 List<Query> subQueries = new ArrayList<>(); 201 for (String id : childrenAndSelf) 202 { 203 subQueries.add(_createQueryString(id, operator)); 204 } 205 206 queries.add(new OrQuery(subQueries)); 207 } 208 209 return new AndQuery(queries); 210 } 211 else 212 { 213 Set<String> allChildrenAndSelf = new HashSet<>(); 214 for (String value: values) 215 { 216 allChildrenAndSelf.addAll(_getChildrenOrSelf(value)); 217 } 218 219 List<Query> subQueries = new ArrayList<>(); 220 for (String id : allChildrenAndSelf) 221 { 222 subQueries.add(_createQueryString(id, operator)); 223 } 224 225 return new OrQuery(subQueries); 226 } 227 } 228 229 private Set<String> _getChildrenOrSelf(String id) 230 { 231 Content content = _resolver.resolveById(id); 232 233 Set<String> childrenOrSelf = new HashSet<>(); 234 childrenOrSelf.add(id); 235 if (_contentHelper.isReferenceTable(content) && _refTableHelper.isHierarchical(content)) 236 { 237 childrenOrSelf.addAll(_refTableHelper.getAllChildren(content)); 238 } 239 240 return childrenOrSelf; 241 } 242 243 private Query _createQueryString(Object value, Operator operator) 244 { 245 String fieldPath = getFieldPath(); 246 247 if (value instanceof Collection<?>) 248 { 249 @SuppressWarnings("unchecked") 250 List<String> stringValues = (List<String>) value; 251 252 List<Query> queries = new ArrayList<>(); 253 for (String val : stringValues) 254 { 255 queries.add(new StringQuery(fieldPath, operator, val, null)); 256 } 257 258 return _isAndMultipleOperand ? new AndQuery(queries) : new OrQuery(queries); 259 } 260 else 261 { 262 return new StringQuery(fieldPath, operator, (String) value, null); 263 } 264 } 265 266 /** 267 * Create a subQuery in order to create the join query 268 * @param values the values to add in the query 269 * @param operator the operator 270 * @return The non-null query 271 */ 272 private Query _createRefParentsQuery(Collection<String> values, Operator operator) 273 { 274 List<Query> queries = new ArrayList<>(); 275 for (String val: values) 276 { 277 queries.add(() -> 278 { 279 StringBuilder q = new StringBuilder(); 280 if (Operator.NE == operator) 281 { 282 NotQuery.appendNegation(q).append('('); 283 } 284 q.append(ParentContentSearchField.NAME).append(":").append(ClientUtils.escapeQueryChars(val)); 285 if (Operator.NE == operator) 286 { 287 q.append(')'); 288 } 289 return q.toString(); 290 }); 291 } 292 293 if (queries.size() == 1) 294 { 295 return queries.get(0); 296 } 297 else 298 { 299 return _isAndMultipleOperand ? new AndQuery(queries) : new OrQuery(queries); 300 } 301 } 302 303 @Override 304 public int hashCode() 305 { 306 final int prime = 31; 307 int result = super.hashCode(); 308 result = prime * result + Objects.hash(_isAndMultipleOperand, _operator, _resolveChildren, _value); 309 return result; 310 } 311 312 @Override 313 public boolean equals(Object obj) 314 { 315 if (this == obj) 316 { 317 return true; 318 } 319 if (!super.equals(obj)) 320 { 321 return false; 322 } 323 if (getClass() != obj.getClass()) 324 { 325 return false; 326 } 327 ContentQuery other = (ContentQuery) obj; 328 return _isAndMultipleOperand == other._isAndMultipleOperand && _operator == other._operator && _resolveChildren == other._resolveChildren && Objects.equals(_value, other._value); 329 } 330}