From: Mathieu Baudier Date: Tue, 11 Feb 2020 13:03:38 +0000 (+0100) Subject: Make workspace indexer and JCR file system more robust. X-Git-Tag: argeo-commons-2.1.86~29 X-Git-Url: https://git.argeo.org/?p=lgpl%2Fargeo-commons.git;a=commitdiff_plain;h=638e94cf1ad4bea9ce39232725d4e21775ce49b3 Make workspace indexer and JCR file system more robust. --- 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 index 1f534af0d..45c71afd1 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/CmsWorkspaceIndexer.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/kernel/CmsWorkspaceIndexer.java @@ -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); + } } /** diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/JackrabbitLocalRepository.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/JackrabbitLocalRepository.java index 572ab1fcc..db1075fe0 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/JackrabbitLocalRepository.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/kernel/JackrabbitLocalRepository.java @@ -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( diff --git a/org.argeo.jcr/src/org/argeo/jcr/fs/BinaryChannel.java b/org.argeo.jcr/src/org/argeo/jcr/fs/BinaryChannel.java index 658fe9330..d6550feee 100644 --- a/org.argeo.jcr/src/org/argeo/jcr/fs/BinaryChannel.java +++ b/org.argeo.jcr/src/org/argeo/jcr/fs/BinaryChannel.java @@ -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 { diff --git a/org.argeo.jcr/src/org/argeo/jcr/fs/JcrFileSystemProvider.java b/org.argeo.jcr/src/org/argeo/jcr/fs/JcrFileSystemProvider.java index 7dbd4e43f..bd0befe7c 100644 --- a/org.argeo.jcr/src/org/argeo/jcr/fs/JcrFileSystemProvider.java +++ b/org.argeo.jcr/src/org/argeo/jcr/fs/JcrFileSystemProvider.java @@ -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. diff --git a/org.argeo.jcr/src/org/argeo/jcr/fs/JcrPath.java b/org.argeo.jcr/src/org/argeo/jcr/fs/JcrPath.java index a2080c546..c5d86f679 100644 --- a/org.argeo.jcr/src/org/argeo/jcr/fs/JcrPath.java +++ b/org.argeo.jcr/src/org/argeo/jcr/fs/JcrPath.java @@ -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 = '/';