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.inputdata;
017
018import java.io.InputStream;
019
020import org.apache.avalon.framework.activity.Initializable;
021import org.apache.avalon.framework.configuration.Configuration;
022import org.apache.avalon.framework.configuration.DefaultConfigurationBuilder;
023import org.apache.avalon.framework.context.Context;
024import org.apache.avalon.framework.context.ContextException;
025import org.apache.avalon.framework.context.Contextualizable;
026import org.apache.avalon.framework.service.ServiceException;
027import org.apache.avalon.framework.service.ServiceManager;
028import org.apache.avalon.framework.service.Serviceable;
029import org.apache.cocoon.ProcessingException;
030import org.apache.cocoon.components.ContextHelper;
031import org.apache.cocoon.environment.Request;
032import org.apache.excalibur.source.Source;
033import org.xml.sax.ContentHandler;
034import org.xml.sax.SAXException;
035
036import org.ametys.core.cache.AbstractCacheManager;
037import org.ametys.core.cache.Cache;
038import org.ametys.core.util.filereloader.FileReloader;
039import org.ametys.core.util.filereloader.FileReloaderUtils;
040import org.ametys.plugins.core.impl.cache.AbstractCacheKey;
041import org.ametys.runtime.i18n.I18nizableText;
042import org.ametys.runtime.plugin.component.AbstractLogEnabled;
043import org.ametys.web.WebConstants;
044import org.ametys.web.pageaccess.RestrictedPagePolicy;
045import org.ametys.web.repository.page.Page;
046import org.ametys.web.repository.site.Site;
047import org.ametys.web.repository.site.SiteManager;
048
049/**
050 * {@link InputData} for SAXing events about the current sitemap.
051 */
052public class SitemapInputData extends AbstractLogEnabled implements InputData, Contextualizable, Serviceable, FileReloader, Initializable
053{
054    private static final String __INITIAL_DEPTH = "initial-depth";
055    private static final String __DESCENDANT_DEPTH = "descendant-depth";
056    
057    private static final String _CACHE_ID = SitemapInputData.class.getName();
058    
059    private static final int __DEFAULT_INITIAL_DEPTH = 2;
060    private static final int __DEFAULT_DESCENDANT_DEPTH = 1;
061    
062    private Context _context;
063    private SitemapSaxer _sitemapSaxer;
064    private FileReloaderUtils _fileReloaderUtils;
065    
066    private SiteManager _siteManager;
067
068    private AbstractCacheManager _cacheManager;
069    
070    static class SitemapKey extends AbstractCacheKey
071    {
072        SitemapKey(String skinId, String depth)
073        {
074            super(skinId, depth);
075        }
076        static SitemapKey of(String skinId, String depth)
077        {
078            return new SitemapKey(skinId, depth);
079        }
080    }
081    
082    @Override
083    public void service(ServiceManager manager) throws ServiceException
084    {
085        _sitemapSaxer = (SitemapSaxer) manager.lookup(SitemapSaxer.ROLE);
086        _fileReloaderUtils = (FileReloaderUtils) manager.lookup(FileReloaderUtils.ROLE);
087        _siteManager = (SiteManager) manager.lookup(SiteManager.ROLE);
088        _cacheManager = (AbstractCacheManager) manager.lookup(AbstractCacheManager.ROLE);
089    }
090
091    @Override
092    public void initialize() throws Exception
093    {
094        _cacheManager.createMemoryCache(_CACHE_ID, 
095                new I18nizableText("plugin.web", "PLUGINS_WEB_SITE_MAP_INPUT_DATA_CACHE_LABEL"),
096                new I18nizableText("plugin.web", "PLUGINS_WEB_SITE_MAP_INPUT_DATA_CACHE_DESCRIPTION"),
097                true,
098                null);
099    }
100    
101    @Override
102    public void contextualize(Context context) throws ContextException
103    {
104        _context = context;
105    }
106
107    @Override
108    public boolean isCacheable(Site site, Page page)
109    {
110        return site.getRestrictedPagePolicy() == RestrictedPagePolicy.DISPLAYED;
111    }
112    
113    public boolean shouldBeCached(Site site, Page page)
114    {
115        // Sometimes we return true to #isCacheable because the whole page is cacheable 
116        // but we don't use general cache, so here we always answer yes
117        return false;
118    }
119    
120    @Override
121    public void toSAX(ContentHandler handler) throws SAXException, ProcessingException
122    {
123        Request request = ContextHelper.getRequest(_context);
124        
125        String siteName = (String) request.getAttribute(WebConstants.REQUEST_ATTR_SITE_NAME);
126        String sitemapName = (String) request.getAttribute(WebConstants.REQUEST_ATTR_SITEMAP_NAME);
127        
128        Page page = (Page) request.getAttribute(WebConstants.REQUEST_ATTR_PAGE);
129        
130        String skinId = _getSkinId();
131        _loadSitemapConfiguration(!_getCache().hasKey(SitemapKey.of(skinId, __INITIAL_DEPTH)));
132        
133        int initialDepth = _getCache().get(SitemapKey.of(skinId, __INITIAL_DEPTH));
134        int descendantDepth = _getCache().get(SitemapKey.of(skinId, __DESCENDANT_DEPTH));
135        
136        _sitemapSaxer.toSAX(handler, siteName, sitemapName, page, initialDepth, descendantDepth);
137    }
138    
139    private void _loadSitemapConfiguration(boolean forceRead)
140    {
141        try
142        {
143            String file = "skin://conf/sitemap.xml";
144            _fileReloaderUtils.updateFile(file, forceRead, this);
145        }
146        catch (Exception e)
147        {
148            getLogger().error("An error occurred reading the sitemap configuration file {}.", "skin://conf/sitemap.xml", e);
149        }
150    }
151    
152    public String getId(String sourceUrl)
153    {
154        return SitemapInputData.class.getName() + "#" + _getSkinId();
155    }
156    
157    private String _getSkinId()
158    {
159        Request request = ContextHelper.getRequest(_context);
160        
161        String skinId = (String) request.getAttribute(WebConstants.REQUEST_ATTR_SKIN_ID);
162        
163        if (skinId == null)
164        {
165            String siteName = (String) request.getAttribute(WebConstants.REQUEST_ATTR_SITE_NAME);
166            Site site = _siteManager.getSite(siteName);
167            return site.getSkinId();
168        }
169        else
170        {
171            return skinId;
172        }
173    }
174    
175    public void updateFile(String sourceUrl, Source source, InputStream is) throws Exception
176    {
177        int initialDepth = __DEFAULT_INITIAL_DEPTH;
178        int descendantDepth = __DEFAULT_DESCENDANT_DEPTH;
179        
180        if (is != null)
181        {
182            Configuration cfg = new DefaultConfigurationBuilder().build(is);
183            initialDepth = _parseInt(cfg, __INITIAL_DEPTH, __DEFAULT_INITIAL_DEPTH);
184            descendantDepth = _parseInt(cfg, __DESCENDANT_DEPTH, __DEFAULT_DESCENDANT_DEPTH);
185        }
186        
187        String skinId = _getSkinId();
188        _getCache().put(SitemapKey.of(skinId, __INITIAL_DEPTH), initialDepth);
189        _getCache().put(SitemapKey.of(skinId, __DESCENDANT_DEPTH), descendantDepth);
190    }
191    
192    private int _parseInt(Configuration cfg, String tagName, int defaultValue)
193    {
194        return cfg.getChild(tagName, true).getValueAsInteger(defaultValue);
195    }
196
197    private Cache<SitemapKey, Integer> _getCache()
198    {
199        return _cacheManager.get(_CACHE_ID);
200    }
201}