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