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