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