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}