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}