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 int hashCode()
146    {
147        final int prime = 31;
148        int result = 1;
149        result = prime * result + ((_joinKeys == null) ? 0 : _joinKeys.hashCode());
150        result = prime * result + ((_subQuery == null) ? 0 : _subQuery.hashCode());
151        return result;
152    }
153
154    @Override
155    public boolean equals(Object obj)
156    {
157        if (this == obj)
158        {
159            return true;
160        }
161        if (obj == null)
162        {
163            return false;
164        }
165        if (getClass() != obj.getClass())
166        {
167            return false;
168        }
169        JoinQuery other = (JoinQuery) obj;
170        if (_joinKeys == null)
171        {
172            if (other._joinKeys != null)
173            {
174                return false;
175            }
176        }
177        else if (!_joinKeys.equals(other._joinKeys))
178        {
179            return false;
180        }
181        if (_subQuery == null)
182        {
183            if (other._subQuery != null)
184            {
185                return false;
186            }
187        }
188        else if (!_subQuery.equals(other._subQuery))
189        {
190            return false;
191        }
192        return true;
193    }
194}