<target name="deploy" depends="package"
description="deploy nuxeo server libs to ${jee.server.cspace}">
+ <ant antfile="reindex/build.xml" target="deploy" inheritall="false"/>
<ant antfile="updateobjectlocationonmove/build.xml" target="deploy" inheritall="false"/>
<ant antfile="updaterelationsondelete/build.xml" target="deploy" inheritall="false"/>
<ant antfile="updateimagederivatives/build.xml" target="deploy" inheritall="false"/>
<ant antfile="naturalhistory/build.xml" target="deploy" inheritall="false"/>
- <ant antfile="botgarden/build.xml" target="deploy" inheritall="false"/>
+ <ant antfile="botgarden/build.xml" target="deploy" inheritall="false"/>
</target>
<target name="undeploy"
description="undeploy nuxeo server libs from ${jee.server.cspace}">
+ <ant antfile="reindex/build.xml" target="undeploy" inheritall="false"/>
<ant antfile="updateobjectlocationonmove/build.xml" target="undeploy" inheritall="false"/>
<ant antfile="updaterelationsondelete/build.xml" target="undeploy" inheritall="false"/>
<ant antfile="updateimagederivatives/build.xml" target="undeploy" inheritall="false"/>
<ant antfile="naturalhistory/build.xml" target="undeploy" inheritall="false"/>
- <ant antfile="botgarden/build.xml" target="undeploy" inheritall="false"/>
+ <ant antfile="botgarden/build.xml" target="undeploy" inheritall="false"/>
</target>
<target name="dist"
description="generate distribution for nuxeo server libs" depends="package">
+ <ant antfile="reindex/build.xml" target="dist" inheritall="false"/>
<ant antfile="updateobjectlocationonmove/build.xml" target="dist" inheritall="false"/>
<ant antfile="updaterelationsondelete/build.xml" target="dist" inheritall="false"/>
<ant antfile="updateimagederivatives/build.xml" target="dist" inheritall="false"/>
<ant antfile="naturalhistory/build.xml" target="dist" inheritall="false"/>
- <ant antfile="botgarden/build.xml" target="dist" inheritall="false"/>
+ <ant antfile="botgarden/build.xml" target="dist" inheritall="false"/>
</target>
</project>
<name>services.3rdparty.nuxeo.listener</name>
<modules>
+ <module>reindex</module>
<module>updateobjectlocationonmove</module>
<module>updaterelationsondelete</module>
<module>updateimagederivatives</module>
<dependency>
<groupId>org.nuxeo.ecm.platform</groupId>
<artifactId>nuxeo-platform-filemanager-api</artifactId>
- <version>${nuxeo.platform.version}</version>
+ <version>${nuxeo.platform.version}</version>
</dependency>
</dependencies>
--- /dev/null
+listener.module.name=reindex
--- /dev/null
+<project name="org.collectionspace.services.3rdparty.nuxeo.listener.reindex">
+ <description>
+ CollectionSpace Nuxeo listener component type
+ </description>
+ <!-- Set global properties for this build -->
+ <property name="services.trunk" value="../../../.."/>
+ <!-- Environment should be declared before reading build.properties -->
+ <property environment="env" />
+ <!-- Set global properties for this build -->
+ <property file="${services.trunk}/build.properties" />
+ <!-- Set local properties for this build -->
+ <property file="build.properties" />
+ <property name="mvn.opts" value="-V" />
+ <property name="src" location="src"/>
+ <property name="build" location="build"/>
+ <property name="dist" location="dist"/>
+
+ <!-- module.name variable is set in the local properties file -->
+ <property name="jar.name"
+ value="org.collectionspace.services.listener.${listener.module.name}-${cspace.release}.jar"/>
+
+ <property name="jar.all"
+ value="org.collectionspace.services.listener.${listener.module.name}-*.jar"/>
+
+ <condition property="osfamily-unix">
+ <os family="unix" />
+ </condition>
+ <condition property="osfamily-windows">
+ <os family="windows" />
+ </condition>
+
+ <target name="init" >
+ <!-- Create the time stamp -->
+ <tstamp/>
+ <!-- Create the build directory structure used by compile -->
+ <mkdir dir="${build}"/>
+ </target>
+
+ <target name="package" depends="package-unix,package-windows"
+ description="Package CollectionSpace Services" />
+ <target name="package-unix" if="osfamily-unix">
+ <exec executable="mvn" failonerror="true">
+ <arg value="package" />
+ <arg value="-Dmaven.test.skip=true" />
+ <arg value="-f" />
+ <arg value="${basedir}/pom.xml" />
+ <arg value="-N" />
+ <arg value="${mvn.opts}" />
+ </exec>
+ </target>
+ <target name="package-windows" if="osfamily-windows">
+ <exec executable="cmd" failonerror="true">
+ <arg value="/c" />
+ <arg value="mvn" />
+ <arg value="package" />
+ <arg value="-Dmaven.test.skip=true" />
+ <arg value="-f" />
+ <arg value="${basedir}/pom.xml" />
+ <arg value="-N" />
+ <arg value="${mvn.opts}" />
+ </exec>
+ </target>
+
+ <target name="install" depends="install-unix,install-windows"
+ description="Install" />
+ <target name="install-unix" if="osfamily-unix">
+ <exec executable="mvn" failonerror="true">
+ <arg value="install" />
+ <arg value="-Dmaven.test.skip=true" />
+ <arg value="-f" />
+ <arg value="${basedir}/pom.xml" />
+ <arg value="-N" />
+ <arg value="${mvn.opts}" />
+ </exec>
+ </target>
+ <target name="install-windows" if="osfamily-windows">
+ <exec executable="cmd" failonerror="true">
+ <arg value="/c" />
+ <arg value="mvn" />
+ <arg value="install" />
+ <arg value="-Dmaven.test.skip=true" />
+ <arg value="-f" />
+ <arg value="${basedir}/pom.xml" />
+ <arg value="-N" />
+ <arg value="${mvn.opts}" />
+ </exec>
+ </target>
+
+ <target name="deploy" depends="install"
+ description="deploy collectionspace core doctype in ${jee.server.nuxeo}">
+ <copy file="${basedir}/target/${jar.name}"
+ todir="${jee.deploy.nuxeo.plugins}"/>
+ </target>
+
+ <target name="undeploy"
+ description="undeploy collectionspace Thumbnail service from ${jee.server.nuxeo}">
+ <delete>
+ <fileset dir="${jee.deploy.nuxeo.plugins}">
+ <include name="${jar.all}"/>
+ </fileset>
+ </delete>
+ </target>
+
+</project>
--- /dev/null
+<?xml version="1.0"?>
+<project
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
+ xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <artifactId>org.collectionspace.services.3rdparty.nuxeo.listener</artifactId>
+ <groupId>org.collectionspace.services</groupId>
+ <version>5.2-SNAPSHOT</version>
+ </parent>
+ <properties>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ </properties>
+ <artifactId>org.collectionspace.services.listener.reindex</artifactId>
+ <name>org.collectionspace.services.listener.reindex</name>
+ <url>http://maven.apache.org</url>
+
+
+ <dependencies>
+ <dependency>
+ <groupId>org.collectionspace.services</groupId>
+ <artifactId>org.collectionspace.services.common</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <resources>
+ <resource>
+ <directory>src/main/resources</directory>
+ <filtering>true</filtering>
+ </resource>
+ </resources>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jar-plugin</artifactId>
+ <configuration>
+ <archive>
+ <manifestFile> src/main/resources/META-INF/MANIFEST.MF </manifestFile>
+ <manifestEntries>
+ <Bundle-Version>${eclipseVersion}</Bundle-Version>
+ <Bundle-ManifestVersion>2</Bundle-ManifestVersion>
+ </manifestEntries>
+ </archive>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
--- /dev/null
+package org.collectionspace.services.listener;
+
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.commons.collections.ListUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.nuxeo.ecm.core.api.DocumentModel;
+import org.nuxeo.ecm.core.api.LifeCycleConstants;
+import org.nuxeo.ecm.core.api.event.DocumentEventTypes;
+import org.nuxeo.ecm.core.event.Event;
+import org.nuxeo.ecm.core.event.EventBundle;
+import org.nuxeo.ecm.core.event.PostCommitEventListener;
+import org.nuxeo.ecm.core.event.impl.DocumentEventContext;
+import org.nuxeo.elasticsearch.ElasticSearchComponent;
+import org.nuxeo.elasticsearch.api.ElasticSearchService;
+import org.nuxeo.runtime.api.Framework;
+
+/**
+ * Event listener that triggers reindexing of records in Elasticsearch when an associated record
+ * is created/updated/deleted. When a record is created or updated Nuxeo will automatically
+ * reindex it in ElasticSearch, but Nuxeo does not know about other records that may also need to
+ * be reindexed; for example, if a related record denormalizes data from the updated record at
+ * index time.
+ */
+public class Reindex implements PostCommitEventListener {
+ // FIXME: This listener runs asynchronously post-commit, so that reindexing records after a
+ // save does not hold up the save. In order to make it async, this class does not extend
+ // AbstractCSEventListenerImpl, because AbstractCSEventListenerImpl does not implement
+ // PostCommitEventListener (DRYD-477). Therefore this listener is not able to use the
+ // isRegistered method of AbstractCSEventListenerImpl to determine if it has been registered to
+ // run for the current tenant. Instead, it relies on the ReindexSupport listener, which does
+ // extend AbstractCSEventListenerImpl, to set a property in the event context that is used to
+ // determine if this listener should run. This means that this listener will be considered to
+ // be registered if and only if the ReindexSupport listener is registered.
+
+ public static final String IS_REGISTERED_KEY = "Reindex.IS_REGISTERED";
+ public static final String PREV_COVERAGE_KEY = "Reindex.PREV_COVERAGE";
+ public static final String PREV_PUBLISH_TO_KEY = "Reindex.PREV_PUBLISH_TO";
+
+ @Override
+ public void handleEvent(EventBundle events) {
+ // When a media record is created, reindex the material item that is referenced by its
+ // coverage field.
+
+ // When a media record is updated and the coverage changed, reindex both the old and new
+ // referenced material items.
+
+ // When a media record is deleted, reindex the material item that was referenced by its
+ // coverage field.
+
+ // TODO: Make this configurable. This is currently hardcoded to the needs of the material
+ // profile/Material Order application.
+
+ if (Framework.isBooleanPropertyTrue("elasticsearch.enabled") && events.size() > 0) {
+ Iterator<Event> iter = events.iterator();
+
+ while (iter.hasNext()) {
+ Event event = iter.next();
+ DocumentEventContext eventContext = (DocumentEventContext) event.getContext();
+ Boolean isRegistered = (Boolean) eventContext.getProperty(IS_REGISTERED_KEY);
+
+ if (isRegistered != null && isRegistered == true) {
+ DocumentModel doc = eventContext.getSourceDocument();
+ String docType = doc.getType();
+ String eventName = event.getName();
+
+ if (docType.startsWith("Media")) {
+ if (
+ eventName.equals(DocumentEventTypes.DOCUMENT_CREATED) ||
+ eventName.equals(DocumentEventTypes.DOCUMENT_UPDATED)
+ ) {
+ String prevCoverage = (String) eventContext.getProperty(PREV_COVERAGE_KEY);
+ String coverage = (String) doc.getProperty("media_common", "coverage");
+
+ List<String> prevPublishTo = (List<String>) eventContext.getProperty(PREV_PUBLISH_TO_KEY);
+ List<String> publishTo = (List<String>) doc.getProperty("media_materials", "publishToList");
+
+ if (doc.getCurrentLifeCycleState().equals(LifeCycleConstants.DELETED_STATE)) {
+ reindex(doc.getRepositoryName(), coverage);
+ }
+ else if (
+ !ListUtils.isEqualList(prevPublishTo, publishTo) ||
+ !StringUtils.equals(prevCoverage, coverage)
+ ) {
+ if (!StringUtils.equals(prevCoverage, coverage)) {
+ reindex(doc.getRepositoryName(), prevCoverage);
+ }
+
+ reindex(doc.getRepositoryName(), coverage);
+ }
+ }
+ else if (eventName.equals(DocumentEventTypes.DOCUMENT_REMOVED)) {
+ String prevCoverage = (String) eventContext.getProperty(PREV_COVERAGE_KEY);
+
+ reindex(doc.getRepositoryName(), prevCoverage);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private void reindex(String repositoryName, String refName) {
+ if (StringUtils.isEmpty(refName)) {
+ return;
+ }
+
+ String escapedRefName = refName.replace("'", "\\'");
+ String query = String.format("SELECT ecm:uuid FROM Materialitem WHERE collectionspace_core:refName = '%s'", escapedRefName);
+
+ ElasticSearchComponent es = (ElasticSearchComponent) Framework.getService(ElasticSearchService.class);
+ es.runReindexingWorker(repositoryName, query);
+ }
+}
--- /dev/null
+package org.collectionspace.services.listener;
+
+import java.io.Serializable;
+import java.util.List;
+
+import org.collectionspace.services.nuxeo.listener.AbstractCSEventListenerImpl;
+import org.nuxeo.ecm.core.api.DocumentModel;
+import org.nuxeo.ecm.core.api.event.CoreEventConstants;
+import org.nuxeo.ecm.core.api.event.DocumentEventTypes;
+import org.nuxeo.ecm.core.event.Event;
+import org.nuxeo.ecm.core.event.impl.DocumentEventContext;
+import org.nuxeo.runtime.api.Framework;
+
+/**
+ * Event listener that stores the values of fields of interest before documents are updated or
+ * deleted. This is necessary because the previous/deleted doument model will not be available
+ * to a post-modification/deletion event listener. Storing the previous/deleted values allows
+ * the post-modification/deletion event listener to take action if a field value was changed,
+ * or if a document was deleted that had a certain field value.
+ *
+ * This is a separate class from the Reindex listener, because the Reindex listener should be
+ * async and post-commit, so it must implement PostCommitEventListener. This listener must be
+ * synchronous and pre-commit, so it must implement EventListener. Nuxeo does not support
+ * a single class that implements both PostCommitEventListener and EventListener (such a listener
+ * will only run synchronously).
+ */
+public class ReindexSupport extends AbstractCSEventListenerImpl {
+
+ @Override
+ public void handleEvent(Event event) {
+ // When a media record is about to be updated, store the value of the coverage and
+ // publishToList fields.
+
+ // When a media record is about to be removed, store the value of the coverage field.
+
+ // TODO: Make this configurable. This is currently hardcoded to the needs of the material
+ // profile/Material Order application.
+
+ if (isRegistered(event)) {
+ DocumentEventContext eventContext = (DocumentEventContext) event.getContext();
+
+ // Set a property if this listener is registered for the current tenant. This allows
+ // the Reindex listener to determine if it should run (since it is async, it cannot
+ // extend AbstractCSEventListenerImpl, so it cannot use the isRegistered method).
+
+ eventContext.setProperty(Reindex.IS_REGISTERED_KEY, true);
+
+ if (Framework.isBooleanPropertyTrue("elasticsearch.enabled")) {
+ DocumentModel doc = eventContext.getSourceDocument();
+ String docType = doc.getType();
+ String eventName = event.getName();
+
+ if (docType.startsWith("Media")) {
+ if (eventName.equals(DocumentEventTypes.BEFORE_DOC_UPDATE)) {
+ DocumentModel previousDoc = (DocumentModel) eventContext.getProperty(CoreEventConstants.PREVIOUS_DOCUMENT_MODEL);
+ String coverage = (String) previousDoc.getProperty("media_common", "coverage");
+ List<String> publishTo = (List<String>) previousDoc.getProperty("media_materials", "publishToList");
+
+ eventContext.setProperty(Reindex.PREV_COVERAGE_KEY, coverage);
+ eventContext.setProperty(Reindex.PREV_PUBLISH_TO_KEY, (Serializable) publishTo);
+ }
+ else if (eventName.equals(DocumentEventTypes.ABOUT_TO_REMOVE)) {
+ String coverage = (String) doc.getProperty("media_common", "coverage");
+
+ eventContext.setProperty(Reindex.PREV_COVERAGE_KEY, coverage);
+ }
+ }
+ }
+ }
+ }
+}
--- /dev/null
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 1
+Bundle-Name: org.collectionspace.services.listener.reindex
+Bundle-SymbolicName: org.collectionspace.services.listener.reindex;singleton:=true
+Bundle-Version: 1.0.0
+Bundle-Localization: plugin
+Bundle-Vendor: Nuxeo
+Require-Bundle: org.nuxeo.runtime,
+ org.nuxeo.ecm.core.api,
+ org.nuxeo.ecm.core,
+ org.nuxeo.ecm.webapp.core
+Provide-Package: org.collectionspace.services.listener.reindex
+Nuxeo-Component: OSGI-INF/event-contrib.xml
--- /dev/null
+<?xml version="1.0"?>
+<fragment>
+
+ <extension target="application#MODULE">
+ <module>
+ <java>${bundle.fileName}</java>
+ </module>
+ </extension>
+
+</fragment>
--- /dev/null
+<?xml version="1.0"?>
+<component name="org.collectionspace.services.listener.reindex">
+ <extension target="org.nuxeo.ecm.core.event.EventServiceComponent" point="listener">
+ <listener name="reindexlistener" async="true" postCommit="true"
+ class="org.collectionspace.services.listener.Reindex">
+ <event>documentCreated</event>
+ <event>documentModified</event>
+ <event>documentRemoved</event>
+ </listener>
+ </extension>
+ <extension target="org.nuxeo.ecm.core.event.EventServiceComponent" point="listener">
+ <listener name="reindexsupportlistener" async="false" postCommit="false"
+ class="org.collectionspace.services.listener.ReindexSupport">
+ <event>aboutToCreate</event>
+ <event>aboutToRemove</event>
+ <event>beforeDocumentModification</event>
+ </listener>
+ </extension>
+</component>