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.model.properties;
017
018import java.util.ArrayList;
019import java.util.Arrays;
020import java.util.HashMap;
021import java.util.HashSet;
022import java.util.List;
023import java.util.Map;
024import java.util.Set;
025
026import org.apache.avalon.framework.service.ServiceException;
027import org.apache.avalon.framework.service.ServiceManager;
028import org.apache.solr.common.SolrInputDocument;
029import org.xml.sax.ContentHandler;
030import org.xml.sax.SAXException;
031
032import org.ametys.cms.contenttype.RichTextAttributeDefinition;
033import org.ametys.cms.data.RichText;
034import org.ametys.cms.data.type.indexing.IndexableDataContext;
035import org.ametys.cms.repository.Content;
036import org.ametys.cms.search.SearchField;
037import org.ametys.cms.search.query.Query;
038import org.ametys.cms.search.query.Query.Operator;
039import org.ametys.cms.search.systemprop.AbstractSystemProperty;
040import org.ametys.core.util.JSONUtils;
041import org.ametys.runtime.model.Model;
042import org.ametys.runtime.model.ModelHelper;
043import org.ametys.runtime.model.type.DataContext;
044import org.ametys.runtime.model.type.ModelItemTypeConstants;
045
046/**
047 * Property for semantic annotations.
048 * Do not use the property in search model. There is no search field nor query, the criteria would not work
049 */
050public class SemanticAnnotationSystemProperty extends AbstractSystemProperty<String, Content>
051{
052    /** Prefix for annotations */
053    public static final String ANNOTATION_PREFIX = "annotation-";
054    
055    /** The JSON utils */
056    protected JSONUtils _jsonUtils;
057    
058    @Override
059    public void service(ServiceManager manager) throws ServiceException
060    {
061        super.service(manager);
062        _jsonUtils = (JSONUtils) manager.lookup(JSONUtils.ROLE);
063    }
064    
065    @Override
066    public boolean isMultiple()
067    {
068        return true;
069    }
070    
071    @Override
072    public boolean isSortable()
073    {
074        return false;
075    }
076    
077    @Override
078    public boolean isDisplayable()
079    {
080        return false;
081    }
082    
083    @Override
084    public Object getValue(Content content)
085    {
086        List<String> values = new ArrayList<>();
087        
088        Map<String, List<String>> semanticAnnotations = _getSemanticAnnotations(content);
089        for (String annotationName : semanticAnnotations.keySet())
090        {
091            String value = annotationName + ":" + _jsonUtils.convertObjectToJson(semanticAnnotations.get(annotationName));
092            values.add(value);
093        }
094        
095        return values.toArray(new String[values.size()]);
096    }
097    
098    public Object valueToJSON(Content content, DataContext context)
099    {
100        return _getSemanticAnnotations(content);
101    }
102    
103    public void valueToSAX(ContentHandler contentHandler, Content content, DataContext context) throws SAXException
104    {
105        Map<String, List<String>> semanticAnnotations = _getSemanticAnnotations(content);
106        for (String annotationName : semanticAnnotations.keySet())
107        {
108            List<String> semanticAnnotation = semanticAnnotations.get(annotationName);
109            String[] semanticAnnotationAsStringArray = semanticAnnotation.toArray(new String[semanticAnnotation.size()]);
110            getType().valueToSAX(contentHandler, annotationName, semanticAnnotationAsStringArray, context);
111        }
112    }
113    
114    @Override
115    public void indexValue(SolrInputDocument document, Content content, IndexableDataContext context)
116    {
117        Map<String, List<String>> semanticAnnotations = _getSemanticAnnotations(content);
118        for (String annotationName : semanticAnnotations.keySet())
119        {
120            List<String> semanticAnnotation = semanticAnnotations.get(annotationName);
121            String[] semanticAnnotationAsStringArray = semanticAnnotation.toArray(new String[semanticAnnotation.size()]);
122            getType().indexValue(document, document, annotationName, semanticAnnotationAsStringArray, context);
123        }
124    }
125    
126    private Map<String, List<String>> _getSemanticAnnotations(Content content)
127    {
128        // Search for all rich text definitions
129        Set<RichTextAttributeDefinition> richTextDefinitions = new HashSet<>();
130        for (Model model : content.getModel())
131        {
132            ModelHelper.findModelItemsByType(model, org.ametys.cms.data.type.ModelItemTypeConstants.RICH_TEXT_ELEMENT_TYPE_ID)
133                       .stream()
134                       .filter(RichTextAttributeDefinition.class::isInstance)
135                       .map(RichTextAttributeDefinition.class::cast)
136                       .forEach(richTextDefinitions::add);
137        }
138        
139        // For each richText definition, get the rich text values
140        Map<String, List<String>> semanticAnnotations = new HashMap<>();
141        for (RichTextAttributeDefinition richTextDefinition : richTextDefinitions)
142        {
143            Object value = content.getValue(richTextDefinition.getPath(), true);
144            List<RichText> richTexts = null;
145            if (value instanceof RichText richText)
146            {
147                richTexts = List.of(richText);
148            }
149            else if (value instanceof RichText[] richTextsArray)
150            {
151                richTexts = Arrays.asList(richTextsArray);
152            }
153            
154            if (richTexts != null)
155            {
156                for (RichText richText : richTexts)
157                {
158                    Map<String, List<String>> richTextAnnotations = richText.getAllAnnotations();
159                    for (String richTextAnnotationName : richTextAnnotations.keySet())
160                    {
161                        String prefixedAnnotationName = ANNOTATION_PREFIX + richTextAnnotationName;
162                        List<String> semanticAnnotationValues = semanticAnnotations.computeIfAbsent(prefixedAnnotationName, __ -> new ArrayList<>());
163                        semanticAnnotationValues.addAll(richTextAnnotations.get(richTextAnnotationName));
164                    }
165                }
166            }
167        }
168        
169        return semanticAnnotations;
170    }
171    
172    @Override
173    protected String _getTypeId()
174    {
175        return ModelItemTypeConstants.STRING_TYPE_ID;
176    }
177
178    public Query getQuery(Object value, Operator operator, String language, Map<String, Object> contextualParameters)
179    {
180        return null;
181    }
182
183    public SearchField getSearchField()
184    {
185        return null;
186    }
187}