blob: 22a9ca500e16917b6e260a78558f4131f08fb345 [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.uuid.UUID;
import org.apache.jackrabbit.webdav.DavException;
import org.apache.jackrabbit.webdav.DavResourceLocator;
import org.apache.jackrabbit.webdav.DavServletResponse;
import org.apache.jackrabbit.webdav.observation.EventBundle;
import org.apache.jackrabbit.webdav.observation.EventDiscovery;
import org.apache.jackrabbit.webdav.observation.EventType;
import org.apache.jackrabbit.webdav.observation.Filter;
import org.apache.jackrabbit.webdav.observation.ObservationConstants;
import org.apache.jackrabbit.webdav.observation.ObservationResource;
import org.apache.jackrabbit.webdav.observation.Subscription;
import org.apache.jackrabbit.webdav.observation.SubscriptionInfo;
import org.apache.jackrabbit.webdav.xml.DomUtil;
import org.apache.log4j.Logger;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import javax.jcr.RepositoryException;
import javax.jcr.observation.Event;
import javax.jcr.observation.EventIterator;
import javax.jcr.observation.EventListener;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* The <code>Subscription</code> class encapsulates a single subscription with
* the following responsibilities:<ul>
* <li>Providing access to the subscription info,</li>
* <li>Recording events this subscription is interested in,</li>
* <li>Providing access to the events.</li>
* </ul>
*/
public class SubscriptionImpl implements Subscription, ObservationConstants, EventListener {
private static Logger log = Logger.getLogger(SubscriptionImpl.class);
private static final long DEFAULT_TIMEOUT = 300000; // 5 minutes
private SubscriptionInfo info;
private long expirationTime;
private final DavResourceLocator locator;
private final String subscriptionId = UUID.randomUUID().toString();
private final List eventBundles = new ArrayList();
/**
* Create a new <code>Subscription</code> with the given {@link SubscriptionInfo}
* and {@link org.apache.jackrabbit.webdav.observation.ObservationResource resource}.
*
* @param info
* @param resource
*/
public SubscriptionImpl(SubscriptionInfo info, ObservationResource resource) {
setInfo(info);
locator = resource.getLocator();
}
/**
* Returns the id of this subscription.
*
* @return subscriptionId
*/
public String getSubscriptionId() {
return subscriptionId;
}
/**
* Return the Xml representation of this <code>Subscription</code> as required
* for the {@link org.apache.jackrabbit.webdav.observation.SubscriptionDiscovery}
* webdav property that in included in the response body of a sucessful SUBSCRIBE
* request or as part of a PROPFIND response.
*
* @return Xml representation
* @see org.apache.jackrabbit.webdav.xml.XmlSerializable#toXml(Document)
* @param document
*/
public Element toXml(Document document) {
Element subscr = DomUtil.createElement(document, XML_SUBSCRIPTION, NAMESPACE);
subscr.appendChild(info.toXml(document));
subscr.appendChild(DomUtil.depthToXml(info.isDeep(), document));
subscr.appendChild(DomUtil.timeoutToXml(info.getTimeOut(), document));
Element id = DomUtil.addChildElement(subscr, XML_SUBSCRIPTIONID, NAMESPACE);
id.appendChild(DomUtil.hrefToXml(subscriptionId, document));
return subscr;
}
//--------------------------------------------< implementation specific >---
/**
* Modify the {@link SubscriptionInfo} for this subscription.
*
* @param info
*/
void setInfo(SubscriptionInfo info) {
this.info = info;
// validate the timeout and adjust value, if it is invalid or missing
long timeout = info.getTimeOut();
if (timeout <= 0) {
timeout = DEFAULT_TIMEOUT;
}
expirationTime = System.currentTimeMillis() + timeout;
}
/**
* @return JCR compliant integer representation of the event types defined
* for this {@link SubscriptionInfo}.
*/
int getEventTypes() throws DavException {
EventType[] eventTypes = info.getEventTypes();
int events = 0;
for (int i = 0; i < eventTypes.length; i++) {
events |= getEventType(eventTypes[i].getName());
}
return events;
}
/**
* @return a String array with size > 0 or <code>null</code>
*/
String[] getUuidFilters() {
return getFilterValues(XML_UUID);
}
/**
* @return a String array with size > 0 or <code>null</code>
*/
String[] getNodetypeNameFilters() {
return getFilterValues(XML_NODETYPE_NAME);
}
private String[] getFilterValues(String filterLocalName) {
Filter[] filters = info.getFilters(filterLocalName, NAMESPACE);
List values = new ArrayList();
for (int i = 0; i < filters.length; i++) {
String val = filters[i].getValue();
if (val != null) {
values.add(val);
}
}
return (values.size() > 0) ? (String[])values.toArray(new String[values.size()]) : null;
}
/**
*
* @return true if a {@link ObservationConstants#XML_NOLOCAL} element
* is present in the {@link SubscriptionInfo}.
*/
boolean isNoLocal() {
return info.isNoLocal();
}
/**
* @return true if this subscription is intended to be deep.
*/
boolean isDeep() {
return info.isDeep();
}
/**
* @return the locator of the {@link ObservationResource resource} this
* <code>Subscription</code> was requested for.
*/
DavResourceLocator getLocator() {
return locator;
}
/**
* Returns true if this <code>Subscription</code> matches the given
* resource.
*
* @param resource
* @return true if this <code>Subscription</code> matches the given
* resource.
*/
boolean isSubscribedToResource(ObservationResource resource) {
return locator.equals(resource.getLocator());
}
/**
* Returns true if this <code>Subscription</code> is expired and therefore
* stopped recording events.
*
* @return true if this <code>Subscription</code> is expired
*/
boolean isExpired() {
return System.currentTimeMillis() > expirationTime;
}
/**
* Returns a {@link org.apache.jackrabbit.webdav.observation.EventDiscovery} object listing all events that were
* recorded since the last call to this method and clears the list of event
* bundles.
*
* @return object listing all events that were recorded.
* @see #onEvent(EventIterator)
*/
synchronized EventDiscovery discoverEvents() {
EventDiscovery ed = new EventDiscovery();
Iterator it = eventBundles.iterator();
while (it.hasNext()) {
EventBundle eb = (EventBundle) it.next();
ed.addEventBundle(eb);
}
// clear list
eventBundles.clear();
return ed;
}
//--------------------------------------------< EventListener interface >---
/**
* Records the events passed as a new event bundle in order to make them
* available with the next {@link #discoverEvents()} request.
*
* @param events to be recorded.
* @see EventListener#onEvent(EventIterator)
* @see #discoverEvents()
*/
public synchronized void onEvent(EventIterator events) {
// TODO: correct not to accept events after expiration? without unsubscribing?
if (!isExpired()) {
eventBundles.add(new EventBundleImpl(events));
}
}
//--------------------------------------------------------------------------
/**
* Static utility method in order to convert the types defined by
* {@link javax.jcr.observation.Event} into their Xml representation.
*
* @param eventType The jcr event type
* @return Xml representation of the event type.
*/
private static String getEventName(int eventType) {
String eventName;
switch (eventType) {
case Event.NODE_ADDED:
eventName = EVENT_NODEADDED;
break;
case Event.NODE_REMOVED:
eventName = EVENT_NODEREMOVED;
break;
case Event.PROPERTY_ADDED:
eventName = EVENT_PROPERTYADDED;
break;
case Event.PROPERTY_CHANGED:
eventName = EVENT_PROPERTYCHANGED;
break;
default:
//Event.PROPERTY_REMOVED:
eventName = EVENT_PROPERTYREMOVED;
break;
}
return eventName;
}
/**
* Static utility method to convert an event type name as present in the
* Xml request body into the corresponding constant defined by
* {@link javax.jcr.observation.Event}.
*
* @param eventName
* @return event type as defined by {@link javax.jcr.observation.Event}.
* @throws DavException if the given element cannot be translated
* to any of the events defined by {@link javax.jcr.observation.Event}.
*/
private static int getEventType(String eventName) throws DavException {
int eType;
if (EVENT_NODEADDED.equals(eventName)) {
eType = Event.NODE_ADDED;
} else if (EVENT_NODEREMOVED.equals(eventName)) {
eType = Event.NODE_REMOVED;
} else if (EVENT_PROPERTYADDED.equals(eventName)) {
eType = Event.PROPERTY_ADDED;
} else if (EVENT_PROPERTYCHANGED.equals(eventName)) {
eType = Event.PROPERTY_CHANGED;
} else if (EVENT_PROPERTYREMOVED.equals(eventName)) {
eType = Event.PROPERTY_REMOVED;
} else {
throw new DavException(DavServletResponse.SC_UNPROCESSABLE_ENTITY, "Invalid event type: "+eventName);
}
return eType;
}
/**
* Inner class <code>EventBundle</code> encapsulats an event bundle as
* recorded {@link SubscriptionImpl#onEvent(EventIterator) on event} and
* provides the possibility to retrieve the Xml representation of the
* bundle and the events included in order to respond to a POLL request.
*
* @see SubscriptionImpl#discoverEvents()
*/
private class EventBundleImpl implements EventBundle {
private final EventIterator events;
private EventBundleImpl(EventIterator events) {
this.events = events;
}
public Element toXml(Document document) {
Element bundle = DomUtil.createElement(document, XML_EVENTBUNDLE, NAMESPACE);
while (events.hasNext()) {
Event event = events.nextEvent();
Element eventElem = DomUtil.addChildElement(bundle, XML_EVENT, NAMESPACE);
// href
String eHref = "";
try {
boolean isCollection = (event.getType() == Event.NODE_ADDED || event.getType() == Event.NODE_REMOVED);
eHref = locator.getFactory().createResourceLocator(locator.getPrefix(), locator.getWorkspacePath(), event.getPath(), false).getHref(isCollection);
} catch (RepositoryException e) {
// should not occur....
log.error(e.getMessage());
}
eventElem.appendChild(DomUtil.hrefToXml(eHref, document));
// eventtype
Element eType = DomUtil.addChildElement(eventElem, XML_EVENTTYPE, NAMESPACE);
DomUtil.addChildElement(eType, getEventName(event.getType()), NAMESPACE);
// user id
DomUtil.addChildElement(eventElem, XML_EVENTUSERID, NAMESPACE, event.getUserID());
}
return bundle;
}
}
}