001/* 002 * Copyright 2015 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.systemprop; 017 018import java.util.ArrayList; 019import java.util.Collection; 020import java.util.Collections; 021import java.util.HashMap; 022import java.util.HashSet; 023import java.util.List; 024import java.util.Map; 025import java.util.Optional; 026import java.util.Set; 027 028import org.apache.avalon.framework.configuration.Configuration; 029import org.apache.avalon.framework.service.ServiceException; 030import org.apache.avalon.framework.service.ServiceManager; 031import org.apache.cocoon.xml.AttributesImpl; 032import org.apache.cocoon.xml.XMLUtils; 033import org.apache.commons.lang.BooleanUtils; 034import org.apache.solr.common.SolrInputDocument; 035import org.xml.sax.ContentHandler; 036import org.xml.sax.SAXException; 037 038import org.ametys.cms.content.indexing.solr.SolrFieldNames; 039import org.ametys.cms.data.type.indexing.IndexableElementTypeHelper; 040import org.ametys.cms.repository.Content; 041import org.ametys.cms.search.SearchField; 042import org.ametys.cms.search.model.SystemProperty; 043import org.ametys.cms.search.query.Query; 044import org.ametys.cms.search.query.Query.LogicalOperator; 045import org.ametys.cms.search.query.Query.Operator; 046import org.ametys.cms.search.query.TagQuery; 047import org.ametys.cms.search.solr.field.TagSearchField; 048import org.ametys.cms.search.solr.schema.FieldDefinition; 049import org.ametys.cms.search.solr.schema.SchemaDefinition; 050import org.ametys.cms.tag.Tag; 051import org.ametys.cms.tag.TagHelper; 052import org.ametys.cms.tag.TagProviderExtensionPoint; 053import org.ametys.core.model.type.ModelItemTypeHelper; 054import org.ametys.core.util.JSONUtils; 055import org.ametys.runtime.i18n.I18nizableText; 056import org.ametys.runtime.model.ViewItem; 057import org.ametys.runtime.model.type.DataContext; 058import org.ametys.runtime.model.type.ModelItemTypeConstants; 059 060/** 061 * {@link SystemProperty} which represents the tags of a Content. 062 */ 063public class TagsSystemProperty extends AbstractSystemProperty<String, Content> 064{ 065 /** The tag provider extension point. */ 066 protected TagProviderExtensionPoint _tagProviderEP; 067 /** The JSON utils */ 068 protected JSONUtils _jsonUtils; 069 070 @Override 071 public void service(ServiceManager manager) throws ServiceException 072 { 073 super.service(manager); 074 _tagProviderEP = (TagProviderExtensionPoint) manager.lookup(TagProviderExtensionPoint.ROLE); 075 _jsonUtils = (JSONUtils) manager.lookup(JSONUtils.ROLE); 076 } 077 078 @Override 079 public boolean isMultiple() 080 { 081 return true; 082 } 083 084 @Override 085 public boolean isSortable() 086 { 087 return false; 088 } 089 090 @SuppressWarnings("unchecked") 091 @Override 092 public Query getQuery(Object value, Operator operator, String language, Map<String, Object> contextualParameters) 093 { 094 // if NE, then each value must be not present => AND 095 // otherwise, at least one of the values must be present => OR 096 LogicalOperator logicalOperator = operator == Operator.NE ? LogicalOperator.AND : LogicalOperator.OR; 097 098 if (value instanceof Map<?, ?>) 099 { 100 // Handle autoposting 101 Map<String, Object> valueAsMap = (Map<String, Object>) value; 102 103 String[] tagNames = parseStringArray(valueAsMap.get("value")); 104 105 boolean autoposting = BooleanUtils.isTrue((Boolean) valueAsMap.get("autoposting")); 106 return new TagQuery(operator, autoposting, logicalOperator, tagNames); 107 } 108 else 109 { 110 return new TagQuery(operator, false, logicalOperator, value != null ? parseStringArray(value) : null); 111 } 112 } 113 114 @Override 115 public SearchField getSearchField() 116 { 117 return new TagSearchField(); 118 } 119 120 @Override 121 public String getCriterionWidget() 122 { 123 return "edition.tag"; 124 } 125 126 public Map<String, I18nizableText> getCriterionWidgetParameters(Configuration configuration) 127 { 128 Map<String, I18nizableText> params = new HashMap<>(); 129 130 params.put("targetType", new I18nizableText("CONTENT")); 131 params.put("allowToggleAutoposting", new I18nizableText("true")); 132 133 return params; 134 } 135 136 @Override 137 public String getRenderer() 138 { 139 return "Ametys.plugins.cms.search.SearchGridHelper.renderTag"; 140 } 141 142 @Override 143 public String getConverter() 144 { 145 return "Ametys.plugins.cms.search.SearchGridHelper.convertTag"; 146 } 147 148 @Override 149 public void indexValue(SolrInputDocument document, Content content, DataContext context) 150 { 151 Set<String> tagsWithAncestors = new HashSet<>(); 152 String language = content.getLanguage(); 153 String[] tagNames = (String[]) getValue(content); 154 155 for (String tagName : tagNames) 156 { 157 Tag tag = _tagProviderEP.getTag(tagName, getTagContextualParameters(content)); 158 if (tag != null) 159 { 160 document.addField(SolrFieldNames.TAGS, tagName); 161 162 if (!tagsWithAncestors.contains(tagName)) 163 { 164 document.addField(SolrFieldNames.ALL_TAGS, tagName); 165 tagsWithAncestors.add(tagName); 166 } 167 168 String title = _i18nUtils.translate(tag.getTitle(), content.getLanguage()); 169 IndexableElementTypeHelper.indexFulltextValue(document, title, context); 170 171 // Index ancestors (descendant autoposting) 172 Set<Tag> ancestors = TagHelper.getAncestors(tag, false); 173 174 for (Tag ancestor : ancestors) 175 { 176 String ancestorName = ancestor.getName(); 177 if (!tagsWithAncestors.contains(ancestorName)) 178 { 179 document.addField(SolrFieldNames.ALL_TAGS, ancestorName); 180 181 // Plain text + autocompletion on ancestors (if enabled) 182 if (isAutopostingEnabled(content)) 183 { 184 String ancestorTitle = _i18nUtils.translate(ancestor.getTitle(), language); 185 IndexableElementTypeHelper.indexFulltextValue(document, ancestorTitle, context); 186 } 187 188 tagsWithAncestors.add(tagName); 189 } 190 } 191 } 192 } 193 } 194 195 @Override 196 public Object getValue(Content content) 197 { 198 Set<String> tags = content.getTags(); 199 return tags.toArray(new String[tags.size()]); 200 } 201 202 @Override 203 public Object valueToJSON(Content content, Optional<ViewItem> viewItem, DataContext context) 204 { 205 String[] tags = (String[]) getValue(content); 206 if (tags != null) 207 { 208 List<Map<String, Object>> fullValue = new ArrayList<>(); 209 210 Map<String, Object> params = getTagContextualParameters(content); 211 212 for (String tagName : tags) 213 { 214 Map<String, Object> infos = new HashMap<>(); 215 infos.put("name", tagName); 216 217 Tag tag = _tagProviderEP.getTag(tagName, params); 218 if (tag != null) 219 { 220 infos.put("label", tag.getTitle()); 221 } 222 223 fullValue.add(infos); 224 } 225 226 return fullValue; 227 } 228 229 return tags; 230 } 231 232 public void valueToSAX(ContentHandler contentHandler, Content content, Optional<ViewItem> viewItem, DataContext context) throws SAXException 233 { 234 String[] tags = (String[]) getValue(content); 235 236 XMLUtils.startElement(contentHandler, getName()); 237 238 for (String tag : tags) 239 { 240 AttributesImpl attr = ModelItemTypeHelper.getXMLAttributesFromDataContext(context); 241 XMLUtils.createElement(contentHandler, "tag", attr, tag); 242 } 243 244 XMLUtils.endElement(contentHandler, getName()); 245 } 246 247 /** 248 * Get the tags contextual parameters. 249 * @param content The content. 250 * @return the tags contextual parameters. 251 */ 252 protected Map<String, Object> getTagContextualParameters(Content content) 253 { 254 return Collections.emptyMap(); 255 } 256 257 /** 258 * Test if autoposting is enabled. 259 * @param content The content. 260 * @return true if enabled. 261 */ 262 protected boolean isAutopostingEnabled(Content content) 263 { 264 return true; 265 } 266 267 @Override 268 public Collection<SchemaDefinition> getSchemaDefinitions() 269 { 270 Collection<SchemaDefinition> definitions = super.getSchemaDefinitions(); 271 272 // Add allTags solr field 273 definitions.add(new FieldDefinition(SolrFieldNames.ALL_TAGS, getType().getSchemaType(), isMultiple(), false)); 274 return definitions; 275 } 276 277 @Override 278 protected String _getTypeId() 279 { 280 return ModelItemTypeConstants.STRING_TYPE_ID; 281 } 282}