Make workspace indexer and JCR file system more robust.
authorMathieu Baudier <mbaudier@argeo.org>
Tue, 11 Feb 2020 13:03:38 +0000 (14:03 +0100)
committerMathieu Baudier <mbaudier@argeo.org>
Tue, 11 Feb 2020 13:03:38 +0000 (14:03 +0100)
org.argeo.cms/src/org/argeo/cms/internal/kernel/CmsWorkspaceIndexer.java
org.argeo.cms/src/org/argeo/cms/internal/kernel/JackrabbitLocalRepository.java
org.argeo.jcr/src/org/argeo/jcr/fs/BinaryChannel.java
org.argeo.jcr/src/org/argeo/jcr/fs/JcrFileSystemProvider.java
org.argeo.jcr/src/org/argeo/jcr/fs/JcrPath.java

index 1f534af0d92dab62d433b3352ccba8219ea65872..45c71afd1f3854e1abf214f153cc503506b0b4d4 100644 (file)
@@ -32,7 +32,9 @@ class CmsWorkspaceIndexer implements EventListener {
        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_MIXIN_TYPES = "jcr:mixinTypes";
        private final static String JCR_DATA = "jcr:data";
+       private final static String JCR_CONTENT = "jcr:data";
 
        private String cn;
        private String workspaceName;
@@ -71,21 +73,31 @@ class CmsWorkspaceIndexer implements EventListener {
        }
 
        private synchronized void processEvents(EventIterator events) {
+               long begin = System.currentTimeMillis();
+               long count = 0;
                while (events.hasNext()) {
                        Event event = events.nextEvent();
                        processEvent(event);
+                       count++;
                }
+               long duration = System.currentTimeMillis() - begin;
+               if (log.isTraceEnabled())
+                       log.trace("Processed " + count + " events in " + duration + " ms");
                notifyAll();
        }
 
        protected synchronized void processEvent(Event event) {
                try {
+                       String eventPath = event.getPath();
                        if (event.getType() == Event.NODE_ADDED) {
-                               if (!versionManager.isCheckedOut(event.getPath()))
+                               if (!versionManager.isCheckedOut(eventPath))
                                        return;// ignore checked-in nodes
-                               session.refresh(true);
-                               Node node = session.getNode(event.getPath());
-                               if (node.getParent().isNodeType(NodeType.NT_FILE)) {
+                               if (log.isTraceEnabled())
+                                       log.trace("NODE_ADDED " + eventPath);
+//                             session.refresh(true);
+                               Node node = session.getNode(eventPath);
+                               Node parentNode = node.getParent();
+                               if (parentNode.isNodeType(NodeType.NT_FILE)) {
                                        if (node.isNodeType(NodeType.NT_UNSTRUCTURED)) {
                                                if (!node.isNodeType(NodeType.MIX_LAST_MODIFIED))
                                                        node.addMixin(NodeType.MIX_LAST_MODIFIED);
@@ -101,22 +113,37 @@ class CmsWorkspaceIndexer implements EventListener {
 //                                             node.setProperty(JCR_ETAG, etag);
 //                                             session.save();
                                        }
-                                       setLastModified(node.getParent(), event);
+                                       setLastModified(parentNode, event);
                                        session.save();
                                        if (log.isTraceEnabled())
                                                log.trace("ETag and last modified added to new " + node);
                                }
+
+                               if (node.isNodeType(NodeType.NT_FOLDER)) {
+                                       setLastModified(node, event);
+                                       session.save();
+                                       if (log.isTraceEnabled())
+                                               log.trace("Last modified added to new " + node);
+                               }
                        } else if (event.getType() == Event.PROPERTY_CHANGED) {
-                               if (!session.propertyExists(event.getPath()))
-                                       return;
-                               session.refresh(true);
-                               Property property = session.getProperty(event.getPath());
-                               String propertyName = property.getName();
+                               String propertyName = extractItemName(eventPath);
                                // skip if last modified properties are explicitly set
                                if (propertyName.equals(JCR_LAST_MODIFIED))
                                        return;
                                if (propertyName.equals(JCR_LAST_MODIFIED_BY))
                                        return;
+                               if (propertyName.equals(JCR_MIXIN_TYPES))
+                                       return;
+                               if (propertyName.equals(JCR_ETAG))
+                                       return;
+
+                               if (log.isTraceEnabled())
+                                       log.trace("PROPERTY_CHANGED " + eventPath);
+
+                               if (!session.propertyExists(eventPath))
+                                       return;
+//                             session.refresh(true);
+                               Property property = session.getProperty(eventPath);
                                Node node = property.getParent();
                                if (property.getType() == PropertyType.BINARY && propertyName.equals(JCR_DATA)
                                                && node.isNodeType(NodeType.NT_UNSTRUCTURED)) {
@@ -128,11 +155,16 @@ class CmsWorkspaceIndexer implements EventListener {
                                if (log.isTraceEnabled())
                                        log.trace("ETag and last modified updated for " + node);
                        } else if (event.getType() == Event.NODE_REMOVED) {
-                               String removeNodePath = event.getPath();
+                               String removeNodePath = eventPath;
+                               String nodeName = extractItemName(eventPath);
+                               if (JCR_CONTENT.equals(nodeName)) // parent is a file, deleted anyhow
+                                       return;
+                               if (log.isTraceEnabled())
+                                       log.trace("NODE_REMOVED " + eventPath);
                                String parentPath = JcrUtils.parentPath(removeNodePath);
-                               session.refresh(true);
-                               setLastModified(parentPath, event);
-                               session.save();
+//                             session.refresh(true);
+//                             setLastModified(parentPath, event);
+//                             session.save();
                                if (log.isTraceEnabled())
                                        log.trace("Last modified updated for parents of removed " + removeNodePath);
                        }
@@ -153,6 +185,17 @@ class CmsWorkspaceIndexer implements EventListener {
 
        }
 
+       private String extractItemName(String path) {
+               if (path == null || path.length() <= 1)
+                       return null;
+               int lastIndex = path.lastIndexOf('/');
+               if (lastIndex >= 0) {
+                       return path.substring(lastIndex + 1);
+               } else {
+                       return path;
+               }
+       }
+
        @Override
        public void onEvent(EventIterator events) {
                Runnable toRun = new Runnable() {
@@ -184,26 +227,34 @@ class CmsWorkspaceIndexer implements EventListener {
        /** Recursively set the last updated time on parents. */
        protected synchronized void setLastModified(Node node, Event event) throws RepositoryException {
                if (versionManager.isCheckedOut(node.getPath())) {
-                       GregorianCalendar calendar = new GregorianCalendar();
-                       calendar.setTimeInMillis(event.getDate());
                        if (node.isNodeType(NodeType.MIX_LAST_MODIFIED)) {
+                               GregorianCalendar calendar = new GregorianCalendar();
+                               calendar.setTimeInMillis(event.getDate());
                                node.setProperty(Property.JCR_LAST_MODIFIED, calendar);
                                node.setProperty(Property.JCR_LAST_MODIFIED_BY, event.getUserID());
+                               if (log.isTraceEnabled())
+                                       log.trace("Last modified set on " + node);
                        }
                        if (node.isNodeType(NodeType.NT_FOLDER) && !node.isNodeType(NodeType.MIX_LAST_MODIFIED)) {
                                node.addMixin(NodeType.MIX_LAST_MODIFIED);
+                               if (log.isTraceEnabled())
+                                       log.trace("Last modified mix-in added to " + node);
                        }
 
-                       try {
-                               node.getSession().save();
-                       } catch (RepositoryException e) {
-                               // fail silently and keep recursing
-                       }
                }
-               if (node.getDepth() == 0)
+
+               // end condition
+               if (node.getDepth() == 0) {
+//                     try {
+//                             node.getSession().save();
+//                     } catch (RepositoryException e) {
+//                             log.warn("Cannot index workspace", e);
+//                     }
                        return;
-               Node parent = node.getParent();
-               setLastModified(parent, event);
+               } else {
+                       Node parent = node.getParent();
+                       setLastModified(parent, event);
+               }
        }
 
        /**
index 572ab1fcc2376aec1df201ec3ae8aaf0e999dc37..db1075fe0137a1153c0b61aee90e9434a1d8ef5e 100644 (file)
@@ -10,6 +10,7 @@ import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 import org.apache.jackrabbit.core.RepositoryImpl;
 import org.argeo.jcr.JcrUtils;
+import org.argeo.node.NodeConstants;
 
 class JackrabbitLocalRepository extends LocalRepository {
        private final static Log log = LogFactory.getLog(JackrabbitLocalRepository.class);
@@ -21,9 +22,10 @@ class JackrabbitLocalRepository extends LocalRepository {
                super(repository, cn);
                Session session = KernelUtils.openAdminSession(repository);
                try {
-                       for (String workspaceName : session.getWorkspace().getAccessibleWorkspaceNames()) {
-                               addMonitor(workspaceName);
-                       }
+                       if (NodeConstants.NODE.equals(cn))
+                               for (String workspaceName : session.getWorkspace().getAccessibleWorkspaceNames()) {
+                                       addMonitor(workspaceName);
+                               }
                } catch (RepositoryException e) {
                        throw new IllegalStateException(e);
                } finally {
@@ -44,6 +46,9 @@ class JackrabbitLocalRepository extends LocalRepository {
        private void addMonitor(String realWorkspaceName) {
                if (realWorkspaceName.equals(SECURITY_WORKSPACE))
                        return;
+               if (!NodeConstants.NODE.equals(getCn()))
+                       return;
+
                if (!workspaceMonitors.containsKey(realWorkspaceName)) {
                        try {
                                CmsWorkspaceIndexer workspaceMonitor = new CmsWorkspaceIndexer(
index 658fe93302981f309fc1e8483ea5bc6b8fe4cda5..d6550feeeb1b25509078c010bc817c0295630d9a 100644 (file)
@@ -21,6 +21,7 @@ import javax.jcr.nodetype.NodeType;
 
 import org.argeo.jcr.JcrUtils;
 
+/** A read/write {@link SeekableByteChannel} based on a {@link Binary}. */
 public class BinaryChannel implements SeekableByteChannel {
        private final Node file;
        private Binary binary;
@@ -32,27 +33,33 @@ public class BinaryChannel implements SeekableByteChannel {
 
        public BinaryChannel(Node file, Path path) throws RepositoryException, IOException {
                this.file = file;
-               if (file.isNodeType(NodeType.NT_FILE)) {
-                       if (file.hasNode(Property.JCR_CONTENT)) {
-                               Node data = file.getNode(Property.JCR_CONTENT);
-                               this.binary = data.getProperty(Property.JCR_DATA).getBinary();
-                       } else {
-                               Node data = file.addNode(Property.JCR_CONTENT, NodeType.NT_UNSTRUCTURED);
-                               try (InputStream in = new ByteArrayInputStream(new byte[0])) {
-                                       this.binary = data.getSession().getValueFactory().createBinary(in);
+               Session session = file.getSession();
+               synchronized (session) {
+                       if (file.isNodeType(NodeType.NT_FILE)) {
+                               if (file.hasNode(Node.JCR_CONTENT)) {
+                                       Node data = file.getNode(Property.JCR_CONTENT);
+                                       this.binary = data.getProperty(Property.JCR_DATA).getBinary();
+                               } else {
+                                       Node data = file.addNode(Node.JCR_CONTENT, NodeType.NT_UNSTRUCTURED);
+                                       data.addMixin(NodeType.MIX_LAST_MODIFIED);
+                                       try (InputStream in = new ByteArrayInputStream(new byte[0])) {
+                                               this.binary = data.getSession().getValueFactory().createBinary(in);
+                                       }
+                                       data.setProperty(Property.JCR_DATA, this.binary);
+
+                                       // MIME type
+                                       String mime = Files.probeContentType(path);
+                                       // String mime = fileTypeMap.getContentType(file.getName());
+                                       data.setProperty(Property.JCR_MIMETYPE, mime);
+
+                                       session.refresh(true);
+                                       session.save();
+                                       session.notifyAll();
                                }
-                               data.setProperty(Property.JCR_DATA, this.binary);
-
-                               // MIME type
-                               String mime = Files.probeContentType(path);
-                               // String mime = fileTypeMap.getContentType(file.getName());
-                               data.setProperty(Property.JCR_MIMETYPE, mime);
-
-                               data.getSession().save();
+                       } else {
+                               throw new IllegalArgumentException(
+                                               "Unsupported file node " + file + " (" + file.getPrimaryNodeType() + ")");
                        }
-               } else {
-                       throw new IllegalArgumentException(
-                                       "Unsupported file node " + file + " (" + file.getPrimaryNodeType() + ")");
                }
        }
 
@@ -67,12 +74,16 @@ public class BinaryChannel implements SeekableByteChannel {
                        Binary newBinary = null;
                        try {
                                Session session = file.getSession();
-                               fc.position(0);
-                               InputStream in = Channels.newInputStream(fc);
-                               newBinary = session.getValueFactory().createBinary(in);
-                               file.getNode(Property.JCR_CONTENT).setProperty(Property.JCR_DATA, newBinary);
-                               session.save();
-                               open = false;
+                               synchronized (session) {
+                                       fc.position(0);
+                                       InputStream in = Channels.newInputStream(fc);
+                                       newBinary = session.getValueFactory().createBinary(in);
+                                       file.getNode(Property.JCR_CONTENT).setProperty(Property.JCR_DATA, newBinary);
+                                       session.refresh(true);
+                                       session.save();
+                                       open = false;
+                                       session.notifyAll();
+                               }
                        } catch (RepositoryException e) {
                                throw new IOException("Cannot close " + file, e);
                        } finally {
index 7dbd4e43f88116e96a127b3b305fd1fae8dfe515..bd0befe7c6c3346bdbc946b9010e4d21c4f35f79 100644 (file)
@@ -32,6 +32,7 @@ import javax.jcr.nodetype.PropertyDefinition;
 
 import org.argeo.jcr.JcrUtils;
 
+/** Operations on a {@link JcrFileSystem}. */
 public abstract class JcrFileSystemProvider extends FileSystemProvider {
 
        @Override
@@ -51,7 +52,7 @@ public abstract class JcrFileSystemProvider extends FileSystemProvider {
                                fileName = Text.escapeIllegalJcrChars(fileName);
                                node = parent.addNode(fileName, NodeType.NT_FILE);
                                node.addMixin(NodeType.MIX_CREATED);
-                               node.addMixin(NodeType.MIX_LAST_MODIFIED);
+//                             node.addMixin(NodeType.MIX_LAST_MODIFIED);
                        }
                        if (!node.isNodeType(NodeType.NT_FILE))
                                throw new UnsupportedOperationException(node + " must be a file");
@@ -82,15 +83,18 @@ public abstract class JcrFileSystemProvider extends FileSystemProvider {
                                Node parent = toNode(dir.getParent());
                                if (parent == null)
                                        throw new IOException("Parent of " + dir + " does not exist");
-                               if (parent.getPrimaryNodeType().isNodeType(NodeType.NT_FILE)
-                                               || parent.getPrimaryNodeType().isNodeType(NodeType.NT_LINKED_FILE))
-                                       throw new IOException(dir + " parent is a file");
-                               String fileName = dir.getFileName().toString();
-                               fileName = Text.escapeIllegalJcrChars(fileName);
-                               node = parent.addNode(fileName, NodeType.NT_FOLDER);
-                               node.addMixin(NodeType.MIX_CREATED);
-                               node.addMixin(NodeType.MIX_LAST_MODIFIED);
-                               node.getSession().save();
+                               Session session = parent.getSession();
+                               synchronized (session) {
+                                       if (parent.getPrimaryNodeType().isNodeType(NodeType.NT_FILE)
+                                                       || parent.getPrimaryNodeType().isNodeType(NodeType.NT_LINKED_FILE))
+                                               throw new IOException(dir + " parent is a file");
+                                       String fileName = dir.getFileName().toString();
+                                       fileName = Text.escapeIllegalJcrChars(fileName);
+                                       node = parent.addNode(fileName, NodeType.NT_FOLDER);
+                                       node.addMixin(NodeType.MIX_CREATED);
+                                       node.addMixin(NodeType.MIX_LAST_MODIFIED);
+                                       save(session);
+                               }
                        } else {
                                // if (!node.getPrimaryNodeType().isNodeType(NodeType.NT_FOLDER))
                                // throw new FileExistsException(dir + " exists and is not a directory");
@@ -108,14 +112,17 @@ public abstract class JcrFileSystemProvider extends FileSystemProvider {
                        if (node == null)
                                throw new NoSuchFileException(path + " does not exist");
                        Session session = node.getSession();
-                       if (node.getPrimaryNodeType().isNodeType(NodeType.NT_FILE))
-                               node.remove();
-                       else if (node.getPrimaryNodeType().isNodeType(NodeType.NT_FOLDER)) {
-                               if (node.hasNodes())// TODO check only files
-                                       throw new DirectoryNotEmptyException(path.toString());
-                               node.remove();
+                       synchronized (session) {
+                               session.refresh(false);
+                               if (node.getPrimaryNodeType().isNodeType(NodeType.NT_FILE))
+                                       node.remove();
+                               else if (node.getPrimaryNodeType().isNodeType(NodeType.NT_FOLDER)) {
+                                       if (node.hasNodes())// TODO check only files
+                                               throw new DirectoryNotEmptyException(path.toString());
+                                       node.remove();
+                               }
+                               save(session);
                        }
-                       session.save();
                } catch (RepositoryException e) {
                        discardChanges(node);
                        throw new IOException("Cannot delete " + path, e);
@@ -128,8 +135,11 @@ public abstract class JcrFileSystemProvider extends FileSystemProvider {
                Node sourceNode = toNode(source);
                Node targetNode = toNode(target);
                try {
-                       JcrUtils.copy(sourceNode, targetNode);
-                       sourceNode.getSession().save();
+                       Session targetSession = targetNode.getSession();
+                       synchronized (targetSession) {
+                               JcrUtils.copy(sourceNode, targetNode);
+                               save(targetSession);
+                       }
                } catch (RepositoryException e) {
                        discardChanges(sourceNode);
                        discardChanges(targetNode);
@@ -142,8 +152,10 @@ public abstract class JcrFileSystemProvider extends FileSystemProvider {
                Node sourceNode = toNode(source);
                try {
                        Session session = sourceNode.getSession();
-                       session.move(sourceNode.getPath(), target.toString());
-                       session.save();
+                       synchronized (session) {
+                               session.move(sourceNode.getPath(), target.toString());
+                               save(session);
+                       }
                } catch (RepositoryException e) {
                        discardChanges(sourceNode);
                        throw new IOException("Cannot move from " + source + " to " + target, e);
@@ -253,14 +265,17 @@ public abstract class JcrFileSystemProvider extends FileSystemProvider {
        public void setAttribute(Path path, String attribute, Object value, LinkOption... options) throws IOException {
                Node node = toNode(path);
                try {
-                       if (value instanceof byte[]) {
-                               JcrUtils.setBinaryAsBytes(node, attribute, (byte[]) value);
-                       } else if (value instanceof Calendar) {
-                               node.setProperty(attribute, (Calendar) value);
-                       } else {
-                               node.setProperty(attribute, value.toString());
+                       Session session = node.getSession();
+                       synchronized (session) {
+                               if (value instanceof byte[]) {
+                                       JcrUtils.setBinaryAsBytes(node, attribute, (byte[]) value);
+                               } else if (value instanceof Calendar) {
+                                       node.setProperty(attribute, (Calendar) value);
+                               } else {
+                                       node.setProperty(attribute, value.toString());
+                               }
+                               save(session);
                        }
-                       node.getSession().save();
                } catch (RepositoryException e) {
                        discardChanges(node);
                        throw new IOException("Cannot set attribute " + attribute + " on " + path, e);
@@ -289,6 +304,13 @@ public abstract class JcrFileSystemProvider extends FileSystemProvider {
                }
        }
 
+       /** Make sure save is robust. */
+       protected void save(Session session) throws RepositoryException {
+               session.refresh(true);
+               session.save();
+               session.notifyAll();
+       }
+
        /**
         * To be overriden in order to support the ~ path, with an implementation
         * specific concept of user home.
index a2080c546a8eb48f178fc133168bc98dd939cc5c..c5d86f679970e012a54da1b5e7d21dc8f90541a4 100644 (file)
@@ -19,6 +19,7 @@ import javax.jcr.Node;
 import javax.jcr.RepositoryException;
 import javax.jcr.Session;
 
+/** A {@link Path} which contains a reference to a JCR {@link Node}. */
 public class JcrPath implements Path {
        private final static String delimStr = "/";
        private final static char delimChar = '/';