001/* 002 * Copyright 2016 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.search.solr; 017 018import java.text.Normalizer; 019import java.util.ArrayList; 020import java.util.Collections; 021import java.util.HashMap; 022import java.util.List; 023import java.util.Map; 024 025import org.apache.avalon.framework.parameters.Parameters; 026import org.apache.avalon.framework.service.ServiceException; 027import org.apache.avalon.framework.service.ServiceManager; 028import org.apache.cocoon.acting.ServiceableAction; 029import org.apache.cocoon.environment.ObjectModelHelper; 030import org.apache.cocoon.environment.Redirector; 031import org.apache.cocoon.environment.Request; 032import org.apache.cocoon.environment.SourceResolver; 033import org.apache.commons.lang3.StringUtils; 034 035import org.ametys.cms.contenttype.ContentType; 036import org.ametys.cms.contenttype.ContentTypeExtensionPoint; 037import org.ametys.cms.contenttype.ContentTypesHelper; 038import org.ametys.cms.contenttype.MetadataDefinition; 039import org.ametys.cms.contenttype.indexing.IndexingField; 040import org.ametys.cms.contenttype.indexing.MetadataIndexingField; 041import org.ametys.cms.search.model.SearchCriterion; 042import org.ametys.cms.search.model.SystemPropertyExtensionPoint; 043import org.ametys.cms.search.ui.model.SearchUICriterion; 044import org.ametys.cms.search.ui.model.SearchUIModel; 045import org.ametys.cms.search.ui.model.SearchUIModelExtensionPoint; 046import org.ametys.cms.search.ui.model.impl.IndexingFieldSearchUICriterion; 047import org.ametys.cms.search.ui.model.impl.SystemSearchUICriterion; 048import org.ametys.core.cocoon.JSonReader; 049import org.ametys.core.util.ServerCommHelper; 050 051/** 052 * Get search facets available to a given list of content types. 053 */ 054public class GetSearchFacetsAction extends ServiceableAction 055{ 056 057 /** The content type extension point */ 058 protected ContentTypeExtensionPoint _cTypeEP; 059 060 /** The content type helper */ 061 protected ContentTypesHelper _cTypeHelper; 062 063 /** The search model helper. */ 064 protected SearchUIModelExtensionPoint _searchModelManager; 065 066 /** The system property extension point. */ 067 protected SystemPropertyExtensionPoint _sysPropEP; 068 069 /** The ServerComm helper. */ 070 protected ServerCommHelper _serverCommHelper; 071 072 @Override 073 public void service(ServiceManager serviceManager) throws ServiceException 074 { 075 super.service(serviceManager); 076 _cTypeEP = (ContentTypeExtensionPoint) serviceManager.lookup(ContentTypeExtensionPoint.ROLE); 077 _cTypeHelper = (ContentTypesHelper) serviceManager.lookup(ContentTypesHelper.ROLE); 078 _searchModelManager = (SearchUIModelExtensionPoint) serviceManager.lookup(SearchUIModelExtensionPoint.ROLE); 079 _sysPropEP = (SystemPropertyExtensionPoint) serviceManager.lookup(SystemPropertyExtensionPoint.ROLE); 080 _serverCommHelper = (ServerCommHelper) serviceManager.lookup(ServerCommHelper.ROLE); 081 } 082 083 @Override 084 public Map act(Redirector redirector, SourceResolver resolver, Map objectModel, String source, Parameters parameters) throws Exception 085 { 086 Request request = ObjectModelHelper.getRequest(objectModel); 087 088 Map<String, Object> results = new HashMap<>(); 089 request.setAttribute(JSonReader.OBJECT_TO_READ, results); 090 091 Map<String, Object> jsParameters = _serverCommHelper.getJsParameters(); 092 093 String modelId = (String) jsParameters.get("modelId"); 094 095 @SuppressWarnings("unchecked") 096 List<String> cTypes = (List<String>) jsParameters.get("cTypes"); 097 098 List<Map<String, String>> properties = new ArrayList<>(); 099 results.put("properties", properties); 100 101 String startFilter = (String) jsParameters.get("startsWith"); 102 if (StringUtils.isNotEmpty(startFilter)) 103 { 104 startFilter = normalize(startFilter); 105 } 106 107 SearchUIModel model = _searchModelManager.getExtension(modelId); 108 109 List<String> propertyIds = new ArrayList<>(); 110 for (String systemProp : _sysPropEP.getSearchProperties()) 111 { 112 addSystemProperty(propertyIds, systemProp, model); 113 } 114 115 // Get the common content type. 116 ContentType cType = null; 117 if (cTypes != null && !cTypes.isEmpty()) 118 { 119 String commonCTypeId = _cTypeHelper.getCommonAncestor(cTypes); 120 if (commonCTypeId != null) 121 { 122 cType = _cTypeEP.getExtension(commonCTypeId); 123 } 124 } 125 126 if (cType != null) 127 { 128 for (String meta : cType.getMetadataNames()) 129 { 130 addMetadata(propertyIds, meta, cType); 131 } 132 for (IndexingField field : cType.getIndexingModel().getFields()) 133 { 134 addIndexingField(propertyIds, field); 135 } 136 } 137 138 for (String propertyId : propertyIds) 139 { 140 if (StringUtils.isEmpty(startFilter) || normalize(propertyId).startsWith(startFilter)) 141 { 142 properties.add(Collections.singletonMap("id", propertyId)); 143 } 144 } 145 146 return EMPTY_MAP; 147 } 148 149 private void addMetadata(List<String> properties, String metaPath, ContentType cType) 150 { 151 if (cType != null) 152 { 153 MetadataDefinition metaDef = cType.getMetadataDefinitionByPath(metaPath); 154 if (metaDef != null && SearchCriterion.isFacetable(metaDef.getType(), metaDef.getEnumerator() != null)) 155 { 156 properties.add(metaPath); 157 } 158 } 159 else 160 { 161 properties.add(metaPath); 162 } 163 } 164 165 private void addIndexingField(List<String> properties, IndexingField field) 166 { 167 if (field instanceof MetadataIndexingField) 168 { 169 MetadataIndexingField metaField = (MetadataIndexingField) field; 170 MetadataDefinition metaDef = metaField.getMetadataDefinition(); 171 172 if (metaDef != null && SearchCriterion.isFacetable(metaDef.getType(), metaDef.getEnumerator() != null)) 173 { 174 properties.add(metaField.getName()); 175 } 176 } 177 } 178 179 private void addSystemProperty(List<String> properties, String propertyId, SearchUIModel model) 180 { 181 if (model != null) 182 { 183 SearchCriterion criterion = _getCriteria(model, propertyId); 184 185 if (criterion != null && criterion.isFacetable()) 186 { 187 properties.add(propertyId); 188 } 189 } 190 } 191 192 private SearchCriterion _getCriteria(SearchUIModel searchModel, String criterionId) 193 { 194 Map<String, SearchUICriterion> criteria = searchModel.getFacetedCriteria(Collections.emptyMap()); 195 196 for (SearchCriterion criterion : criteria.values()) 197 { 198 if (criterion instanceof IndexingFieldSearchUICriterion && ((IndexingFieldSearchUICriterion) criterion).getFieldPath().equals(criterionId)) 199 { 200 return criterion; 201 } 202 else if (criterion instanceof SystemSearchUICriterion && ((SystemSearchUICriterion) criterion).getSystemPropertyId().equals(criterionId)) 203 { 204 return criterion; 205 } 206 else if (criterion.getId().equals(criterionId)) 207 { 208 return criterion; 209 } 210 } 211 212 return null; 213 } 214 215 private static String normalize(String str) 216 { 217 return Normalizer.normalize(str.toLowerCase(), Normalizer.Form.NFD).replaceAll("\\p{InCombiningDiacriticalMarks}+", "").trim(); 218 } 219 220}