001/*
002 *  Copyright 2011 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.site;
017
018import java.util.Map;
019
020import org.apache.avalon.framework.parameters.Parameters;
021import org.apache.avalon.framework.service.ServiceException;
022import org.apache.avalon.framework.thread.ThreadSafe;
023import org.apache.cocoon.acting.ServiceableAction;
024import org.apache.cocoon.environment.ObjectModelHelper;
025import org.apache.cocoon.environment.Redirector;
026import org.apache.cocoon.environment.Request;
027import org.apache.cocoon.environment.SourceResolver;
028import org.apache.excalibur.source.Source;
029import org.apache.excalibur.source.SourceNotFoundException;
030import org.apache.excalibur.source.TraversableSource;
031import org.apache.excalibur.source.impl.FileSource;
032import org.slf4j.Logger;
033import org.slf4j.LoggerFactory;
034
035import org.ametys.plugins.site.cache.monitoring.Constants;
036import org.ametys.plugins.site.cache.monitoring.process.access.ResourceAccessComponent;
037import org.ametys.plugins.site.cache.monitoring.process.access.impl.FrontResourceAccess;
038
039/**
040 * Blocks until the cacheable status is known.
041 */
042public class IsPageCacheableAction extends ServiceableAction implements ThreadSafe
043{
044    /** The cache access component. */
045    protected CacheAccessManager _cacheAccess;
046
047    /** The resource access monitoring component */
048    protected ResourceAccessComponent _resourceAccessComponent;
049
050    /** The source resolver. */
051    protected SourceResolver _resolver;
052
053    private final Logger _logger = LoggerFactory.getLogger("site.cache.log");
054
055    /**
056     * Get the cache access manager
057     * @return the CacheAccessManager
058     */
059    protected CacheAccessManager _getCacheAccessManager()
060    {
061        if (_cacheAccess == null)
062        {
063            try
064            {
065                _cacheAccess = (CacheAccessManager) manager.lookup(CacheAccessManager.ROLE);
066            }
067            catch (ServiceException e)
068            {
069                throw new IllegalStateException("Cannot get CacheAccessManager", e);
070            }
071        }
072        return _cacheAccess;
073    }
074
075    /**
076     * Get the resource access component
077     * @return the ResourceAccessComponent
078     */
079    protected ResourceAccessComponent _getResourceAccessComponent()
080    {
081        if (_resourceAccessComponent == null)
082        {
083            try
084            {
085                _resourceAccessComponent = (ResourceAccessComponent) manager.lookup(ResourceAccessComponent.ROLE);
086            }
087            catch (ServiceException e)
088            {
089                throw new IllegalStateException("Cannot get ResourceAccessComponent", e);
090            }
091        }
092        return _resourceAccessComponent;
093    }
094    
095    @Override
096    public Map act(Redirector redirector, SourceResolver resolver, Map objectModel, String source, Parameters parameters) throws Exception
097    {
098        Request request = ObjectModelHelper.getRequest(objectModel);
099
100        String method = request.getMethod();
101        
102        if (!method.equals("GET"))
103        {
104            return null;
105        }
106        
107        try
108        {
109            // Block until known if the page is cacheable.
110            boolean cacheable = _getCacheAccessManager().isCacheable(source);
111
112            if (_logger.isDebugEnabled())
113            {
114                String uniqueId = request.getHeader("UNIQUE_ID");
115                if (uniqueId != null)
116                {
117                    _logger.debug(uniqueId + " - cacheable : " + cacheable);
118                }
119            }
120
121            // Resource monitoring
122            FrontResourceAccess fra = (FrontResourceAccess) request.getAttribute(Constants.REQUEST_ATTRIBUTE_PAGEACCESS);
123            fra.setCacheable(cacheable);
124
125            // If the page isn't cacheable, unlock and generate (return null).
126            String resourceURI = parameters.getParameter("uri", "");
127            if (!cacheable)
128            {
129                _getResourceAccessComponent().addAccessRecord(fra);
130
131                _getCacheAccessManager().unlock(source);
132                return null;
133            }
134            // If cacheable and the resource exists, we don't have to generate it.
135            else if (cacheable && _resourceExists(resolver, resourceURI))
136            {
137                fra.setCacheHit2(true);
138                _getResourceAccessComponent().addAccessRecord(fra);
139
140                _getCacheAccessManager().unlock(source);
141                return EMPTY_MAP;
142            }
143
144            // Else, return null: the page will be generated into the cache and unlocked when it's done.
145            fra.setCacheHit2(false);
146            _getResourceAccessComponent().addAccessRecord(fra);
147
148            return null;
149        }
150        catch (Exception e)
151        {
152            _getCacheAccessManager().unlock(source);
153            throw e;
154        }
155    }
156
157    /**
158     * Tests if the resource exists.
159     * @param resolver the resolver.
160     * @param resourceURI the resource uri.
161     * @return true if the resource exists, false otherwise.
162     */
163    protected boolean _resourceExists(SourceResolver resolver, String resourceURI)
164    {
165        boolean exists = false;
166
167        Source source = null;
168        try
169        {
170            source = resolver.resolveURI(resourceURI);
171
172            if (source instanceof FileSource && !SiteCacheHelper.isValid((FileSource) source))
173            {
174                // Release the old source and replace it by a hashed file source.
175                source = SiteCacheHelper.getHashedFileSource(resolver, (FileSource) source);
176            }
177
178            if (source.exists() && (!(source instanceof TraversableSource) || !((TraversableSource) source).isCollection()))
179            {
180                exists = true;
181            }
182        }
183        catch (SourceNotFoundException e)
184        {
185            // Do not log
186        }
187        catch (Exception e)
188        {
189            getLogger().warn("Exception resolving resource " + resourceURI, e);
190        }
191        finally
192        {
193            if (source != null)
194            {
195                resolver.release(source);
196            }
197        }
198
199        return exists;
200    }
201}