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.search.cocoon; 017 018import java.io.IOException; 019import java.util.ArrayList; 020import java.util.Collections; 021import java.util.HashMap; 022import java.util.LinkedHashMap; 023import java.util.List; 024import java.util.Map; 025 026import org.apache.avalon.framework.service.ServiceException; 027import org.apache.avalon.framework.service.ServiceManager; 028import org.apache.cocoon.ProcessingException; 029import org.apache.cocoon.environment.ObjectModelHelper; 030import org.apache.cocoon.environment.Request; 031import org.apache.cocoon.reading.ServiceableReader; 032import org.xml.sax.SAXException; 033 034import org.ametys.cms.contenttype.ContentTypesHelper; 035import org.ametys.cms.repository.Content; 036import org.ametys.cms.search.SearchResult; 037import org.ametys.cms.search.SearchResults; 038import org.ametys.cms.search.content.ContentValuesExtractorFactory; 039import org.ametys.cms.search.content.ContentValuesExtractorFactory.SearchModelContentValuesExtractor; 040import org.ametys.cms.search.ui.model.SearchUICriterion; 041import org.ametys.cms.search.ui.model.SearchUIModel; 042import org.ametys.core.util.JSONUtils; 043import org.ametys.core.util.ServerCommHelper; 044import org.ametys.plugins.repository.AmetysObjectResolver; 045import org.ametys.plugins.repository.UnknownAmetysObjectException; 046import org.ametys.runtime.i18n.I18nizableText; 047 048/** 049 * JSON reader for search for contents. 050 */ 051public class SearchJsonReader extends ServiceableReader 052{ 053 /** JSON utils */ 054 protected JSONUtils _jsonUtils; 055 056 /** The serverComm helper */ 057 protected ServerCommHelper _serverCommHelper; 058 059 /** The content types helper. */ 060 protected ContentTypesHelper _contentTypesHelper; 061 062 /** The Ametys object resolver */ 063 protected AmetysObjectResolver _resolver; 064 065 private ContentValuesExtractorFactory _valuesExtractorFactory; 066 067 @Override 068 public void service(ServiceManager smanager) throws ServiceException 069 { 070 super.service(smanager); 071 _jsonUtils = (JSONUtils) smanager.lookup(JSONUtils.ROLE); 072 _serverCommHelper = (ServerCommHelper) smanager.lookup(ServerCommHelper.ROLE); 073 _contentTypesHelper = (ContentTypesHelper) smanager.lookup(ContentTypesHelper.ROLE); 074 _valuesExtractorFactory = (ContentValuesExtractorFactory) smanager.lookup(ContentValuesExtractorFactory.ROLE); 075 _resolver = (AmetysObjectResolver) smanager.lookup(AmetysObjectResolver.ROLE); 076 } 077 078 @Override 079 public String getMimeType() 080 { 081 return "application/json"; 082 } 083 084 @SuppressWarnings("unchecked") 085 @Override 086 public void generate() throws IOException, SAXException, ProcessingException 087 { 088 Request request = ObjectModelHelper.getRequest(objectModel); 089 090 SearchResults<Content> results = (SearchResults<Content>) request.getAttribute(SearchAction.SEARCH_RESULTS); 091 SearchUIModel model = (SearchUIModel) request.getAttribute(SearchAction.SEARCH_MODEL); 092 I18nizableText queryError = (I18nizableText) request.getAttribute(SearchAction.QUERY_ERROR); 093 094 Map<String, Object> jsonObject = new HashMap<>(); 095 096 if (queryError != null) 097 { 098 jsonObject.put("error", queryError); 099 } 100 else if (results != null) 101 { 102 convertResults2JsonObject(jsonObject, results, model); 103 } 104 else 105 { 106 List<String> contentIds = (List<String>) request.getAttribute(SearchAction.SEARCH_CONTENTS); 107 convertContents2JsonObject(jsonObject, contentIds, model); 108 } 109 110 _jsonUtils.convertObjectToJson(out, jsonObject); 111 } 112 113 /** 114 * Convert the query results to a JSON object 115 * @param results2Json the JSON object to fill. 116 * @param results the query results 117 * @param model the search model 118 * @throws ProcessingException If an error occurs. 119 */ 120 protected void convertResults2JsonObject(Map<String, Object> results2Json, SearchResults<Content> results, SearchUIModel model) throws ProcessingException 121 { 122 Map<String, Object> jsParameters = _serverCommHelper.getJsParameters(); 123 @SuppressWarnings("unchecked") 124 Map<String, Object> values = (Map<String, Object>) jsParameters.get("values"); 125 @SuppressWarnings("unchecked") 126 Map<String, Object> contextualParameters = (Map<String, Object>) jsParameters.get("contextualParameters"); 127 if (contextualParameters == null) 128 { 129 contextualParameters = Collections.emptyMap(); 130 } 131 132 setContents(results2Json, results, model, contextualParameters); 133 134 setFacets(results2Json, results.getFacetResults(), model, contextualParameters); 135 136 results2Json.put("values", values); 137 results2Json.put("total", results.getTotalCount()); 138 } 139 140 /** 141 * Convert the query results to a JSON object 142 * @param results2Json the JSON object to fill. 143 * @param contentIds the ids of contents 144 * @param model the search model 145 * @throws ProcessingException If an error occurs. 146 */ 147 protected void convertContents2JsonObject(Map<String, Object> results2Json, List<String> contentIds, SearchUIModel model) throws ProcessingException 148 { 149 Map<String, Object> jsParameters = _serverCommHelper.getJsParameters(); 150 @SuppressWarnings("unchecked") 151 Map<String, Object> contextualParameters = (Map<String, Object>) jsParameters.get("contextualParameters"); 152 if (contextualParameters == null) 153 { 154 contextualParameters = Collections.emptyMap(); 155 } 156 157 int size = setContents(results2Json, contentIds, model, contextualParameters); 158 results2Json.put("total", size); 159 } 160 161 /** 162 * Extract the desired contents from the {@link SearchResults} object and set them in the search results. 163 * @param searchResults the search results to populate. 164 * @param results the {@link SearchResults}. 165 * @param model the search model. 166 * @param contextualParameters the search contextual parameters. 167 */ 168 protected void setContents(Map<String, Object> searchResults, SearchResults<Content> results, SearchUIModel model, Map<String, Object> contextualParameters) 169 { 170 List<Map<String, Object>> contents = new ArrayList<>(); 171 searchResults.put("contents", contents); 172 173 boolean fullValues = parameters.getParameterAsBoolean("fullValues", "true".equals(contextualParameters.get("fullValues"))); 174 175 SearchModelContentValuesExtractor extractor = _valuesExtractorFactory.create(model).setFullValues(fullValues); 176 177 for (SearchResult<Content> result : results.getResults()) 178 { 179 contents.add(getContentData(result.getObject(), extractor, contextualParameters)); 180 } 181 } 182 183 /** 184 * Extract the desired contents and set them in the search results. 185 * @param searchResults the search results to populate. 186 * @param contentIds the id of desired contents 187 * @param model the search model. 188 * @param contextualParameters the search contextual parameters. 189 * @return the number of resolved contents without error 190 */ 191 protected int setContents(Map<String, Object> searchResults, List<String> contentIds, SearchUIModel model, Map<String, Object> contextualParameters) 192 { 193 List<Map<String, Object>> contents = new ArrayList<>(); 194 searchResults.put("contents", contents); 195 196 boolean fullValues = parameters.getParameterAsBoolean("fullValues", "true".equals(contextualParameters.get("fullValues"))); 197 198 SearchModelContentValuesExtractor extractor = _valuesExtractorFactory.create(model).setFullValues(fullValues); 199 200 int count = 0; 201 for (String contentId : contentIds) 202 { 203 try 204 { 205 Content content = _resolver.resolveById(contentId); 206 contents.add(getContentData(content, extractor, contextualParameters)); 207 count++; 208 } 209 catch (UnknownAmetysObjectException e) 210 { 211 getLogger().warn("The Ametys object with id '" + contentId + "' does not exist anymore."); 212 } 213 } 214 return count; 215 } 216 217 /** 218 * Generate standard content data. 219 * @param content The content. 220 * @param extractor The content values extractor which generates. 221 * @param contextualParameters The search contextual parameters. 222 * @return A Map containing the content data. 223 */ 224 protected Map<String, Object> getContentData(Content content, SearchModelContentValuesExtractor extractor, Map<String, Object> contextualParameters) 225 { 226 Map<String, Object> contentData = new HashMap<>(); 227 228 contentData.put("id", content.getId()); 229 contentData.put("name", content.getName()); 230 contentData.put("title", content.getTitle()); 231 contentData.put("language", content.getLanguage()); 232 contentData.put("contentTypes", content.getTypes()); 233 contentData.put("mixins", content.getMixinTypes()); 234 contentData.put("iconGlyph", _contentTypesHelper.getIconGlyph(content)); 235 contentData.put("iconDecorator", _contentTypesHelper.getIconDecorator(content)); 236 contentData.put("smallIcon", _contentTypesHelper.getSmallIcon(content)); 237 contentData.put("mediumIcon", _contentTypesHelper.getMediumIcon(content)); 238 contentData.put("largeIcon", _contentTypesHelper.getLargeIcon(content)); 239 240 contentData.put("properties", extractor.getValues(content, contextualParameters)); 241 242 return contentData; 243 } 244 245 /** 246 * Set the facet results in the search results. 247 * @param searchResults the search results. 248 * @param facetResults the facet results as a Map<column name, Map<value, result count>>. 249 * @param model the search model. 250 * @param contextualParameters the search contextual parameters. 251 */ 252 protected void setFacets(Map<String, Object> searchResults, Map<String, Map<String, Integer>> facetResults, SearchUIModel model, Map<String, Object> contextualParameters) 253 { 254 List<Object> facets = new ArrayList<>(); 255 searchResults.put("facets", facets); 256 257 // Index facets by search field name. 258 Map<String, SearchUICriterion> facetByName = new HashMap<>(); 259 for (SearchUICriterion facet : model.getFacetedCriteria(contextualParameters).values()) 260 { 261 if (facet.getSearchField() != null) 262 { 263 facetByName.put(facet.getSearchField().getName(), facet); 264 } 265 } 266 267 for (String criterionName : facetResults.keySet()) 268 { 269 SearchUICriterion searchCriterion = facetByName.get(criterionName); 270 271 if (searchCriterion != null) 272 { 273 Map<String, Integer> values = facetResults.get(criterionName); 274 275 Map<String, Object> column = new HashMap<>(); 276 facets.add(column); 277 278 List<Map<String, Object>> criterionFacets = new ArrayList<>(); 279 column.put("children", criterionFacets); 280 column.put("name", criterionName); 281 column.put("label", searchCriterion.getLabel()); 282 column.put("type", "criterion"); 283 284 for (String value : values.keySet()) 285 { 286 Map<String, Object> facet = new LinkedHashMap<>(); 287 criterionFacets.add(facet); 288 289 Integer count = values.get(value); 290 I18nizableText label = searchCriterion.getFacetLabel(value); 291 292 facet.put("value", value); 293 facet.put("count", count); 294 facet.put("label", label); 295 facet.put("type", "facet"); 296 } 297 } 298 } 299 } 300}