001: /*
002:
003: This software is OSI Certified Open Source Software.
004: OSI Certified is a certification mark of the Open Source Initiative.
005:
006: The license (Mozilla version 1.0) can be read at the MMBase site.
007: See http://www.MMBase.org/license
008:
009: */
010:
011: package org.mmbase.util;
012:
013: import java.io.File;
014: import java.net.URL;
015: import java.util.*;
016:
017: import org.mmbase.core.event.*;
018: import org.mmbase.util.logging.*;
019: import org.mmbase.bridge.*;
020:
021: /**
022: * Like {@link org.mmbase.util.FileWatcher} but for Resources. If (one of the) file(s) to which the resource resolves
023: * to is added or changed, it's onChange will be triggered, if not a 'more important' wil was
024: * existing already. If a file is removed, and was the most important one, it will be removed from the filewatcher.
025: *
026: * @author Michiel Meeuwissen
027: * @since MMBase-1.8
028: * @version $Id: ResourceWatcher.java,v 1.19 2007/06/29 10:29:09 michiel Exp $
029: * @see org.mmbase.util.FileWatcher
030: * @see org.mmbase.util.ResourceLoader
031: */
032: public abstract class ResourceWatcher implements NodeEventListener {
033: private static final Logger log = Logging
034: .getLoggerInstance(ResourceWatcher.class);
035:
036: /**
037: * All instantiated ResourceWatchers. Only used until setResourceBuilder is called. Then it
038: * is set to null, and not used any more (also used in ResourceLoader).
039: *
040: */
041: static Set<ResourceWatcher> resourceWatchers = new HashSet<ResourceWatcher>();
042:
043: /**
044: * Considers all resource-watchers. Perhaps onChange must be called, because there is a node for this resource available now.
045: */
046: static void setResourceBuilder() {
047: synchronized (resourceWatchers) {
048: for (ResourceWatcher rw : resourceWatchers) {
049: if (rw.running) {
050: EventManager.getInstance().addEventListener(rw);
051: }
052: for (String resource : rw.resources) {
053: if (rw.mapNodeNumber(resource)) {
054: log
055: .service("ResourceBuilder is available now. Resource "
056: + resource
057: + " must be reloaded.");
058: rw.onChange(resource);
059:
060: }
061: }
062: }
063: }
064: resourceWatchers = null; // no need to store those any more.
065: }
066:
067: /**
068: * Delay setting used for the filewatchers.
069: */
070: private long delay = -1;
071:
072: /**
073: * All resources watched by this ResourceWatcher. A Set of Strings. Often, a ResourceWatcher would watch only one resource.
074: */
075: protected SortedSet<String> resources = new TreeSet<String>();
076:
077: /**
078: * When a resource is loaded from a Node, we must know which Nodes correspond to which
079: * resource. You could ask the node itself, but if the node happens to be deleted, then you
080: * can't know that any more. Used in {@link #notify(NodeEvent)}
081: */
082: protected Map<Integer, String> nodeNumberToResourceName = new HashMap<Integer, String>();
083:
084: /**
085: * Whether this ResourceWatcher has been started (see {@link #start})
086: */
087: private boolean running = false;
088:
089: /**
090: * Wrapped FileWatcher for watching the file-resources. ResourceName -> FileWatcher.
091: */
092: protected Map<String, FileWatcher> fileWatchers = new HashMap<String, FileWatcher>();
093:
094: /**
095: * The resource-loader associated with this ResourceWatcher.
096: */
097: protected ResourceLoader resourceLoader;
098:
099: /**
100: * Constructor.
101: */
102: protected ResourceWatcher(ResourceLoader rl) {
103: resourceLoader = rl;
104: if (resourceWatchers != null) {
105: synchronized (resourceWatchers) {
106: resourceWatchers.add(this );
107: }
108: }
109: }
110:
111: /**
112: * Constructor, defaulting to the Root ResourceLoader (see {@link ResourceLoader#getConfigurationRoot}).
113: */
114: protected ResourceWatcher() {
115: this (ResourceLoader.getConfigurationRoot());
116: }
117:
118: /**
119: * @return Unmodifiable set of String of watched resources
120: */
121: public Set<String> getResources() {
122: return Collections.unmodifiableSortedSet(resources);
123: }
124:
125: /**
126: * The associated ResourceLoader
127: */
128: public ResourceLoader getResourceLoader() {
129: return resourceLoader;
130: }
131:
132: /**
133: *
134: * @param resourceName The resource to be monitored.
135: */
136: public synchronized void add(String resourceName) {
137: if (resourceName == null || resourceName.equals("")) {
138: log.warn("Cannot watch resource '" + resourceName + "' "
139: + Logging.stackTrace());
140: return;
141: }
142: resources.add(resourceName);
143: if (log.isDebugEnabled()) {
144: log.debug("Started watching '" + resourceName
145: + "' for resource loader "
146: + resourceLoader.getContext());
147: log.trace("(now watching " + resources + ")");
148: }
149: if (running) {
150: createFileWatcher(resourceName);
151: mapNodeNumber(resourceName);
152: }
153: }
154:
155: /**
156: * If you resolved a resource already to an URL, you can still add it for watching.
157: */
158: public synchronized void add(URL url) {
159: if (url.getProtocol().equals(ResourceLoader.PROTOCOL)) {
160: String path = url.getPath();
161: add(path.substring(resourceLoader.getContext().getPath()
162: .length()));
163: } else {
164: throw new UnsupportedOperationException(
165: "Don't know how to watch "
166: + url
167: + " (Only URLs produced by ResourceLoader are supported)");
168: }
169: }
170:
171: /**
172: * When a resource is added to this ResourceWatcher, this method is called to create a
173: * {@link FileWatcher}, and add all files associated with the resource to it.
174: */
175: protected synchronized void createFileWatcher(String resource) {
176: FileWatcher fileWatcher = new ResourceFileWatcher(resource);
177: if (delay != -1) {
178: fileWatcher.setDelay(delay);
179: }
180: fileWatcher.getFiles()
181: .addAll(resourceLoader.getFiles(resource));
182: fileWatcher.start(); // filewatchers are only created on start, so must always be started themselves.
183: fileWatchers.put(resource, fileWatcher);
184: }
185:
186: /**
187: * When a resource is added to this ResourceWatcher, this method is called to check wether a
188: * ResourceBuilder node is associated with this resource. If so, this methods maps the number of
189: * the node to the resource name. This is needed in {@link #notify(NodeEvent)} in case of a
190: * node-deletion.
191: * @return Whether a Node as found to map.
192: */
193: protected synchronized boolean mapNodeNumber(String resource) {
194: Node node = resourceLoader.getResourceNode(resource);
195: if (node != null) {
196: nodeNumberToResourceName.put(node.getNumber(), resource);
197: return true;
198: } else {
199: return false;
200: }
201:
202: }
203:
204: /**
205: * If a node (of the type 'resourceBuilder') changes, checks if it is a node belonging to one of the resource of this resource-watcher.
206: * If so, {@link #onChange} is called.
207: */
208: public void notify(NodeEvent event) {
209: if (event.getBuilderName().equals("resources")) {
210: int number = event.getNodeNumber();
211: switch (event.getType()) {
212: case NodeEvent.TYPE_DELETE: {
213: // hard..
214: String name = nodeNumberToResourceName.get(number);
215: if (name != null && resources.contains(name)) {
216: nodeNumberToResourceName.remove(number);
217: log.service("Resource " + name
218: + " changed (node removed)");
219: onChange(name);
220: }
221: break;
222: }
223: default: {
224: Node node = ResourceLoader.resourceBuilder.getCloud()
225: .getNode(number);
226: int contextPrefix = resourceLoader.getContext()
227: .getPath().length() - 1;
228: String name = node
229: .getStringValue(ResourceLoader.RESOURCENAME_FIELD);
230: if (name.length() > contextPrefix
231: && resources.contains(name
232: .substring(contextPrefix))) {
233: log.service("Resource " + name
234: + " changed (node added or changed)");
235: nodeNumberToResourceName.put(number, name);
236: onChange(name);
237: }
238: }
239: }
240: }
241: }
242:
243: public synchronized void start() {
244: // create and start all filewatchers.
245: for (String resource : resources) {
246: //resourceLoader.checkShadowedNewerResources(resource);
247: mapNodeNumber(resource);
248: createFileWatcher(resource);
249: }
250: if (resourceWatchers == null) {
251: EventManager.getInstance().addEventListener(this );
252: }
253: running = true;
254: }
255:
256: /**
257: * Put here the stuff that has to be executed, when a file has been changed.
258: * @param resourceName The resource that was changed.
259: */
260: abstract public void onChange(String resourceName);
261:
262: /**
263: * Calls {@link #onChange(String)} for every added resource.
264: */
265: public final void onChange() {
266: for (String resource : resources) {
267: onChange(resource);
268: }
269: }
270:
271: /**
272: * Set the delay to observe between each check of the file changes.
273: * @param delay The delay in milliseconds
274: */
275: public synchronized void setDelay(long delay) {
276: this .delay = delay;
277: for (FileWatcher fw : fileWatchers.values()) {
278: fw.setDelay(delay);
279: }
280: }
281:
282: /**
283: */
284: public synchronized void remove(String resourceName) {
285: boolean wasRunning = running;
286: if (running) { // it's simplest like this.
287: exit();
288: }
289: resources.remove(resourceName);
290: if (wasRunning) {
291: start();
292: }
293: }
294:
295: /**
296: * Removes all resources.
297: */
298: public synchronized void clear() {
299: if (running) {
300: exit();
301: resources.clear();
302: start();
303: } else {
304: resources.clear();
305: }
306: }
307:
308: /**
309: * Stops watching. Stops all filewatchers, removes observers.
310: */
311: public synchronized void exit() {
312: Iterator<FileWatcher> i = fileWatchers.values().iterator();
313: while (i.hasNext()) {
314: FileWatcher fw = i.next();
315: fw.exit();
316: i.remove();
317: }
318: if (ResourceLoader.resourceBuilder != null) {
319: EventManager.getInstance().removeEventListener(this );
320: }
321: running = false;
322: }
323:
324: /**
325: * Shows the 'contents' of the filewatcher. It shows a list of files/last modified timestamps.
326: */
327: public String toString() {
328: return "" + resources + " " + fileWatchers;
329: }
330:
331: /**
332: * A FileWatcher associated with a certain resource of this ResourceWatcher.
333: */
334:
335: protected class ResourceFileWatcher extends FileWatcher {
336: private String resource;
337:
338: ResourceFileWatcher(String resource) {
339: this .resource = resource;
340: }
341:
342: public void onChange(File f) {
343: URL shadower = resourceLoader.shadowed(f, resource);
344: if (shadower == null) {
345: ResourceWatcher.this .onChange(resource);
346: } else {
347: log
348: .warn("File " + f
349: + " changed, but it is shadowed by "
350: + shadower);
351: }
352: }
353: }
354:
355: }
|