001/* 002 * Copyright 2022 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.List; 019import java.util.Map; 020import java.util.Objects; 021import java.util.Optional; 022 023import org.apache.solr.client.solrj.util.ClientUtils; 024 025/** 026 * Base class for multivalued queries. 027 * @param <T> the value type. 028 */ 029public abstract class AbstractMultivaluedQuery<T> extends AbstractOperatorQuery<List<T>> 030{ 031 private LogicalOperator _logicalOperator; 032 033 /** 034 * Create a new Query 035 * @param fieldName the Solr field name 036 * @param operator the {@link org.ametys.cms.search.query.Query.Operator} 037 * @param logicalOperator the logical operator, default is LogicalOperator.OR 038 * @param values the values 039 */ 040 public AbstractMultivaluedQuery(String fieldName, Operator operator, LogicalOperator logicalOperator, List<T> values) 041 { 042 super(fieldName, operator, values); 043 _logicalOperator = Optional.ofNullable(logicalOperator).orElse(LogicalOperator.OR); 044 045 if (Operator.EQ != operator && Operator.NE != operator && Operator.EXISTS != operator) 046 { 047 throw new IllegalArgumentException("Test operator '" + operator + "' is unknown for test's expression."); 048 } 049 } 050 051 /** 052 * Computes the value for Solr client 053 * @param value the typed value 054 * @return the value, adapted for Solr 055 */ 056 public String singleValueForQuery(T value) 057 { 058 return ClientUtils.escapeQueryChars(value.toString()); 059 } 060 061 @Override 062 public String build() throws QuerySyntaxException 063 { 064 List<T> values = getValue(); 065 StringBuilder query = new StringBuilder(); 066 067 if (getOperator() == Operator.EXISTS) 068 { 069 query.append(getFieldName()).append(":").append(QueryHelper.EXISTS_VALUE); 070 071 return query.toString(); 072 } 073 074 if (getOperator() == Operator.NE) 075 { 076 NotQuery.appendNegation(query); 077 } 078 079 if (values.contains("")) 080 { 081 query.append(getFieldName()).append(":*"); 082 } 083 else 084 { 085 int count = values.size(); 086 if (count > 1) 087 { 088 query.append('('); 089 } 090 091 boolean first = true; 092 for (T v : values) 093 { 094 if (!first) 095 { 096 query.append(" " + _logicalOperator + " "); 097 } 098 099 query.append(getFieldName()).append(':').append(singleValueForQuery(v)); 100 first = false; 101 } 102 103 if (count > 1) 104 { 105 query.append(')'); 106 } 107 } 108 109 return query.toString(); 110 } 111 112 public Optional<Object> buildAsJson() throws QuerySyntaxException 113 { 114 if (getOperator() == Operator.EXISTS) 115 { 116 return Optional.of(getFieldName() + ":" + QueryHelper.EXISTS_VALUE); 117 } 118 119 List<String> clauses = getValue().stream().map(v -> getFieldName() + ":" + singleValueForQuery(v)).toList(); 120 121 if (clauses.isEmpty()) 122 { 123 return new MatchAllQuery().buildAsJson(); 124 } 125 else if (clauses.size() == 1) 126 { 127 return Optional.of(clauses.get(0)); 128 } 129 130 return Optional.of(Map.of("bool", Map.of(getOperator() == Operator.EQ ? "should" : "must_not", clauses))); 131 } 132 133 @Override 134 public int hashCode() 135 { 136 return 31 * super.hashCode() + Objects.hash(_logicalOperator); 137 } 138 139 @Override 140 public boolean equals(Object obj) 141 { 142 return super.equals(obj) && Objects.equals(_logicalOperator, ((AbstractMultivaluedQuery) obj)._logicalOperator); 143 } 144}