/*
* 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.core.observation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import javax.jcr.AccessDeniedException;
import javax.jcr.RepositoryException;
import javax.jcr.UnsupportedRepositoryOperationException;
import javax.jcr.observation.EventJournal;
import javax.jcr.observation.EventListener;
import javax.jcr.observation.EventListenerIterator;
import javax.jcr.observation.ObservationManager;
import org.apache.jackrabbit.api.observation.JackrabbitEventFilter;
import org.apache.jackrabbit.api.observation.JackrabbitObservationManager;
import org.apache.jackrabbit.core.SessionImpl;
import org.apache.jackrabbit.core.cluster.ClusterNode;
import org.apache.jackrabbit.core.id.NodeId;
import org.apache.jackrabbit.core.nodetype.NodeTypeImpl;
import org.apache.jackrabbit.core.nodetype.NodeTypeManagerImpl;
import org.apache.jackrabbit.spi.Path;
import org.apache.jackrabbit.spi.commons.conversion.NameException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Each <code>Session</code> instance has its own <code>ObservationManager</code>
* instance. The class <code>SessionLocalObservationManager</code> implements
* this behaviour.
*/
public class ObservationManagerImpl implements EventStateCollectionFactory,
JackrabbitObservationManager {
/**
* The logger instance of this class
*/
private static final Logger log = LoggerFactory.getLogger(ObservationManagerImpl.class);
/**
* The <code>Session</code> this <code>ObservationManager</code>
* belongs to.
*/
private final SessionImpl session;
/**
* The cluster node where this session is running.
*/
private final ClusterNode clusterNode;
/**
* The <code>ObservationDispatcher</code>
*/
private final ObservationDispatcher dispatcher;
/**
* The currently set user data.
*/
private String userData;
static {
// preload EventListenerIteratorImpl to prevent classloader issues during shutdown
EventListenerIteratorImpl.class.hashCode();
}
/**
* Creates an <code>ObservationManager</code> instance.
*
* @param dispatcher observation dispatcher
* @param session the <code>Session</code> this ObservationManager
* belongs to.
* @param clusterNode
* @throws NullPointerException if <code>dispatcher</code>, <code>session</code>
* or <code>clusterNode</code> is <code>null</code>.
*/
public ObservationManagerImpl(
ObservationDispatcher dispatcher, SessionImpl session,
ClusterNode clusterNode) {
if (dispatcher == null) {
throw new NullPointerException("dispatcher");
}
if (session == null) {
throw new NullPointerException("session");
}
this.dispatcher = dispatcher;
this.session = session;
this.clusterNode = clusterNode;
}
/**
* {@inheritDoc}
*/
public void addEventListener(EventListener listener,
int eventTypes,
String absPath,
boolean isDeep,
String[] uuid,
String[] nodeTypeName,
boolean noLocal)
throws RepositoryException {
// create filter
EventFilter filter = createEventFilter(eventTypes, Collections.singletonList(absPath),
isDeep, uuid, nodeTypeName, noLocal, false, false);
dispatcher.addConsumer(new EventConsumer(session, listener, filter));
}
@Override
public void addEventListener(EventListener listener, JackrabbitEventFilter filter)
throws RepositoryException {
String[] excludedPaths = filter.getExcludedPaths();
if (excludedPaths.length > 0) {
log.warn("JackrabbitEventFilter excludedPaths is not implemented and will be ignored: {}",
Arrays.toString(excludedPaths));
}
List<String> absPaths = new ArrayList<String>(Arrays.asList(filter.getAdditionalPaths()));
if (filter.getAbsPath() != null) {
absPaths.add(filter.getAbsPath());
}
EventFilter f = createEventFilter(filter.getEventTypes(), absPaths,
filter.getIsDeep(), filter.getIdentifiers(), filter.getNodeTypes(),
filter.getNoLocal(), filter.getNoExternal(), filter.getNoInternal());
dispatcher.addConsumer(new EventConsumer(session, listener, f));
}
/**
* {@inheritDoc}
*/
public void removeEventListener(EventListener listener)
throws RepositoryException {
dispatcher.removeConsumer(
new EventConsumer(session, listener, EventFilter.BLOCK_ALL));
}
/**
* {@inheritDoc}
*/
public EventListenerIterator getRegisteredEventListeners()
throws RepositoryException {
return new EventListenerIteratorImpl(
session,
dispatcher.getSynchronousConsumers(),
dispatcher.getAsynchronousConsumers());
}
/**
* {@inheritDoc}
*/
public void setUserData(String userData) throws RepositoryException {
this.userData = userData;
}
/**
* @return the currently set user data.
*/
String getUserData() {
return userData;
}
/**
* Unregisters all EventListeners.
*/
public void dispose() {
try {
EventListenerIterator it = getRegisteredEventListeners();
while (it.hasNext()) {
EventListener l = it.nextEventListener();
log.debug("removing EventListener: " + l);
removeEventListener(l);
}
} catch (RepositoryException e) {
log.error("Internal error: Unable to dispose ObservationManager.", e);
}
}
/**
* Creates a new event filter with the given restrictions.
*
* @param eventTypes A combination of one or more event type constants encoded as a bitmask.
* @param absPaths absolute paths.
* @param isDeep a <code>boolean</code>.
* @param uuid array of UUIDs.
* @param nodeTypeName array of node type names.
* @param noLocal a <code>boolean</code>.
* @param noExternal a <code>boolean</code>.
* @param noInternal a <code>boolean</code>.
* @return the event filter with the given restrictions.
* @throws RepositoryException if an error occurs.
*/
public EventFilter createEventFilter(int eventTypes,
List<String> absPaths,
boolean isDeep,
String[] uuid,
String[] nodeTypeName,
boolean noLocal,
boolean noExternal,
boolean noInternal)
throws RepositoryException {
// create NodeType instances from names
NodeTypeImpl[] nodeTypes;
if (nodeTypeName == null) {
nodeTypes = null;
} else {
NodeTypeManagerImpl ntMgr = session.getNodeTypeManager();
nodeTypes = new NodeTypeImpl[nodeTypeName.length];
for (int i = 0; i < nodeTypes.length; i++) {
nodeTypes[i] = (NodeTypeImpl) ntMgr.getNodeType(nodeTypeName[i]);
}
}
List<Path> paths = new ArrayList<Path>();
for (String absPath : absPaths) {
try {
Path normalizedPath = session.getQPath(absPath).getNormalizedPath();
if (!normalizedPath.isAbsolute()) {
throw new RepositoryException("absPath must be absolute");
}
paths.add(normalizedPath);
} catch (NameException e) {
String msg = "invalid path syntax: " + absPath;
log.debug(msg);
throw new RepositoryException(msg, e);
} }
NodeId[] ids = null;
if (uuid != null) {
ids = new NodeId[uuid.length];
for (int i = 0; i < uuid.length; i++) {
ids[i] = NodeId.valueOf(uuid[i]);
}
}
// create filter
return new EventFilter(
session, eventTypes, paths, isDeep, ids, nodeTypes, noLocal, noExternal, noInternal);
}
/**
* Returns the event journal for this workspace. The events are filtered
* according to the passed criteria.
*
* @param eventTypes A combination of one or more event type constants encoded as a bitmask.
* @param absPath an absolute path.
* @param isDeep a <code>boolean</code>.
* @param uuid array of UUIDs.
* @param nodeTypeName array of node type names.
* @return the event journal for this repository.
* @throws UnsupportedRepositoryOperationException if this repository does
* not support an event journal (cluster journal disabled).
* @throws RepositoryException if another error occurs.
* @see ObservationManager#getEventJournal(int, String, boolean, String[], String[])
*/
public EventJournal getEventJournal(
int eventTypes, String absPath, boolean isDeep,
String[] uuid, String[] nodeTypeName)
throws RepositoryException {
if (clusterNode == null) {
throw new UnsupportedRepositoryOperationException(
"Event journal is only available in cluster deployments");
}
if (!session.isAdmin()) {
throw new AccessDeniedException("Only administrator session may access EventJournal");
}
EventFilter filter = createEventFilter(
eventTypes, Collections.singletonList(absPath), isDeep, uuid, nodeTypeName, false, false, false);
return new EventJournalImpl(
filter, clusterNode.getJournal(), clusterNode.getId(), session);
}
/**
* Returns an unfiltered event journal for this workspace.
*
* @return the event journal for this repository.
* @throws UnsupportedRepositoryOperationException if this repository does
* not support an event journal (cluster journal disabled).
* @throws RepositoryException if another error occurs.
*/
public EventJournal getEventJournal() throws RepositoryException {
return getEventJournal(-1, "/", true, null, null);
}
//------------------------------------------< EventStateCollectionFactory >
/**
* {@inheritDoc}
* <p>
* Creates an <code>EventStateCollection</code> tied to the session
* which is attached to this <code>ObservationManager</code> instance.
*/
public EventStateCollection createEventStateCollection() {
EventStateCollection esc = new EventStateCollection(dispatcher, session, null);
esc.setUserData(userData);
return esc;
}
}