001: /*
002: * $Id: ModificationWatcher.java 460636 2006-05-19 09:55:28Z jcompagner $
003: * $Revision: 460636 $ $Date: 2006-05-19 11:55:28 +0200 (Fri, 19 May 2006) $
004: *
005: * ==============================================================================
006: * Licensed under the Apache License, Version 2.0 (the "License"); you may not
007: * use this file except in compliance with the License. You may obtain a copy of
008: * the License at
009: *
010: * http://www.apache.org/licenses/LICENSE-2.0
011: *
012: * Unless required by applicable law or agreed to in writing, software
013: * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
014: * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
015: * License for the specific language governing permissions and limitations under
016: * the License.
017: */
018: package wicket.util.watch;
019:
020: import java.util.ArrayList;
021: import java.util.HashMap;
022: import java.util.Iterator;
023: import java.util.Map;
024:
025: import org.apache.commons.logging.Log;
026: import org.apache.commons.logging.LogFactory;
027:
028: import wicket.util.listener.ChangeListenerSet;
029: import wicket.util.listener.IChangeListener;
030: import wicket.util.thread.ICode;
031: import wicket.util.thread.Task;
032: import wicket.util.time.Duration;
033: import wicket.util.time.Time;
034:
035: /**
036: * Monitors one or more Modifiable objects, calling a ChangeListener when a
037: * given object's modification time changes.
038: *
039: * @author Jonathan Locke
040: */
041: public final class ModificationWatcher {
042: /** Logging */
043: private static final Log log = LogFactory
044: .getLog(ModificationWatcher.class);
045:
046: /** Maps Modifiable objects to Entry objects */
047: private final Map modifiableToEntry = new HashMap();
048:
049: private Task task;
050:
051: // MarkupContainer class for holding modifiable entries to watch
052: private static final class Entry {
053: // The most recent lastModificationTime polled on the object
054: Time lastModifiedTime;
055:
056: // The set of listeners to call when the modifiable changes
057: final ChangeListenerSet listeners = new ChangeListenerSet();
058:
059: // The modifiable thing
060: IModifiable modifiable;
061: }
062:
063: /**
064: * For two-phase construction
065: */
066: public ModificationWatcher() {
067: }
068:
069: /**
070: * Constructor
071: *
072: * @param pollFrequency
073: * How often to check on modifiables
074: */
075: public ModificationWatcher(final Duration pollFrequency) {
076: start(pollFrequency);
077: }
078:
079: /**
080: * Adds a Modifiable object and an IChangeListener to call when the
081: * modifiable object is modified.
082: *
083: * @param modifiable
084: * The modifiable thing to monitor
085: * @param listener
086: * The listener to call if the modifiable is modified
087: * @return <tt>true</tt> if the set did not already contain the specified element.
088: */
089: public final boolean add(final IModifiable modifiable,
090: final IChangeListener listener) {
091: // Look up entry for modifiable
092: final Entry entry = (Entry) modifiableToEntry.get(modifiable);
093:
094: // Found it?
095: if (entry == null) {
096: if (modifiable.lastModifiedTime() != null) {
097: // Construct new entry
098: final Entry newEntry = new Entry();
099:
100: newEntry.modifiable = modifiable;
101: newEntry.lastModifiedTime = modifiable
102: .lastModifiedTime();
103: newEntry.listeners.add(listener);
104:
105: // Put in map
106: modifiableToEntry.put(modifiable, newEntry);
107: } else {
108: // The IModifiable is not returning a valid lastModifiedTime
109: log.info("Cannot track modifications to resource "
110: + modifiable);
111: }
112:
113: return true;
114: } else {
115: // Add listener to existing entry
116: return entry.listeners.add(listener);
117: }
118: }
119:
120: /**
121: * Remove all entries associated with 'modifiable'
122: *
123: * @param modifiable
124: * @return the object removed, else null
125: */
126: public IModifiable remove(final IModifiable modifiable) {
127: final Entry entry = (Entry) modifiableToEntry
128: .remove(modifiable);
129: if (entry != null) {
130: return entry.modifiable;
131: }
132: return null;
133: }
134:
135: /**
136: * Start watching at a given polling rate
137: *
138: * @param pollFrequency
139: * The poll rate
140: */
141: public void start(final Duration pollFrequency) {
142: // Construct task with the given polling frequency
143: task = new Task("ModificationWatcher");
144:
145: task.run(pollFrequency, new ICode() {
146: public void run(final Log log) {
147: // Iterate over a copy of the list of entries to avoid
148: // concurrent
149: // modification problems without the associated liveness issues
150: // of holding a lock while potentially polling file times!
151: for (final Iterator iterator = new ArrayList(
152: modifiableToEntry.values()).iterator(); iterator
153: .hasNext();) {
154: // Get next entry
155: final Entry entry = (Entry) iterator.next();
156:
157: // If the modifiable has been modified after the last known
158: // modification time
159: final Time modifiableLastModified = entry.modifiable
160: .lastModifiedTime();
161:
162: if (modifiableLastModified
163: .after(entry.lastModifiedTime)) {
164: // Notify all listeners that the modifiable was modified
165: entry.listeners.notifyListeners();
166:
167: // Update timestamp
168: entry.lastModifiedTime = modifiableLastModified;
169: }
170: }
171: }
172: });
173: }
174:
175: /**
176: * stops the modification watcher from watching.
177: */
178: public void destroy() {
179: if (task != null) {
180: task.stop();
181: }
182: }
183: }
|