Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Remove JCR leakage
- Use new PrefixingIdentifierTranslator

Partial resolution of: https://jira.duraspace.org/browse/FCREPO-1489
  • Loading branch information
escowles authored and Andrew Woods committed Apr 24, 2015
1 parent e4f762d commit ace483a
Show file tree
Hide file tree
Showing 3 changed files with 188 additions and 97 deletions.
5 changes: 5 additions & 0 deletions pom.xml
Expand Up @@ -53,6 +53,11 @@
<artifactId>fcrepo-kernel</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.fcrepo</groupId>
<artifactId>fcrepo-kernel-impl</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.fcrepo</groupId>
<artifactId>fcrepo-mint</artifactId>
Expand Down
218 changes: 141 additions & 77 deletions src/main/java/org/fcrepo/audit/InternalAuditor.java
Expand Up @@ -15,14 +15,26 @@
*/
package org.fcrepo.audit;

import static com.hp.hpl.jena.datatypes.xsd.XSDDatatype.XSDdateTime;
import static com.hp.hpl.jena.datatypes.xsd.XSDDatatype.XSDstring;
import static com.hp.hpl.jena.rdf.model.ModelFactory.createDefaultModel;
import static com.hp.hpl.jena.rdf.model.ResourceFactory.createProperty;
import static com.hp.hpl.jena.rdf.model.ResourceFactory.createResource;
import static com.hp.hpl.jena.rdf.model.ResourceFactory.createTypedLiteral;

import static org.fcrepo.audit.AuditNamespaces.AUDIT;
import static org.fcrepo.audit.AuditNamespaces.EVENT_TYPE;
import static org.fcrepo.audit.AuditNamespaces.PREMIS;
import static org.fcrepo.audit.AuditNamespaces.PROV;
import static org.fcrepo.audit.AuditNamespaces.REPOSITORY;
import static org.fcrepo.kernel.RdfLexicon.RDF_NAMESPACE;
import static org.fcrepo.kernel.RdfLexicon.REPOSITORY_NAMESPACE;
import static org.modeshape.jcr.api.JcrConstants.JCR_CONTENT;
import static org.slf4j.LoggerFactory.getLogger;

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
Expand All @@ -33,14 +45,16 @@
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.inject.Inject;
import javax.jcr.Node;
import javax.jcr.PropertyType;
import javax.jcr.RepositoryException;

import org.fcrepo.kernel.impl.rdf.impl.PrefixingIdentifierTranslator;
import org.fcrepo.kernel.exception.RepositoryRuntimeException;
import org.fcrepo.kernel.identifiers.PidMinter;
import org.fcrepo.kernel.models.FedoraResource;
import org.fcrepo.kernel.observer.FedoraEvent;
import org.fcrepo.kernel.services.ContainerService;
import org.fcrepo.kernel.utils.EventType;
import org.fcrepo.kernel.utils.iterators.RdfStream;
import org.fcrepo.mint.UUIDPathMinter;

import org.modeshape.jcr.api.Repository;
Expand All @@ -49,15 +63,23 @@

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.hp.hpl.jena.rdf.model.Model;
import com.hp.hpl.jena.rdf.model.RDFNode;
import com.hp.hpl.jena.rdf.model.Resource;
import com.hp.hpl.jena.rdf.model.ResourceFactory;
import com.hp.hpl.jena.rdf.model.Statement;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.collect.Iterables;
import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;

/**
* Auditor implementation that creates audit nodes in the repository.
* @author mohideen
* @date 4/15/15.
* @author escowles
* @since 2015-04-15
*/
public class InternalAuditor implements Auditor {

Expand Down Expand Up @@ -85,26 +107,31 @@ public class InternalAuditor implements Auditor {

/**
* Register with the EventBus to receive events.
* @throws RepositoryRuntimeException
*/
@PostConstruct
public void register() throws RepositoryException {
AUDIT_CONTAINER_LOCATION = System.getProperty(AUDIT_CONTAINER);
if (AUDIT_CONTAINER_LOCATION != null) {
LOGGER.info("Initializing: {}", this.getClass().getCanonicalName());
eventBus.register(this);
if (!AUDIT_CONTAINER_LOCATION.startsWith("/")) {
AUDIT_CONTAINER_LOCATION = "/" + AUDIT_CONTAINER_LOCATION;
}
if (AUDIT_CONTAINER_LOCATION.endsWith("/")) {
AUDIT_CONTAINER_LOCATION = AUDIT_CONTAINER_LOCATION.substring(0,
AUDIT_CONTAINER_LOCATION.length() - 2);
public void register() throws RepositoryRuntimeException {
try {
AUDIT_CONTAINER_LOCATION = System.getProperty(AUDIT_CONTAINER);
if (AUDIT_CONTAINER_LOCATION != null) {
LOGGER.info("Initializing: {}", this.getClass().getCanonicalName());
eventBus.register(this);
if (!AUDIT_CONTAINER_LOCATION.startsWith("/")) {
AUDIT_CONTAINER_LOCATION = "/" + AUDIT_CONTAINER_LOCATION;
}
if (AUDIT_CONTAINER_LOCATION.endsWith("/")) {
AUDIT_CONTAINER_LOCATION = AUDIT_CONTAINER_LOCATION.substring(0,
AUDIT_CONTAINER_LOCATION.length() - 2);
}
session = repository.login();
containerService.findOrCreate(session, AUDIT_CONTAINER_LOCATION);
session.save();
} else {
LOGGER.warn("Cannot Initialize: {}", this.getClass().getCanonicalName());
LOGGER.warn("System property not found: " + AUDIT_CONTAINER);
}
session = repository.login();
containerService.findOrCreate(session, AUDIT_CONTAINER_LOCATION);
session.save();
} else {
LOGGER.warn("Cannot Initialize: {}", this.getClass().getCanonicalName());
LOGGER.warn("System property not found: " + AUDIT_CONTAINER);
} catch (RepositoryException e) {
throw new RepositoryRuntimeException(e);
}
}

Expand All @@ -113,31 +140,35 @@ public void register() throws RepositoryException {
*
* @param event
* The {@link FedoraEvent} to record.
* @throws RepositoryException
* @throws RepositoryRuntimeException
*/
@Subscribe
public void recordEvent(final FedoraEvent event) throws RepositoryException, IOException {
LOGGER.debug("Event detected: {} {}", event.getUserID(), event.getPath());
boolean isParentNodeLastModifiedEvent = false;
final String eventType = getEventURIs(event.getTypes());
final Set<String> properties = event.getProperties();
if (eventType.contains(PROPERTY_CHANGED)) {
isParentNodeLastModifiedEvent = true;
final Iterator<String> propertiesIter = properties.iterator();
String property;
while (properties.iterator().hasNext()) {
property = propertiesIter.next();
if (!property.equals(LAST_MODIFIED) && !property.equals(LAST_MODIFIED_BY)) {
/* adding/removing a file updates the lastModified property of the parent container,
so ignore updates when only lastModified is changed */
isParentNodeLastModifiedEvent = false;
break;
public void recordEvent(final FedoraEvent event) throws RepositoryRuntimeException, IOException {
try {
LOGGER.debug("Event detected: {} {}", event.getUserID(), event.getPath());
boolean isParentNodeLastModifiedEvent = false;
final String eventType = getEventURIs(event.getTypes());
final Set<String> properties = event.getProperties();
if (eventType.contains(PROPERTY_CHANGED)) {
isParentNodeLastModifiedEvent = true;
final Iterator<String> propertiesIter = properties.iterator();
String property;
while (properties.iterator().hasNext()) {
property = propertiesIter.next();
if (!property.equals(LAST_MODIFIED) && !property.equals(LAST_MODIFIED_BY)) {
/* adding/removing a file updates the lastModified property of the parent container,
so ignore updates when only lastModified is changed */
isParentNodeLastModifiedEvent = false;
break;
}
}
}
}
if (!event.getPath().startsWith(AUDIT_CONTAINER_LOCATION)
&& !isParentNodeLastModifiedEvent) {
createAuditNode(event);
if (!event.getPath().startsWith(AUDIT_CONTAINER_LOCATION)
&& !isParentNodeLastModifiedEvent) {
createAuditNode(event);
}
} catch (RepositoryException e) {
throw new RepositoryRuntimeException(e);
}
}

Expand All @@ -152,7 +183,7 @@ public void releaseConnections() {

// namespaces and properties
private static final String INTERNAL_EVENT = AUDIT + "InternalEvent";
private static final String PREMIS_EVENT = "premis:Event";
private static final String PREMIS_EVENT = PREMIS + "Event";
private static final String PROV_EVENT = PROV + "InstantaneousEvent";

private static final String CONTENT_MOD = AUDIT + "contentModification";
Expand All @@ -163,12 +194,11 @@ public void releaseConnections() {
private static final String OBJECT_ADD = EVENT_TYPE + "cre";
private static final String OBJECT_REM = EVENT_TYPE + "del";

private static final String PREMIS_TIME = "premis:hasEventDateTime";
private static final String PREMIS_OBJ = "premis:hasEventRelatedObject";
private static final String PREMIS_AGENT = "premis:hasEventRelatedAgent";
private static final String PREMIS_TYPE = "premis:hasEventType";
private static final String PREMIS_TIME = PREMIS + "hasEventDateTime";
private static final String PREMIS_AGENT = PREMIS + "hasEventRelatedAgent";
private static final String PREMIS_TYPE = PREMIS + "hasEventType";

private static final String RDF_TYPE = "rdf:type";
private static final String RDF_TYPE = RDF_NAMESPACE + "type";

private static final String HAS_CONTENT = REPOSITORY + "hasContent";
private static final String LAST_MODIFIED = REPOSITORY + "lastModified";
Expand All @@ -177,42 +207,76 @@ public void releaseConnections() {
private static final String NODE_REMOVED = REPOSITORY + "NODE_REMOVED";
private static final String PROPERTY_CHANGED = REPOSITORY + "PROPERTY_CHANGED";

// JCR property name, not URI
private static final String PREMIS_OBJ = "premis:hasEventRelatedObject";

/**
* Creates a node for the audit event under the configured container.
*
* @param event
* @throws RepositoryException
* @throws RepositoryRuntimeException
*/
public void createAuditNode(final FedoraEvent event) throws RepositoryException, IOException {
final String uuid = pidMinter.mintPid();
final String userData = event.getUserData();
final ObjectMapper mapper = new ObjectMapper();
final JsonNode json = mapper.readTree(userData);
final String userAgent = json.get("userAgent").asText();
final String baseURL = json.get("baseURL").asText();
final String path = event.getPath();
final String uri = baseURL + (baseURL.endsWith("/") ? path.substring(1) : path);
final Long timestamp = event.getDate();
final DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
df.setTimeZone(TimeZone.getTimeZone("UTC"));
final String eventDate = df.format(new Date(timestamp));
final String userID = event.getUserID();
final String eventType = getEventURIs(event.getTypes());
final String properties = Joiner.on(',').join(event.getProperties());
final String auditEventType = getAuditEventType(eventType, properties);
final Node auditNode = containerService.findOrCreate(session, AUDIT_CONTAINER_LOCATION + "/" + uuid).getNode();

LOGGER.debug("Audit node {} created for event.", uuid);

auditNode.addMixin("fedora:Resource");
auditNode.setProperty(RDF_TYPE, new String[]{INTERNAL_EVENT, PREMIS_EVENT, PROV_EVENT});
auditNode.setProperty(PREMIS_TIME, eventDate, PropertyType.DATE);
auditNode.setProperty(PREMIS_OBJ, uri, PropertyType.URI);
auditNode.setProperty(PREMIS_AGENT, new String[]{userID,userAgent});
if (auditEventType != null) {
auditNode.setProperty(PREMIS_TYPE, auditEventType, PropertyType.URI);
public void createAuditNode(final FedoraEvent event) throws RepositoryRuntimeException, IOException {
try {
final String uuid = pidMinter.mintPid();
final String userData = event.getUserData();
final ObjectMapper mapper = new ObjectMapper();
final JsonNode json = mapper.readTree(userData);
final String userAgent = json.get("userAgent").asText();
String baseURL = json.get("baseURL").asText();
if (baseURL.endsWith("/")) {
baseURL = baseURL.substring(0, baseURL.length() - 1);
}
String path = event.getPath();
if ( path.endsWith("/" + JCR_CONTENT) ) {
path = path.replaceAll("/" + JCR_CONTENT,"");
}
final String uri = baseURL + path;
final Long timestamp = event.getDate();
final DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
df.setTimeZone(TimeZone.getTimeZone("UTC"));
final String eventDate = df.format(new Date(timestamp));
final String userID = event.getUserID();
final String eventType = getEventURIs(event.getTypes());
final String properties = Joiner.on(',').join(event.getProperties());
final String auditEventType = getAuditEventType(eventType, properties);
final FedoraResource auditResource = containerService.findOrCreate(session,
AUDIT_CONTAINER_LOCATION + "/" + uuid);

LOGGER.debug("Audit node {} created for event.", uuid);

final Model m = createDefaultModel();
final String auditResourceURI = baseURL + AUDIT_CONTAINER_LOCATION + "/" + uuid;
final Resource s = createResource(auditResourceURI);
m.add(createStatement(s, RDF_TYPE, createResource(INTERNAL_EVENT)));
m.add(createStatement(s, RDF_TYPE, createResource(PREMIS_EVENT)));
m.add(createStatement(s, RDF_TYPE, createResource(PROV_EVENT)));
m.add(createStatement(s, PREMIS_TIME, createTypedLiteral(eventDate, XSDdateTime)));
m.add(createStatement(s, PREMIS_AGENT, createTypedLiteral(userID, XSDstring)));
m.add(createStatement(s, PREMIS_AGENT, createTypedLiteral(userAgent, XSDstring)));
if (auditEventType != null) {
m.add(createStatement(s, PREMIS_TYPE, createResource(auditEventType)));
}

auditResource.replaceProperties(new PrefixingIdentifierTranslator(session, baseURL + "/"), m,
new RdfStream(), containerService);

// set link to impacted object using a URI property to preserve the link if it's deleted
try {
auditResource.setURIProperty(PREMIS_OBJ, new URI(uri));
} catch (URISyntaxException e) {
LOGGER.warn("Error creating URI for repository resource {}", uri);
}

session.save();
} catch (RepositoryException e) {
throw new RepositoryRuntimeException(e);
}
session.save();
}

@VisibleForTesting
protected Statement createStatement(final Resource subject, final String property, final RDFNode object) {
return ResourceFactory.createStatement(subject, createProperty(property), object);
}

/**
Expand Down

0 comments on commit ace483a

Please sign in to comment.