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 isDisplayable()
073    {
074        return false;
075    }
076    
077    @Override
078    public Object getValue(Content content)
079    {
080        List<String> values = new ArrayList<>();
081        
082        Map<String, List<String>> semanticAnnotations = _getSemanticAnnotations(content);
083        for (String annotationName : semanticAnnotations.keySet())
084        {
085            String value = annotationName + ":" + _jsonUtils.convertObjectToJson(semanticAnnotations.get(annotationName));
086            values.add(value);
087        }
088        
089        return values.toArray(new String[values.size()]);
090    }
091    
092    public Object valueToJSON(Content content, DataContext context)
093    {
094        return _getSemanticAnnotations(content);
095    }
096    
097    public void valueToSAX(ContentHandler contentHandler, String tagName, Content content, DataContext context) throws SAXException
098    {
099        // tag name should not be ignored
100        Map<String, List<String>> semanticAnnotations = _getSemanticAnnotations(content);
101        for (String annotationName : semanticAnnotations.keySet())
102        {
103            List<String> semanticAnnotation = semanticAnnotations.get(annotationName);
104            String[] semanticAnnotationAsStringArray = semanticAnnotation.toArray(new String[semanticAnnotation.size()]);
105            getType().valueToSAX(contentHandler, annotationName, semanticAnnotationAsStringArray, context);
106        }
107    }
108    
109    @Override
110    public void indexValue(SolrInputDocument document, Content content, CMSDataContext context)
111    {
112        Map<String, List<String>> semanticAnnotations = _getSemanticAnnotations(content);
113        for (String annotationName : semanticAnnotations.keySet())
114        {
115            List<String> semanticAnnotation = semanticAnnotations.get(annotationName);
116            String[] semanticAnnotationAsStringArray = semanticAnnotation.toArray(new String[semanticAnnotation.size()]);
117            getType().indexValue(document, document, annotationName, semanticAnnotationAsStringArray, context);
118        }
119    }
120    
121    private Map<String, List<String>> _getSemanticAnnotations(Content content)
122    {
123        // Search for all rich text definitions
124        Set<RichTextAttributeDefinition> richTextDefinitions = new HashSet<>();
125        for (Model model : content.getModel())
126        {
127            ModelHelper.findModelItemsByType(model, org.ametys.cms.data.type.ModelItemTypeConstants.RICH_TEXT_ELEMENT_TYPE_ID)
128                       .stream()
129                       .filter(RichTextAttributeDefinition.class::isInstance)
130                       .map(RichTextAttributeDefinition.class::cast)
131                       .forEach(richTextDefinitions::add);
132        }
133        
134        // For each richText definition, get the rich text values
135        Map<String, List<String>> semanticAnnotations = new HashMap<>();
136        for (RichTextAttributeDefinition richTextDefinition : richTextDefinitions)
137        {
138            Object value = content.getValueWithMultiValuedPathSegments(richTextDefinition.getPath());
139            List<RichText> richTexts = null;
140            if (value instanceof RichText richText)
141            {
142                richTexts = List.of(richText);
143            }
144            else if (value instanceof RichText[] richTextsArray)
145            {
146                richTexts = Arrays.asList(richTextsArray);
147            }
148            
149            if (richTexts != null)
150            {
151                for (RichText richText : richTexts)
152                {
153                    Map<String, List<String>> richTextAnnotations = richText.getAllAnnotations();
154                    for (String richTextAnnotationName : richTextAnnotations.keySet())
155                    {
156                        String prefixedAnnotationName = ANNOTATION_PREFIX + richTextAnnotationName;
157                        List<String> semanticAnnotationValues = semanticAnnotations.computeIfAbsent(prefixedAnnotationName, __ -> new ArrayList<>());
158                        semanticAnnotationValues.addAll(richTextAnnotations.get(richTextAnnotationName));
159                    }
160                }
161            }
162        }
163        
164        return semanticAnnotations;
165    }
166    
167    @Override
168    protected String getTypeId()
169    {
170        return ModelItemTypeConstants.STRING_TYPE_ID;
171    }
172
173    public String getSolrFieldName()
174    {
175        // No specific value to index: the field won't be used.
176        return null;
177    }
178    
179    public String getSolrSortFieldName()
180    {
181        // Not sortable
182        return null;
183    }
184    
185    public String getSolrFacetFieldName()
186    {
187        // Not facetable
188        return null;
189    }
190    
191    public Collection<SchemaDefinition> getSchemaDefinitions()
192    {
193        // No specific schema definition
194        return List.of();
195    }
196}