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}