/* Copyright (c) 2015 OpenPlans - www.openplans.org. All rights reserved.
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geoserver.platform.resource;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.geoserver.platform.resource.Resource.Type;
import org.geoserver.platform.resource.ResourceNotification.Event;
import org.geoserver.platform.resource.ResourceNotification.Kind;
/**
*
* Simple ResourceWatcher implementation, only distributes events to this GeoServer instance.
*
* @author Niels Charlier
*
*/
public class SimpleResourceNotificationDispatcher implements ResourceNotificationDispatcher {
private Map<String, List<ResourceListener>> handlers = new HashMap<String, List<ResourceListener>>();
@Override
public synchronized void addListener(String resource, ResourceListener listener) {
List<ResourceListener> listeners = handlers.get(resource);
if (listeners == null) {
listeners = new ArrayList<ResourceListener>();
handlers.put(resource, listeners);
}
listeners.add(listener);
}
@Override
public synchronized boolean removeListener(String resource, ResourceListener listener) {
List<ResourceListener> listeners = handlers.get(resource);
if (listeners != null) {
return listeners.remove(listener);
}
return false;
}
/**
*
* Send notification (without propagation), children may override this.
*
* @param notification
*/
protected void changedInternal(ResourceNotification notification) {
List<ResourceListener> listeners = handlers.get(notification.getPath());
if (listeners != null) {
for (ResourceListener listener : listeners) {
listener.changed(notification);
}
}
}
@Override
public void changed(ResourceNotification notification) {
changedInternal(notification);
//if delete, propagate delete notifications to children, which can be found in the events (see {@link createEvents})
if (notification.getKind() == Kind.ENTRY_DELETE) {
for (Event event : notification.events()) {
if (!notification.getPath().equals(event.getPath())) {
changedInternal(new ResourceNotification(event.getPath(), Kind.ENTRY_DELETE,
notification.getTimestamp(), notification.events()));
}
}
}
//if create, propagate CREATE events to its created parents, which can be found in the events (see {@link createEvents})
Set<String> createdParents = new HashSet<String>();
if (notification.getKind() == Kind.ENTRY_CREATE) {
for (Event event : notification.events()) {
if (!notification.getPath().equals(event.getPath())) {
createdParents.add(event.getPath());
}
}
}
//propagate any event to its direct parent (as MODIFY if not a created parent)
String path = Paths.parent(notification.getPath());
while (path != null) {
boolean isCreate = createdParents.contains(path);
changedInternal(new ResourceNotification(path,
isCreate ? Kind.ENTRY_CREATE : Kind.ENTRY_MODIFY,
notification.getTimestamp(), notification.events()));
//stop propagating after first modify
path = isCreate ? Paths.parent(path) : null;
}
}
/**
* Helper method to create all events for any operation (except rename) that happens to a resource.
* This method should be called just before the action takes place in order to analyze the effects properly.
*
* Operations are assumed to be atomic except in the following two cases:
* (1) deleting causes children to delete as well
* (2) creating causes non existing parents on the path to be created as well
*
* (Note: do not confuse the creation of notification events with propagation of notifications).
*
* @param resource Resource being changed
* @param kind Nature of resource change
* @return List of notification events covering change
*/
public static List<Event> createEvents(Resource resource, Kind kind) {
List<Event> events = new ArrayList<Event>();
events.add(new ResourceNotification.Event(resource.path(), kind));
// (1)
if (resource.getType() == Type.DIRECTORY && kind == Kind.ENTRY_DELETE) {
for (Resource child : Resources.listRecursively(resource)) {
events.add(new ResourceNotification.Event(child.path(), kind));
}
}
// (2)
if (kind == Kind.ENTRY_CREATE) {
Resource parent = resource.parent();
while (parent != null && !Resources.exists(parent)) {
events.add(new ResourceNotification.Event(parent.path(), kind));
parent = parent.parent();
}
}
return events;
}
/**
* Helper method to create all create/modify events caused by a rename/move operation.
* (delete events must be created separately.)
* This method should be called just before the action takes place in order to analyze the effects properly.
*
* Rename/move causes children to be moved as well.
*
* @param src Resource being renamed
* @param dest Target resource after renatmed
* @return List of notification events covering all the modified Resources
*/
public static List<Event> createRenameEvents(Resource src, Resource dest) {
List<Event> events = new ArrayList<Event>();
events.add(new ResourceNotification.Event(dest.path(), Resources.exists(dest) ? Kind.ENTRY_MODIFY: Kind.ENTRY_CREATE));
for (Resource child : Resources.listRecursively(src)) {
Resource newChild = dest.get(child.path().substring(src.path().length() + 1));
events.add(new ResourceNotification.Event(newChild.path(), Resources.exists(newChild) ? Kind.ENTRY_MODIFY: Kind.ENTRY_CREATE));
}
return events;
}
}