    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.internal.util.Holder;
    019import org.apache.tapestry5.ioc.Invokable;
    020import org.apache.tapestry5.ioc.annotations.IntermediateType;
    021import org.apache.tapestry5.ioc.annotations.Symbol;
    022import org.apache.tapestry5.ioc.internal.util.ConcurrentBarrier;
    023import org.apache.tapestry5.ioc.util.TimeInterval;
    024import org.apache.tapestry5.services.*;
    025
    026import java.io.IOException;
    027import java.util.concurrent.TimeUnit;
    028
    029/**
    030 * Implements a barrier that periodically asks the {@link org.apache.tapestry5.services.UpdateListenerHub} to check for
    031 * updates to files. The UpdateListenerHub is invoked from a write method, meaning that when it is called, all other
    032 * threads will be blocked.
    033 */
    034public class CheckForUpdatesFilter implements RequestFilter
    035{
    036    private final long checkInterval;
    037
    038    private final long updateTimeout;
    039
    040    private final UpdateListenerHub updateListenerHub;
    041
    042    private final ConcurrentBarrier barrier = new ConcurrentBarrier();
    043
    044    private final Runnable checker = new Runnable()
    045    {
    046        public void run()
    047        {
    048            // On a race condition, multiple threads may hit this method briefly. If we've
    049            // already done a check, don't run it again.
    050
    051            if (System.currentTimeMillis() - lastCheck >= checkInterval)
    052            {
    053
    054                // Fire the update event which will force a number of checks and then
    055                // corresponding invalidation events.
    056
    057                updateListenerHub.fireCheckForUpdates();
    058
    059                lastCheck = System.currentTimeMillis();
    060            }
    061        }
    062    };
    063
    064    private long lastCheck = 0;
    065
    066    /**
    067     * @param updateListenerHub
    068     *            invoked, at intervals, to spur the process of detecting changes
    069     * @param checkInterval
    070     *            interval, in milliseconds, between checks
    071     * @param updateTimeout
    072     *            time, in milliseconds, to wait to obtain update lock.
    073     */
    074    public CheckForUpdatesFilter(UpdateListenerHub updateListenerHub,
    075
    076    @Symbol(SymbolConstants.FILE_CHECK_INTERVAL)
    077    @IntermediateType(TimeInterval.class)
    078    long checkInterval,
    079
    080    @Symbol(SymbolConstants.FILE_CHECK_UPDATE_TIMEOUT)
    081    @IntermediateType(TimeInterval.class)
    082    long updateTimeout)
    083    {
    084        this.updateListenerHub = updateListenerHub;
    085        this.checkInterval = checkInterval;
    086        this.updateTimeout = updateTimeout;
    087    }
    088
    089    public boolean service(final Request request, final Response response, final RequestHandler handler)
    090            throws IOException
    091    {
    092        final Holder<IOException> exceptionHolder = new Holder<IOException>();
    093
    094        Invokable<Boolean> invokable = new Invokable<Boolean>()
    095        {
    096            public Boolean invoke()
    097            {
    098                if (System.currentTimeMillis() - lastCheck >= checkInterval)
    099                    barrier.tryWithWrite(checker, updateTimeout, TimeUnit.MILLISECONDS);
    100
    101                // And, now, back to code within the read lock.
    102
    103                try
    104                {
    105                    return handler.service(request, response);
    106                }
    107                catch (IOException ex)
    108                {
    109                    exceptionHolder.put(ex);
    110                    return false;
    111                }
    112            }
    113        };
    114
    115        // Obtain a read lock while handling the request. This will not impair parallel operations, except when a file
    116        // check
    117        // is needed (the exclusive write lock will block threads attempting to get a read lock).
    118
    119        boolean result = barrier.withRead(invokable);
    120
    121        if (exceptionHolder.hasValue())
    122            throw exceptionHolder.get();
    123
    124        return result;
    125    }
    126}