blob: e279b9c81715ced4aaca5fad41657be65330e2f5 [file] [log] [blame]
/*
* Copyright 2005 The Apache Software Foundation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.jackrabbit.webdav.jcr.observation;
import org.apache.jackrabbit.webdav.DavException;
import org.apache.jackrabbit.webdav.DavResourceLocator;
import org.apache.jackrabbit.webdav.DavServletResponse;
import org.apache.jackrabbit.webdav.DavSession;
import org.apache.jackrabbit.webdav.jcr.JcrDavException;
import org.apache.jackrabbit.webdav.observation.EventDiscovery;
import org.apache.jackrabbit.webdav.observation.ObservationResource;
import org.apache.jackrabbit.webdav.observation.Subscription;
import org.apache.jackrabbit.webdav.observation.SubscriptionDiscovery;
import org.apache.jackrabbit.webdav.observation.SubscriptionInfo;
import org.apache.jackrabbit.webdav.observation.SubscriptionManager;
import org.apache.log4j.Logger;
import javax.jcr.RepositoryException;
import javax.jcr.observation.ObservationManager;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
/**
* <code>SubscriptionManager</code> collects all subscriptions requested, handles
* the subscription timeout and provides METHODS to discover subscriptions
* present on a given resource as well as events for an specific subscription.
*
* @todo make sure all expired subscriptions are removed!
*/
public class SubscriptionManagerImpl implements SubscriptionManager {
private static Logger log = Logger.getLogger(SubscriptionManager.class);
/**
* Map containing all {@link org.apache.jackrabbit.webdav.observation.Subscription subscriptions}.
*/
private final SubscriptionMap subscriptions = new SubscriptionMap();
/**
* Retrieve the {@link org.apache.jackrabbit.webdav.observation.SubscriptionDiscovery} object for the given
* resource. Note, that the discovery object will be empty if there are
* no subscriptions present.
*
* @param resource
* @todo is it correct to return subscriptions made by another session?
*/
public SubscriptionDiscovery getSubscriptionDiscovery(ObservationResource resource) {
Subscription[] subsForResource = subscriptions.getByPath(resource.getLocator());
return new SubscriptionDiscovery(subsForResource);
}
/**
* Create a new <code>Subscription</code> or update an existing <code>Subscription</code>
* and add it as eventlistener to the {@link javax.jcr.observation.ObservationManager}.
*
* @param info
* @param subscriptionId
* @param resource
* @return <code>Subscription</code> that has been added to the {@link javax.jcr.observation.ObservationManager}
* @throws DavException if the subscription fails
*/
public Subscription subscribe(SubscriptionInfo info, String subscriptionId,
ObservationResource resource)
throws DavException {
SubscriptionImpl subscription;
DavSession session = resource.getSession();
if (subscriptionId == null) {
// new subscription
subscription = new SubscriptionImpl(info, resource);
registerSubscription(subscription, session);
// ajust references to this subscription
subscriptions.put(subscription.getSubscriptionId(), subscription);
session.addReference(subscription.getSubscriptionId());
} else {
// refresh/modify existing one
subscription = validate(subscriptionId, resource);
subscription.setInfo(info);
registerSubscription(subscription, session);
}
return subscription;
}
/**
* Register the event listener defined by the given subscription to the
* repository's observation manager.
*
* @param subscription
* @param session
* @throws DavException
*/
private void registerSubscription(SubscriptionImpl subscription, DavSession session)
throws DavException {
try {
ObservationManager oMgr = session.getRepositorySession().getWorkspace().getObservationManager();
String itemPath = subscription.getLocator().getJcrPath();
oMgr.addEventListener(subscription, subscription.getEventTypes(),
itemPath, subscription.isDeep(),
subscription.getUuidFilters(),
subscription.getNodetypeNameFilters(),
subscription.isNoLocal());
} catch (RepositoryException e) {
log.error("Unable to register eventlistener: "+e.getMessage());
throw new JcrDavException(e);
}
}
/**
* Unsubscribe the <code>Subscription</code> with the given id and remove it
* from the {@link javax.jcr.observation.ObservationManager} as well as
* from the internal map.
*
* @param subscriptionId
* @param resource
* @throws DavException
*/
public void unsubscribe(String subscriptionId, ObservationResource resource)
throws DavException {
SubscriptionImpl subs = validate(subscriptionId, resource);
unregisterSubscription(subs, resource.getSession());
}
/**
* Remove the event listener defined by the specified subscription from
* the repository's observation manager.
*
* @param subscription
* @param session
* @throws DavException
*/
private void unregisterSubscription(SubscriptionImpl subscription,
DavSession session) throws DavException {
try {
session.getRepositorySession().getWorkspace().getObservationManager().removeEventListener(subscription);
String sId = subscription.getSubscriptionId();
// clean up any references
subscriptions.remove(sId);
session.removeReference(sId);
} catch (RepositoryException e) {
log.error("Unable to remove eventlistener: "+e.getMessage());
throw new JcrDavException(e);
}
}
/**
* Retrieve all event bundles accumulated since for the subscription specified
* by the given id.
*
* @param subscriptionId
* @param resource
* @return object encapsulating the events.
*/
public EventDiscovery poll(String subscriptionId, ObservationResource resource)
throws DavException {
SubscriptionImpl subs = validate(subscriptionId, resource);
return subs.discoverEvents();
}
/**
* Validate the given subscription id. The validation will fail under the following
* conditions:<ul>
* <li>The subscription with the given id does not exist,</li>
* <li>DavResource path does not match the subscription id,</li>
* <li>The subscription with the given id is already expired.</li>
* </ul>
*
* @param subscriptionId
* @param resource
* @return <code>Subscription</code> with the given id.
* @throws DavException if an error occured while retrieving the <code>Subscription</code>
*/
private SubscriptionImpl validate(String subscriptionId, ObservationResource resource)
throws DavException {
SubscriptionImpl subs;
if (subscriptions.contains(subscriptionId)) {
subs = (SubscriptionImpl) subscriptions.get(subscriptionId);
if (!subs.isSubscribedToResource(resource)) {
throw new DavException(DavServletResponse.SC_PRECONDITION_FAILED, "Attempt to operate on subscription with invalid resource path.");
}
if (subs.isExpired()) {
unregisterSubscription(subs, resource.getSession());
throw new DavException(DavServletResponse.SC_PRECONDITION_FAILED, "Attempt to operate on expired subscription.");
}
return subs;
} else {
throw new DavException(DavServletResponse.SC_PRECONDITION_FAILED, "Attempt to modify or to poll for non-existing subscription.");
}
}
/**
* Private inner class <code>SubscriptionMap</code> that allows for quick
* access by resource path as well as by subscription id.
*/
private class SubscriptionMap {
private HashMap subscriptions = new HashMap();
private HashMap ids = new HashMap();
private boolean contains(String subscriptionId) {
return subscriptions.containsKey(subscriptionId);
}
private Subscription get(String subscriptionId) {
return (Subscription) subscriptions.get(subscriptionId);
}
private void put(String subscriptionId, SubscriptionImpl subscription) {
subscriptions.put(subscriptionId, subscription);
DavResourceLocator key = subscription.getLocator();
Set idSet;
if (ids.containsKey(key)) {
idSet = (Set) ids.get(key);
} else {
idSet = new HashSet();
ids.put(key, idSet);
}
if (!idSet.contains(subscriptionId)) {
idSet.add(subscriptionId);
}
}
private void remove(String subscriptionId) {
SubscriptionImpl sub = (SubscriptionImpl) subscriptions.remove(subscriptionId);
((Set)ids.get(sub.getLocator())).remove(subscriptionId);
}
private Subscription[] getByPath(DavResourceLocator locator) {
Set idSet = (Set) ids.get(locator);
if (idSet != null && !idSet.isEmpty()) {
Iterator idIterator = idSet.iterator();
Subscription[] subsForResource = new Subscription[idSet.size()];
int i = 0;
while (idIterator.hasNext()) {
subsForResource[i] = (Subscription) subscriptions.get(idIterator.next());
}
return subsForResource;
} else {
return new Subscription[0];
}
}
}
}