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.Set; 025 026import org.apache.solr.client.solrj.util.ClientUtils; 027 028import org.ametys.cms.content.ContentHelper; 029import org.ametys.cms.content.referencetable.HierarchicalReferenceTablesHelper; 030import org.ametys.cms.content.referencetable.search.ParentContentSearchField; 031import org.ametys.cms.repository.Content; 032import org.ametys.plugins.repository.AmetysObjectResolver; 033 034/** 035 * Represents a {@link Query} testing a content field. 036 */ 037public class ContentQuery extends AbstractFieldQuery 038{ 039 /** The value to test. */ 040 protected Object _value; 041 /** The resolver to resolve the results. */ 042 protected AmetysObjectResolver _resolver; 043 /** The extension point. */ 044 protected ContentHelper _contentHelper; 045 /** <code>true</code> if the query should be executed with an AndQuery. */ 046 protected boolean _isAndMultipleOperand; 047 /** The helper to resolve the reference tables. */ 048 protected HierarchicalReferenceTablesHelper _refTableHelper; 049 /** <code>true</code> if the query should be executed by resolving the children. */ 050 protected boolean _resolveChildren; 051 052 /** 053 * Build a content query. 054 * @param fieldPath the field's path 055 * @param value the value 056 * @param resolver the resolver 057 * @param contentHelper the content helper 058 */ 059 public ContentQuery(String fieldPath, Object value, AmetysObjectResolver resolver, ContentHelper contentHelper) 060 { 061 062 this(fieldPath, value, resolver, contentHelper, false); 063 } 064 065 /** 066 * Build a content query by testing the operand. 067 * @param fieldPath the field's path 068 * @param value the value 069 * @param resolver the resolver 070 * @param contentHelper the content helper 071 * @param isAndMultipleOperand 'true' to execute an AndQuery, OrQuery otherwise 072 */ 073 public ContentQuery(String fieldPath, Object value, AmetysObjectResolver resolver, ContentHelper contentHelper, boolean isAndMultipleOperand) 074 { 075 this(fieldPath, value, resolver, contentHelper, null, false, isAndMultipleOperand); 076 } 077 078 /** 079 * Build a content query by testing if the ancestors should be resolved. 080 * @param fieldPath the field's path 081 * @param value the value 082 * @param resolver the resolver 083 * @param contentHelper the content helper 084 * @param refTableHelper The helper to resolve the ancestors 085 * @param resolveChildren 'true' to execute an OrQuery on the ancestors and itself, 'false' will execute a join request 086 */ 087 public ContentQuery(String fieldPath, Object value, AmetysObjectResolver resolver, ContentHelper contentHelper, HierarchicalReferenceTablesHelper refTableHelper, boolean resolveChildren) 088 { 089 this(fieldPath, value, resolver, contentHelper, refTableHelper, resolveChildren, false); 090 } 091 092 /** 093 * Build a content query by testing the operand and if the ancestors should be resolved. 094 * @param fieldPath the field's path 095 * @param value the value 096 * @param resolver the resolver 097 * @param contentHelper the content helper 098 * @param refTableHelper The helper to resolve the ancestors 099 * @param resolveChildren 'true' to execute an OrQuery on the ancestors and itself, 'false' will execute a join request 100 * @param isAndMultipleOperand 'true' to execute an AndQuery, OrQuery otherwise 101 */ 102 public ContentQuery(String fieldPath, Object value, AmetysObjectResolver resolver, ContentHelper contentHelper, HierarchicalReferenceTablesHelper refTableHelper, boolean resolveChildren, boolean isAndMultipleOperand) 103 { 104 super(fieldPath); 105 this._value = value; 106 this._resolver = resolver; 107 this._contentHelper = contentHelper; 108 this._refTableHelper = refTableHelper; 109 this._resolveChildren = resolveChildren; 110 this._isAndMultipleOperand = isAndMultipleOperand; 111 } 112 113 114 /** 115 * Get the value. 116 * @return the value 117 */ 118 public Object getValue() 119 { 120 return this._value; 121 } 122 123 @SuppressWarnings("unchecked") 124 public String build() throws QuerySyntaxException 125 { 126 if (_value instanceof Map<?, ?>) 127 { 128 Map<String, Object> mapValues = (Map<String, Object>) this._value; 129 Object values = mapValues.get("value"); 130 boolean autoposting = (boolean) mapValues.get("autoposting"); 131 if (autoposting) 132 { 133 if (_resolveChildren) 134 { 135 Query query = null; 136 if (values instanceof Collection<?>) 137 { 138 query = _getChildrenOrSelfQuery((Collection<String>) values); 139 } 140 else 141 { 142 query = _getChildrenOrSelfQuery(Collections.singletonList((String) values)); 143 } 144 145 return query.build(); 146 } 147 else 148 { 149 Query subQuery = null; 150 if (values instanceof Collection<?>) 151 { 152 subQuery = _createRefParentsQuery((Collection<String>) values); 153 } 154 else 155 { 156 subQuery = _createRefParentsQuery(Collections.singletonList((String) values)); 157 } 158 159 Query joinQuery = new JoinQuery(subQuery, _fieldPath); 160 return joinQuery.build(); 161 } 162 163 } 164 else 165 { 166 Query query = _createQueryString(values); 167 return query.build(); 168 } 169 } 170 else 171 { 172 Query query = _createQueryString(_value); 173 return query.build(); 174 } 175 } 176 177 private Query _getChildrenOrSelfQuery(Collection<String> values) 178 { 179 List<Query> queries = new ArrayList<>(); 180 181 if (_isAndMultipleOperand) 182 { 183 for (String value: values) 184 { 185 Set<String> childrenAndSelf = _getChildrenOrSelf(value); 186 187 List<Query> subQueries = new ArrayList<>(); 188 for (String id : childrenAndSelf) 189 { 190 subQueries.add(_createQueryString(id)); 191 } 192 193 queries.add(new OrQuery(subQueries)); 194 } 195 196 return new AndQuery(queries); 197 } 198 else 199 { 200 Set<String> allChildrenAndSelf = new HashSet<>(); 201 for (String value: values) 202 { 203 allChildrenAndSelf.addAll(_getChildrenOrSelf(value)); 204 } 205 206 List<Query> subQueries = new ArrayList<>(); 207 for (String id : allChildrenAndSelf) 208 { 209 subQueries.add(_createQueryString(id)); 210 } 211 212 return new OrQuery(subQueries); 213 } 214 } 215 216 private Set<String> _getChildrenOrSelf(String id) 217 { 218 Content content = _resolver.resolveById(id); 219 220 Set<String> childrenOrSelf = new HashSet<>(); 221 childrenOrSelf.add(id); 222 if (_contentHelper.isReferenceTable(content) && _refTableHelper.isHierarchical(content)) 223 { 224 childrenOrSelf.addAll(_refTableHelper.getAllChildren(content)); 225 } 226 227 return childrenOrSelf; 228 } 229 230 private Query _createQueryString(Object value) 231 { 232 String fieldPath = getFieldPath(); 233 234 if (value instanceof Collection<?>) 235 { 236 @SuppressWarnings("unchecked") 237 List<String> stringValues = (List<String>) value; 238 239 List<Query> queries = new ArrayList<>(); 240 for (String val : stringValues) 241 { 242 queries.add(new StringQuery(fieldPath, val)); 243 } 244 245 return _isAndMultipleOperand ? new AndQuery(queries) : new OrQuery(queries); 246 } 247 else 248 { 249 return new StringQuery(fieldPath, (String) value); 250 } 251 } 252 253 /** 254 * Create a subQuery in order to create the join query 255 * @param values the values to add in the query 256 * @return The non-null query 257 */ 258 private Query _createRefParentsQuery(Collection<String> values) 259 { 260 List<Query> queries = new ArrayList<>(); 261 for (String val: values) 262 { 263 queries.add(() -> ParentContentSearchField.NAME + ":" + ClientUtils.escapeQueryChars(val)); 264 } 265 266 if (queries.size() == 1) 267 { 268 return queries.get(0); 269 } 270 else 271 { 272 return _isAndMultipleOperand ? new AndQuery(queries) : new OrQuery(queries); 273 } 274 } 275 276 @Override 277 public int hashCode() 278 { 279 final int prime = 31; 280 int result = super.hashCode(); 281 result = prime * result + (_isAndMultipleOperand ? 1231 : 1237); 282 result = prime * result + (_resolveChildren ? 1231 : 1237); 283 result = prime * result + ((_value == null) ? 0 : _value.hashCode()); 284 return result; 285 } 286 287 @Override 288 public boolean equals(Object obj) 289 { 290 if (this == obj) 291 { 292 return true; 293 } 294 if (!super.equals(obj)) 295 { 296 return false; 297 } 298 if (getClass() != obj.getClass()) 299 { 300 return false; 301 } 302 ContentQuery other = (ContentQuery) obj; 303 if (_isAndMultipleOperand != other._isAndMultipleOperand) 304 { 305 return false; 306 } 307 if (_resolveChildren != other._resolveChildren) 308 { 309 return false; 310 } 311 if (_value == null) 312 { 313 if (other._value != null) 314 { 315 return false; 316 } 317 } 318 else if (!_value.equals(other._value)) 319 { 320 return false; 321 } 322 return true; 323 } 324}