View Javadoc

1   /*
2    * Copyright 2006 University Corporation for Advanced Internet Development, Inc.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.opensaml.util.resource;
18  
19  import java.util.ArrayList;
20  import java.util.List;
21  import java.util.TimerTask;
22  
23  import org.joda.time.DateTime;
24  import org.opensaml.util.resource.ResourceChangeListener.ResourceChange;
25  import org.slf4j.Logger;
26  import org.slf4j.LoggerFactory;
27  
28  /**
29   * A watcher that invokes a callback when a resource update/deletion has been detected.
30   */
31  public class ResourceChangeWatcher extends TimerTask {
32  
33      /** Default polling frequency, 12 hours. */
34      public static final long DEFAULT_POLL_FREQUENCY = 1000 * 60 * 60 * 12;
35  
36      /** Default maximum retry attempts, 0. */
37      public static final int DEFAULT_MAX_RETRY_ATTEMPTS = 0;
38  
39      /** Class logger. */
40      private final Logger log = LoggerFactory.getLogger(ResourceChangeWatcher.class);
41  
42      /** Resource being watched. */
43      private Resource watchedResource;
44  
45      /** Frequency, in milliseconds, the resource is polled for changes. */
46      private long pollFrequency;
47  
48      /** Max number of polls to try before considering the resource inaccessible. */
49      private int maxRetryAttempts;
50  
51      /** Number of times the resource has been polled but generated an error. */
52      private int currentRetryAttempts;
53  
54      /** Whether the resource currently exists. */
55      private boolean resourceExist;
56  
57      /** Last time the resource was modified. */
58      private DateTime lastModification;
59  
60      /** Registered listeners of resource change notifications. */
61      private List<ResourceChangeListener> resourceListeners;
62  
63      /**
64       * Constructor.
65       * 
66       * @param resource the resource to be watched
67       * 
68       * @throws ResourceException thrown if resource existence or last modification time can not be determined
69       */
70      public ResourceChangeWatcher(Resource resource) throws ResourceException {
71          this(resource, DEFAULT_POLL_FREQUENCY, DEFAULT_MAX_RETRY_ATTEMPTS);
72      }
73  
74      /**
75       * Constructor.
76       * 
77       * @param resource the resource to be watched
78       * @param pollingFrequency the frequency, in milliseconds, to poll the resource for changes
79       * 
80       * @throws ResourceException thrown if resource existence or last modification time can not be determined
81       */
82      public ResourceChangeWatcher(Resource resource, long pollingFrequency) throws ResourceException {
83          this(resource, pollingFrequency, DEFAULT_MAX_RETRY_ATTEMPTS);
84      }
85  
86      /**
87       * Constructor.
88       * 
89       * @param resource the resource to be watched
90       * @param pollingFrequency the frequency, in milliseconds, to poll the resource for changes
91       * @param retryAttempts maximum number of poll attempts before the resource is considered inaccessible
92       * 
93       * @throws ResourceException thrown if resource existence or last modification time can not be determined
94       */
95      public ResourceChangeWatcher(Resource resource, long pollingFrequency, int retryAttempts) throws ResourceException {
96          if (resource == null) {
97              throw new NullPointerException("Watched resource is null");
98          }
99  
100         if (pollingFrequency <= 0) {
101             throw new IllegalArgumentException("Polling frequency must be greater than zero");
102         }
103 
104         if (retryAttempts < 0) {
105             throw new IllegalArgumentException("Max retry attempts must be greater than, or equal to, zero");
106         }
107 
108         watchedResource = resource;
109         pollFrequency = pollingFrequency;
110         maxRetryAttempts = retryAttempts;
111         currentRetryAttempts = 0;
112 
113         if (watchedResource.exists()) {
114             resourceExist = true;
115             lastModification = watchedResource.getLastModifiedTime();
116         } else {
117             resourceExist = false;
118         }
119 
120         resourceListeners = new ArrayList<ResourceChangeListener>(5);
121         log.debug("Watching resource: " + watchedResource.getLocation()
122                 + ", polling frequency: {}ms, max retry attempts: {}", pollFrequency, maxRetryAttempts);
123     }
124 
125     /**
126      * Gets the frequency, in milliseconds, the watched resource should be polled.
127      * 
128      * @return frequency the watched resource should be polled
129      */
130     public long getPollingFrequency() {
131         return pollFrequency;
132     }
133 
134     /**
135      * Gets the list of registered resource listeners. New listeners may be registered with the list or old ones
136      * removed.
137      * 
138      * @return list of registered resource listeners
139      */
140     public List<ResourceChangeListener> getResourceListeners() {
141         return resourceListeners;
142     }
143 
144     /** {@inheritDoc} */
145     public void run() {
146         try {
147             log.trace("Checking resource for changes: {}", watchedResource.getLocation());
148             if (watchedResource.exists()) {
149                 if (!resourceExist) {
150                     resourceExist = true;
151                     signalListeners(ResourceChange.CREATION);
152                     lastModification = watchedResource.getLastModifiedTime();
153                 } else {
154                     if (lastModification.isBefore(watchedResource.getLastModifiedTime())) {
155                         signalListeners(ResourceChange.UPDATE);
156                         lastModification = watchedResource.getLastModifiedTime();
157                     }
158                 }
159             } else {
160                 if (resourceExist) {
161                     resourceExist = false;
162                     signalListeners(ResourceChange.DELETE);
163                 }
164             }
165             currentRetryAttempts = 0;
166         } catch (ResourceException e) {
167             log.warn("Resource " + watchedResource.getLocation() + " could not be accessed", e);
168             currentRetryAttempts++;
169             if (currentRetryAttempts >= maxRetryAttempts) {
170                 cancel();
171                 log.error("Resource " + watchedResource.getLocation()
172                             + " was not accessible for max number of retry attempts.  This resource will no longer be watched");
173             }
174         }
175     }
176 
177     /**
178      * Signals all registered listeners of a resource change.
179      * 
180      * @param changeType the resource change type
181      */
182     protected void signalListeners(ResourceChange changeType) {
183         synchronized (resourceListeners) {
184             switch (changeType) {
185                 case CREATION:
186                     log.debug("Publishing creation event for resource: {}", watchedResource.getLocation());
187                     for (ResourceChangeListener listener : resourceListeners) {
188                         listener.onResourceCreate(watchedResource);
189                     }
190                     break;
191                 case UPDATE:
192                     log.debug("Publishing update event for resource: {}", watchedResource.getLocation());
193                     for (ResourceChangeListener listener : resourceListeners) {
194                         listener.onResourceUpdate(watchedResource);
195                     }
196                     break;
197                 case DELETE:
198                     log.debug("Publishing delete event for resource: {}", watchedResource.getLocation());
199                     for (ResourceChangeListener listener : resourceListeners) {
200                         listener.onResourceDelete(watchedResource);
201                     }
202                     break;
203                 default:
204                     break;
205             }
206         }
207     }
208 }