    001// Copyright 2006, 2007, 2008, 2009, 2011 The Apache Software Foundation
    002//
    003// Licensed under the Apache License, Version 2.0 (the "License");
    004// you may not use this file except in compliance with the License.
    005// You may obtain a copy of the License at
    006//
    007// http://www.apache.org/licenses/LICENSE-2.0
    008//
    009// Unless required by applicable law or agreed to in writing, software
    010// distributed under the License is distributed on an "AS IS" BASIS,
    011// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    012// See the License for the specific language governing permissions and
    013// limitations under the License.
    014
    015package org.apache.tapestry5.internal.services;
    016
    017import org.apache.tapestry5.SymbolConstants;
    018import org.apache.tapestry5.beanmodel.services.*;
    019import org.apache.tapestry5.commons.util.TimeInterval;
    020import org.apache.tapestry5.http.services.Request;
    021import org.apache.tapestry5.http.services.RequestFilter;
    022import org.apache.tapestry5.http.services.RequestHandler;
    023import org.apache.tapestry5.http.services.Response;
    024import org.apache.tapestry5.internal.util.Holder;
    025import org.apache.tapestry5.ioc.Invokable;
    026import org.apache.tapestry5.ioc.annotations.IntermediateType;
    027import org.apache.tapestry5.ioc.annotations.Symbol;
    028import org.apache.tapestry5.ioc.internal.util.ConcurrentBarrier;
    029import org.apache.tapestry5.ioc.services.UpdateListenerHub;
    030
    031import java.io.IOException;
    032import java.util.concurrent.TimeUnit;
    033
    034/**
    035 * Implements a barrier that periodically asks the {@link org.apache.tapestry5.ioc.services.UpdateListenerHub} to check for
    036 * updates to files. The UpdateListenerHub is invoked from a write method, meaning that when it is called, all other
    037 * threads will be blocked.
    038 */
    039public class CheckForUpdatesFilter implements RequestFilter
    040{
    041    private final long checkInterval;
    042
    043    private final long updateTimeout;
    044
    045    private final UpdateListenerHub updateListenerHub;
    046
    047    private final ConcurrentBarrier barrier = new ConcurrentBarrier();
    048
    049    private final Runnable checker = new Runnable()
    050    {
    051        public void run()
    052        {
    053            // On a race condition, multiple threads may hit this method briefly. If we've
    054            // already done a check, don't run it again.
    055
    056            if (System.currentTimeMillis() - lastCheck >= checkInterval)
    057            {
    058
    059                // Fire the update event which will force a number of checks and then
    060                // corresponding invalidation events.
    061
    062                updateListenerHub.fireCheckForUpdates();
    063
    064                lastCheck = System.currentTimeMillis();
    065            }
    066        }
    067    };
    068
    069    private long lastCheck = 0;
    070
    071    /**
    072     * @param updateListenerHub
    073     *            invoked, at intervals, to spur the process of detecting changes
    074     * @param checkInterval
    075     *            interval, in milliseconds, between checks
    076     * @param updateTimeout
    077     *            time, in milliseconds, to wait to obtain update lock.
    078     */
    079    public CheckForUpdatesFilter(UpdateListenerHub updateListenerHub,
    080
    081    @Symbol(SymbolConstants.FILE_CHECK_INTERVAL)
    082    @IntermediateType(TimeInterval.class)
    083    long checkInterval,
    084
    085    @Symbol(SymbolConstants.FILE_CHECK_UPDATE_TIMEOUT)
    086    @IntermediateType(TimeInterval.class)
    087    long updateTimeout)
    088    {
    089        this.updateListenerHub = updateListenerHub;
    090        this.checkInterval = checkInterval;
    091        this.updateTimeout = updateTimeout;
    092    }
    093
    094    public boolean service(final Request request, final Response response, final RequestHandler handler)
    095            throws IOException
    096    {
    097        final Holder<IOException> exceptionHolder = new Holder<IOException>();
    098
    099        Invokable<Boolean> invokable = new Invokable<Boolean>()
    100        {
    101            public Boolean invoke()
    102            {
    103                if (System.currentTimeMillis() - lastCheck >= checkInterval)
    104                    barrier.tryWithWrite(checker, updateTimeout, TimeUnit.MILLISECONDS);
    105
    106                // And, now, back to code within the read lock.
    107
    108                try
    109                {
    110                    return handler.service(request, response);
    111                }
    112                catch (IOException ex)
    113                {
    114                    exceptionHolder.put(ex);
    115                    return false;
    116                }
    117            }
    118        };
    119
    120        // Obtain a read lock while handling the request. This will not impair parallel operations, except when a file
    121        // check
    122        // is needed (the exclusive write lock will block threads attempting to get a read lock).
    123
    124        boolean result = barrier.withRead(invokable);
    125
    126        if (exceptionHolder.hasValue())
    127            throw exceptionHolder.get();
    128
    129        return result;
    130    }
    131}