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}