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.Collection;
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.service.ServiceException;
028import org.apache.avalon.framework.service.ServiceManager;
029import org.apache.solr.common.SolrInputDocument;
030import org.xml.sax.ContentHandler;
031import org.xml.sax.SAXException;
032
033import org.ametys.cms.contenttype.RichTextAttributeDefinition;
034import org.ametys.cms.data.RichText;
035import org.ametys.cms.model.CMSDataContext;
036import org.ametys.cms.repository.Content;
037import org.ametys.cms.search.solr.schema.SchemaDefinition;
038import org.ametys.cms.search.systemprop.AbstractSystemProperty;
039import org.ametys.cms.search.systemprop.IndexationAwareSystemProperty;
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> implements IndexationAwareSystemProperty<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, CMSDataContext 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 String getSolrFieldName()
179    {
180        // No specific value to index: the field won't be used.
181        return null;
182    }
183    
184    public String getSolrSortFieldName()
185    {
186        // Not sortable
187        return null;
188    }
189    
190    public String getSolrFacetFieldName()
191    {
192        // Not facetable
193        return null;
194    }
195    
196    public Collection<SchemaDefinition> getSchemaDefinitions()
197    {
198        // No specific schema definition
199        return List.of();
200    }
201}