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