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.metamodel.impl; 017 018import java.util.Collections; 019import java.util.HashMap; 020import java.util.List; 021import java.util.Map; 022import java.util.Objects; 023import java.util.Optional; 024import java.util.function.Function; 025import java.util.stream.Collectors; 026import java.util.stream.Stream; 027 028import org.ametys.cms.contenttype.ContentType; 029import org.ametys.cms.search.model.SystemProperty; 030import org.ametys.cms.search.query.Query; 031import org.ametys.cms.search.query.Query.Operator; 032import org.ametys.cms.search.ui.model.SearchUICriterion; 033import org.ametys.cms.tag.CMSTag; 034import org.ametys.cms.tag.Tag; 035import org.ametys.cms.tag.TagProvider; 036import org.ametys.cms.tag.TagProviderExtensionPoint; 037import org.ametys.core.util.JSONUtils; 038import org.ametys.core.util.LambdaUtils; 039import org.ametys.runtime.i18n.I18nizableText; 040import org.ametys.web.frontoffice.search.metamodel.EnumeratedValues; 041import org.ametys.web.frontoffice.search.metamodel.EnumeratedValues.RestrictedValues; 042import org.ametys.web.frontoffice.search.metamodel.Searchable; 043 044import com.google.common.collect.ImmutableMap; 045 046/** 047 * {@link ContentSearchCriterionDefinition} for the 'tag' {@link SystemProperty}. 048 */ 049public class TagSearchCriterionDefinition extends ContentSearchCriterionDefinition 050{ 051 TagProviderExtensionPoint _tagProviderEP; 052 JSONUtils _jsonUtils; 053 054 /** 055 * Default constructor 056 * @param id The id 057 * @param pluginName The plugin name 058 * @param searchable the {@link Searchable} 059 * @param criterion The linked {@link SearchUICriterion} 060 * @param contentType The content type on which this criterion definition applies. Can be empty if it applies to all types of contents. 061 * @param tagProviderEP The extension point for tag providers 062 * @param jsonUtils The JSON utils 063 */ 064 public TagSearchCriterionDefinition( 065 String id, 066 String pluginName, 067 Optional<Searchable> searchable, 068 SearchUICriterion criterion, 069 Optional<ContentType> contentType, 070 TagProviderExtensionPoint tagProviderEP, 071 JSONUtils jsonUtils) 072 { 073 super(id, pluginName, searchable, criterion, contentType, Optional.empty()); 074 _tagProviderEP = tagProviderEP; 075 _jsonUtils = jsonUtils; 076 setWidget(_widget()); 077 setWidgetParameters(_widgetParameters(criterion)); 078 } 079 080 private static String _widget() 081 { 082 return "edition.select-tags-criterion"; 083 } 084 085 private static Map<String, I18nizableText> _widgetParameters(SearchUICriterion criterion) 086 { 087 Map<String, I18nizableText> widgetParameters = new HashMap<>(criterion.getWidgetParameters()); 088// widgetParameters.put("onlyTagsWithChildren", new I18nizableText(Boolean.TRUE.toString())); // we would like this conf only for USER_RESTRICTED_VALUES 089 return widgetParameters; 090 } 091 092 @Override 093 public boolean isEnumerated() 094 { 095 return true; 096 } 097 098 @Override 099 public Optional<EnumeratedValues> getEnumeratedValues(Map<String, Object> contextualParameters) 100 { 101 return Optional.of(new TagEnumeratedValues(this, contextualParameters)); 102 } 103 104 @Override 105 public Query getQuery(Object value, Operator operator, String language, Map<String, Object> contextualParameters) 106 { 107 Object modifiedValue = _tryToParseJson(value); 108 109 if (modifiedValue instanceof List<?> && !((List<?>) modifiedValue).isEmpty()) 110 { 111 modifiedValue = ((List<?>) modifiedValue).stream() 112 .map(this::_tryToParseJson) 113 .collect(Collectors.toList()); 114 } 115 116 if (modifiedValue instanceof List<?> && !((List<?>) modifiedValue).isEmpty()) 117 { 118 List<?> valueAsList = (List<?>) modifiedValue; 119 Object firstValue = valueAsList.get(0); 120 if (firstValue instanceof Map<?, ?>) 121 { 122 Boolean autoposting = (Boolean) ((Map<?, ?>) firstValue).get("autoposting"); 123 String[] realValues = valueAsList 124 .stream() 125 .filter(Map.class::isInstance) 126 .map(Map.class::cast) 127 .map(map -> map.get("value")) 128 .filter(String.class::isInstance) 129 .map(String.class::cast) 130 .toArray(String[]::new); 131 modifiedValue = ImmutableMap.of("autoposting", autoposting, "value", realValues); 132 } 133 } 134 135 return super.getQuery(modifiedValue, operator, language, contextualParameters); 136 } 137 138 private Object _tryToParseJson(Object value) 139 { 140 if (value instanceof String) 141 { 142 try 143 { 144 return _jsonUtils.convertJsonToMap((String) value); 145 } 146 catch (Exception e) 147 { 148 // was not json 149 } 150 } 151 return value; 152 } 153 154 static class TagEnumeratedValues implements EnumeratedValues 155 { 156 TagSearchCriterionDefinition _tagSearchCriterionDef; 157 Map<String, Object> _contextualParameters; 158 159 TagEnumeratedValues(TagSearchCriterionDefinition tagSearchCriterionDef, Map<String, Object> contextualParameters) 160 { 161 _tagSearchCriterionDef = tagSearchCriterionDef; 162 _contextualParameters = contextualParameters; 163 } 164 165 @Override 166 public Map<Object, I18nizableText> getAllValues() 167 { 168 Map<Object, I18nizableText> tags = _tagSearchCriterionDef._tagProviderEP 169 .getExtensionsIds() 170 .stream() 171 .map(_tagSearchCriterionDef._tagProviderEP::getExtension) 172 .map(this::_getAllTags) 173 .flatMap(Function.identity()) 174 .filter(this::_isTargetingContents) 175 .collect(LambdaUtils.Collectors.toLinkedHashMap( 176 Tag::getName, 177 Tag::getTitle)); 178 return tags; 179 } 180 181 private Stream<Tag> _getAllTags(TagProvider<? extends Tag> tagProvider) 182 { 183 return Optional.ofNullable(tagProvider.getTags(_contextualParameters)) 184 .orElseGet(Collections::emptyMap) 185 .values() 186 .stream() 187 .map(this::_getDescendantAndSelfTags) 188 .flatMap(Function.identity()); 189 } 190 191 private Stream<Tag> _getDescendantAndSelfTags(Tag parentTag) 192 { 193 return Stream.concat( 194 Stream.of(parentTag), 195 _getDescendantTags(parentTag)); 196 } 197 198 Stream<Tag> _getDescendantTags(Tag parentTag) 199 { 200 return _getChildrenTags(parentTag) 201 .map(this::_getDescendantAndSelfTags) 202 .flatMap(Function.identity()); 203 } 204 205 private Stream<? extends Tag> _getChildrenTags(Tag parentTag) 206 { 207 return parentTag == null 208 ? Stream.empty() 209 : parentTag.getTags() 210 .values() 211 .stream(); 212 } 213 214 private boolean _isTargetingContents(Tag tag) 215 { 216 return tag instanceof CMSTag && "CONTENT".equals(((CMSTag) tag).getTarget().getName()); 217 } 218 219 @Override 220 public RestrictedValues getRestrictedValuesFor(List<Object> objs) 221 { 222 return new TagRestrictedValues(this, objs); 223 } 224 } 225 226 static class TagRestrictedValues implements RestrictedValues 227 { 228 private TagEnumeratedValues _enumeratedValues; 229 private List<Object> _objs; 230 231 TagRestrictedValues(TagEnumeratedValues enumeratedValues, List<Object> objs) 232 { 233 _enumeratedValues = enumeratedValues; 234 _objs = objs; 235 } 236 237 @Override 238 public Map<Object, I18nizableText> values() 239 { 240 Boolean autoposting; 241 if (!_objs.isEmpty()) 242 { 243 autoposting = _objectToAutoposting(_objs.get(0)); 244 } 245 else 246 { 247 autoposting = Boolean.FALSE; 248 } 249 250 Map<Object, I18nizableText> restrictedTags = _objs.stream() 251 .map(this::_objectToTagId) 252 .filter(Objects::nonNull) 253 .map(this::_getRestrictedValuesFor) 254 .flatMap(Function.identity()) 255 .collect(LambdaUtils.Collectors.toLinkedHashMap( 256 tag -> _tagToSerializedJson(tag, autoposting), 257 Tag::getTitle)); 258 return restrictedTags; 259 } 260 261 private Boolean _objectToAutoposting(Object obj) 262 { 263 if (obj instanceof Map<?, ?>) 264 { 265 return (Boolean) ((Map<?, ?>) obj).get("autoposting"); 266 } 267 return Boolean.FALSE; 268 } 269 270 private String _objectToTagId(Object obj) 271 { 272 if (obj instanceof Map<?, ?>) 273 { 274 return (String) ((Map<?, ?>) obj).get("value"); 275 } 276 else if (obj instanceof String) 277 { 278 return (String) obj; 279 } 280 return null; 281 } 282 283 private Stream<Tag> _getRestrictedValuesFor(String tagName) 284 { 285 Tag tag = _enumeratedValues._tagSearchCriterionDef._tagProviderEP.getTag(tagName, _enumeratedValues._contextualParameters); 286 // Gets its descendants 287 return tag == null ? Stream.empty() : _enumeratedValues._getDescendantTags(tag); 288 } 289 290 private String _tagToSerializedJson(Tag tag, Boolean autoposting) 291 { 292 Map<String, Object> tagAsJson = _tagToJson(tag, autoposting); 293 return _enumeratedValues._tagSearchCriterionDef._jsonUtils.convertObjectToJson(tagAsJson); 294 } 295 296 private Map<String, Object> _tagToJson(Tag tag, Boolean autoposting) 297 { 298 return ImmutableMap.of( 299 "value", tag.getName(), 300 "autoposting", autoposting); 301 } 302 } 303} 304