All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.jackrabbit.webdav.jcr.observation.SubscriptionImpl Maven / Gradle / Ivy

There is a newer version: 2.23.0-beta
Show newest version
/*
 * 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.commons.webdav.EventUtil;
import org.apache.jackrabbit.commons.webdav.JcrRemotingConstants;
import org.apache.jackrabbit.spi.Name;
import org.apache.jackrabbit.spi.commons.AdditionalEventInfo;
import org.apache.jackrabbit.spi.commons.SessionExtensions;
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.transaction.TransactionListener;
import org.apache.jackrabbit.webdav.jcr.JcrDavException;
import org.apache.jackrabbit.webdav.jcr.JcrDavSession;
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.observation.DefaultEventType;
import org.apache.jackrabbit.webdav.xml.DomUtil;
import org.apache.jackrabbit.webdav.xml.Namespace;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.UnsupportedRepositoryOperationException;
import javax.jcr.observation.Event;
import javax.jcr.observation.EventIterator;
import javax.jcr.observation.EventListener;
import javax.jcr.observation.ObservationManager;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;

/**
 * The Subscription class encapsulates a single subscription with
 * the following responsibilities:
    *
  • Providing access to the subscription info,
  • *
  • Recording events this subscription is interested in,
  • *
  • Providing access to the events.
  • *
*/ public class SubscriptionImpl implements Subscription, ObservationConstants, EventListener { private static Logger log = LoggerFactory.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(); private final ObservationManager obsMgr; private final Session session; /** * Create a new Subscription with the given {@link SubscriptionInfo} * and {@link org.apache.jackrabbit.webdav.observation.ObservationResource resource}. * * @param info * @param resource * @throws DavException if resource is not based on a JCR repository or * the repository does not support observation. */ public SubscriptionImpl(SubscriptionInfo info, ObservationResource resource) throws DavException { setInfo(info); locator = resource.getLocator(); session = JcrDavSession.getRepositorySession(resource.getSession()); try { obsMgr = session.getWorkspace().getObservationManager(); } catch (RepositoryException e) { throw new DavException(DavServletResponse.SC_INTERNAL_SERVER_ERROR, e); } } //-------------------------------------------------------< Subscription >--- /** * Returns the id of this subscription. * * @return subscriptionId */ public String getSubscriptionId() { return subscriptionId; } public boolean eventsProvideNodeTypeInformation() { String t = session.getRepository().getDescriptor("org.apache.jackrabbit.spi.commons.AdditionalEventInfo"); return t == null ? false : Boolean.parseBoolean(t); } public boolean eventsProvideNoLocalFlag() { return session instanceof SessionExtensions; } //----------------------------------------------------< XmlSerializable >--- /** * Return the Xml representation of this Subscription as required * for the {@link org.apache.jackrabbit.webdav.observation.SubscriptionDiscovery} * webdav property that in included in the response body of a successful 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)); if (getSubscriptionId() != null) { Element id = DomUtil.addChildElement(subscr, XML_SUBSCRIPTIONID, NAMESPACE); id.appendChild(DomUtil.hrefToXml(getSubscriptionId(), document)); } DomUtil.addChildElement(subscr, XML_EVENTSWITHTYPES, NAMESPACE, Boolean.toString(eventsProvideNodeTypeInformation())); DomUtil.addChildElement(subscr, XML_EVENTSWITHLOCALFLAG, NAMESPACE, Boolean.toString(eventsProvideNoLocalFlag())); 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 getJcrEventTypes() throws DavException { EventType[] eventTypes = info.getEventTypes(); int events = 0; for (EventType eventType : eventTypes) { events |= getJcrEventType(eventType); } return events; } /** * @return a String array with size > 0 or null */ String[] getUuidFilters() { return getFilterValues(XML_UUID); } /** * @return a String array with size > 0 or null */ String[] getNodetypeNameFilters() { return getFilterValues(XML_NODETYPE_NAME); } private String[] getFilterValues(String filterLocalName) { List values = new ArrayList(); for (Filter filter : info.getFilters(filterLocalName, NAMESPACE)) { String val = filter.getValue(); if (val != null) { values.add(val); } } return (values.size() > 0) ? 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 * Subscription was requested for. */ DavResourceLocator getLocator() { return locator; } /** * Returns true if this Subscription matches the given * resource. * * @param resource * @return true if this Subscription matches the given * resource. */ boolean isSubscribedToResource(ObservationResource resource) { return locator.getResourcePath().equals(resource.getResourcePath()); } /** * Returns true if this Subscription is expired and therefore * stopped recording events. * * @return true if this Subscription 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. * * @param timeout time in milliseconds to wait at most for events if none * are present currently. * @return object listing all events that were recorded. * @see #onEvent(EventIterator) */ synchronized EventDiscovery discoverEvents(long timeout) { EventDiscovery ed = new EventDiscovery(); if (eventBundles.isEmpty() && timeout > 0) { try { wait(timeout); } catch (InterruptedException e) { // continue and possibly return empty event discovery } } for (EventBundle eb : eventBundles) { ed.addEventBundle(eb); } // clear list eventBundles.clear(); return ed; } /** * Creates a new transaction listener for the scope of a transaction * commit (save call). * @return a transaction listener for this subscription. */ TransactionListener createTransactionListener() { if (info.isNoLocal()) { // a subscription which is not interested in local changes does // not need the transaction id return new TransactionEvent() { @Override public void onEvent(EventIterator events) { // ignore } @Override public void beforeCommit(TransactionResource resource, String lockToken) { // ignore } @Override public void afterCommit(TransactionResource resource, String lockToken, boolean success) { // ignore } }; } else { return new TransactionEvent(); } } /** * Suspend this subscription. This call will remove this subscription as * event listener from the observation manager. */ void suspend() throws DavException { try { obsMgr.removeEventListener(this); } catch (RepositoryException e) { throw new JcrDavException(e); } } /** * Resumes this subscription. This call will register this subscription * again as event listener to the observation manager. */ void resume() throws DavException { try { obsMgr.addEventListener(this, getJcrEventTypes(), getLocator().getRepositoryPath(), isDeep(), getUuidFilters(), getNodetypeNameFilters(), isNoLocal()); } catch (RepositoryException e) { throw new JcrDavException(e); } } //--------------------------------------------< EventListener interface >--- /** * Records the events passed as a new event bundle in order to make them * available with the next {@link #discoverEvents(long)} request. If this * subscription is expired it will remove itself as listener from the * observation manager. * * @param events to be recorded. * @see EventListener#onEvent(EventIterator) * @see #discoverEvents(long) */ public synchronized void onEvent(EventIterator events) { if (!isExpired()) { eventBundles.add(new EventBundleImpl(events)); } else { // expired -> unsubscribe try { obsMgr.removeEventListener(this); } catch (RepositoryException e) { log.warn("Exception while unsubscribing: " + e); } } notifyAll(); } //-------------------------------------------------------------------------- /** * Static utility method to convert the type defined by a * {@link javax.jcr.observation.Event JCR event} into an EventType * object. * * @param jcrEventType * @return EventType representation of the given JCR event type. * @throws IllegalArgumentException if the given int does not represent a * valid type constants as defined by {@link Event}.
* Valid values are *
    *
  • {@link Event#NODE_ADDED}
  • *
  • {@link Event#NODE_REMOVED}
  • *
  • {@link Event#PROPERTY_ADDED}
  • *
  • {@link Event#PROPERTY_REMOVED}
  • *
  • {@link Event#PROPERTY_CHANGED}
  • *
  • {@link Event#NODE_MOVED}
  • *
  • {@link Event#PERSIST}
  • *
*/ public static EventType getEventType(int jcrEventType) { String localName = EventUtil.getEventName(jcrEventType); return DefaultEventType.create(localName, NAMESPACE); } /** * @return The DAV event type representation for all known JCR event types. */ public static EventType[] getAllEventTypes() { EventType[] types = DefaultEventType.create(EventUtil.EVENT_ALL, NAMESPACE); return types; } /** * Static utility method to convert an EventType as present in * the Xml body into the corresponding JCR event constant defined by * {@link javax.jcr.observation.Event}. * * @param eventType * @return Any of the event types defined by {@link Event}.
* Possible values are *
    *
  • {@link Event#NODE_ADDED}
  • *
  • {@link Event#NODE_REMOVED}
  • *
  • {@link Event#PROPERTY_ADDED}
  • *
  • {@link Event#PROPERTY_REMOVED}
  • *
  • {@link Event#PROPERTY_CHANGED}
  • *
  • {@link Event#NODE_MOVED}
  • *
  • {@link Event#PERSIST}
  • *
* @throws DavException if the given event type does not define a valid * JCR event type, such as returned by {@link #getEventType(int)}. */ public static int getJcrEventType(EventType eventType) throws DavException { if (eventType == null || !NAMESPACE.equals(eventType.getNamespace())) { throw new DavException(DavServletResponse.SC_UNPROCESSABLE_ENTITY, "Invalid JCR event type: "+ eventType + ": Namespace mismatch."); } String eventName = eventType.getName(); if (!EventUtil.isValidEventName(eventName)) { throw new DavException(DavServletResponse.SC_UNPROCESSABLE_ENTITY, "Invalid event type: "+eventName); } return EventUtil.getJcrEventType(eventName); } /** * Inner class EventBundle encapsulates 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(long) */ private class EventBundleImpl implements EventBundle { private final EventIterator events; private final String transactionId; private EventBundleImpl(EventIterator events) { this(events, null); } private EventBundleImpl(EventIterator events, String transactionId) { this.events = events; this.transactionId = transactionId; } public Element toXml(Document document) { Element bundle = DomUtil.createElement(document, XML_EVENTBUNDLE, NAMESPACE); // TODO: this appears to be unused now if (transactionId != null) { DomUtil.setAttribute(bundle, XML_EVENT_TRANSACTION_ID, NAMESPACE, transactionId); } boolean localFlagSet = false; while (events.hasNext()) { Event event = events.nextEvent(); if (!localFlagSet) { // obtain remote session identifier localFlagSet = true; String name = JcrRemotingConstants.RELATION_REMOTE_SESSION_ID; Object forSessionId = session.getAttribute(name); // calculate "local" flags if (forSessionId != null && event instanceof AdditionalEventInfo) { AdditionalEventInfo aei = (AdditionalEventInfo) event; try { boolean isLocal = forSessionId.equals( aei.getSessionAttribute(name)); DomUtil.setAttribute( bundle, XML_EVENT_LOCAL, null, Boolean.toString(isLocal)); } catch (UnsupportedRepositoryOperationException ex) { // optional feature } } } 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)); // event type Element eType = DomUtil.addChildElement(eventElem, XML_EVENTTYPE, NAMESPACE); eType.appendChild(getEventType(event.getType()).toXml(document)); // user id DomUtil.addChildElement(eventElem, XML_EVENTUSERID, NAMESPACE, event.getUserID()); // try to compute nodetype information if (event instanceof AdditionalEventInfo) { try { DomUtil.addChildElement(eventElem, XML_EVENTPRIMARNODETYPE, NAMESPACE, ((AdditionalEventInfo) event) .getPrimaryNodeTypeName().toString()); for (Name mixin : ((AdditionalEventInfo) event) .getMixinTypeNames()) { DomUtil.addChildElement(eventElem, XML_EVENTMIXINNODETYPE, NAMESPACE, mixin.toString()); } } catch (UnsupportedRepositoryOperationException ex) { // optional } } // Additional JCR 2.0 event information // user data try { DomUtil.addChildElement(eventElem, XML_EVENTUSERDATA, NAMESPACE, event.getUserData()); } catch (RepositoryException e) { log.error("Internal error while retrieving event user data. {}", e.getMessage()); } // time stamp try { DomUtil.addChildElement(eventElem, XML_EVENTDATE, NAMESPACE, String.valueOf(event.getDate())); } catch (RepositoryException e) { log.error("Internal error while retrieving event date. {}", e.getMessage()); } // identifier try { DomUtil.addChildElement(eventElem, XML_EVENTIDENTIFIER, NAMESPACE, event.getIdentifier()); } catch (RepositoryException e) { log.error("Internal error while retrieving event identifier. {}", e.getMessage()); } // info try { serializeInfoMap(eventElem, session, event.getInfo()); } catch (RepositoryException e) { log.error("Internal error while retrieving event info. {}", e.getMessage()); } } return bundle; } } protected static void serializeInfoMap(Element eventElem, Session session, Map map) { // info Element info = DomUtil.addChildElement(eventElem, XML_EVENTINFO, NAMESPACE); Map m = map; for (Map.Entry entry : m.entrySet()) { try { String key = entry.getKey().toString(); Namespace ns = Namespace.EMPTY_NAMESPACE; int colon = key.indexOf(':'); if (colon >= 0) { String prefix = key.substring(0, colon); String localname = key.substring(colon + 1); ns = Namespace.getNamespace(prefix, session.getNamespaceURI(prefix)); key = localname; } Object value = entry.getValue(); if (value != null) { DomUtil.addChildElement(info, key, ns, value.toString()); } else { DomUtil.addChildElement(info, key, ns); } } catch (RepositoryException nse) { log.error("Internal error while getting namespaceUri, info map field skipped for {}", entry.getKey()); } } } //----------------------------< TransactionEvent >------------------------ /** * Implements a transaction event which listeners for events during a save * call on the repository. */ private class TransactionEvent implements EventListener, TransactionListener { private String transactionId; /** * {@inheritDoc} */ public void onEvent(EventIterator events) { String tId = transactionId; if (tId == null) { tId = UUID.randomUUID().toString(); } synchronized (SubscriptionImpl.this) { eventBundles.add(new EventBundleImpl(events, tId)); SubscriptionImpl.this.notifyAll(); } } //-----------------------------< TransactionListener >------------------ /** * {@inheritDoc} */ public void beforeCommit(TransactionResource resource, String lockToken) { try { transactionId = lockToken; obsMgr.addEventListener(this, getJcrEventTypes(), getLocator().getRepositoryPath(), isDeep(), getUuidFilters(), getNodetypeNameFilters(), isNoLocal()); // suspend the subscription suspend(); } catch (RepositoryException e) { log.warn("Unable to register TransactionListener: " + e); } catch (DavException e) { log.warn("Unable to register TransactionListener: " + e); } } /** * {@inheritDoc} */ public void afterCommit(TransactionResource resource, String lockToken, boolean success) { try { // resume the subscription resume(); // remove this transaction event obsMgr.removeEventListener(this); } catch (RepositoryException e) { log.warn("Unable to remove listener: " + e); } catch (DavException e) { log.warn("Unable to resume Subscription: " + e); } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy