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}