001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.wicket.util.watch;
018:
019: import java.util.ArrayList;
020: import java.util.HashMap;
021: import java.util.Iterator;
022: import java.util.Map;
023: import java.util.Set;
024:
025: import org.apache.wicket.util.listener.ChangeListenerSet;
026: import org.apache.wicket.util.listener.IChangeListener;
027: import org.apache.wicket.util.thread.ICode;
028: import org.apache.wicket.util.thread.Task;
029: import org.apache.wicket.util.time.Duration;
030: import org.apache.wicket.util.time.Time;
031: import org.slf4j.Logger;
032: import org.slf4j.LoggerFactory;
033:
034: /**
035: * Monitors one or more Modifiable objects, calling a ChangeListener when a
036: * given object's modification time changes.
037: *
038: * @author Jonathan Locke
039: */
040: public final class ModificationWatcher {
041: /** Logging */
042: private static final Logger log = LoggerFactory
043: .getLogger(ModificationWatcher.class);
044:
045: /** Maps Modifiable objects to Entry objects */
046: private final Map modifiableToEntry = new HashMap();
047:
048: private Task task;
049:
050: // MarkupContainer class for holding modifiable entries to watch
051: private static final class Entry {
052: // The most recent lastModificationTime polled on the object
053: Time lastModifiedTime;
054:
055: // The set of listeners to call when the modifiable changes
056: final ChangeListenerSet listeners = new ChangeListenerSet();
057:
058: // The modifiable thing
059: IModifiable modifiable;
060: }
061:
062: /**
063: * For two-phase construction
064: */
065: public ModificationWatcher() {
066: }
067:
068: /**
069: * Constructor
070: *
071: * @param pollFrequency
072: * How often to check on modifiables
073: */
074: public ModificationWatcher(final Duration pollFrequency) {
075: start(pollFrequency);
076: }
077:
078: /**
079: * Adds a Modifiable object and an IChangeListener to call when the
080: * modifiable object is modified.
081: *
082: * @param modifiable
083: * The modifiable thing to monitor
084: * @param listener
085: * The listener to call if the modifiable is modified
086: * @return <tt>true</tt> if the set did not already contain the specified
087: * 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 Logger 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: task.interrupt();
182: }
183: }
184:
185: /**
186: * @return Gets all IModifiable entries currently maintained
187: */
188: public final Set getEntries() {
189: return this.modifiableToEntry.keySet();
190: }
191: }
|