001/* 002 * Copyright 2018 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.web.frontoffice.search.requesttime.impl; 017 018import java.io.IOException; 019import java.time.ZonedDateTime; 020import java.time.format.DateTimeFormatter; 021import java.util.ArrayList; 022import java.util.List; 023import java.util.Locale; 024 025import org.apache.avalon.framework.service.ServiceException; 026import org.apache.avalon.framework.service.ServiceManager; 027import org.apache.avalon.framework.service.Serviceable; 028import org.apache.cocoon.components.source.impl.SitemapSource; 029import org.apache.cocoon.environment.Request; 030import org.apache.cocoon.environment.Response; 031import org.apache.cocoon.xml.XMLUtils; 032import org.apache.commons.lang3.StringUtils; 033import org.apache.excalibur.source.SourceResolver; 034import org.slf4j.Logger; 035import org.xml.sax.ContentHandler; 036import org.xml.sax.SAXException; 037 038import org.ametys.cms.repository.Content; 039import org.ametys.cms.search.SearchResult; 040import org.ametys.cms.search.SearchResults; 041import org.ametys.core.util.IgnoreRootHandler; 042import org.ametys.plugins.repository.AmetysObject; 043import org.ametys.web.frontoffice.search.instance.SearchServiceInstance; 044import org.ametys.web.frontoffice.search.instance.model.RightCheckingMode; 045import org.ametys.web.frontoffice.search.metamodel.impl.ContentReturnable; 046import org.ametys.web.frontoffice.search.requesttime.SearchComponent; 047import org.ametys.web.frontoffice.search.requesttime.SearchComponentArguments; 048import org.ametys.web.renderingcontext.RenderingContext; 049import org.ametys.web.renderingcontext.RenderingContextHandler; 050import org.ametys.web.repository.site.Site; 051 052/** 053 * {@link SearchComponent} for saxing results for RSS feed 054 */ 055public class RssSearchComponent implements SearchComponent, Serviceable 056{ 057 private static final String __ENABLE_RSS_PARAMETER_NAME = "rss"; 058 private static final String __RSS_VIEW_PARAMETER_NAME = "rssView"; 059 private static final String __RSS_VIEW_PARAMETER_DEFAULT_VALUE = "abstract"; 060 private SourceResolver _resolver; 061 private RenderingContextHandler _renderingContextHandler; 062 063 @Override 064 public void service(ServiceManager manager) throws ServiceException 065 { 066 _resolver = (SourceResolver) manager.lookup(SourceResolver.ROLE); 067 _renderingContextHandler = (RenderingContextHandler) manager.lookup(RenderingContextHandler.ROLE); 068 } 069 070 @Override 071 public int priority() 072 { 073 return MIN_PRIORITY - 500; 074 } 075 076 @Override 077 public boolean supports(SearchComponentArguments args) 078 { 079 return args.generatorParameters().getParameterAsBoolean(__ENABLE_RSS_PARAMETER_NAME, false); 080 } 081 082 @Override 083 public void execute(SearchComponentArguments args) throws Exception 084 { 085 SearchResults<AmetysObject> results = args.results().orElseThrow(() -> new IllegalStateException("Results have not been set yet.")); 086 ContentHandler contentHandler = args.contentHandler(); 087 String view = args.generatorParameters().getParameter(__RSS_VIEW_PARAMETER_NAME, _getRssViewParameterDefaultValue(args)); 088 Site site = args.currentSite(); 089 Logger logger = args.logger(); 090 SearchServiceInstance serviceInstance = args.serviceInstance(); 091 String title = serviceInstance.getTitle(); 092 093 _setRssHeader(serviceInstance, args.response()); 094 _setSiteAttribute(args.request(), site); 095 _saxRssFeed(contentHandler, title, results, view, site, logger); 096 } 097 098 private String _getRssViewParameterDefaultValue(SearchComponentArguments args) 099 { 100 String view = args.serviceInstance().getAdditionalParameterValues().getValue(ContentReturnable.PARAMETER_VIEW); 101 return view == null ? __RSS_VIEW_PARAMETER_DEFAULT_VALUE : view; 102 } 103 104 private void _setRssHeader(SearchServiceInstance serviceInstance, Response response) 105 { 106 boolean isCacheable = serviceInstance.getRightCheckingMode() == RightCheckingMode.FAST; 107 if (isCacheable) 108 { 109 response.setHeader("X-Ametys-Cacheable", "true"); 110 } 111 } 112 113 private void _setSiteAttribute(Request request, Site currentSite) 114 { 115 final String siteAttrName = "site"; 116 if (request.getAttribute(siteAttrName) == null) 117 { 118 request.setAttribute(siteAttrName, currentSite.getName()); 119 } 120 } 121 122 private void _saxRssFeed(ContentHandler contentHandler, String title, SearchResults<AmetysObject> results, String view, Site site, Logger logger) throws Exception 123 { 124 XMLUtils.startElement(contentHandler, "RssFeed"); 125 126 if (StringUtils.isNotEmpty(title)) 127 { 128 XMLUtils.createElement(contentHandler, "title", title); 129 } 130 131 DateTimeFormatter dtfRFC = DateTimeFormatter.ofPattern("EEE, d MMM yyyy HH:mm:ss Z").withLocale(Locale.ENGLISH); 132 XMLUtils.createElement(contentHandler, "pubDate", dtfRFC.format(ZonedDateTime.now())); 133 134 List<String> noContentResults = new ArrayList<>(); 135 for (SearchResult<AmetysObject> searchResult : results.getResults()) 136 { 137 AmetysObject object = searchResult.getObject(); 138 if (object instanceof Content) 139 { 140 _saxContentItem(contentHandler, (Content) object, view, site.getName()); 141 } 142 else 143 { 144 noContentResults.add(object.getId()); 145 } 146 } 147 if (!noContentResults.isEmpty()) 148 { 149 logger.warn("{}\n could not be SAXed. Search results which are not Contents are not yet handled by RSS.", noContentResults); 150 } 151 152 XMLUtils.endElement(contentHandler, "RssFeed"); 153 } 154 155 private void _saxContentItem(ContentHandler contentHandler, Content content, String view, String siteName) throws SAXException, IOException 156 { 157 RenderingContext currentContext = _renderingContextHandler.getRenderingContext(); 158 SitemapSource src = null; 159 try 160 { 161 if (currentContext == RenderingContext.BACK) 162 { 163 // Issue CMS-2642 164 _renderingContextHandler.setRenderingContext(RenderingContext.PREVIEW); 165 } 166 167 String uri = "cocoon:/_content.rss?contentId=" + content.getId() + "&viewName=" + view + "&siteName=" + siteName; 168 169 src = (SitemapSource) _resolver.resolveURI(uri); 170 src.toSAX(new IgnoreRootHandler(contentHandler)); 171 } 172 finally 173 { 174 if (src != null) 175 { 176 _resolver.release(src); 177 } 178 _renderingContextHandler.setRenderingContext(currentContext); 179 } 180 } 181}