001/* 002 * Copyright 2017 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.List; 022import java.util.Locale; 023import java.util.Map; 024import java.util.Set; 025import java.util.stream.Collectors; 026 027import org.apache.avalon.framework.container.ContainerUtil; 028import org.apache.avalon.framework.service.ServiceException; 029import org.apache.avalon.framework.service.ServiceManager; 030import org.apache.cocoon.ProcessingException; 031import org.apache.cocoon.components.ContextHelper; 032import org.apache.cocoon.components.source.SourceUtil; 033import org.apache.cocoon.environment.Request; 034import org.apache.cocoon.xml.AttributesImpl; 035import org.apache.cocoon.xml.XMLUtils; 036import org.apache.commons.lang3.StringUtils; 037import org.apache.excalibur.source.Source; 038import org.xml.sax.SAXException; 039 040import org.ametys.cms.contenttype.ContentType; 041import org.ametys.cms.repository.Content; 042import org.ametys.cms.search.GroupSearchContentHelper; 043import org.ametys.cms.search.SearchResults; 044import org.ametys.cms.search.model.ResultField; 045import org.ametys.cms.search.model.SearchModel; 046import org.ametys.cms.search.solr.CriteriaSearchUIModelWrapper; 047import org.ametys.cms.search.ui.model.ColumnHelper.Column; 048import org.ametys.cms.search.ui.model.SearchUIColumn; 049import org.ametys.cms.search.ui.model.SearchUIModel; 050import org.ametys.core.util.AvalonLoggerAdapter; 051import org.ametys.core.util.IgnoreRootHandler; 052import org.ametys.plugins.repository.AmetysRepositoryException; 053 054/** 055 * Generate contents returned by the {@link SearchAction}, groups by selected fields. 056 */ 057public class DocSearchGenerator extends SearchGenerator 058{ 059 private GroupSearchContentHelper _groupSearchContentHelper; 060 061 @Override 062 public void service(ServiceManager smanager) throws ServiceException 063 { 064 super.service(smanager); 065 _groupSearchContentHelper = (GroupSearchContentHelper) smanager.lookup(GroupSearchContentHelper.ROLE); 066 } 067 068 @Override 069 protected void saxContents(SearchModel model, SearchResults<Content> results, Collection< ? extends ResultField> resultFields, String versionLabel, Map<String, Object> jsParameters, Locale locale, Map<String, Object> contextualParameters) throws SAXException, AmetysRepositoryException, IOException, ProcessingException 070 { 071 String modelId = (String) jsParameters.get("model"); 072 SearchUIModel uiModel = _searchModelManager.getExtension(modelId); 073 074 Set<String> cTypeIds = getCommonContentTypeIds(jsParameters, uiModel, contextualParameters); 075 076 List<String> groupingFields = _getGroupingFields(jsParameters); 077 078 // Columns 079 List<Column> columns = getColumnsFromParameters(jsParameters, cTypeIds); 080 081 Collection< ? extends ResultField> resultFieldsFiltered = _filterResultFields(groupingFields, columns.stream().map(Column::getId).collect(Collectors.toList()), resultFields); 082 083 CriteriaSearchUIModelWrapper modelWrapper = new CriteriaSearchUIModelWrapper(uiModel, manager, _context, new AvalonLoggerAdapter(getLogger())); 084 085 try 086 { 087 ContainerUtil.service(modelWrapper, manager); 088 modelWrapper.setResultColumns(cTypeIds, columns, contextualParameters); 089 } 090 catch (Exception e) 091 { 092 throw new AmetysRepositoryException(e); 093 } 094 Map<String, SearchUIColumn> uiResultFields = modelWrapper.getResultFields(contextualParameters); 095 096 Set<ContentType> contentTypes = cTypeIds.stream() 097 .map(_contentTypeExtensionPoint::getExtension) 098 .collect(Collectors.toSet()); 099 GroupSearchContent groupSearchContent = _groupSearchContentHelper.organizeContentsInGroups(results.getObjects(), groupingFields, contentTypes, locale); 100 101 // Set the request attributes from the contextual parameters 102 Request request = ContextHelper.getRequest(_context); 103 for (String name : contextualParameters.keySet()) 104 { 105 request.setAttribute(name, contextualParameters.get(name)); 106 } 107 108 XMLUtils.startElement(contentHandler, "contents"); 109 _saxGroup(groupSearchContent, 0, resultFieldsFiltered, uiResultFields, locale); 110 XMLUtils.endElement(contentHandler, "contents"); 111 } 112 113 private List<String> _getGroupingFields(Map<String, Object> jsParameters) 114 { 115 @SuppressWarnings("unchecked") 116 List<String> groupingFields = (List<String>) jsParameters.get("groupingFields"); 117 if (groupingFields == null || groupingFields.size() == 1 && StringUtils.isEmpty(groupingFields.get(0))) 118 { 119 groupingFields = new ArrayList<>(); 120 } 121 122 return groupingFields; 123 } 124 125 private Collection< ? extends ResultField> _filterResultFields(List<String> groupingFields, List<String> columns, Collection< ? extends ResultField> resultFields) 126 { 127 // Remove grouping fields from the result fields (they don't need to be displayed) and only keep columns requested 128 Collection<ResultField> resultFieldsFiltered = new ArrayList<>(); 129 for (ResultField resultField : resultFields) 130 { 131 if (!groupingFields.contains(resultField.getId()) && (columns.size() == 0 || columns.contains(resultField.getId()))) 132 { 133 resultFieldsFiltered.add(resultField); 134 } 135 } 136 137 return resultFieldsFiltered; 138 } 139 140 /** 141 * Sax a group of content recursively. 142 * First level (level=0) will not create a xml group (usually, 1st group is just here to handle a list) 143 * @param group group to sax 144 * @param level current level (start with zero) 145 * @param resultFields result fields to sax 146 * @param uiResultFields The columns to be exported 147 * @param locale The locale for search. Can be null. 148 * @throws SAXException if a error occurred during sax 149 * @throws AmetysRepositoryException if a error occurred 150 * @throws IOException if a error occurred 151 * @throws ProcessingException if a error occurred 152 */ 153 private void _saxGroup(GroupSearchContent group, int level, Collection< ? extends ResultField> resultFields, Map<String, SearchUIColumn> uiResultFields, Locale locale) throws SAXException, AmetysRepositoryException, IOException, ProcessingException 154 { 155 if (level > 0) 156 { 157 AttributesImpl attrs = new AttributesImpl(); 158 attrs.addCDATAAttribute("level", String.valueOf(level)); 159 attrs.addCDATAAttribute("fullModelPath", group.getGroupFieldPath()); 160 attrs.addCDATAAttribute("value", group.getGroupName()); 161 XMLUtils.startElement(contentHandler, "group", attrs); 162 } 163 164 for (GroupSearchContent groupSearchContent : group.getSubList()) 165 { 166 _saxGroup(groupSearchContent, level + 1, resultFields, uiResultFields, locale); 167 } 168 169 for (Content content : group.getContents()) 170 { 171 _saxContentForDoc(content, resultFields, uiResultFields, locale); 172 } 173 174 if (level > 0) 175 { 176 XMLUtils.endElement(contentHandler, "group"); 177 } 178 } 179 /** 180 * SAX the result content 181 * @param content the result 182 * @param resultFields the result fields 183 * @param uiResultFields The columns to be exported 184 * @param locale The locale for search. Can be null. 185 * @throws SAXException if a error occurred during sax 186 * @throws AmetysRepositoryException if a error occurred 187 * @throws IOException if a error occurred 188 * @throws ProcessingException if a error occurred 189 */ 190 private void _saxContentForDoc(Content content, Collection<? extends ResultField> resultFields, Map<String, SearchUIColumn> uiResultFields, Locale locale) throws SAXException, AmetysRepositoryException, IOException, ProcessingException 191 { 192 Request request = ContextHelper.getRequest(_context); 193 194 try 195 { 196 request.setAttribute(Content.class.getName(), content); 197 request.setAttribute(ResultField.class.getName(), resultFields); 198 request.setAttribute("viewName", "export-word"); 199 request.setAttribute("forceRemoteUrl", true); 200 201 request.setAttribute("uiResultFields", uiResultFields); 202 203 AttributesImpl attrs = new AttributesImpl(); 204 attrs.addCDATAAttribute("id", content.getId()); 205 attrs.addCDATAAttribute("name", content.getName()); 206 attrs.addCDATAAttribute("title", content.getTitle(locale)); 207 if (content.getLanguage() != null) 208 { 209 attrs.addCDATAAttribute("language", content.getLanguage()); 210 } 211 212 XMLUtils.startElement(contentHandler, "content", attrs); 213 Source contentSource = resolver.resolveURI("cocoon:/export/content.doc" + (locale != null ? "?lang=" + locale.getLanguage() : "")); 214 SourceUtil.toSAX(contentSource, new IgnoreRootHandler(contentHandler)); 215 216 XMLUtils.endElement(contentHandler, "content"); 217 } 218 finally 219 { 220 request.setAttribute(Content.class.getName(), null); 221 request.removeAttribute("forceRemoteUrl"); 222 } 223 } 224 225}