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    
102    @Override
103    public void contextualize(Context context) throws ContextException
104    {
105        _context = context;
106    }
107
108    @Override
109    public boolean isCacheable(Site site, Page page)
110    {
111        return site.getRestrictedPagePolicy() == RestrictedPagePolicy.DISPLAYED;
112    }
113    
114    public boolean shouldBeCached(Site site, Page page)
115    {
116        // Sometimes we return true to #isCacheable because the whole page is cacheable 
117        // but we don't use general cache, so here we always answer yes
118        return false;
119    }
120    
121    @Override
122    public void toSAX(ContentHandler handler) throws SAXException, ProcessingException
123    {
124        Request request = ContextHelper.getRequest(_context);
125        
126        String siteName = (String) request.getAttribute(WebConstants.REQUEST_ATTR_SITE_NAME);
127        String sitemapName = (String) request.getAttribute(WebConstants.REQUEST_ATTR_SITEMAP_NAME);
128        
129        Page page = (Page) request.getAttribute(WebConstants.REQUEST_ATTR_PAGE);
130        
131        String skinId = _getSkinId();
132        _loadSitemapConfiguration(!_getCache().hasKey(SitemapKey.of(skinId, __INITIAL_DEPTH)));
133        
134        int initialDepth = _getCache().get(SitemapKey.of(skinId, __INITIAL_DEPTH));
135        int descendantDepth = _getCache().get(SitemapKey.of(skinId, __DESCENDANT_DEPTH));
136        
137        _sitemapSaxer.toSAX(handler, siteName, sitemapName, page, initialDepth, descendantDepth);
138    }
139    
140    private void _loadSitemapConfiguration(boolean forceRead)
141    {
142        try
143        {
144            String file = "skin://conf/sitemap.xml";
145            _fileReloaderUtils.updateFile(file, forceRead, this);
146        }
147        catch (Exception e)
148        {
149            getLogger().error("An error occurred reading the sitemap configuration file {}.", "skin://conf/sitemap.xml", e);
150        }
151    }
152    
153    public String getId(String sourceUrl)
154    {
155        return SitemapInputData.class.getName() + "#" + _getSkinId();
156    }
157    
158    private String _getSkinId()
159    {
160        Request request = ContextHelper.getRequest(_context);
161        
162        String skinId = (String) request.getAttribute(WebConstants.REQUEST_ATTR_SKIN_ID);
163        
164        if (skinId == null)
165        {
166            String siteName = (String) request.getAttribute(WebConstants.REQUEST_ATTR_SITE_NAME);
167            Site site = _siteManager.getSite(siteName);
168            return site.getSkinId();
169        }
170        else
171        {
172            return skinId;
173        }
174        
175    }
176    
177    public void updateFile(String sourceUrl, Source source, InputStream is) throws Exception
178    {
179        int initialDepth = __DEFAULT_INITIAL_DEPTH;
180        int descendantDepth = __DEFAULT_DESCENDANT_DEPTH;
181        
182        if (is != null)
183        {
184            Configuration cfg = new DefaultConfigurationBuilder().build(is);
185            initialDepth = _parseInt(cfg, __INITIAL_DEPTH, __DEFAULT_INITIAL_DEPTH);
186            descendantDepth = _parseInt(cfg, __DESCENDANT_DEPTH, __DEFAULT_DESCENDANT_DEPTH);
187        }
188        
189        String skinId = _getSkinId();
190        _getCache().put(SitemapKey.of(skinId, __INITIAL_DEPTH), initialDepth);
191        _getCache().put(SitemapKey.of(skinId, __DESCENDANT_DEPTH), descendantDepth);
192    }
193    
194    private int _parseInt(Configuration cfg, String tagName, int defaultValue)
195    {
196        return cfg.getChild(tagName, true).getValueAsInteger(defaultValue);
197    }
198
199    private Cache<SitemapKey, Integer> _getCache()
200    {
201        return _cacheManager.get(_CACHE_ID);
202    }
203}