--- /dev/null
+package org.argeo.cms.internal.kernel;
+
+import java.util.GregorianCalendar;
+
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.Value;
+import javax.jcr.nodetype.NodeType;
+import javax.jcr.observation.Event;
+import javax.jcr.observation.EventIterator;
+import javax.jcr.observation.EventListener;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.jackrabbit.api.JackrabbitValue;
+import org.apache.jackrabbit.core.RepositoryImpl;
+import org.argeo.jcr.JcrUtils;
+
+/** Ensure consistency of files, folder and last modified nodes. */
+class CmsWorkspaceIndexer implements EventListener {
+ private final static Log log = LogFactory.getLog(CmsWorkspaceIndexer.class);
+
+ private final static String MIX_ETAG = "mix:etag";
+ private final static String JCR_ETAG = "jcr:etag";
+ private final static String JCR_LAST_MODIFIED = "jcr:lastModified";
+ private final static String JCR_LAST_MODIFIED_BY = "jcr:lastModifiedBy";
+ private final static String JCR_DATA = "jcr:data";
+ String cn;
+ String workspaceName;
+ RepositoryImpl repositoryImpl;
+ Session session;
+
+ public CmsWorkspaceIndexer(RepositoryImpl repositoryImpl, String cn, String workspaceName)
+ throws RepositoryException {
+ this.cn = cn;
+ this.workspaceName = workspaceName;
+ this.repositoryImpl = repositoryImpl;
+ }
+
+ public void init() {
+ session = KernelUtils.openAdminSession(repositoryImpl, workspaceName);
+ try {
+ String[] nodeTypes = { NodeType.NT_FILE, NodeType.MIX_LAST_MODIFIED };
+ session.getWorkspace().getObservationManager().addEventListener(this,
+ Event.NODE_ADDED | Event.NODE_REMOVED | Event.PROPERTY_CHANGED, "/", true, null, nodeTypes, true);
+ } catch (RepositoryException e1) {
+ throw new IllegalStateException(e1);
+ }
+ }
+
+ public void destroy() {
+ try {
+ session.getWorkspace().getObservationManager().removeEventListener(this);
+ } catch (RepositoryException e) {
+ log.warn("Cannot unregistered JCR event listener", e);
+ } finally {
+ JcrUtils.logoutQuietly(session);
+ }
+ }
+
+ private synchronized void processEvents(EventIterator events) {
+ while (events.hasNext()) {
+ Event event = events.nextEvent();
+ processEvent(event);
+ }
+ notifyAll();
+ }
+
+ protected synchronized void processEvent(Event event) {
+ try {
+ if (event.getType() == Event.NODE_ADDED) {
+ session.refresh(true);
+ Node node = session.getNode(event.getPath());
+ if (node.getParent().isNodeType(NodeType.NT_FILE)) {
+ if (node.isNodeType(NodeType.NT_UNSTRUCTURED)) {
+ if (!node.isNodeType(NodeType.MIX_LAST_MODIFIED))
+ node.addMixin(NodeType.MIX_LAST_MODIFIED);
+ Property property = node.getProperty(Property.JCR_DATA);
+ String etag = toEtag(property.getValue());
+ node.setProperty(JCR_ETAG, etag);
+ } else if (node.isNodeType(NodeType.NT_RESOURCE)) {
+ if (!node.isNodeType(MIX_ETAG))
+ node.addMixin(MIX_ETAG);
+ session.save();
+ Property property = node.getProperty(Property.JCR_DATA);
+ String etag = toEtag(property.getValue());
+ node.setProperty(JCR_ETAG, etag);
+ session.save();
+ }
+ setLastModified(node.getParent(), event);
+ session.save();
+ if (log.isDebugEnabled())
+ log.debug("ETag and last modified added to new " + node);
+ }
+ } else if (event.getType() == Event.PROPERTY_CHANGED) {
+ if (!session.propertyExists(event.getPath()))
+ return;
+ Property property = session.getProperty(event.getPath());
+ String propertyName = property.getName();
+ // skip if last modified properties are explicitly set
+ if (propertyName.equals(JCR_LAST_MODIFIED))
+ return;
+ if (propertyName.equals(JCR_LAST_MODIFIED_BY))
+ return;
+ Node node = property.getParent();
+ if (property.getType() == PropertyType.BINARY && propertyName.equals(JCR_DATA)
+ && node.isNodeType(NodeType.NT_UNSTRUCTURED)) {
+ String etag = toEtag(property.getValue());
+ node.setProperty(JCR_ETAG, etag);
+ }
+ setLastModified(node, event);
+ session.save();
+ if (log.isDebugEnabled())
+ log.debug("ETag and last modified updated for " + node);
+ } else if (event.getType() == Event.NODE_REMOVED) {
+ String removeNodePath = event.getPath();
+ String parentPath = JcrUtils.parentPath(removeNodePath);
+ session.refresh(true);
+ setLastModified(session, parentPath, event);
+ session.save();
+ if (log.isDebugEnabled())
+ log.debug("Last modified updated for parents of removed " + removeNodePath);
+ }
+ } catch (Exception e) {
+ log.warn("Cannot process event " + event, e);
+ } finally {
+ try {
+ if (session.hasPendingChanges())
+ session.save();
+// session.refresh(false);
+ } catch (RepositoryException e) {
+ log.warn("Cannot refresh JCR session", e);
+ }
+ }
+
+ }
+
+ @Override
+ public void onEvent(EventIterator events) {
+ Runnable toRun = new Runnable() {
+
+ @Override
+ public void run() {
+ processEvents(events);
+ }
+ };
+ Activator.getInternalExecutorService().execute(toRun);
+ }
+
+ static String toEtag(Value v) {
+ if (v instanceof JackrabbitValue) {
+ JackrabbitValue value = (JackrabbitValue) v;
+ return '\"' + value.getContentIdentity() + '\"';
+ } else {
+ return null;
+ }
+
+ }
+
+ /** Recursively set the last updated time on parents. */
+ static void setLastModified(Node node, Event event) throws RepositoryException {
+ GregorianCalendar calendar = new GregorianCalendar();
+ calendar.setTimeInMillis(event.getDate());
+ if (node.isNodeType(NodeType.MIX_LAST_MODIFIED)) {
+ node.setProperty(Property.JCR_LAST_MODIFIED, calendar);
+ node.setProperty(Property.JCR_LAST_MODIFIED_BY, event.getUserID());
+ }
+ if (node.isNodeType(NodeType.NT_FOLDER) && !node.isNodeType(NodeType.MIX_LAST_MODIFIED)) {
+ node.addMixin(NodeType.MIX_LAST_MODIFIED);
+ }
+
+ try {
+ node.getSession().save();
+ } catch (RepositoryException e) {
+ // fail silently and keep recursing
+ }
+ if (node.getDepth() == 0)
+ return;
+ Node parent = node.getParent();
+ setLastModified(parent, event);
+ }
+
+ /**
+ * Recursively set the last updated time on parents. Useful to use paths when
+ * dealing with deletions.
+ */
+ static void setLastModified(Session session, String path, Event event) throws RepositoryException {
+ // root node will always exist, so end condition is delegated to the other
+ // recursive setLastModified method
+ if (session.nodeExists(path)) {
+ setLastModified(session.getNode(path), event);
+ } else {
+ setLastModified(session, JcrUtils.parentPath(path), event);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "Monitor workspace " + workspaceName + " of repository " + cn;
+ }
+
+}
\ No newline at end of file