001/*
002 *  Copyright 2016 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.Arrays;
019import java.util.Collection;
020import java.util.stream.Collectors;
021
022import org.apache.commons.lang3.StringUtils;
023import org.apache.solr.client.solrj.util.ClientUtils;
024
025import org.ametys.cms.content.indexing.solr.SolrFieldHelper;
026import org.ametys.cms.search.query.join.JoinKey;
027import org.ametys.cms.search.solr.schema.SchemaHelper;
028import org.ametys.core.util.LambdaUtils;
029
030/**
031 * Represents a {@link Query} on a joined document.
032 */
033public class JoinQuery implements Query
034{
035    
036    /** The field path. */
037    protected Query _subQuery;
038    
039    /** The join keys (paths and optional nested queries) */
040    protected Collection<JoinKey> _joinKeys;
041    
042    /**
043     * Build a join query.
044     * @param subQuery The sub query.
045     * @param joinPaths The field's join paths
046     */
047    public JoinQuery(Query subQuery, String... joinPaths)
048    {
049        this(subQuery, Arrays.asList(joinPaths));
050    }
051    
052    /**
053     * Build a join query.
054     * @param subQuery The sub query.
055     * @param joinPaths The field's join paths
056     */
057    public JoinQuery(Query subQuery, Collection<String> joinPaths)
058    {
059        this(joinPaths.stream().map(jp -> new JoinKey(jp, null)).collect(Collectors.toList()));
060        _subQuery = subQuery;
061    }
062    
063    /**
064     * Build a join query.
065     * @param joinKeys The join paths and optional nested queries
066     */
067    public JoinQuery(JoinKey... joinKeys)
068    {
069        this(Arrays.asList(joinKeys));
070    }
071    
072    /**
073     * Build a join query.
074     * @param joinKeys The join paths and optional nested queries
075     */
076    public JoinQuery(Collection<JoinKey> joinKeys)
077    {
078        _joinKeys = joinKeys;
079        _checkValidPathNames();
080        _checkJoinParams();
081    }
082    
083    private void _checkValidPathNames()
084    {
085        for (JoinKey joinKey : _joinKeys)
086        {
087            String path = joinKey.getKey();
088            if (!SchemaHelper.isNameValid(path))
089            {
090                throw new IllegalArgumentException("Invalid path name '" + path + "' in join.");
091            }
092        }
093    }
094    
095    private void _checkJoinParams()
096    {
097        if (_subQuery == null && _joinKeys.isEmpty())
098        {
099            throw new IllegalArgumentException("The join path is empty and there is no subquery in JoinQuery.");
100        }
101    }
102    
103    @Override
104    public String build() throws QuerySyntaxException
105    {
106        StringBuilder queryString = new StringBuilder();
107        
108        queryString.append("{!ametys join=\"");
109        boolean first = true;
110        for (JoinKey joinKey : _joinKeys)
111        {
112            if (!first)
113            {
114                queryString.append("->");
115            }
116            first = false;
117            String path = joinKey.getKey();
118            queryString.append(SolrFieldHelper.getJoinFieldName(path));
119            joinKey.getNestedQuery()
120                   .map(LambdaUtils.wrap(this::_buildQuery))
121                   .filter(StringUtils::isNotBlank)
122                   .ifPresent(nq -> queryString.append('[').append(nq).append(']'));
123        }
124        queryString.append('"');
125        
126        if (_subQuery != null)
127        {
128            String subQuery = _buildQuery(_subQuery);
129            queryString.append(" q=\"")
130                       .append(subQuery)
131                       .append('"');
132        }
133        
134        queryString.append('}');
135        
136        return queryString.toString();
137    }
138    
139    private String _buildQuery(Query query) throws QuerySyntaxException
140    {
141        return ClientUtils.escapeQueryChars(query.build());
142    }
143    
144    @Override
145    public String toString(int indent)
146    {
147        final String joinLineIndent = StringUtils.repeat(' ', indent);
148        final int subIndent = indent + 2;
149        final String subLineIndent = StringUtils.repeat(' ', subIndent);
150        final String join = subLineIndent + "[PATH]" + _joinKeys
151                .stream()
152                .map(JoinKey::toString)
153                .collect(Collectors.joining("->"))
154            + "[/PATH]";
155        final String subq = subLineIndent + "[Q]" + (_subQuery == null ? String.valueOf(_subQuery) : ("\n" + _subQuery.toString(subIndent + 2) + "\n" + subLineIndent)) + "[/Q]";
156        return joinLineIndent + "[JOIN]\n" + join + "\n" + subq + "\n" + joinLineIndent + "[/JOIN]";
157    }
158
159    @Override
160    public int hashCode()
161    {
162        final int prime = 31;
163        int result = 1;
164        result = prime * result + ((_joinKeys == null) ? 0 : _joinKeys.hashCode());
165        result = prime * result + ((_subQuery == null) ? 0 : _subQuery.hashCode());
166        return result;
167    }
168
169    @Override
170    public boolean equals(Object obj)
171    {
172        if (this == obj)
173        {
174            return true;
175        }
176        if (obj == null)
177        {
178            return false;
179        }
180        if (getClass() != obj.getClass())
181        {
182            return false;
183        }
184        JoinQuery other = (JoinQuery) obj;
185        if (_joinKeys == null)
186        {
187            if (other._joinKeys != null)
188            {
189                return false;
190            }
191        }
192        else if (!_joinKeys.equals(other._joinKeys))
193        {
194            return false;
195        }
196        if (_subQuery == null)
197        {
198            if (other._subQuery != null)
199            {
200                return false;
201            }
202        }
203        else if (!_subQuery.equals(other._subQuery))
204        {
205            return false;
206        }
207        return true;
208    }
209}