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