Intorduce JCR sync, compatible with native image generation.
authorMathieu Baudier <mbaudier@argeo.org>
Fri, 8 May 2020 09:23:22 +0000 (11:23 +0200)
committerMathieu Baudier <mbaudier@argeo.org>
Fri, 8 May 2020 09:23:22 +0000 (11:23 +0200)
17 files changed:
dist/argeo-cli/pom.xml
org.argeo.cms/src/org/argeo/cms/cli/ArgeoCli.java
org.argeo.cms/src/org/argeo/cms/internal/http/CmsSessionProvider.java
org.argeo.core/bnd.bnd
org.argeo.core/build.properties
org.argeo.core/src/org/argeo/cli/CommandRuntimeException.java [new file with mode: 0644]
org.argeo.core/src/org/argeo/cli/jcr/JcrCommands.java [new file with mode: 0644]
org.argeo.core/src/org/argeo/cli/jcr/JcrSync.java [new file with mode: 0644]
org.argeo.core/src/org/argeo/cli/jcr/package-info.java [new file with mode: 0644]
org.argeo.core/src/org/argeo/cli/jcr/repository-localfs.xml [new file with mode: 0644]
org.argeo.jcr/src/org/argeo/jackrabbit/client/ClientDavexRepositoryFactory.java [new file with mode: 0644]
org.argeo.jcr/src/org/argeo/jackrabbit/client/ClientDavexRepositoryService.java [new file with mode: 0644]
org.argeo.jcr/src/org/argeo/jackrabbit/client/ClientDavexRepositoryServiceFactory.java [new file with mode: 0644]
org.argeo.jcr/src/org/argeo/jackrabbit/client/JackrabbitClient.java [new file with mode: 0644]
org.argeo.jcr/src/org/argeo/jackrabbit/client/NonSerialBasicAuthCache.java [new file with mode: 0644]
org.argeo.jcr/src/org/argeo/jackrabbit/fs/DavexFsProvider.java
org.argeo.jcr/src/org/argeo/jcr/JcrUtils.java

index b067131cfc3bea72e477d01018d0a13213c1953a..c455e0a17e2c6afa4a79be6fc10b8bb7cb1eae30 100644 (file)
@@ -1,4 +1,6 @@
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <parent>
                <groupId>org.argeo.commons</groupId>
        <artifactId>argeo-cli</artifactId>
        <packaging>pom</packaging>
        <name>Argeo Command Line</name>
+       <properties>
+               <graalvm.version>20.0.0</graalvm.version>
+       </properties>
+       <dependencies>
+               <dependency>
+                       <groupId>org.argeo.commons</groupId>
+                       <artifactId>org.argeo.dep.cms.client</artifactId>
+                       <version>2.1.89-SNAPSHOT</version>
+               </dependency>
+               <dependency>
+                       <groupId>org.argeo.commons</groupId>
+                       <artifactId>org.argeo.dep.cms.node</artifactId>
+                       <version>2.1.89-SNAPSHOT</version>
+               </dependency>
+       </dependencies>
        <profiles>
                <profile>
                        <id>dist</id>
-                       <dependencies>
-                               <dependency>
-                                       <groupId>org.argeo.commons</groupId>
-                                       <artifactId>org.argeo.dep.cms.client</artifactId>
-                                       <version>2.1.89-SNAPSHOT</version>
-                               </dependency>
-                               <dependency>
-                                       <groupId>org.argeo.commons</groupId>
-                                       <artifactId>org.argeo.dep.cms.node</artifactId>
-                                       <version>2.1.89-SNAPSHOT</version>
-                               </dependency>
-                       </dependencies>
                        <build>
                                <plugins>
                                        <plugin>
                                </plugins>
                        </build>
                </profile>
+               <profile>
+                       <id>native-image</id>
+                       <build>
+                               <plugins>
+                                       <plugin>
+                                               <groupId>org.graalvm.nativeimage</groupId>
+                                               <artifactId>native-image-maven-plugin</artifactId>
+                                               <version>${graalvm.version}</version>
+                                               <executions>
+                                                       <execution>
+                                                               <goals>
+                                                                       <goal>native-image</goal>
+                                                               </goals>
+                                                               <phase>package</phase>
+                                                       </execution>
+                                               </executions>
+                                               <configuration>
+                                                       <imageName>argeo</imageName>
+                                                       <mainClass>org.argeo.cms.cli.ArgeoCli</mainClass>
+                                                       <buildArgs>
+                                                               --no-fallback
+                                                               --allow-incomplete-classpath
+                                                               --initialize-at-build-time=org.apache.lucene.util.AttributeImpl,org.apache.lucene.util.VirtualMethod,org.apache.lucene.util.WeakIdentityMap
+                                                               -H:EnableURLProtocols=http,https
+                                                               -H:IncludeResourceBundles=sun.security.util.Resources
+                                                               -H:ConfigurationFileDirectories=${basedir}/native-image
+                                                               -H:ReflectionConfigurationFiles=${basedir}/native-image/reflect-config.json
+                                                               -H:ResourceConfigurationFiles=${basedir}/native-image/resource-config.json
+                                                               -H:JNIConfigurationFiles=${basedir}/native-image/jni-config.json
+                                                       </buildArgs>
+                                                       <skip>false</skip>
+                                               </configuration>
+                                       </plugin>
+                               </plugins>
+                       </build>
+                       <dependencies>
+                               <dependency>
+                                       <groupId>org.graalvm.sdk</groupId>
+                                       <artifactId>graal-sdk</artifactId>
+                                       <version>${graalvm.version}</version>
+                                       <scope>provided</scope>
+                               </dependency>
+                       </dependencies>
+               </profile>
        </profiles>
 </project>
index 5e589229d820f536146457f8d9123a9f62a01e10..fbac64dc983fc54c858ffdf3eec3da32197698f1 100644 (file)
@@ -3,6 +3,7 @@ package org.argeo.cms.cli;
 import org.apache.commons.cli.Option;
 import org.argeo.cli.CommandsCli;
 import org.argeo.cli.fs.FsCommands;
+import org.argeo.cli.jcr.JcrCommands;
 import org.argeo.cli.posix.PosixCommands;
 
 /** Argeo command line tools. */
@@ -17,6 +18,7 @@ public class ArgeoCli extends CommandsCli {
 
                addCommandsCli(new PosixCommands("posix"));
                addCommandsCli(new FsCommands("fs"));
+               addCommandsCli(new JcrCommands("jcr"));
        }
 
        @Override
index 14f311f3796903721987b8c197f8cd4d3f0ca690..524244d827f3bef6f153795b6b0e8ae667601beb 100644 (file)
@@ -1,6 +1,7 @@
 package org.argeo.cms.internal.http;
 
 import java.io.Serializable;
+import java.util.LinkedHashMap;
 
 import javax.jcr.Repository;
 import javax.jcr.RepositoryException;
@@ -25,7 +26,7 @@ public class CmsSessionProvider implements SessionProvider, Serializable {
 
        private final String alias;
 
-//     private LinkedHashMap<Session, CmsSessionImpl> cmsSessions = new LinkedHashMap<>();
+       private LinkedHashMap<Session, CmsSessionImpl> cmsSessions = new LinkedHashMap<>();
 
        public CmsSessionProvider(String alias) {
                this.alias = alias;
@@ -35,26 +36,26 @@ public class CmsSessionProvider implements SessionProvider, Serializable {
                        throws javax.jcr.LoginException, ServletException, RepositoryException {
 
                // a client is scanning parent URLs.
-               if (workspace == null)
-                       return null;
+//             if (workspace == null)
+//                     return null;
 
                CmsSessionImpl cmsSession = WebCmsSessionImpl.getCmsSession(request);
                if (log.isTraceEnabled()) {
                        log.trace("Get JCR session from " + cmsSession);
                }
                Session session = cmsSession.newDataSession(alias, workspace, rep);
-//             cmsSessions.put(session, cmsSession);
+               cmsSessions.put(session, cmsSession);
                return session;
        }
 
        public void releaseSession(Session session) {
-               JcrUtils.logoutQuietly(session);
-//             if (cmsSessions.containsKey(session)) {
-//                     CmsSessionImpl cmsSession = cmsSessions.get(session);
-//                     cmsSession.releaseDataSession(alias, session);
-//             } else {
-//                     log.warn("JCR session " + session + " not found in CMS session list. Logging it out...");
-//                     JcrUtils.logoutQuietly(session);
-//             }
+//             JcrUtils.logoutQuietly(session);
+               if (cmsSessions.containsKey(session)) {
+                       CmsSessionImpl cmsSession = cmsSessions.get(session);
+                       cmsSession.releaseDataSession(alias, session);
+               } else {
+                       log.warn("JCR session " + session + " not found in CMS session list. Logging it out...");
+                       JcrUtils.logoutQuietly(session);
+               }
        }
 }
index fc2cfe4b5e596dbbeaec04e4be97e1052259c0a1..6e0c5458ffebb7e97c2021601de99d13e6649cdc 100644 (file)
@@ -1,4 +1,6 @@
 Import-Package: com.fasterxml.jackson.annotation;resolution:=optional,\
  com.fasterxml.jackson.core;resolution:=optional,\
  com.fasterxml.jackson.databind;resolution:=optional,\
+ org.apache.jackrabbit.api;resolution:=optional,\
+ org.apache.jackrabbit.commons;resolution:=optional,\
  *;resolution:=optional
\ No newline at end of file
index 89b99406da2509cbe1a75f82fd6a3479d1700292..6562d830908494d9a506dd88943f5e0ff88e88ed 100644 (file)
@@ -3,6 +3,7 @@ output.. = bin/
 bin.includes = META-INF/,\
                .
 additional.bundles = org.apache.sshd.common,\
+                     org.apache.jackrabbit.data,\
                      org.slf4j.log4j12,\
                      org.slf4j.api,\
                      org.apache.sshd.core,\
diff --git a/org.argeo.core/src/org/argeo/cli/CommandRuntimeException.java b/org.argeo.core/src/org/argeo/cli/CommandRuntimeException.java
new file mode 100644 (file)
index 0000000..68b9a18
--- /dev/null
@@ -0,0 +1,35 @@
+package org.argeo.cli;
+
+import java.util.List;
+
+/** {@link RuntimeException} referring during a command run. */
+public class CommandRuntimeException extends RuntimeException {
+       private static final long serialVersionUID = 5595999301269377128L;
+
+       private final DescribedCommand<?> command;
+       private final List<String> arguments;
+
+       public CommandRuntimeException(Throwable e, DescribedCommand<?> command, List<String> arguments) {
+               this(null, e, command, arguments);
+       }
+
+       public CommandRuntimeException(String message, DescribedCommand<?> command, List<String> arguments) {
+               this(message, null, command, arguments);
+       }
+
+       public CommandRuntimeException(String message, Throwable e, DescribedCommand<?> command, List<String> arguments) {
+               super(message == null ? "(" + command.getClass().getName() + " " + arguments.toString() + ")"
+                               : message + " (" + command.getClass().getName() + " " + arguments.toString() + ")", e);
+               this.command = command;
+               this.arguments = arguments;
+       }
+
+       public DescribedCommand<?> getCommand() {
+               return command;
+       }
+
+       public List<String> getArguments() {
+               return arguments;
+       }
+
+}
diff --git a/org.argeo.core/src/org/argeo/cli/jcr/JcrCommands.java b/org.argeo.core/src/org/argeo/cli/jcr/JcrCommands.java
new file mode 100644 (file)
index 0000000..ea74674
--- /dev/null
@@ -0,0 +1,18 @@
+package org.argeo.cli.jcr;
+
+import org.argeo.cli.CommandsCli;
+
+/** File utilities. */
+public class JcrCommands extends CommandsCli {
+
+       public JcrCommands(String commandName) {
+               super(commandName);
+               addCommand("sync", new JcrSync());
+       }
+
+       @Override
+       public String getDescription() {
+               return "Utilities around remote and local JCR repositories";
+       }
+
+}
diff --git a/org.argeo.core/src/org/argeo/cli/jcr/JcrSync.java b/org.argeo.core/src/org/argeo/cli/jcr/JcrSync.java
new file mode 100644 (file)
index 0000000..a9ec6e2
--- /dev/null
@@ -0,0 +1,132 @@
+package org.argeo.cli.jcr;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.file.Paths;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.jcr.Credentials;
+import javax.jcr.Node;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.RepositoryFactory;
+import javax.jcr.Session;
+import javax.jcr.SimpleCredentials;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.Options;
+import org.apache.jackrabbit.core.RepositoryImpl;
+import org.apache.jackrabbit.core.config.RepositoryConfig;
+import org.argeo.cli.CommandArgsException;
+import org.argeo.cli.CommandRuntimeException;
+import org.argeo.cli.DescribedCommand;
+import org.argeo.jackrabbit.client.ClientDavexRepositoryFactory;
+import org.argeo.jcr.JcrUtils;
+import org.argeo.sync.SyncResult;
+
+public class JcrSync implements DescribedCommand<SyncResult<Node>> {
+       public final static String DEFAULT_LOCALFS_CONFIG = "repository-localfs.xml";
+
+       final static Option deleteOption = Option.builder().longOpt("delete").desc("delete from target").build();
+       final static Option recursiveOption = Option.builder("r").longOpt("recursive").desc("recurse into directories")
+                       .build();
+       final static Option progressOption = Option.builder().longOpt("progress").hasArg(false).desc("show progress")
+                       .build();
+
+       @Override
+       public SyncResult<Node> apply(List<String> t) {
+               try {
+                       CommandLine line = toCommandLine(t);
+                       List<String> remaining = line.getArgList();
+                       if (remaining.size() == 0) {
+                               throw new CommandArgsException("There must be at least one argument");
+                       }
+                       URI sourceUri = new URI(remaining.get(0));
+                       URI targetUri;
+                       if (remaining.size() == 1) {
+                               targetUri = Paths.get(System.getProperty("user.dir")).toUri();
+                       } else {
+                               targetUri = new URI(remaining.get(1));
+                       }
+                       boolean delete = line.hasOption(deleteOption.getLongOpt());
+                       boolean recursive = line.hasOption(recursiveOption.getLongOpt());
+
+                       // TODO make it configurable
+                       String sourceWorkspace = "home";
+                       String targetWorkspace = sourceWorkspace;
+
+                       final Repository sourceRepository;
+                       final Session sourceSession;
+                       Credentials sourceCredentials = null;
+                       final Repository targetRepository;
+                       final Session targetSession;
+                       Credentials targetCredentials = null;
+
+                       if ("http".equals(sourceUri.getScheme()) || "https".equals(sourceUri.getScheme())) {
+                               sourceRepository = createRemoteRepository(sourceUri);
+                       } else if (null == sourceUri.getScheme() || "file".equals(sourceUri.getScheme())) {
+                               RepositoryConfig repositoryConfig = RepositoryConfig.create(
+                                               JcrSync.class.getResourceAsStream(DEFAULT_LOCALFS_CONFIG), sourceUri.getPath().toString());
+                               sourceRepository = RepositoryImpl.create(repositoryConfig);
+                               sourceCredentials = new SimpleCredentials("admin", "admin".toCharArray());
+                       } else {
+                               throw new IllegalArgumentException("Unsupported scheme " + sourceUri.getScheme());
+                       }
+                       sourceSession = JcrUtils.loginOrCreateWorkspace(sourceRepository, sourceWorkspace, sourceCredentials);
+
+                       if ("http".equals(targetUri.getScheme()) || "https".equals(targetUri.getScheme())) {
+                               targetRepository = createRemoteRepository(targetUri);
+                       } else if (null == targetUri.getScheme() || "file".equals(targetUri.getScheme())) {
+                               RepositoryConfig repositoryConfig = RepositoryConfig.create(
+                                               JcrSync.class.getResourceAsStream(DEFAULT_LOCALFS_CONFIG), targetUri.getPath().toString());
+                               targetRepository = RepositoryImpl.create(repositoryConfig);
+                               targetCredentials = new SimpleCredentials("admin", "admin".toCharArray());
+                       } else {
+                               throw new IllegalArgumentException("Unsupported scheme " + targetUri.getScheme());
+                       }
+                       targetSession = JcrUtils.loginOrCreateWorkspace(targetRepository, targetWorkspace, targetCredentials);
+
+                       JcrUtils.copy(sourceSession.getRootNode(), targetSession.getRootNode());
+                       return new SyncResult<Node>();
+               } catch (URISyntaxException e) {
+                       throw new CommandArgsException(e);
+               } catch (Exception e) {
+                       throw new CommandRuntimeException(e, this, t);
+               }
+       }
+
+       protected Repository createRemoteRepository(URI uri) throws RepositoryException {
+               RepositoryFactory repositoryFactory = new ClientDavexRepositoryFactory();
+               Map<String, String> params = new HashMap<String, String>();
+               params.put(ClientDavexRepositoryFactory.JACKRABBIT_DAVEX_URI, uri.toString());
+               params.put(ClientDavexRepositoryFactory.JACKRABBIT_REMOTE_DEFAULT_WORKSPACE, "main");
+               return repositoryFactory.getRepository(params);
+       }
+
+       @Override
+       public Options getOptions() {
+               Options options = new Options();
+               options.addOption(recursiveOption);
+               options.addOption(deleteOption);
+               options.addOption(progressOption);
+               return options;
+       }
+
+       @Override
+       public String getUsage() {
+               return "[source URI] [target URI]";
+       }
+
+       public static void main(String[] args) {
+               DescribedCommand.mainImpl(new JcrSync(), args);
+       }
+
+       @Override
+       public String getDescription() {
+               return "Synchronises JCR repositories";
+       }
+
+}
diff --git a/org.argeo.core/src/org/argeo/cli/jcr/package-info.java b/org.argeo.core/src/org/argeo/cli/jcr/package-info.java
new file mode 100644 (file)
index 0000000..6f3f01f
--- /dev/null
@@ -0,0 +1,2 @@
+/** JCR CLI commands. */
+package org.argeo.cli.jcr;
\ No newline at end of file
diff --git a/org.argeo.core/src/org/argeo/cli/jcr/repository-localfs.xml b/org.argeo.core/src/org/argeo/cli/jcr/repository-localfs.xml
new file mode 100644 (file)
index 0000000..5e7759c
--- /dev/null
@@ -0,0 +1,76 @@
+<?xml version="1.0"?>
+<!--
+
+    Copyright (C) 2007-2012 Argeo GmbH
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+            http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+-->
+<!DOCTYPE Repository PUBLIC "-//The Apache Software Foundation//DTD Jackrabbit 1.6//EN"
+                            "http://jackrabbit.apache.org/dtd/repository-2.0.dtd">
+<Repository>
+       <!-- File system and datastore -->
+       <FileSystem class="org.apache.jackrabbit.core.fs.local.LocalFileSystem">
+               <param name="path" value="${rep.home}/repository" />
+       </FileSystem>
+       <DataStore class="org.apache.jackrabbit.core.data.FileDataStore">
+               <param name="path" value="${rep.home}/datastore" />
+       </DataStore>
+
+       <!-- Workspace templates -->
+       <Workspaces rootPath="${rep.home}/workspaces"
+               defaultWorkspace="main" configRootPath="/workspaces" />
+       <Workspace name="${wsp.name}">
+               <FileSystem class="org.apache.jackrabbit.core.fs.local.LocalFileSystem">
+                       <param name="path" value="${wsp.home}" />
+               </FileSystem>
+               <PersistenceManager
+                       class="org.apache.jackrabbit.core.persistence.bundle.BundleFsPersistenceManager">
+                       <param name="blobFSBlockSize" value="1" />
+               </PersistenceManager>
+               <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
+                       <param name="path" value="${rep.home}/repository/index" />
+               </SearchIndex>
+       </Workspace>
+
+       <!-- Versioning -->
+       <Versioning rootPath="${rep.home}/version">
+               <FileSystem class="org.apache.jackrabbit.core.fs.local.LocalFileSystem">
+                       <param name="path" value="${rep.home}/version" />
+               </FileSystem>
+               <PersistenceManager
+                       class="org.apache.jackrabbit.core.persistence.bundle.BundleFsPersistenceManager">
+                       <param name="blobFSBlockSize" value="1" />
+               </PersistenceManager>
+       </Versioning>
+
+       <!-- Indexing -->
+       <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
+               <param name="path" value="${rep.home}/repository/index" />
+               <param name="tikaConfigPath" value="tika-config.xml"/>
+       </SearchIndex>
+
+       <!-- Security -->
+       <Security appName="Jackrabbit">
+               <SecurityManager
+                       class="org.apache.jackrabbit.core.security.simple.SimpleSecurityManager"
+                       workspaceName="security" />
+               <AccessManager
+                       class="org.apache.jackrabbit.core.security.simple.SimpleAccessManager" />
+               <LoginModule
+                       class="org.apache.jackrabbit.core.security.simple.SimpleLoginModule">
+                       <param name="anonymousId" value="anonymous" />
+                       <param name="adminId" value="admin" />
+               </LoginModule>
+       </Security>
+</Repository>
\ No newline at end of file
diff --git a/org.argeo.jcr/src/org/argeo/jackrabbit/client/ClientDavexRepositoryFactory.java b/org.argeo.jcr/src/org/argeo/jackrabbit/client/ClientDavexRepositoryFactory.java
new file mode 100644 (file)
index 0000000..77ad527
--- /dev/null
@@ -0,0 +1,26 @@
+package org.argeo.jackrabbit.client;
+
+import java.util.Map;
+
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.RepositoryFactory;
+
+import org.apache.jackrabbit.jcr2spi.Jcr2spiRepositoryFactory;
+import org.apache.jackrabbit.jcr2spi.RepositoryImpl;
+import org.apache.jackrabbit.spi.RepositoryServiceFactory;
+
+/** A customised {@link RepositoryFactory} access a remote DAVEX service. */
+public class ClientDavexRepositoryFactory implements RepositoryFactory {
+       public final static String JACKRABBIT_DAVEX_URI = ClientDavexRepositoryServiceFactory.PARAM_REPOSITORY_URI;
+       public final static String JACKRABBIT_REMOTE_DEFAULT_WORKSPACE = ClientDavexRepositoryServiceFactory.PARAM_WORKSPACE_NAME_DEFAULT;
+
+       @SuppressWarnings("rawtypes")
+       @Override
+       public Repository getRepository(Map parameters) throws RepositoryException {
+               RepositoryServiceFactory repositoryServiceFactory = new ClientDavexRepositoryServiceFactory();
+               return RepositoryImpl
+                               .create(new Jcr2spiRepositoryFactory.RepositoryConfigImpl(repositoryServiceFactory, parameters));
+       }
+
+}
diff --git a/org.argeo.jcr/src/org/argeo/jackrabbit/client/ClientDavexRepositoryService.java b/org.argeo.jcr/src/org/argeo/jackrabbit/client/ClientDavexRepositoryService.java
new file mode 100644 (file)
index 0000000..0f9db87
--- /dev/null
@@ -0,0 +1,40 @@
+package org.argeo.jackrabbit.client;
+
+import javax.jcr.RepositoryException;
+
+import org.apache.http.client.protocol.HttpClientContext;
+import org.apache.http.protocol.HttpContext;
+import org.apache.jackrabbit.spi.SessionInfo;
+import org.apache.jackrabbit.spi2davex.BatchReadConfig;
+import org.apache.jackrabbit.spi2davex.RepositoryServiceImpl;
+
+/**
+ * Wrapper for {@link RepositoryServiceImpl} in order to access the underlying
+ * {@link HttpClientContext}.
+ */
+public class ClientDavexRepositoryService extends RepositoryServiceImpl {
+
+       public ClientDavexRepositoryService(String jcrServerURI, BatchReadConfig batchReadConfig)
+                       throws RepositoryException {
+               super(jcrServerURI, batchReadConfig);
+       }
+
+       public ClientDavexRepositoryService(String jcrServerURI, String defaultWorkspaceName,
+                       BatchReadConfig batchReadConfig, int itemInfoCacheSize, int maximumHttpConnections)
+                       throws RepositoryException {
+               super(jcrServerURI, defaultWorkspaceName, batchReadConfig, itemInfoCacheSize, maximumHttpConnections);
+       }
+
+       public ClientDavexRepositoryService(String jcrServerURI, String defaultWorkspaceName,
+                       BatchReadConfig batchReadConfig, int itemInfoCacheSize) throws RepositoryException {
+               super(jcrServerURI, defaultWorkspaceName, batchReadConfig, itemInfoCacheSize);
+       }
+
+       @Override
+       protected HttpContext getContext(SessionInfo sessionInfo) throws RepositoryException {
+               HttpClientContext result = HttpClientContext.create();
+               result.setAuthCache(new NonSerialBasicAuthCache());
+               return result;
+       }
+
+}
diff --git a/org.argeo.jcr/src/org/argeo/jackrabbit/client/ClientDavexRepositoryServiceFactory.java b/org.argeo.jcr/src/org/argeo/jackrabbit/client/ClientDavexRepositoryServiceFactory.java
new file mode 100644 (file)
index 0000000..4b240f0
--- /dev/null
@@ -0,0 +1,82 @@
+package org.argeo.jackrabbit.client;
+
+import java.util.Map;
+
+import javax.jcr.RepositoryException;
+
+import org.apache.jackrabbit.spi.RepositoryService;
+import org.apache.jackrabbit.spi.commons.ItemInfoCacheImpl;
+import org.apache.jackrabbit.spi2davex.BatchReadConfig;
+import org.apache.jackrabbit.spi2davex.Spi2davexRepositoryServiceFactory;
+
+/**
+ * Wrapper for {@link Spi2davexRepositoryServiceFactory} in order to create a
+ * {@link ClientDavexRepositoryService}.
+ */
+public class ClientDavexRepositoryServiceFactory extends Spi2davexRepositoryServiceFactory {
+       @Override
+       public RepositoryService createRepositoryService(Map<?, ?> parameters) throws RepositoryException {
+               // retrieve the repository uri
+               String uri;
+               if (parameters == null) {
+                       uri = System.getProperty(PARAM_REPOSITORY_URI);
+               } else {
+                       Object repoUri = parameters.get(PARAM_REPOSITORY_URI);
+                       uri = (repoUri == null) ? null : repoUri.toString();
+               }
+               if (uri == null) {
+                       uri = DEFAULT_REPOSITORY_URI;
+               }
+
+               // load other optional configuration parameters
+               BatchReadConfig brc = null;
+               int itemInfoCacheSize = ItemInfoCacheImpl.DEFAULT_CACHE_SIZE;
+               int maximumHttpConnections = 0;
+
+               // since JCR-4120 the default workspace name is no longer set to 'default'
+               // note: if running with JCR Server < 1.5 a default workspace name must
+               // therefore be configured
+               String workspaceNameDefault = null;
+
+               if (parameters != null) {
+                       // batchRead config
+                       Object param = parameters.get(PARAM_BATCHREAD_CONFIG);
+                       if (param != null && param instanceof BatchReadConfig) {
+                               brc = (BatchReadConfig) param;
+                       }
+
+                       // itemCache size config
+                       param = parameters.get(PARAM_ITEMINFO_CACHE_SIZE);
+                       if (param != null) {
+                               try {
+                                       itemInfoCacheSize = Integer.parseInt(param.toString());
+                               } catch (NumberFormatException e) {
+                                       // ignore, use default
+                               }
+                       }
+
+                       // max connections config
+                       param = parameters.get(PARAM_MAX_CONNECTIONS);
+                       if (param != null) {
+                               try {
+                                       maximumHttpConnections = Integer.parseInt(param.toString());
+                               } catch (NumberFormatException e) {
+                                       // using default
+                               }
+                       }
+
+                       param = parameters.get(PARAM_WORKSPACE_NAME_DEFAULT);
+                       if (param != null) {
+                               workspaceNameDefault = param.toString();
+                       }
+               }
+
+               if (maximumHttpConnections > 0) {
+                       return new ClientDavexRepositoryService(uri, workspaceNameDefault, brc, itemInfoCacheSize,
+                                       maximumHttpConnections);
+               } else {
+                       return new ClientDavexRepositoryService(uri, workspaceNameDefault, brc, itemInfoCacheSize);
+               }
+       }
+
+}
diff --git a/org.argeo.jcr/src/org/argeo/jackrabbit/client/JackrabbitClient.java b/org.argeo.jcr/src/org/argeo/jackrabbit/client/JackrabbitClient.java
new file mode 100644 (file)
index 0000000..32b8c06
--- /dev/null
@@ -0,0 +1,124 @@
+package org.argeo.jackrabbit.client;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.RepositoryFactory;
+import javax.jcr.Session;
+
+import org.apache.http.client.protocol.HttpClientContext;
+import org.apache.http.protocol.HttpContext;
+import org.apache.jackrabbit.jcr2dav.Jcr2davRepositoryFactory;
+import org.apache.jackrabbit.jcr2spi.Jcr2spiRepositoryFactory;
+import org.apache.jackrabbit.jcr2spi.RepositoryImpl;
+import org.apache.jackrabbit.spi.RepositoryService;
+import org.apache.jackrabbit.spi.RepositoryServiceFactory;
+import org.apache.jackrabbit.spi.SessionInfo;
+import org.apache.jackrabbit.spi.commons.ItemInfoCacheImpl;
+import org.apache.jackrabbit.spi2davex.BatchReadConfig;
+import org.apache.jackrabbit.spi2davex.RepositoryServiceImpl;
+import org.apache.jackrabbit.spi2davex.Spi2davexRepositoryServiceFactory;
+import org.argeo.jcr.JcrUtils;
+
+/** Minimal client to test JCR DAVEX connectivity. */
+public class JackrabbitClient {
+       final static String JACKRABBIT_REPOSITORY_URI = "org.apache.jackrabbit.repository.uri";
+       final static String JACKRABBIT_DAVEX_URI = "org.apache.jackrabbit.spi2davex.uri";
+       final static String JACKRABBIT_REMOTE_DEFAULT_WORKSPACE = "org.apache.jackrabbit.spi2davex.WorkspaceNameDefault";
+
+       public static void main(String[] args) {
+               String repoUri = args.length == 0 ? "http://root:demo@localhost:7070/jcr/ego" : args[0];
+               String workspace = args.length < 2 ? "home" : args[1];
+
+               Repository repository = null;
+               Session session = null;
+
+               URI uri;
+               try {
+                       uri = new URI(repoUri);
+               } catch (URISyntaxException e1) {
+                       throw new IllegalArgumentException(e1);
+               }
+
+               if (uri.getScheme().equals("http") || uri.getScheme().equals("https")) {
+
+                       RepositoryFactory repositoryFactory = new Jcr2davRepositoryFactory() {
+                               @SuppressWarnings("rawtypes")
+                               public Repository getRepository(Map parameters) throws RepositoryException {
+                                       RepositoryServiceFactory repositoryServiceFactory = new Spi2davexRepositoryServiceFactory() {
+
+                                               @Override
+                                               public RepositoryService createRepositoryService(Map<?, ?> parameters)
+                                                               throws RepositoryException {
+                                                       Object uri = parameters.get(JACKRABBIT_DAVEX_URI);
+                                                       Object defaultWorkspace = parameters.get(JACKRABBIT_REMOTE_DEFAULT_WORKSPACE);
+                                                       BatchReadConfig brc = null;
+                                                       return new RepositoryServiceImpl(uri.toString(), defaultWorkspace.toString(), brc,
+                                                                       ItemInfoCacheImpl.DEFAULT_CACHE_SIZE) {
+
+                                                               @Override
+                                                               protected HttpContext getContext(SessionInfo sessionInfo) throws RepositoryException {
+                                                                       HttpClientContext result = HttpClientContext.create();
+                                                                       result.setAuthCache(new NonSerialBasicAuthCache());
+                                                                       return result;
+                                                               }
+
+                                                       };
+                                               }
+                                       };
+                                       return RepositoryImpl.create(
+                                                       new Jcr2spiRepositoryFactory.RepositoryConfigImpl(repositoryServiceFactory, parameters));
+                               }
+                       };
+                       Map<String, String> params = new HashMap<String, String>();
+                       params.put(JACKRABBIT_DAVEX_URI, repoUri.toString());
+                       params.put(JACKRABBIT_REMOTE_DEFAULT_WORKSPACE, "main");
+
+                       try {
+                               repository = repositoryFactory.getRepository(params);
+                               if (repository != null)
+                                       session = repository.login(workspace);
+                               else
+                                       throw new IllegalArgumentException("Repository " + repoUri + " not found");
+                       } catch (RepositoryException e) {
+                               e.printStackTrace();
+                       }
+
+               } else {
+                       Path path = Paths.get(uri.getPath());
+               }
+
+               try {
+                       Node rootNode = session.getRootNode();
+                       NodeIterator nit = rootNode.getNodes();
+                       while (nit.hasNext()) {
+                               System.out.println(nit.nextNode().getPath());
+                       }
+
+                       Node newNode = JcrUtils.mkdirs(rootNode, "dir/subdir");
+                       System.out.println("Created folder " + newNode.getPath());
+                       Node newFile = JcrUtils.copyBytesAsFile(newNode, "test.txt", "TEST".getBytes());
+                       System.out.println("Created file " + newFile.getPath());
+                       try (BufferedReader reader = new BufferedReader(new InputStreamReader(JcrUtils.getFileAsStream(newFile)))) {
+                               System.out.println("Read " + reader.readLine());
+                       } catch (IOException e) {
+                               e.printStackTrace();
+                       }
+                       newNode.getParent().remove();
+                       System.out.println("Removed new nodes");
+               } catch (RepositoryException e) {
+                       e.printStackTrace();
+               }
+       }
+}
diff --git a/org.argeo.jcr/src/org/argeo/jackrabbit/client/NonSerialBasicAuthCache.java b/org.argeo.jcr/src/org/argeo/jackrabbit/client/NonSerialBasicAuthCache.java
new file mode 100644 (file)
index 0000000..3fb0db9
--- /dev/null
@@ -0,0 +1,41 @@
+package org.argeo.jackrabbit.client;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.http.HttpHost;
+import org.apache.http.auth.AuthScheme;
+import org.apache.http.client.AuthCache;
+
+/**
+ * Implementation of {@link AuthCache} which doesn't use serialization, as it is
+ * not supported by GraalVM at this stage.
+ */
+public class NonSerialBasicAuthCache implements AuthCache {
+       private final Map<HttpHost, AuthScheme> cache;
+
+       public NonSerialBasicAuthCache() {
+               cache = new ConcurrentHashMap<HttpHost, AuthScheme>();
+       }
+
+       @Override
+       public void put(HttpHost host, AuthScheme authScheme) {
+               cache.put(host, authScheme);
+       }
+
+       @Override
+       public AuthScheme get(HttpHost host) {
+               return cache.get(host);
+       }
+
+       @Override
+       public void remove(HttpHost host) {
+               cache.remove(host);
+       }
+
+       @Override
+       public void clear() {
+               cache.clear();
+       }
+
+}
index 30cabbe65fa770df778215535a1b5a57de940765..331e9acdd948062ebb6644b4b70e26424d1f1be5 100644 (file)
@@ -15,14 +15,19 @@ import javax.jcr.Repository;
 import javax.jcr.RepositoryFactory;
 import javax.jcr.Session;
 
-import org.apache.jackrabbit.jcr2dav.Jcr2davRepositoryFactory;
+import org.apache.jackrabbit.core.security.authentication.LocalAuthContext;
+import org.argeo.jackrabbit.client.ClientDavexRepositoryFactory;
 import org.argeo.jcr.ArgeoJcrException;
 import org.argeo.jcr.fs.JcrFileSystem;
 import org.argeo.jcr.fs.JcrFsException;
 
+/**
+ * A file system provider based on a JCR repository remotely accessed via the
+ * DAVEX protocol.
+ */
 public class DavexFsProvider extends AbstractJackrabbitFsProvider {
-       final static String JACKRABBIT_REPOSITORY_URI = "org.apache.jackrabbit.repository.uri";
-       final static String JACKRABBIT_REMOTE_DEFAULT_WORKSPACE = "org.apache.jackrabbit.spi2davex.WorkspaceNameDefault";
+//     final static String JACKRABBIT_REPOSITORY_URI = "org.apache.jackrabbit.repository.uri";
+//     final static String JACKRABBIT_REMOTE_DEFAULT_WORKSPACE = "org.apache.jackrabbit.spi2davex.WorkspaceNameDefault";
 
        private Map<String, JcrFileSystem> fileSystems = new HashMap<>();
 
@@ -40,8 +45,8 @@ public class DavexFsProvider extends AbstractJackrabbitFsProvider {
                        String repoKey = repoUri.toString();
                        if (fileSystems.containsKey(repoKey))
                                throw new FileSystemAlreadyExistsException("CMS file system already exists for " + repoKey);
-                       RepositoryFactory repositoryFactory = new Jcr2davRepositoryFactory();
-                       return tryGetRepo(repositoryFactory, repoUri, "main");
+                       RepositoryFactory repositoryFactory = new ClientDavexRepositoryFactory();
+                       return tryGetRepo(repositoryFactory, repoUri, "home");
                } catch (Exception e) {
                        throw new ArgeoJcrException("Cannot open file system " + uri, e);
                }
@@ -50,8 +55,8 @@ public class DavexFsProvider extends AbstractJackrabbitFsProvider {
        private JcrFileSystem tryGetRepo(RepositoryFactory repositoryFactory, URI repoUri, String workspace)
                        throws IOException {
                Map<String, String> params = new HashMap<String, String>();
-               params.put(JACKRABBIT_REPOSITORY_URI, repoUri.toString());
-               params.put(JACKRABBIT_REMOTE_DEFAULT_WORKSPACE, "main");
+               params.put(ClientDavexRepositoryFactory.JACKRABBIT_DAVEX_URI, repoUri.toString());
+               params.put(ClientDavexRepositoryFactory.JACKRABBIT_REMOTE_DEFAULT_WORKSPACE, "main");
                Repository repository = null;
                Session session = null;
                try {
@@ -102,8 +107,7 @@ public class DavexFsProvider extends AbstractJackrabbitFsProvider {
                try {
                        repoUri = new URI("http", uri.getUserInfo(), uri.getHost(), uri.getPort(), uri.getPath(), null, null);
                } catch (URISyntaxException e) {
-                       // TODO Auto-generated catch block
-                       e.printStackTrace();
+                       throw new IllegalArgumentException(e);
                }
                String uriStr = repoUri.toString();
                String localPath = null;
@@ -112,6 +116,8 @@ public class DavexFsProvider extends AbstractJackrabbitFsProvider {
                                localPath = uriStr.toString().substring(key.length());
                        }
                }
+               if ("".equals(localPath))
+                       localPath = "/";
                return fileSystem.getPath(localPath);
        }
 
@@ -126,7 +132,7 @@ public class DavexFsProvider extends AbstractJackrabbitFsProvider {
        public static void main(String args[]) {
                try {
                        DavexFsProvider fsProvider = new DavexFsProvider();
-                       Path path = fsProvider.getPath(new URI("davex://root:demo@localhost:7070/jcr/node/main/home/"));
+                       Path path = fsProvider.getPath(new URI("davex://root:demo@localhost:7070/jcr/ego/"));
                        System.out.println(path);
                        DirectoryStream<Path> ds = Files.newDirectoryStream(path);
                        for (Path p : ds) {
index 416d035f8b5925555e421c46ede06e21e85dd231..94b1dfdfae31488762ab6ede2cfcb3580eeb3458 100644 (file)
@@ -38,6 +38,7 @@ import java.util.Map;
 import java.util.TreeMap;
 
 import javax.jcr.Binary;
+import javax.jcr.Credentials;
 import javax.jcr.NamespaceRegistry;
 import javax.jcr.NoSuchWorkspaceException;
 import javax.jcr.Node;
@@ -1067,16 +1068,25 @@ public class JcrUtils {
         */
        public static Session loginOrCreateWorkspace(Repository repository, String workspaceName)
                        throws RepositoryException {
+               return loginOrCreateWorkspace(repository, workspaceName, null);
+       }
+
+       /**
+        * Login to a workspace with implicit credentials, creates the workspace with
+        * these credentials if it does not already exist.
+        */
+       public static Session loginOrCreateWorkspace(Repository repository, String workspaceName, Credentials credentials)
+                       throws RepositoryException {
                Session workspaceSession = null;
                Session defaultSession = null;
                try {
                        try {
-                               workspaceSession = repository.login(workspaceName);
+                               workspaceSession = repository.login(credentials, workspaceName);
                        } catch (NoSuchWorkspaceException e) {
                                // try to create workspace
-                               defaultSession = repository.login();
+                               defaultSession = repository.login(credentials);
                                defaultSession.getWorkspace().createWorkspace(workspaceName);
-                               workspaceSession = repository.login(workspaceName);
+                               workspaceSession = repository.login(credentials, workspaceName);
                        }
                        return workspaceSession;
                } finally {