org.apache.jackrabbit.webdav.jcr.observation.SubscriptionManagerImpl Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jackrabbit-jcr-server
Show all versions of jackrabbit-jcr-server
WebDAV server implementations for JCR
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.transaction.TransactionResource;
import org.apache.jackrabbit.webdav.jcr.JcrDavException;
import org.apache.jackrabbit.webdav.jcr.JcrDavSession;
import org.apache.jackrabbit.webdav.jcr.transaction.TransactionListener;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Element;
import org.w3c.dom.Document;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.observation.ObservationManager;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.Map;
import java.util.List;
import java.util.ArrayList;
/**
* SubscriptionManager
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, TransactionListener {
private static Logger log = LoggerFactory.getLogger(SubscriptionManager.class);
/**
* Map containing all {@link org.apache.jackrabbit.webdav.observation.Subscription subscriptions}.
*/
private final SubscriptionMap subscriptions = new SubscriptionMap();
private final Map transactionListenerById = new HashMap();
/**
* 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.
* Note that all subscriptions present on the given resource are returned.
* However, the subscription id will not be visible in order to avoid abuse
* by clients not having registered the subscription originally.
*
* @param resource
*/
public SubscriptionDiscovery getSubscriptionDiscovery(ObservationResource resource) {
Subscription[] subsForResource = subscriptions.getByPath(resource.getLocator());
return new SubscriptionDiscovery(subsForResource);
}
/**
* Create a new Subscription
or update an existing Subscription
* and add it as eventlistener to the {@link javax.jcr.observation.ObservationManager}.
*
* @param info
* @param subscriptionId
* @param resource
* @return Subscription
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 {
Subscription subscription;
if (subscriptionId == null) {
// new subscription
SubscriptionImpl newSubs = new SubscriptionImpl(info, resource);
registerSubscription(newSubs, resource);
// ajust references to this subscription
subscriptions.put(newSubs.getSubscriptionId(), newSubs);
resource.getSession().addReference(newSubs.getSubscriptionId());
subscription = newSubs;
} else {
// refresh/modify existing one
SubscriptionImpl existing = validate(subscriptionId, resource);
existing.setInfo(info);
registerSubscription(existing, resource);
subscription = new WrappedSubscription(existing);
}
return subscription;
}
/**
* Register the event listener defined by the given subscription to the
* repository's observation manager.
*
* @param subscription
* @param resource
* @throws DavException
*/
private void registerSubscription(SubscriptionImpl subscription,
ObservationResource resource) throws DavException {
try {
Session session = getRepositorySession(resource);
ObservationManager oMgr = session.getWorkspace().getObservationManager();
String itemPath = subscription.getLocator().getRepositoryPath();
oMgr.addEventListener(subscription, subscription.getJcrEventTypes(),
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 Subscription
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);
}
/**
* Remove the event listener defined by the specified subscription from
* the repository's observation manager and clean up the references present
* on the DavSession
.
*
* @param subscription
* @param resource
* @throws DavException
*/
private void unregisterSubscription(SubscriptionImpl subscription,
ObservationResource resource) throws DavException {
try {
Session session = getRepositorySession(resource);
session.getWorkspace().getObservationManager().removeEventListener(subscription);
String sId = subscription.getSubscriptionId();
// clean up any references
subscriptions.remove(sId);
resource.getSession().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:
* - The subscription with the given id does not exist,
* - DavResource path does not match the subscription id,
* - The subscription with the given id is already expired.
*
*
* @param subscriptionId
* @param resource
* @return Subscription
with the given id.
* @throws DavException if an error occured while retrieving the Subscription
*/
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);
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.");
}
}
/**
* @param resource
* @return JCR session
*/
private static Session getRepositorySession(ObservationResource resource) throws DavException {
return JcrDavSession.getRepositorySession(resource.getSession());
}
//---------------------------< TransactionListener >------------------------
/**
* {@inheritDoc}
*/
public synchronized void beforeCommit(TransactionResource resource,
String lockToken) {
// suspend regular subscriptions during a commit
List transactionListeners = new ArrayList();
for (Iterator it = subscriptions.iterator(); it.hasNext(); ) {
SubscriptionImpl sub = (SubscriptionImpl) it.next();
TransactionListener tl = sub.createTransactionListener();
tl.beforeCommit(resource, lockToken);
transactionListeners.add(tl);
}
transactionListenerById.put(lockToken, transactionListeners);
}
/**
* {@inheritDoc}
*/
public void afterCommit(TransactionResource resource, String lockToken, boolean success) {
List transactionListeners = (List) transactionListenerById.remove(lockToken);
if (transactionListeners != null) {
for (Iterator it = transactionListeners.iterator(); it.hasNext(); ) {
TransactionListener txListener = (TransactionListener) it.next();
txListener.afterCommit(resource, lockToken, success);
}
}
}
//----------------------------------------------< private inner classes >---
/**
* Private inner class wrapping around an Subscription
as
* present in the internal map. This allows to hide the subscription Id
* from other sessions, that did create the subscription.
*/
private class WrappedSubscription implements Subscription {
private final Subscription delegatee;
private WrappedSubscription(Subscription subsc) {
this.delegatee = subsc;
}
public String getSubscriptionId() {
// always return null, since the subscription id must not be exposed
// but to the client, that created the subscription.
return null;
}
public Element toXml(Document document) {
return delegatee.toXml(document);
}
}
/**
* Private inner class SubscriptionMap
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 Iterator iterator() {
return subscriptions.values().iterator();
}
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()) {
SubscriptionImpl s = (SubscriptionImpl) subscriptions.get(idIterator.next());
subsForResource[i] = new WrappedSubscription(s);
}
return subsForResource;
} else {
return new Subscription[0];
}
}
}
}