001/*
002 *  Copyright 2010 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.tags.inputdata;
017
018import java.io.IOException;
019import java.io.InputStream;
020import java.util.ArrayList;
021import java.util.List;
022
023import org.apache.avalon.framework.activity.Initializable;
024import org.apache.avalon.framework.configuration.Configuration;
025import org.apache.avalon.framework.configuration.DefaultConfigurationBuilder;
026import org.apache.avalon.framework.context.Context;
027import org.apache.avalon.framework.context.ContextException;
028import org.apache.avalon.framework.context.Contextualizable;
029import org.apache.avalon.framework.logger.AbstractLogEnabled;
030import org.apache.avalon.framework.service.ServiceException;
031import org.apache.avalon.framework.service.ServiceManager;
032import org.apache.avalon.framework.service.Serviceable;
033import org.apache.avalon.framework.thread.ThreadSafe;
034import org.apache.cocoon.ProcessingException;
035import org.apache.cocoon.components.ContextHelper;
036import org.apache.cocoon.environment.Request;
037import org.apache.cocoon.xml.XMLUtils;
038import org.apache.excalibur.source.Source;
039import org.apache.excalibur.source.SourceResolver;
040import org.xml.sax.ContentHandler;
041import org.xml.sax.SAXException;
042
043import org.ametys.cms.contenttype.ContentTypeExtensionPoint;
044import org.ametys.cms.tag.TagProviderExtensionPoint;
045import org.ametys.core.cache.AbstractCacheManager;
046import org.ametys.core.cache.Cache;
047import org.ametys.core.util.filereloader.FileReloader;
048import org.ametys.core.util.filereloader.FileReloaderUtils;
049import org.ametys.plugins.repository.AmetysObjectResolver;
050import org.ametys.runtime.i18n.I18nizableText;
051import org.ametys.runtime.plugin.component.PluginAware;
052import org.ametys.web.content.GetSiteAction;
053import org.ametys.web.filter.ContentFilterHelper;
054import org.ametys.web.filter.StaticWebContentFilter;
055import org.ametys.web.filter.WebContentFilter;
056import org.ametys.web.filter.WebContentFilter.AccessLimitation;
057import org.ametys.web.inputdata.InputData;
058import org.ametys.web.repository.page.Page;
059import org.ametys.web.repository.site.Site;
060import org.ametys.web.repository.site.SiteManager;
061
062/**
063 * This class generates an output with all existing filters defined on context's current page.
064 */
065public class FilteredContentsInputData extends AbstractLogEnabled implements InputData, Serviceable, Contextualizable, ThreadSafe, PluginAware, FileReloader, Initializable
066{
067    private static final String _CACHE_ID = FilteredContentsInputData.class.getName();
068    
069    /** The plugin name */
070    protected String _pluginName;
071    /** The source resolver */
072    protected SourceResolver _sourceResolver;
073    /** The repository */
074    protected AmetysObjectResolver _resolver;
075    /** The extension point for content types */
076    protected ContentTypeExtensionPoint _contentTypeEP;
077    /** The avalon context. */
078    protected Context _context;
079    /** The site manager */
080    protected SiteManager _siteManager;
081    /** The tag provider EP */
082    protected TagProviderExtensionPoint _tagProviderEP;
083
084    private ContentFilterHelper _filterHelper;
085    private FileReloaderUtils _fileReloaderUtils;
086    private AbstractCacheManager _cacheManager;
087   
088    @Override
089    public void service(ServiceManager smanager) throws ServiceException
090    {
091        _sourceResolver = (SourceResolver) smanager.lookup(SourceResolver.ROLE);
092        _resolver = (AmetysObjectResolver) smanager.lookup(AmetysObjectResolver.ROLE);
093        _contentTypeEP = (ContentTypeExtensionPoint) smanager.lookup(ContentTypeExtensionPoint.ROLE);
094        _filterHelper = (ContentFilterHelper) smanager.lookup(ContentFilterHelper.ROLE);
095        _siteManager = (SiteManager) smanager.lookup(SiteManager.ROLE);
096        _tagProviderEP = (TagProviderExtensionPoint) smanager.lookup(TagProviderExtensionPoint.ROLE);
097        _fileReloaderUtils = (FileReloaderUtils) smanager.lookup(FileReloaderUtils.ROLE);
098        _cacheManager = (AbstractCacheManager) smanager.lookup(AbstractCacheManager.ROLE);
099    }
100    
101    @Override
102    public void initialize() throws Exception
103    {
104        _cacheManager.createMemoryCache(_CACHE_ID, 
105                new I18nizableText("plugin.web", "PLUGINS_WEB_FILTERED_CONTENTS_INPUT_DATA_CACHE_LABEL"),
106                new I18nizableText("plugin.web", "PLUGINS_WEB_FILTERED_CONTENTS_INPUT_DATA_CACHE_DESCRIPTION"),
107                true,
108                null);
109    }
110    
111    @Override
112    public boolean isCacheable(Site site, Page page)
113    {
114        try
115        {
116            List<WebContentFilter> filters = configureFilters(site, page);
117            return filters.stream()
118                          .map(WebContentFilter::getAccessLimitation)
119                          .noneMatch(limitation -> limitation == AccessLimitation.USER_ACCESS);
120        }
121        catch (Exception e)
122        {
123            // unable to parse filters file
124            throw new IllegalArgumentException(e);
125        }
126    }
127    
128    @Override
129    public void toSAX(ContentHandler contentHandler) throws SAXException, ProcessingException
130    {
131        Page page = (Page) ContextHelper.getRequest(_context).getAttribute(Page.class.getName());
132        String siteName = (String) ContextHelper.getRequest(_context).getAttribute("site");
133        String lang = (String) ContextHelper.getRequest(_context).getAttribute("sitemapLanguage");
134        
135        contentHandler.startDocument();
136        XMLUtils.startElement(contentHandler, "Model");
137        
138        try
139        {
140            List<WebContentFilter> filters = configureFilters(_siteManager.getSite(siteName), page);
141            for (WebContentFilter filter : filters)
142            {
143                saxFilter(contentHandler, filter, siteName, lang, page);
144            }
145        }
146        catch (Exception e)
147        {
148            throw new ProcessingException("An error occurred while performing search on filtered contents", e);
149        }
150        
151        XMLUtils.endElement(contentHandler, "Model");
152        contentHandler.endDocument();
153    }
154    
155    /**
156     * Retrieves the configured filters
157     * @param site the current {@link Site}
158     * @param page the current {@link Page}
159     * @return The list of filters
160     * @throws Exception If an error occurred while parsing the file.
161     */
162    protected List<WebContentFilter> configureFilters(Site site, Page page) throws Exception
163    {
164        String sourceUri = "skin:" + site.getSkinId() + "://templates/" + _getTemplate(page) + "/filters/default.xml"; 
165        
166        boolean forceReload = !_getCache().hasKey(sourceUri);
167        _fileReloaderUtils.updateFile(sourceUri, forceReload, this);
168        
169        return _getCache().get(sourceUri);
170    }
171    
172    private String _getTemplate(Page page)
173    {
174        // We can have a null page on specific pages (like login page)
175        if (page != null)
176        {
177            return page.getTemplate();
178        }
179        
180        Request request = ContextHelper.getRequest(_context);
181        return (String) request.getAttribute("template");
182    }
183    
184    /**
185     * SAX a filter
186     * @param contentHandler The content handler
187     * @param filter The filter to SAX
188     * @param siteName The current site name
189     * @param lang The current language
190     * @param page The current page
191     * @throws SAXException If an errors occurs while SAXing
192     * @throws IOException If an errors occurs
193     */
194    @SuppressWarnings("deprecation")
195    protected void saxFilter (ContentHandler contentHandler, WebContentFilter filter, String siteName, String lang, Page page)  throws SAXException, IOException
196    {
197        Request request = ContextHelper.getRequest(_context);
198        String currentSiteName = (String) request.getAttribute("site");
199        String currentSkinName = (String) request.getAttribute("skin");
200        String currentTemplateName = (String) request.getAttribute("template");
201        String currentLanguage = (String) request.getAttribute("renderingLanguage");
202        
203        // Issue CMS-3391
204        request.setAttribute(GetSiteAction.OVERRIDE_SITE_REQUEST_ATTR, currentSiteName);
205        request.setAttribute(GetSiteAction.OVERRIDE_SKIN_REQUEST_ATTR, currentSkinName);
206        
207        try
208        {
209            String id = filter.getId();
210            
211            XMLUtils.startElement(contentHandler, id);
212            _filterHelper.saxMatchingContents(contentHandler, filter, siteName, lang, page);
213            XMLUtils.endElement(contentHandler, id);
214        }
215        finally
216        {
217            request.removeAttribute(GetSiteAction.OVERRIDE_SITE_REQUEST_ATTR);
218            request.removeAttribute(GetSiteAction.OVERRIDE_SKIN_REQUEST_ATTR);
219            request.setAttribute("site", currentSiteName);
220            request.setAttribute("skin", currentSkinName);
221            request.setAttribute("template", currentTemplateName);
222            request.setAttribute("renderingLanguage", currentLanguage);
223        }
224    }
225    
226    @Override
227    public void contextualize(Context context) throws ContextException
228    {
229        _context = context;
230    }
231
232    @Override
233    public void setPluginInfo(String pluginName, String featureName, String id)
234    {
235        _pluginName = pluginName;
236    }
237    
238    public String getId(String sourceUrl)
239    {
240        return FilteredContentsInputData.class.getName() + "#" + sourceUrl;
241    }
242    
243    public void updateFile(String sourceUrl, Source source, InputStream is) throws Exception
244    {
245        List<WebContentFilter> filters = new ArrayList<>();
246        
247        if (is != null)
248        {
249            Configuration configuration = new DefaultConfigurationBuilder().build(source.getInputStream());
250            Configuration[] filterConfiguration = configuration.getChildren();
251            
252            for (Configuration filterConf : filterConfiguration)
253            {
254                if ("content".equals(filterConf.getAttribute("target")))
255                {
256                    StaticWebContentFilter filter = new StaticWebContentFilter (filterConf.getAttribute("id"), _resolver, _contentTypeEP, _siteManager, _tagProviderEP);
257                    filter.configure(filterConf);
258                    
259                    filters.add(filter);
260                }
261            }
262        }    
263        
264        _getCache().put(sourceUrl, filters);
265    }
266    
267    private Cache<String, List<WebContentFilter>> _getCache()
268    {
269        return _cacheManager.get(_CACHE_ID);
270    }
271}