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.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.repository.Content; 044import org.ametys.cms.search.SearchResult; 045import org.ametys.cms.search.SearchResults; 046import org.ametys.cms.search.content.ContentValuesExtractorFactory; 047import org.ametys.cms.search.model.ResultField; 048import org.ametys.cms.search.model.SearchModel; 049import org.ametys.cms.search.solr.CriteriaSearchUIModelWrapper.Column; 050import org.ametys.cms.search.solr.SolrQuerySearchAction; 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; 055 056/** 057 * Generate contents returned by the {@link SearchAction}. 058 */ 059public class SearchGenerator extends ServiceableGenerator implements Contextualizable 060{ 061 /** Constant for getting content in specific version label */ 062 public static final String CONTENT_VERSION_LABEL = "versionLabel"; 063 064 /** The server comm helper */ 065 protected ServerCommHelper _serverCommHelper; 066 /** The content values extractor factory. */ 067 protected ContentValuesExtractorFactory _valuesExtractorFactory; // FIXME not used 068 /** Helper for saxing result */ 069 protected ContentResultSetHelper _contentRSH; 070 /** Context */ 071 protected Context _context; 072 /** The search model manager */ 073 protected SearchUIModelExtensionPoint _searchModelManager; 074 075 @Override 076 public void service(ServiceManager smanager) throws ServiceException 077 { 078 super.service(smanager); 079 _serverCommHelper = (ServerCommHelper) smanager.lookup(ServerCommHelper.ROLE); 080 _valuesExtractorFactory = (ContentValuesExtractorFactory) smanager.lookup(ContentValuesExtractorFactory.ROLE); 081 082 _contentRSH = (ContentResultSetHelper) smanager.lookup(ContentResultSetHelper.ROLE); 083 _searchModelManager = (SearchUIModelExtensionPoint) smanager.lookup(SearchUIModelExtensionPoint.ROLE); 084 } 085 086 @Override 087 public void contextualize(Context context) throws ContextException 088 { 089 _context = context; 090 } 091 092 @SuppressWarnings("unchecked") 093 @Override 094 public void generate() throws IOException, SAXException, ProcessingException 095 { 096 Request request = ObjectModelHelper.getRequest(objectModel); 097 098 SearchResults<Content> results = (SearchResults<Content>) request.getAttribute(SearchAction.SEARCH_RESULTS); 099 SearchModel model = (SearchModel) request.getAttribute(SearchAction.SEARCH_MODEL); 100 101 Map<String, Object> jsParameters = _serverCommHelper.getJsParameters(); 102 103 Map<String, Object> contextualParameters = (Map<String, Object>) jsParameters.get("contextualParameters"); 104 if (contextualParameters == null) 105 { 106 contextualParameters = Collections.emptyMap(); 107 } 108 109 Locale defaultLocale = (Locale) request.getAttribute(SearchAction.SEARCH_LOCALE); 110 111 Collection< ? extends ResultField> resultFields = model.getResultFields(contextualParameters).values(); 112 113 String versionLabel = (String) jsParameters.get(CONTENT_VERSION_LABEL); 114 115 saxContents(model, results, resultFields, versionLabel, jsParameters, defaultLocale); 116 } 117 /** 118 * Sax a set of contents 119 * @param model search model 120 * @param results contents 121 * @param resultFields fields to retreive 122 * @param versionLabel version label 123 * @param jsParameters parameters of the search 124 * @param defaultLocale The locale to use for localized values if content's language is null. 125 * @throws SAXException if a error occurred during sax 126 * @throws AmetysRepositoryException if a error occurred 127 * @throws IOException if a error occurred 128 * @throws ProcessingException if a error occurred 129 */ 130 protected void saxContents(SearchModel model, SearchResults<Content> results, Collection< ? extends ResultField> resultFields, String versionLabel, Map<String, Object> jsParameters, Locale defaultLocale) throws SAXException, AmetysRepositoryException, IOException, ProcessingException 131 { 132 // Filter the resultFields, using columns if available 133 Collection<ResultField> resultFieldsFiltered = new ArrayList<>(); 134 List<String> columns = getColumnsFromParameters(jsParameters).stream().map(Column::getId).collect(Collectors.toList()); 135 for (ResultField resultField : resultFields) 136 { 137 if (columns.size() == 0 || columns.contains(resultField.getId())) 138 { 139 resultFieldsFiltered.add(resultField); 140 } 141 } 142 143 contentHandler.startDocument(); 144 XMLUtils.startElement(contentHandler, "contents"); 145 146 Iterable<SearchResult<Content>> contents = results.getResults(); 147 for (SearchResult<Content> result : contents) 148 { 149 Content content = result.getObject(); 150 151 if (StringUtils.isBlank(versionLabel) || switchToLabel(content, versionLabel)) 152 { 153 if (resultFieldsFiltered.size() > 0) 154 { 155 saxContent(content, resultFieldsFiltered, defaultLocale); 156 } 157 else 158 { 159 saxContent(content, resultFields, defaultLocale); 160 } 161 } 162 } 163 164 XMLUtils.endElement(contentHandler, "contents"); 165 contentHandler.endDocument(); 166 } 167 168 /** 169 * Get the columns from JS parameters 170 * @param jsParameters The JS parameters 171 * @return the requested columns 172 */ 173 @SuppressWarnings("unchecked") 174 protected List<Column> getColumnsFromParameters(Map<String, Object> jsParameters) 175 { 176 Map<String, Object> values = (Map<String, Object>) jsParameters.get("values"); 177 178 if (values != null && values.containsKey("columns")) 179 { 180 Object columnsAsObj = values.get("columns"); 181 if (columnsAsObj instanceof String) 182 { 183 return SolrQuerySearchAction.getColumns((String) columnsAsObj); 184 } 185 else 186 { 187 return SolrQuerySearchAction.getColumns((List<String>) columnsAsObj); 188 } 189 } 190 return Collections.EMPTY_LIST; 191 } 192 193 /** 194 * Switch to the revision corresponding to the specified label. 195 * @param content The content 196 * @param label the label to switch to 197 * @return <code>true</code> if a revision with this label exists and the content was switch, <code>false</code> otherwise. 198 */ 199 protected boolean switchToLabel(Content content, String label) 200 { 201 if (content instanceof VersionAwareAmetysObject) 202 { 203 String[] allLabels = ((VersionAwareAmetysObject) content).getAllLabels(); 204 if (ArrayUtils.contains(allLabels, label)) 205 { 206 ((VersionAwareAmetysObject) content).switchToLabel(label); 207 return true; 208 } 209 } 210 211 return false; 212 } 213 214 /** 215 * SAX the result content 216 * @param content the result 217 * @param resultFields the result fields 218 * @param defaultLocale The locale to use for localized values if content's language is null. 219 * @throws SAXException if a error occurred during sax 220 * @throws AmetysRepositoryException if a error occurred 221 * @throws IOException if a error occurred 222 */ 223 protected void saxContent(Content content, Collection<? extends ResultField> resultFields, Locale defaultLocale) throws SAXException, AmetysRepositoryException, IOException 224 { 225 Request request = ContextHelper.getRequest(_context); 226 227 try 228 { 229 request.setAttribute(Content.class.getName(), content); 230 231 AttributesImpl attrs = new AttributesImpl(); 232 attrs.addCDATAAttribute("id", content.getId()); 233 attrs.addCDATAAttribute("name", content.getName()); 234 attrs.addCDATAAttribute("title", content.getTitle(defaultLocale)); 235 if (content.getLanguage() != null) 236 { 237 attrs.addCDATAAttribute("language", content.getLanguage()); 238 } 239 240 XMLUtils.startElement(contentHandler, "content", attrs); 241 _contentRSH.saxResultFields(contentHandler, content, resultFields, defaultLocale); 242 XMLUtils.endElement(contentHandler, "content"); 243 } 244 finally 245 { 246 request.setAttribute(Content.class.getName(), null); 247 } 248 } 249}