/* * 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.cluster; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import javax.jcr.RepositoryException; import javax.jcr.nodetype.NoSuchNodeTypeException; import org.apache.jackrabbit.core.id.NodeId; import org.apache.jackrabbit.core.nodetype.InvalidNodeTypeDefException; import org.apache.jackrabbit.core.state.ChangeLog; import org.apache.jackrabbit.core.state.ItemState; import org.apache.jackrabbit.spi.PrivilegeDefinition; import org.apache.jackrabbit.spi.QNodeTypeDefinition; /** * Simple event listener that can be registered for all cluster event listener * types and records external events in an array list. */ public class SimpleEventListener implements LockEventListener, NodeTypeEventListener, NamespaceEventListener, PrivilegeEventListener, UpdateEventListener { /** * List of cluster events received. */ public List clusterEvents = new ArrayList(); //-------------------------------------------------------- LockEventListener /** * {@inheritDoc} */ public void externalLock(NodeId nodeId, boolean isDeep, String lockOwner) throws RepositoryException { clusterEvents.add(new LockEvent(nodeId, isDeep, lockOwner)); } /** * Lock event auxiliary class. */ public static class LockEvent { /** * Node id. */ private final NodeId nodeId; /** * Deep flag. */ private final boolean isDeep; /** * User id. */ private final String userId; /** * Create a new instance of this class. * * @param nodeId node id * @param isDeep deep flag * @param userId user id */ public LockEvent(NodeId nodeId, boolean isDeep, String userId) { this.nodeId = nodeId; this.isDeep = isDeep; this.userId = userId; } /** * Return the node id. * * @return the node id */ public NodeId getNodeId() { return nodeId; } /** * Return a flag indicating whether the lock is deep. * * @return <code>true</code> if the lock is deep; * <code>false</code> otherwise */ public boolean isDeep() { return isDeep; } /** * Return the user owning the lock. * * @return user id */ public String getUserId() { return userId; } /** * {@inheritDoc} */ public int hashCode() { return nodeId.hashCode() ^ userId.hashCode(); } /** * {@inheritDoc} */ public boolean equals(Object obj) { if (obj instanceof LockEvent) { LockEvent other = (LockEvent) obj; return nodeId.equals(other.nodeId) && isDeep == other.isDeep && userId.equals(other.userId); } return false; } } /** * {@inheritDoc} */ public void externalUnlock(NodeId nodeId) throws RepositoryException { clusterEvents.add(new UnlockEvent(nodeId)); } /** * Unlock event auxiliary class. */ public static class UnlockEvent { /** * Node id. */ private final NodeId nodeId; /** * Create a new instance of this class. * * @param nodeId node id */ public UnlockEvent(NodeId nodeId) { this.nodeId = nodeId; } /** * Return the node id. * * @return node id */ public NodeId getNodeId() { return nodeId; } /** * {@inheritDoc} */ public int hashCode() { return nodeId.hashCode(); } /** * {@inheritDoc} */ public boolean equals(Object obj) { if (obj instanceof UnlockEvent) { UnlockEvent other = (UnlockEvent) obj; return nodeId.equals(other.nodeId); } return false; } } //---------------------------------------------------- NodeTypeEventListener /** * {@inheritDoc} */ public void externalRegistered(Collection ntDefs) throws RepositoryException, InvalidNodeTypeDefException { clusterEvents.add(new NodeTypeEvent(NodeTypeEvent.REGISTER, ntDefs)); } /** * {@inheritDoc} */ public void externalReregistered(QNodeTypeDefinition ntDef) throws NoSuchNodeTypeException, InvalidNodeTypeDefException, RepositoryException { ArrayList ntDefs = new ArrayList(); ntDefs.add(ntDef); clusterEvents.add(new NodeTypeEvent(NodeTypeEvent.REREGISTER, ntDefs)); } /** * {@inheritDoc} */ public void externalUnregistered(Collection ntNames) throws RepositoryException, NoSuchNodeTypeException { clusterEvents.add(new NodeTypeEvent(NodeTypeEvent.UNREGISTER, ntNames)); } /** * Node type event auxiliary class. */ public static class NodeTypeEvent { /** * Operation type: registration. */ public static final int REGISTER = NodeTypeRecord.REGISTER; /** * Operation type: re-registration. */ public static final int REREGISTER = NodeTypeRecord.REREGISTER; /** * Operation type: unregistration. */ public static final int UNREGISTER = NodeTypeRecord.UNREGISTER; /** * Operation. */ private int operation; /** * Collection of node type definitions or node type names. */ private Collection collection; /** * Create a new instance of this class. * * @param operation operation * @param collection collection of node type definitions or node * type names */ public NodeTypeEvent(int operation, Collection collection) { this.operation = operation; this.collection = collection; } /** * Return the operation. * * @return operation */ public int getOperation() { return operation; } /** * Return the collection. * * @return collection */ public Collection getCollection() { return collection; } /** * {@inheritDoc} */ public int hashCode() { return operation ^ collection.hashCode(); } /** * {@inheritDoc} */ public boolean equals(Object obj) { if (obj instanceof NodeTypeEvent) { NodeTypeEvent other = (NodeTypeEvent) obj; return operation == other.operation && SimpleEventListener.equals(collection, other.collection); } return false; } } //--------------------------------------------------- NamespaceEventListener /** * {@inheritDoc} */ public void externalRemap(String oldPrefix, String newPrefix, String uri) throws RepositoryException { clusterEvents.add(new NamespaceEvent(oldPrefix, newPrefix, uri)); } /** * Namespace event auxiliary class. */ public static class NamespaceEvent { /** * Old prefix. */ private final String oldPrefix; /** * New prefix. */ private final String newPrefix; /** * URI. */ private final String uri; /** * Create a new instance of this class. * * @param oldPrefix old prefix * @param newPrefix new prefix * @param uri URI */ public NamespaceEvent(String oldPrefix, String newPrefix, String uri) { this.oldPrefix = oldPrefix; this.newPrefix = newPrefix; this.uri = uri; } /** * Return the old prefix. * * @return old prefix */ public String getOldPrefix() { return oldPrefix; } /** * Return the new prefix. * * @return new prefix */ public String getNewPrefix() { return newPrefix; } /** * Return the URI. * * @return URI */ public String getUri() { return uri; } /** * {@inheritDoc} */ public int hashCode() { int hashCode = 0; if (oldPrefix != null) { hashCode ^= oldPrefix.hashCode(); } if (newPrefix != null) { hashCode ^= newPrefix.hashCode(); } if (uri != null) { hashCode ^= uri.hashCode(); } return hashCode; } /** * {@inheritDoc} */ public boolean equals(Object obj) { if (obj instanceof NamespaceEvent) { NamespaceEvent other = (NamespaceEvent) obj; return SimpleEventListener.equals(oldPrefix, other.oldPrefix) && SimpleEventListener.equals(newPrefix, other.newPrefix) && SimpleEventListener.equals(uri, other.uri); } return false; } } //---------------------------------------------< PrivilegeEventListener >--- /** * {@inheritDoc} */ public void externalRegisteredPrivileges(Collection<PrivilegeDefinition> definitions) throws RepositoryException { clusterEvents.add(new PrivilegeEvent(definitions)); } /** * privilege event auxiliary class. */ public static class PrivilegeEvent { /** * Collection of node type definitions or node type names. */ private Collection<PrivilegeDefinition> definitions; /** * Create a new instance of this class. * * @param definitions */ public PrivilegeEvent(Collection<PrivilegeDefinition> definitions) { this.definitions = definitions; } /** * Return the definitions. * * @return definitions */ public Collection<PrivilegeDefinition> getDefinitions() { return definitions; } /** * {@inheritDoc} */ public int hashCode() { return definitions.hashCode(); } /** * {@inheritDoc} */ public boolean equals(Object obj) { if (obj instanceof PrivilegeEvent) { PrivilegeEvent other = (PrivilegeEvent) obj; return SimpleEventListener.equals(definitions, other.definitions); } return false; } } //------------------------------------------------------ UpdateEventListener /** * {@inheritDoc} */ public void externalUpdate(ChangeLog changes, List events, long timestamp, String userData) throws RepositoryException { clusterEvents.add(new UpdateEvent(changes, events, timestamp, userData)); } /** * Update event auxiliary class. */ public static class UpdateEvent implements Update { /** * Change log. */ private final ChangeLog changes; /** * List of <code>EventState</code>s. */ private final List events; /** * Attributes to be stored. */ private final transient Map attributes = new HashMap(); /** * Timestamp when the changes in this update event occured. */ private final long timestamp; /** * The user data associated with this update. */ private final String userData; /** * Create a new instance of this class. * * @param changes change log * @param events list of <code>EventState</code>s * @param timestamp time when the changes in this event occured. * @param userData the user data associated with this update. */ public UpdateEvent(ChangeLog changes, List events, long timestamp, String userData) { this.changes = changes; this.events = events; this.timestamp = timestamp; this.userData = userData; } /** * Return the change log. * * @return the change log */ public ChangeLog getChanges() { return changes; } /** * Return the list of <code>EventState</code>s * * @return list of <code>EventState</code>s */ public List getEvents() { return events; } /** * {@inheritDoc} */ public long getTimestamp() { return timestamp; } public String getUserData() { return userData; } /** * {@inheritDoc} */ public void setAttribute(String name, Object value) { attributes.put(name, value); } /** * {@inheritDoc} */ public Object getAttribute(String name) { return attributes.get(name); } /** * {@inheritDoc} */ public int hashCode() { int h = changes.hashCode() ^ events.hashCode() ^ (int) (timestamp ^ (timestamp >>> 32)); if (userData != null) { h = h ^ userData.hashCode(); } return h; } /** * {@inheritDoc} */ public boolean equals(Object obj) { if (obj instanceof UpdateEvent) { UpdateEvent other = (UpdateEvent) obj; return SimpleEventListener.equals(changes, other.changes) && SimpleEventListener.equals(events, other.events) && timestamp == other.timestamp && SimpleEventListener.equals(userData, other.userData); } return false; } } /** * Return the collected cluster events. * * @return cluster events */ public List getClusterEvents() { return Collections.unmodifiableList(clusterEvents); } /** * Check whether two objects are equals, allowing <code>null</code> values. * * @param o1 object 1 * @param o2 object 2 * @return <code>true</code> if they are equal; <code>false</code> otherwise */ private static boolean equals(Object o1, Object o2) { if (o1 == null) { return o2 == null; } else { return o1.equals(o2); } } /** * Check whether two collections contain the same elements. Made necessary * because the <code>Collections.unmodifiableXXX</code> methods do not * return objects that override <code>equals</code> and <code>hashCode</code>. * * @param c1 collection 1 * @param c2 collection 2 * @return <code>true</code> if they are equal; <code>false</code> otherwise */ private static boolean equals(Collection c1, Collection c2) { if (c1.size() != c2.size()) { return false; } Iterator iter1 = c1.iterator(); Iterator iter2 = c2.iterator(); while (iter1.hasNext()) { Object o1 = iter1.next(); Object o2 = iter2.next(); if (!o1.equals(o2)) { return false; } } return true; } /** * Check whether two changes logs contain the same elements. Not feasible * by comparing the maps because <code>ItemState</code>s do not override * {@link Object#equals(Object)}. * * @param changes1 change log * @param changes2 change log * @return <code>true</code> if the change logs are equals; * <code>false</code> otherwise. */ private static boolean equals(ChangeLog changes1, ChangeLog changes2) { return equals(changes1.addedStates().iterator(), changes2.addedStates().iterator()) && equals(changes1.deletedStates().iterator(), changes2.deletedStates().iterator()) && equals(changes1.modifiedStates().iterator(), changes2.modifiedStates().iterator()); } /** * Check whether two iterators return the same item states, where "same" * means having the same <code>ItemId</code> * @param iter1 first iterator * @param iter2 second iterator * @return <code>true</code> if the two iterators are equal; * <code>false</code> otherwise */ private static boolean equals(Iterator iter1, Iterator iter2) { for (;;) { if (!iter1.hasNext() && !iter2.hasNext()) { return true; } if (iter1.hasNext() && !iter2.hasNext()) { return false; } if (!iter1.hasNext() && iter2.hasNext()) { return false; } ItemState state1 = (ItemState) iter1.next(); ItemState state2 = (ItemState) iter2.next(); return state1.getId().equals(state2.getId()); } } }