From 905c6bcd909d84c6e980994d9eeed22a0bf237af Mon Sep 17 00:00:00 2001 From: Mathieu Baudier Date: Fri, 8 May 2020 11:23:22 +0200 Subject: [PATCH] Intorduce JCR sync, compatible with native image generation. --- dist/argeo-cli/pom.xml | 75 ++++++++-- .../src/org/argeo/cms/cli/ArgeoCli.java | 2 + .../cms/internal/http/CmsSessionProvider.java | 25 ++-- org.argeo.core/bnd.bnd | 2 + org.argeo.core/build.properties | 1 + .../argeo/cli/CommandRuntimeException.java | 35 +++++ .../src/org/argeo/cli/jcr/JcrCommands.java | 18 +++ .../src/org/argeo/cli/jcr/JcrSync.java | 132 ++++++++++++++++++ .../src/org/argeo/cli/jcr/package-info.java | 2 + .../org/argeo/cli/jcr/repository-localfs.xml | 76 ++++++++++ .../client/ClientDavexRepositoryFactory.java | 26 ++++ .../client/ClientDavexRepositoryService.java | 40 ++++++ .../ClientDavexRepositoryServiceFactory.java | 82 +++++++++++ .../jackrabbit/client/JackrabbitClient.java | 124 ++++++++++++++++ .../client/NonSerialBasicAuthCache.java | 41 ++++++ .../argeo/jackrabbit/fs/DavexFsProvider.java | 26 ++-- org.argeo.jcr/src/org/argeo/jcr/JcrUtils.java | 16 ++- 17 files changed, 685 insertions(+), 38 deletions(-) create mode 100644 org.argeo.core/src/org/argeo/cli/CommandRuntimeException.java create mode 100644 org.argeo.core/src/org/argeo/cli/jcr/JcrCommands.java create mode 100644 org.argeo.core/src/org/argeo/cli/jcr/JcrSync.java create mode 100644 org.argeo.core/src/org/argeo/cli/jcr/package-info.java create mode 100644 org.argeo.core/src/org/argeo/cli/jcr/repository-localfs.xml create mode 100644 org.argeo.jcr/src/org/argeo/jackrabbit/client/ClientDavexRepositoryFactory.java create mode 100644 org.argeo.jcr/src/org/argeo/jackrabbit/client/ClientDavexRepositoryService.java create mode 100644 org.argeo.jcr/src/org/argeo/jackrabbit/client/ClientDavexRepositoryServiceFactory.java create mode 100644 org.argeo.jcr/src/org/argeo/jackrabbit/client/JackrabbitClient.java create mode 100644 org.argeo.jcr/src/org/argeo/jackrabbit/client/NonSerialBasicAuthCache.java diff --git a/dist/argeo-cli/pom.xml b/dist/argeo-cli/pom.xml index b067131cf..c455e0a17 100644 --- a/dist/argeo-cli/pom.xml +++ b/dist/argeo-cli/pom.xml @@ -1,4 +1,6 @@ - + 4.0.0 org.argeo.commons @@ -9,21 +11,24 @@ argeo-cli pom Argeo Command Line + + 20.0.0 + + + + org.argeo.commons + org.argeo.dep.cms.client + 2.1.89-SNAPSHOT + + + org.argeo.commons + org.argeo.dep.cms.node + 2.1.89-SNAPSHOT + + dist - - - org.argeo.commons - org.argeo.dep.cms.client - 2.1.89-SNAPSHOT - - - org.argeo.commons - org.argeo.dep.cms.node - 2.1.89-SNAPSHOT - - @@ -106,5 +111,49 @@ + + native-image + + + + org.graalvm.nativeimage + native-image-maven-plugin + ${graalvm.version} + + + + native-image + + package + + + + argeo + org.argeo.cms.cli.ArgeoCli + + --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 + + false + + + + + + + org.graalvm.sdk + graal-sdk + ${graalvm.version} + provided + + + diff --git a/org.argeo.cms/src/org/argeo/cms/cli/ArgeoCli.java b/org.argeo.cms/src/org/argeo/cms/cli/ArgeoCli.java index 5e589229d..fbac64dc9 100644 --- a/org.argeo.cms/src/org/argeo/cms/cli/ArgeoCli.java +++ b/org.argeo.cms/src/org/argeo/cms/cli/ArgeoCli.java @@ -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 diff --git a/org.argeo.cms/src/org/argeo/cms/internal/http/CmsSessionProvider.java b/org.argeo.cms/src/org/argeo/cms/internal/http/CmsSessionProvider.java index 14f311f37..524244d82 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/http/CmsSessionProvider.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/http/CmsSessionProvider.java @@ -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 cmsSessions = new LinkedHashMap<>(); + private LinkedHashMap 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); + } } } diff --git a/org.argeo.core/bnd.bnd b/org.argeo.core/bnd.bnd index fc2cfe4b5..6e0c5458f 100644 --- a/org.argeo.core/bnd.bnd +++ b/org.argeo.core/bnd.bnd @@ -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 diff --git a/org.argeo.core/build.properties b/org.argeo.core/build.properties index 89b99406d..6562d8309 100644 --- a/org.argeo.core/build.properties +++ b/org.argeo.core/build.properties @@ -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 index 000000000..68b9a18cf --- /dev/null +++ b/org.argeo.core/src/org/argeo/cli/CommandRuntimeException.java @@ -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 arguments; + + public CommandRuntimeException(Throwable e, DescribedCommand command, List arguments) { + this(null, e, command, arguments); + } + + public CommandRuntimeException(String message, DescribedCommand command, List arguments) { + this(message, null, command, arguments); + } + + public CommandRuntimeException(String message, Throwable e, DescribedCommand command, List 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 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 index 000000000..ea7467462 --- /dev/null +++ b/org.argeo.core/src/org/argeo/cli/jcr/JcrCommands.java @@ -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 index 000000000..a9ec6e26d --- /dev/null +++ b/org.argeo.core/src/org/argeo/cli/jcr/JcrSync.java @@ -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> { + 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 apply(List t) { + try { + CommandLine line = toCommandLine(t); + List 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(); + } 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 params = new HashMap(); + 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 index 000000000..6f3f01f3a --- /dev/null +++ b/org.argeo.core/src/org/argeo/cli/jcr/package-info.java @@ -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 index 000000000..5e7759cf4 --- /dev/null +++ b/org.argeo.core/src/org/argeo/cli/jcr/repository-localfs.xml @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ 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 index 000000000..77ad527e1 --- /dev/null +++ b/org.argeo.jcr/src/org/argeo/jackrabbit/client/ClientDavexRepositoryFactory.java @@ -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 index 000000000..0f9db8772 --- /dev/null +++ b/org.argeo.jcr/src/org/argeo/jackrabbit/client/ClientDavexRepositoryService.java @@ -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 index 000000000..4b240f060 --- /dev/null +++ b/org.argeo.jcr/src/org/argeo/jackrabbit/client/ClientDavexRepositoryServiceFactory.java @@ -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 index 000000000..32b8c06d9 --- /dev/null +++ b/org.argeo.jcr/src/org/argeo/jackrabbit/client/JackrabbitClient.java @@ -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 params = new HashMap(); + 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 index 000000000..3fb0db9a0 --- /dev/null +++ b/org.argeo.jcr/src/org/argeo/jackrabbit/client/NonSerialBasicAuthCache.java @@ -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 cache; + + public NonSerialBasicAuthCache() { + cache = new ConcurrentHashMap(); + } + + @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(); + } + +} diff --git a/org.argeo.jcr/src/org/argeo/jackrabbit/fs/DavexFsProvider.java b/org.argeo.jcr/src/org/argeo/jackrabbit/fs/DavexFsProvider.java index 30cabbe65..331e9acdd 100644 --- a/org.argeo.jcr/src/org/argeo/jackrabbit/fs/DavexFsProvider.java +++ b/org.argeo.jcr/src/org/argeo/jackrabbit/fs/DavexFsProvider.java @@ -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 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 params = new HashMap(); - 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 ds = Files.newDirectoryStream(path); for (Path p : ds) { diff --git a/org.argeo.jcr/src/org/argeo/jcr/JcrUtils.java b/org.argeo.jcr/src/org/argeo/jcr/JcrUtils.java index 416d035f8..94b1dfdfa 100644 --- a/org.argeo.jcr/src/org/argeo/jcr/JcrUtils.java +++ b/org.argeo.jcr/src/org/argeo/jcr/JcrUtils.java @@ -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 { -- 2.30.2