001/* 002 * Copyright 2019 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.web.frontoffice.search.instance.model; 017 018import java.util.List; 019import java.util.Map; 020import java.util.Optional; 021 022import org.slf4j.Logger; 023import org.slf4j.LoggerFactory; 024 025import org.ametys.cms.search.advanced.WrappedValue; 026import org.ametys.cms.search.model.CriterionDefinition; 027import org.ametys.web.frontoffice.search.metamodel.SearchServiceCriterionDefinition; 028 029/** 030 * The mode of a {@link SearchServiceCriterion} 031 */ 032public enum SearchServiceCriterionMode 033{ 034 /** 035 * The criterion is static, i.e. valued by the contributor and is not a proposed criterion to the final user. 036 */ 037 STATIC 038 { 039 @Override 040 public <T> CriterionWrappedValue getValue(SearchServiceCriterion<T> criterion, Map<String, Object> userCriteria, Map<String, Object> contextualParameters) 041 { 042 Object staticValue = criterion.getStaticValue().get(); 043 Object convertedValue = criterion.getCriterionDefinition().convertQueryValue(staticValue, contextualParameters); 044 CriterionWrappedValue result = new CriterionWrappedValue(convertedValue); 045 __LOGGER.debug("Value for static criterion '{}': {}", criterion.getName(), result); 046 return result; 047 } 048 }, 049 050 /** 051 * The criterion is proposed to the final user. 052 */ 053 USER_INPUT 054 { 055 @Override 056 public <T> CriterionWrappedValue getValue(SearchServiceCriterion<T> criterion, Map<String, Object> userCriteria, Map<String, Object> contextualParameters) 057 { 058 String criterionId = criterion.getName(); 059 Object val = userCriteria.get(criterionId); 060 SearchServiceCriterionDefinition criterionDefinition = criterion.getCriterionDefinition(); 061 if (val != null) 062 { 063 boolean hasValue = !val.equals(NONE_VALUE); 064 boolean requestEmptyValue = !hasValue 065 && !_isMandatory(criterion) 066 && criterionDefinition.isEnumerated(); 067 068 Object convertedValue = criterion.getCriterionDefinition().convertQueryValue(val, contextualParameters); 069 CriterionWrappedValue result = new CriterionWrappedValue(convertedValue, hasValue, requestEmptyValue); 070 __LOGGER.debug("Value from user for criterion '{}': {}", criterionId, result); 071 return result; 072 } 073 074 // val is null 075 // no need to build a collection of all existing entries of the enumerated values as all are allowed, and empty is allowed too => avoid an unnecessary query on the indexed field 076 __LOGGER.debug("Value from user for criterion '{}': null", criterionId); 077 return new CriterionWrappedValue(null); 078 } 079 }, 080 081 /** 082 * The criterion is proposed to the final user, with a restricted enumeration of choices. 083 */ 084 RESTRICTED_USER_INPUT 085 { 086 @Override 087 public <T> CriterionWrappedValue getValue(SearchServiceCriterion<T> criterion, Map<String, Object> userCriteria, Map<String, Object> contextualParameters) 088 { 089 String criterionId = criterion.getName(); 090 Object val = userCriteria.get(criterionId); 091 if (val != null) 092 { 093 boolean hasValue = !val.equals(NONE_VALUE); 094 boolean requestEmptyValue = !hasValue && !_isMandatory(criterion); 095 Object convertedValue = criterion.getCriterionDefinition().convertQueryValue(val, contextualParameters); 096 CriterionWrappedValue result = new CriterionWrappedValue(convertedValue, hasValue, requestEmptyValue); 097 __LOGGER.debug("Value from user for (restricted) criterion '{}': {}", criterionId, result); 098 return result; 099 } 100 else 101 { 102 try 103 { 104 // The criterion was not filled by the visitor 105 // Consider it as the 'All' option to filter results matching with at least one value in the restricted values 106 List<T> vals = criterion 107 .getRestrictedValues() 108 .get() 109 .values() 110 .keySet() 111 .stream() 112 .toList(); 113 114 boolean requestEmptyValue = !_isMandatory(criterion); 115 Object convertedValue = criterion.getCriterionDefinition().convertQueryValue(vals, contextualParameters); 116 CriterionWrappedValue result = new CriterionWrappedValue(convertedValue, true, requestEmptyValue); 117 __LOGGER.debug("Value for criterion '{}' (computed because not filled by user but is restricted): {}", criterionId, result); 118 return result; 119 } 120 catch (Exception e) 121 { 122 // An error occurred while retrieving restricted values 123 throw new IllegalStateException("An unexpected error occured. Unable to compute restricted values for criterion '" + criterionId + "'", e); 124 } 125 } 126 } 127 }, 128 129 /** 130 * The criterion is proposed for content profiled by user group tag. 131 */ 132 PROFILED_GROUPS_TAGS_INPUT 133 { 134 @Override 135 public <T> CriterionWrappedValue getValue(SearchServiceCriterion<T> criterion, Map<String, Object> userCriteria, Map<String, Object> contextualParameters) 136 { 137 return new CriterionWrappedValue(PROFILED_GROUPS_TAGS_VALUE); 138 } 139 }; 140 141 /** The none value */ 142 public static final String NONE_VALUE = "__ametys_none"; 143 144 /** The profiled groups tags value */ 145 public static final String PROFILED_GROUPS_TAGS_VALUE = "__ametys_profiled_groups_tags"; 146 147 static final Logger __LOGGER = LoggerFactory.getLogger(SearchServiceCriterionMode.class); 148 149 /** 150 * Gets the value from the {@link SearchServiceCriterion criterion} and the final user criteria. 151 * @param criterion The {@link SearchServiceCriterion} which is in this mode 152 * @param finalUserCriteria The criteria from the final user 153 * @param contextualParameters The contextual parameters 154 * @return the value 155 * @param <T> Type of the criterion value 156 */ 157 public abstract <T> CriterionWrappedValue getValue(SearchServiceCriterion<T> criterion, Map<String, Object> finalUserCriteria, Map<String, Object> contextualParameters); 158 159 /** 160 * <code>true</code> if the criterion is static 161 * @return <code>true</code> if the criterion is static 162 */ 163 public boolean isStatic() 164 { 165 return this.equals(STATIC) || this.equals(PROFILED_GROUPS_TAGS_INPUT); 166 } 167 168 private static boolean _isMandatory(SearchServiceCriterion criterion) 169 { 170 return Optional.of(criterion.getCriterionDefinition()) 171 .map(CriterionDefinition::getValidator) 172 .map(validator -> validator.getConfiguration().containsKey("mandatory") && (Boolean) validator.getConfiguration().get("mandatory")) 173 .orElse(false); 174 } 175 176 /** 177 * A {@link WrappedValue}, being the return type of {@link SearchServiceCriterionMode#getValue}. It can notify for enumerated criteria if empty value need to be requested, through {@link #requestEmptyValue()} 178 */ 179 public static final class CriterionWrappedValue extends WrappedValue 180 { 181 private final boolean _hasValue; 182 private final boolean _requestEmptyValue; 183 184 private CriterionWrappedValue(Object value) 185 { 186 this(value, true, false); 187 } 188 189 private CriterionWrappedValue(Object values, boolean hasValue, boolean requestEmptyValue) 190 { 191 super(values); 192 _hasValue = hasValue; 193 _requestEmptyValue = requestEmptyValue; 194 } 195 196 /** 197 * Determines if an empty value request is necessary 198 * An empty value request is necessary if the wrapped value is from enumerated and not mandatory data, or if the user has selected none or all values for restricted user input. 199 * @return <code>true</code> if an empty value request is necessary, <code>false</code> otherwise 200 */ 201 public boolean requestEmptyValue() 202 { 203 return _requestEmptyValue; 204 } 205 206 /** 207 * Determines if a real value (not the none one) has been selected 208 * @return <code>true</code> if a value has been selected, <code>false</code> otherwise 209 */ 210 public boolean hasValue() 211 { 212 return _hasValue; 213 } 214 215 @Override 216 public String toString() 217 { 218 return super.toString() + "{_hasValue:" + _hasValue + ", requestEmptyValue:" + _requestEmptyValue + "}"; 219 } 220 } 221}