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}