001/* 002 * Copyright 2013 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.Collection; 021import java.util.Collections; 022import java.util.List; 023import java.util.Locale; 024import java.util.Map; 025import java.util.Set; 026import java.util.stream.Collectors; 027 028import org.apache.avalon.framework.context.Context; 029import org.apache.avalon.framework.context.ContextException; 030import org.apache.avalon.framework.context.Contextualizable; 031import org.apache.avalon.framework.service.ServiceException; 032import org.apache.avalon.framework.service.ServiceManager; 033import org.apache.cocoon.ProcessingException; 034import org.apache.cocoon.components.ContextHelper; 035import org.apache.cocoon.environment.ObjectModelHelper; 036import org.apache.cocoon.environment.Request; 037import org.apache.cocoon.generation.ServiceableGenerator; 038import org.apache.cocoon.xml.AttributesImpl; 039import org.apache.cocoon.xml.XMLUtils; 040import org.apache.commons.lang3.ArrayUtils; 041import org.apache.commons.lang3.StringUtils; 042import org.xml.sax.SAXException; 043 044import org.ametys.cms.contenttype.ContentTypeExtensionPoint; 045import org.ametys.cms.contenttype.ContentTypesHelper; 046import org.ametys.cms.repository.Content; 047import org.ametys.cms.search.SearchResult; 048import org.ametys.cms.search.SearchResults; 049import org.ametys.cms.search.content.ContentValuesExtractorFactory; 050import org.ametys.cms.search.model.ResultField; 051import org.ametys.cms.search.model.SearchModel; 052import org.ametys.cms.search.ui.model.ColumnHelper; 053import org.ametys.cms.search.ui.model.ColumnHelper.Column; 054import org.ametys.cms.search.ui.model.SearchUIModelExtensionPoint; 055import org.ametys.core.util.ServerCommHelper; 056import org.ametys.plugins.repository.AmetysRepositoryException; 057import org.ametys.plugins.repository.version.VersionAwareAmetysObject; 058 059/** 060 * Generate contents returned by the {@link SearchAction}. 061 */ 062public class SearchGenerator extends ServiceableGenerator implements Contextualizable 063{ 064 /** Constant for getting content in specific version label */ 065 public static final String CONTENT_VERSION_LABEL = "versionLabel"; 066 067 /** The server comm helper */ 068 protected ServerCommHelper _serverCommHelper; 069 /** The content values extractor factory. */ 070 protected ContentValuesExtractorFactory _valuesExtractorFactory; // FIXME not used 071 /** Helper for saxing result */ 072 protected ContentResultSetHelper _contentRSH; 073 /** Context */ 074 protected Context _context; 075 /** The search model manager */ 076 protected SearchUIModelExtensionPoint _searchModelManager; 077 /** The content type helper. */ 078 protected ContentTypesHelper _cTypeHelper; 079 /** The helper for columns */ 080 protected ColumnHelper _columnHelper; 081 /** The content type extension point */ 082 protected ContentTypeExtensionPoint _contentTypeExtensionPoint; 083 084 @Override 085 public void service(ServiceManager smanager) throws ServiceException 086 { 087 super.service(smanager); 088 _serverCommHelper = (ServerCommHelper) smanager.lookup(ServerCommHelper.ROLE); 089 _valuesExtractorFactory = (ContentValuesExtractorFactory) smanager.lookup(ContentValuesExtractorFactory.ROLE); 090 091 _contentRSH = (ContentResultSetHelper) smanager.lookup(ContentResultSetHelper.ROLE); 092 _searchModelManager = (SearchUIModelExtensionPoint) smanager.lookup(SearchUIModelExtensionPoint.ROLE); 093 094 _cTypeHelper = (ContentTypesHelper) smanager.lookup(ContentTypesHelper.ROLE); 095 _columnHelper = (ColumnHelper) smanager.lookup(ColumnHelper.ROLE); 096 _contentTypeExtensionPoint = (ContentTypeExtensionPoint) smanager.lookup(ContentTypeExtensionPoint.ROLE); 097 } 098 099 @Override 100 public void contextualize(Context context) throws ContextException 101 { 102 _context = context; 103 } 104 105 @SuppressWarnings("unchecked") 106 @Override 107 public void generate() throws IOException, SAXException, ProcessingException 108 { 109 Request request = ObjectModelHelper.getRequest(objectModel); 110 111 SearchResults<Content> results = (SearchResults<Content>) request.getAttribute(SearchAction.SEARCH_RESULTS); 112 SearchModel model = (SearchModel) request.getAttribute(SearchAction.SEARCH_MODEL); 113 114 Map<String, Object> jsParameters = _serverCommHelper.getJsParameters(); 115 116 Map<String, Object> contextualParameters = (Map<String, Object>) jsParameters.get("contextualParameters"); 117 if (contextualParameters == null) 118 { 119 contextualParameters = Collections.emptyMap(); 120 } 121 122 Locale defaultLocale = (Locale) request.getAttribute(SearchAction.SEARCH_LOCALE); 123 124 Collection< ? extends ResultField> resultFields = model.getResultFields(contextualParameters).values(); 125 126 String versionLabel = (String) jsParameters.get(CONTENT_VERSION_LABEL); 127 128 saxContents(model, results, resultFields, versionLabel, jsParameters, defaultLocale, contextualParameters); 129 } 130 131 /** 132 * Sax a set of contents 133 * @param model search model 134 * @param results contents 135 * @param resultFields fields to retreive 136 * @param versionLabel version label 137 * @param jsParameters parameters of the search 138 * @param defaultLocale The locale to use for localized values if content's language is null. 139 * @param contextualParameters The contextual parameters 140 * @throws SAXException if a error occurred during sax 141 * @throws AmetysRepositoryException if a error occurred 142 * @throws IOException if a error occurred 143 * @throws ProcessingException if a error occurred 144 */ 145 protected void saxContents(SearchModel model, SearchResults<Content> results, Collection< ? extends ResultField> resultFields, String versionLabel, Map<String, Object> jsParameters, Locale defaultLocale, Map<String, Object> contextualParameters) throws SAXException, AmetysRepositoryException, IOException, ProcessingException 146 { 147 // Common content type 148 Set<String> commonContentTypeIds = getCommonContentTypeIds(jsParameters, model, contextualParameters); 149 150 // Filter the resultFields, using columns if available 151 Collection<ResultField> resultFieldsFiltered = new ArrayList<>(); 152 List<String> columns = getColumnsFromParameters(jsParameters, commonContentTypeIds) 153 .stream() 154 .map(Column::getId) 155 .collect(Collectors.toList()); 156 for (ResultField resultField : resultFields) 157 { 158 if (columns.size() == 0 || columns.contains(resultField.getId())) 159 { 160 resultFieldsFiltered.add(resultField); 161 } 162 } 163 164 contentHandler.startDocument(); 165 XMLUtils.startElement(contentHandler, "contents"); 166 167 Iterable<SearchResult<Content>> contents = results.getResults(); 168 for (SearchResult<Content> result : contents) 169 { 170 Content content = result.getObject(); 171 172 if (StringUtils.isBlank(versionLabel) || switchToLabel(content, versionLabel)) 173 { 174 if (resultFieldsFiltered.size() > 0) 175 { 176 saxContent(content, resultFieldsFiltered, defaultLocale); 177 } 178 else 179 { 180 saxContent(content, resultFields, defaultLocale); 181 } 182 } 183 } 184 185 XMLUtils.endElement(contentHandler, "contents"); 186 contentHandler.endDocument(); 187 } 188 189 /** 190 * Get the common content type 191 * @param jsParameters The JS parameters 192 * @param model The search model 193 * @param contextualParameters The contextual parameters 194 * @return The common content type 195 */ 196 @SuppressWarnings("unchecked") 197 protected Set<String> getCommonContentTypeIds(Map<String, Object> jsParameters, SearchModel model, Map<String, Object> contextualParameters) 198 { 199 // Get content types from model... 200 Collection<String> contentTypes = model.getContentTypes(contextualParameters); 201 202 // ...or from the JS parameters 203 Map<String, Object> values = (Map<String, Object>) jsParameters.get("values"); 204 if (values != null && values.containsKey("contentTypes")) 205 { 206 contentTypes = (List<String>) values.get("contentTypes"); 207 208 } 209 210 // Retrieves the common ancestors of the content types 211 return _cTypeHelper.getCommonAncestors(contentTypes).stream() 212 .filter(_contentTypeExtensionPoint::hasExtension) 213 .collect(Collectors.toSet()); 214 } 215 216 /** 217 * Get the columns from JS parameters 218 * @param jsParameters The JS parameters 219 * @param contentTypeIds The content type identifiers 220 * @return the requested columns 221 */ 222 @SuppressWarnings("unchecked") 223 protected List<Column> getColumnsFromParameters(Map<String, Object> jsParameters, Set<String> contentTypeIds) 224 { 225 Map<String, Object> values = (Map<String, Object>) jsParameters.get("values"); 226 227 if (values != null && values.containsKey("columns")) 228 { 229 Object columnsAsObj = values.get("columns"); 230 if (columnsAsObj instanceof String) 231 { 232 return _columnHelper.getColumns((String) columnsAsObj, contentTypeIds); 233 } 234 else 235 { 236 return _columnHelper.getColumns((List<String>) columnsAsObj, contentTypeIds); 237 } 238 } 239 240 return List.of(); 241 } 242 243 /** 244 * Switch to the revision corresponding to the specified label. 245 * @param content The content 246 * @param label the label to switch to 247 * @return <code>true</code> if a revision with this label exists and the content was switch, <code>false</code> otherwise. 248 */ 249 protected boolean switchToLabel(Content content, String label) 250 { 251 if (content instanceof VersionAwareAmetysObject) 252 { 253 String[] allLabels = ((VersionAwareAmetysObject) content).getAllLabels(); 254 if (ArrayUtils.contains(allLabels, label)) 255 { 256 ((VersionAwareAmetysObject) content).switchToLabel(label); 257 return true; 258 } 259 } 260 261 return false; 262 } 263 264 /** 265 * SAX the result content 266 * @param content the result 267 * @param resultFields the result fields 268 * @param defaultLocale The locale to use for localized values if content's language is null. 269 * @throws SAXException if a error occurred during sax 270 * @throws AmetysRepositoryException if a error occurred 271 * @throws IOException if a error occurred 272 */ 273 protected void saxContent(Content content, Collection<? extends ResultField> resultFields, Locale defaultLocale) throws SAXException, AmetysRepositoryException, IOException 274 { 275 Request request = ContextHelper.getRequest(_context); 276 277 try 278 { 279 request.setAttribute(Content.class.getName(), content); 280 281 AttributesImpl attrs = new AttributesImpl(); 282 attrs.addCDATAAttribute("id", content.getId()); 283 attrs.addCDATAAttribute("name", content.getName()); 284 attrs.addCDATAAttribute("title", content.getTitle(defaultLocale)); 285 if (content.getLanguage() != null) 286 { 287 attrs.addCDATAAttribute("language", content.getLanguage()); 288 } 289 290 XMLUtils.startElement(contentHandler, "content", attrs); 291 _contentRSH.saxResultFields(contentHandler, content, resultFields, defaultLocale); 292 XMLUtils.endElement(contentHandler, "content"); 293 } 294 finally 295 { 296 request.setAttribute(Content.class.getName(), null); 297 } 298 } 299}