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    
015    package org.apache.tapestry5.internal.services;
016    
017    import org.apache.tapestry5.SymbolConstants;
018    import org.apache.tapestry5.internal.util.Holder;
019    import org.apache.tapestry5.ioc.Invokable;
020    import org.apache.tapestry5.ioc.annotations.IntermediateType;
021    import org.apache.tapestry5.ioc.annotations.Symbol;
022    import org.apache.tapestry5.ioc.internal.util.ConcurrentBarrier;
023    import org.apache.tapestry5.ioc.util.TimeInterval;
024    import org.apache.tapestry5.services.*;
025    
026    import java.io.IOException;
027    import 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     */
034    public 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    }