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