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, value != null ? parseStringArray(value) : null);
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}