/*
* 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.Map;
import java.util.HashMap;
import java.util.Set;
import org.apache.jackrabbit.api.observation.JackrabbitEvent;
import javax.jcr.observation.Event;
import org.apache.jackrabbit.core.id.NodeId;
import org.apache.jackrabbit.core.SessionImpl;
import org.apache.jackrabbit.core.value.InternalValue;
import org.apache.jackrabbit.spi.commons.AdditionalEventInfo;
import org.apache.jackrabbit.spi.commons.conversion.MalformedPathException;
import org.apache.jackrabbit.spi.Name;
import org.apache.jackrabbit.spi.Path;
import org.apache.jackrabbit.spi.commons.name.PathFactoryImpl;
import org.apache.jackrabbit.spi.commons.value.ValueFormat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.jcr.RepositoryException;
/**
* Implementation of the {@link javax.jcr.observation.Event} and
* the {@link JackrabbitEvent} interface.
*/
public final class EventImpl implements JackrabbitEvent, AdditionalEventInfo, Event {
/**
* Logger instance for this class
*/
private static final Logger log = LoggerFactory.getLogger(EventImpl.class);
/**
* The session of the {@link javax.jcr.observation.EventListener} this
* event will be delivered to.
*/
private final SessionImpl session;
/**
* The shared {@link EventState} object.
*/
private final EventState eventState;
/**
* The timestamp of this event.
*/
private final long timestamp;
/**
* The user data associated with this event.
*/
private final String userData;
/**
* Cached String value of this <code>Event</code> instance.
*/
private String stringValue;
/**
* Creates a new {@link javax.jcr.observation.Event} instance based on an
* {@link EventState eventState}.
*
* @param session the session of the registered <code>EventListener</code>
* where this <code>Event</code> will be delivered to.
* @param eventState the underlying <code>EventState</code>.
* @param timestamp the time when the change occurred that caused this event.
* @param userData the user data associated with this event.
*/
EventImpl(SessionImpl session, EventState eventState,
long timestamp, String userData) {
this.session = session;
this.eventState = eventState;
this.timestamp = timestamp;
this.userData = userData;
}
//---------------------------------------------------------------< Event >
/**
* {@inheritDoc}
*/
public int getType() {
return eventState.getType();
}
/**
* {@inheritDoc}
*/
public String getPath() throws RepositoryException {
Path p = getQPath();
return p != null ? session.getJCRPath(p) : null;
}
/**
* {@inheritDoc}
*/
public String getUserID() {
return eventState.getUserId();
}
/**
* {@inheritDoc}
*/
public long getDate() {
return timestamp;
}
/**
* {@inheritDoc}
*/
public String getUserData() {
return userData;
}
/**
* {@inheritDoc}
*/
public String getIdentifier() throws RepositoryException {
if (eventState.getType() == Event.PERSIST) {
return null;
}
else {
NodeId id = eventState.getChildId();
if (id != null) {
return id.toString();
}
else {
// property event
return eventState.getParentId().toString();
}
}
}
/**
* {@inheritDoc}
*/
public Map<String, String> getInfo() throws RepositoryException {
Map<String, String> info = new HashMap<String, String>();
for (Map.Entry<String, InternalValue> entry : eventState.getInfo().entrySet()) {
InternalValue value = entry.getValue();
String strValue = null;
if (value != null) {
strValue = ValueFormat.getJCRString(value, session);
}
info.put(entry.getKey(), strValue);
}
return info;
}
//-----------------------------------------------------------< EventImpl >
/**
* Returns the <code>Path</code> of this event.
*
* @return path or <code>null</code> when no path is associated with the event
* @throws RepositoryException if the path can't be constructed
*/
public Path getQPath() throws RepositoryException {
try {
Path parent = eventState.getParentPath();
Path child = eventState.getChildRelPath();
if (parent == null || child == null) {
// an event without associated path information
return null;
}
else {
int index = child.getIndex();
if (index > 0) {
return PathFactoryImpl.getInstance().create(parent, child.getName(), index, false);
} else {
return PathFactoryImpl.getInstance().create(parent, child.getName(), false);
}
}
} catch (MalformedPathException e) {
String msg = "internal error: malformed path for event";
log.debug(msg);
throw new RepositoryException(msg, e);
}
}
/**
* Returns the uuid of the parent node.
*
* @return the uuid of the parent node.
*/
public NodeId getParentId() {
return eventState.getParentId();
}
/**
* Returns the id of a child node operation.
* If this <code>Event</code> was generated for a property
* operation this method returns <code>null</code>.
*
* @return the id of a child node operation.
*/
public NodeId getChildId() {
return eventState.getChildId();
}
/**
* Returns a flag indicating whether the child node of this event is a
* shareable node. Only applies to node added/removed events.
*
* @return <code>true</code> for a shareable child node, <code>false</code>
* otherwise.
*/
public boolean isShareableChildNode() {
return eventState.isShareableNode();
}
/**
* Return a flag indicating whether this is an externally generated event.
*
* @return <code>true</code> if this is an external event;
* <code>false</code> otherwise
* @see JackrabbitEvent#isExternal()
*/
public boolean isExternal() {
return eventState.isExternal();
}
//---------------------------------------------------------------< AdditionalEventInfo >
/**
* @return the primary node type of the node associated with the event
* @see AdditionalEventInfo#getPrimaryNodeTypeName()
*/
public Name getPrimaryNodeTypeName() {
return eventState.getNodeType();
}
/**
* @return the mixin node types of the node associated with the event
* @see AdditionalEventInfo#getMixinTypeNames()
*/
public Set<Name> getMixinTypeNames() {
return eventState.getMixinNames();
}
/**
* @return the specified session attribute
*/
public Object getSessionAttribute(String name) {
return eventState.getSession().getAttribute(name);
}
/**
* Returns a String representation of this <code>Event</code>.
*
* @return a String representation of this <code>Event</code>.
*/
public String toString() {
if (stringValue == null) {
StringBuilder sb = new StringBuilder();
sb.append("Event: Path: ");
try {
sb.append(getPath());
} catch (RepositoryException e) {
log.error("Exception retrieving path: " + e);
sb.append("[Error retrieving path]");
}
sb.append(", ").append(EventState.valueOf(getType())).append(": ");
sb.append(", UserId: ").append(getUserID());
sb.append(", Timestamp: ").append(timestamp);
sb.append(", UserData: ").append(userData);
sb.append(", Info: ").append(eventState.getInfo());
stringValue = sb.toString();
}
return stringValue;
}
/**
* @see Object#hashCode()
*/
public int hashCode() {
int h = eventState.hashCode() ^ new Long(timestamp).hashCode() ^ session.hashCode();
if (userData != null) {
h = h ^ userData.hashCode();
}
return h;
}
/**
* Returns <code>true</code> if this <code>Event</code> is equal to another
* object.
* <p>
* Two <code>Event</code> instances are equal if their respective
* <code>EventState</code> instances are equal and both <code>Event</code>
* instances are intended for the same <code>Session</code> that registerd
* the <code>EventListener</code>.
*
* @param obj the reference object with which to compare.
* @return <code>true</code> if this <code>Event</code> is equal to another
* object.
*/
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj instanceof EventImpl) {
EventImpl other = (EventImpl) obj;
return this.eventState.equals(other.eventState)
&& this.session.equals(other.session)
&& this.timestamp == other.timestamp
&& equals(this.userData, other.userData);
}
return false;
}
/**
* Returns <code>true</code> if the objects are equal or both are
* <code>null</code>; otherwise returns <code>false</code>.
*
* @param o1 an object.
* @param o2 another object.
* @return <code>true</code> if equal; <code>false</code> otherwise.
*/
private static boolean equals(Object o1, Object o2) {
if (o1 == null) {
return o2 == null;
} else {
return o1.equals(o2);
}
}
}