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