X-Git-Url: https://git.argeo.org/?a=blobdiff_plain;f=org.argeo.cms%2Fsrc%2Forg%2Fargeo%2Fcms%2Finternal%2Fkernel%2FCmsWorkspaceIndexer.java;fp=org.argeo.cms%2Fsrc%2Forg%2Fargeo%2Fcms%2Finternal%2Fkernel%2FCmsWorkspaceIndexer.java;h=20ced323091024ca139a5f5400777117b8ec7e47;hb=eb3116df3624b3d32793548b79e137e2dad429cb;hp=0000000000000000000000000000000000000000;hpb=9e5ea515aa9a855645c9d20c478c14770264a5cb;p=lgpl%2Fargeo-commons.git diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/CmsWorkspaceIndexer.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/CmsWorkspaceIndexer.java new file mode 100644 index 000000000..20ced3230 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/internal/kernel/CmsWorkspaceIndexer.java @@ -0,0 +1,205 @@ +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