<artifactId>org.argeo.enterprise</artifactId>
<version>2.3-SNAPSHOT</version>
</dependency>
- <dependency>
- <groupId>org.argeo.commons</groupId>
- <artifactId>org.argeo.jcr</artifactId>
- <version>2.3-SNAPSHOT</version>
- </dependency>
+<!-- <dependency> -->
+<!-- <groupId>org.argeo.commons</groupId> -->
+<!-- <artifactId>org.argeo.jcr</artifactId> -->
+<!-- <version>2.3-SNAPSHOT</version> -->
+<!-- </dependency> -->
<dependency>
<groupId>org.argeo.commons</groupId>
<artifactId>org.argeo.core</artifactId>
</parent>
<artifactId>org.argeo.dep.cms.node</artifactId>
<name>CMS Node</name>
+
<dependencies>
<!-- Parent dependencies -->
<version>2.3-SNAPSHOT</version>
</dependency>
<dependency>
+
<groupId>org.argeo.commons</groupId>
<artifactId>org.argeo.cms</artifactId>
<version>2.3-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.argeo.commons</groupId>
- <artifactId>org.argeo.maintenance</artifactId>
+ <artifactId>org.argeo.cms.jcr</artifactId>
<version>2.3-SNAPSHOT</version>
</dependency>
+<!-- <dependency> -->
+<!-- <groupId>org.argeo.commons</groupId> -->
+<!-- <artifactId>org.argeo.maintenance</artifactId> -->
+<!-- <version>2.3-SNAPSHOT</version> -->
+<!-- </dependency> -->
<!-- CMS Dependencies -->
<!-- <dependency> -->
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11">
+ <attributes>
+ <attribute name="module" value="true"/>
+ </attributes>
+ </classpathentry>
+ <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+ <classpathentry kind="src" path="src"/>
+ <classpathentry kind="src" path="ext/test"/>
+ <classpathentry kind="output" path="bin"/>
+</classpath>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>org.argeo.cms.jcr</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>org.eclipse.jdt.core.javabuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.pde.ManifestBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.pde.SchemaBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.eclipse.pde.PluginNature</nature>
+ <nature>org.eclipse.jdt.core.javanature</nature>
+ </natures>
+</projectDescription>
--- /dev/null
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.annotation.inheritNullAnnotations=disabled
+org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation=ignore
+org.eclipse.jdt.core.compiler.annotation.nonnull=org.eclipse.jdt.annotation.NonNull
+org.eclipse.jdt.core.compiler.annotation.nonnull.secondary=
+org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jdt.annotation.NonNullByDefault
+org.eclipse.jdt.core.compiler.annotation.nonnullbydefault.secondary=
+org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nullable
+org.eclipse.jdt.core.compiler.annotation.nullable.secondary=
+org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled
+org.eclipse.jdt.core.compiler.problem.APILeak=warning
+org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning
+org.eclipse.jdt.core.compiler.problem.autoboxing=ignore
+org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning
+org.eclipse.jdt.core.compiler.problem.deadCode=warning
+org.eclipse.jdt.core.compiler.problem.deprecation=warning
+org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled
+org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled
+org.eclipse.jdt.core.compiler.problem.discouragedReference=warning
+org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore
+org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=ignore
+org.eclipse.jdt.core.compiler.problem.fallthroughCase=ignore
+org.eclipse.jdt.core.compiler.problem.fatalOptionalError=disabled
+org.eclipse.jdt.core.compiler.problem.fieldHiding=ignore
+org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning
+org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning
+org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
+org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning
+org.eclipse.jdt.core.compiler.problem.includeNullInfoFromAsserts=disabled
+org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning
+org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=warning
+org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore
+org.eclipse.jdt.core.compiler.problem.localVariableHiding=ignore
+org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning
+org.eclipse.jdt.core.compiler.problem.missingDefaultCase=ignore
+org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=ignore
+org.eclipse.jdt.core.compiler.problem.missingEnumCaseDespiteDefault=disabled
+org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=ignore
+org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=ignore
+org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled
+org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning
+org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore
+org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning
+org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning
+org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore
+org.eclipse.jdt.core.compiler.problem.nonnullParameterAnnotationDropped=warning
+org.eclipse.jdt.core.compiler.problem.nonnullTypeVariableFromLegacyInvocation=warning
+org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=error
+org.eclipse.jdt.core.compiler.problem.nullReference=warning
+org.eclipse.jdt.core.compiler.problem.nullSpecViolation=error
+org.eclipse.jdt.core.compiler.problem.nullUncheckedConversion=warning
+org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning
+org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore
+org.eclipse.jdt.core.compiler.problem.pessimisticNullAnalysisForFreeTypeVariables=warning
+org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=ignore
+org.eclipse.jdt.core.compiler.problem.potentialNullReference=ignore
+org.eclipse.jdt.core.compiler.problem.potentiallyUnclosedCloseable=ignore
+org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning
+org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning
+org.eclipse.jdt.core.compiler.problem.redundantNullCheck=ignore
+org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=ignore
+org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=ignore
+org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore
+org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore
+org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled
+org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning
+org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled
+org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled
+org.eclipse.jdt.core.compiler.problem.syntacticNullAnalysisForFields=disabled
+org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore
+org.eclipse.jdt.core.compiler.problem.terminalDeprecation=warning
+org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning
+org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=enabled
+org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning
+org.eclipse.jdt.core.compiler.problem.unclosedCloseable=warning
+org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore
+org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning
+org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentType=warning
+org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentTypeStrict=disabled
+org.eclipse.jdt.core.compiler.problem.unlikelyEqualsArgumentType=info
+org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore
+org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=ignore
+org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore
+org.eclipse.jdt.core.compiler.problem.unstableAutoModuleName=warning
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=ignore
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled
+org.eclipse.jdt.core.compiler.problem.unusedExceptionParameter=ignore
+org.eclipse.jdt.core.compiler.problem.unusedImport=warning
+org.eclipse.jdt.core.compiler.problem.unusedLabel=warning
+org.eclipse.jdt.core.compiler.problem.unusedLocal=warning
+org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=ignore
+org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore
+org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled
+org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled
+org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled
+org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning
+org.eclipse.jdt.core.compiler.problem.unusedTypeParameter=ignore
+org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning
+org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning
--- /dev/null
+Provide-Capability:\
+cms.datamodel; name=jcrx; cnd=/org/argeo/jcr/jcrx.cnd; abstract=true
+
+Import-Package:\
+org.apache.jackrabbit.api,\
+org.apache.jackrabbit.commons,\
+org.apache.jackrabbit.spi,\
+org.apache.jackrabbit.spi2dav,\
+org.apache.jackrabbit.spi2davex,\
+org.apache.jackrabbit.webdav,\
+junit.*;resolution:=optional,\
+*
\ No newline at end of file
--- /dev/null
+source.. = src/,\
+ ext/test/
+output.. = bin/
+bin.includes = META-INF/,\
+ .
+additional.bundles = org.junit,\
+ org.hamcrest,\
+ org.apache.jackrabbit.core,\
+ javax.jcr,\
+ org.apache.jackrabbit.api,\
+ org.apache.jackrabbit.data,\
+ org.apache.jackrabbit.jcr.commons,\
+ org.apache.jackrabbit.spi,\
+ org.apache.jackrabbit.spi.commons,\
+ org.slf4j.api,\
+ org.slf4j.log4j12,\
+ org.apache.log4j,\
+ org.apache.commons.collections,\
+ EDU.oswego.cs.dl.util.concurrent,\
+ org.apache.lucene,\
+ org.apache.tika.core,\
+ org.apache.commons.dbcp,\
+ org.apache.commons.pool,\
+ com.google.guava,\
+ org.apache.jackrabbit.jcr2spi,\
+ org.apache.jackrabbit.spi2dav,\
+ org.apache.httpcomponents.httpclient,\
+ org.apache.httpcomponents.httpcore,\
+ org.apache.tika.parsers
+
\ No newline at end of file
--- /dev/null
+package org.argeo.jcr.fs;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URI;
+import java.nio.file.DirectoryStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.nio.file.attribute.FileTime;
+import java.nio.file.spi.FileSystemProvider;
+import java.util.Arrays;
+import java.util.Map;
+
+import javax.jcr.Property;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.nodetype.NodeType;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.jackrabbit.core.RepositoryImpl;
+import org.argeo.jackrabbit.fs.JackrabbitMemoryFsProvider;
+
+import junit.framework.TestCase;
+
+public class JcrFileSystemTest extends TestCase {
+ private final static Log log = LogFactory.getLog(JcrFileSystemTest.class);
+
+ public void testMounts() throws Exception {
+ JackrabbitMemoryFsProvider fsProvider = new JackrabbitMemoryFsProvider() {
+
+ @Override
+ protected void postRepositoryCreation(RepositoryImpl repositoryImpl) throws RepositoryException {
+ // create workspace
+ Session session = login();
+ session.getWorkspace().createWorkspace("test");
+ }
+
+ };
+
+ Path rootPath = fsProvider.getPath(new URI("jcr+memory:/"));
+ log.debug("Got root " + rootPath);
+ Path testDir = rootPath.resolve("testDir");
+ Files.createDirectory(testDir);
+
+ Path testMount = fsProvider.getPath(new URI("jcr+memory:/test"));
+ log.debug("Test path");
+ assertEquals(rootPath, testMount.getParent());
+ assertEquals(testMount.getFileName(), rootPath.relativize(testMount));
+
+ Path testPath = testMount.resolve("test.txt");
+ log.debug("Create file " + testPath);
+ Files.createFile(testPath);
+ BasicFileAttributes bfa = Files.readAttributes(testPath, BasicFileAttributes.class);
+ FileTime ft = bfa.creationTime();
+ assertNotNull(ft);
+ assertTrue(bfa.isRegularFile());
+ log.debug("Created " + testPath + " (" + ft + ")");
+ Files.delete(testPath);
+ log.debug("Deleted " + testPath);
+
+ // Browse directories from root
+ DirectoryStream<Path> files = Files.newDirectoryStream(rootPath);
+ int directoryCount = 0;
+ for (Path file : files) {
+ if (Files.isDirectory(file)) {
+ directoryCount++;
+ }
+ }
+ assertEquals(2, directoryCount);
+
+ // Browse directories from mount
+ Path mountSubDir = testMount.resolve("mountSubDir");
+ Files.createDirectory(mountSubDir);
+ Path otherSubDir = testMount.resolve("otherSubDir");
+ Files.createDirectory(otherSubDir);
+ testPath = testMount.resolve("test.txt");
+ Files.createFile(testPath);
+ files = Files.newDirectoryStream(testMount);
+ int fileCount = 0;
+ for (Path file : files) {
+ fileCount++;
+ }
+ assertEquals(3, fileCount);
+
+ }
+
+ public void testSimple() throws Exception {
+ FileSystemProvider fsProvider = new JackrabbitMemoryFsProvider();
+
+ // Simple file
+ Path rootPath = fsProvider.getPath(new URI("jcr+memory:/"));
+ log.debug("Got root " + rootPath);
+ Path testPath = fsProvider.getPath(new URI("jcr+memory:/test.txt"));
+ log.debug("Test path");
+ assertEquals("test.txt", testPath.getFileName().toString());
+ assertEquals(rootPath, testPath.getParent());
+ assertEquals(testPath.getFileName(), rootPath.relativize(testPath));
+ // relativize self should be empty path
+ Path selfRelative = testPath.relativize(testPath);
+ assertEquals("", selfRelative.toString());
+
+ log.debug("Create file " + testPath);
+ Files.createFile(testPath);
+ BasicFileAttributes bfa = Files.readAttributes(testPath, BasicFileAttributes.class);
+ FileTime ft = bfa.creationTime();
+ assertNotNull(ft);
+ assertTrue(bfa.isRegularFile());
+ log.debug("Created " + testPath + " (" + ft + ")");
+ Files.delete(testPath);
+ log.debug("Deleted " + testPath);
+ String txt = "TEST\nTEST2\n";
+ byte[] arr = txt.getBytes();
+ Files.write(testPath, arr);
+ log.debug("Wrote " + testPath);
+ byte[] read = Files.readAllBytes(testPath);
+ assertTrue(Arrays.equals(arr, read));
+ assertEquals(txt, new String(read));
+ log.debug("Read " + testPath);
+ Path testDir = rootPath.resolve("testDir");
+ log.debug("Resolved " + testDir);
+ // Copy
+ Files.createDirectory(testDir);
+ log.debug("Created directory " + testDir);
+ Path subsubdir = Files.createDirectories(testDir.resolve("subdir/subsubdir"));
+ log.debug("Created sub directories " + subsubdir);
+ Path copiedFile = testDir.resolve("copiedFile.txt");
+ log.debug("Resolved " + copiedFile);
+ Path relativeCopiedFile = testDir.relativize(copiedFile);
+ assertEquals(copiedFile.getFileName().toString(), relativeCopiedFile.toString());
+ log.debug("Relative copied file " + relativeCopiedFile);
+ try (OutputStream out = Files.newOutputStream(copiedFile); InputStream in = Files.newInputStream(testPath)) {
+ IOUtils.copy(in, out);
+ }
+ log.debug("Copied " + testPath + " to " + copiedFile);
+ Files.delete(testPath);
+ log.debug("Deleted " + testPath);
+ byte[] copiedRead = Files.readAllBytes(copiedFile);
+ assertTrue(Arrays.equals(copiedRead, read));
+ log.debug("Read " + copiedFile);
+ // Browse directories
+ DirectoryStream<Path> files = Files.newDirectoryStream(testDir);
+ int fileCount = 0;
+ Path listedFile = null;
+ for (Path file : files) {
+ fileCount++;
+ if (!Files.isDirectory(file))
+ listedFile = file;
+ }
+ assertEquals(2, fileCount);
+ assertEquals(copiedFile, listedFile);
+ assertEquals(copiedFile.toString(), listedFile.toString());
+ log.debug("Listed " + testDir);
+ // Generic attributes
+ Map<String, Object> attrs = Files.readAttributes(copiedFile, "*");
+ assertEquals(3, attrs.size());
+ log.debug("Read attributes of " + copiedFile + ": " + attrs.keySet());
+ // Direct node access
+ NodeFileAttributes nfa = Files.readAttributes(copiedFile, NodeFileAttributes.class);
+ nfa.getNode().addMixin(NodeType.MIX_LANGUAGE);
+ nfa.getNode().getSession().save();
+ log.debug("Add mix:language");
+ Files.setAttribute(copiedFile, Property.JCR_LANGUAGE, "fr");
+ log.debug("Set language");
+ attrs = Files.readAttributes(copiedFile, "*");
+ assertEquals(4, attrs.size());
+ log.debug("Read attributes of " + copiedFile + ": " + attrs.keySet());
+ }
+
+ public void testIllegalCharacters() throws Exception {
+ FileSystemProvider fsProvider = new JackrabbitMemoryFsProvider();
+ String fileName = "tüßçt[1].txt";
+ String pathStr = "/testDir/" + fileName;
+ Path testDir = fsProvider.getPath(new URI("jcr+memory:/testDir"));
+ Files.createDirectory(testDir);
+ Path testPath = testDir.resolve(fileName);
+ assertEquals(pathStr, testPath.toString());
+ Files.createFile(testPath);
+ DirectoryStream<Path> files = Files.newDirectoryStream(testDir);
+ Path listedPath = files.iterator().next();
+ assertEquals(pathStr, listedPath.toString());
+
+ String dirName = "*[~WeirdDir~]*";
+ Path subDir = testDir.resolve(dirName);
+ Files.createDirectory(subDir);
+ subDir = testDir.resolve(dirName);
+ assertEquals(dirName, subDir.getFileName().toString());
+ }
+}
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<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-commons</artifactId>
+ <version>2.3-SNAPSHOT</version>
+ <relativePath>..</relativePath>
+ </parent>
+ <artifactId>org.argeo.cms.jcr</artifactId>
+ <name>Commons CMS JCR</name>
+ <dependencies>
+ <dependency>
+ <groupId>org.argeo.commons</groupId>
+ <artifactId>org.argeo.api</artifactId>
+ <version>2.3-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>org.argeo.commons</groupId>
+ <artifactId>org.argeo.enterprise</artifactId>
+ <version>2.3-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>org.argeo.commons</groupId>
+ <artifactId>org.argeo.core</artifactId>
+ <version>2.3-SNAPSHOT</version>
+ </dependency>
+ </dependencies>
+</project>
\ No newline at end of file
--- /dev/null
+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";
+ }
+
+}
--- /dev/null
+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());
+ // FIXME make it configurable
+ params.put(ClientDavexRepositoryFactory.JACKRABBIT_REMOTE_DEFAULT_WORKSPACE, "sys");
+ 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";
+ }
+
+}
--- /dev/null
+/** JCR CLI commands. */
+package org.argeo.cli.jcr;
\ No newline at end of file
--- /dev/null
+<?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
--- /dev/null
+package org.argeo.jackrabbit;
+
+import java.util.Map;
+
+import javax.security.auth.Subject;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.login.LoginException;
+import javax.security.auth.spi.LoginModule;
+
+import org.apache.jackrabbit.core.security.SecurityConstants;
+import org.apache.jackrabbit.core.security.principal.AdminPrincipal;
+
+@Deprecated
+public class JackrabbitAdminLoginModule implements LoginModule {
+ private Subject subject;
+
+ @Override
+ public void initialize(Subject subject, CallbackHandler callbackHandler,
+ Map<String, ?> sharedState, Map<String, ?> options) {
+ this.subject = subject;
+ }
+
+ @Override
+ public boolean login() throws LoginException {
+ // TODO check permission?
+ return true;
+ }
+
+ @Override
+ public boolean commit() throws LoginException {
+ subject.getPrincipals().add(
+ new AdminPrincipal(SecurityConstants.ADMIN_ID));
+ return true;
+ }
+
+ @Override
+ public boolean abort() throws LoginException {
+ return true;
+ }
+
+ @Override
+ public boolean logout() throws LoginException {
+ subject.getPrincipals().removeAll(
+ subject.getPrincipals(AdminPrincipal.class));
+ return true;
+ }
+
+}
--- /dev/null
+package org.argeo.jackrabbit;
+
+import java.awt.geom.CubicCurve2D;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.net.URL;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.jackrabbit.commons.cnd.CndImporter;
+import org.apache.jackrabbit.commons.cnd.ParseException;
+import org.apache.jackrabbit.core.config.RepositoryConfig;
+import org.apache.jackrabbit.core.fs.FileSystemException;
+import org.argeo.jcr.JcrCallback;
+import org.argeo.jcr.JcrException;
+import org.argeo.jcr.JcrUtils;
+
+/** Migrate the data in a Jackrabbit repository. */
+@Deprecated
+public class JackrabbitDataModelMigration implements Comparable<JackrabbitDataModelMigration> {
+ private final static Log log = LogFactory.getLog(JackrabbitDataModelMigration.class);
+
+ private String dataModelNodePath;
+ private String targetVersion;
+ private URL migrationCnd;
+ private JcrCallback dataModification;
+
+ /**
+ * Expects an already started repository with the old data model to migrate.
+ * Expects to be run with admin rights (Repository.login() will be used).
+ *
+ * @return true if a migration was performed and the repository needs to be
+ * restarted and its caches cleared.
+ */
+ public Boolean migrate(Session session) {
+ long begin = System.currentTimeMillis();
+ Reader reader = null;
+ try {
+ // check if already migrated
+ if (!session.itemExists(dataModelNodePath)) {
+// log.warn("Node " + dataModelNodePath + " does not exist: nothing to migrate.");
+ return false;
+ }
+// Node dataModelNode = session.getNode(dataModelNodePath);
+// if (dataModelNode.hasProperty(ArgeoNames.ARGEO_DATA_MODEL_VERSION)) {
+// String currentVersion = dataModelNode.getProperty(
+// ArgeoNames.ARGEO_DATA_MODEL_VERSION).getString();
+// if (compareVersions(currentVersion, targetVersion) >= 0) {
+// log.info("Data model at version " + currentVersion
+// + ", no need to migrate.");
+// return false;
+// }
+// }
+
+ // apply transitional CND
+ if (migrationCnd != null) {
+ reader = new InputStreamReader(migrationCnd.openStream());
+ CndImporter.registerNodeTypes(reader, session, true);
+ session.save();
+// log.info("Registered migration node types from " + migrationCnd);
+ }
+
+ // modify data
+ dataModification.execute(session);
+
+ // apply changes
+ session.save();
+
+ long duration = System.currentTimeMillis() - begin;
+// log.info("Migration of data model " + dataModelNodePath + " to " + targetVersion + " performed in "
+// + duration + "ms");
+ return true;
+ } catch (RepositoryException e) {
+ JcrUtils.discardQuietly(session);
+ throw new JcrException("Migration of data model " + dataModelNodePath + " to " + targetVersion + " failed.",
+ e);
+ } catch (ParseException | IOException e) {
+ JcrUtils.discardQuietly(session);
+ throw new RuntimeException(
+ "Migration of data model " + dataModelNodePath + " to " + targetVersion + " failed.", e);
+ } finally {
+ JcrUtils.logoutQuietly(session);
+ IOUtils.closeQuietly(reader);
+ }
+ }
+
+ protected static int compareVersions(String version1, String version2) {
+ // TODO do a proper version analysis and comparison
+ return version1.compareTo(version2);
+ }
+
+ /** To be called on a stopped repository. */
+ public static void clearRepositoryCaches(RepositoryConfig repositoryConfig) {
+ try {
+ String customeNodeTypesPath = "/nodetypes/custom_nodetypes.xml";
+ // FIXME causes weird error in Eclipse
+ repositoryConfig.getFileSystem().deleteFile(customeNodeTypesPath);
+ if (log.isDebugEnabled())
+ log.debug("Cleared " + customeNodeTypesPath);
+ } catch (RuntimeException e) {
+ throw e;
+ } catch (RepositoryException e) {
+ throw new JcrException(e);
+ } catch (FileSystemException e) {
+ throw new RuntimeException("Cannot clear node types cache.",e);
+ }
+
+ // File customNodeTypes = new File(home.getPath()
+ // + "/repository/nodetypes/custom_nodetypes.xml");
+ // if (customNodeTypes.exists()) {
+ // customNodeTypes.delete();
+ // if (log.isDebugEnabled())
+ // log.debug("Cleared " + customNodeTypes);
+ // } else {
+ // log.warn("File " + customNodeTypes + " not found.");
+ // }
+ }
+
+ /*
+ * FOR USE IN (SORTED) SETS
+ */
+
+ public int compareTo(JackrabbitDataModelMigration dataModelMigration) {
+ // TODO make ordering smarter
+ if (dataModelNodePath.equals(dataModelMigration.dataModelNodePath))
+ return compareVersions(targetVersion, dataModelMigration.targetVersion);
+ else
+ return dataModelNodePath.compareTo(dataModelMigration.dataModelNodePath);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof JackrabbitDataModelMigration))
+ return false;
+ JackrabbitDataModelMigration dataModelMigration = (JackrabbitDataModelMigration) obj;
+ return dataModelNodePath.equals(dataModelMigration.dataModelNodePath)
+ && targetVersion.equals(dataModelMigration.targetVersion);
+ }
+
+ @Override
+ public int hashCode() {
+ return targetVersion.hashCode();
+ }
+
+ public void setDataModelNodePath(String dataModelNodePath) {
+ this.dataModelNodePath = dataModelNodePath;
+ }
+
+ public void setTargetVersion(String targetVersion) {
+ this.targetVersion = targetVersion;
+ }
+
+ public void setMigrationCnd(URL migrationCnd) {
+ this.migrationCnd = migrationCnd;
+ }
+
+ public void setDataModification(JcrCallback dataModification) {
+ this.dataModification = dataModification;
+ }
+
+ public String getDataModelNodePath() {
+ return dataModelNodePath;
+ }
+
+ public String getTargetVersion() {
+ return targetVersion;
+ }
+
+}
--- /dev/null
+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));
+ }
+
+}
--- /dev/null
+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;
+ }
+
+}
--- /dev/null
+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);
+ }
+ }
+
+}
--- /dev/null
+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());
+ // FIXME make it configurable
+ params.put(JACKRABBIT_REMOTE_DEFAULT_WORKSPACE, "sys");
+
+ 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();
+ }
+ }
+}
--- /dev/null
+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();
+ }
+
+}
--- /dev/null
+package org.argeo.jackrabbit.fs;
+
+import org.argeo.jcr.fs.JcrFileSystemProvider;
+
+public abstract class AbstractJackrabbitFsProvider extends JcrFileSystemProvider {
+
+}
--- /dev/null
+package org.argeo.jackrabbit.fs;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.file.DirectoryStream;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystemAlreadyExistsException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.jcr.Repository;
+import javax.jcr.RepositoryFactory;
+import javax.jcr.Session;
+
+import org.argeo.jackrabbit.client.ClientDavexRepositoryFactory;
+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 DEFAULT_JACKRABBIT_REMOTE_DEFAULT_WORKSPACE = "sys";
+
+ private Map<String, JcrFileSystem> fileSystems = new HashMap<>();
+
+ @Override
+ public String getScheme() {
+ return "davex";
+ }
+
+ @Override
+ public FileSystem newFileSystem(URI uri, Map<String, ?> env) throws IOException {
+ if (uri.getHost() == null)
+ throw new IllegalArgumentException("An host should be provided");
+ try {
+ URI repoUri = new URI("http", uri.getUserInfo(), uri.getHost(), uri.getPort(), uri.getPath(), null, null);
+ String repoKey = repoUri.toString();
+ if (fileSystems.containsKey(repoKey))
+ throw new FileSystemAlreadyExistsException("CMS file system already exists for " + repoKey);
+ RepositoryFactory repositoryFactory = new ClientDavexRepositoryFactory();
+ return tryGetRepo(repositoryFactory, repoUri, "home");
+ } catch (URISyntaxException e) {
+ throw new IllegalArgumentException("Cannot open file system " + uri, e);
+ }
+ }
+
+ private JcrFileSystem tryGetRepo(RepositoryFactory repositoryFactory, URI repoUri, String workspace)
+ throws IOException {
+ Map<String, String> params = new HashMap<String, String>();
+ params.put(ClientDavexRepositoryFactory.JACKRABBIT_DAVEX_URI, repoUri.toString());
+ // TODO better integrate with OSGi or other configuration than system
+ // properties.
+ String remoteDefaultWorkspace = System.getProperty(
+ ClientDavexRepositoryFactory.JACKRABBIT_REMOTE_DEFAULT_WORKSPACE,
+ DEFAULT_JACKRABBIT_REMOTE_DEFAULT_WORKSPACE);
+ params.put(ClientDavexRepositoryFactory.JACKRABBIT_REMOTE_DEFAULT_WORKSPACE, remoteDefaultWorkspace);
+ Repository repository = null;
+ Session session = null;
+ try {
+ repository = repositoryFactory.getRepository(params);
+ if (repository != null)
+ session = repository.login(workspace);
+ } catch (Exception e) {
+ // silent
+ }
+
+ if (session == null) {
+ if (repoUri.getPath() == null || repoUri.getPath().equals("/"))
+ return null;
+ String repoUriStr = repoUri.toString();
+ if (repoUriStr.endsWith("/"))
+ repoUriStr = repoUriStr.substring(0, repoUriStr.length() - 1);
+ String nextRepoUriStr = repoUriStr.substring(0, repoUriStr.lastIndexOf('/'));
+ String nextWorkspace = repoUriStr.substring(repoUriStr.lastIndexOf('/') + 1);
+ URI nextUri;
+ try {
+ nextUri = new URI(nextRepoUriStr);
+ } catch (URISyntaxException e) {
+ throw new IllegalArgumentException("Badly formatted URI", e);
+ }
+ return tryGetRepo(repositoryFactory, nextUri, nextWorkspace);
+ } else {
+ JcrFileSystem fileSystem = new JcrFileSystem(this, repository);
+ fileSystems.put(repoUri.toString() + "/" + workspace, fileSystem);
+ return fileSystem;
+ }
+ }
+
+ @Override
+ public FileSystem getFileSystem(URI uri) {
+ return currentUserFileSystem(uri);
+ }
+
+ @Override
+ public Path getPath(URI uri) {
+ JcrFileSystem fileSystem = currentUserFileSystem(uri);
+ if (fileSystem == null)
+ try {
+ fileSystem = (JcrFileSystem) newFileSystem(uri, new HashMap<String, Object>());
+ if (fileSystem == null)
+ throw new IllegalArgumentException("No file system found for " + uri);
+ } catch (IOException e) {
+ throw new JcrFsException("Could not autocreate file system", e);
+ }
+ URI repoUri = null;
+ try {
+ repoUri = new URI("http", uri.getUserInfo(), uri.getHost(), uri.getPort(), uri.getPath(), null, null);
+ } catch (URISyntaxException e) {
+ throw new IllegalArgumentException(e);
+ }
+ String uriStr = repoUri.toString();
+ String localPath = null;
+ for (String key : fileSystems.keySet()) {
+ if (uriStr.startsWith(key)) {
+ localPath = uriStr.toString().substring(key.length());
+ }
+ }
+ if ("".equals(localPath))
+ localPath = "/";
+ return fileSystem.getPath(localPath);
+ }
+
+ private JcrFileSystem currentUserFileSystem(URI uri) {
+ for (String key : fileSystems.keySet()) {
+ if (uri.toString().startsWith(key))
+ return fileSystems.get(key);
+ }
+ return null;
+ }
+
+ public static void main(String args[]) {
+ try {
+ DavexFsProvider fsProvider = new DavexFsProvider();
+ 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) {
+ System.out.println("- " + p);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+}
--- /dev/null
+package org.argeo.jackrabbit.fs;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.nio.file.FileSystem;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.jcr.Credentials;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.SimpleCredentials;
+
+import org.apache.jackrabbit.core.RepositoryImpl;
+import org.apache.jackrabbit.core.config.RepositoryConfig;
+import org.argeo.jcr.fs.JcrFileSystem;
+import org.argeo.jcr.fs.JcrFsException;
+
+public class JackrabbitMemoryFsProvider extends AbstractJackrabbitFsProvider {
+ private RepositoryImpl repository;
+ private JcrFileSystem fileSystem;
+
+ private Credentials credentials;
+
+ public JackrabbitMemoryFsProvider() {
+ String username = System.getProperty("user.name");
+ credentials = new SimpleCredentials(username, username.toCharArray());
+ }
+
+ @Override
+ public String getScheme() {
+ return "jcr+memory";
+ }
+
+ @Override
+ public FileSystem newFileSystem(URI uri, Map<String, ?> env) throws IOException {
+ try {
+ Path tempDir = Files.createTempDirectory("fs-memory");
+ URL confUrl = JackrabbitMemoryFsProvider.class.getResource("fs-memory.xml");
+ RepositoryConfig repositoryConfig = RepositoryConfig.create(confUrl.toURI(), tempDir.toString());
+ repository = RepositoryImpl.create(repositoryConfig);
+ postRepositoryCreation(repository);
+ fileSystem = new JcrFileSystem(this, repository, credentials);
+ return fileSystem;
+ } catch (RepositoryException | URISyntaxException e) {
+ throw new IOException("Cannot login to repository", e);
+ }
+ }
+
+ @Override
+ public FileSystem getFileSystem(URI uri) {
+ return fileSystem;
+ }
+
+ @Override
+ public Path getPath(URI uri) {
+ String path = uri.getPath();
+ if (fileSystem == null)
+ try {
+ newFileSystem(uri, new HashMap<String, Object>());
+ } catch (IOException e) {
+ throw new JcrFsException("Could not autocreate file system", e);
+ }
+ return fileSystem.getPath(path);
+ }
+
+ public Repository getRepository() {
+ return repository;
+ }
+
+ public Session login() throws RepositoryException {
+ return getRepository().login(credentials);
+ }
+
+ /**
+ * Called after the repository has been created and before the file system is
+ * created.
+ */
+ protected void postRepositoryCreation(RepositoryImpl repositoryImpl) throws RepositoryException {
+
+ }
+}
--- /dev/null
+<?xml version="1.0"?>
+<!DOCTYPE Repository PUBLIC "Jackrabbit 2.6" "http://jackrabbit.apache.org/dtd/repository-2.6.dtd">
+<Repository>
+ <!-- File system and datastore -->
+ <FileSystem
+ class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
+
+ <!-- Workspace templates -->
+ <Workspaces rootPath="${rep.home}/workspaces"
+ defaultWorkspace="main" configRootPath="/workspaces" />
+ <Workspace name="${wsp.name}">
+ <FileSystem
+ class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
+ <PersistenceManager
+ class="org.apache.jackrabbit.core.persistence.bundle.BundleFsPersistenceManager">
+ </PersistenceManager>
+ <SearchIndex
+ class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
+ <param name="path" value="${wsp.home}/index" />
+ <param name="directoryManagerClass"
+ value="org.apache.jackrabbit.core.query.lucene.directory.RAMDirectoryManager" />
+ <param name="extractorPoolSize" value="0" />
+ <FileSystem
+ class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
+ </SearchIndex>
+ </Workspace>
+
+ <!-- Versioning -->
+ <Versioning rootPath="${rep.home}/version">
+ <FileSystem
+ class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
+ <PersistenceManager
+ class="org.apache.jackrabbit.core.persistence.bundle.BundleFsPersistenceManager">
+ </PersistenceManager>
+ </Versioning>
+
+ <!-- Indexing -->
+ <SearchIndex
+ class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
+ <param name="path" value="${rep.home}/index" />
+ <param name="directoryManagerClass"
+ value="org.apache.jackrabbit.core.query.lucene.directory.RAMDirectoryManager" />
+ <param name="extractorPoolSize" value="0" />
+ <FileSystem
+ class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
+ </SearchIndex>
+
+ <!-- Security -->
+ <Security appName="Jackrabbit">
+ <LoginModule
+ class="org.apache.jackrabbit.core.security.SimpleLoginModule" />
+ <!-- <SecurityManager class="org.argeo.security.jackrabbit.ArgeoSecurityManager" -->
+ <!-- workspaceName="security" /> -->
+ <!-- <AccessManager class="org.argeo.security.jackrabbit.ArgeoAccessManager"
+ /> -->
+ </Security>
+</Repository>
\ No newline at end of file
--- /dev/null
+/** Java NIO file system implementation based on Jackrabbit. */
+package org.argeo.jackrabbit.fs;
\ No newline at end of file
--- /dev/null
+/** Generic Jackrabbit utilities. */
+package org.argeo.jackrabbit;
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE Repository PUBLIC "Jackrabbit 2.6" "http://jackrabbit.apache.org/dtd/repository-2.6.dtd">
+<Repository>
+ <!-- Shared datasource -->
+ <DataSources>
+ <DataSource name="dataSource">
+ <param name="driver" value="org.h2.Driver" />
+ <param name="url" value="${dburl}" />
+ <param name="user" value="${dbuser}" />
+ <param name="password" value="${dbpassword}" />
+ <param name="databaseType" value="h2" />
+ <param name="maxPoolSize" value="${maxPoolSize}" />
+ </DataSource>
+ </DataSources>
+
+ <!-- File system and datastore -->
+ <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
+ <param name="dataSourceName" value="dataSource" />
+ <param name="schema" value="default" />
+ <param name="schemaObjectPrefix" value="fs_" />
+ </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="${defaultWorkspace}" />
+ <Workspace name="${wsp.name}">
+ <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
+ <param name="dataSourceName" value="dataSource" />
+ <param name="schema" value="default" />
+ <param name="schemaObjectPrefix" value="${wsp.name}_fs_" />
+ </FileSystem>
+ <PersistenceManager
+ class="org.apache.jackrabbit.core.persistence.pool.H2PersistenceManager">
+ <param name="dataSourceName" value="dataSource" />
+ <param name="schemaObjectPrefix" value="${wsp.name}_pm_" />
+ <param name="bundleCacheSize" value="${bundleCacheMB}" />
+ </PersistenceManager>
+ <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
+ <param name="path" value="${wsp.home}/index" />
+ <param name="extractorPoolSize" value="${extractorPoolSize}" />
+ <param name="cacheSize" value="${searchCacheSize}" />
+ <param name="maxVolatileIndexSize" value="${maxVolatileIndexSize}" />
+ </SearchIndex>
+ <WorkspaceSecurity>
+ <AccessControlProvider
+ class="org.argeo.security.jackrabbit.ArgeoAccessControlProvider" />
+ </WorkspaceSecurity>
+ </Workspace>
+
+ <!-- Versioning -->
+ <Versioning rootPath="${rep.home}/version">
+ <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
+ <param name="dataSourceName" value="dataSource" />
+ <param name="schema" value="default" />
+ <param name="schemaObjectPrefix" value="fs_ver_" />
+ </FileSystem>
+ <PersistenceManager
+ class="org.apache.jackrabbit.core.persistence.pool.H2PersistenceManager">
+ <param name="dataSourceName" value="dataSource" />
+ <param name="schemaObjectPrefix" value="pm_ver_" />
+ <param name="bundleCacheSize" value="${bundleCacheMB}" />
+ </PersistenceManager>
+ </Versioning>
+
+ <!-- Indexing -->
+ <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
+ <param name="path" value="${rep.home}/index" />
+ <param name="extractorPoolSize" value="${extractorPoolSize}" />
+ <param name="cacheSize" value="${searchCacheSize}" />
+ <param name="maxVolatileIndexSize" value="${maxVolatileIndexSize}" />
+ </SearchIndex>
+
+ <!-- Security -->
+ <Security appName="Jackrabbit">
+ <SecurityManager class="org.argeo.security.jackrabbit.ArgeoSecurityManager"
+ workspaceName="security" />
+ <AccessManager class="org.argeo.security.jackrabbit.ArgeoAccessManager" />
+ </Security>
+</Repository>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE Repository PUBLIC "Jackrabbit 2.6" "http://jackrabbit.apache.org/dtd/repository-2.6.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="${defaultWorkspace}" />
+ <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="bundleCacheSize" value="${bundleCacheMB}" />
+ </PersistenceManager>
+ <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
+ <param name="path" value="${wsp.home}/index" />
+ <param name="extractorPoolSize" value="${extractorPoolSize}" />
+ <param name="cacheSize" value="${searchCacheSize}" />
+ <param name="maxVolatileIndexSize" value="${maxVolatileIndexSize}" />
+ </SearchIndex>
+ <WorkspaceSecurity>
+ <AccessControlProvider
+ class="org.argeo.security.jackrabbit.ArgeoAccessControlProvider" />
+ </WorkspaceSecurity>
+ </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="bundleCacheSize" value="${bundleCacheMB}" />
+ </PersistenceManager>
+ </Versioning>
+
+ <!-- Indexing -->
+ <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
+ <param name="path" value="${rep.home}/index" />
+ <param name="extractorPoolSize" value="${extractorPoolSize}" />
+ <param name="cacheSize" value="${searchCacheSize}" />
+ <param name="maxVolatileIndexSize" value="${maxVolatileIndexSize}" />
+ </SearchIndex>
+
+ <!-- Security -->
+ <Security appName="Jackrabbit">
+ <SecurityManager class="org.argeo.security.jackrabbit.ArgeoSecurityManager"
+ workspaceName="security" />
+ <AccessManager class="org.argeo.security.jackrabbit.ArgeoAccessManager" />
+ </Security>
+</Repository>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0"?>
+<!DOCTYPE Repository PUBLIC "Jackrabbit 2.6" "http://jackrabbit.apache.org/dtd/repository-2.6.dtd">
+<Repository>
+ <!-- File system and datastore -->
+ <FileSystem class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
+
+ <!-- Workspace templates -->
+ <Workspaces rootPath="${rep.home}/workspaces"
+ defaultWorkspace="${defaultWorkspace}" configRootPath="/workspaces" />
+ <Workspace name="${wsp.name}">
+ <FileSystem class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
+ <PersistenceManager
+ class="org.apache.jackrabbit.core.persistence.bundle.BundleFsPersistenceManager">
+ <param name="blobFSBlockSize" value="1" />
+ <param name="bundleCacheSize" value="${bundleCacheMB}" />
+ </PersistenceManager>
+ <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
+ <param name="path" value="${wsp.home}/index" />
+ <param name="directoryManagerClass"
+ value="org.apache.jackrabbit.core.query.lucene.directory.RAMDirectoryManager" />
+ <param name="extractorPoolSize" value="${extractorPoolSize}" />
+ <param name="cacheSize" value="${searchCacheSize}" />
+ <param name="maxVolatileIndexSize" value="${maxVolatileIndexSize}" />
+ <FileSystem class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
+ </SearchIndex>
+ </Workspace>
+
+ <!-- Versioning -->
+ <Versioning rootPath="${rep.home}/version">
+ <FileSystem class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
+ <PersistenceManager
+ class="org.apache.jackrabbit.core.persistence.bundle.BundleFsPersistenceManager">
+ <param name="blobFSBlockSize" value="1" />
+ <param name="bundleCacheSize" value="${bundleCacheMB}" />
+ </PersistenceManager>
+ </Versioning>
+
+ <!-- Indexing -->
+ <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
+ <param name="path" value="${rep.home}/index" />
+ <param name="directoryManagerClass"
+ value="org.apache.jackrabbit.core.query.lucene.directory.RAMDirectoryManager" />
+ <param name="extractorPoolSize" value="${extractorPoolSize}" />
+ <param name="cacheSize" value="${searchCacheSize}" />
+ <param name="maxVolatileIndexSize" value="${maxVolatileIndexSize}" />
+ <FileSystem class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
+ </SearchIndex>
+
+ <!-- Security -->
+ <Security appName="Jackrabbit">
+ <SecurityManager class="org.argeo.security.jackrabbit.ArgeoSecurityManager"
+ workspaceName="security" />
+ <AccessManager class="org.argeo.security.jackrabbit.ArgeoAccessManager" />
+ </Security>
+</Repository>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0"?>
+<!DOCTYPE Repository PUBLIC "Jackrabbit 2.6" "http://jackrabbit.apache.org/dtd/repository-2.6.dtd">
+<Repository>
+ <!-- Shared datasource -->
+ <DataSources>
+ <DataSource name="dataSource">
+ <param name="driver" value="org.postgresql.Driver" />
+ <param name="url" value="${dburl}" />
+ <param name="user" value="${dbuser}" />
+ <param name="password" value="${dbpassword}" />
+ <param name="databaseType" value="postgresql" />
+ <param name="maxPoolSize" value="${maxPoolSize}" />
+ </DataSource>
+ </DataSources>
+
+ <!-- File system and datastore -->
+ <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
+ <param name="dataSourceName" value="dataSource" />
+ <param name="schema" value="postgresql" />
+ <param name="schemaObjectPrefix" value="fs_" />
+ </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="${defaultWorkspace}" />
+ <Workspace name="${wsp.name}">
+ <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
+ <param name="dataSourceName" value="dataSource" />
+ <param name="schema" value="postgresql" />
+ <param name="schemaObjectPrefix" value="${wsp.name}_fs_" />
+ </FileSystem>
+ <PersistenceManager
+ class="org.apache.jackrabbit.core.persistence.pool.PostgreSQLPersistenceManager">
+ <param name="dataSourceName" value="dataSource" />
+ <param name="schemaObjectPrefix" value="${wsp.name}_pm_" />
+ <param name="bundleCacheSize" value="${bundleCacheMB}" />
+ </PersistenceManager>
+ <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
+ <param name="path" value="${wsp.home}/index" />
+ <param name="extractorPoolSize" value="${extractorPoolSize}" />
+ <param name="cacheSize" value="${searchCacheSize}" />
+ <param name="maxVolatileIndexSize" value="${maxVolatileIndexSize}" />
+ </SearchIndex>
+ <WorkspaceSecurity>
+ <AccessControlProvider
+ class="org.argeo.security.jackrabbit.ArgeoAccessControlProvider" />
+ </WorkspaceSecurity>
+ </Workspace>
+
+ <!-- Versioning -->
+ <Versioning rootPath="${rep.home}/version">
+ <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
+ <param name="dataSourceName" value="dataSource" />
+ <param name="schema" value="postgresql" />
+ <param name="schemaObjectPrefix" value="fs_ver_" />
+ </FileSystem>
+ <PersistenceManager
+ class="org.apache.jackrabbit.core.persistence.pool.PostgreSQLPersistenceManager">
+ <param name="dataSourceName" value="dataSource" />
+ <param name="schemaObjectPrefix" value="pm_ver_" />
+ <param name="bundleCacheSize" value="${bundleCacheMB}" />
+ </PersistenceManager>
+ </Versioning>
+
+ <!-- Indexing -->
+ <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
+ <param name="path" value="${rep.home}/index" />
+ <param name="extractorPoolSize" value="${extractorPoolSize}" />
+ <param name="cacheSize" value="${searchCacheSize}" />
+ <param name="maxVolatileIndexSize" value="${maxVolatileIndexSize}" />
+ </SearchIndex>
+
+ <!-- Security -->
+ <Security appName="Jackrabbit">
+ <SecurityManager class="org.argeo.security.jackrabbit.ArgeoSecurityManager"
+ workspaceName="security" />
+ <AccessManager class="org.argeo.security.jackrabbit.ArgeoAccessManager" />
+ </Security>
+</Repository>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0"?>
+<!DOCTYPE Repository PUBLIC "Jackrabbit 2.6" "http://jackrabbit.apache.org/dtd/repository-2.6.dtd">
+<Repository>
+ <!-- Shared datasource -->
+ <DataSources>
+ <DataSource name="dataSource">
+ <param name="driver" value="org.postgresql.Driver" />
+ <param name="url" value="${dburl}" />
+ <param name="user" value="${dbuser}" />
+ <param name="password" value="${dbpassword}" />
+ <param name="databaseType" value="postgresql" />
+ <param name="maxPoolSize" value="${maxPoolSize}" />
+ </DataSource>
+ </DataSources>
+
+ <!-- File system and datastore -->
+ <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
+ <param name="dataSourceName" value="dataSource" />
+ <param name="schema" value="postgresql" />
+ <param name="schemaObjectPrefix" value="fs_" />
+ </FileSystem>
+
+ <!-- Workspace templates -->
+ <Workspaces rootPath="${rep.home}/workspaces"
+ defaultWorkspace="${defaultWorkspace}" />
+ <Workspace name="${wsp.name}">
+ <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
+ <param name="dataSourceName" value="dataSource" />
+ <param name="schema" value="postgresql" />
+ <param name="schemaObjectPrefix" value="${wsp.name}_fs_" />
+ </FileSystem>
+ <PersistenceManager
+ class="org.apache.jackrabbit.core.persistence.pool.PostgreSQLPersistenceManager">
+ <param name="dataSourceName" value="dataSource" />
+ <param name="schemaObjectPrefix" value="${wsp.name}_pm_" />
+ <param name="bundleCacheSize" value="${bundleCacheMB}" />
+ </PersistenceManager>
+ <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
+ <param name="path" value="${wsp.home}/index" />
+ <param name="extractorPoolSize" value="${extractorPoolSize}" />
+ <param name="cacheSize" value="${searchCacheSize}" />
+ <param name="maxVolatileIndexSize" value="${maxVolatileIndexSize}" />
+ </SearchIndex>
+ <WorkspaceSecurity>
+ <AccessControlProvider
+ class="org.argeo.security.jackrabbit.ArgeoAccessControlProvider" />
+ </WorkspaceSecurity>
+ </Workspace>
+
+ <!-- Versioning -->
+ <Versioning rootPath="${rep.home}/version">
+ <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
+ <param name="dataSourceName" value="dataSource" />
+ <param name="schema" value="postgresql" />
+ <param name="schemaObjectPrefix" value="fs_ver_" />
+ </FileSystem>
+ <PersistenceManager
+ class="org.apache.jackrabbit.core.persistence.pool.PostgreSQLPersistenceManager">
+ <param name="dataSourceName" value="dataSource" />
+ <param name="schemaObjectPrefix" value="pm_ver_" />
+ <param name="bundleCacheSize" value="${bundleCacheMB}" />
+ </PersistenceManager>
+ </Versioning>
+
+ <!-- Indexing -->
+ <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
+ <param name="path" value="${rep.home}/index" />
+ <param name="extractorPoolSize" value="${extractorPoolSize}" />
+ <param name="cacheSize" value="${searchCacheSize}" />
+ <param name="maxVolatileIndexSize" value="${maxVolatileIndexSize}" />
+ </SearchIndex>
+
+ <!-- Security -->
+ <Security appName="Jackrabbit">
+ <SecurityManager class="org.argeo.security.jackrabbit.ArgeoSecurityManager"
+ workspaceName="security" />
+ <AccessManager class="org.argeo.security.jackrabbit.ArgeoAccessManager" />
+ </Security>
+</Repository>
\ No newline at end of file
--- /dev/null
+package org.argeo.jackrabbit.security;
+
+import java.security.Principal;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.security.Privilege;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.jackrabbit.api.security.JackrabbitAccessControlList;
+import org.apache.jackrabbit.api.security.JackrabbitAccessControlManager;
+import org.argeo.jcr.JcrUtils;
+
+/** Utilities around Jackrabbit security extensions. */
+public class JackrabbitSecurityUtils {
+ private final static Log log = LogFactory.getLog(JackrabbitSecurityUtils.class);
+
+ /**
+ * Convenience method for denying a single privilege to a principal (user or
+ * role), typically jcr:all
+ */
+ public synchronized static void denyPrivilege(Session session, String path, String principal, String privilege)
+ throws RepositoryException {
+ List<Privilege> privileges = new ArrayList<Privilege>();
+ privileges.add(session.getAccessControlManager().privilegeFromName(privilege));
+ denyPrivileges(session, path, () -> principal, privileges);
+ }
+
+ /**
+ * Deny privileges on a path to a {@link Principal}. The path must already
+ * exist. Session is saved. Synchronized to prevent concurrent modifications of
+ * the same node.
+ */
+ public synchronized static Boolean denyPrivileges(Session session, String path, Principal principal,
+ List<Privilege> privs) throws RepositoryException {
+ // make sure the session is in line with the persisted state
+ session.refresh(false);
+ JackrabbitAccessControlManager acm = (JackrabbitAccessControlManager) session.getAccessControlManager();
+ JackrabbitAccessControlList acl = (JackrabbitAccessControlList) JcrUtils.getAccessControlList(acm, path);
+
+// accessControlEntries: for (AccessControlEntry ace : acl.getAccessControlEntries()) {
+// Principal currentPrincipal = ace.getPrincipal();
+// if (currentPrincipal.getName().equals(principal.getName())) {
+// Privilege[] currentPrivileges = ace.getPrivileges();
+// if (currentPrivileges.length != privs.size())
+// break accessControlEntries;
+// for (int i = 0; i < currentPrivileges.length; i++) {
+// Privilege currP = currentPrivileges[i];
+// Privilege p = privs.get(i);
+// if (!currP.getName().equals(p.getName())) {
+// break accessControlEntries;
+// }
+// }
+// return false;
+// }
+// }
+
+ Privilege[] privileges = privs.toArray(new Privilege[privs.size()]);
+ acl.addEntry(principal, privileges, false);
+ acm.setPolicy(path, acl);
+ if (log.isDebugEnabled()) {
+ StringBuffer privBuf = new StringBuffer();
+ for (Privilege priv : privs)
+ privBuf.append(priv.getName());
+ log.debug("Denied privileges " + privBuf + " to " + principal.getName() + " on " + path + " in '"
+ + session.getWorkspace().getName() + "'");
+ }
+ session.refresh(true);
+ session.save();
+ return true;
+ }
+
+ /** Singleton. */
+ private JackrabbitSecurityUtils() {
+
+ }
+}
--- /dev/null
+/** Generic Jackrabbit security utilities. */
+package org.argeo.jackrabbit.security;
\ No newline at end of file
--- /dev/null
+package org.argeo.jackrabbit.unit;
+
+import java.net.URL;
+
+import javax.jcr.Repository;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.jackrabbit.core.RepositoryImpl;
+import org.apache.jackrabbit.core.config.RepositoryConfig;
+import org.argeo.jcr.unit.AbstractJcrTestCase;
+
+/** Factorizes configuration of an in memory transient repository */
+public abstract class AbstractJackrabbitTestCase extends AbstractJcrTestCase {
+ protected RepositoryImpl repositoryImpl;
+
+ // protected File getRepositoryFile() throws Exception {
+ // Resource res = new ClassPathResource(
+ // "org/argeo/jackrabbit/unit/repository-memory.xml");
+ // return res.getFile();
+ // }
+
+ public AbstractJackrabbitTestCase() {
+ URL url = AbstractJackrabbitTestCase.class.getResource("jaas.config");
+ assert url != null;
+ System.setProperty("java.security.auth.login.config", url.toString());
+ }
+
+ protected Repository createRepository() throws Exception {
+ // Repository repository = new TransientRepository(getRepositoryFile(),
+ // getHomeDir());
+ RepositoryConfig repositoryConfig = RepositoryConfig.create(
+ AbstractJackrabbitTestCase.class
+ .getResourceAsStream(getRepositoryConfigResource()),
+ getHomeDir().getAbsolutePath());
+ RepositoryImpl repositoryImpl = RepositoryImpl.create(repositoryConfig);
+ return repositoryImpl;
+ }
+
+ protected String getRepositoryConfigResource() {
+ return "repository-memory.xml";
+ }
+
+ @Override
+ protected void clearRepository(Repository repository) throws Exception {
+ RepositoryImpl repositoryImpl = (RepositoryImpl) repository;
+ if (repositoryImpl != null)
+ repositoryImpl.shutdown();
+ FileUtils.deleteDirectory(getHomeDir());
+ }
+
+}
--- /dev/null
+TEST_JACKRABBIT_ADMIN {
+ org.argeo.cms.auth.DataAdminLoginModule requisite;
+};
+
+Jackrabbit {
+ org.argeo.security.jackrabbit.SystemJackrabbitLoginModule requisite;
+};
--- /dev/null
+/** Helpers for unit tests with Jackrabbit repositories. */
+package org.argeo.jackrabbit.unit;
\ No newline at end of file
--- /dev/null
+<?xml version="1.0"?>
+<!DOCTYPE Repository PUBLIC "-//The Apache Software Foundation//DTD Jackrabbit 1.6//EN"
+ "http://jackrabbit.apache.org/dtd/repository-2.0.dtd">
+<Repository>
+ <!-- Shared datasource -->
+ <DataSources>
+ <DataSource name="dataSource">
+ <param name="driver" value="org.h2.Driver" />
+ <param name="url" value="jdbc:h2:mem:jackrabbit" />
+ <param name="user" value="sa" />
+ <param name="password" value="" />
+ <param name="databaseType" value="h2" />
+ <param name="maxPoolSize" value="10" />
+ </DataSource>
+ </DataSources>
+
+ <!-- File system and datastore -->
+ <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
+ <param name="dataSourceName" value="dataSource" />
+ <param name="schema" value="default" />
+ <param name="schemaObjectPrefix" value="fs_" />
+ </FileSystem>
+ <DataStore class="org.apache.jackrabbit.core.data.db.DbDataStore">
+ <param name="dataSourceName" value="dataSource" />
+ <param name="schemaObjectPrefix" value="ds_" />
+ </DataStore>
+
+ <!-- Workspace templates -->
+ <Workspaces rootPath="${rep.home}/workspaces"
+ defaultWorkspace="dev" />
+ <Workspace name="${wsp.name}">
+ <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
+ <param name="dataSourceName" value="dataSource" />
+ <param name="schema" value="default" />
+ <param name="schemaObjectPrefix" value="${wsp.name}_fs_" />
+ </FileSystem>
+ <PersistenceManager
+ class="org.apache.jackrabbit.core.persistence.pool.H2PersistenceManager">
+ <param name="dataSourceName" value="dataSource" />
+ <param name="schemaObjectPrefix" value="${wsp.name}_pm_" />
+ </PersistenceManager>
+ <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
+ <param name="path" value="${wsp.home}/index" />
+ </SearchIndex>
+ </Workspace>
+
+ <!-- Versioning -->
+ <Versioning rootPath="${rep.home}/version">
+ <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
+ <param name="dataSourceName" value="dataSource" />
+ <param name="schema" value="default" />
+ <param name="schemaObjectPrefix" value="fs_ver_" />
+ </FileSystem>
+ <PersistenceManager
+ class="org.apache.jackrabbit.core.persistence.pool.H2PersistenceManager">
+ <param name="dataSourceName" value="dataSource" />
+ <param name="schemaObjectPrefix" value="pm_ver_" />
+ </PersistenceManager>
+ </Versioning>
+
+ <!-- Indexing -->
+ <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
+ <param name="path" value="${rep.home}/repository/index" />
+ <param name="extractorPoolSize" value="2" />
+ <param name="supportHighlighting" value="true" />
+ </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
--- /dev/null
+<?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.mem.MemoryFileSystem" />
+
+ <!-- Workspace templates -->
+ <Workspaces rootPath="${rep.home}/workspaces"
+ defaultWorkspace="main" configRootPath="/workspaces" />
+ <Workspace name="${wsp.name}">
+ <FileSystem class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
+ <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" />
+ <param name="directoryManagerClass"
+ value="org.apache.jackrabbit.core.query.lucene.directory.RAMDirectoryManager" />
+ <FileSystem class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
+ </SearchIndex>
+ </Workspace>
+
+ <!-- Versioning -->
+ <Versioning rootPath="${rep.home}/version">
+ <FileSystem class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
+ <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="directoryManagerClass"
+ value="org.apache.jackrabbit.core.query.lucene.directory.RAMDirectoryManager" />
+ <FileSystem class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
+ </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
--- /dev/null
+package org.argeo.jcr;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.jcr.Binary;
+import javax.jcr.Property;
+import javax.jcr.RepositoryException;
+
+/**
+ * A {@link Binary} wrapper implementing {@link AutoCloseable} for ease of use
+ * in try/catch blocks.
+ */
+public class Bin implements Binary, AutoCloseable {
+ private final Binary wrappedBinary;
+
+ public Bin(Property property) throws RepositoryException {
+ this(property.getBinary());
+ }
+
+ public Bin(Binary wrappedBinary) {
+ if (wrappedBinary == null)
+ throw new IllegalArgumentException("Wrapped binary cannot be null");
+ this.wrappedBinary = wrappedBinary;
+ }
+
+ // private static Binary getBinary(Property property) throws IOException {
+ // try {
+ // return property.getBinary();
+ // } catch (RepositoryException e) {
+ // throw new IOException("Cannot get binary from property " + property, e);
+ // }
+ // }
+
+ @Override
+ public void close() {
+ dispose();
+ }
+
+ @Override
+ public InputStream getStream() throws RepositoryException {
+ return wrappedBinary.getStream();
+ }
+
+ @Override
+ public int read(byte[] b, long position) throws IOException, RepositoryException {
+ return wrappedBinary.read(b, position);
+ }
+
+ @Override
+ public long getSize() throws RepositoryException {
+ return wrappedBinary.getSize();
+ }
+
+ @Override
+ public void dispose() {
+ wrappedBinary.dispose();
+ }
+
+}
--- /dev/null
+package org.argeo.jcr;
+
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+
+/** Wraps a collection of nodes in order to read it as a {@link NodeIterator} */
+public class CollectionNodeIterator implements NodeIterator {
+ private final Long collectionSize;
+ private final Iterator<Node> iterator;
+ private Integer position = 0;
+
+ public CollectionNodeIterator(Collection<Node> nodes) {
+ super();
+ this.collectionSize = (long) nodes.size();
+ this.iterator = nodes.iterator();
+ }
+
+ public void skip(long skipNum) {
+ if (skipNum < 0)
+ throw new IllegalArgumentException(
+ "Skip count has to be positive: " + skipNum);
+
+ for (long i = 0; i < skipNum; i++) {
+ if (!hasNext())
+ throw new NoSuchElementException("Last element past (position="
+ + getPosition() + ")");
+ nextNode();
+ }
+ }
+
+ public long getSize() {
+ return collectionSize;
+ }
+
+ public long getPosition() {
+ return position;
+ }
+
+ public boolean hasNext() {
+ return iterator.hasNext();
+ }
+
+ public Object next() {
+ return nextNode();
+ }
+
+ public void remove() {
+ iterator.remove();
+ }
+
+ public Node nextNode() {
+ Node node = iterator.next();
+ position++;
+ return node;
+ }
+
+}
--- /dev/null
+package org.argeo.jcr;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.observation.Event;
+import javax.jcr.observation.EventIterator;
+import javax.jcr.observation.EventListener;
+import javax.jcr.observation.ObservationManager;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/** To be overridden */
+public class DefaultJcrListener implements EventListener {
+ private final static Log log = LogFactory.getLog(DefaultJcrListener.class);
+ private Session session;
+ private String path = "/";
+ private Boolean deep = true;
+
+ public void start() {
+ try {
+ addEventListener(session().getWorkspace().getObservationManager());
+ if (log.isDebugEnabled())
+ log.debug("Registered JCR event listener on " + path);
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot register event listener", e);
+ }
+ }
+
+ public void stop() {
+ try {
+ session().getWorkspace().getObservationManager()
+ .removeEventListener(this);
+ if (log.isDebugEnabled())
+ log.debug("Unregistered JCR event listener on " + path);
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot unregister event listener", e);
+ }
+ }
+
+ /** Default is listen to all events */
+ protected Integer getEvents() {
+ return Event.NODE_ADDED | Event.NODE_REMOVED | Event.PROPERTY_ADDED
+ | Event.PROPERTY_CHANGED | Event.PROPERTY_REMOVED;
+ }
+
+ /** To be overidden */
+ public void onEvent(EventIterator events) {
+ while (events.hasNext()) {
+ Event event = events.nextEvent();
+ log.debug(event);
+ }
+ }
+
+ /** To be overidden */
+ protected void addEventListener(ObservationManager observationManager)
+ throws RepositoryException {
+ observationManager.addEventListener(this, getEvents(), path, deep,
+ null, null, false);
+ }
+
+ private Session session() {
+ return session;
+ }
+
+ public void setPath(String path) {
+ this.path = path;
+ }
+
+ public void setDeep(Boolean deep) {
+ this.deep = deep;
+ }
+
+ public void setSession(Session session) {
+ this.session = session;
+ }
+
+}
--- /dev/null
+package org.argeo.jcr;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.math.BigDecimal;
+import java.text.MessageFormat;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.jcr.Binary;
+import javax.jcr.ItemNotFoundException;
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.Property;
+import javax.jcr.PropertyType;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.Value;
+import javax.jcr.Workspace;
+import javax.jcr.nodetype.NodeType;
+import javax.jcr.query.Query;
+import javax.jcr.query.QueryManager;
+import javax.jcr.security.Privilege;
+import javax.jcr.version.Version;
+import javax.jcr.version.VersionHistory;
+import javax.jcr.version.VersionIterator;
+import javax.jcr.version.VersionManager;
+
+import org.apache.commons.io.IOUtils;
+
+/**
+ * Utility class whose purpose is to make using JCR less verbose by
+ * systematically using unchecked exceptions and returning <code>null</code>
+ * when something is not found. This is especially useful when writing user
+ * interfaces (such as with SWT) where listeners and callbacks expect unchecked
+ * exceptions. Loosely inspired by Java's <code>Files</code> singleton.
+ */
+public class Jcr {
+ /**
+ * The name of a node which will be serialized as XML text, as per section 7.3.1
+ * of the JCR 2.0 specifications.
+ */
+ public final static String JCR_XMLTEXT = "jcr:xmltext";
+ /**
+ * The name of a property which will be serialized as XML text, as per section
+ * 7.3.1 of the JCR 2.0 specifications.
+ */
+ public final static String JCR_XMLCHARACTERS = "jcr:xmlcharacters";
+ /**
+ * <code>jcr:name</code>, when used in another context than
+ * {@link Property#JCR_NAME}, typically to name a node rather than a property.
+ */
+ public final static String JCR_NAME = "jcr:name";
+ /**
+ * <code>jcr:path</code>, when used in another context than
+ * {@link Property#JCR_PATH}, typically to name a node rather than a property.
+ */
+ public final static String JCR_PATH = "jcr:path";
+ /**
+ * <code>jcr:primaryType</code> with prefix instead of namespace (as in
+ * {@link Property#JCR_PRIMARY_TYPE}.
+ */
+ public final static String JCR_PRIMARY_TYPE = "jcr:primaryType";
+ /**
+ * <code>jcr:mixinTypes</code> with prefix instead of namespace (as in
+ * {@link Property#JCR_MIXIN_TYPES}.
+ */
+ public final static String JCR_MIXIN_TYPES = "jcr:mixinTypes";
+ /**
+ * <code>jcr:uuid</code> with prefix instead of namespace (as in
+ * {@link Property#JCR_UUID}.
+ */
+ public final static String JCR_UUID = "jcr:uuid";
+ /**
+ * <code>jcr:created</code> with prefix instead of namespace (as in
+ * {@link Property#JCR_CREATED}.
+ */
+ public final static String JCR_CREATED = "jcr:created";
+ /**
+ * <code>jcr:createdBy</code> with prefix instead of namespace (as in
+ * {@link Property#JCR_CREATED_BY}.
+ */
+ public final static String JCR_CREATED_BY = "jcr:createdBy";
+ /**
+ * <code>jcr:lastModified</code> with prefix instead of namespace (as in
+ * {@link Property#JCR_LAST_MODIFIED}.
+ */
+ public final static String JCR_LAST_MODIFIED = "jcr:lastModified";
+ /**
+ * <code>jcr:lastModifiedBy</code> with prefix instead of namespace (as in
+ * {@link Property#JCR_LAST_MODIFIED_BY}.
+ */
+ public final static String JCR_LAST_MODIFIED_BY = "jcr:lastModifiedBy";
+
+ /**
+ * @see Node#isNodeType(String)
+ * @throws JcrException caused by {@link RepositoryException}
+ */
+ public static boolean isNodeType(Node node, String nodeTypeName) {
+ try {
+ return node.isNodeType(nodeTypeName);
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot get whether " + node + " is of type " + nodeTypeName, e);
+ }
+ }
+
+ /**
+ * @see Node#hasNodes()
+ * @throws JcrException caused by {@link RepositoryException}
+ */
+ public static boolean hasNodes(Node node) {
+ try {
+ return node.hasNodes();
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot get whether " + node + " has children.", e);
+ }
+ }
+
+ /**
+ * @see Node#getParent()
+ * @throws JcrException caused by {@link RepositoryException}
+ */
+ public static Node getParent(Node node) {
+ try {
+ return isRoot(node) ? null : node.getParent();
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot get parent of " + node, e);
+ }
+ }
+
+ /**
+ * @see Node#getParent()
+ * @throws JcrException caused by {@link RepositoryException}
+ */
+ public static String getParentPath(Node node) {
+ return getPath(getParent(node));
+ }
+
+ /**
+ * Whether this node is the root node.
+ *
+ * @throws JcrException caused by {@link RepositoryException}
+ */
+ public static boolean isRoot(Node node) {
+ try {
+ return node.getDepth() == 0;
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot get depth of " + node, e);
+ }
+ }
+
+ /**
+ * @see Node#getPath()
+ * @throws JcrException caused by {@link RepositoryException}
+ */
+ public static String getPath(Node node) {
+ try {
+ return node.getPath();
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot get path of " + node, e);
+ }
+ }
+
+ /**
+ * @see Node#getSession()
+ * @see Session#getWorkspace()
+ * @see Workspace#getName()
+ */
+ public static String getWorkspaceName(Node node) {
+ return session(node).getWorkspace().getName();
+ }
+
+ /**
+ * @see Node#getIdentifier()
+ * @throws JcrException caused by {@link RepositoryException}
+ */
+ public static String getIdentifier(Node node) {
+ try {
+ return node.getIdentifier();
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot get identifier of " + node, e);
+ }
+ }
+
+ /**
+ * @see Node#getName()
+ * @throws JcrException caused by {@link RepositoryException}
+ */
+ public static String getName(Node node) {
+ try {
+ return node.getName();
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot get name of " + node, e);
+ }
+ }
+
+ /**
+ * Returns the node name with its current index (useful for re-ordering).
+ *
+ * @see Node#getName()
+ * @see Node#getIndex()
+ * @throws JcrException caused by {@link RepositoryException}
+ */
+ public static String getIndexedName(Node node) {
+ try {
+ return node.getName() + "[" + node.getIndex() + "]";
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot get name of " + node, e);
+ }
+ }
+
+ /**
+ * @see Node#getProperty(String)
+ * @throws JcrException caused by {@link RepositoryException}
+ */
+ public static Property getProperty(Node node, String property) {
+ try {
+ if (node.hasProperty(property))
+ return node.getProperty(property);
+ else
+ return null;
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot get property " + property + " of " + node, e);
+ }
+ }
+
+ /**
+ * @see Node#getIndex()
+ * @throws JcrException caused by {@link RepositoryException}
+ */
+ public static int getIndex(Node node) {
+ try {
+ return node.getIndex();
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot get index of " + node, e);
+ }
+ }
+
+ /**
+ * If node has mixin {@link NodeType#MIX_TITLE}, return
+ * {@link Property#JCR_TITLE}, otherwise return {@link #getName(Node)}.
+ */
+ public static String getTitle(Node node) {
+ if (Jcr.isNodeType(node, NodeType.MIX_TITLE))
+ return get(node, Property.JCR_TITLE);
+ else
+ return Jcr.getName(node);
+ }
+
+ /** Accesses a {@link NodeIterator} as an {@link Iterable}. */
+ @SuppressWarnings("unchecked")
+ public static Iterable<Node> iterate(NodeIterator nodeIterator) {
+ return new Iterable<Node>() {
+
+ @Override
+ public Iterator<Node> iterator() {
+ return nodeIterator;
+ }
+ };
+ }
+
+ /**
+ * @return the children as an {@link Iterable} for use in for-each llops.
+ * @see Node#getNodes()
+ * @throws JcrException caused by {@link RepositoryException}
+ */
+ public static Iterable<Node> nodes(Node node) {
+ try {
+ return iterate(node.getNodes());
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot get children of " + node, e);
+ }
+ }
+
+ /**
+ * @return the children as a (possibly empty) {@link List}.
+ * @see Node#getNodes()
+ * @throws JcrException caused by {@link RepositoryException}
+ */
+ public static List<Node> getNodes(Node node) {
+ List<Node> nodes = new ArrayList<>();
+ try {
+ if (node.hasNodes()) {
+ NodeIterator nit = node.getNodes();
+ while (nit.hasNext())
+ nodes.add(nit.nextNode());
+ return nodes;
+ } else
+ return nodes;
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot get children of " + node, e);
+ }
+ }
+
+ /**
+ * @return the child or <code>null</node> if not found
+ * @see Node#getNode(String)
+ * @throws JcrException caused by {@link RepositoryException}
+ */
+ public static Node getNode(Node node, String child) {
+ try {
+ if (node.hasNode(child))
+ return node.getNode(child);
+ else
+ return null;
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot get child of " + node, e);
+ }
+ }
+
+ /**
+ * @return the node at this path or <code>null</node> if not found
+ * @see Session#getNode(String)
+ * @throws JcrException caused by {@link RepositoryException}
+ */
+ public static Node getNode(Session session, String path) {
+ try {
+ if (session.nodeExists(path))
+ return session.getNode(path);
+ else
+ return null;
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot get node " + path, e);
+ }
+ }
+
+ /**
+ * Add a node to this parent, setting its primary type and its mixins.
+ *
+ * @param parent the parent node
+ * @param name the name of the node, if <code>null</code>, the primary
+ * type will be used (typically for XML structures)
+ * @param primaryType the primary type, if <code>null</code>
+ * {@link NodeType#NT_UNSTRUCTURED} will be used.
+ * @param mixins the mixins
+ * @return the created node
+ * @see Node#addNode(String, String)
+ * @see Node#addMixin(String)
+ */
+ public static Node addNode(Node parent, String name, String primaryType, String... mixins) {
+ if (name == null && primaryType == null)
+ throw new IllegalArgumentException("Both node name and primary type cannot be null");
+ try {
+ Node newNode = parent.addNode(name == null ? primaryType : name,
+ primaryType == null ? NodeType.NT_UNSTRUCTURED : primaryType);
+ for (String mixin : mixins) {
+ newNode.addMixin(mixin);
+ }
+ return newNode;
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot add node " + name + " to " + parent, e);
+ }
+ }
+
+ /**
+ * Add an {@link NodeType#NT_BASE} node to this parent.
+ *
+ * @param parent the parent node
+ * @param name the name of the node, cannot be <code>null</code>
+ * @return the created node
+ *
+ * @see Node#addNode(String)
+ */
+ public static Node addNode(Node parent, String name) {
+ if (name == null)
+ throw new IllegalArgumentException("Node name cannot be null");
+ try {
+ Node newNode = parent.addNode(name);
+ return newNode;
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot add node " + name + " to " + parent, e);
+ }
+ }
+
+ /**
+ * Add mixins to a node.
+ *
+ * @param node the node
+ * @param mixins the mixins
+ * @return the created node
+ * @see Node#addMixin(String)
+ */
+ public static void addMixin(Node node, String... mixins) {
+ try {
+ for (String mixin : mixins) {
+ node.addMixin(mixin);
+ }
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot add mixins " + Arrays.asList(mixins) + " to " + node, e);
+ }
+ }
+
+ /**
+ * Removes this node.
+ *
+ * @see Node#remove()
+ */
+ public static void remove(Node node) {
+ try {
+ node.remove();
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot remove node " + node, e);
+ }
+ }
+
+ /**
+ * @return the node with htis id or <code>null</node> if not found
+ * @see Session#getNodeByIdentifier(String)
+ * @throws JcrException caused by {@link RepositoryException}
+ */
+ public static Node getNodeById(Session session, String id) {
+ try {
+ return session.getNodeByIdentifier(id);
+ } catch (ItemNotFoundException e) {
+ return null;
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot get node with id " + id, e);
+ }
+ }
+
+ /**
+ * Set a property to the given value, or remove it if the value is
+ * <code>null</code>.
+ *
+ * @throws JcrException caused by {@link RepositoryException}
+ */
+ public static void set(Node node, String property, Object value) {
+ try {
+ if (!node.hasProperty(property)) {
+ if (value != null) {
+ if (value instanceof List) {// multiple
+ List<?> lst = (List<?>) value;
+ String[] values = new String[lst.size()];
+ for (int i = 0; i < lst.size(); i++) {
+ values[i] = lst.get(i).toString();
+ }
+ node.setProperty(property, values);
+ } else {
+ node.setProperty(property, value.toString());
+ }
+ }
+ return;
+ }
+ Property prop = node.getProperty(property);
+ if (value == null) {
+ prop.remove();
+ return;
+ }
+
+ // multiple
+ if (value instanceof List) {
+ List<?> lst = (List<?>) value;
+ String[] values = new String[lst.size()];
+ // TODO better cast?
+ for (int i = 0; i < lst.size(); i++) {
+ values[i] = lst.get(i).toString();
+ }
+ if (!prop.isMultiple())
+ prop.remove();
+ node.setProperty(property, values);
+ return;
+ }
+
+ // single
+ if (prop.isMultiple()) {
+ prop.remove();
+ node.setProperty(property, value.toString());
+ return;
+ }
+
+ if (value instanceof String)
+ prop.setValue((String) value);
+ else if (value instanceof Long)
+ prop.setValue((Long) value);
+ else if (value instanceof Integer)
+ prop.setValue(((Integer) value).longValue());
+ else if (value instanceof Double)
+ prop.setValue((Double) value);
+ else if (value instanceof Float)
+ prop.setValue(((Float) value).doubleValue());
+ else if (value instanceof Calendar)
+ prop.setValue((Calendar) value);
+ else if (value instanceof BigDecimal)
+ prop.setValue((BigDecimal) value);
+ else if (value instanceof Boolean)
+ prop.setValue((Boolean) value);
+ else if (value instanceof byte[])
+ JcrUtils.setBinaryAsBytes(prop, (byte[]) value);
+ else if (value instanceof Instant) {
+ Instant instant = (Instant) value;
+ GregorianCalendar calendar = new GregorianCalendar();
+ calendar.setTime(Date.from(instant));
+ prop.setValue(calendar);
+ } else // try with toString()
+ prop.setValue(value.toString());
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot set property " + property + " of " + node + " to " + value, e);
+ }
+ }
+
+ /**
+ * Get property as {@link String}.
+ *
+ * @return the value of
+ * {@link Node#getProperty(String)}.{@link Property#getString()} or
+ * <code>null</code> if the property does not exist.
+ * @throws JcrException caused by {@link RepositoryException}
+ */
+ public static String get(Node node, String property) {
+ return get(node, property, null);
+ }
+
+ /**
+ * Get property as a {@link String}. If the property is multiple it returns the
+ * first value.
+ *
+ * @return the value of
+ * {@link Node#getProperty(String)}.{@link Property#getString()} or
+ * <code>defaultValue</code> if the property does not exist.
+ * @throws JcrException caused by {@link RepositoryException}
+ */
+ public static String get(Node node, String property, String defaultValue) {
+ try {
+ if (node.hasProperty(property)) {
+ Property p = node.getProperty(property);
+ if (!p.isMultiple())
+ return p.getString();
+ else {
+ Value[] values = p.getValues();
+ if (values.length == 0)
+ return defaultValue;
+ else
+ return values[0].getString();
+ }
+ } else
+ return defaultValue;
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot retrieve property " + property + " from " + node, e);
+ }
+ }
+
+ /**
+ * Get property as a {@link Value}.
+ *
+ * @return {@link Node#getProperty(String)} or <code>null</code> if the property
+ * does not exist.
+ * @throws JcrException caused by {@link RepositoryException}
+ */
+ public static Value getValue(Node node, String property) {
+ try {
+ if (node.hasProperty(property))
+ return node.getProperty(property).getValue();
+ else
+ return null;
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot retrieve property " + property + " from " + node, e);
+ }
+ }
+
+ /**
+ * Get property doing a best effort to cast it as the target object.
+ *
+ * @return the value of {@link Node#getProperty(String)} or
+ * <code>defaultValue</code> if the property does not exist.
+ * @throws IllegalArgumentException if the value could not be cast
+ * @throws JcrException in case of unexpected
+ * {@link RepositoryException}
+ */
+ @SuppressWarnings("unchecked")
+ public static <T> T getAs(Node node, String property, T defaultValue) {
+ try {
+ // TODO deal with multiple
+ if (node.hasProperty(property)) {
+ Property p = node.getProperty(property);
+ try {
+ if (p.isMultiple()) {
+ throw new UnsupportedOperationException("Multiple values properties are not supported");
+ }
+ Value value = p.getValue();
+ return (T) get(value);
+ } catch (ClassCastException e) {
+ throw new IllegalArgumentException(
+ "Cannot cast property of type " + PropertyType.nameFromValue(p.getType()), e);
+ }
+ } else {
+ return defaultValue;
+ }
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot retrieve property " + property + " from " + node, e);
+ }
+ }
+
+ /**
+ * Get a multiple property as a list, doing a best effort to cast it as the
+ * target list.
+ *
+ * @return the value of {@link Node#getProperty(String)}.
+ * @throws IllegalArgumentException if the value could not be cast
+ * @throws JcrException in case of unexpected
+ * {@link RepositoryException}
+ */
+ public static <T> List<T> getMultiple(Node node, String property) {
+ try {
+ if (node.hasProperty(property)) {
+ Property p = node.getProperty(property);
+ return getMultiple(p);
+ } else {
+ return null;
+ }
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot retrieve multiple values property " + property + " from " + node, e);
+ }
+ }
+
+ /**
+ * Get a multiple property as a list, doing a best effort to cast it as the
+ * target list.
+ */
+ @SuppressWarnings("unchecked")
+ public static <T> List<T> getMultiple(Property p) {
+ try {
+ List<T> res = new ArrayList<>();
+ if (!p.isMultiple()) {
+ res.add((T) get(p.getValue()));
+ return res;
+ }
+ Value[] values = p.getValues();
+ for (Value value : values) {
+ res.add((T) get(value));
+ }
+ return res;
+ } catch (ClassCastException | RepositoryException e) {
+ throw new IllegalArgumentException("Cannot get property " + p, e);
+ }
+ }
+
+ /** Cast a {@link Value} to a standard Java object. */
+ public static Object get(Value value) {
+ Binary binary = null;
+ try {
+ switch (value.getType()) {
+ case PropertyType.STRING:
+ return value.getString();
+ case PropertyType.DOUBLE:
+ return (Double) value.getDouble();
+ case PropertyType.LONG:
+ return (Long) value.getLong();
+ case PropertyType.BOOLEAN:
+ return (Boolean) value.getBoolean();
+ case PropertyType.DATE:
+ return value.getDate();
+ case PropertyType.BINARY:
+ binary = value.getBinary();
+ byte[] arr = null;
+ try (InputStream in = binary.getStream(); ByteArrayOutputStream out = new ByteArrayOutputStream();) {
+ IOUtils.copy(in, out);
+ arr = out.toByteArray();
+ } catch (IOException e) {
+ throw new RuntimeException("Cannot read binary from " + value, e);
+ }
+ return arr;
+ default:
+ return value.getString();
+ }
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot cast value from " + value, e);
+ } finally {
+ if (binary != null)
+ binary.dispose();
+ }
+ }
+
+ /**
+ * Retrieves the {@link Session} related to this node.
+ *
+ * @deprecated Use {@link #getSession(Node)} instead.
+ */
+ @Deprecated
+ public static Session session(Node node) {
+ return getSession(node);
+ }
+
+ /** Retrieves the {@link Session} related to this node. */
+ public static Session getSession(Node node) {
+ try {
+ return node.getSession();
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot retrieve session related to " + node, e);
+ }
+ }
+
+ /** Retrieves the root node related to this session. */
+ public static Node getRootNode(Session session) {
+ try {
+ return session.getRootNode();
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot get root node for " + session, e);
+ }
+ }
+
+ /** Whether this item exists. */
+ public static boolean itemExists(Session session, String path) {
+ try {
+ return session.itemExists(path);
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot check whether " + path + " exists", e);
+ }
+ }
+
+ /**
+ * Saves the {@link Session} related to this node. Note that all other unrelated
+ * modifications in this session will also be saved.
+ */
+ public static void save(Node node) {
+ try {
+ Session session = node.getSession();
+// if (node.isNodeType(NodeType.MIX_LAST_MODIFIED)) {
+// set(node, Property.JCR_LAST_MODIFIED, Instant.now());
+// set(node, Property.JCR_LAST_MODIFIED_BY, session.getUserID());
+// }
+ if (session.hasPendingChanges())
+ session.save();
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot save session related to " + node + " in workspace "
+ + session(node).getWorkspace().getName(), e);
+ }
+ }
+
+ /** Login to a JCR repository. */
+ public static Session login(Repository repository, String workspace) {
+ try {
+ return repository.login(workspace);
+ } catch (RepositoryException e) {
+ throw new IllegalArgumentException("Cannot login to repository", e);
+ }
+ }
+
+ /** Safely and silently logs out a session. */
+ public static void logout(Session session) {
+ try {
+ if (session != null)
+ if (session.isLive())
+ session.logout();
+ } catch (Exception e) {
+ // silent
+ }
+ }
+
+ /** Safely and silently logs out the underlying session. */
+ public static void logout(Node node) {
+ Jcr.logout(session(node));
+ }
+
+ /*
+ * SECURITY
+ */
+ /**
+ * Add a single privilege to a node.
+ *
+ * @see Privilege
+ */
+ public static void addPrivilege(Node node, String principal, String privilege) {
+ try {
+ Session session = node.getSession();
+ JcrUtils.addPrivilege(session, node.getPath(), principal, privilege);
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot add privilege " + privilege + " to " + node, e);
+ }
+ }
+
+ /*
+ * VERSIONING
+ */
+ /** Get checked out status. */
+ public static boolean isCheckedOut(Node node) {
+ try {
+ return node.isCheckedOut();
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot retrieve checked out status of " + node, e);
+ }
+ }
+
+ /** @see VersionManager#checkpoint(String) */
+ public static void checkpoint(Node node) {
+ try {
+ versionManager(node).checkpoint(node.getPath());
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot check in " + node, e);
+ }
+ }
+
+ /** @see VersionManager#checkin(String) */
+ public static void checkin(Node node) {
+ try {
+ versionManager(node).checkin(node.getPath());
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot check in " + node, e);
+ }
+ }
+
+ /** @see VersionManager#checkout(String) */
+ public static void checkout(Node node) {
+ try {
+ versionManager(node).checkout(node.getPath());
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot check out " + node, e);
+ }
+ }
+
+ /** Get the {@link VersionManager} related to this node. */
+ public static VersionManager versionManager(Node node) {
+ try {
+ return node.getSession().getWorkspace().getVersionManager();
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot get version manager from " + node, e);
+ }
+ }
+
+ /** Get the {@link VersionHistory} related to this node. */
+ public static VersionHistory getVersionHistory(Node node) {
+ try {
+ return versionManager(node).getVersionHistory(node.getPath());
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot get version history from " + node, e);
+ }
+ }
+
+ /**
+ * The linear versions of this version history in reverse order and without the
+ * root version.
+ */
+ public static List<Version> getLinearVersions(VersionHistory versionHistory) {
+ try {
+ List<Version> lst = new ArrayList<>();
+ VersionIterator vit = versionHistory.getAllLinearVersions();
+ while (vit.hasNext())
+ lst.add(vit.nextVersion());
+ lst.remove(0);
+ Collections.reverse(lst);
+ return lst;
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot get linear versions from " + versionHistory, e);
+ }
+ }
+
+ /** The frozen node related to this {@link Version}. */
+ public static Node getFrozenNode(Version version) {
+ try {
+ return version.getFrozenNode();
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot get frozen node from " + version, e);
+ }
+ }
+
+ /** Get the base {@link Version} related to this node. */
+ public static Version getBaseVersion(Node node) {
+ try {
+ return versionManager(node).getBaseVersion(node.getPath());
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot get base version from " + node, e);
+ }
+ }
+
+ /*
+ * FILES
+ */
+ /**
+ * Returns the size of this file.
+ *
+ * @see NodeType#NT_FILE
+ */
+ public static long getFileSize(Node fileNode) {
+ try {
+ if (!fileNode.isNodeType(NodeType.NT_FILE))
+ throw new IllegalArgumentException(fileNode + " must be a file.");
+ return getBinarySize(fileNode.getNode(Node.JCR_CONTENT).getProperty(Property.JCR_DATA).getBinary());
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot get file size of " + fileNode, e);
+ }
+ }
+
+ /** Returns the size of this {@link Binary}. */
+ public static long getBinarySize(Binary binaryArg) {
+ try {
+ try (Bin binary = new Bin(binaryArg)) {
+ return binary.getSize();
+ }
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot get file size of binary " + binaryArg, e);
+ }
+ }
+
+ // QUERY
+ /** Creates a JCR-SQL2 query using {@link MessageFormat}. */
+ public static Query createQuery(QueryManager qm, String sql, Object... args) {
+ // fix single quotes
+ sql = sql.replaceAll("'", "''");
+ String query = MessageFormat.format(sql, args);
+ try {
+ return qm.createQuery(query, Query.JCR_SQL2);
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot create JCR-SQL2 query from " + query, e);
+ }
+ }
+
+ /** Executes a JCR-SQL2 query using {@link MessageFormat}. */
+ public static NodeIterator executeQuery(QueryManager qm, String sql, Object... args) {
+ Query query = createQuery(qm, sql, args);
+ try {
+ return query.execute().getNodes();
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot execute query " + sql + " with arguments " + Arrays.asList(args), e);
+ }
+ }
+
+ /** Executes a JCR-SQL2 query using {@link MessageFormat}. */
+ public static NodeIterator executeQuery(Session session, String sql, Object... args) {
+ QueryManager queryManager;
+ try {
+ queryManager = session.getWorkspace().getQueryManager();
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot get query manager from session " + session, e);
+ }
+ return executeQuery(queryManager, sql, args);
+ }
+
+ /**
+ * Executes a JCR-SQL2 query using {@link MessageFormat}, which must return a
+ * single node at most.
+ *
+ * @return the node or <code>null</code> if not found.
+ */
+ public static Node getNode(QueryManager qm, String sql, Object... args) {
+ NodeIterator nit = executeQuery(qm, sql, args);
+ if (nit.hasNext()) {
+ Node node = nit.nextNode();
+ if (nit.hasNext())
+ throw new IllegalStateException(
+ "Query " + sql + " with arguments " + Arrays.asList(args) + " returned more than one node.");
+ return node;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Executes a JCR-SQL2 query using {@link MessageFormat}, which must return a
+ * single node at most.
+ *
+ * @return the node or <code>null</code> if not found.
+ */
+ public static Node getNode(Session session, String sql, Object... args) {
+ QueryManager queryManager;
+ try {
+ queryManager = session.getWorkspace().getQueryManager();
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot get query manager from session " + session, e);
+ }
+ return getNode(queryManager, sql, args);
+ }
+
+ /** Singleton. */
+ private Jcr() {
+
+ }
+}
--- /dev/null
+package org.argeo.jcr;
+
+import java.security.Principal;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.security.AccessControlManager;
+import javax.jcr.security.Privilege;
+import javax.naming.InvalidNameException;
+import javax.naming.ldap.LdapName;
+
+/** Apply authorizations to a JCR repository. */
+public class JcrAuthorizations implements Runnable {
+ // private final static Log log =
+ // LogFactory.getLog(JcrAuthorizations.class);
+
+ private Repository repository;
+ private String workspace = null;
+
+ private String securityWorkspace = "security";
+
+ /**
+ * key := privilege1,privilege2/path/to/node<br/>
+ * value := group1,group2,user1
+ */
+ private Map<String, String> principalPrivileges = new HashMap<String, String>();
+
+ public void run() {
+ String currentWorkspace = workspace;
+ Session session = null;
+ try {
+ if (workspace != null && workspace.equals("*")) {
+ session = repository.login();
+ String[] workspaces = session.getWorkspace().getAccessibleWorkspaceNames();
+ JcrUtils.logoutQuietly(session);
+ for (String wksp : workspaces) {
+ currentWorkspace = wksp;
+ if (currentWorkspace.equals(securityWorkspace))
+ continue;
+ session = repository.login(currentWorkspace);
+ initAuthorizations(session);
+ JcrUtils.logoutQuietly(session);
+ }
+ } else {
+ session = repository.login(workspace);
+ initAuthorizations(session);
+ }
+ } catch (RepositoryException e) {
+ JcrUtils.discardQuietly(session);
+ throw new JcrException(
+ "Cannot set authorizations " + principalPrivileges + " on workspace " + currentWorkspace, e);
+ } finally {
+ JcrUtils.logoutQuietly(session);
+ }
+ }
+
+ protected void processWorkspace(String workspace) {
+ Session session = null;
+ try {
+ session = repository.login(workspace);
+ initAuthorizations(session);
+ } catch (RepositoryException e) {
+ JcrUtils.discardQuietly(session);
+ throw new JcrException(
+ "Cannot set authorizations " + principalPrivileges + " on repository " + repository, e);
+ } finally {
+ JcrUtils.logoutQuietly(session);
+ }
+ }
+
+ /** @deprecated call {@link #run()} instead. */
+ @Deprecated
+ public void init() {
+ run();
+ }
+
+ protected void initAuthorizations(Session session) throws RepositoryException {
+ AccessControlManager acm = session.getAccessControlManager();
+
+ for (String privileges : principalPrivileges.keySet()) {
+ String path = null;
+ int slashIndex = privileges.indexOf('/');
+ if (slashIndex == 0) {
+ throw new IllegalArgumentException("Privilege " + privileges + " badly formatted it starts with /");
+ } else if (slashIndex > 0) {
+ path = privileges.substring(slashIndex);
+ privileges = privileges.substring(0, slashIndex);
+ }
+
+ if (path == null)
+ path = "/";
+
+ List<Privilege> privs = new ArrayList<Privilege>();
+ for (String priv : privileges.split(",")) {
+ privs.add(acm.privilegeFromName(priv));
+ }
+
+ String principalNames = principalPrivileges.get(privileges);
+ try {
+ new LdapName(principalNames);
+ // TODO differentiate groups and users ?
+ Principal principal = getOrCreatePrincipal(session, principalNames);
+ JcrUtils.addPrivileges(session, path, principal, privs);
+ } catch (InvalidNameException e) {
+ for (String principalName : principalNames.split(",")) {
+ Principal principal = getOrCreatePrincipal(session, principalName);
+ JcrUtils.addPrivileges(session, path, principal, privs);
+ // if (log.isDebugEnabled()) {
+ // StringBuffer privBuf = new StringBuffer();
+ // for (Privilege priv : privs)
+ // privBuf.append(priv.getName());
+ // log.debug("Added privileges " + privBuf + " to "
+ // + principal.getName() + " on " + path + " in '"
+ // + session.getWorkspace().getName() + "'");
+ // }
+ }
+ }
+ }
+
+ // if (log.isDebugEnabled())
+ // log.debug("JCR authorizations applied on '"
+ // + session.getWorkspace().getName() + "'");
+ }
+
+ /**
+ * Returns a {@link SimplePrincipal}, does not check whether it exists since
+ * such capabilities is not provided by the standard JCR API. Can be
+ * overridden to provide smarter handling
+ */
+ protected Principal getOrCreatePrincipal(Session session, String principalName) throws RepositoryException {
+ return new SimplePrincipal(principalName);
+ }
+
+ // public static void addPrivileges(Session session, Principal principal,
+ // String path, List<Privilege> privs) throws RepositoryException {
+ // AccessControlManager acm = session.getAccessControlManager();
+ // // search for an access control list
+ // AccessControlList acl = null;
+ // AccessControlPolicyIterator policyIterator = acm
+ // .getApplicablePolicies(path);
+ // if (policyIterator.hasNext()) {
+ // while (policyIterator.hasNext()) {
+ // AccessControlPolicy acp = policyIterator
+ // .nextAccessControlPolicy();
+ // if (acp instanceof AccessControlList)
+ // acl = ((AccessControlList) acp);
+ // }
+ // } else {
+ // AccessControlPolicy[] existingPolicies = acm.getPolicies(path);
+ // for (AccessControlPolicy acp : existingPolicies) {
+ // if (acp instanceof AccessControlList)
+ // acl = ((AccessControlList) acp);
+ // }
+ // }
+ //
+ // if (acl != null) {
+ // acl.addAccessControlEntry(principal,
+ // privs.toArray(new Privilege[privs.size()]));
+ // acm.setPolicy(path, acl);
+ // session.save();
+ // if (log.isDebugEnabled()) {
+ // StringBuffer buf = new StringBuffer("");
+ // for (int i = 0; i < privs.size(); i++) {
+ // if (i != 0)
+ // buf.append(',');
+ // buf.append(privs.get(i).getName());
+ // }
+ // log.debug("Added privilege(s) '" + buf + "' to '"
+ // + principal.getName() + "' on " + path
+ // + " from workspace '"
+ // + session.getWorkspace().getName() + "'");
+ // }
+ // } else {
+ // throw new ArgeoJcrException("Don't know how to apply privileges "
+ // + privs + " to " + principal + " on " + path
+ // + " from workspace '" + session.getWorkspace().getName()
+ // + "'");
+ // }
+ // }
+
+ @Deprecated
+ public void setGroupPrivileges(Map<String, String> groupPrivileges) {
+ this.principalPrivileges = groupPrivileges;
+ }
+
+ public void setPrincipalPrivileges(Map<String, String> principalPrivileges) {
+ this.principalPrivileges = principalPrivileges;
+ }
+
+ public void setRepository(Repository repository) {
+ this.repository = repository;
+ }
+
+ public void setWorkspace(String workspace) {
+ this.workspace = workspace;
+ }
+
+ public void setSecurityWorkspace(String securityWorkspace) {
+ this.securityWorkspace = securityWorkspace;
+ }
+
+}
--- /dev/null
+package org.argeo.jcr;
+
+import java.util.function.Function;
+
+import javax.jcr.Session;
+
+/** An arbitrary execution on a JCR session, optionally returning a result. */
+@FunctionalInterface
+public interface JcrCallback extends Function<Session, Object> {
+ /** @deprecated Use {@link #apply(Session)} instead. */
+ @Deprecated
+ public default Object execute(Session session) {
+ return apply(session);
+ }
+}
--- /dev/null
+package org.argeo.jcr;
+
+import javax.jcr.RepositoryException;
+
+/**
+ * Wraps a {@link RepositoryException} in a {@link RuntimeException}.
+ */
+public class JcrException extends IllegalStateException {
+ private static final long serialVersionUID = -4530350094877964989L;
+
+ public JcrException(String message, RepositoryException e) {
+ super(message, e);
+ }
+
+ public JcrException(RepositoryException e) {
+ super(e);
+ }
+
+ public RepositoryException getRepositoryCause() {
+ return (RepositoryException) getCause();
+ }
+}
--- /dev/null
+package org.argeo.jcr;
+
+
+/**
+ * Simple monitor abstraction. Inspired by Eclipse IProgressMOnitor, but without
+ * dependency to it.
+ */
+public interface JcrMonitor {
+ /**
+ * Constant indicating an unknown amount of work.
+ */
+ public final static int UNKNOWN = -1;
+
+ /**
+ * Notifies that the main task is beginning. This must only be called once
+ * on a given progress monitor instance.
+ *
+ * @param name
+ * the name (or description) of the main task
+ * @param totalWork
+ * the total number of work units into which the main task is
+ * been subdivided. If the value is <code>UNKNOWN</code> the
+ * implementation is free to indicate progress in a way which
+ * doesn't require the total number of work units in advance.
+ */
+ public void beginTask(String name, int totalWork);
+
+ /**
+ * Notifies that the work is done; that is, either the main task is
+ * completed or the user canceled it. This method may be called more than
+ * once (implementations should be prepared to handle this case).
+ */
+ public void done();
+
+ /**
+ * Returns whether cancelation of current operation has been requested.
+ * Long-running operations should poll to see if cancelation has been
+ * requested.
+ *
+ * @return <code>true</code> if cancellation has been requested, and
+ * <code>false</code> otherwise
+ * @see #setCanceled(boolean)
+ */
+ public boolean isCanceled();
+
+ /**
+ * Sets the cancel state to the given value.
+ *
+ * @param value
+ * <code>true</code> indicates that cancelation has been
+ * requested (but not necessarily acknowledged);
+ * <code>false</code> clears this flag
+ * @see #isCanceled()
+ */
+ public void setCanceled(boolean value);
+
+ /**
+ * Sets the task name to the given value. This method is used to restore the
+ * task label after a nested operation was executed. Normally there is no
+ * need for clients to call this method.
+ *
+ * @param name
+ * the name (or description) of the main task
+ * @see #beginTask(java.lang.String, int)
+ */
+ public void setTaskName(String name);
+
+ /**
+ * Notifies that a subtask of the main task is beginning. Subtasks are
+ * optional; the main task might not have subtasks.
+ *
+ * @param name
+ * the name (or description) of the subtask
+ */
+ public void subTask(String name);
+
+ /**
+ * Notifies that a given number of work unit of the main task has been
+ * completed. Note that this amount represents an installment, as opposed to
+ * a cumulative amount of work done to date.
+ *
+ * @param work
+ * a non-negative number of work units just completed
+ */
+ public void worked(int work);
+
+}
--- /dev/null
+package org.argeo.jcr;
+
+import java.io.InputStream;
+import java.math.BigDecimal;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.jcr.Binary;
+import javax.jcr.Credentials;
+import javax.jcr.LoginException;
+import javax.jcr.NoSuchWorkspaceException;
+import javax.jcr.PropertyType;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.Value;
+import javax.jcr.ValueFormatException;
+
+/**
+ * Wrapper around a JCR repository which allows to simplify configuration and
+ * intercept some actions. It exposes itself as a {@link Repository}.
+ */
+public abstract class JcrRepositoryWrapper implements Repository {
+ // private final static Log log = LogFactory
+ // .getLog(JcrRepositoryWrapper.class);
+
+ // wrapped repository
+ private Repository repository;
+
+ private Map<String, String> additionalDescriptors = new HashMap<>();
+
+ private Boolean autocreateWorkspaces = false;
+
+ public JcrRepositoryWrapper(Repository repository) {
+ setRepository(repository);
+ }
+
+ /**
+ * Empty constructor
+ */
+ public JcrRepositoryWrapper() {
+ }
+
+ // /** Initializes */
+ // public void init() {
+ // }
+ //
+ // /** Shutdown the repository */
+ // public void destroy() throws Exception {
+ // }
+
+ protected void putDescriptor(String key, String value) {
+ if (Arrays.asList(getRepository().getDescriptorKeys()).contains(key))
+ throw new IllegalArgumentException("Descriptor key " + key + " is already defined in wrapped repository");
+ if (value == null)
+ additionalDescriptors.remove(key);
+ else
+ additionalDescriptors.put(key, value);
+ }
+
+ /*
+ * DELEGATED JCR REPOSITORY METHODS
+ */
+
+ public String getDescriptor(String key) {
+ if (additionalDescriptors.containsKey(key))
+ return additionalDescriptors.get(key);
+ return getRepository().getDescriptor(key);
+ }
+
+ public String[] getDescriptorKeys() {
+ if (additionalDescriptors.size() == 0)
+ return getRepository().getDescriptorKeys();
+ List<String> keys = Arrays.asList(getRepository().getDescriptorKeys());
+ keys.addAll(additionalDescriptors.keySet());
+ return keys.toArray(new String[keys.size()]);
+ }
+
+ /** Central login method */
+ public Session login(Credentials credentials, String workspaceName)
+ throws LoginException, NoSuchWorkspaceException, RepositoryException {
+ Session session;
+ try {
+ session = getRepository(workspaceName).login(credentials, workspaceName);
+ } catch (NoSuchWorkspaceException e) {
+ if (autocreateWorkspaces && workspaceName != null)
+ session = createWorkspaceAndLogsIn(credentials, workspaceName);
+ else
+ throw e;
+ }
+ processNewSession(session, workspaceName);
+ return session;
+ }
+
+ public Session login() throws LoginException, RepositoryException {
+ return login(null, null);
+ }
+
+ public Session login(Credentials credentials) throws LoginException, RepositoryException {
+ return login(credentials, null);
+ }
+
+ public Session login(String workspaceName) throws LoginException, NoSuchWorkspaceException, RepositoryException {
+ return login(null, workspaceName);
+ }
+
+ /** Called after a session has been created, does nothing by default. */
+ protected void processNewSession(Session session, String workspaceName) {
+ }
+
+ /**
+ * Wraps access to the repository, making sure it is available.
+ *
+ * @deprecated Use {@link #getDefaultRepository()} instead.
+ */
+ @Deprecated
+ protected synchronized Repository getRepository() {
+ return getDefaultRepository();
+ }
+
+ protected synchronized Repository getDefaultRepository() {
+ return repository;
+ }
+
+ protected synchronized Repository getRepository(String workspaceName) {
+ return getDefaultRepository();
+ }
+
+ /**
+ * Logs in to the default workspace, creates the required workspace, logs out,
+ * logs in to the required workspace.
+ */
+ protected Session createWorkspaceAndLogsIn(Credentials credentials, String workspaceName)
+ throws RepositoryException {
+ if (workspaceName == null)
+ throw new IllegalArgumentException("No workspace specified.");
+ Session session = getRepository(workspaceName).login(credentials);
+ session.getWorkspace().createWorkspace(workspaceName);
+ session.logout();
+ return getRepository(workspaceName).login(credentials, workspaceName);
+ }
+
+ public boolean isStandardDescriptor(String key) {
+ return getRepository().isStandardDescriptor(key);
+ }
+
+ public boolean isSingleValueDescriptor(String key) {
+ if (additionalDescriptors.containsKey(key))
+ return true;
+ return getRepository().isSingleValueDescriptor(key);
+ }
+
+ public Value getDescriptorValue(String key) {
+ if (additionalDescriptors.containsKey(key))
+ return new StrValue(additionalDescriptors.get(key));
+ return getRepository().getDescriptorValue(key);
+ }
+
+ public Value[] getDescriptorValues(String key) {
+ return getRepository().getDescriptorValues(key);
+ }
+
+ public synchronized void setRepository(Repository repository) {
+ this.repository = repository;
+ }
+
+ public void setAutocreateWorkspaces(Boolean autocreateWorkspaces) {
+ this.autocreateWorkspaces = autocreateWorkspaces;
+ }
+
+ protected static class StrValue implements Value {
+ private final String str;
+
+ public StrValue(String str) {
+ this.str = str;
+ }
+
+ @Override
+ public String getString() throws ValueFormatException, IllegalStateException, RepositoryException {
+ return str;
+ }
+
+ @Override
+ public InputStream getStream() throws RepositoryException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Binary getBinary() throws RepositoryException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public long getLong() throws ValueFormatException, RepositoryException {
+ try {
+ return Long.parseLong(str);
+ } catch (NumberFormatException e) {
+ throw new ValueFormatException("Cannot convert", e);
+ }
+ }
+
+ @Override
+ public double getDouble() throws ValueFormatException, RepositoryException {
+ try {
+ return Double.parseDouble(str);
+ } catch (NumberFormatException e) {
+ throw new ValueFormatException("Cannot convert", e);
+ }
+ }
+
+ @Override
+ public BigDecimal getDecimal() throws ValueFormatException, RepositoryException {
+ try {
+ return new BigDecimal(str);
+ } catch (NumberFormatException e) {
+ throw new ValueFormatException("Cannot convert", e);
+ }
+ }
+
+ @Override
+ public Calendar getDate() throws ValueFormatException, RepositoryException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean getBoolean() throws ValueFormatException, RepositoryException {
+ try {
+ return Boolean.parseBoolean(str);
+ } catch (NumberFormatException e) {
+ throw new ValueFormatException("Cannot convert", e);
+ }
+ }
+
+ @Override
+ public int getType() {
+ return PropertyType.STRING;
+ }
+
+ }
+
+}
--- /dev/null
+package org.argeo.jcr;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLStreamHandler;
+
+import javax.jcr.Item;
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.nodetype.NodeType;
+
+/** URL stream handler able to deal with nt:file node and properties. NOT FINISHED */
+public class JcrUrlStreamHandler extends URLStreamHandler {
+ private final Session session;
+
+ public JcrUrlStreamHandler(Session session) {
+ this.session = session;
+ }
+
+ @Override
+ protected URLConnection openConnection(final URL u) throws IOException {
+ // TODO Auto-generated method stub
+ return new URLConnection(u) {
+
+ @Override
+ public void connect() throws IOException {
+ String itemPath = u.getPath();
+ try {
+ if (!session.itemExists(itemPath))
+ throw new IOException("No item under " + itemPath);
+
+ Item item = session.getItem(u.getPath());
+ if (item.isNode()) {
+ // this should be a nt:file node
+ Node node = (Node) item;
+ if (!node.getPrimaryNodeType().isNodeType(
+ NodeType.NT_FILE))
+ throw new IOException("Node " + node + " is not a "
+ + NodeType.NT_FILE);
+
+ } else {
+ Property property = (Property) item;
+ if(property.getType()==PropertyType.BINARY){
+ //Binary binary = property.getBinary();
+
+ }
+ }
+ } catch (RepositoryException e) {
+ IOException ioe = new IOException(
+ "Unexpected JCR exception");
+ ioe.initCause(e);
+ throw ioe;
+ }
+ }
+
+ @Override
+ public InputStream getInputStream() throws IOException {
+ // TODO Auto-generated method stub
+ return super.getInputStream();
+ }
+
+ };
+ }
+
+}
--- /dev/null
+package org.argeo.jcr;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.Principal;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+import javax.jcr.Binary;
+import javax.jcr.Credentials;
+import javax.jcr.ImportUUIDBehavior;
+import javax.jcr.NamespaceRegistry;
+import javax.jcr.NoSuchWorkspaceException;
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.Property;
+import javax.jcr.PropertyIterator;
+import javax.jcr.PropertyType;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.Value;
+import javax.jcr.Workspace;
+import javax.jcr.nodetype.NoSuchNodeTypeException;
+import javax.jcr.nodetype.NodeType;
+import javax.jcr.observation.EventListener;
+import javax.jcr.query.Query;
+import javax.jcr.query.QueryResult;
+import javax.jcr.security.AccessControlEntry;
+import javax.jcr.security.AccessControlList;
+import javax.jcr.security.AccessControlManager;
+import javax.jcr.security.AccessControlPolicy;
+import javax.jcr.security.AccessControlPolicyIterator;
+import javax.jcr.security.Privilege;
+
+import org.apache.commons.io.IOUtils;
+
+/** Utility methods to simplify common JCR operations. */
+public class JcrUtils {
+
+// final private static Log log = LogFactory.getLog(JcrUtils.class);
+
+ /**
+ * Not complete yet. See
+ * http://www.day.com/specs/jcr/2.0/3_Repository_Model.html#3.2.2%20Local
+ * %20Names
+ */
+ public final static char[] INVALID_NAME_CHARACTERS = { '/', ':', '[', ']', '|', '*', /* invalid for XML: */ '<',
+ '>', '&' };
+
+ /** Prevents instantiation */
+ private JcrUtils() {
+ }
+
+ /**
+ * Queries one single node.
+ *
+ * @return one single node or null if none was found
+ * @throws JcrException if more than one node was found
+ */
+ public static Node querySingleNode(Query query) {
+ NodeIterator nodeIterator;
+ try {
+ QueryResult queryResult = query.execute();
+ nodeIterator = queryResult.getNodes();
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot execute query " + query, e);
+ }
+ Node node;
+ if (nodeIterator.hasNext())
+ node = nodeIterator.nextNode();
+ else
+ return null;
+
+ if (nodeIterator.hasNext())
+ throw new IllegalArgumentException("Query returned more than one node.");
+ return node;
+ }
+
+ /** Retrieves the node name from the provided path */
+ public static String nodeNameFromPath(String path) {
+ if (path.equals("/"))
+ return "";
+ if (path.charAt(0) != '/')
+ throw new IllegalArgumentException("Path " + path + " must start with a '/'");
+ String pathT = path;
+ if (pathT.charAt(pathT.length() - 1) == '/')
+ pathT = pathT.substring(0, pathT.length() - 2);
+
+ int index = pathT.lastIndexOf('/');
+ return pathT.substring(index + 1);
+ }
+
+ /** Retrieves the parent path of the provided path */
+ public static String parentPath(String path) {
+ if (path.equals("/"))
+ throw new IllegalArgumentException("Root path '/' has no parent path");
+ if (path.charAt(0) != '/')
+ throw new IllegalArgumentException("Path " + path + " must start with a '/'");
+ String pathT = path;
+ if (pathT.charAt(pathT.length() - 1) == '/')
+ pathT = pathT.substring(0, pathT.length() - 2);
+
+ int index = pathT.lastIndexOf('/');
+ return pathT.substring(0, index);
+ }
+
+ /** The provided data as a path ('/' at the end, not the beginning) */
+ public static String dateAsPath(Calendar cal) {
+ return dateAsPath(cal, false);
+ }
+
+ /**
+ * Creates a deep path based on a URL:
+ * http://subdomain.example.com/to/content?args becomes
+ * com/example/subdomain/to/content
+ */
+ public static String urlAsPath(String url) {
+ try {
+ URL u = new URL(url);
+ StringBuffer path = new StringBuffer(url.length());
+ // invert host
+ path.append(hostAsPath(u.getHost()));
+ // we don't put port since it may not always be there and may change
+ path.append(u.getPath());
+ return path.toString();
+ } catch (MalformedURLException e) {
+ throw new IllegalArgumentException("Cannot generate URL path for " + url, e);
+ }
+ }
+
+ /** Set the {@link NodeType#NT_ADDRESS} properties based on this URL. */
+ public static void urlToAddressProperties(Node node, String url) {
+ try {
+ URL u = new URL(url);
+ node.setProperty(Property.JCR_PROTOCOL, u.getProtocol());
+ node.setProperty(Property.JCR_HOST, u.getHost());
+ node.setProperty(Property.JCR_PORT, Integer.toString(u.getPort()));
+ node.setProperty(Property.JCR_PATH, normalizePath(u.getPath()));
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot set URL " + url + " as nt:address properties", e);
+ } catch (MalformedURLException e) {
+ throw new IllegalArgumentException("Cannot set URL " + url + " as nt:address properties", e);
+ }
+ }
+
+ /** Build URL based on the {@link NodeType#NT_ADDRESS} properties. */
+ public static String urlFromAddressProperties(Node node) {
+ try {
+ URL u = new URL(node.getProperty(Property.JCR_PROTOCOL).getString(),
+ node.getProperty(Property.JCR_HOST).getString(),
+ (int) node.getProperty(Property.JCR_PORT).getLong(),
+ node.getProperty(Property.JCR_PATH).getString());
+ return u.toString();
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot get URL from nt:address properties of " + node, e);
+ } catch (MalformedURLException e) {
+ throw new IllegalArgumentException("Cannot get URL from nt:address properties of " + node, e);
+ }
+ }
+
+ /*
+ * PATH UTILITIES
+ */
+
+ /**
+ * Make sure that: starts with '/', do not end with '/', do not have '//'
+ */
+ public static String normalizePath(String path) {
+ List<String> tokens = tokenize(path);
+ StringBuffer buf = new StringBuffer(path.length());
+ for (String token : tokens) {
+ buf.append('/');
+ buf.append(token);
+ }
+ return buf.toString();
+ }
+
+ /**
+ * Creates a path from a FQDN, inverting the order of the component:
+ * www.argeo.org becomes org.argeo.www
+ */
+ public static String hostAsPath(String host) {
+ StringBuffer path = new StringBuffer(host.length());
+ String[] hostTokens = host.split("\\.");
+ for (int i = hostTokens.length - 1; i >= 0; i--) {
+ path.append(hostTokens[i]);
+ if (i != 0)
+ path.append('/');
+ }
+ return path.toString();
+ }
+
+ /**
+ * Creates a path from a UUID (e.g. 6ebda899-217d-4bf1-abe4-2839085c8f3c becomes
+ * 6ebda899-217d/4bf1/abe4/2839085c8f3c/). '/' at the end, not the beginning
+ */
+ public static String uuidAsPath(String uuid) {
+ StringBuffer path = new StringBuffer(uuid.length());
+ String[] tokens = uuid.split("-");
+ for (int i = 0; i < tokens.length; i++) {
+ path.append(tokens[i]);
+ if (i != 0)
+ path.append('/');
+ }
+ return path.toString();
+ }
+
+ /**
+ * The provided data as a path ('/' at the end, not the beginning)
+ *
+ * @param cal the date
+ * @param addHour whether to add hour as well
+ */
+ public static String dateAsPath(Calendar cal, Boolean addHour) {
+ StringBuffer buf = new StringBuffer(14);
+ buf.append('Y');
+ buf.append(cal.get(Calendar.YEAR));
+ buf.append('/');
+
+ int month = cal.get(Calendar.MONTH) + 1;
+ buf.append('M');
+ if (month < 10)
+ buf.append(0);
+ buf.append(month);
+ buf.append('/');
+
+ int day = cal.get(Calendar.DAY_OF_MONTH);
+ buf.append('D');
+ if (day < 10)
+ buf.append(0);
+ buf.append(day);
+ buf.append('/');
+
+ if (addHour) {
+ int hour = cal.get(Calendar.HOUR_OF_DAY);
+ buf.append('H');
+ if (hour < 10)
+ buf.append(0);
+ buf.append(hour);
+ buf.append('/');
+ }
+ return buf.toString();
+
+ }
+
+ /** Converts in one call a string into a gregorian calendar. */
+ public static Calendar parseCalendar(DateFormat dateFormat, String value) {
+ try {
+ Date date = dateFormat.parse(value);
+ Calendar calendar = new GregorianCalendar();
+ calendar.setTime(date);
+ return calendar;
+ } catch (ParseException e) {
+ throw new IllegalArgumentException("Cannot parse " + value + " with date format " + dateFormat, e);
+ }
+
+ }
+
+ /** The last element of a path. */
+ public static String lastPathElement(String path) {
+ if (path.charAt(path.length() - 1) == '/')
+ throw new IllegalArgumentException("Path " + path + " cannot end with '/'");
+ int index = path.lastIndexOf('/');
+ if (index < 0)
+ return path;
+ return path.substring(index + 1);
+ }
+
+ /**
+ * Call {@link Node#getName()} without exceptions (useful in super
+ * constructors).
+ */
+ public static String getNameQuietly(Node node) {
+ try {
+ return node.getName();
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot get name from " + node, e);
+ }
+ }
+
+ /**
+ * Call {@link Node#getProperty(String)} without exceptions (useful in super
+ * constructors).
+ */
+ public static String getStringPropertyQuietly(Node node, String propertyName) {
+ try {
+ return node.getProperty(propertyName).getString();
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot get name from " + node, e);
+ }
+ }
+
+// /**
+// * Routine that get the child with this name, adding it if it does not already
+// * exist
+// */
+// public static Node getOrAdd(Node parent, String name, String primaryNodeType) throws RepositoryException {
+// return parent.hasNode(name) ? parent.getNode(name) : parent.addNode(name, primaryNodeType);
+// }
+
+ /**
+ * Routine that get the child with this name, adding it if it does not already
+ * exist
+ */
+ public static Node getOrAdd(Node parent, String name, String primaryNodeType, String... mixinNodeTypes)
+ throws RepositoryException {
+ Node node;
+ if (parent.hasNode(name)) {
+ node = parent.getNode(name);
+ if (primaryNodeType != null && !node.isNodeType(primaryNodeType))
+ throw new IllegalArgumentException("Node " + node + " exists but is of primary node type "
+ + node.getPrimaryNodeType().getName() + ", not " + primaryNodeType);
+ for (String mixin : mixinNodeTypes) {
+ if (!node.isNodeType(mixin))
+ node.addMixin(mixin);
+ }
+ return node;
+ } else {
+ node = primaryNodeType != null ? parent.addNode(name, primaryNodeType) : parent.addNode(name);
+ for (String mixin : mixinNodeTypes) {
+ node.addMixin(mixin);
+ }
+ return node;
+ }
+ }
+
+ /**
+ * Routine that get the child with this name, adding it if it does not already
+ * exist
+ */
+ public static Node getOrAdd(Node parent, String name) throws RepositoryException {
+ return parent.hasNode(name) ? parent.getNode(name) : parent.addNode(name);
+ }
+
+ /** Convert a {@link NodeIterator} to a list of {@link Node} */
+ public static List<Node> nodeIteratorToList(NodeIterator nodeIterator) {
+ List<Node> nodes = new ArrayList<Node>();
+ while (nodeIterator.hasNext()) {
+ nodes.add(nodeIterator.nextNode());
+ }
+ return nodes;
+ }
+
+ /*
+ * PROPERTIES
+ */
+
+ /**
+ * Concisely get the string value of a property or null if this node doesn't
+ * have this property
+ */
+ public static String get(Node node, String propertyName) {
+ try {
+ if (!node.hasProperty(propertyName))
+ return null;
+ return node.getProperty(propertyName).getString();
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot get property " + propertyName + " of " + node, e);
+ }
+ }
+
+ /** Concisely get the path of the given node. */
+ public static String getPath(Node node) {
+ try {
+ return node.getPath();
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot get path of " + node, e);
+ }
+ }
+
+ /** Concisely get the boolean value of a property */
+ public static Boolean check(Node node, String propertyName) {
+ try {
+ return node.getProperty(propertyName).getBoolean();
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot get property " + propertyName + " of " + node, e);
+ }
+ }
+
+ /** Concisely get the bytes array value of a property */
+ public static byte[] getBytes(Node node, String propertyName) {
+ try {
+ return getBinaryAsBytes(node.getProperty(propertyName));
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot get property " + propertyName + " of " + node, e);
+ }
+ }
+
+ /*
+ * MKDIRS
+ */
+
+ /**
+ * Create sub nodes relative to a parent node
+ */
+ public static Node mkdirs(Node parentNode, String relativePath) {
+ return mkdirs(parentNode, relativePath, null, null);
+ }
+
+ /**
+ * Create sub nodes relative to a parent node
+ *
+ * @param nodeType the type of the leaf node
+ */
+ public static Node mkdirs(Node parentNode, String relativePath, String nodeType) {
+ return mkdirs(parentNode, relativePath, nodeType, null);
+ }
+
+ /**
+ * Create sub nodes relative to a parent node
+ *
+ * @param nodeType the type of the leaf node
+ */
+ public static Node mkdirs(Node parentNode, String relativePath, String nodeType, String intermediaryNodeType) {
+ List<String> tokens = tokenize(relativePath);
+ Node currParent = parentNode;
+ try {
+ for (int i = 0; i < tokens.size(); i++) {
+ String name = tokens.get(i);
+ if (currParent.hasNode(name)) {
+ currParent = currParent.getNode(name);
+ } else {
+ if (i != (tokens.size() - 1)) {// intermediary
+ currParent = currParent.addNode(name, intermediaryNodeType);
+ } else {// leaf
+ currParent = currParent.addNode(name, nodeType);
+ }
+ }
+ }
+ return currParent;
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot mkdirs relative path " + relativePath + " from " + parentNode, e);
+ }
+ }
+
+ /**
+ * Synchronized and save is performed, to avoid race conditions in initializers
+ * leading to duplicate nodes.
+ */
+ public synchronized static Node mkdirsSafe(Session session, String path, String type) {
+ try {
+ if (session.hasPendingChanges())
+ throw new IllegalStateException("Session has pending changes, save them first.");
+ Node node = mkdirs(session, path, type);
+ session.save();
+ return node;
+ } catch (RepositoryException e) {
+ discardQuietly(session);
+ throw new JcrException("Cannot safely make directories", e);
+ }
+ }
+
+ public synchronized static Node mkdirsSafe(Session session, String path) {
+ return mkdirsSafe(session, path, null);
+ }
+
+ /** Creates the nodes making path, if they don't exist. */
+ public static Node mkdirs(Session session, String path) {
+ return mkdirs(session, path, null, null, false);
+ }
+
+ /**
+ * @param type the type of the leaf node
+ */
+ public static Node mkdirs(Session session, String path, String type) {
+ return mkdirs(session, path, type, null, false);
+ }
+
+ /**
+ * Creates the nodes making path, if they don't exist. This is up to the caller
+ * to save the session. Use with caution since it can create duplicate nodes if
+ * used concurrently. Requires read access to the root node of the workspace.
+ */
+ public static Node mkdirs(Session session, String path, String type, String intermediaryNodeType,
+ Boolean versioning) {
+ try {
+ if (path.equals("/"))
+ return session.getRootNode();
+
+ if (session.itemExists(path)) {
+ Node node = session.getNode(path);
+ // check type
+ if (type != null && !node.isNodeType(type) && !node.getPath().equals("/"))
+ throw new IllegalArgumentException("Node " + node + " exists but is of type "
+ + node.getPrimaryNodeType().getName() + " not of type " + type);
+ // TODO: check versioning
+ return node;
+ }
+
+ // StringBuffer current = new StringBuffer("/");
+ // Node currentNode = session.getRootNode();
+
+ Node currentNode = findClosestExistingParent(session, path);
+ String closestExistingParentPath = currentNode.getPath();
+ StringBuffer current = new StringBuffer(closestExistingParentPath);
+ if (!closestExistingParentPath.endsWith("/"))
+ current.append('/');
+ Iterator<String> it = tokenize(path.substring(closestExistingParentPath.length())).iterator();
+ while (it.hasNext()) {
+ String part = it.next();
+ current.append(part).append('/');
+ if (!session.itemExists(current.toString())) {
+ if (!it.hasNext() && type != null)
+ currentNode = currentNode.addNode(part, type);
+ else if (it.hasNext() && intermediaryNodeType != null)
+ currentNode = currentNode.addNode(part, intermediaryNodeType);
+ else
+ currentNode = currentNode.addNode(part);
+ if (versioning)
+ currentNode.addMixin(NodeType.MIX_VERSIONABLE);
+// if (log.isTraceEnabled())
+// log.debug("Added folder " + part + " as " + current);
+ } else {
+ currentNode = (Node) session.getItem(current.toString());
+ }
+ }
+ return currentNode;
+ } catch (RepositoryException e) {
+ discardQuietly(session);
+ throw new JcrException("Cannot mkdirs " + path, e);
+ } finally {
+ }
+ }
+
+ private static Node findClosestExistingParent(Session session, String path) throws RepositoryException {
+ int idx = path.lastIndexOf('/');
+ if (idx == 0)
+ return session.getRootNode();
+ String parentPath = path.substring(0, idx);
+ if (session.itemExists(parentPath))
+ return session.getNode(parentPath);
+ else
+ return findClosestExistingParent(session, parentPath);
+ }
+
+ /** Convert a path to the list of its tokens */
+ public static List<String> tokenize(String path) {
+ List<String> tokens = new ArrayList<String>();
+ boolean optimized = false;
+ if (!optimized) {
+ String[] rawTokens = path.split("/");
+ for (String token : rawTokens) {
+ if (!token.equals(""))
+ tokens.add(token);
+ }
+ } else {
+ StringBuffer curr = new StringBuffer();
+ char[] arr = path.toCharArray();
+ chars: for (int i = 0; i < arr.length; i++) {
+ char c = arr[i];
+ if (c == '/') {
+ if (i == 0 || (i == arr.length - 1))
+ continue chars;
+ if (curr.length() > 0) {
+ tokens.add(curr.toString());
+ curr = new StringBuffer();
+ }
+ } else
+ curr.append(c);
+ }
+ if (curr.length() > 0) {
+ tokens.add(curr.toString());
+ curr = new StringBuffer();
+ }
+ }
+ return Collections.unmodifiableList(tokens);
+ }
+
+ // /**
+ // * use {@link #mkdirs(Session, String, String, String, Boolean)} instead.
+ // *
+ // * @deprecated
+ // */
+ // @Deprecated
+ // public static Node mkdirs(Session session, String path, String type,
+ // Boolean versioning) {
+ // return mkdirs(session, path, type, type, false);
+ // }
+
+ /**
+ * Safe and repository implementation independent registration of a namespace.
+ */
+ public static void registerNamespaceSafely(Session session, String prefix, String uri) {
+ try {
+ registerNamespaceSafely(session.getWorkspace().getNamespaceRegistry(), prefix, uri);
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot find namespace registry", e);
+ }
+ }
+
+ /**
+ * Safe and repository implementation independent registration of a namespace.
+ */
+ public static void registerNamespaceSafely(NamespaceRegistry nr, String prefix, String uri) {
+ try {
+ String[] prefixes = nr.getPrefixes();
+ for (String pref : prefixes)
+ if (pref.equals(prefix)) {
+ String registeredUri = nr.getURI(pref);
+ if (!registeredUri.equals(uri))
+ throw new IllegalArgumentException("Prefix " + pref + " already registered for URI "
+ + registeredUri + " which is different from provided URI " + uri);
+ else
+ return;// skip
+ }
+ nr.registerNamespace(prefix, uri);
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot register namespace " + uri + " under prefix " + prefix, e);
+ }
+ }
+
+// /** Recursively outputs the contents of the given node. */
+// public static void debug(Node node) {
+// debug(node, log);
+// }
+//
+// /** Recursively outputs the contents of the given node. */
+// public static void debug(Node node, Log log) {
+// try {
+// // First output the node path
+// log.debug(node.getPath());
+// // Skip the virtual (and large!) jcr:system subtree
+// if (node.getName().equals("jcr:system")) {
+// return;
+// }
+//
+// // Then the children nodes (recursive)
+// NodeIterator it = node.getNodes();
+// while (it.hasNext()) {
+// Node childNode = it.nextNode();
+// debug(childNode, log);
+// }
+//
+// // Then output the properties
+// PropertyIterator properties = node.getProperties();
+// // log.debug("Property are : ");
+//
+// properties: while (properties.hasNext()) {
+// Property property = properties.nextProperty();
+// if (property.getType() == PropertyType.BINARY)
+// continue properties;// skip
+// if (property.getDefinition().isMultiple()) {
+// // A multi-valued property, print all values
+// Value[] values = property.getValues();
+// for (int i = 0; i < values.length; i++) {
+// log.debug(property.getPath() + "=" + values[i].getString());
+// }
+// } else {
+// // A single-valued property
+// log.debug(property.getPath() + "=" + property.getString());
+// }
+// }
+// } catch (Exception e) {
+// log.error("Could not debug " + node, e);
+// }
+//
+// }
+
+// /** Logs the effective access control policies */
+// public static void logEffectiveAccessPolicies(Node node) {
+// try {
+// logEffectiveAccessPolicies(node.getSession(), node.getPath());
+// } catch (RepositoryException e) {
+// log.error("Cannot log effective access policies of " + node, e);
+// }
+// }
+//
+// /** Logs the effective access control policies */
+// public static void logEffectiveAccessPolicies(Session session, String path) {
+// if (!log.isDebugEnabled())
+// return;
+//
+// try {
+// AccessControlPolicy[] effectivePolicies = session.getAccessControlManager().getEffectivePolicies(path);
+// if (effectivePolicies.length > 0) {
+// for (AccessControlPolicy policy : effectivePolicies) {
+// if (policy instanceof AccessControlList) {
+// AccessControlList acl = (AccessControlList) policy;
+// log.debug("Access control list for " + path + "\n" + accessControlListSummary(acl));
+// }
+// }
+// } else {
+// log.debug("No effective access control policy for " + path);
+// }
+// } catch (RepositoryException e) {
+// log.error("Cannot log effective access policies of " + path, e);
+// }
+// }
+
+ /** Returns a human-readable summary of this access control list. */
+ public static String accessControlListSummary(AccessControlList acl) {
+ StringBuffer buf = new StringBuffer("");
+ try {
+ for (AccessControlEntry ace : acl.getAccessControlEntries()) {
+ buf.append('\t').append(ace.getPrincipal().getName()).append('\n');
+ for (Privilege priv : ace.getPrivileges())
+ buf.append("\t\t").append(priv.getName()).append('\n');
+ }
+ return buf.toString();
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot write summary of " + acl, e);
+ }
+ }
+
+ /** Copy the whole workspace via a system view XML. */
+ public static void copyWorkspaceXml(Session fromSession, Session toSession) {
+ Workspace fromWorkspace = fromSession.getWorkspace();
+ Workspace toWorkspace = toSession.getWorkspace();
+ String errorMsg = "Cannot copy workspace " + fromWorkspace + " to " + toWorkspace + " via XML.";
+
+ try (PipedInputStream in = new PipedInputStream(1024 * 1024);) {
+ new Thread(() -> {
+ try (PipedOutputStream out = new PipedOutputStream(in)) {
+ fromSession.exportSystemView("/", out, false, false);
+ out.flush();
+ } catch (IOException e) {
+ throw new RuntimeException(errorMsg, e);
+ } catch (RepositoryException e) {
+ throw new JcrException(errorMsg, e);
+ }
+ }, "Copy workspace" + fromWorkspace + " to " + toWorkspace).start();
+
+ toSession.importXML("/", in, ImportUUIDBehavior.IMPORT_UUID_COLLISION_REPLACE_EXISTING);
+ toSession.save();
+ } catch (IOException e) {
+ throw new RuntimeException(errorMsg, e);
+ } catch (RepositoryException e) {
+ throw new JcrException(errorMsg, e);
+ }
+ }
+
+ /**
+ * Copies recursively the content of a node to another one. Do NOT copy the
+ * property values of {@link NodeType#MIX_CREATED} and
+ * {@link NodeType#MIX_LAST_MODIFIED}, but update the
+ * {@link Property#JCR_LAST_MODIFIED} and {@link Property#JCR_LAST_MODIFIED_BY}
+ * properties if the target node has the {@link NodeType#MIX_LAST_MODIFIED}
+ * mixin.
+ */
+ public static void copy(Node fromNode, Node toNode) {
+ try {
+ if (toNode.getDefinition().isProtected())
+ return;
+
+ // add mixins
+ for (NodeType mixinType : fromNode.getMixinNodeTypes()) {
+ try {
+ toNode.addMixin(mixinType.getName());
+ } catch (NoSuchNodeTypeException e) {
+ // ignore unknown mixins
+ // TODO log it
+ }
+ }
+
+ // process properties
+ PropertyIterator pit = fromNode.getProperties();
+ properties: while (pit.hasNext()) {
+ Property fromProperty = pit.nextProperty();
+ String propertyName = fromProperty.getName();
+ if (toNode.hasProperty(propertyName) && toNode.getProperty(propertyName).getDefinition().isProtected())
+ continue properties;
+
+ if (fromProperty.getDefinition().isProtected())
+ continue properties;
+
+ if (propertyName.equals("jcr:created") || propertyName.equals("jcr:createdBy")
+ || propertyName.equals("jcr:lastModified") || propertyName.equals("jcr:lastModifiedBy"))
+ continue properties;
+
+ if (fromProperty.isMultiple()) {
+ toNode.setProperty(propertyName, fromProperty.getValues());
+ } else {
+ toNode.setProperty(propertyName, fromProperty.getValue());
+ }
+ }
+
+ // update jcr:lastModified and jcr:lastModifiedBy in toNode in case
+ // they existed, before adding the mixins
+ updateLastModified(toNode, true);
+
+ // process children nodes
+ NodeIterator nit = fromNode.getNodes();
+ while (nit.hasNext()) {
+ Node fromChild = nit.nextNode();
+ Integer index = fromChild.getIndex();
+ String nodeRelPath = fromChild.getName() + "[" + index + "]";
+ Node toChild;
+ if (toNode.hasNode(nodeRelPath))
+ toChild = toNode.getNode(nodeRelPath);
+ else {
+ try {
+ toChild = toNode.addNode(fromChild.getName(), fromChild.getPrimaryNodeType().getName());
+ } catch (NoSuchNodeTypeException e) {
+ // ignore unknown primary types
+ // TODO log it
+ return;
+ }
+ }
+ copy(fromChild, toChild);
+ }
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot copy " + fromNode + " to " + toNode, e);
+ }
+ }
+
+ /**
+ * Check whether all first-level properties (except jcr:* properties) are equal.
+ * Skip jcr:* properties
+ */
+ public static Boolean allPropertiesEquals(Node reference, Node observed, Boolean onlyCommonProperties) {
+ try {
+ PropertyIterator pit = reference.getProperties();
+ props: while (pit.hasNext()) {
+ Property propReference = pit.nextProperty();
+ String propName = propReference.getName();
+ if (propName.startsWith("jcr:"))
+ continue props;
+
+ if (!observed.hasProperty(propName))
+ if (onlyCommonProperties)
+ continue props;
+ else
+ return false;
+ // TODO: deal with multiple property values?
+ if (!observed.getProperty(propName).getValue().equals(propReference.getValue()))
+ return false;
+ }
+ return true;
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot check all properties equals of " + reference + " and " + observed, e);
+ }
+ }
+
+ public static Map<String, PropertyDiff> diffProperties(Node reference, Node observed) {
+ Map<String, PropertyDiff> diffs = new TreeMap<String, PropertyDiff>();
+ diffPropertiesLevel(diffs, null, reference, observed);
+ return diffs;
+ }
+
+ /**
+ * Compare the properties of two nodes. Recursivity to child nodes is not yet
+ * supported. Skip jcr:* properties.
+ */
+ static void diffPropertiesLevel(Map<String, PropertyDiff> diffs, String baseRelPath, Node reference,
+ Node observed) {
+ try {
+ // check removed and modified
+ PropertyIterator pit = reference.getProperties();
+ props: while (pit.hasNext()) {
+ Property p = pit.nextProperty();
+ String name = p.getName();
+ if (name.startsWith("jcr:"))
+ continue props;
+
+ if (!observed.hasProperty(name)) {
+ String relPath = propertyRelPath(baseRelPath, name);
+ PropertyDiff pDiff = new PropertyDiff(PropertyDiff.REMOVED, relPath, p.getValue(), null);
+ diffs.put(relPath, pDiff);
+ } else {
+ if (p.isMultiple()) {
+ // FIXME implement multiple
+ } else {
+ Value referenceValue = p.getValue();
+ Value newValue = observed.getProperty(name).getValue();
+ if (!referenceValue.equals(newValue)) {
+ String relPath = propertyRelPath(baseRelPath, name);
+ PropertyDiff pDiff = new PropertyDiff(PropertyDiff.MODIFIED, relPath, referenceValue,
+ newValue);
+ diffs.put(relPath, pDiff);
+ }
+ }
+ }
+ }
+ // check added
+ pit = observed.getProperties();
+ props: while (pit.hasNext()) {
+ Property p = pit.nextProperty();
+ String name = p.getName();
+ if (name.startsWith("jcr:"))
+ continue props;
+ if (!reference.hasProperty(name)) {
+ if (p.isMultiple()) {
+ // FIXME implement multiple
+ } else {
+ String relPath = propertyRelPath(baseRelPath, name);
+ PropertyDiff pDiff = new PropertyDiff(PropertyDiff.ADDED, relPath, null, p.getValue());
+ diffs.put(relPath, pDiff);
+ }
+ }
+ }
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot diff " + reference + " and " + observed, e);
+ }
+ }
+
+ /**
+ * Compare only a restricted list of properties of two nodes. No recursivity.
+ *
+ */
+ public static Map<String, PropertyDiff> diffProperties(Node reference, Node observed, List<String> properties) {
+ Map<String, PropertyDiff> diffs = new TreeMap<String, PropertyDiff>();
+ try {
+ Iterator<String> pit = properties.iterator();
+
+ props: while (pit.hasNext()) {
+ String name = pit.next();
+ if (!reference.hasProperty(name)) {
+ if (!observed.hasProperty(name))
+ continue props;
+ Value val = observed.getProperty(name).getValue();
+ try {
+ // empty String but not null
+ if ("".equals(val.getString()))
+ continue props;
+ } catch (Exception e) {
+ // not parseable as String, silent
+ }
+ PropertyDiff pDiff = new PropertyDiff(PropertyDiff.ADDED, name, null, val);
+ diffs.put(name, pDiff);
+ } else if (!observed.hasProperty(name)) {
+ PropertyDiff pDiff = new PropertyDiff(PropertyDiff.REMOVED, name,
+ reference.getProperty(name).getValue(), null);
+ diffs.put(name, pDiff);
+ } else {
+ Value referenceValue = reference.getProperty(name).getValue();
+ Value newValue = observed.getProperty(name).getValue();
+ if (!referenceValue.equals(newValue)) {
+ PropertyDiff pDiff = new PropertyDiff(PropertyDiff.MODIFIED, name, referenceValue, newValue);
+ diffs.put(name, pDiff);
+ }
+ }
+ }
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot diff " + reference + " and " + observed, e);
+ }
+ return diffs;
+ }
+
+ /** Builds a property relPath to be used in the diff. */
+ private static String propertyRelPath(String baseRelPath, String propertyName) {
+ if (baseRelPath == null)
+ return propertyName;
+ else
+ return baseRelPath + '/' + propertyName;
+ }
+
+ /**
+ * Normalizes a name so that it can be stored in contexts not supporting names
+ * with ':' (typically databases). Replaces ':' by '_'.
+ */
+ public static String normalize(String name) {
+ return name.replace(':', '_');
+ }
+
+ /**
+ * Replaces characters which are invalid in a JCR name by '_'. Currently not
+ * exhaustive.
+ *
+ * @see JcrUtils#INVALID_NAME_CHARACTERS
+ */
+ public static String replaceInvalidChars(String name) {
+ return replaceInvalidChars(name, '_');
+ }
+
+ /**
+ * Replaces characters which are invalid in a JCR name. Currently not
+ * exhaustive.
+ *
+ * @see JcrUtils#INVALID_NAME_CHARACTERS
+ */
+ public static String replaceInvalidChars(String name, char replacement) {
+ boolean modified = false;
+ char[] arr = name.toCharArray();
+ for (int i = 0; i < arr.length; i++) {
+ char c = arr[i];
+ invalid: for (char invalid : INVALID_NAME_CHARACTERS) {
+ if (c == invalid) {
+ arr[i] = replacement;
+ modified = true;
+ break invalid;
+ }
+ }
+ }
+ if (modified)
+ return new String(arr);
+ else
+ // do not create new object if unnecessary
+ return name;
+ }
+
+ // /**
+ // * Removes forbidden characters from a path, replacing them with '_'
+ // *
+ // * @deprecated use {@link #replaceInvalidChars(String)} instead
+ // */
+ // public static String removeForbiddenCharacters(String str) {
+ // return str.replace('[', '_').replace(']', '_').replace('/', '_').replace('*',
+ // '_');
+ //
+ // }
+
+ /** Cleanly disposes a {@link Binary} even if it is null. */
+ public static void closeQuietly(Binary binary) {
+ if (binary == null)
+ return;
+ binary.dispose();
+ }
+
+ /** Retrieve a {@link Binary} as a byte array */
+ public static byte[] getBinaryAsBytes(Property property) {
+ try (ByteArrayOutputStream out = new ByteArrayOutputStream();
+ Bin binary = new Bin(property);
+ InputStream in = binary.getStream()) {
+ IOUtils.copy(in, out);
+ return out.toByteArray();
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot read binary " + property + " as bytes", e);
+ } catch (IOException e) {
+ throw new RuntimeException("Cannot read binary " + property + " as bytes", e);
+ }
+ }
+
+ /** Writes a {@link Binary} from a byte array */
+ public static void setBinaryAsBytes(Node node, String property, byte[] bytes) {
+ Binary binary = null;
+ try (InputStream in = new ByteArrayInputStream(bytes)) {
+ binary = node.getSession().getValueFactory().createBinary(in);
+ node.setProperty(property, binary);
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot set binary " + property + " as bytes", e);
+ } catch (IOException e) {
+ throw new RuntimeException("Cannot set binary " + property + " as bytes", e);
+ } finally {
+ closeQuietly(binary);
+ }
+ }
+
+ /** Writes a {@link Binary} from a byte array */
+ public static void setBinaryAsBytes(Property prop, byte[] bytes) {
+ Binary binary = null;
+ try (InputStream in = new ByteArrayInputStream(bytes)) {
+ binary = prop.getSession().getValueFactory().createBinary(in);
+ prop.setValue(binary);
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot set binary " + prop + " as bytes", e);
+ } catch (IOException e) {
+ throw new RuntimeException("Cannot set binary " + prop + " as bytes", e);
+ } finally {
+ closeQuietly(binary);
+ }
+ }
+
+ /**
+ * Creates depth from a string (typically a username) by adding levels based on
+ * its first characters: "aBcD",2 becomes a/aB
+ */
+ public static String firstCharsToPath(String str, Integer nbrOfChars) {
+ if (str.length() < nbrOfChars)
+ throw new IllegalArgumentException("String " + str + " length must be greater or equal than " + nbrOfChars);
+ StringBuffer path = new StringBuffer("");
+ StringBuffer curr = new StringBuffer("");
+ for (int i = 0; i < nbrOfChars; i++) {
+ curr.append(str.charAt(i));
+ path.append(curr);
+ if (i < nbrOfChars - 1)
+ path.append('/');
+ }
+ return path.toString();
+ }
+
+ /**
+ * Discards the current changes in the session attached to this node. To be used
+ * typically in a catch block.
+ *
+ * @see #discardQuietly(Session)
+ */
+ public static void discardUnderlyingSessionQuietly(Node node) {
+ try {
+ discardQuietly(node.getSession());
+ } catch (RepositoryException e) {
+ // silent
+ }
+ }
+
+ /**
+ * Discards the current changes in a session by calling
+ * {@link Session#refresh(boolean)} with <code>false</code>, only logging
+ * potential errors when doing so. To be used typically in a catch block.
+ */
+ public static void discardQuietly(Session session) {
+ try {
+ if (session != null)
+ session.refresh(false);
+ } catch (RepositoryException e) {
+ // silent
+ }
+ }
+
+ /**
+ * 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)
+ 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(credentials, workspaceName);
+ } catch (NoSuchWorkspaceException e) {
+ // try to create workspace
+ defaultSession = repository.login(credentials);
+ defaultSession.getWorkspace().createWorkspace(workspaceName);
+ workspaceSession = repository.login(credentials, workspaceName);
+ }
+ return workspaceSession;
+ } finally {
+ logoutQuietly(defaultSession);
+ }
+ }
+
+ /**
+ * Logs out the session, not throwing any exception, even if it is null.
+ * {@link Jcr#logout(Session)} should rather be used.
+ */
+ public static void logoutQuietly(Session session) {
+ Jcr.logout(session);
+// try {
+// if (session != null)
+// if (session.isLive())
+// session.logout();
+// } catch (Exception e) {
+// // silent
+// }
+ }
+
+ /**
+ * Convenient method to add a listener. uuids passed as null, deep=true,
+ * local=true, only one node type
+ */
+ public static void addListener(Session session, EventListener listener, int eventTypes, String basePath,
+ String nodeType) {
+ try {
+ session.getWorkspace().getObservationManager().addEventListener(listener, eventTypes, basePath, true, null,
+ nodeType == null ? null : new String[] { nodeType }, true);
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot add JCR listener " + listener + " to session " + session, e);
+ }
+ }
+
+ /** Removes a listener without throwing exception */
+ public static void removeListenerQuietly(Session session, EventListener listener) {
+ if (session == null || !session.isLive())
+ return;
+ try {
+ session.getWorkspace().getObservationManager().removeEventListener(listener);
+ } catch (RepositoryException e) {
+ // silent
+ }
+ }
+
+ /**
+ * Quietly unregisters an {@link EventListener} from the udnerlying workspace of
+ * this node.
+ */
+ public static void unregisterQuietly(Node node, EventListener eventListener) {
+ try {
+ unregisterQuietly(node.getSession().getWorkspace(), eventListener);
+ } catch (RepositoryException e) {
+ // silent
+ }
+ }
+
+ /** Quietly unregisters an {@link EventListener} from this workspace */
+ public static void unregisterQuietly(Workspace workspace, EventListener eventListener) {
+ if (eventListener == null)
+ return;
+ try {
+ workspace.getObservationManager().removeEventListener(eventListener);
+ } catch (RepositoryException e) {
+ // silent
+ }
+ }
+
+ /**
+ * Checks whether {@link Property#JCR_LAST_MODIFIED} or (afterwards)
+ * {@link Property#JCR_CREATED} are set and returns it as an {@link Instant}.
+ */
+ public static Instant getModified(Node node) {
+ Calendar calendar = null;
+ try {
+ if (node.hasProperty(Property.JCR_LAST_MODIFIED))
+ calendar = node.getProperty(Property.JCR_LAST_MODIFIED).getDate();
+ else if (node.hasProperty(Property.JCR_CREATED))
+ calendar = node.getProperty(Property.JCR_CREATED).getDate();
+ else
+ throw new IllegalArgumentException("No modification time found in " + node);
+ return calendar.toInstant();
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot get modification time for " + node, e);
+ }
+
+ }
+
+ /**
+ * Get {@link Property#JCR_CREATED} as an {@link Instant}, if it is set.
+ */
+ public static Instant getCreated(Node node) {
+ Calendar calendar = null;
+ try {
+ if (node.hasProperty(Property.JCR_CREATED))
+ calendar = node.getProperty(Property.JCR_CREATED).getDate();
+ else
+ throw new IllegalArgumentException("No created time found in " + node);
+ return calendar.toInstant();
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot get created time for " + node, e);
+ }
+
+ }
+
+ /**
+ * Updates the {@link Property#JCR_LAST_MODIFIED} property with the current time
+ * and the {@link Property#JCR_LAST_MODIFIED_BY} property with the underlying
+ * session user id.
+ */
+ public static void updateLastModified(Node node) {
+ updateLastModified(node, false);
+ }
+
+ /**
+ * Updates the {@link Property#JCR_LAST_MODIFIED} property with the current time
+ * and the {@link Property#JCR_LAST_MODIFIED_BY} property with the underlying
+ * session user id. In Jackrabbit 2.x,
+ * <a href="https://issues.apache.org/jira/browse/JCR-2233">these properties are
+ * not automatically updated</a>, hence the need for manual update. The session
+ * is not saved.
+ */
+ public static void updateLastModified(Node node, boolean addMixin) {
+ try {
+ if (addMixin && !node.isNodeType(NodeType.MIX_LAST_MODIFIED))
+ node.addMixin(NodeType.MIX_LAST_MODIFIED);
+ node.setProperty(Property.JCR_LAST_MODIFIED, new GregorianCalendar());
+ node.setProperty(Property.JCR_LAST_MODIFIED_BY, node.getSession().getUserID());
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot update last modified on " + node, e);
+ }
+ }
+
+ /**
+ * Update lastModified recursively until this parent.
+ *
+ * @param node the node
+ * @param untilPath the base path, null is equivalent to "/"
+ */
+ public static void updateLastModifiedAndParents(Node node, String untilPath) {
+ updateLastModifiedAndParents(node, untilPath, true);
+ }
+
+ /**
+ * Update lastModified recursively until this parent.
+ *
+ * @param node the node
+ * @param untilPath the base path, null is equivalent to "/"
+ */
+ public static void updateLastModifiedAndParents(Node node, String untilPath, boolean addMixin) {
+ try {
+ if (untilPath != null && !node.getPath().startsWith(untilPath))
+ throw new IllegalArgumentException(node + " is not under " + untilPath);
+ updateLastModified(node, addMixin);
+ if (untilPath == null) {
+ if (!node.getPath().equals("/"))
+ updateLastModifiedAndParents(node.getParent(), untilPath, addMixin);
+ } else {
+ if (!node.getPath().equals(untilPath))
+ updateLastModifiedAndParents(node.getParent(), untilPath, addMixin);
+ }
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot update lastModified from " + node + " until " + untilPath, e);
+ }
+ }
+
+ /**
+ * Returns a String representing the short version (see
+ * <a href="http://jackrabbit.apache.org/node-type-notation.html"> Node type
+ * Notation </a> attributes grammar) of the main business attributes of this
+ * property definition
+ *
+ * @param prop
+ */
+ public static String getPropertyDefinitionAsString(Property prop) {
+ StringBuffer sbuf = new StringBuffer();
+ try {
+ if (prop.getDefinition().isAutoCreated())
+ sbuf.append("a");
+ if (prop.getDefinition().isMandatory())
+ sbuf.append("m");
+ if (prop.getDefinition().isProtected())
+ sbuf.append("p");
+ if (prop.getDefinition().isMultiple())
+ sbuf.append("*");
+ } catch (RepositoryException re) {
+ throw new JcrException("unexpected error while getting property definition as String", re);
+ }
+ return sbuf.toString();
+ }
+
+ /**
+ * Estimate the sub tree size from current node. Computation is based on the Jcr
+ * {@link Property#getLength()} method. Note : it is not the exact size used on
+ * the disk by the current part of the JCR Tree.
+ */
+
+ public static long getNodeApproxSize(Node node) {
+ long curNodeSize = 0;
+ try {
+ PropertyIterator pi = node.getProperties();
+ while (pi.hasNext()) {
+ Property prop = pi.nextProperty();
+ if (prop.isMultiple()) {
+ int nb = prop.getLengths().length;
+ for (int i = 0; i < nb; i++) {
+ curNodeSize += (prop.getLengths()[i] > 0 ? prop.getLengths()[i] : 0);
+ }
+ } else
+ curNodeSize += (prop.getLength() > 0 ? prop.getLength() : 0);
+ }
+
+ NodeIterator ni = node.getNodes();
+ while (ni.hasNext())
+ curNodeSize += getNodeApproxSize(ni.nextNode());
+ return curNodeSize;
+ } catch (RepositoryException re) {
+ throw new JcrException("Unexpected error while recursively determining node size.", re);
+ }
+ }
+
+ /*
+ * SECURITY
+ */
+
+ /**
+ * Convenience method for adding a single privilege to a principal (user or
+ * role), typically jcr:all
+ */
+ public synchronized static void addPrivilege(Session session, String path, String principal, String privilege)
+ throws RepositoryException {
+ List<Privilege> privileges = new ArrayList<Privilege>();
+ privileges.add(session.getAccessControlManager().privilegeFromName(privilege));
+ addPrivileges(session, path, new SimplePrincipal(principal), privileges);
+ }
+
+ /**
+ * Add privileges on a path to a {@link Principal}. The path must already exist.
+ * Session is saved. Synchronized to prevent concurrent modifications of the
+ * same node.
+ */
+ public synchronized static Boolean addPrivileges(Session session, String path, Principal principal,
+ List<Privilege> privs) throws RepositoryException {
+ // make sure the session is in line with the persisted state
+ session.refresh(false);
+ AccessControlManager acm = session.getAccessControlManager();
+ AccessControlList acl = getAccessControlList(acm, path);
+
+ accessControlEntries: for (AccessControlEntry ace : acl.getAccessControlEntries()) {
+ Principal currentPrincipal = ace.getPrincipal();
+ if (currentPrincipal.getName().equals(principal.getName())) {
+ Privilege[] currentPrivileges = ace.getPrivileges();
+ if (currentPrivileges.length != privs.size())
+ break accessControlEntries;
+ for (int i = 0; i < currentPrivileges.length; i++) {
+ Privilege currP = currentPrivileges[i];
+ Privilege p = privs.get(i);
+ if (!currP.getName().equals(p.getName())) {
+ break accessControlEntries;
+ }
+ }
+ return false;
+ }
+ }
+
+ Privilege[] privileges = privs.toArray(new Privilege[privs.size()]);
+ acl.addAccessControlEntry(principal, privileges);
+ acm.setPolicy(path, acl);
+// if (log.isDebugEnabled()) {
+// StringBuffer privBuf = new StringBuffer();
+// for (Privilege priv : privs)
+// privBuf.append(priv.getName());
+// log.debug("Added privileges " + privBuf + " to " + principal.getName() + " on " + path + " in '"
+// + session.getWorkspace().getName() + "'");
+// }
+ session.refresh(true);
+ session.save();
+ return true;
+ }
+
+ /**
+ * Gets the first available access control list for this path, throws exception
+ * if not found
+ */
+ public synchronized static AccessControlList getAccessControlList(AccessControlManager acm, String path)
+ throws RepositoryException {
+ // search for an access control list
+ AccessControlList acl = null;
+ AccessControlPolicyIterator policyIterator = acm.getApplicablePolicies(path);
+ applicablePolicies: if (policyIterator.hasNext()) {
+ while (policyIterator.hasNext()) {
+ AccessControlPolicy acp = policyIterator.nextAccessControlPolicy();
+ if (acp instanceof AccessControlList) {
+ acl = ((AccessControlList) acp);
+ break applicablePolicies;
+ }
+ }
+ } else {
+ AccessControlPolicy[] existingPolicies = acm.getPolicies(path);
+ existingPolicies: for (AccessControlPolicy acp : existingPolicies) {
+ if (acp instanceof AccessControlList) {
+ acl = ((AccessControlList) acp);
+ break existingPolicies;
+ }
+ }
+ }
+ if (acl != null)
+ return acl;
+ else
+ throw new IllegalArgumentException("ACL not found at " + path);
+ }
+
+ /** Clear authorizations for a user at this path */
+ public synchronized static void clearAccessControList(Session session, String path, String username)
+ throws RepositoryException {
+ AccessControlManager acm = session.getAccessControlManager();
+ AccessControlList acl = getAccessControlList(acm, path);
+ for (AccessControlEntry ace : acl.getAccessControlEntries()) {
+ if (ace.getPrincipal().getName().equals(username)) {
+ acl.removeAccessControlEntry(ace);
+ }
+ }
+ // the new access control list must be applied otherwise this call:
+ // acl.removeAccessControlEntry(ace); has no effect
+ acm.setPolicy(path, acl);
+ session.refresh(true);
+ session.save();
+ }
+
+ /*
+ * FILES UTILITIES
+ */
+ /**
+ * Creates the nodes making the path as {@link NodeType#NT_FOLDER}
+ */
+ public static Node mkfolders(Session session, String path) {
+ return mkdirs(session, path, NodeType.NT_FOLDER, NodeType.NT_FOLDER, false);
+ }
+
+ /**
+ * Copy only nt:folder and nt:file, without their additional types and
+ * properties.
+ *
+ * @param recursive if true copies folders as well, otherwise only first level
+ * files
+ * @return how many files were copied
+ */
+ public static Long copyFiles(Node fromNode, Node toNode, Boolean recursive, JcrMonitor monitor, boolean onlyAdd) {
+ long count = 0l;
+
+ // Binary binary = null;
+ // InputStream in = null;
+ try {
+ NodeIterator fromChildren = fromNode.getNodes();
+ children: while (fromChildren.hasNext()) {
+ if (monitor != null && monitor.isCanceled())
+ throw new IllegalStateException("Copy cancelled before it was completed");
+
+ Node fromChild = fromChildren.nextNode();
+ String fileName = fromChild.getName();
+ if (fromChild.isNodeType(NodeType.NT_FILE)) {
+ if (onlyAdd && toNode.hasNode(fileName)) {
+ monitor.subTask("Skip existing " + fileName);
+ continue children;
+ }
+
+ if (monitor != null)
+ monitor.subTask("Copy " + fileName);
+ try (Bin binary = new Bin(fromChild.getNode(Node.JCR_CONTENT).getProperty(Property.JCR_DATA));
+ InputStream in = binary.getStream();) {
+ copyStreamAsFile(toNode, fileName, in);
+ } catch (IOException e) {
+ throw new RuntimeException("Cannot copy " + fileName + " to " + toNode, e);
+ }
+
+ // save session
+ toNode.getSession().save();
+ count++;
+
+// if (log.isDebugEnabled())
+// log.debug("Copied file " + fromChild.getPath());
+ if (monitor != null)
+ monitor.worked(1);
+ } else if (fromChild.isNodeType(NodeType.NT_FOLDER) && recursive) {
+ Node toChildFolder;
+ if (toNode.hasNode(fileName)) {
+ toChildFolder = toNode.getNode(fileName);
+ if (!toChildFolder.isNodeType(NodeType.NT_FOLDER))
+ throw new IllegalArgumentException(toChildFolder + " is not of type nt:folder");
+ } else {
+ toChildFolder = toNode.addNode(fileName, NodeType.NT_FOLDER);
+
+ // save session
+ toNode.getSession().save();
+ }
+ count = count + copyFiles(fromChild, toChildFolder, recursive, monitor, onlyAdd);
+ }
+ }
+ return count;
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot copy files between " + fromNode + " and " + toNode, e);
+ } finally {
+ // in case there was an exception
+ // IOUtils.closeQuietly(in);
+ // closeQuietly(binary);
+ }
+ }
+
+ /**
+ * Iteratively count all file nodes in subtree, inefficient but can be useful
+ * when query are poorly supported, such as in remoting.
+ */
+ public static Long countFiles(Node node) {
+ Long localCount = 0l;
+ try {
+ for (NodeIterator nit = node.getNodes(); nit.hasNext();) {
+ Node child = nit.nextNode();
+ if (child.isNodeType(NodeType.NT_FOLDER))
+ localCount = localCount + countFiles(child);
+ else if (child.isNodeType(NodeType.NT_FILE))
+ localCount = localCount + 1;
+ }
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot count all children of " + node, e);
+ }
+ return localCount;
+ }
+
+ /**
+ * Copy a file as an nt:file, assuming an nt:folder hierarchy. The session is
+ * NOT saved.
+ *
+ * @return the created file node
+ */
+ @Deprecated
+ public static Node copyFile(Node folderNode, File file) {
+ try (InputStream in = new FileInputStream(file)) {
+ return copyStreamAsFile(folderNode, file.getName(), in);
+ } catch (IOException e) {
+ throw new RuntimeException("Cannot copy file " + file + " under " + folderNode, e);
+ }
+ }
+
+ /** Copy bytes as an nt:file */
+ public static Node copyBytesAsFile(Node folderNode, String fileName, byte[] bytes) {
+ // InputStream in = null;
+ try (InputStream in = new ByteArrayInputStream(bytes)) {
+ // in = new ByteArrayInputStream(bytes);
+ return copyStreamAsFile(folderNode, fileName, in);
+ } catch (IOException e) {
+ throw new RuntimeException("Cannot copy file " + fileName + " under " + folderNode, e);
+ // } finally {
+ // IOUtils.closeQuietly(in);
+ }
+ }
+
+ /**
+ * Copy a stream as an nt:file, assuming an nt:folder hierarchy. The session is
+ * NOT saved.
+ *
+ * @return the created file node
+ */
+ public static Node copyStreamAsFile(Node folderNode, String fileName, InputStream in) {
+ Binary binary = null;
+ try {
+ Node fileNode;
+ Node contentNode;
+ if (folderNode.hasNode(fileName)) {
+ fileNode = folderNode.getNode(fileName);
+ if (!fileNode.isNodeType(NodeType.NT_FILE))
+ throw new IllegalArgumentException(fileNode + " is not of type nt:file");
+ // we assume that the content node is already there
+ contentNode = fileNode.getNode(Node.JCR_CONTENT);
+ } else {
+ fileNode = folderNode.addNode(fileName, NodeType.NT_FILE);
+ contentNode = fileNode.addNode(Node.JCR_CONTENT, NodeType.NT_UNSTRUCTURED);
+ }
+ binary = contentNode.getSession().getValueFactory().createBinary(in);
+ contentNode.setProperty(Property.JCR_DATA, binary);
+ updateLastModified(contentNode);
+ return fileNode;
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot create file node " + fileName + " under " + folderNode, e);
+ } finally {
+ closeQuietly(binary);
+ }
+ }
+
+ /** Read an an nt:file as an {@link InputStream}. */
+ public static InputStream getFileAsStream(Node fileNode) throws RepositoryException {
+ return fileNode.getNode(Node.JCR_CONTENT).getProperty(Property.JCR_DATA).getBinary().getStream();
+ }
+
+ /**
+ * Set the properties of {@link NodeType#MIX_MIMETYPE} on the content of this
+ * file node.
+ */
+ public static void setFileMimeType(Node fileNode, String mimeType, String encoding) throws RepositoryException {
+ Node contentNode = fileNode.getNode(Node.JCR_CONTENT);
+ if (mimeType != null)
+ contentNode.setProperty(Property.JCR_MIMETYPE, mimeType);
+ if (encoding != null)
+ contentNode.setProperty(Property.JCR_ENCODING, encoding);
+ // TODO remove properties if args are null?
+ }
+
+ public static void copyFilesToFs(Node baseNode, Path targetDir, boolean recursive) {
+ try {
+ Files.createDirectories(targetDir);
+ for (NodeIterator nit = baseNode.getNodes(); nit.hasNext();) {
+ Node node = nit.nextNode();
+ if (node.isNodeType(NodeType.NT_FILE)) {
+ Path filePath = targetDir.resolve(node.getName());
+ try (OutputStream out = Files.newOutputStream(filePath); InputStream in = getFileAsStream(node)) {
+ IOUtils.copy(in, out);
+ }
+ } else if (recursive && node.isNodeType(NodeType.NT_FOLDER)) {
+ Path dirPath = targetDir.resolve(node.getName());
+ copyFilesToFs(node, dirPath, true);
+ }
+ }
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot copy " + baseNode + " to " + targetDir, e);
+ } catch (IOException e) {
+ throw new RuntimeException("Cannot copy " + baseNode + " to " + targetDir, e);
+ }
+ }
+
+ /**
+ * Computes the checksum of an nt:file.
+ *
+ * @deprecated use separate digest utilities
+ */
+ @Deprecated
+ public static String checksumFile(Node fileNode, String algorithm) {
+ try (InputStream in = fileNode.getNode(Node.JCR_CONTENT).getProperty(Property.JCR_DATA).getBinary()
+ .getStream()) {
+ return digest(algorithm, in);
+ } catch (IOException e) {
+ throw new RuntimeException("Cannot checksum file " + fileNode + " with algorithm " + algorithm, e);
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot checksum file " + fileNode + " with algorithm " + algorithm, e);
+ }
+ }
+
+ @Deprecated
+ private static String digest(String algorithm, InputStream in) {
+ final Integer byteBufferCapacity = 100 * 1024;// 100 KB
+ try {
+ MessageDigest digest = MessageDigest.getInstance(algorithm);
+ byte[] buffer = new byte[byteBufferCapacity];
+ int read = 0;
+ while ((read = in.read(buffer)) > 0) {
+ digest.update(buffer, 0, read);
+ }
+
+ byte[] checksum = digest.digest();
+ String res = encodeHexString(checksum);
+ return res;
+ } catch (IOException e) {
+ throw new RuntimeException("Cannot digest with algorithm " + algorithm, e);
+ } catch (NoSuchAlgorithmException e) {
+ throw new IllegalArgumentException("Cannot digest with algorithm " + algorithm, e);
+ }
+ }
+
+ /**
+ * From
+ * http://stackoverflow.com/questions/9655181/how-to-convert-a-byte-array-to
+ * -a-hex-string-in-java
+ */
+ @Deprecated
+ private static String encodeHexString(byte[] bytes) {
+ final char[] hexArray = "0123456789abcdef".toCharArray();
+ char[] hexChars = new char[bytes.length * 2];
+ for (int j = 0; j < bytes.length; j++) {
+ int v = bytes[j] & 0xFF;
+ hexChars[j * 2] = hexArray[v >>> 4];
+ hexChars[j * 2 + 1] = hexArray[v & 0x0F];
+ }
+ return new String(hexChars);
+ }
+
+ /** Export a subtree as a compact XML without namespaces. */
+ public static void toSimpleXml(Node node, StringBuilder sb) throws RepositoryException {
+ sb.append('<');
+ String nodeName = node.getName();
+ int colIndex = nodeName.indexOf(':');
+ if (colIndex > 0) {
+ nodeName = nodeName.substring(colIndex + 1);
+ }
+ sb.append(nodeName);
+ PropertyIterator pit = node.getProperties();
+ properties: while (pit.hasNext()) {
+ Property p = pit.nextProperty();
+ // skip multiple properties
+ if (p.isMultiple())
+ continue properties;
+ String propertyName = p.getName();
+ int pcolIndex = propertyName.indexOf(':');
+ // skip properties with namespaces
+ if (pcolIndex > 0)
+ continue properties;
+ // skip binaries
+ if (p.getType() == PropertyType.BINARY) {
+ continue properties;
+ // TODO retrieve identifier?
+ }
+ sb.append(' ');
+ sb.append(propertyName);
+ sb.append('=');
+ sb.append('\"').append(p.getString()).append('\"');
+ }
+
+ if (node.hasNodes()) {
+ sb.append('>');
+ NodeIterator children = node.getNodes();
+ while (children.hasNext()) {
+ toSimpleXml(children.nextNode(), sb);
+ }
+ sb.append("</");
+ sb.append(nodeName);
+ sb.append('>');
+ } else {
+ sb.append("/>");
+ }
+ }
+
+}
--- /dev/null
+package org.argeo.jcr;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.RepositoryException;
+import javax.jcr.Value;
+
+/** Uilities around the JCR extensions. */
+public class JcrxApi {
+ public final static String MD5 = "MD5";
+ public final static String SHA1 = "SHA1";
+ public final static String SHA256 = "SHA-256";
+ public final static String SHA512 = "SHA-512";
+
+ public final static String EMPTY_MD5 = "d41d8cd98f00b204e9800998ecf8427e";
+ public final static String EMPTY_SHA1 = "da39a3ee5e6b4b0d3255bfef95601890afd80709";
+ public final static String EMPTY_SHA256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
+ public final static String EMPTY_SHA512 = "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e";
+
+ public final static int LENGTH_MD5 = EMPTY_MD5.length();
+ public final static int LENGTH_SHA1 = EMPTY_SHA1.length();
+ public final static int LENGTH_SHA256 = EMPTY_SHA256.length();
+ public final static int LENGTH_SHA512 = EMPTY_SHA512.length();
+
+ /*
+ * XML
+ */
+ /**
+ * Get the XML text of this child node.
+ */
+ public static String getXmlValue(Node node, String name) {
+ try {
+ if (!node.hasNode(name))
+ return null;
+ Node child = node.getNode(name);
+ return getXmlValue(child);
+ } catch (RepositoryException e) {
+ throw new IllegalStateException("Cannot get " + name + " as XML text", e);
+ }
+ }
+
+ /**
+ * Get the XML text of this node.
+ */
+ public static String getXmlValue(Node node) {
+ try {
+ if (!node.hasNode(Jcr.JCR_XMLTEXT))
+ return null;
+ Node xmlText = node.getNode(Jcr.JCR_XMLTEXT);
+ if (!xmlText.hasProperty(Jcr.JCR_XMLCHARACTERS))
+ throw new IllegalArgumentException(
+ "Node " + xmlText + " has no " + Jcr.JCR_XMLCHARACTERS + " property");
+ return xmlText.getProperty(Jcr.JCR_XMLCHARACTERS).getString();
+ } catch (RepositoryException e) {
+ throw new IllegalStateException("Cannot get " + node + " as XML text", e);
+ }
+ }
+
+ /**
+ * Set as a subnode which will be exported as an XML element.
+ */
+ public static void setXmlValue(Node node, String name, String value) {
+ try {
+ if (node.hasNode(name)) {
+ Node child = node.getNode(name);
+ setXmlValue(node, child, value);
+ } else
+ node.addNode(name, JcrxType.JCRX_XMLVALUE).addNode(Jcr.JCR_XMLTEXT, JcrxType.JCRX_XMLTEXT)
+ .setProperty(Jcr.JCR_XMLCHARACTERS, value);
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot set " + name + " as XML text", e);
+ }
+ }
+
+ public static void setXmlValue(Node node, Node child, String value) {
+ try {
+ if (!child.hasNode(Jcr.JCR_XMLTEXT))
+ child.addNode(Jcr.JCR_XMLTEXT, JcrxType.JCRX_XMLTEXT);
+ child.getNode(Jcr.JCR_XMLTEXT).setProperty(Jcr.JCR_XMLCHARACTERS, value);
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot set " + child + " as XML text", e);
+ }
+ }
+
+ /**
+ * Add a checksum replacing the one which was previously set with the same
+ * length.
+ */
+ public static void addChecksum(Node node, String checksum) {
+ try {
+ if (!node.hasProperty(JcrxName.JCRX_SUM)) {
+ node.setProperty(JcrxName.JCRX_SUM, new String[] { checksum });
+ return;
+ } else {
+ int stringLength = checksum.length();
+ Property property = node.getProperty(JcrxName.JCRX_SUM);
+ List<Value> values = Arrays.asList(property.getValues());
+ Integer indexToRemove = null;
+ values: for (int i = 0; i < values.size(); i++) {
+ Value value = values.get(i);
+ if (value.getString().length() == stringLength) {
+ indexToRemove = i;
+ break values;
+ }
+ }
+ if (indexToRemove != null)
+ values.set(indexToRemove, node.getSession().getValueFactory().createValue(checksum));
+ else
+ values.add(0, node.getSession().getValueFactory().createValue(checksum));
+ property.setValue(values.toArray(new Value[values.size()]));
+ }
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot set checksum on " + node, e);
+ }
+ }
+
+ /** Replace all checksums. */
+ public static void setChecksums(Node node, List<String> checksums) {
+ try {
+ node.setProperty(JcrxName.JCRX_SUM, checksums.toArray(new String[checksums.size()]));
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot set checksums on " + node, e);
+ }
+ }
+
+ /** Replace all checksums. */
+ public static List<String> getChecksums(Node node) {
+ try {
+ List<String> res = new ArrayList<>();
+ if (!node.hasProperty(JcrxName.JCRX_SUM))
+ return res;
+ Property property = node.getProperty(JcrxName.JCRX_SUM);
+ for (Value value : property.getValues()) {
+ res.add(value.getString());
+ }
+ return res;
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot get checksums from " + node, e);
+ }
+ }
+
+// /** Replace all checksums with this single one. */
+// public static void setChecksum(Node node, String checksum) {
+// setChecksums(node, Collections.singletonList(checksum));
+// }
+
+ /** Retrieves the checksum with this algorithm, or null if not found. */
+ public static String getChecksum(Node node, String algorithm) {
+ int stringLength;
+ switch (algorithm) {
+ case MD5:
+ stringLength = LENGTH_MD5;
+ break;
+ case SHA1:
+ stringLength = LENGTH_SHA1;
+ break;
+ case SHA256:
+ stringLength = LENGTH_SHA256;
+ break;
+ case SHA512:
+ stringLength = LENGTH_SHA512;
+ break;
+ default:
+ throw new IllegalArgumentException("Unkown algorithm " + algorithm);
+ }
+ return getChecksum(node, stringLength);
+ }
+
+ /** Retrieves the checksum with this string length, or null if not found. */
+ public static String getChecksum(Node node, int stringLength) {
+ try {
+ if (!node.hasProperty(JcrxName.JCRX_SUM))
+ return null;
+ Property property = node.getProperty(JcrxName.JCRX_SUM);
+ for (Value value : property.getValues()) {
+ String str = value.getString();
+ if (str.length() == stringLength)
+ return str;
+ }
+ return null;
+ } catch (RepositoryException e) {
+ throw new IllegalStateException("Cannot get checksum for " + node, e);
+ }
+ }
+
+}
--- /dev/null
+package org.argeo.jcr;
+
+/** Names declared by the JCR extensions. */
+public interface JcrxName {
+ /** The multiple property holding various coherent checksums. */
+ public final static String JCRX_SUM = "{http://www.argeo.org/ns/jcrx}sum";
+}
--- /dev/null
+package org.argeo.jcr;
+
+/** Node types declared by the JCR extensions. */
+public interface JcrxType {
+ /**
+ * Node type for an XML value, which will be serialized in XML as an element
+ * containing text.
+ */
+ public final static String JCRX_XMLVALUE = "{http://www.argeo.org/ns/jcrx}xmlvalue";
+
+ /** Node type for the node containing the text. */
+ public final static String JCRX_XMLTEXT = "{http://www.argeo.org/ns/jcrx}xmltext";
+
+ /** Mixin node type for a set of checksums. */
+ public final static String JCRX_CSUM = "{http://www.argeo.org/ns/jcrx}csum";
+
+}
--- /dev/null
+package org.argeo.jcr;
+
+import javax.jcr.Value;
+
+/** The result of the comparison of two JCR properties. */
+public class PropertyDiff {
+ public final static Integer MODIFIED = 0;
+ public final static Integer ADDED = 1;
+ public final static Integer REMOVED = 2;
+
+ private final Integer type;
+ private final String relPath;
+ private final Value referenceValue;
+ private final Value newValue;
+
+ public PropertyDiff(Integer type, String relPath, Value referenceValue, Value newValue) {
+ super();
+
+ if (type == MODIFIED) {
+ if (referenceValue == null || newValue == null)
+ throw new IllegalArgumentException("Reference and new values must be specified.");
+ } else if (type == ADDED) {
+ if (referenceValue != null || newValue == null)
+ throw new IllegalArgumentException("New value and only it must be specified.");
+ } else if (type == REMOVED) {
+ if (referenceValue == null || newValue != null)
+ throw new IllegalArgumentException("Reference value and only it must be specified.");
+ } else {
+ throw new IllegalArgumentException("Unkown diff type " + type);
+ }
+
+ if (relPath == null)
+ throw new IllegalArgumentException("Relative path must be specified");
+
+ this.type = type;
+ this.relPath = relPath;
+ this.referenceValue = referenceValue;
+ this.newValue = newValue;
+ }
+
+ public Integer getType() {
+ return type;
+ }
+
+ public String getRelPath() {
+ return relPath;
+ }
+
+ public Value getReferenceValue() {
+ return referenceValue;
+ }
+
+ public Value getNewValue() {
+ return newValue;
+ }
+
+}
--- /dev/null
+package org.argeo.jcr;
+
+import java.security.Principal;
+
+/** Canonical implementation of a {@link Principal} */
+class SimplePrincipal implements Principal {
+ private final String name;
+
+ public SimplePrincipal(String name) {
+ if (name == null)
+ throw new IllegalArgumentException("Principal name cannot be null");
+ this.name = name;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public int hashCode() {
+ return name.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null)
+ return false;
+ if (obj instanceof Principal)
+ return name.equals((((Principal) obj).getName()));
+ return name.equals(obj.toString());
+ }
+
+ @Override
+ protected Object clone() throws CloneNotSupportedException {
+ return new SimplePrincipal(name);
+ }
+
+ @Override
+ public String toString() {
+ return name;
+ }
+
+}
--- /dev/null
+package org.argeo.jcr;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import javax.jcr.LoginException;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.SimpleCredentials;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/** Proxy JCR sessions and attach them to calling threads. */
+@Deprecated
+public abstract class ThreadBoundJcrSessionFactory {
+ private final static Log log = LogFactory.getLog(ThreadBoundJcrSessionFactory.class);
+
+ private Repository repository;
+ /** can be injected as list, only used if repository is null */
+ private List<Repository> repositories;
+
+ private ThreadLocal<Session> session = new ThreadLocal<Session>();
+ private final Session proxiedSession;
+ /** If workspace is null, default will be used. */
+ private String workspace = null;
+
+ private String defaultUsername = "demo";
+ private String defaultPassword = "demo";
+ private Boolean forceDefaultCredentials = false;
+
+ private boolean active = true;
+
+ // monitoring
+ private final List<Thread> threads = Collections.synchronizedList(new ArrayList<Thread>());
+ private final Map<Long, Session> activeSessions = Collections.synchronizedMap(new HashMap<Long, Session>());
+ private MonitoringThread monitoringThread;
+
+ public ThreadBoundJcrSessionFactory() {
+ Class<?>[] interfaces = { Session.class };
+ proxiedSession = (Session) Proxy.newProxyInstance(ThreadBoundJcrSessionFactory.class.getClassLoader(),
+ interfaces, new JcrSessionInvocationHandler());
+ }
+
+ /** Logs in to the repository using various strategies. */
+ protected synchronized Session login() {
+ if (!isActive())
+ throw new IllegalStateException("Thread bound session factory inactive");
+
+ // discard session previously attached to this thread
+ Thread thread = Thread.currentThread();
+ if (activeSessions.containsKey(thread.getId())) {
+ Session oldSession = activeSessions.remove(thread.getId());
+ oldSession.logout();
+ session.remove();
+ }
+
+ Session newSession = null;
+ // first try to login without credentials, assuming the underlying login
+ // module will have dealt with authentication (typically using Spring
+ // Security)
+ if (!forceDefaultCredentials)
+ try {
+ newSession = repository().login(workspace);
+ } catch (LoginException e1) {
+ log.warn("Cannot login without credentials: " + e1.getMessage());
+ // invalid credentials, go to the next step
+ } catch (RepositoryException e1) {
+ // other kind of exception, fail
+ throw new JcrException("Cannot log in to repository", e1);
+ }
+
+ // log using default username / password (useful for testing purposes)
+ if (newSession == null)
+ try {
+ SimpleCredentials sc = new SimpleCredentials(defaultUsername, defaultPassword.toCharArray());
+ newSession = repository().login(sc, workspace);
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot log in to repository", e);
+ }
+
+ session.set(newSession);
+ // Log and monitor new session
+ if (log.isTraceEnabled())
+ log.trace("Logged in to JCR session " + newSession + "; userId=" + newSession.getUserID());
+
+ // monitoring
+ activeSessions.put(thread.getId(), newSession);
+ threads.add(thread);
+ return newSession;
+ }
+
+ public Object getObject() {
+ return proxiedSession;
+ }
+
+ public void init() throws Exception {
+ // log.error("SHOULD NOT BE USED ANYMORE");
+ monitoringThread = new MonitoringThread();
+ monitoringThread.start();
+ }
+
+ public void dispose() throws Exception {
+ // if (activeSessions.size() == 0)
+ // return;
+
+ if (log.isTraceEnabled())
+ log.trace("Cleaning up " + activeSessions.size() + " active JCR sessions...");
+
+ deactivate();
+ for (Session sess : activeSessions.values()) {
+ JcrUtils.logoutQuietly(sess);
+ }
+ activeSessions.clear();
+ }
+
+ protected Boolean isActive() {
+ return active;
+ }
+
+ protected synchronized void deactivate() {
+ active = false;
+ notifyAll();
+ }
+
+ protected synchronized void removeSession(Thread thread) {
+ if (!isActive())
+ return;
+ activeSessions.remove(thread.getId());
+ threads.remove(thread);
+ }
+
+ protected synchronized void cleanDeadThreads() {
+ if (!isActive())
+ return;
+ Iterator<Thread> it = threads.iterator();
+ while (it.hasNext()) {
+ Thread thread = it.next();
+ if (!thread.isAlive() && isActive()) {
+ if (activeSessions.containsKey(thread.getId())) {
+ Session session = activeSessions.get(thread.getId());
+ activeSessions.remove(thread.getId());
+ session.logout();
+ if (log.isTraceEnabled())
+ log.trace("Cleaned up JCR session (userID=" + session.getUserID() + ") from dead thread "
+ + thread.getId());
+ }
+ it.remove();
+ }
+ }
+ try {
+ wait(1000);
+ } catch (InterruptedException e) {
+ // silent
+ }
+ }
+
+ public Class<? extends Session> getObjectType() {
+ return Session.class;
+ }
+
+ public boolean isSingleton() {
+ return true;
+ }
+
+ /**
+ * Called before a method is actually called, allowing to check the session or
+ * re-login it (e.g. if authentication has changed). The default implementation
+ * returns the session.
+ */
+ protected Session preCall(Session session) {
+ return session;
+ }
+
+ protected Repository repository() {
+ if (repository != null)
+ return repository;
+ if (repositories != null) {
+ // hardened for OSGi dynamic services
+ Iterator<Repository> it = repositories.iterator();
+ if (it.hasNext())
+ return it.next();
+ }
+ throw new IllegalStateException("No repository injected");
+ }
+
+ // /** Useful for declarative registration of OSGi services (blueprint) */
+ // public void register(Repository repository, Map<?, ?> params) {
+ // this.repository = repository;
+ // }
+ //
+ // /** Useful for declarative registration of OSGi services (blueprint) */
+ // public void unregister(Repository repository, Map<?, ?> params) {
+ // this.repository = null;
+ // }
+
+ public void setRepository(Repository repository) {
+ this.repository = repository;
+ }
+
+ public void setRepositories(List<Repository> repositories) {
+ this.repositories = repositories;
+ }
+
+ public void setDefaultUsername(String defaultUsername) {
+ this.defaultUsername = defaultUsername;
+ }
+
+ public void setDefaultPassword(String defaultPassword) {
+ this.defaultPassword = defaultPassword;
+ }
+
+ public void setForceDefaultCredentials(Boolean forceDefaultCredentials) {
+ this.forceDefaultCredentials = forceDefaultCredentials;
+ }
+
+ public void setWorkspace(String workspace) {
+ this.workspace = workspace;
+ }
+
+ protected class JcrSessionInvocationHandler implements InvocationHandler {
+
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable, RepositoryException {
+ Session threadSession = session.get();
+ if (threadSession == null) {
+ if ("logout".equals(method.getName()))// no need to login
+ return Void.TYPE;
+ else if ("toString".equals(method.getName()))// maybe logging
+ return "Uninitialized Argeo thread bound JCR session";
+ threadSession = login();
+ }
+
+ preCall(threadSession);
+ Object ret;
+ try {
+ ret = method.invoke(threadSession, args);
+ } catch (InvocationTargetException e) {
+ Throwable cause = e.getCause();
+ if (cause instanceof RepositoryException)
+ throw (RepositoryException) cause;
+ else
+ throw cause;
+ }
+ if ("logout".equals(method.getName())) {
+ session.remove();
+ Thread thread = Thread.currentThread();
+ removeSession(thread);
+ if (log.isTraceEnabled())
+ log.trace("Logged out JCR session (userId=" + threadSession.getUserID() + ") on thread "
+ + thread.getId());
+ }
+ return ret;
+ }
+ }
+
+ /** Monitors registered thread in order to clean up dead ones. */
+ private class MonitoringThread extends Thread {
+
+ public MonitoringThread() {
+ super("ThreadBound JCR Session Monitor");
+ }
+
+ @Override
+ public void run() {
+ while (isActive()) {
+ cleanDeadThreads();
+ }
+ }
+
+ }
+}
--- /dev/null
+package org.argeo.jcr;
+
+import java.util.Calendar;
+import java.util.Map;
+
+/**
+ * Generic Object that enables the creation of history reports based on a JCR
+ * versionable node. userId and creation date are added to the map of
+ * PropertyDiff.
+ *
+ * These two fields might be null
+ *
+ */
+public class VersionDiff {
+
+ private String userId;
+ private Map<String, PropertyDiff> diffs;
+ private Calendar updateTime;
+
+ public VersionDiff(String userId, Calendar updateTime,
+ Map<String, PropertyDiff> diffs) {
+ this.userId = userId;
+ this.updateTime = updateTime;
+ this.diffs = diffs;
+ }
+
+ public String getUserId() {
+ return userId;
+ }
+
+ public Map<String, PropertyDiff> getDiffs() {
+ return diffs;
+ }
+
+ public Calendar getUpdateTime() {
+ return updateTime;
+ }
+}
--- /dev/null
+package org.argeo.jcr.fs;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.channels.Channels;
+import java.nio.channels.FileChannel;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.channels.SeekableByteChannel;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+
+import javax.jcr.Binary;
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+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;
+ private boolean open = true;
+
+ private long position = 0;
+
+ private FileChannel fc = null;
+
+ public BinaryChannel(Node file, Path path) throws RepositoryException, IOException {
+ this.file = file;
+ 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();
+ }
+ } else {
+ throw new IllegalArgumentException(
+ "Unsupported file node " + file + " (" + file.getPrimaryNodeType() + ")");
+ }
+ }
+ }
+
+ @Override
+ public synchronized boolean isOpen() {
+ return open;
+ }
+
+ @Override
+ public synchronized void close() throws IOException {
+ if (isModified()) {
+ Binary newBinary = null;
+ try {
+ Session session = file.getSession();
+ 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 {
+ JcrUtils.closeQuietly(newBinary);
+ // IOUtils.closeQuietly(fc);
+ if (fc != null) {
+ fc.close();
+ }
+ }
+ } else {
+ clearReadState();
+ open = false;
+ }
+ }
+
+ @Override
+ public int read(ByteBuffer dst) throws IOException {
+ if (isModified()) {
+ return fc.read(dst);
+ } else {
+
+ try {
+ int read;
+ byte[] arr = dst.array();
+ read = binary.read(arr, position);
+
+ if (read != -1)
+ position = position + read;
+ return read;
+ } catch (RepositoryException e) {
+ throw new IOException("Cannot read into buffer", e);
+ }
+ }
+ }
+
+ @Override
+ public int write(ByteBuffer src) throws IOException {
+ int written = getFileChannel().write(src);
+ return written;
+ }
+
+ @Override
+ public long position() throws IOException {
+ if (isModified())
+ return getFileChannel().position();
+ else
+ return position;
+ }
+
+ @Override
+ public SeekableByteChannel position(long newPosition) throws IOException {
+ if (isModified()) {
+ getFileChannel().position(position);
+ } else {
+ this.position = newPosition;
+ }
+ return this;
+ }
+
+ @Override
+ public long size() throws IOException {
+ if (isModified()) {
+ return getFileChannel().size();
+ } else {
+ try {
+ return binary.getSize();
+ } catch (RepositoryException e) {
+ throw new IOException("Cannot get size", e);
+ }
+ }
+ }
+
+ @Override
+ public SeekableByteChannel truncate(long size) throws IOException {
+ getFileChannel().truncate(size);
+ return this;
+ }
+
+ private FileChannel getFileChannel() throws IOException {
+ try {
+ if (fc == null) {
+ Path tempPath = Files.createTempFile(getClass().getSimpleName(), null);
+ fc = FileChannel.open(tempPath, StandardOpenOption.WRITE, StandardOpenOption.READ,
+ StandardOpenOption.DELETE_ON_CLOSE, StandardOpenOption.SPARSE);
+ ReadableByteChannel readChannel = Channels.newChannel(binary.getStream());
+ fc.transferFrom(readChannel, 0, binary.getSize());
+ clearReadState();
+ }
+ return fc;
+ } catch (RepositoryException e) {
+ throw new IOException("Cannot get temp file channel", e);
+ }
+ }
+
+ private boolean isModified() {
+ return fc != null;
+ }
+
+ private void clearReadState() {
+ position = -1;
+ JcrUtils.closeQuietly(binary);
+ binary = null;
+ }
+}
--- /dev/null
+package org.argeo.jcr.fs;
+
+import static javax.jcr.Property.JCR_CREATED;
+import static javax.jcr.Property.JCR_LAST_MODIFIED;
+
+import java.nio.file.attribute.FileTime;
+import java.time.Instant;
+
+import javax.jcr.Binary;
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.RepositoryException;
+import javax.jcr.nodetype.NodeType;
+
+import org.argeo.jcr.JcrUtils;
+
+public class JcrBasicfileAttributes implements NodeFileAttributes {
+ private final Node node;
+
+ private final static FileTime EPOCH = FileTime.fromMillis(0);
+
+ public JcrBasicfileAttributes(Node node) {
+ if (node == null)
+ throw new JcrFsException("Node underlying the attributes cannot be null");
+ this.node = node;
+ }
+
+ @Override
+ public FileTime lastModifiedTime() {
+ try {
+ if (node.hasProperty(JCR_LAST_MODIFIED)) {
+ Instant instant = node.getProperty(JCR_LAST_MODIFIED).getDate().toInstant();
+ return FileTime.from(instant);
+ } else if (node.hasProperty(JCR_CREATED)) {
+ Instant instant = node.getProperty(JCR_CREATED).getDate().toInstant();
+ return FileTime.from(instant);
+ }
+// if (node.isNodeType(NodeType.MIX_LAST_MODIFIED)) {
+// Instant instant = node.getProperty(Property.JCR_LAST_MODIFIED).getDate().toInstant();
+// return FileTime.from(instant);
+// }
+ return EPOCH;
+ } catch (RepositoryException e) {
+ throw new JcrFsException("Cannot get last modified time", e);
+ }
+ }
+
+ @Override
+ public FileTime lastAccessTime() {
+ return lastModifiedTime();
+ }
+
+ @Override
+ public FileTime creationTime() {
+ try {
+ if (node.hasProperty(JCR_CREATED)) {
+ Instant instant = node.getProperty(JCR_CREATED).getDate().toInstant();
+ return FileTime.from(instant);
+ } else if (node.hasProperty(JCR_LAST_MODIFIED)) {
+ Instant instant = node.getProperty(JCR_LAST_MODIFIED).getDate().toInstant();
+ return FileTime.from(instant);
+ }
+// if (node.isNodeType(NodeType.MIX_CREATED)) {
+// Instant instant = node.getProperty(JCR_CREATED).getDate().toInstant();
+// return FileTime.from(instant);
+// }
+ return EPOCH;
+ } catch (RepositoryException e) {
+ throw new JcrFsException("Cannot get creation time", e);
+ }
+ }
+
+ @Override
+ public boolean isRegularFile() {
+ try {
+ return node.isNodeType(NodeType.NT_FILE);
+ } catch (RepositoryException e) {
+ throw new JcrFsException("Cannot check if regular file", e);
+ }
+ }
+
+ @Override
+ public boolean isDirectory() {
+ try {
+ if (node.isNodeType(NodeType.NT_FOLDER))
+ return true;
+ // all other non file nodes
+ return !(node.isNodeType(NodeType.NT_FILE) || node.isNodeType(NodeType.NT_LINKED_FILE));
+ } catch (RepositoryException e) {
+ throw new JcrFsException("Cannot check if directory", e);
+ }
+ }
+
+ @Override
+ public boolean isSymbolicLink() {
+ try {
+ return node.isNodeType(NodeType.NT_LINKED_FILE);
+ } catch (RepositoryException e) {
+ throw new JcrFsException("Cannot check if linked file", e);
+ }
+ }
+
+ @Override
+ public boolean isOther() {
+ return !(isDirectory() || isRegularFile() || isSymbolicLink());
+ }
+
+ @Override
+ public long size() {
+ if (isRegularFile()) {
+ Binary binary = null;
+ try {
+ binary = node.getNode(Property.JCR_CONTENT).getProperty(Property.JCR_DATA).getBinary();
+ return binary.getSize();
+ } catch (RepositoryException e) {
+ throw new JcrFsException("Cannot check size", e);
+ } finally {
+ JcrUtils.closeQuietly(binary);
+ }
+ }
+ return -1;
+ }
+
+ @Override
+ public Object fileKey() {
+ try {
+ return node.getIdentifier();
+ } catch (RepositoryException e) {
+ throw new JcrFsException("Cannot get identifier", e);
+ }
+ }
+
+ @Override
+ public Node getNode() {
+ return node;
+ }
+
+}
--- /dev/null
+package org.argeo.jcr.fs;
+
+import java.io.IOException;
+import java.nio.file.FileStore;
+import java.nio.file.FileSystem;
+import java.nio.file.Path;
+import java.nio.file.PathMatcher;
+import java.nio.file.WatchService;
+import java.nio.file.attribute.UserPrincipalLookupService;
+import java.nio.file.spi.FileSystemProvider;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+
+import javax.jcr.Credentials;
+import javax.jcr.Node;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.nodetype.NodeType;
+
+import org.argeo.jcr.Jcr;
+import org.argeo.jcr.JcrUtils;
+
+public class JcrFileSystem extends FileSystem {
+ private final JcrFileSystemProvider provider;
+
+ private final Repository repository;
+ private Session session;
+ private WorkspaceFileStore baseFileStore;
+
+ private Map<String, WorkspaceFileStore> mounts = new TreeMap<>();
+
+ private String userHomePath = null;
+
+ @Deprecated
+ public JcrFileSystem(JcrFileSystemProvider provider, Session session) throws IOException {
+ super();
+ this.provider = provider;
+ baseFileStore = new WorkspaceFileStore(null, session.getWorkspace());
+ this.session = session;
+// Node userHome = provider.getUserHome(session);
+// if (userHome != null)
+// try {
+// userHomePath = userHome.getPath();
+// } catch (RepositoryException e) {
+// throw new IOException("Cannot retrieve user home path", e);
+// }
+ this.repository = null;
+ }
+
+ public JcrFileSystem(JcrFileSystemProvider provider, Repository repository) throws IOException {
+ this(provider, repository, null);
+ }
+
+ public JcrFileSystem(JcrFileSystemProvider provider, Repository repository, Credentials credentials)
+ throws IOException {
+ super();
+ this.provider = provider;
+ this.repository = repository;
+ try {
+ this.session = credentials == null ? repository.login() : repository.login(credentials);
+ baseFileStore = new WorkspaceFileStore(null, session.getWorkspace());
+ workspaces: for (String workspaceName : baseFileStore.getWorkspace().getAccessibleWorkspaceNames()) {
+ if (workspaceName.equals(baseFileStore.getWorkspace().getName()))
+ continue workspaces;// do not mount base
+ if (workspaceName.equals("security")) {
+ continue workspaces;// do not mount security workspace
+ // TODO make it configurable
+ }
+ Session mountSession = credentials == null ? repository.login(workspaceName)
+ : repository.login(credentials, workspaceName);
+ String mountPath = JcrPath.separator + workspaceName;
+ mounts.put(mountPath, new WorkspaceFileStore(mountPath, mountSession.getWorkspace()));
+ }
+ } catch (RepositoryException e) {
+ throw new IOException("Cannot initialise file system", e);
+ }
+
+ Node userHome = provider.getUserHome(repository);
+ if (userHome != null)
+ try {
+ userHomePath = toFsPath(userHome);
+ } catch (RepositoryException e) {
+ throw new IOException("Cannot retrieve user home path", e);
+ } finally {
+ JcrUtils.logoutQuietly(Jcr.session(userHome));
+ }
+ }
+
+ public String toFsPath(Node node) throws RepositoryException {
+ return getFileStore(node).toFsPath(node);
+ }
+
+ /** Whether this node should be skipped in directory listings */
+ public boolean skipNode(Node node) throws RepositoryException {
+ if (node.isNodeType(NodeType.NT_HIERARCHY_NODE))
+ return false;
+ return true;
+ }
+
+ public String getUserHomePath() {
+ return userHomePath;
+ }
+
+ public WorkspaceFileStore getFileStore(String path) {
+ WorkspaceFileStore res = baseFileStore;
+ for (String mountPath : mounts.keySet()) {
+ if (path.equals(mountPath))
+ return mounts.get(mountPath);
+ if (path.startsWith(mountPath + JcrPath.separator)) {
+ res = mounts.get(mountPath);
+ // we keep the last one
+ }
+ }
+ assert res != null;
+ return res;
+ }
+
+ public WorkspaceFileStore getFileStore(Node node) throws RepositoryException {
+ String workspaceName = node.getSession().getWorkspace().getName();
+ if (workspaceName.equals(baseFileStore.getWorkspace().getName()))
+ return baseFileStore;
+ for (String mountPath : mounts.keySet()) {
+ WorkspaceFileStore fileStore = mounts.get(mountPath);
+ if (workspaceName.equals(fileStore.getWorkspace().getName()))
+ return fileStore;
+ }
+ throw new IllegalStateException("No workspace mount found for " + node + " in workspace " + workspaceName);
+ }
+
+ public Iterator<JcrPath> listDirectMounts(Path base) {
+ String baseStr = base.toString();
+ Set<JcrPath> res = new HashSet<>();
+ mounts: for (String mountPath : mounts.keySet()) {
+ if (mountPath.equals(baseStr))
+ continue mounts;
+ if (mountPath.startsWith(baseStr)) {
+ JcrPath path = new JcrPath(this, mountPath);
+ Path relPath = base.relativize(path);
+ if (relPath.getNameCount() == 1)
+ res.add(path);
+ }
+ }
+ return res.iterator();
+ }
+
+ public WorkspaceFileStore getBaseFileStore() {
+ return baseFileStore;
+ }
+
+ @Override
+ public FileSystemProvider provider() {
+ return provider;
+ }
+
+ @Override
+ public void close() throws IOException {
+ JcrUtils.logoutQuietly(session);
+ for (String mountPath : mounts.keySet()) {
+ WorkspaceFileStore fileStore = mounts.get(mountPath);
+ try {
+ fileStore.close();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ @Override
+ public boolean isOpen() {
+ return session.isLive();
+ }
+
+ @Override
+ public boolean isReadOnly() {
+ return false;
+ }
+
+ @Override
+ public String getSeparator() {
+ return JcrPath.separator;
+ }
+
+ @Override
+ public Iterable<Path> getRootDirectories() {
+ Set<Path> single = new HashSet<>();
+ single.add(new JcrPath(this, JcrPath.separator));
+ return single;
+ }
+
+ @Override
+ public Iterable<FileStore> getFileStores() {
+ List<FileStore> stores = new ArrayList<>();
+ stores.add(baseFileStore);
+ stores.addAll(mounts.values());
+ return stores;
+ }
+
+ @Override
+ public Set<String> supportedFileAttributeViews() {
+ try {
+ String[] prefixes = session.getNamespacePrefixes();
+ Set<String> res = new HashSet<>();
+ for (String prefix : prefixes)
+ res.add(prefix);
+ res.add("basic");
+ return res;
+ } catch (RepositoryException e) {
+ throw new JcrFsException("Cannot get supported file attributes views", e);
+ }
+ }
+
+ @Override
+ public Path getPath(String first, String... more) {
+ StringBuilder sb = new StringBuilder(first);
+ // TODO Make it more robust
+ for (String part : more)
+ sb.append('/').append(part);
+ return new JcrPath(this, sb.toString());
+ }
+
+ @Override
+ public PathMatcher getPathMatcher(String syntaxAndPattern) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public UserPrincipalLookupService getUserPrincipalLookupService() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public WatchService newWatchService() throws IOException {
+ throw new UnsupportedOperationException();
+ }
+
+// public Session getSession() {
+// return session;
+// }
+
+ public Repository getRepository() {
+ return repository;
+ }
+
+}
--- /dev/null
+package org.argeo.jcr.fs;
+
+import java.io.IOException;
+import java.nio.channels.SeekableByteChannel;
+import java.nio.file.AccessMode;
+import java.nio.file.CopyOption;
+import java.nio.file.DirectoryNotEmptyException;
+import java.nio.file.DirectoryStream;
+import java.nio.file.DirectoryStream.Filter;
+import java.nio.file.FileStore;
+import java.nio.file.LinkOption;
+import java.nio.file.NoSuchFileException;
+import java.nio.file.OpenOption;
+import java.nio.file.Path;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.nio.file.attribute.FileAttribute;
+import java.nio.file.attribute.FileAttributeView;
+import java.nio.file.spi.FileSystemProvider;
+import java.util.Calendar;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.PropertyIterator;
+import javax.jcr.PropertyType;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.nodetype.NodeType;
+import javax.jcr.nodetype.PropertyDefinition;
+
+import org.argeo.jcr.JcrUtils;
+
+/** Operations on a {@link JcrFileSystem}. */
+public abstract class JcrFileSystemProvider extends FileSystemProvider {
+
+ @Override
+ public SeekableByteChannel newByteChannel(Path path, Set<? extends OpenOption> options, FileAttribute<?>... attrs)
+ throws IOException {
+ Node node = toNode(path);
+ try {
+ if (node == null) {
+ Node parent = toNode(path.getParent());
+ if (parent == null)
+ throw new IOException("No parent directory for " + path);
+ if (parent.getPrimaryNodeType().isNodeType(NodeType.NT_FILE)
+ || parent.getPrimaryNodeType().isNodeType(NodeType.NT_LINKED_FILE))
+ throw new IOException(path + " parent is a file");
+
+ String fileName = path.getFileName().toString();
+ fileName = Text.escapeIllegalJcrChars(fileName);
+ node = parent.addNode(fileName, NodeType.NT_FILE);
+ node.addMixin(NodeType.MIX_CREATED);
+// node.addMixin(NodeType.MIX_LAST_MODIFIED);
+ }
+ if (!node.isNodeType(NodeType.NT_FILE))
+ throw new UnsupportedOperationException(node + " must be a file");
+ return new BinaryChannel(node, path);
+ } catch (RepositoryException e) {
+ discardChanges(node);
+ throw new IOException("Cannot read file", e);
+ }
+ }
+
+ @Override
+ public DirectoryStream<Path> newDirectoryStream(Path dir, Filter<? super Path> filter) throws IOException {
+ try {
+ Node base = toNode(dir);
+ if (base == null)
+ throw new IOException(dir + " is not a JCR node");
+ JcrFileSystem fileSystem = (JcrFileSystem) dir.getFileSystem();
+ return new NodeDirectoryStream(fileSystem, base.getNodes(), fileSystem.listDirectMounts(dir), filter);
+ } catch (RepositoryException e) {
+ throw new IOException("Cannot list directory", e);
+ }
+ }
+
+ @Override
+ public void createDirectory(Path dir, FileAttribute<?>... attrs) throws IOException {
+ Node node = toNode(dir);
+ try {
+ if (node == null) {
+ Node parent = toNode(dir.getParent());
+ if (parent == null)
+ throw new IOException("Parent of " + dir + " does not exist");
+ 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");
+ }
+ } catch (RepositoryException e) {
+ discardChanges(node);
+ throw new IOException("Cannot create directory " + dir, e);
+ }
+ }
+
+ @Override
+ public void delete(Path path) throws IOException {
+ Node node = toNode(path);
+ try {
+ if (node == null)
+ throw new NoSuchFileException(path + " does not exist");
+ Session session = node.getSession();
+ 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);
+ }
+ } catch (RepositoryException e) {
+ discardChanges(node);
+ throw new IOException("Cannot delete " + path, e);
+ }
+
+ }
+
+ @Override
+ public void copy(Path source, Path target, CopyOption... options) throws IOException {
+ Node sourceNode = toNode(source);
+ Node targetNode = toNode(target);
+ try {
+ Session targetSession = targetNode.getSession();
+ synchronized (targetSession) {
+ JcrUtils.copy(sourceNode, targetNode);
+ save(targetSession);
+ }
+ } catch (RepositoryException e) {
+ discardChanges(sourceNode);
+ discardChanges(targetNode);
+ throw new IOException("Cannot copy from " + source + " to " + target, e);
+ }
+ }
+
+ @Override
+ public void move(Path source, Path target, CopyOption... options) throws IOException {
+ JcrFileSystem sourceFileSystem = (JcrFileSystem) source.getFileSystem();
+ WorkspaceFileStore sourceStore = sourceFileSystem.getFileStore(source.toString());
+ WorkspaceFileStore targetStore = sourceFileSystem.getFileStore(target.toString());
+ try {
+ if (sourceStore.equals(targetStore)) {
+ sourceStore.getWorkspace().move(sourceStore.toJcrPath(source.toString()),
+ targetStore.toJcrPath(target.toString()));
+ } else {
+ // TODO implement it
+ throw new UnsupportedOperationException("Can only move paths within the same workspace.");
+ }
+ } catch (RepositoryException e) {
+ throw new IOException("Cannot move from " + source + " to " + target, e);
+ }
+
+// Node sourceNode = toNode(source);
+// try {
+// Session session = sourceNode.getSession();
+// 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);
+// }
+ }
+
+ @Override
+ public boolean isSameFile(Path path, Path path2) throws IOException {
+ if (path.getFileSystem() != path2.getFileSystem())
+ return false;
+ boolean equ = path.equals(path2);
+ if (equ)
+ return true;
+ else {
+ try {
+ Node node = toNode(path);
+ Node node2 = toNode(path2);
+ return node.isSame(node2);
+ } catch (RepositoryException e) {
+ throw new IOException("Cannot check whether " + path + " and " + path2 + " are same", e);
+ }
+ }
+
+ }
+
+ @Override
+ public boolean isHidden(Path path) throws IOException {
+ return path.getFileName().toString().charAt(0) == '.';
+ }
+
+ @Override
+ public FileStore getFileStore(Path path) throws IOException {
+ JcrFileSystem fileSystem = (JcrFileSystem) path.getFileSystem();
+ return fileSystem.getFileStore(path.toString());
+ }
+
+ @Override
+ public void checkAccess(Path path, AccessMode... modes) throws IOException {
+ Node node = toNode(path);
+ if (node == null)
+ throw new NoSuchFileException(path + " does not exist");
+ // TODO check access via JCR api
+ }
+
+ @Override
+ public <V extends FileAttributeView> V getFileAttributeView(Path path, Class<V> type, LinkOption... options) {
+ throw new UnsupportedOperationException();
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public <A extends BasicFileAttributes> A readAttributes(Path path, Class<A> type, LinkOption... options)
+ throws IOException {
+ // TODO check if assignable
+ Node node = toNode(path);
+ if (node == null) {
+ throw new IOException("JCR node not found for " + path);
+ }
+ return (A) new JcrBasicfileAttributes(node);
+ }
+
+ @Override
+ public Map<String, Object> readAttributes(Path path, String attributes, LinkOption... options) throws IOException {
+ try {
+ Node node = toNode(path);
+ String pattern = attributes.replace(',', '|');
+ Map<String, Object> res = new HashMap<String, Object>();
+ PropertyIterator it = node.getProperties(pattern);
+ props: while (it.hasNext()) {
+ Property prop = it.nextProperty();
+ PropertyDefinition pd = prop.getDefinition();
+ if (pd.isMultiple())
+ continue props;
+ int requiredType = pd.getRequiredType();
+ switch (requiredType) {
+ case PropertyType.LONG:
+ res.put(prop.getName(), prop.getLong());
+ break;
+ case PropertyType.DOUBLE:
+ res.put(prop.getName(), prop.getDouble());
+ break;
+ case PropertyType.BOOLEAN:
+ res.put(prop.getName(), prop.getBoolean());
+ break;
+ case PropertyType.DATE:
+ res.put(prop.getName(), prop.getDate());
+ break;
+ case PropertyType.BINARY:
+ byte[] arr = JcrUtils.getBinaryAsBytes(prop);
+ res.put(prop.getName(), arr);
+ break;
+ default:
+ res.put(prop.getName(), prop.getString());
+ }
+ }
+ return res;
+ } catch (RepositoryException e) {
+ throw new IOException("Cannot read attributes of " + path, e);
+ }
+ }
+
+ @Override
+ public void setAttribute(Path path, String attribute, Object value, LinkOption... options) throws IOException {
+ Node node = toNode(path);
+ try {
+ 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);
+ }
+ } catch (RepositoryException e) {
+ discardChanges(node);
+ throw new IOException("Cannot set attribute " + attribute + " on " + path, e);
+ }
+ }
+
+ protected Node toNode(Path path) {
+ try {
+ return ((JcrPath) path).getNode();
+ } catch (RepositoryException e) {
+ throw new JcrFsException("Cannot convert path " + path + " to JCR Node", e);
+ }
+ }
+
+ /** Discard changes in the underlying session */
+ protected void discardChanges(Node node) {
+ if (node == null)
+ return;
+ try {
+ // discard changes
+ node.getSession().refresh(false);
+ } catch (RepositoryException e) {
+ e.printStackTrace();
+ // TODO log out session?
+ // TODO use Commons logging?
+ }
+ }
+
+ /** 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.
+ *
+ * @return null by default
+ */
+ public Node getUserHome(Repository session) {
+ return null;
+ }
+
+}
--- /dev/null
+package org.argeo.jcr.fs;
+
+
+/** Exception related to the JCR FS */
+public class JcrFsException extends RuntimeException {
+ private static final long serialVersionUID = -7973896038244922980L;
+
+ public JcrFsException(String message, Throwable e) {
+ super(message, e);
+ }
+
+ public JcrFsException(String message) {
+ super(message);
+ }
+}
--- /dev/null
+package org.argeo.jcr.fs;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.file.FileSystem;
+import java.nio.file.LinkOption;
+import java.nio.file.Path;
+import java.nio.file.WatchEvent.Kind;
+import java.nio.file.WatchEvent.Modifier;
+import java.nio.file.WatchKey;
+import java.nio.file.WatchService;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+
+/** A {@link Path} which contains a reference to a JCR {@link Node}. */
+public class JcrPath implements Path {
+ final static String separator = "/";
+ final static char separatorChar = '/';
+
+ private final JcrFileSystem fs;
+ /** null for non absolute paths */
+ private final WorkspaceFileStore fileStore;
+ private final String[] path;// null means root
+ private final boolean absolute;
+
+ // optim
+ private final int hashCode;
+
+ public JcrPath(JcrFileSystem filesSystem, String path) {
+ this.fs = filesSystem;
+ if (path == null)
+ throw new JcrFsException("Path cannot be null");
+ if (path.equals(separator)) {// root
+ this.path = null;
+ this.absolute = true;
+ this.hashCode = 0;
+ this.fileStore = fs.getBaseFileStore();
+ return;
+ } else if (path.equals("")) {// empty path
+ this.path = new String[] { "" };
+ this.absolute = false;
+ this.fileStore = null;
+ this.hashCode = "".hashCode();
+ return;
+ }
+
+ if (path.equals("~")) {// home
+ path = filesSystem.getUserHomePath();
+ if (path == null)
+ throw new JcrFsException("No home directory available");
+ }
+
+ this.absolute = path.charAt(0) == separatorChar ? true : false;
+
+ this.fileStore = absolute ? fs.getFileStore(path) : null;
+
+ String trimmedPath = path.substring(absolute ? 1 : 0,
+ path.charAt(path.length() - 1) == separatorChar ? path.length() - 1 : path.length());
+ this.path = trimmedPath.split(separator);
+ for (int i = 0; i < this.path.length; i++) {
+ this.path[i] = Text.unescapeIllegalJcrChars(this.path[i]);
+ }
+ this.hashCode = this.path[this.path.length - 1].hashCode();
+ assert !(absolute && fileStore == null);
+ }
+
+ public JcrPath(JcrFileSystem filesSystem, Node node) throws RepositoryException {
+ this(filesSystem, filesSystem.getFileStore(node).toFsPath(node));
+ }
+
+ /** Internal optimisation */
+ private JcrPath(JcrFileSystem filesSystem, WorkspaceFileStore fileStore, String[] path, boolean absolute) {
+ this.fs = filesSystem;
+ this.path = path;
+ this.absolute = path == null ? true : absolute;
+ if (this.absolute && fileStore == null)
+ throw new IllegalArgumentException("Absolute path requires a file store");
+ if (!this.absolute && fileStore != null)
+ throw new IllegalArgumentException("A file store should not be provided for a relative path");
+ this.fileStore = fileStore;
+ this.hashCode = path == null ? 0 : path[path.length - 1].hashCode();
+ assert !(absolute && fileStore == null);
+ }
+
+ @Override
+ public FileSystem getFileSystem() {
+ return fs;
+ }
+
+ @Override
+ public boolean isAbsolute() {
+ return absolute;
+ }
+
+ @Override
+ public Path getRoot() {
+ if (path == null)
+ return this;
+ return new JcrPath(fs, separator);
+ }
+
+ @Override
+ public String toString() {
+ return toFsPath(path);
+ }
+
+ private String toFsPath(String[] path) {
+ if (path == null)
+ return "/";
+ StringBuilder sb = new StringBuilder();
+ if (isAbsolute())
+ sb.append('/');
+ for (int i = 0; i < path.length; i++) {
+ if (i != 0)
+ sb.append('/');
+ sb.append(path[i]);
+ }
+ return sb.toString();
+ }
+
+// @Deprecated
+// private String toJcrPath() {
+// return toJcrPath(path);
+// }
+//
+// @Deprecated
+// private String toJcrPath(String[] path) {
+// if (path == null)
+// return "/";
+// StringBuilder sb = new StringBuilder();
+// if (isAbsolute())
+// sb.append('/');
+// for (int i = 0; i < path.length; i++) {
+// if (i != 0)
+// sb.append('/');
+// sb.append(Text.escapeIllegalJcrChars(path[i]));
+// }
+// return sb.toString();
+// }
+
+ @Override
+ public Path getFileName() {
+ if (path == null)
+ return null;
+ return new JcrPath(fs, path[path.length - 1]);
+ }
+
+ @Override
+ public Path getParent() {
+ if (path == null)
+ return null;
+ if (path.length == 1)// root
+ return new JcrPath(fs, separator);
+ String[] parentPath = Arrays.copyOfRange(path, 0, path.length - 1);
+ if (!absolute)
+ return new JcrPath(fs, null, parentPath, absolute);
+ else
+ return new JcrPath(fs, toFsPath(parentPath));
+ }
+
+ @Override
+ public int getNameCount() {
+ if (path == null)
+ return 0;
+ return path.length;
+ }
+
+ @Override
+ public Path getName(int index) {
+ if (path == null)
+ return null;
+ return new JcrPath(fs, path[index]);
+ }
+
+ @Override
+ public Path subpath(int beginIndex, int endIndex) {
+ if (path == null)
+ return null;
+ String[] parentPath = Arrays.copyOfRange(path, beginIndex, endIndex);
+ return new JcrPath(fs, null, parentPath, false);
+ }
+
+ @Override
+ public boolean startsWith(Path other) {
+ return toString().startsWith(other.toString());
+ }
+
+ @Override
+ public boolean startsWith(String other) {
+ return toString().startsWith(other);
+ }
+
+ @Override
+ public boolean endsWith(Path other) {
+ return toString().endsWith(other.toString());
+ }
+
+ @Override
+ public boolean endsWith(String other) {
+ return toString().endsWith(other);
+ }
+
+ @Override
+ public Path normalize() {
+ // always normalized
+ return this;
+ }
+
+ @Override
+ public Path resolve(Path other) {
+ JcrPath otherPath = (JcrPath) other;
+ if (otherPath.isAbsolute())
+ return other;
+ String[] newPath;
+ if (path == null) {
+ newPath = new String[otherPath.path.length];
+ System.arraycopy(otherPath.path, 0, newPath, 0, otherPath.path.length);
+ } else {
+ newPath = new String[path.length + otherPath.path.length];
+ System.arraycopy(path, 0, newPath, 0, path.length);
+ System.arraycopy(otherPath.path, 0, newPath, path.length, otherPath.path.length);
+ }
+ if (!absolute)
+ return new JcrPath(fs, null, newPath, absolute);
+ else {
+ return new JcrPath(fs, toFsPath(newPath));
+ }
+ }
+
+ @Override
+ public final Path resolve(String other) {
+ return resolve(getFileSystem().getPath(other));
+ }
+
+ @Override
+ public final Path resolveSibling(Path other) {
+ if (other == null)
+ throw new NullPointerException();
+ Path parent = getParent();
+ return (parent == null) ? other : parent.resolve(other);
+ }
+
+ @Override
+ public final Path resolveSibling(String other) {
+ return resolveSibling(getFileSystem().getPath(other));
+ }
+
+ @Override
+ public final Iterator<Path> iterator() {
+ return new Iterator<Path>() {
+ private int i = 0;
+
+ @Override
+ public boolean hasNext() {
+ return (i < getNameCount());
+ }
+
+ @Override
+ public Path next() {
+ if (i < getNameCount()) {
+ Path result = getName(i);
+ i++;
+ return result;
+ } else {
+ throw new NoSuchElementException();
+ }
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+ };
+ }
+
+ @Override
+ public Path relativize(Path other) {
+ if (equals(other))
+ return new JcrPath(fs, "");
+ if (other.startsWith(this)) {
+ String p1 = toString();
+ String p2 = other.toString();
+ String relative = p2.substring(p1.length(), p2.length());
+ if (relative.charAt(0) == '/')
+ relative = relative.substring(1);
+ return new JcrPath(fs, relative);
+ }
+ throw new IllegalArgumentException(other + " cannot be relativized against " + this);
+ }
+
+ @Override
+ public URI toUri() {
+ try {
+ return new URI(fs.provider().getScheme(), toString(), null);
+ } catch (URISyntaxException e) {
+ throw new JcrFsException("Cannot create URI for " + toString(), e);
+ }
+ }
+
+ @Override
+ public Path toAbsolutePath() {
+ if (isAbsolute())
+ return this;
+ return new JcrPath(fs, fileStore, path, true);
+ }
+
+ @Override
+ public Path toRealPath(LinkOption... options) throws IOException {
+ return this;
+ }
+
+ @Override
+ public File toFile() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public WatchKey register(WatchService watcher, Kind<?>[] events, Modifier... modifiers) throws IOException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public WatchKey register(WatchService watcher, Kind<?>... events) throws IOException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public int compareTo(Path other) {
+ return toString().compareTo(other.toString());
+ }
+
+ public Node getNode() throws RepositoryException {
+ if (!isAbsolute())// TODO default dir
+ throw new JcrFsException("Cannot get a JCR node from a relative path");
+ assert fileStore != null;
+ return fileStore.toNode(path);
+// String pathStr = toJcrPath();
+// Session session = fs.getSession();
+// // TODO synchronize on the session ?
+// if (!session.itemExists(pathStr))
+// return null;
+// return session.getNode(pathStr);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof JcrPath))
+ return false;
+ JcrPath other = (JcrPath) obj;
+
+ if (path == null) {// root
+ if (other.path == null)// root
+ return true;
+ else
+ return false;
+ } else {
+ if (other.path == null)// root
+ return false;
+ }
+ // non root
+ if (path.length != other.path.length)
+ return false;
+ for (int i = 0; i < path.length; i++) {
+ if (!path[i].equals(other.path[i]))
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return hashCode;
+ }
+
+ @Override
+ protected Object clone() throws CloneNotSupportedException {
+ return new JcrPath(fs, toString());
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ Arrays.fill(path, null);
+ }
+
+}
--- /dev/null
+package org.argeo.jcr.fs;
+
+import java.io.IOException;
+import java.nio.file.DirectoryStream;
+import java.nio.file.Path;
+import java.util.Iterator;
+
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+
+public class NodeDirectoryStream implements DirectoryStream<Path> {
+ private final JcrFileSystem fs;
+ private final NodeIterator nodeIterator;
+ private final Iterator<JcrPath> additionalPaths;
+ private final Filter<? super Path> filter;
+
+ public NodeDirectoryStream(JcrFileSystem fs, NodeIterator nodeIterator, Iterator<JcrPath> additionalPaths,
+ Filter<? super Path> filter) {
+ this.fs = fs;
+ this.nodeIterator = nodeIterator;
+ this.additionalPaths = additionalPaths;
+ this.filter = filter;
+ }
+
+ @Override
+ public void close() throws IOException {
+ }
+
+ @Override
+ public Iterator<Path> iterator() {
+ return new Iterator<Path>() {
+ private JcrPath next = null;
+
+ @Override
+ public synchronized boolean hasNext() {
+ if (next != null)
+ return true;
+ nodes: while (nodeIterator.hasNext()) {
+ try {
+ Node node = nodeIterator.nextNode();
+ String nodeName = node.getName();
+ if (nodeName.startsWith("rep:") || nodeName.startsWith("jcr:"))
+ continue nodes;
+ if (fs.skipNode(node))
+ continue nodes;
+ next = new JcrPath(fs, node);
+ if (filter != null) {
+ if (filter.accept(next))
+ break nodes;
+ } else
+ break nodes;
+ } catch (Exception e) {
+ throw new JcrFsException("Could not get next path", e);
+ }
+ }
+
+ if (next == null) {
+ if (additionalPaths.hasNext())
+ next = additionalPaths.next();
+ }
+
+ return next != null;
+ }
+
+ @Override
+ public synchronized Path next() {
+ if (!hasNext())// make sure has next has been called
+ return null;
+ JcrPath res = next;
+ next = null;
+ return res;
+ }
+
+ };
+ }
+
+}
--- /dev/null
+package org.argeo.jcr.fs;
+
+import java.nio.file.attribute.BasicFileAttributes;
+
+import javax.jcr.Node;
+
+public interface NodeFileAttributes extends BasicFileAttributes {
+ public Node getNode();
+}
--- /dev/null
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+package org.argeo.jcr.fs;
+
+import java.io.ByteArrayOutputStream;
+import java.io.UnsupportedEncodingException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.BitSet;
+import java.util.Properties;
+
+/**
+ * <b>Hacked from org.apache.jackrabbit.util.Text in Jackrabbit JCR Commons</b>
+ * This Class provides some text related utilities
+ */
+class Text {
+
+ /**
+ * Hidden constructor.
+ */
+ private Text() {
+ }
+
+ /**
+ * used for the md5
+ */
+ public static final char[] hexTable = "0123456789abcdef".toCharArray();
+
+ /**
+ * Calculate an MD5 hash of the string given.
+ *
+ * @param data
+ * the data to encode
+ * @param enc
+ * the character encoding to use
+ * @return a hex encoded string of the md5 digested input
+ */
+ public static String md5(String data, String enc) throws UnsupportedEncodingException {
+ try {
+ return digest("MD5", data.getBytes(enc));
+ } catch (NoSuchAlgorithmException e) {
+ throw new InternalError("MD5 digest not available???");
+ }
+ }
+
+ /**
+ * Calculate an MD5 hash of the string given using 'utf-8' encoding.
+ *
+ * @param data
+ * the data to encode
+ * @return a hex encoded string of the md5 digested input
+ */
+ public static String md5(String data) {
+ try {
+ return md5(data, "utf-8");
+ } catch (UnsupportedEncodingException e) {
+ throw new InternalError("UTF8 digest not available???");
+ }
+ }
+
+ /**
+ * Digest the plain string using the given algorithm.
+ *
+ * @param algorithm
+ * The alogrithm for the digest. This algorithm must be supported
+ * by the MessageDigest class.
+ * @param data
+ * The plain text String to be digested.
+ * @param enc
+ * The character encoding to use
+ * @return The digested plain text String represented as Hex digits.
+ * @throws java.security.NoSuchAlgorithmException
+ * if the desired algorithm is not supported by the
+ * MessageDigest class.
+ * @throws java.io.UnsupportedEncodingException
+ * if the encoding is not supported
+ */
+ public static String digest(String algorithm, String data, String enc)
+ throws NoSuchAlgorithmException, UnsupportedEncodingException {
+
+ return digest(algorithm, data.getBytes(enc));
+ }
+
+ /**
+ * Digest the plain string using the given algorithm.
+ *
+ * @param algorithm
+ * The algorithm for the digest. This algorithm must be supported
+ * by the MessageDigest class.
+ * @param data
+ * the data to digest with the given algorithm
+ * @return The digested plain text String represented as Hex digits.
+ * @throws java.security.NoSuchAlgorithmException
+ * if the desired algorithm is not supported by the
+ * MessageDigest class.
+ */
+ public static String digest(String algorithm, byte[] data) throws NoSuchAlgorithmException {
+
+ MessageDigest md = MessageDigest.getInstance(algorithm);
+ byte[] digest = md.digest(data);
+ StringBuilder res = new StringBuilder(digest.length * 2);
+ for (byte b : digest) {
+ res.append(hexTable[(b >> 4) & 15]);
+ res.append(hexTable[b & 15]);
+ }
+ return res.toString();
+ }
+
+ /**
+ * returns an array of strings decomposed of the original string, split at
+ * every occurrence of 'ch'. if 2 'ch' follow each other with no
+ * intermediate characters, empty "" entries are avoided.
+ *
+ * @param str
+ * the string to decompose
+ * @param ch
+ * the character to use a split pattern
+ * @return an array of strings
+ */
+ public static String[] explode(String str, int ch) {
+ return explode(str, ch, false);
+ }
+
+ /**
+ * returns an array of strings decomposed of the original string, split at
+ * every occurrence of 'ch'.
+ *
+ * @param str
+ * the string to decompose
+ * @param ch
+ * the character to use a split pattern
+ * @param respectEmpty
+ * if <code>true</code>, empty elements are generated
+ * @return an array of strings
+ */
+ public static String[] explode(String str, int ch, boolean respectEmpty) {
+ if (str == null || str.length() == 0) {
+ return new String[0];
+ }
+
+ ArrayList<String> strings = new ArrayList<String>();
+ int pos;
+ int lastpos = 0;
+
+ // add snipples
+ while ((pos = str.indexOf(ch, lastpos)) >= 0) {
+ if (pos - lastpos > 0 || respectEmpty) {
+ strings.add(str.substring(lastpos, pos));
+ }
+ lastpos = pos + 1;
+ }
+ // add rest
+ if (lastpos < str.length()) {
+ strings.add(str.substring(lastpos));
+ } else if (respectEmpty && lastpos == str.length()) {
+ strings.add("");
+ }
+
+ // return string array
+ return strings.toArray(new String[strings.size()]);
+ }
+
+ /**
+ * Concatenates all strings in the string array using the specified
+ * delimiter.
+ *
+ * @param arr
+ * @param delim
+ * @return the concatenated string
+ */
+ public static String implode(String[] arr, String delim) {
+ StringBuilder buf = new StringBuilder();
+ for (int i = 0; i < arr.length; i++) {
+ if (i > 0) {
+ buf.append(delim);
+ }
+ buf.append(arr[i]);
+ }
+ return buf.toString();
+ }
+
+ /**
+ * Replaces all occurrences of <code>oldString</code> in <code>text</code>
+ * with <code>newString</code>.
+ *
+ * @param text
+ * @param oldString
+ * old substring to be replaced with <code>newString</code>
+ * @param newString
+ * new substring to replace occurrences of <code>oldString</code>
+ * @return a string
+ */
+ public static String replace(String text, String oldString, String newString) {
+ if (text == null || oldString == null || newString == null) {
+ throw new IllegalArgumentException("null argument");
+ }
+ int pos = text.indexOf(oldString);
+ if (pos == -1) {
+ return text;
+ }
+ int lastPos = 0;
+ StringBuilder sb = new StringBuilder(text.length());
+ while (pos != -1) {
+ sb.append(text.substring(lastPos, pos));
+ sb.append(newString);
+ lastPos = pos + oldString.length();
+ pos = text.indexOf(oldString, lastPos);
+ }
+ if (lastPos < text.length()) {
+ sb.append(text.substring(lastPos));
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Replaces XML characters in the given string that might need escaping as
+ * XML text or attribute
+ *
+ * @param text
+ * text to be escaped
+ * @return a string
+ */
+ public static String encodeIllegalXMLCharacters(String text) {
+ return encodeMarkupCharacters(text, false);
+ }
+
+ /**
+ * Replaces HTML characters in the given string that might need escaping as
+ * HTML text or attribute
+ *
+ * @param text
+ * text to be escaped
+ * @return a string
+ */
+ public static String encodeIllegalHTMLCharacters(String text) {
+ return encodeMarkupCharacters(text, true);
+ }
+
+ private static String encodeMarkupCharacters(String text, boolean isHtml) {
+ if (text == null) {
+ throw new IllegalArgumentException("null argument");
+ }
+ StringBuilder buf = null;
+ int length = text.length();
+ int pos = 0;
+ for (int i = 0; i < length; i++) {
+ int ch = text.charAt(i);
+ switch (ch) {
+ case '<':
+ case '>':
+ case '&':
+ case '"':
+ case '\'':
+ if (buf == null) {
+ buf = new StringBuilder();
+ }
+ if (i > 0) {
+ buf.append(text.substring(pos, i));
+ }
+ pos = i + 1;
+ break;
+ default:
+ continue;
+ }
+ if (ch == '<') {
+ buf.append("<");
+ } else if (ch == '>') {
+ buf.append(">");
+ } else if (ch == '&') {
+ buf.append("&");
+ } else if (ch == '"') {
+ buf.append(""");
+ } else if (ch == '\'') {
+ buf.append(isHtml ? "'" : "'");
+ }
+ }
+ if (buf == null) {
+ return text;
+ } else {
+ if (pos < length) {
+ buf.append(text.substring(pos));
+ }
+ return buf.toString();
+ }
+ }
+
+ /**
+ * The list of characters that are not encoded by the <code>escape()</code>
+ * and <code>unescape()</code> METHODS. They contains the characters as
+ * defined 'unreserved' in section 2.3 of the RFC 2396 'URI generic syntax':
+ * <p>
+ *
+ * <pre>
+ * unreserved = alphanum | mark
+ * mark = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")"
+ * </pre>
+ */
+ public static BitSet URISave;
+
+ /**
+ * Same as {@link #URISave} but also contains the '/'
+ */
+ public static BitSet URISaveEx;
+
+ static {
+ URISave = new BitSet(256);
+ int i;
+ for (i = 'a'; i <= 'z'; i++) {
+ URISave.set(i);
+ }
+ for (i = 'A'; i <= 'Z'; i++) {
+ URISave.set(i);
+ }
+ for (i = '0'; i <= '9'; i++) {
+ URISave.set(i);
+ }
+ URISave.set('-');
+ URISave.set('_');
+ URISave.set('.');
+ URISave.set('!');
+ URISave.set('~');
+ URISave.set('*');
+ URISave.set('\'');
+ URISave.set('(');
+ URISave.set(')');
+
+ URISaveEx = (BitSet) URISave.clone();
+ URISaveEx.set('/');
+ }
+
+ /**
+ * Does an URL encoding of the <code>string</code> using the
+ * <code>escape</code> character. The characters that don't need encoding
+ * are those defined 'unreserved' in section 2.3 of the 'URI generic syntax'
+ * RFC 2396, but without the escape character.
+ *
+ * @param string
+ * the string to encode.
+ * @param escape
+ * the escape character.
+ * @return the escaped string
+ * @throws NullPointerException
+ * if <code>string</code> is <code>null</code>.
+ */
+ public static String escape(String string, char escape) {
+ return escape(string, escape, false);
+ }
+
+ /**
+ * Does an URL encoding of the <code>string</code> using the
+ * <code>escape</code> character. The characters that don't need encoding
+ * are those defined 'unreserved' in section 2.3 of the 'URI generic syntax'
+ * RFC 2396, but without the escape character. If <code>isPath</code> is
+ * <code>true</code>, additionally the slash '/' is ignored, too.
+ *
+ * @param string
+ * the string to encode.
+ * @param escape
+ * the escape character.
+ * @param isPath
+ * if <code>true</code>, the string is treated as path
+ * @return the escaped string
+ * @throws NullPointerException
+ * if <code>string</code> is <code>null</code>.
+ */
+ public static String escape(String string, char escape, boolean isPath) {
+ try {
+ BitSet validChars = isPath ? URISaveEx : URISave;
+ byte[] bytes = string.getBytes("utf-8");
+ StringBuilder out = new StringBuilder(bytes.length);
+ for (byte aByte : bytes) {
+ int c = aByte & 0xff;
+ if (validChars.get(c) && c != escape) {
+ out.append((char) c);
+ } else {
+ out.append(escape);
+ out.append(hexTable[(c >> 4) & 0x0f]);
+ out.append(hexTable[(c) & 0x0f]);
+ }
+ }
+ return out.toString();
+ } catch (UnsupportedEncodingException e) {
+ throw new InternalError(e.toString());
+ }
+ }
+
+ /**
+ * Does a URL encoding of the <code>string</code>. The characters that don't
+ * need encoding are those defined 'unreserved' in section 2.3 of the 'URI
+ * generic syntax' RFC 2396.
+ *
+ * @param string
+ * the string to encode
+ * @return the escaped string
+ * @throws NullPointerException
+ * if <code>string</code> is <code>null</code>.
+ */
+ public static String escape(String string) {
+ return escape(string, '%');
+ }
+
+ /**
+ * Does a URL encoding of the <code>path</code>. The characters that don't
+ * need encoding are those defined 'unreserved' in section 2.3 of the 'URI
+ * generic syntax' RFC 2396. In contrast to the {@link #escape(String)}
+ * method, not the entire path string is escaped, but every individual part
+ * (i.e. the slashes are not escaped).
+ *
+ * @param path
+ * the path to encode
+ * @return the escaped path
+ * @throws NullPointerException
+ * if <code>path</code> is <code>null</code>.
+ */
+ public static String escapePath(String path) {
+ return escape(path, '%', true);
+ }
+
+ /**
+ * Does a URL decoding of the <code>string</code> using the
+ * <code>escape</code> character. Please note that in opposite to the
+ * {@link java.net.URLDecoder} it does not transform the + into spaces.
+ *
+ * @param string
+ * the string to decode
+ * @param escape
+ * the escape character
+ * @return the decoded string
+ * @throws NullPointerException
+ * if <code>string</code> is <code>null</code>.
+ * @throws IllegalArgumentException
+ * if the 2 characters following the escape character do not
+ * represent a hex-number or if not enough characters follow an
+ * escape character
+ */
+ public static String unescape(String string, char escape) {
+ try {
+ byte[] utf8 = string.getBytes("utf-8");
+
+ // Check whether escape occurs at invalid position
+ if ((utf8.length >= 1 && utf8[utf8.length - 1] == escape)
+ || (utf8.length >= 2 && utf8[utf8.length - 2] == escape)) {
+ throw new IllegalArgumentException("Premature end of escape sequence at end of input");
+ }
+
+ ByteArrayOutputStream out = new ByteArrayOutputStream(utf8.length);
+ for (int k = 0; k < utf8.length; k++) {
+ byte b = utf8[k];
+ if (b == escape) {
+ out.write((decodeDigit(utf8[++k]) << 4) + decodeDigit(utf8[++k]));
+ } else {
+ out.write(b);
+ }
+ }
+
+ return new String(out.toByteArray(), "utf-8");
+ } catch (UnsupportedEncodingException e) {
+ throw new InternalError(e.toString());
+ }
+ }
+
+ /**
+ * Does a URL decoding of the <code>string</code>. Please note that in
+ * opposite to the {@link java.net.URLDecoder} it does not transform the +
+ * into spaces.
+ *
+ * @param string
+ * the string to decode
+ * @return the decoded string
+ * @throws NullPointerException
+ * if <code>string</code> is <code>null</code>.
+ * @throws ArrayIndexOutOfBoundsException
+ * if not enough character follow an escape character
+ * @throws IllegalArgumentException
+ * if the 2 characters following the escape character do not
+ * represent a hex-number.
+ */
+ public static String unescape(String string) {
+ return unescape(string, '%');
+ }
+
+ /**
+ * Escapes all illegal JCR name characters of a string. The encoding is
+ * loosely modeled after URI encoding, but only encodes the characters it
+ * absolutely needs to in order to make the resulting string a valid JCR
+ * name. Use {@link #unescapeIllegalJcrChars(String)} for decoding.
+ * <p>
+ * QName EBNF:<br>
+ * <xmp> simplename ::= onecharsimplename | twocharsimplename |
+ * threeormorecharname onecharsimplename ::= (* Any Unicode character
+ * except: '.', '/', ':', '[', ']', '*', '|' or any whitespace character *)
+ * twocharsimplename ::= '.' onecharsimplename | onecharsimplename '.' |
+ * onecharsimplename onecharsimplename threeormorecharname ::= nonspace
+ * string nonspace string ::= char | string char char ::= nonspace | ' '
+ * nonspace ::= (* Any Unicode character except: '/', ':', '[', ']', '*',
+ * '|' or any whitespace character *) </xmp>
+ *
+ * @param name
+ * the name to escape
+ * @return the escaped name
+ */
+ public static String escapeIllegalJcrChars(String name) {
+ return escapeIllegalChars(name, "%/:[]*|\t\r\n");
+ }
+
+ /**
+ * Escapes all illegal JCR 1.0 name characters of a string. Use
+ * {@link #unescapeIllegalJcrChars(String)} for decoding.
+ * <p>
+ * QName EBNF:<br>
+ * <xmp> simplename ::= onecharsimplename | twocharsimplename |
+ * threeormorecharname onecharsimplename ::= (* Any Unicode character
+ * except: '.', '/', ':', '[', ']', '*', ''', '"', '|' or any whitespace
+ * character *) twocharsimplename ::= '.' onecharsimplename |
+ * onecharsimplename '.' | onecharsimplename onecharsimplename
+ * threeormorecharname ::= nonspace string nonspace string ::= char | string
+ * char char ::= nonspace | ' ' nonspace ::= (* Any Unicode character
+ * except: '/', ':', '[', ']', '*', ''', '"', '|' or any whitespace
+ * character *) </xmp>
+ *
+ * @since Apache Jackrabbit 2.3.2 and 2.2.10
+ * @see <a href=
+ * "https://issues.apache.org/jira/browse/JCR-3128">JCR-3128</a>
+ * @param name
+ * the name to escape
+ * @return the escaped name
+ */
+ public static String escapeIllegalJcr10Chars(String name) {
+ return escapeIllegalChars(name, "%/:[]*'\"|\t\r\n");
+ }
+
+ private static String escapeIllegalChars(String name, String illegal) {
+ StringBuilder buffer = new StringBuilder(name.length() * 2);
+ for (int i = 0; i < name.length(); i++) {
+ char ch = name.charAt(i);
+ if (illegal.indexOf(ch) != -1 || (ch == '.' && name.length() < 3)
+ || (ch == ' ' && (i == 0 || i == name.length() - 1))) {
+ buffer.append('%');
+ buffer.append(Character.toUpperCase(Character.forDigit(ch / 16, 16)));
+ buffer.append(Character.toUpperCase(Character.forDigit(ch % 16, 16)));
+ } else {
+ buffer.append(ch);
+ }
+ }
+ return buffer.toString();
+ }
+
+ /**
+ * Escapes illegal XPath search characters at the end of a string.
+ * <p>
+ * Example:<br>
+ * A search string like 'test?' will run into a ParseException documented in
+ * http://issues.apache.org/jira/browse/JCR-1248
+ *
+ * @param s
+ * the string to encode
+ * @return the escaped string
+ */
+ public static String escapeIllegalXpathSearchChars(String s) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(s.substring(0, (s.length() - 1)));
+ char c = s.charAt(s.length() - 1);
+ // NOTE: keep this in sync with _ESCAPED_CHAR below!
+ if (c == '!' || c == '(' || c == ':' || c == '^' || c == '[' || c == ']' || c == '{' || c == '}' || c == '?') {
+ sb.append('\\');
+ }
+ sb.append(c);
+ return sb.toString();
+ }
+
+ /**
+ * Unescapes previously escaped jcr chars.
+ * <p>
+ * Please note, that this does not exactly the same as the url related
+ * {@link #unescape(String)}, since it handles the byte-encoding
+ * differently.
+ *
+ * @param name
+ * the name to unescape
+ * @return the unescaped name
+ */
+ public static String unescapeIllegalJcrChars(String name) {
+ StringBuilder buffer = new StringBuilder(name.length());
+ int i = name.indexOf('%');
+ while (i > -1 && i + 2 < name.length()) {
+ buffer.append(name.toCharArray(), 0, i);
+ int a = Character.digit(name.charAt(i + 1), 16);
+ int b = Character.digit(name.charAt(i + 2), 16);
+ if (a > -1 && b > -1) {
+ buffer.append((char) (a * 16 + b));
+ name = name.substring(i + 3);
+ } else {
+ buffer.append('%');
+ name = name.substring(i + 1);
+ }
+ i = name.indexOf('%');
+ }
+ buffer.append(name);
+ return buffer.toString();
+ }
+
+ /**
+ * Returns the name part of the path. If the given path is already a name
+ * (i.e. contains no slashes) it is returned.
+ *
+ * @param path
+ * the path
+ * @return the name part or <code>null</code> if <code>path</code> is
+ * <code>null</code>.
+ */
+ public static String getName(String path) {
+ return getName(path, '/');
+ }
+
+ /**
+ * Returns the name part of the path, delimited by the given
+ * <code>delim</code>. If the given path is already a name (i.e. contains no
+ * <code>delim</code> characters) it is returned.
+ *
+ * @param path
+ * the path
+ * @param delim
+ * the delimiter
+ * @return the name part or <code>null</code> if <code>path</code> is
+ * <code>null</code>.
+ */
+ public static String getName(String path, char delim) {
+ return path == null ? null : path.substring(path.lastIndexOf(delim) + 1);
+ }
+
+ /**
+ * Same as {@link #getName(String)} but adding the possibility to pass paths
+ * that end with a trailing '/'
+ *
+ * @see #getName(String)
+ */
+ public static String getName(String path, boolean ignoreTrailingSlash) {
+ if (ignoreTrailingSlash && path != null && path.endsWith("/") && path.length() > 1) {
+ path = path.substring(0, path.length() - 1);
+ }
+ return getName(path);
+ }
+
+ /**
+ * Returns the namespace prefix of the given <code>qname</code>. If the
+ * prefix is missing, an empty string is returned. Please note, that this
+ * method does not validate the name or prefix.
+ * </p>
+ * the qname has the format: qname := [prefix ':'] local;
+ *
+ * @param qname
+ * a qualified name
+ * @return the prefix of the name or "".
+ *
+ * @see #getLocalName(String)
+ *
+ * @throws NullPointerException
+ * if <code>qname</code> is <code>null</code>
+ */
+ public static String getNamespacePrefix(String qname) {
+ int pos = qname.indexOf(':');
+ return pos >= 0 ? qname.substring(0, pos) : "";
+ }
+
+ /**
+ * Returns the local name of the given <code>qname</code>. Please note, that
+ * this method does not validate the name.
+ * </p>
+ * the qname has the format: qname := [prefix ':'] local;
+ *
+ * @param qname
+ * a qualified name
+ * @return the localname
+ *
+ * @see #getNamespacePrefix(String)
+ *
+ * @throws NullPointerException
+ * if <code>qname</code> is <code>null</code>
+ */
+ public static String getLocalName(String qname) {
+ int pos = qname.indexOf(':');
+ return pos >= 0 ? qname.substring(pos + 1) : qname;
+ }
+
+ /**
+ * Determines, if two paths denote hierarchical siblins.
+ *
+ * @param p1
+ * first path
+ * @param p2
+ * second path
+ * @return true if on same level, false otherwise
+ */
+ public static boolean isSibling(String p1, String p2) {
+ int pos1 = p1.lastIndexOf('/');
+ int pos2 = p2.lastIndexOf('/');
+ return (pos1 == pos2 && pos1 >= 0 && p1.regionMatches(0, p2, 0, pos1));
+ }
+
+ /**
+ * Determines if the <code>descendant</code> path is hierarchical a
+ * descendant of <code>path</code>.
+ *
+ * @param path
+ * the current path
+ * @param descendant
+ * the potential descendant
+ * @return <code>true</code> if the <code>descendant</code> is a descendant;
+ * <code>false</code> otherwise.
+ */
+ public static boolean isDescendant(String path, String descendant) {
+ String pattern = path.endsWith("/") ? path : path + "/";
+ return !pattern.equals(descendant) && descendant.startsWith(pattern);
+ }
+
+ /**
+ * Determines if the <code>descendant</code> path is hierarchical a
+ * descendant of <code>path</code> or equal to it.
+ *
+ * @param path
+ * the path to check
+ * @param descendant
+ * the potential descendant
+ * @return <code>true</code> if the <code>descendant</code> is a descendant
+ * or equal; <code>false</code> otherwise.
+ */
+ public static boolean isDescendantOrEqual(String path, String descendant) {
+ if (path.equals(descendant)) {
+ return true;
+ } else {
+ String pattern = path.endsWith("/") ? path : path + "/";
+ return descendant.startsWith(pattern);
+ }
+ }
+
+ /**
+ * Returns the n<sup>th</sup> relative parent of the path, where n=level.
+ * <p>
+ * Example:<br>
+ * <code>
+ * Text.getRelativeParent("/foo/bar/test", 1) == "/foo/bar"
+ * </code>
+ *
+ * @param path
+ * the path of the page
+ * @param level
+ * the level of the parent
+ */
+ public static String getRelativeParent(String path, int level) {
+ int idx = path.length();
+ while (level > 0) {
+ idx = path.lastIndexOf('/', idx - 1);
+ if (idx < 0) {
+ return "";
+ }
+ level--;
+ }
+ return (idx == 0) ? "/" : path.substring(0, idx);
+ }
+
+ /**
+ * Same as {@link #getRelativeParent(String, int)} but adding the
+ * possibility to pass paths that end with a trailing '/'
+ *
+ * @see #getRelativeParent(String, int)
+ */
+ public static String getRelativeParent(String path, int level, boolean ignoreTrailingSlash) {
+ if (ignoreTrailingSlash && path.endsWith("/") && path.length() > 1) {
+ path = path.substring(0, path.length() - 1);
+ }
+ return getRelativeParent(path, level);
+ }
+
+ /**
+ * Returns the n<sup>th</sup> absolute parent of the path, where n=level.
+ * <p>
+ * Example:<br>
+ * <code>
+ * Text.getAbsoluteParent("/foo/bar/test", 1) == "/foo/bar"
+ * </code>
+ *
+ * @param path
+ * the path of the page
+ * @param level
+ * the level of the parent
+ */
+ public static String getAbsoluteParent(String path, int level) {
+ int idx = 0;
+ int len = path.length();
+ while (level >= 0 && idx < len) {
+ idx = path.indexOf('/', idx + 1);
+ if (idx < 0) {
+ idx = len;
+ }
+ level--;
+ }
+ return level >= 0 ? "" : path.substring(0, idx);
+ }
+
+ /**
+ * Performs variable replacement on the given string value. Each
+ * <code>${...}</code> sequence within the given value is replaced with the
+ * value of the named parser variable. If a variable is not found in the
+ * properties an IllegalArgumentException is thrown unless
+ * <code>ignoreMissing</code> is <code>true</code>. In the later case, the
+ * missing variable is replaced by the empty string.
+ *
+ * @param value
+ * the original value
+ * @param ignoreMissing
+ * if <code>true</code>, missing variables are replaced by the
+ * empty string.
+ * @return value after variable replacements
+ * @throws IllegalArgumentException
+ * if the replacement of a referenced variable is not found
+ */
+ public static String replaceVariables(Properties variables, String value, boolean ignoreMissing)
+ throws IllegalArgumentException {
+ StringBuilder result = new StringBuilder();
+
+ // Value:
+ // +--+-+--------+-+-----------------+
+ // | |p|--> |q|--> |
+ // +--+-+--------+-+-----------------+
+ int p = 0, q = value.indexOf("${"); // Find first ${
+ while (q != -1) {
+ result.append(value.substring(p, q)); // Text before ${
+ p = q;
+ q = value.indexOf("}", q + 2); // Find }
+ if (q != -1) {
+ String variable = value.substring(p + 2, q);
+ String replacement = variables.getProperty(variable);
+ if (replacement == null) {
+ if (ignoreMissing) {
+ replacement = "";
+ } else {
+ throw new IllegalArgumentException("Replacement not found for ${" + variable + "}.");
+ }
+ }
+ result.append(replacement);
+ p = q + 1;
+ q = value.indexOf("${", p); // Find next ${
+ }
+ }
+ result.append(value.substring(p, value.length())); // Trailing text
+
+ return result.toString();
+ }
+
+ private static byte decodeDigit(byte b) {
+ if (b >= 0x30 && b <= 0x39) {
+ return (byte) (b - 0x30);
+ } else if (b >= 0x41 && b <= 0x46) {
+ return (byte) (b - 0x37);
+ } else if (b >= 0x61 && b <= 0x66) {
+ return (byte) (b - 0x57);
+ } else {
+ throw new IllegalArgumentException("Escape sequence is not hexadecimal: " + (char) b);
+ }
+ }
+
+}
--- /dev/null
+package org.argeo.jcr.fs;
+
+import java.io.IOException;
+import java.nio.file.FileStore;
+import java.nio.file.attribute.FileAttributeView;
+import java.nio.file.attribute.FileStoreAttributeView;
+import java.util.Arrays;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.Workspace;
+
+import org.argeo.jcr.JcrUtils;
+
+/** A {@link FileStore} implementation based on JCR {@link Workspace}. */
+public class WorkspaceFileStore extends FileStore {
+ private final String mountPath;
+ private final Workspace workspace;
+ private final String workspaceName;
+ private final int mountDepth;
+
+ public WorkspaceFileStore(String mountPath, Workspace workspace) {
+ if ("/".equals(mountPath) || "".equals(mountPath))
+ throw new IllegalArgumentException(
+ "Mount path '" + mountPath + "' is unsupported, use null for the base file store");
+ if (mountPath != null && !mountPath.startsWith(JcrPath.separator))
+ throw new IllegalArgumentException("Mount path '" + mountPath + "' cannot end with /");
+ if (mountPath != null && mountPath.endsWith(JcrPath.separator))
+ throw new IllegalArgumentException("Mount path '" + mountPath + "' cannot end with /");
+ this.mountPath = mountPath;
+ if (mountPath == null)
+ mountDepth = 0;
+ else {
+ mountDepth = mountPath.split(JcrPath.separator).length - 1;
+ }
+ this.workspace = workspace;
+ this.workspaceName = workspace.getName();
+ }
+
+ public void close() {
+ JcrUtils.logoutQuietly(workspace.getSession());
+ }
+
+ @Override
+ public String name() {
+ return workspace.getName();
+ }
+
+ @Override
+ public String type() {
+ return "workspace";
+ }
+
+ @Override
+ public boolean isReadOnly() {
+ return false;
+ }
+
+ @Override
+ public long getTotalSpace() throws IOException {
+ return 0;
+ }
+
+ @Override
+ public long getUsableSpace() throws IOException {
+ return 0;
+ }
+
+ @Override
+ public long getUnallocatedSpace() throws IOException {
+ return 0;
+ }
+
+ @Override
+ public boolean supportsFileAttributeView(Class<? extends FileAttributeView> type) {
+ return false;
+ }
+
+ @Override
+ public boolean supportsFileAttributeView(String name) {
+ return false;
+ }
+
+ @Override
+ public <V extends FileStoreAttributeView> V getFileStoreAttributeView(Class<V> type) {
+ return null;
+ }
+
+ @Override
+ public Object getAttribute(String attribute) throws IOException {
+ return workspace.getSession().getRepository().getDescriptor(attribute);
+ }
+
+ public Workspace getWorkspace() {
+ return workspace;
+ }
+
+ public String toFsPath(Node node) throws RepositoryException {
+ String nodeWorkspaceName = node.getSession().getWorkspace().getName();
+ if (!nodeWorkspaceName.equals(workspace.getName()))
+ throw new IllegalArgumentException("Icompatible " + node + " from workspace '" + nodeWorkspaceName
+ + "' in file store '" + workspace.getName() + "'");
+ return mountPath == null ? node.getPath() : mountPath + node.getPath();
+ }
+
+ public boolean isBase() {
+ return mountPath == null;
+ }
+
+ Node toNode(String[] fullPath) throws RepositoryException {
+ String jcrPath = toJcrPath(fullPath);
+ Session session = workspace.getSession();
+ if (!session.itemExists(jcrPath))
+ return null;
+ Node node = session.getNode(jcrPath);
+ return node;
+ }
+
+ String toJcrPath(String fsPath) {
+ if (fsPath.length() == 1)
+ return toJcrPath((String[]) null);// root
+ String[] arr = fsPath.substring(1).split("/");
+// if (arr.length == 0 || (arr.length == 1 && arr[0].equals("")))
+// return toJcrPath((String[]) null);// root
+// else
+ return toJcrPath(arr);
+ }
+
+ private String toJcrPath(String[] path) {
+ if (path == null)
+ return "/";
+ if (path.length < mountDepth)
+ throw new IllegalArgumentException(
+ "Path " + Arrays.asList(path) + " is no compatible with mount " + mountPath);
+
+ if (!isBase()) {
+ // check mount compatibility
+ StringBuilder mount = new StringBuilder();
+ mount.append('/');
+ for (int i = 0; i < mountDepth; i++) {
+ if (i != 0)
+ mount.append('/');
+ mount.append(Text.escapeIllegalJcrChars(path[i]));
+ }
+ if (!mountPath.equals(mount.toString()))
+ throw new IllegalArgumentException(
+ "Path " + Arrays.asList(path) + " is no compatible with mount " + mountPath);
+ }
+
+ StringBuilder sb = new StringBuilder();
+ sb.append('/');
+ for (int i = mountDepth; i < path.length; i++) {
+ if (i != mountDepth)
+ sb.append('/');
+ sb.append(Text.escapeIllegalJcrChars(path[i]));
+ }
+ return sb.toString();
+ }
+
+ public String getMountPath() {
+ return mountPath;
+ }
+
+ public String getWorkspaceName() {
+ return workspaceName;
+ }
+
+ public int getMountDepth() {
+ return mountDepth;
+ }
+
+ @Override
+ public int hashCode() {
+ return workspaceName.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof WorkspaceFileStore))
+ return false;
+ WorkspaceFileStore other = (WorkspaceFileStore) obj;
+ return workspaceName.equals(other.workspaceName);
+ }
+
+ @Override
+ public String toString() {
+ return "WorkspaceFileStore " + workspaceName;
+ }
+
+}
--- /dev/null
+/** Java NIO file system implementation based on plain JCR. */
+package org.argeo.jcr.fs;
\ No newline at end of file
--- /dev/null
+//
+// JCR EXTENSIONS
+//
+<jcrx = "http://www.argeo.org/ns/jcrx">
+
+[jcrx:xmlvalue]
+- *
++ jcr:xmltext (jcrx:xmltext) = jcrx:xmltext
+
+[jcrx:xmltext]
+ - jcr:xmlcharacters (STRING) mandatory
+
+[jcrx:csum]
+mixin
+ - jcrx:sum (STRING) *
+
\ No newline at end of file
--- /dev/null
+/** Generic JCR utilities. */
+package org.argeo.jcr;
\ No newline at end of file
--- /dev/null
+package org.argeo.jcr.proxy;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+
+import javax.jcr.Binary;
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.nodetype.NodeType;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.argeo.jcr.JcrException;
+import org.argeo.jcr.JcrUtils;
+
+/** Base class for URL based proxys. */
+public abstract class AbstractUrlProxy implements ResourceProxy {
+ private final static Log log = LogFactory.getLog(AbstractUrlProxy.class);
+
+ private Repository jcrRepository;
+ private Session jcrAdminSession;
+ private String proxyWorkspace = "proxy";
+
+ protected abstract Node retrieve(Session session, String path);
+
+ void init() {
+ try {
+ jcrAdminSession = JcrUtils.loginOrCreateWorkspace(jcrRepository, proxyWorkspace);
+ beforeInitSessionSave(jcrAdminSession);
+ if (jcrAdminSession.hasPendingChanges())
+ jcrAdminSession.save();
+ } catch (RepositoryException e) {
+ JcrUtils.discardQuietly(jcrAdminSession);
+ throw new JcrException("Cannot initialize URL proxy", e);
+ }
+ }
+
+ /**
+ * Called before the (admin) session is saved at the end of the initialization.
+ * Does nothing by default, to be overridden.
+ */
+ protected void beforeInitSessionSave(Session session) throws RepositoryException {
+ }
+
+ void destroy() {
+ JcrUtils.logoutQuietly(jcrAdminSession);
+ }
+
+ /**
+ * Called before the (admin) session is logged out when resources are released.
+ * Does nothing by default, to be overridden.
+ */
+ protected void beforeDestroySessionLogout() throws RepositoryException {
+ }
+
+ public Node proxy(String path) {
+ // we open a JCR session with client credentials in order not to use the
+ // admin session in multiple thread or make it a bottleneck.
+ Node nodeAdmin = null;
+ Node nodeClient = null;
+ Session clientSession = null;
+ try {
+ clientSession = jcrRepository.login(proxyWorkspace);
+ if (!clientSession.itemExists(path) || shouldUpdate(clientSession, path)) {
+ nodeAdmin = retrieveAndSave(path);
+ if (nodeAdmin != null)
+ nodeClient = clientSession.getNode(path);
+ } else
+ nodeClient = clientSession.getNode(path);
+ return nodeClient;
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot proxy " + path, e);
+ } finally {
+ if (nodeClient == null)
+ JcrUtils.logoutQuietly(clientSession);
+ }
+ }
+
+ protected synchronized Node retrieveAndSave(String path) {
+ try {
+ Node node = retrieve(jcrAdminSession, path);
+ if (node == null)
+ return null;
+ jcrAdminSession.save();
+ return node;
+ } catch (RepositoryException e) {
+ JcrUtils.discardQuietly(jcrAdminSession);
+ throw new JcrException("Cannot retrieve and save " + path, e);
+ } finally {
+ notifyAll();
+ }
+ }
+
+ /** Session is not saved */
+ protected synchronized Node proxyUrl(Session session, String remoteUrl, String path) throws RepositoryException {
+ Node node = null;
+ if (session.itemExists(path)) {
+ // throw new ArgeoJcrException("Node " + path + " already exists");
+ }
+ try (InputStream in = new URL(remoteUrl).openStream()) {
+ // URL u = new URL(remoteUrl);
+ // in = u.openStream();
+ node = importFile(session, path, in);
+ } catch (IOException e) {
+ if (log.isDebugEnabled()) {
+ log.debug("Cannot read " + remoteUrl + ", skipping... " + e.getMessage());
+ // log.trace("Cannot read because of ", e);
+ }
+ JcrUtils.discardQuietly(session);
+ // } finally {
+ // IOUtils.closeQuietly(in);
+ }
+ return node;
+ }
+
+ protected synchronized Node importFile(Session session, String path, InputStream in) throws RepositoryException {
+ Binary binary = null;
+ try {
+ Node content = null;
+ Node node = null;
+ if (!session.itemExists(path)) {
+ node = JcrUtils.mkdirs(session, path, NodeType.NT_FILE, NodeType.NT_FOLDER, false);
+ content = node.addNode(Node.JCR_CONTENT, NodeType.NT_UNSTRUCTURED);
+ } else {
+ node = session.getNode(path);
+ content = node.getNode(Node.JCR_CONTENT);
+ }
+ binary = session.getValueFactory().createBinary(in);
+ content.setProperty(Property.JCR_DATA, binary);
+ JcrUtils.updateLastModifiedAndParents(node, null, true);
+ return node;
+ } finally {
+ JcrUtils.closeQuietly(binary);
+ }
+ }
+
+ /** Whether the file should be updated. */
+ protected Boolean shouldUpdate(Session clientSession, String nodePath) {
+ return false;
+ }
+
+ public void setJcrRepository(Repository jcrRepository) {
+ this.jcrRepository = jcrRepository;
+ }
+
+ public void setProxyWorkspace(String localWorkspace) {
+ this.proxyWorkspace = localWorkspace;
+ }
+
+}
--- /dev/null
+package org.argeo.jcr.proxy;
+
+import javax.jcr.Node;
+
+/** A proxy which nows how to resolve and synchronize relative URLs */
+public interface ResourceProxy {
+ /**
+ * Proxy the file referenced by this relative path in the underlying
+ * repository. A new session is created by each call, so the underlying
+ * session of the returned node must be closed by the caller.
+ *
+ * @return the proxied Node, <code>null</code> if the resource was not found
+ * (e.g. HTTP 404)
+ */
+ public Node proxy(String relativePath);
+}
--- /dev/null
+package org.argeo.jcr.proxy;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.RepositoryException;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.io.FilenameUtils;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.argeo.jcr.JcrException;
+import org.argeo.jcr.Bin;
+import org.argeo.jcr.JcrUtils;
+
+/** Wraps a proxy via HTTP */
+public class ResourceProxyServlet extends HttpServlet {
+ private static final long serialVersionUID = -8886549549223155801L;
+
+ private final static Log log = LogFactory
+ .getLog(ResourceProxyServlet.class);
+
+ private ResourceProxy proxy;
+
+ private String contentTypeCharset = "UTF-8";
+
+ @Override
+ protected void doGet(HttpServletRequest request,
+ HttpServletResponse response) throws ServletException, IOException {
+ String path = request.getPathInfo();
+
+ if (log.isTraceEnabled()) {
+ log.trace("path=" + path);
+ log.trace("UserPrincipal = " + request.getUserPrincipal().getName());
+ log.trace("SessionID = " + request.getSession(false).getId());
+ log.trace("ContextPath = " + request.getContextPath());
+ log.trace("ServletPath = " + request.getServletPath());
+ log.trace("PathInfo = " + request.getPathInfo());
+ log.trace("Method = " + request.getMethod());
+ log.trace("User-Agent = " + request.getHeader("User-Agent"));
+ }
+
+ Node node = null;
+ try {
+ node = proxy.proxy(path);
+ if (node == null)
+ response.sendError(404);
+ else
+ processResponse(node, response);
+ } finally {
+ if (node != null)
+ try {
+ JcrUtils.logoutQuietly(node.getSession());
+ } catch (RepositoryException e) {
+ // silent
+ }
+ }
+
+ }
+
+ /** Retrieve the content of the node. */
+ protected void processResponse(Node node, HttpServletResponse response) {
+// Binary binary = null;
+// InputStream in = null;
+ try(Bin binary = new Bin( node.getNode(Property.JCR_CONTENT)
+ .getProperty(Property.JCR_DATA));InputStream in = binary.getStream()) {
+ String fileName = node.getName();
+ String ext = FilenameUtils.getExtension(fileName);
+
+ // TODO use a more generic / standard approach
+ // see http://svn.apache.org/viewvc/tomcat/trunk/conf/web.xml
+ String contentType;
+ if ("xml".equals(ext))
+ contentType = "text/xml;charset=" + contentTypeCharset;
+ else if ("jar".equals(ext))
+ contentType = "application/java-archive";
+ else if ("zip".equals(ext))
+ contentType = "application/zip";
+ else if ("gz".equals(ext))
+ contentType = "application/x-gzip";
+ else if ("bz2".equals(ext))
+ contentType = "application/x-bzip2";
+ else if ("tar".equals(ext))
+ contentType = "application/x-tar";
+ else if ("rpm".equals(ext))
+ contentType = "application/x-redhat-package-manager";
+ else
+ contentType = "application/octet-stream";
+ contentType = contentType + ";name=\"" + fileName + "\"";
+ response.setHeader("Content-Disposition", "attachment; filename=\""
+ + fileName + "\"");
+ response.setHeader("Expires", "0");
+ response.setHeader("Cache-Control", "no-cache, must-revalidate");
+ response.setHeader("Pragma", "no-cache");
+
+ response.setContentType(contentType);
+
+ IOUtils.copy(in, response.getOutputStream());
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot download " + node, e);
+ } catch (IOException e) {
+ throw new RuntimeException("Cannot download " + node, e);
+ }
+ }
+
+ public void setProxy(ResourceProxy resourceProxy) {
+ this.proxy = resourceProxy;
+ }
+
+}
--- /dev/null
+/** Components to build proxys based on JCR. */
+package org.argeo.jcr.proxy;
\ No newline at end of file
--- /dev/null
+package org.argeo.jcr.unit;
+
+import java.io.File;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.SimpleCredentials;
+import javax.security.auth.Subject;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.argeo.jcr.JcrException;
+
+import junit.framework.TestCase;
+
+/** Base for unit tests with a JCR repository. */
+public abstract class AbstractJcrTestCase extends TestCase {
+ private final static Log log = LogFactory.getLog(AbstractJcrTestCase.class);
+
+ private Repository repository;
+ private Session session = null;
+
+ public final static String LOGIN_CONTEXT_TEST_SYSTEM = "TEST_JACKRABBIT_ADMIN";
+
+ // protected abstract File getRepositoryFile() throws Exception;
+
+ protected abstract Repository createRepository() throws Exception;
+
+ protected abstract void clearRepository(Repository repository) throws Exception;
+
+ @Override
+ protected void setUp() throws Exception {
+ File homeDir = getHomeDir();
+ FileUtils.deleteDirectory(homeDir);
+ repository = createRepository();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ if (session != null) {
+ session.logout();
+ if (log.isTraceEnabled())
+ log.trace("Logout session");
+ }
+ clearRepository(repository);
+ }
+
+ protected Session session() {
+ if (session != null && session.isLive())
+ return session;
+ Session session;
+ if (getLoginContext() != null) {
+ LoginContext lc;
+ try {
+ lc = new LoginContext(getLoginContext());
+ lc.login();
+ } catch (LoginException e) {
+ throw new IllegalStateException("JAAS login failed", e);
+ }
+ session = Subject.doAs(lc.getSubject(), new PrivilegedAction<Session>() {
+
+ @Override
+ public Session run() {
+ return login();
+ }
+
+ });
+ } else
+ session = login();
+ this.session = session;
+ return this.session;
+ }
+
+ protected String getLoginContext() {
+ return null;
+ }
+
+ protected Session login() {
+ try {
+ if (log.isTraceEnabled())
+ log.trace("Login session");
+ Subject subject = Subject.getSubject(AccessController.getContext());
+ if (subject != null)
+ return getRepository().login();
+ else
+ return getRepository().login(new SimpleCredentials("demo", "demo".toCharArray()));
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot login to repository", e);
+ }
+ }
+
+ protected Repository getRepository() {
+ return repository;
+ }
+
+ /**
+ * enables children class to set an existing repository in case it is not
+ * deleted on startup, to test migration by instance
+ */
+ public void setRepository(Repository repository) {
+ this.repository = repository;
+ }
+
+ protected File getHomeDir() {
+ File homeDir = new File(System.getProperty("java.io.tmpdir"),
+ AbstractJcrTestCase.class.getSimpleName() + "-" + System.getProperty("user.name"));
+ return homeDir;
+ }
+
+}
--- /dev/null
+/** Helpers for unit tests with JCR repositories. */
+package org.argeo.jcr.unit;
\ No newline at end of file
--- /dev/null
+package org.argeo.jcr.xml;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.Map;
+import java.util.TreeMap;
+
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.Property;
+import javax.jcr.PropertyIterator;
+import javax.jcr.RepositoryException;
+import javax.jcr.Value;
+import javax.jcr.nodetype.NodeType;
+
+import org.argeo.jcr.Jcr;
+
+/** Utilities around JCR and XML. */
+public class JcrXmlUtils {
+ /**
+ * Convenience method calling {@link #toXmlElements(Writer, Node, boolean)} with
+ * <code>false</code>.
+ */
+ public static void toXmlElements(Writer writer, Node node) throws RepositoryException, IOException {
+ toXmlElements(writer, node, null, false, false, false);
+ }
+
+ /**
+ * Write JCR properties as XML elements in a tree structure whose elements are
+ * named by node primary type.
+ *
+ * @param writer the writer to use
+ * @param node the subtree
+ * @param depth maximal depth, or if <code>null</code> the whole
+ * subtree. It must be positive, with depth 0
+ * describing just the node without its children.
+ * @param withMetadata whether to write the primary type and mixins as
+ * elements
+ * @param withPrefix whether to keep the namespace prefixes
+ * @param propertiesAsElements whether single properties should be written as
+ * elements rather than attributes. If
+ * <code>false</code>, multiple properties will be
+ * skipped.
+ */
+ public static void toXmlElements(Writer writer, Node node, Integer depth, boolean withMetadata, boolean withPrefix,
+ boolean propertiesAsElements) throws RepositoryException, IOException {
+ if (depth != null && depth < 0)
+ throw new IllegalArgumentException("Depth " + depth + " is negative.");
+
+ if (node.getName().equals(Jcr.JCR_XMLTEXT)) {
+ writer.write(node.getProperty(Jcr.JCR_XMLCHARACTERS).getString());
+ return;
+ }
+
+ if (!propertiesAsElements) {
+ Map<String, String> attrs = new TreeMap<>();
+ PropertyIterator pit = node.getProperties();
+ properties: while (pit.hasNext()) {
+ Property p = pit.nextProperty();
+ if (!p.isMultiple()) {
+ String pName = p.getName();
+ if (!withMetadata && (pName.equals(Jcr.JCR_PRIMARY_TYPE) || pName.equals(Jcr.JCR_UUID)
+ || pName.equals(Jcr.JCR_CREATED) || pName.equals(Jcr.JCR_CREATED_BY)
+ || pName.equals(Jcr.JCR_LAST_MODIFIED) || pName.equals(Jcr.JCR_LAST_MODIFIED_BY)))
+ continue properties;
+ attrs.put(withPrefix(p.getName(), withPrefix), p.getString());
+ }
+ }
+ if (withMetadata && node.hasProperty(Property.JCR_UUID))
+ attrs.put("id", "urn:uuid:" + node.getProperty(Property.JCR_UUID).getString());
+ attrs.put(withPrefix ? Jcr.JCR_NAME : "name", node.getName());
+ writeStart(writer, withPrefix(node.getPrimaryNodeType().getName(), withPrefix), attrs, node.hasNodes());
+ } else {
+ if (withMetadata && node.hasProperty(Property.JCR_UUID)) {
+ writeStart(writer, withPrefix(node.getPrimaryNodeType().getName(), withPrefix), "id",
+ "urn:uuid:" + node.getProperty(Property.JCR_UUID).getString());
+ } else {
+ writeStart(writer, withPrefix(node.getPrimaryNodeType().getName(), withPrefix));
+ }
+ // name
+ writeStart(writer, withPrefix ? Jcr.JCR_NAME : "name");
+ writer.append(node.getName());
+ writeEnd(writer, withPrefix ? Jcr.JCR_NAME : "name");
+ }
+
+ // mixins
+ if (withMetadata) {
+ for (NodeType mixin : node.getMixinNodeTypes()) {
+ writeStart(writer, withPrefix ? Jcr.JCR_MIXIN_TYPES : "mixinTypes");
+ writer.append(mixin.getName());
+ writeEnd(writer, withPrefix ? Jcr.JCR_MIXIN_TYPES : "mixinTypes");
+ }
+ }
+
+ // properties as elements
+ if (propertiesAsElements) {
+ PropertyIterator pit = node.getProperties();
+ properties: while (pit.hasNext()) {
+ Property p = pit.nextProperty();
+ if (p.isMultiple()) {
+ for (Value value : p.getValues()) {
+ writeStart(writer, withPrefix(p.getName(), withPrefix));
+ writer.write(value.getString());
+ writeEnd(writer, withPrefix(p.getName(), withPrefix));
+ }
+ } else {
+ Value value = p.getValue();
+ String pName = p.getName();
+ if (!withMetadata && (pName.equals(Jcr.JCR_PRIMARY_TYPE) || pName.equals(Jcr.JCR_UUID)
+ || pName.equals(Jcr.JCR_CREATED) || pName.equals(Jcr.JCR_CREATED_BY)
+ || pName.equals(Jcr.JCR_LAST_MODIFIED) || pName.equals(Jcr.JCR_LAST_MODIFIED_BY)))
+ continue properties;
+ writeStart(writer, withPrefix(p.getName(), withPrefix));
+ writer.write(value.getString());
+ writeEnd(writer, withPrefix(p.getName(), withPrefix));
+ }
+ }
+ }
+
+ // children
+ if (node.hasNodes()) {
+ if (depth == null || depth > 0) {
+ NodeIterator nit = node.getNodes();
+ while (nit.hasNext()) {
+ toXmlElements(writer, nit.nextNode(), depth == null ? null : depth - 1, withMetadata, withPrefix,
+ propertiesAsElements);
+ }
+ }
+ writeEnd(writer, withPrefix(node.getPrimaryNodeType().getName(), withPrefix));
+ }
+ }
+
+ private static String withPrefix(String str, boolean withPrefix) {
+ if (withPrefix)
+ return str;
+ int index = str.indexOf(':');
+ if (index < 0)
+ return str;
+ return str.substring(index + 1);
+ }
+
+ private static void writeStart(Writer writer, String tagName) throws IOException {
+ writer.append('<');
+ writer.append(tagName);
+ writer.append('>');
+ }
+
+ private static void writeStart(Writer writer, String tagName, String attr, String value) throws IOException {
+ writer.append('<');
+ writer.append(tagName);
+ writer.append(' ');
+ writer.append(attr);
+ writer.append("=\"");
+ writer.append(value);
+ writer.append("\">");
+ }
+
+ private static void writeStart(Writer writer, String tagName, Map<String, String> attrs, boolean hasChildren)
+ throws IOException {
+ writer.append('<');
+ writer.append(tagName);
+ for (String attr : attrs.keySet()) {
+ writer.append(' ');
+ writer.append(attr);
+ writer.append("=\"");
+ writer.append(attrs.get(attr));
+ writer.append('\"');
+ }
+ if (hasChildren)
+ writer.append('>');
+ else
+ writer.append("/>");
+ }
+
+ private static void writeEnd(Writer writer, String tagName) throws IOException {
+ writer.append("</");
+ writer.append(tagName);
+ writer.append('>');
+ }
+
+ /** Singleton. */
+ private JcrXmlUtils() {
+
+ }
+
+}
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" ?>
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
+ <xsl:output method="xml" indent="yes"/>
+ <xsl:template match="/|comment()|processing-instruction()">
+ <xsl:copy>
+ <xsl:apply-templates/>
+ </xsl:copy>
+ </xsl:template>
+ <xsl:template match="*">
+ <xsl:element name="{local-name()}">
+ <xsl:apply-templates select="@*|node()"/>
+ </xsl:element>
+ </xsl:template>
+ <xsl:template match="@*">
+ <xsl:attribute name="{local-name()}">
+ <xsl:value-of select="."/>
+ </xsl:attribute>
+ </xsl:template>
+</xsl:stylesheet>
\ No newline at end of file
--- /dev/null
+package org.argeo.maintenance;
+
+import java.io.IOException;
+import java.util.EnumSet;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.jcr.NoSuchWorkspaceException;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.transaction.UserTransaction;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.argeo.api.NodeUtils;
+import org.argeo.jcr.Jcr;
+import org.argeo.jcr.JcrUtils;
+import org.argeo.naming.Distinguished;
+import org.osgi.service.useradmin.Group;
+import org.osgi.service.useradmin.Role;
+import org.osgi.service.useradmin.UserAdmin;
+
+/** Make sure roles and access rights are properly configured. */
+public abstract class AbstractMaintenanceService {
+ private final static Log log = LogFactory.getLog(AbstractMaintenanceService.class);
+
+ private Repository repository;
+// private UserAdminService userAdminService;
+ private UserAdmin userAdmin;
+ private UserTransaction userTransaction;
+
+ public void init() {
+ makeSureRolesExists(getRequiredRoles());
+ configureStandardRoles();
+
+ Set<String> workspaceNames = getWorkspaceNames();
+ if (workspaceNames == null || workspaceNames.isEmpty()) {
+ configureJcr(repository, null);
+ } else {
+ for (String workspaceName : workspaceNames)
+ configureJcr(repository, workspaceName);
+ }
+ }
+
+ /** Configures a workspace. */
+ protected void configureJcr(Repository repository, String workspaceName) {
+ Session adminSession;
+ try {
+ adminSession = NodeUtils.openDataAdminSession(repository, workspaceName);
+ } catch (RuntimeException e1) {
+ if (e1.getCause() != null && e1.getCause() instanceof NoSuchWorkspaceException) {
+ Session defaultAdminSession = NodeUtils.openDataAdminSession(repository, null);
+ try {
+ defaultAdminSession.getWorkspace().createWorkspace(workspaceName);
+ log.info("Created JCR workspace " + workspaceName);
+ } catch (RepositoryException e) {
+ throw new IllegalStateException("Cannot create workspace " + workspaceName, e);
+ } finally {
+ Jcr.logout(defaultAdminSession);
+ }
+ adminSession = NodeUtils.openDataAdminSession(repository, workspaceName);
+ } else
+ throw e1;
+ }
+ try {
+ if (prepareJcrTree(adminSession)) {
+ configurePrivileges(adminSession);
+ }
+ } catch (RepositoryException | IOException e) {
+ throw new IllegalStateException("Cannot initialise JCR data layer.", e);
+ } finally {
+ JcrUtils.logoutQuietly(adminSession);
+ }
+ }
+
+ /** To be overridden. */
+ protected Set<String> getWorkspaceNames() {
+ return null;
+ }
+
+ /**
+ * To be overridden in order to programmatically set relationships between
+ * roles. Does nothing by default.
+ */
+ protected void configureStandardRoles() {
+ }
+
+ /**
+ * Creates the base JCR tree structure expected for this app if necessary.
+ *
+ * Expects a clean session ({@link Session#hasPendingChanges()} should return
+ * false) and saves it once the changes have been done. Thus the session can be
+ * rolled back if an exception occurs.
+ *
+ * @return true if something as been updated
+ */
+ public boolean prepareJcrTree(Session adminSession) throws RepositoryException, IOException {
+ return false;
+ }
+
+ /**
+ * Adds app specific default privileges.
+ *
+ * Expects a clean session ({@link Session#hasPendingChanges()} should return
+ * false} and saves it once the changes have been done. Thus the session can be
+ * rolled back if an exception occurs.
+ *
+ * Warning: no check is done and corresponding privileges are always added, so
+ * only call this when necessary
+ */
+ public void configurePrivileges(Session session) throws RepositoryException {
+ }
+
+ /** The system roles that must be available in the system. */
+ protected Set<String> getRequiredRoles() {
+ return new HashSet<>();
+ }
+
+ public void destroy() {
+
+ }
+
+ /*
+ * UTILITIES
+ */
+
+ /** Create these roles as group if they don't exist. */
+ protected void makeSureRolesExists(EnumSet<? extends Distinguished> enumSet) {
+ makeSureRolesExists(Distinguished.enumToDns(enumSet));
+ }
+
+ /** Create these roles as group if they don't exist. */
+ protected void makeSureRolesExists(Set<String> requiredRoles) {
+ if (requiredRoles == null)
+ return;
+ if (getUserAdmin() == null) {
+ log.warn("No user admin service available, cannot make sure that role exists");
+ return;
+ }
+ for (String role : requiredRoles) {
+ Role systemRole = getUserAdmin().getRole(role);
+ if (systemRole == null) {
+ try {
+ getUserTransaction().begin();
+ getUserAdmin().createRole(role, Role.GROUP);
+ getUserTransaction().commit();
+ log.info("Created role " + role);
+ } catch (Exception e) {
+ try {
+ getUserTransaction().rollback();
+ } catch (Exception e1) {
+ // silent
+ }
+ throw new IllegalStateException("Cannot create role " + role, e);
+ }
+ }
+ }
+ }
+
+ /** Add a user or group to a group. */
+ protected void addToGroup(String groupToAddDn, String groupDn) {
+ if (groupToAddDn.contentEquals(groupDn)) {
+ if (log.isTraceEnabled())
+ log.trace("Ignore adding group " + groupDn + " to itself");
+ return;
+ }
+
+ if (getUserAdmin() == null) {
+ log.warn("No user admin service available, cannot add group " + groupToAddDn + " to " + groupDn);
+ return;
+ }
+ Group groupToAdd = (Group) getUserAdmin().getRole(groupToAddDn);
+ if (groupToAdd == null)
+ throw new IllegalArgumentException("Group " + groupToAddDn + " not found");
+ Group group = (Group) getUserAdmin().getRole(groupDn);
+ if (group == null)
+ throw new IllegalArgumentException("Group " + groupDn + " not found");
+ try {
+ getUserTransaction().begin();
+ if (group.addMember(groupToAdd))
+ log.info("Added " + groupToAddDn + " to " + group);
+ getUserTransaction().commit();
+ } catch (Exception e) {
+ try {
+ getUserTransaction().rollback();
+ } catch (Exception e1) {
+ // silent
+ }
+ throw new IllegalStateException("Cannot add " + groupToAddDn + " to " + groupDn);
+ }
+ }
+
+ /*
+ * DEPENDENCY INJECTION
+ */
+ public void setRepository(Repository repository) {
+ this.repository = repository;
+ }
+
+// public void setUserAdminService(UserAdminService userAdminService) {
+// this.userAdminService = userAdminService;
+// }
+
+ protected UserTransaction getUserTransaction() {
+ return userTransaction;
+ }
+
+ protected UserAdmin getUserAdmin() {
+ return userAdmin;
+ }
+
+ public void setUserAdmin(UserAdmin userAdmin) {
+ this.userAdmin = userAdmin;
+ }
+
+ public void setUserTransaction(UserTransaction userTransaction) {
+ this.userTransaction = userTransaction;
+ }
+
+}
--- /dev/null
+package org.argeo.maintenance;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import javax.naming.InvalidNameException;
+import javax.naming.ldap.LdapName;
+import javax.transaction.UserTransaction;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.osgi.service.useradmin.Role;
+import org.osgi.service.useradmin.UserAdmin;
+
+/**
+ * Register one or many roles via a user admin service. Does nothing if the role
+ * is already registered.
+ */
+public class SimpleRoleRegistration implements Runnable {
+ private final static Log log = LogFactory.getLog(SimpleRoleRegistration.class);
+
+ private String role;
+ private List<String> roles = new ArrayList<String>();
+ private UserAdmin userAdmin;
+ private UserTransaction userTransaction;
+
+ @Override
+ public void run() {
+ try {
+ userTransaction.begin();
+ if (role != null && !roleExists(role))
+ newRole(toDn(role));
+
+ for (String r : roles)
+ if (!roleExists(r))
+ newRole(toDn(r));
+ userTransaction.commit();
+ } catch (Exception e) {
+ try {
+ userTransaction.rollback();
+ } catch (Exception e1) {
+ log.error("Cannot rollback", e1);
+ }
+ throw new IllegalArgumentException("Cannot add roles", e);
+ }
+ }
+
+ private boolean roleExists(String role) {
+ return userAdmin.getRole(toDn(role).toString()) != null;
+ }
+
+ protected void newRole(LdapName r) {
+ userAdmin.createRole(r.toString(), Role.GROUP);
+ log.info("Added role " + r + " required by application.");
+ }
+
+ public void register(UserAdmin userAdminService, Map<?, ?> properties) {
+ this.userAdmin = userAdminService;
+ run();
+ }
+
+ protected LdapName toDn(String name) {
+ try {
+ return new LdapName("cn=" + name + ",ou=roles,ou=node");
+ } catch (InvalidNameException e) {
+ throw new IllegalArgumentException("Badly formatted role name " + name, e);
+ }
+ }
+
+ public void setRole(String role) {
+ this.role = role;
+ }
+
+ public void setRoles(List<String> roles) {
+ this.roles = roles;
+ }
+
+ public void setUserAdmin(UserAdmin userAdminService) {
+ this.userAdmin = userAdminService;
+ }
+
+ public void setUserTransaction(UserTransaction userTransaction) {
+ this.userTransaction = userTransaction;
+ }
+
+}
--- /dev/null
+package org.argeo.maintenance.backup;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Writer;
+import java.util.Arrays;
+import java.util.Base64;
+import java.util.Set;
+import java.util.TreeSet;
+
+import javax.jcr.Binary;
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.apache.commons.io.IOUtils;
+import org.argeo.jcr.Jcr;
+import org.argeo.jcr.JcrException;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+/** XML handler serialising a JCR system view. */
+public class BackupContentHandler extends DefaultHandler {
+ final static int MAX_DEPTH = 1024;
+ final static String SV_NAMESPACE_URI = "http://www.jcp.org/jcr/sv/1.0";
+ final static String SV_PREFIX = "sv";
+ // elements
+ final static String NODE = "node";
+ final static String PROPERTY = "property";
+ final static String VALUE = "value";
+ // attributes
+ final static String NAME = "name";
+ final static String MULTIPLE = "multiple";
+ final static String TYPE = "type";
+
+ // values
+ final static String BINARY = "Binary";
+ final static String JCR_CONTENT = "jcr:content";
+
+ private Writer out;
+ private Session session;
+ private Set<String> contentPaths = new TreeSet<>();
+
+ boolean prettyPrint = true;
+
+ private final String parentPath;
+
+// private boolean inSystem = false;
+
+ public BackupContentHandler(Writer out, Node node) {
+ super();
+ this.out = out;
+ this.session = Jcr.getSession(node);
+ parentPath = Jcr.getParentPath(node);
+ }
+
+ private int currentDepth = -1;
+ private String[] currentPath = new String[MAX_DEPTH];
+
+ private boolean currentPropertyIsMultiple = false;
+ private String currentEncoded = null;
+ private Base64.Encoder base64encore = Base64.getEncoder();
+
+ @Override
+ public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
+ boolean isNode;
+ boolean isProperty;
+ switch (localName) {
+ case NODE:
+ isNode = true;
+ isProperty = false;
+ break;
+ case PROPERTY:
+ isNode = false;
+ isProperty = true;
+ break;
+ default:
+ isNode = false;
+ isProperty = false;
+ }
+
+ if (isNode) {
+ String nodeName = attributes.getValue(SV_NAMESPACE_URI, NAME);
+ currentDepth = currentDepth + 1;
+// if (currentDepth >= 0)
+ currentPath[currentDepth] = nodeName;
+// System.out.println(getCurrentPath() + " , depth=" + currentDepth);
+// if ("jcr:system".equals(nodeName)) {
+// inSystem = true;
+// }
+ }
+// if (inSystem)
+// return;
+
+ if (SV_NAMESPACE_URI.equals(uri))
+ try {
+ if (prettyPrint) {
+ if (isNode) {
+ out.write(spaces());
+ out.write("<!-- ");
+ out.write(getCurrentJcrPath());
+ out.write(" -->\n");
+ out.write(spaces());
+ } else if (isProperty)
+ out.write(spaces());
+ else if (currentPropertyIsMultiple)
+ out.write(spaces());
+ }
+
+ out.write("<");
+ out.write(SV_PREFIX + ":" + localName);
+ if (isProperty)
+ currentPropertyIsMultiple = false; // always reset
+ for (int i = 0; i < attributes.getLength(); i++) {
+ String ns = attributes.getURI(i);
+ if (SV_NAMESPACE_URI.equals(ns)) {
+ String attrName = attributes.getLocalName(i);
+ String attrValue = attributes.getValue(i);
+ out.write(" ");
+ out.write(SV_PREFIX + ":" + attrName);
+ out.write("=");
+ out.write("\"");
+ out.write(attrValue);
+ out.write("\"");
+ if (isProperty) {
+ if (MULTIPLE.equals(attrName))
+ currentPropertyIsMultiple = Boolean.parseBoolean(attrValue);
+ else if (TYPE.equals(attrName)) {
+ if (BINARY.equals(attrValue)) {
+ if (JCR_CONTENT.equals(getCurrentName())) {
+ contentPaths.add(getCurrentJcrPath());
+ } else {
+ Binary binary = session.getNode(getCurrentJcrPath()).getProperty(attrName)
+ .getBinary();
+ try (InputStream in = binary.getStream()) {
+ currentEncoded = base64encore.encodeToString(IOUtils.toByteArray(in));
+ } finally {
+
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ if (isNode && currentDepth == 0) {
+ // out.write(" xmlns=\"" + SV_NAMESPACE_URI + "\"");
+ out.write(" xmlns:" + SV_PREFIX + "=\"" + SV_NAMESPACE_URI + "\"");
+ }
+ out.write(">");
+
+ if (prettyPrint)
+ if (isNode)
+ out.write("\n");
+ else if (isProperty && currentPropertyIsMultiple)
+ out.write("\n");
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ } catch (RepositoryException e) {
+ throw new JcrException(e);
+ }
+ }
+
+ @Override
+ public void endElement(String uri, String localName, String qName) throws SAXException {
+ boolean isNode = localName.equals(NODE);
+ boolean isValue = localName.equals(VALUE);
+ if (prettyPrint)
+ if (!isValue)
+ try {
+ if (isNode || currentPropertyIsMultiple)
+ out.write(spaces());
+ } catch (IOException e1) {
+ throw new RuntimeException(e1);
+ }
+ if (isNode) {
+// System.out.println("endElement " + getCurrentPath() + " , depth=" + currentDepth);
+// if (currentDepth > 0)
+ currentPath[currentDepth] = null;
+ currentDepth = currentDepth - 1;
+// if (inSystem) {
+// // System.out.println("Skip " + getCurrentPath()+" ,
+// // currentDepth="+currentDepth);
+// if (currentDepth == 0) {
+// inSystem = false;
+// return;
+// }
+// }
+ }
+// if (inSystem)
+// return;
+ if (SV_NAMESPACE_URI.equals(uri))
+ try {
+ if (isValue && currentEncoded != null) {
+ out.write(currentEncoded);
+ }
+ currentEncoded = null;
+ out.write("</");
+ out.write(SV_PREFIX + ":" + localName);
+ out.write(">");
+ if (prettyPrint)
+ if (!isValue)
+ out.write("\n");
+ else {
+ if (currentPropertyIsMultiple)
+ out.write("\n");
+ }
+ if (currentDepth == 0)
+ out.flush();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+
+ }
+
+ private char[] spaces() {
+ char[] arr = new char[currentDepth];
+ Arrays.fill(arr, ' ');
+ return arr;
+ }
+
+ @Override
+ public void characters(char[] ch, int start, int length) throws SAXException {
+// if (inSystem)
+// return;
+ try {
+ out.write(ch, start, length);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ protected String getCurrentName() {
+ assert currentDepth >= 0;
+// if (currentDepth == 0)
+// return "jcr:root";
+ return currentPath[currentDepth];
+ }
+
+ protected String getCurrentJcrPath() {
+// if (currentDepth == 0)
+// return "/";
+ StringBuilder sb = new StringBuilder(parentPath.equals("/") ? "" : parentPath);
+ for (int i = 0; i <= currentDepth; i++) {
+// if (i != 0)
+ sb.append('/');
+ sb.append(currentPath[i]);
+ }
+ return sb.toString();
+ }
+
+ public Set<String> getContentPaths() {
+ return contentPaths;
+ }
+
+}
--- /dev/null
+package org.argeo.maintenance.backup;
+
+import java.io.BufferedWriter;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.net.URI;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.jar.JarOutputStream;
+import java.util.jar.Manifest;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipException;
+import java.util.zip.ZipOutputStream;
+
+import javax.jcr.Binary;
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.Property;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.RepositoryFactory;
+import javax.jcr.Session;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.jackrabbit.api.JackrabbitSession;
+import org.apache.jackrabbit.api.JackrabbitValue;
+import org.argeo.api.NodeConstants;
+import org.argeo.api.NodeUtils;
+import org.argeo.jackrabbit.client.ClientDavexRepositoryFactory;
+import org.argeo.jcr.Jcr;
+import org.argeo.jcr.JcrException;
+import org.argeo.jcr.JcrUtils;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+
+/**
+ * Performs a backup of the data based only on programmatic interfaces. Useful
+ * for migration or live backup. Physical backups of the underlying file
+ * systems, databases, LDAP servers, etc. should be performed for disaster
+ * recovery.
+ */
+public class LogicalBackup implements Runnable {
+ private final static Log log = LogFactory.getLog(LogicalBackup.class);
+
+ public final static String WORKSPACES_BASE = "workspaces/";
+ public final static String FILES_BASE = "files/";
+ public final static String OSGI_BASE = "share/osgi/";
+
+ public final static String JCR_SYSTEM = "jcr:system";
+ public final static String JCR_VERSION_STORAGE_PATH = "/jcr:system/jcr:versionStorage";
+
+ private final Repository repository;
+ private String defaultWorkspace;
+ private final BundleContext bundleContext;
+
+ private final ZipOutputStream zout;
+ private final Path basePath;
+
+ private ExecutorService executorService;
+
+ private boolean performSoftwareBackup = false;
+
+ private Map<String, String> checksums = new TreeMap<>();
+
+ private int threadCount = 5;
+
+ private boolean backupFailed = false;
+
+ public LogicalBackup(BundleContext bundleContext, Repository repository, Path basePath) {
+ this.repository = repository;
+ this.zout = null;
+ this.basePath = basePath;
+ this.bundleContext = bundleContext;
+ }
+
+ @Override
+ public void run() {
+ try {
+ log.info("Start logical backup to " + basePath);
+ perform();
+ } catch (Exception e) {
+ log.error("Unexpected exception when performing logical backup", e);
+ throw new IllegalStateException("Logical backup failed", e);
+ }
+
+ }
+
+ public void perform() throws RepositoryException, IOException {
+ if (executorService != null && !executorService.isTerminated())
+ throw new IllegalStateException("Another backup is running");
+ executorService = Executors.newFixedThreadPool(threadCount);
+ long begin = System.currentTimeMillis();
+ // software backup
+ if (bundleContext != null && performSoftwareBackup)
+ executorService.submit(() -> performSoftwareBackup(bundleContext));
+
+ // data backup
+ Session defaultSession = login(null);
+ defaultWorkspace = defaultSession.getWorkspace().getName();
+ try {
+ String[] workspaceNames = defaultSession.getWorkspace().getAccessibleWorkspaceNames();
+ workspaces: for (String workspaceName : workspaceNames) {
+ if ("security".equals(workspaceName))
+ continue workspaces;
+ performDataBackup(workspaceName);
+ }
+ } finally {
+ JcrUtils.logoutQuietly(defaultSession);
+ executorService.shutdown();
+ try {
+ executorService.awaitTermination(24, TimeUnit.HOURS);
+ } catch (InterruptedException e) {
+ // silent
+ throw new IllegalStateException("Backup was interrupted before completion", e);
+ }
+ }
+ // versions
+ executorService = Executors.newFixedThreadPool(threadCount);
+ try {
+ performVersionsBackup();
+ } finally {
+ executorService.shutdown();
+ try {
+ executorService.awaitTermination(24, TimeUnit.HOURS);
+ } catch (InterruptedException e) {
+ // silent
+ throw new IllegalStateException("Backup was interrupted before completion", e);
+ }
+ }
+ long duration = System.currentTimeMillis() - begin;
+ if (isBackupFailed())
+ log.info("System logical backup failed after " + (duration / 60000) + "min " + (duration / 1000) + "s");
+ else
+ log.info("System logical backup completed in " + (duration / 60000) + "min " + (duration / 1000) + "s");
+ }
+
+ protected void performDataBackup(String workspaceName) throws RepositoryException, IOException {
+ Session session = login(workspaceName);
+ try {
+ nodes: for (NodeIterator nit = session.getRootNode().getNodes(); nit.hasNext();) {
+ if (isBackupFailed())
+ return;
+ Node nodeToExport = nit.nextNode();
+ if (JCR_SYSTEM.equals(nodeToExport.getName()))
+ continue nodes;
+ String nodePath = nodeToExport.getPath();
+ Future<Set<String>> contentPathsFuture = executorService
+ .submit(() -> performNodeBackup(workspaceName, nodePath));
+ executorService.submit(() -> performFilesBackup(workspaceName, contentPathsFuture));
+ }
+ } finally {
+ Jcr.logout(session);
+ }
+ }
+
+ protected void performVersionsBackup() throws RepositoryException, IOException {
+ Session session = login(defaultWorkspace);
+ Node versionStorageNode = session.getNode(JCR_VERSION_STORAGE_PATH);
+ try {
+ for (NodeIterator nit = versionStorageNode.getNodes(); nit.hasNext();) {
+ Node nodeToExport = nit.nextNode();
+ String nodePath = nodeToExport.getPath();
+ if (isBackupFailed())
+ return;
+ Future<Set<String>> contentPathsFuture = executorService
+ .submit(() -> performNodeBackup(defaultWorkspace, nodePath));
+ executorService.submit(() -> performFilesBackup(defaultWorkspace, contentPathsFuture));
+ }
+ } finally {
+ Jcr.logout(session);
+ }
+
+ }
+
+ protected Set<String> performNodeBackup(String workspaceName, String nodePath) {
+ Session session = login(workspaceName);
+ try {
+ Node nodeToExport = session.getNode(nodePath);
+// String nodeName = nodeToExport.getName();
+// if (nodeName.startsWith("jcr:") || nodeName.startsWith("rep:"))
+// continue nodes;
+// // TODO make it more robust / configurable
+// if (nodeName.equals("user"))
+// continue nodes;
+ String relativePath = WORKSPACES_BASE + workspaceName + nodePath + ".xml";
+ OutputStream xmlOut = openOutputStream(relativePath);
+ BackupContentHandler contentHandler;
+ try (Writer writer = new BufferedWriter(new OutputStreamWriter(xmlOut, StandardCharsets.UTF_8))) {
+ contentHandler = new BackupContentHandler(writer, nodeToExport);
+ session.exportSystemView(nodeToExport.getPath(), contentHandler, true, false);
+ if (log.isDebugEnabled())
+ log.debug(workspaceName + ":" + nodePath + " metadata exported to " + relativePath);
+ }
+
+ // Files
+ Set<String> contentPaths = contentHandler.getContentPaths();
+ return contentPaths;
+ } catch (Exception e) {
+ markBackupFailed("Cannot backup node " + workspaceName + ":" + nodePath, e);
+ throw new ThreadDeath();
+ } finally {
+ Jcr.logout(session);
+ }
+ }
+
+ protected void performFilesBackup(String workspaceName, Future<Set<String>> contentPathsFuture) {
+ Set<String> contentPaths;
+ try {
+ contentPaths = contentPathsFuture.get(24, TimeUnit.HOURS);
+ } catch (InterruptedException | ExecutionException | TimeoutException e1) {
+ markBackupFailed("Cannot retrieve content paths for workspace " + workspaceName, e1);
+ return;
+ }
+ if (contentPaths == null || contentPaths.size() == 0)
+ return;
+ Session session = login(workspaceName);
+ try {
+ String workspacesFilesBasePath = FILES_BASE + workspaceName;
+ for (String path : contentPaths) {
+ if (isBackupFailed())
+ return;
+ Node contentNode = session.getNode(path);
+ Binary binary = null;
+ try {
+ binary = contentNode.getProperty(Property.JCR_DATA).getBinary();
+ String fileRelativePath = workspacesFilesBasePath + contentNode.getParent().getPath();
+
+ // checksum
+ boolean skip = false;
+ String checksum = null;
+ if (session instanceof JackrabbitSession) {
+ JackrabbitValue value = (JackrabbitValue) contentNode.getProperty(Property.JCR_DATA).getValue();
+// ReferenceBinary referenceBinary = (ReferenceBinary) binary;
+ checksum = value.getContentIdentity();
+ }
+ if (checksum != null) {
+ if (!checksums.containsKey(checksum)) {
+ checksums.put(checksum, fileRelativePath);
+ } else {
+ skip = true;
+ String sourcePath = checksums.get(checksum);
+ if (log.isTraceEnabled())
+ log.trace(fileRelativePath + " : already " + sourcePath + " with checksum " + checksum);
+ createLink(sourcePath, fileRelativePath);
+ try (Writer writerSum = new OutputStreamWriter(
+ openOutputStream(fileRelativePath + ".sha256"), StandardCharsets.UTF_8)) {
+ writerSum.write(checksum);
+ }
+ }
+ }
+
+ // copy file
+ if (!skip)
+ try (InputStream in = binary.getStream();
+ OutputStream out = openOutputStream(fileRelativePath)) {
+ IOUtils.copy(in, out);
+ if (log.isTraceEnabled())
+ log.trace("Workspace " + workspaceName + ": file content exported to "
+ + fileRelativePath);
+ }
+ } finally {
+ JcrUtils.closeQuietly(binary);
+ }
+ }
+ if (log.isDebugEnabled())
+ log.debug(workspaceName + ":" + contentPaths.size() + " files exported to " + workspacesFilesBasePath);
+ } catch (Exception e) {
+ markBackupFailed("Cannot backup files from " + workspaceName + ":", e);
+ } finally {
+ Jcr.logout(session);
+ }
+ }
+
+ protected OutputStream openOutputStream(String relativePath) throws IOException {
+ if (zout != null) {
+ ZipEntry entry = new ZipEntry(relativePath);
+ zout.putNextEntry(entry);
+ return zout;
+ } else if (basePath != null) {
+ Path targetPath = basePath.resolve(Paths.get(relativePath));
+ Files.createDirectories(targetPath.getParent());
+ return Files.newOutputStream(targetPath);
+ } else {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ protected void createLink(String source, String target) throws IOException {
+ if (zout != null) {
+ // TODO implement for zip
+ throw new UnsupportedOperationException();
+ } else if (basePath != null) {
+ Path sourcePath = basePath.resolve(Paths.get(source));
+ Path targetPath = basePath.resolve(Paths.get(target));
+ Path relativeSource = targetPath.getParent().relativize(sourcePath);
+ Files.createDirectories(targetPath.getParent());
+ Files.createSymbolicLink(targetPath, relativeSource);
+ } else {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ protected void closeOutputStream(String relativePath, OutputStream out) throws IOException {
+ if (zout != null) {
+ zout.closeEntry();
+ } else if (basePath != null) {
+ out.close();
+ } else {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ protected Session login(String workspaceName) {
+ if (bundleContext != null) {// local
+ return NodeUtils.openDataAdminSession(repository, workspaceName);
+ } else {// remote
+ try {
+ return repository.login(workspaceName);
+ } catch (RepositoryException e) {
+ throw new JcrException(e);
+ }
+ }
+ }
+
+ public final static void main(String[] args) throws Exception {
+ if (args.length == 0) {
+ printUsage("No argument");
+ System.exit(1);
+ }
+ URI uri = new URI(args[0]);
+ Repository repository = createRemoteRepository(uri);
+ Path basePath = args.length > 1 ? Paths.get(args[1]) : Paths.get(System.getProperty("user.dir"));
+ if (!Files.exists(basePath))
+ Files.createDirectories(basePath);
+ LogicalBackup backup = new LogicalBackup(null, repository, basePath);
+ backup.run();
+ }
+
+ private static void printUsage(String errorMessage) {
+ if (errorMessage != null)
+ System.err.println(errorMessage);
+ System.out.println("Usage: LogicalBackup <remote URL> [<target directory>]");
+
+ }
+
+ protected static 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());
+ // TODO make it configurable
+ params.put(ClientDavexRepositoryFactory.JACKRABBIT_REMOTE_DEFAULT_WORKSPACE, NodeConstants.SYS_WORKSPACE);
+ return repositoryFactory.getRepository(params);
+ }
+
+ public void performSoftwareBackup(BundleContext bundleContext) {
+ String bootBasePath = OSGI_BASE + "boot";
+ Bundle[] bundles = bundleContext.getBundles();
+ for (Bundle bundle : bundles) {
+ String relativePath = bootBasePath + "/" + bundle.getSymbolicName() + ".jar";
+ Dictionary<String, String> headers = bundle.getHeaders();
+ Manifest manifest = new Manifest();
+ Enumeration<String> headerKeys = headers.keys();
+ while (headerKeys.hasMoreElements()) {
+ String headerKey = headerKeys.nextElement();
+ String headerValue = headers.get(headerKey);
+ manifest.getMainAttributes().putValue(headerKey, headerValue);
+ }
+ try (JarOutputStream jarOut = new JarOutputStream(openOutputStream(relativePath), manifest)) {
+ Enumeration<URL> resourcePaths = bundle.findEntries("/", "*", true);
+ resources: while (resourcePaths.hasMoreElements()) {
+ URL entryUrl = resourcePaths.nextElement();
+ String entryPath = entryUrl.getPath();
+ if (entryPath.equals(""))
+ continue resources;
+ if (entryPath.endsWith("/"))
+ continue resources;
+ String entryName = entryPath.substring(1);// remove first '/'
+ if (entryUrl.getPath().equals("/META-INF/"))
+ continue resources;
+ if (entryUrl.getPath().equals("/META-INF/MANIFEST.MF"))
+ continue resources;
+ // dev
+ if (entryUrl.getPath().startsWith("/target"))
+ continue resources;
+ if (entryUrl.getPath().startsWith("/src"))
+ continue resources;
+ if (entryUrl.getPath().startsWith("/ext"))
+ continue resources;
+
+ if (entryName.startsWith("bin/")) {// dev
+ entryName = entryName.substring("bin/".length());
+ }
+
+ ZipEntry entry = new ZipEntry(entryName);
+ try (InputStream in = entryUrl.openStream()) {
+ try {
+ jarOut.putNextEntry(entry);
+ } catch (ZipException e) {// duplicate
+ continue resources;
+ }
+ IOUtils.copy(in, jarOut);
+ jarOut.closeEntry();
+// log.info(entryUrl);
+ } catch (FileNotFoundException e) {
+ log.warn(entryUrl + ": " + e.getMessage());
+ }
+ }
+ } catch (IOException e1) {
+ throw new RuntimeException("Cannot export bundle " + bundle, e1);
+ }
+ }
+ if (log.isDebugEnabled())
+ log.debug(bundles.length + " OSGi bundles exported to " + bootBasePath);
+
+ }
+
+ protected synchronized void markBackupFailed(Object message, Exception e) {
+ log.error(message, e);
+ backupFailed = true;
+ notifyAll();
+ if (executorService != null)
+ executorService.shutdownNow();
+ }
+
+ protected boolean isBackupFailed() {
+ return backupFailed;
+ }
+}
--- /dev/null
+package org.argeo.maintenance.backup;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.DirectoryStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import javax.jcr.ImportUUIDBehavior;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.argeo.api.NodeConstants;
+import org.argeo.api.NodeUtils;
+import org.argeo.jcr.Jcr;
+import org.argeo.jcr.JcrException;
+import org.argeo.jcr.JcrUtils;
+import org.osgi.framework.BundleContext;
+
+/** Restores a backup in the format defined by {@link LogicalBackup}. */
+public class LogicalRestore implements Runnable {
+ private final static Log log = LogFactory.getLog(LogicalRestore.class);
+
+ private final Repository repository;
+ private final BundleContext bundleContext;
+ private final Path basePath;
+
+ public LogicalRestore(BundleContext bundleContext, Repository repository, Path basePath) {
+ this.repository = repository;
+ this.basePath = basePath;
+ this.bundleContext = bundleContext;
+ }
+
+ @Override
+ public void run() {
+ Path workspaces = basePath.resolve(LogicalBackup.WORKSPACES_BASE);
+ try {
+ // import jcr:system first
+// Session defaultSession = NodeUtils.openDataAdminSession(repository, null);
+// try (DirectoryStream<Path> xmls = Files.newDirectoryStream(
+// workspaces.resolve(NodeConstants.SYS_WORKSPACE + LogicalBackup.JCR_VERSION_STORAGE_PATH),
+// "*.xml")) {
+// for (Path xml : xmls) {
+// try (InputStream in = Files.newInputStream(xml)) {
+// defaultSession.getWorkspace().importXML(LogicalBackup.JCR_VERSION_STORAGE_PATH, in,
+// ImportUUIDBehavior.IMPORT_UUID_COLLISION_REPLACE_EXISTING);
+// if (log.isDebugEnabled())
+// log.debug("Restored " + xml + " to " + defaultSession.getWorkspace().getName() + ":");
+// }
+// }
+// } finally {
+// Jcr.logout(defaultSession);
+// }
+
+ // non-system content
+ try (DirectoryStream<Path> workspaceDirs = Files.newDirectoryStream(workspaces)) {
+ for (Path workspacePath : workspaceDirs) {
+ String workspaceName = workspacePath.getFileName().toString();
+ Session session = JcrUtils.loginOrCreateWorkspace(repository, workspaceName);
+ try (DirectoryStream<Path> xmls = Files.newDirectoryStream(workspacePath, "*.xml")) {
+ xmls: for (Path xml : xmls) {
+ if (xml.getFileName().toString().startsWith("rep:"))
+ continue xmls;
+ try (InputStream in = Files.newInputStream(xml)) {
+ session.getWorkspace().importXML("/", in,
+ ImportUUIDBehavior.IMPORT_UUID_COLLISION_REPLACE_EXISTING);
+ if (log.isDebugEnabled())
+ log.debug("Restored " + xml + " to workspace " + workspaceName);
+ }
+ }
+ } finally {
+ Jcr.logout(session);
+ }
+ }
+ }
+ } catch (IOException e) {
+ throw new RuntimeException("Cannot restore backup from " + basePath, e);
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot restore backup from " + basePath, e);
+ }
+ }
+
+}
--- /dev/null
+/** Argeo Node backup utilities. */
+package org.argeo.maintenance.backup;
\ No newline at end of file
--- /dev/null
+package org.argeo.maintenance.internal;
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import javax.jcr.Repository;
+
+import org.argeo.maintenance.backup.LogicalBackup;
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+
+public class Activator implements BundleActivator {
+
+ @Override
+ public void start(BundleContext context) throws Exception {
+ // Start backup
+ Repository repository = context.getService(context.getServiceReference(Repository.class));
+ Path basePath = Paths.get(System.getProperty("user.dir"), "backup");
+ LogicalBackup backup = new LogicalBackup(context, repository, basePath);
+ backup.run();
+ }
+
+ @Override
+ public void stop(BundleContext context) throws Exception {
+ }
+
+}
--- /dev/null
+/** Utilities for the maintenance of an Argeo Node. */
+package org.argeo.maintenance;
\ No newline at end of file
</dependency>
<dependency>
<groupId>org.argeo.commons</groupId>
- <artifactId>org.argeo.jcr</artifactId>
+ <artifactId>org.argeo.cms.jcr</artifactId>
<version>2.3-SNAPSHOT</version>
</dependency>
<dependency>
<artifactId>org.argeo.core</artifactId>
<version>2.3-SNAPSHOT</version>
</dependency>
- <dependency>
- <groupId>org.argeo.commons</groupId>
- <artifactId>org.argeo.maintenance</artifactId>
- <version>2.3-SNAPSHOT</version>
- </dependency>
+<!-- <dependency> -->
+<!-- <groupId>org.argeo.commons</groupId> -->
+<!-- <artifactId>org.argeo.maintenance</artifactId> -->
+<!-- <version>2.3-SNAPSHOT</version> -->
+<!-- </dependency> -->
</dependencies>
</project>
\ No newline at end of file
+++ /dev/null
-eclipse.preferences.version=1
-org.eclipse.jdt.core.compiler.annotation.inheritNullAnnotations=disabled
-org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation=ignore
-org.eclipse.jdt.core.compiler.annotation.nonnull=org.eclipse.jdt.annotation.NonNull
-org.eclipse.jdt.core.compiler.annotation.nonnull.secondary=
-org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jdt.annotation.NonNullByDefault
-org.eclipse.jdt.core.compiler.annotation.nonnullbydefault.secondary=
-org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nullable
-org.eclipse.jdt.core.compiler.annotation.nullable.secondary=
-org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled
-org.eclipse.jdt.core.compiler.problem.APILeak=warning
-org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning
-org.eclipse.jdt.core.compiler.problem.autoboxing=ignore
-org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning
-org.eclipse.jdt.core.compiler.problem.deadCode=warning
-org.eclipse.jdt.core.compiler.problem.deprecation=warning
-org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled
-org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled
-org.eclipse.jdt.core.compiler.problem.discouragedReference=warning
-org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore
-org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=ignore
-org.eclipse.jdt.core.compiler.problem.fallthroughCase=ignore
-org.eclipse.jdt.core.compiler.problem.fatalOptionalError=disabled
-org.eclipse.jdt.core.compiler.problem.fieldHiding=ignore
-org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning
-org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning
-org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
-org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning
-org.eclipse.jdt.core.compiler.problem.includeNullInfoFromAsserts=disabled
-org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning
-org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=warning
-org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore
-org.eclipse.jdt.core.compiler.problem.localVariableHiding=ignore
-org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning
-org.eclipse.jdt.core.compiler.problem.missingDefaultCase=ignore
-org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=ignore
-org.eclipse.jdt.core.compiler.problem.missingEnumCaseDespiteDefault=disabled
-org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=ignore
-org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=ignore
-org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled
-org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning
-org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore
-org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning
-org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning
-org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore
-org.eclipse.jdt.core.compiler.problem.nonnullParameterAnnotationDropped=warning
-org.eclipse.jdt.core.compiler.problem.nonnullTypeVariableFromLegacyInvocation=warning
-org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=error
-org.eclipse.jdt.core.compiler.problem.nullReference=warning
-org.eclipse.jdt.core.compiler.problem.nullSpecViolation=error
-org.eclipse.jdt.core.compiler.problem.nullUncheckedConversion=warning
-org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning
-org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore
-org.eclipse.jdt.core.compiler.problem.pessimisticNullAnalysisForFreeTypeVariables=warning
-org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=ignore
-org.eclipse.jdt.core.compiler.problem.potentialNullReference=ignore
-org.eclipse.jdt.core.compiler.problem.potentiallyUnclosedCloseable=ignore
-org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning
-org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning
-org.eclipse.jdt.core.compiler.problem.redundantNullCheck=ignore
-org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=ignore
-org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=ignore
-org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore
-org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore
-org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled
-org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning
-org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled
-org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled
-org.eclipse.jdt.core.compiler.problem.syntacticNullAnalysisForFields=disabled
-org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore
-org.eclipse.jdt.core.compiler.problem.terminalDeprecation=warning
-org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning
-org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=enabled
-org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning
-org.eclipse.jdt.core.compiler.problem.unclosedCloseable=warning
-org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore
-org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning
-org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentType=warning
-org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentTypeStrict=disabled
-org.eclipse.jdt.core.compiler.problem.unlikelyEqualsArgumentType=info
-org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore
-org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=ignore
-org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore
-org.eclipse.jdt.core.compiler.problem.unstableAutoModuleName=warning
-org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=ignore
-org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled
-org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled
-org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled
-org.eclipse.jdt.core.compiler.problem.unusedExceptionParameter=ignore
-org.eclipse.jdt.core.compiler.problem.unusedImport=warning
-org.eclipse.jdt.core.compiler.problem.unusedLabel=warning
-org.eclipse.jdt.core.compiler.problem.unusedLocal=warning
-org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=ignore
-org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore
-org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled
-org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled
-org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled
-org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning
-org.eclipse.jdt.core.compiler.problem.unusedTypeParameter=ignore
-org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning
-org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning
-Import-Package:\
-org.apache.jackrabbit.api,\
-org.apache.jackrabbit.commons,\
-org.apache.jackrabbit.spi,\
-org.apache.jackrabbit.spi2dav,\
-org.apache.jackrabbit.spi2davex,\
-org.apache.jackrabbit.webdav,\
-junit.*;resolution:=optional,\
-*
\ No newline at end of file
-source.. = src/
+source.. = src/,\
+ ext/test/
output.. = bin/
bin.includes = META-INF/,\
.
-additional.bundles = org.junit,\
- org.hamcrest,\
- org.apache.jackrabbit.core,\
- javax.jcr,\
- org.apache.jackrabbit.api,\
- org.apache.jackrabbit.data,\
- org.apache.jackrabbit.jcr.commons,\
- org.apache.jackrabbit.spi,\
- org.apache.jackrabbit.spi.commons,\
- org.slf4j.api,\
- org.slf4j.log4j12,\
- org.apache.log4j,\
- org.apache.commons.collections,\
- EDU.oswego.cs.dl.util.concurrent,\
- org.apache.lucene,\
- org.apache.tika.core,\
- org.apache.commons.dbcp,\
- org.apache.commons.pool,\
- com.google.guava,\
- org.apache.jackrabbit.jcr2spi,\
- org.apache.jackrabbit.spi2dav,\
- org.apache.httpcomponents.httpclient,\
- org.apache.httpcomponents.httpcore,\
- org.apache.tika.parsers
\ No newline at end of file
+++ /dev/null
-package org.argeo.jcr.fs;
-
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.URI;
-import java.nio.file.DirectoryStream;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.attribute.BasicFileAttributes;
-import java.nio.file.attribute.FileTime;
-import java.nio.file.spi.FileSystemProvider;
-import java.util.Arrays;
-import java.util.Map;
-
-import javax.jcr.Property;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.nodetype.NodeType;
-
-import org.apache.commons.io.IOUtils;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.apache.jackrabbit.core.RepositoryImpl;
-import org.argeo.jackrabbit.fs.JackrabbitMemoryFsProvider;
-
-import junit.framework.TestCase;
-
-public class JcrFileSystemTest extends TestCase {
- private final static Log log = LogFactory.getLog(JcrFileSystemTest.class);
-
- public void testMounts() throws Exception {
- JackrabbitMemoryFsProvider fsProvider = new JackrabbitMemoryFsProvider() {
-
- @Override
- protected void postRepositoryCreation(RepositoryImpl repositoryImpl) throws RepositoryException {
- // create workspace
- Session session = login();
- session.getWorkspace().createWorkspace("test");
- }
-
- };
-
- Path rootPath = fsProvider.getPath(new URI("jcr+memory:/"));
- log.debug("Got root " + rootPath);
- Path testDir = rootPath.resolve("testDir");
- Files.createDirectory(testDir);
-
- Path testMount = fsProvider.getPath(new URI("jcr+memory:/test"));
- log.debug("Test path");
- assertEquals(rootPath, testMount.getParent());
- assertEquals(testMount.getFileName(), rootPath.relativize(testMount));
-
- Path testPath = testMount.resolve("test.txt");
- log.debug("Create file " + testPath);
- Files.createFile(testPath);
- BasicFileAttributes bfa = Files.readAttributes(testPath, BasicFileAttributes.class);
- FileTime ft = bfa.creationTime();
- assertNotNull(ft);
- assertTrue(bfa.isRegularFile());
- log.debug("Created " + testPath + " (" + ft + ")");
- Files.delete(testPath);
- log.debug("Deleted " + testPath);
-
- // Browse directories from root
- DirectoryStream<Path> files = Files.newDirectoryStream(rootPath);
- int directoryCount = 0;
- for (Path file : files) {
- if (Files.isDirectory(file)) {
- directoryCount++;
- }
- }
- assertEquals(2, directoryCount);
-
- // Browse directories from mount
- Path mountSubDir = testMount.resolve("mountSubDir");
- Files.createDirectory(mountSubDir);
- Path otherSubDir = testMount.resolve("otherSubDir");
- Files.createDirectory(otherSubDir);
- testPath = testMount.resolve("test.txt");
- Files.createFile(testPath);
- files = Files.newDirectoryStream(testMount);
- int fileCount = 0;
- for (Path file : files) {
- fileCount++;
- }
- assertEquals(3, fileCount);
-
- }
-
- public void testSimple() throws Exception {
- FileSystemProvider fsProvider = new JackrabbitMemoryFsProvider();
-
- // Simple file
- Path rootPath = fsProvider.getPath(new URI("jcr+memory:/"));
- log.debug("Got root " + rootPath);
- Path testPath = fsProvider.getPath(new URI("jcr+memory:/test.txt"));
- log.debug("Test path");
- assertEquals("test.txt", testPath.getFileName().toString());
- assertEquals(rootPath, testPath.getParent());
- assertEquals(testPath.getFileName(), rootPath.relativize(testPath));
- // relativize self should be empty path
- Path selfRelative = testPath.relativize(testPath);
- assertEquals("", selfRelative.toString());
-
- log.debug("Create file " + testPath);
- Files.createFile(testPath);
- BasicFileAttributes bfa = Files.readAttributes(testPath, BasicFileAttributes.class);
- FileTime ft = bfa.creationTime();
- assertNotNull(ft);
- assertTrue(bfa.isRegularFile());
- log.debug("Created " + testPath + " (" + ft + ")");
- Files.delete(testPath);
- log.debug("Deleted " + testPath);
- String txt = "TEST\nTEST2\n";
- byte[] arr = txt.getBytes();
- Files.write(testPath, arr);
- log.debug("Wrote " + testPath);
- byte[] read = Files.readAllBytes(testPath);
- assertTrue(Arrays.equals(arr, read));
- assertEquals(txt, new String(read));
- log.debug("Read " + testPath);
- Path testDir = rootPath.resolve("testDir");
- log.debug("Resolved " + testDir);
- // Copy
- Files.createDirectory(testDir);
- log.debug("Created directory " + testDir);
- Path subsubdir = Files.createDirectories(testDir.resolve("subdir/subsubdir"));
- log.debug("Created sub directories " + subsubdir);
- Path copiedFile = testDir.resolve("copiedFile.txt");
- log.debug("Resolved " + copiedFile);
- Path relativeCopiedFile = testDir.relativize(copiedFile);
- assertEquals(copiedFile.getFileName().toString(), relativeCopiedFile.toString());
- log.debug("Relative copied file " + relativeCopiedFile);
- try (OutputStream out = Files.newOutputStream(copiedFile); InputStream in = Files.newInputStream(testPath)) {
- IOUtils.copy(in, out);
- }
- log.debug("Copied " + testPath + " to " + copiedFile);
- Files.delete(testPath);
- log.debug("Deleted " + testPath);
- byte[] copiedRead = Files.readAllBytes(copiedFile);
- assertTrue(Arrays.equals(copiedRead, read));
- log.debug("Read " + copiedFile);
- // Browse directories
- DirectoryStream<Path> files = Files.newDirectoryStream(testDir);
- int fileCount = 0;
- Path listedFile = null;
- for (Path file : files) {
- fileCount++;
- if (!Files.isDirectory(file))
- listedFile = file;
- }
- assertEquals(2, fileCount);
- assertEquals(copiedFile, listedFile);
- assertEquals(copiedFile.toString(), listedFile.toString());
- log.debug("Listed " + testDir);
- // Generic attributes
- Map<String, Object> attrs = Files.readAttributes(copiedFile, "*");
- assertEquals(3, attrs.size());
- log.debug("Read attributes of " + copiedFile + ": " + attrs.keySet());
- // Direct node access
- NodeFileAttributes nfa = Files.readAttributes(copiedFile, NodeFileAttributes.class);
- nfa.getNode().addMixin(NodeType.MIX_LANGUAGE);
- nfa.getNode().getSession().save();
- log.debug("Add mix:language");
- Files.setAttribute(copiedFile, Property.JCR_LANGUAGE, "fr");
- log.debug("Set language");
- attrs = Files.readAttributes(copiedFile, "*");
- assertEquals(4, attrs.size());
- log.debug("Read attributes of " + copiedFile + ": " + attrs.keySet());
- }
-
- public void testIllegalCharacters() throws Exception {
- FileSystemProvider fsProvider = new JackrabbitMemoryFsProvider();
- String fileName = "tüßçt[1].txt";
- String pathStr = "/testDir/" + fileName;
- Path testDir = fsProvider.getPath(new URI("jcr+memory:/testDir"));
- Files.createDirectory(testDir);
- Path testPath = testDir.resolve(fileName);
- assertEquals(pathStr, testPath.toString());
- Files.createFile(testPath);
- DirectoryStream<Path> files = Files.newDirectoryStream(testDir);
- Path listedPath = files.iterator().next();
- assertEquals(pathStr, listedPath.toString());
-
- String dirName = "*[~WeirdDir~]*";
- Path subDir = testDir.resolve(dirName);
- Files.createDirectory(subDir);
- subDir = testDir.resolve(dirName);
- assertEquals(dirName, subDir.getFileName().toString());
- }
-}
<artifactId>org.argeo.enterprise</artifactId>
<version>2.3-SNAPSHOT</version>
</dependency>
- <dependency>
- <groupId>org.argeo.commons</groupId>
- <artifactId>org.argeo.jcr</artifactId>
- <version>2.3-SNAPSHOT</version>
- </dependency>
+<!-- <dependency> -->
+<!-- <groupId>org.argeo.commons</groupId> -->
+<!-- <artifactId>org.argeo.jcr</artifactId> -->
+<!-- <version>2.3-SNAPSHOT</version> -->
+<!-- </dependency> -->
</dependencies>
</project>
\ No newline at end of file
import java.nio.file.spi.FileSystemProvider;
import java.util.concurrent.Callable;
-import org.argeo.jackrabbit.fs.DavexFsProvider;
import org.argeo.sync.SyncResult;
/** Synchronises two paths. */
FileSystemProvider fsProvider = FileSystems.getDefault().provider();
path = fsProvider.getPath(uri);
} else if (uri.getScheme().equals("davex")) {
- FileSystemProvider fsProvider = new DavexFsProvider();
- path = fsProvider.getPath(uri);
+ throw new UnsupportedOperationException();
+// FileSystemProvider fsProvider = new DavexFsProvider();
+// path = fsProvider.getPath(uri);
// } else if (uri.getScheme().equals("sftp")) {
// Sftp sftp = new Sftp(uri);
// path = sftp.getBasePath();
+++ /dev/null
-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";
- }
-
-}
+++ /dev/null
-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());
- // FIXME make it configurable
- params.put(ClientDavexRepositoryFactory.JACKRABBIT_REMOTE_DEFAULT_WORKSPACE, "sys");
- 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";
- }
-
-}
+++ /dev/null
-/** JCR CLI commands. */
-package org.argeo.cli.jcr;
\ No newline at end of file
+++ /dev/null
-<?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
+++ /dev/null
-package org.argeo.jackrabbit;
-
-import java.util.Map;
-
-import javax.security.auth.Subject;
-import javax.security.auth.callback.CallbackHandler;
-import javax.security.auth.login.LoginException;
-import javax.security.auth.spi.LoginModule;
-
-import org.apache.jackrabbit.core.security.SecurityConstants;
-import org.apache.jackrabbit.core.security.principal.AdminPrincipal;
-
-@Deprecated
-public class JackrabbitAdminLoginModule implements LoginModule {
- private Subject subject;
-
- @Override
- public void initialize(Subject subject, CallbackHandler callbackHandler,
- Map<String, ?> sharedState, Map<String, ?> options) {
- this.subject = subject;
- }
-
- @Override
- public boolean login() throws LoginException {
- // TODO check permission?
- return true;
- }
-
- @Override
- public boolean commit() throws LoginException {
- subject.getPrincipals().add(
- new AdminPrincipal(SecurityConstants.ADMIN_ID));
- return true;
- }
-
- @Override
- public boolean abort() throws LoginException {
- return true;
- }
-
- @Override
- public boolean logout() throws LoginException {
- subject.getPrincipals().removeAll(
- subject.getPrincipals(AdminPrincipal.class));
- return true;
- }
-
-}
+++ /dev/null
-package org.argeo.jackrabbit;
-
-import java.awt.geom.CubicCurve2D;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.Reader;
-import java.net.URL;
-
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-
-import org.apache.commons.io.IOUtils;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.apache.jackrabbit.commons.cnd.CndImporter;
-import org.apache.jackrabbit.commons.cnd.ParseException;
-import org.apache.jackrabbit.core.config.RepositoryConfig;
-import org.apache.jackrabbit.core.fs.FileSystemException;
-import org.argeo.jcr.JcrCallback;
-import org.argeo.jcr.JcrException;
-import org.argeo.jcr.JcrUtils;
-
-/** Migrate the data in a Jackrabbit repository. */
-@Deprecated
-public class JackrabbitDataModelMigration implements Comparable<JackrabbitDataModelMigration> {
- private final static Log log = LogFactory.getLog(JackrabbitDataModelMigration.class);
-
- private String dataModelNodePath;
- private String targetVersion;
- private URL migrationCnd;
- private JcrCallback dataModification;
-
- /**
- * Expects an already started repository with the old data model to migrate.
- * Expects to be run with admin rights (Repository.login() will be used).
- *
- * @return true if a migration was performed and the repository needs to be
- * restarted and its caches cleared.
- */
- public Boolean migrate(Session session) {
- long begin = System.currentTimeMillis();
- Reader reader = null;
- try {
- // check if already migrated
- if (!session.itemExists(dataModelNodePath)) {
-// log.warn("Node " + dataModelNodePath + " does not exist: nothing to migrate.");
- return false;
- }
-// Node dataModelNode = session.getNode(dataModelNodePath);
-// if (dataModelNode.hasProperty(ArgeoNames.ARGEO_DATA_MODEL_VERSION)) {
-// String currentVersion = dataModelNode.getProperty(
-// ArgeoNames.ARGEO_DATA_MODEL_VERSION).getString();
-// if (compareVersions(currentVersion, targetVersion) >= 0) {
-// log.info("Data model at version " + currentVersion
-// + ", no need to migrate.");
-// return false;
-// }
-// }
-
- // apply transitional CND
- if (migrationCnd != null) {
- reader = new InputStreamReader(migrationCnd.openStream());
- CndImporter.registerNodeTypes(reader, session, true);
- session.save();
-// log.info("Registered migration node types from " + migrationCnd);
- }
-
- // modify data
- dataModification.execute(session);
-
- // apply changes
- session.save();
-
- long duration = System.currentTimeMillis() - begin;
-// log.info("Migration of data model " + dataModelNodePath + " to " + targetVersion + " performed in "
-// + duration + "ms");
- return true;
- } catch (RepositoryException e) {
- JcrUtils.discardQuietly(session);
- throw new JcrException("Migration of data model " + dataModelNodePath + " to " + targetVersion + " failed.",
- e);
- } catch (ParseException | IOException e) {
- JcrUtils.discardQuietly(session);
- throw new RuntimeException(
- "Migration of data model " + dataModelNodePath + " to " + targetVersion + " failed.", e);
- } finally {
- JcrUtils.logoutQuietly(session);
- IOUtils.closeQuietly(reader);
- }
- }
-
- protected static int compareVersions(String version1, String version2) {
- // TODO do a proper version analysis and comparison
- return version1.compareTo(version2);
- }
-
- /** To be called on a stopped repository. */
- public static void clearRepositoryCaches(RepositoryConfig repositoryConfig) {
- try {
- String customeNodeTypesPath = "/nodetypes/custom_nodetypes.xml";
- // FIXME causes weird error in Eclipse
- repositoryConfig.getFileSystem().deleteFile(customeNodeTypesPath);
- if (log.isDebugEnabled())
- log.debug("Cleared " + customeNodeTypesPath);
- } catch (RuntimeException e) {
- throw e;
- } catch (RepositoryException e) {
- throw new JcrException(e);
- } catch (FileSystemException e) {
- throw new RuntimeException("Cannot clear node types cache.",e);
- }
-
- // File customNodeTypes = new File(home.getPath()
- // + "/repository/nodetypes/custom_nodetypes.xml");
- // if (customNodeTypes.exists()) {
- // customNodeTypes.delete();
- // if (log.isDebugEnabled())
- // log.debug("Cleared " + customNodeTypes);
- // } else {
- // log.warn("File " + customNodeTypes + " not found.");
- // }
- }
-
- /*
- * FOR USE IN (SORTED) SETS
- */
-
- public int compareTo(JackrabbitDataModelMigration dataModelMigration) {
- // TODO make ordering smarter
- if (dataModelNodePath.equals(dataModelMigration.dataModelNodePath))
- return compareVersions(targetVersion, dataModelMigration.targetVersion);
- else
- return dataModelNodePath.compareTo(dataModelMigration.dataModelNodePath);
- }
-
- @Override
- public boolean equals(Object obj) {
- if (!(obj instanceof JackrabbitDataModelMigration))
- return false;
- JackrabbitDataModelMigration dataModelMigration = (JackrabbitDataModelMigration) obj;
- return dataModelNodePath.equals(dataModelMigration.dataModelNodePath)
- && targetVersion.equals(dataModelMigration.targetVersion);
- }
-
- @Override
- public int hashCode() {
- return targetVersion.hashCode();
- }
-
- public void setDataModelNodePath(String dataModelNodePath) {
- this.dataModelNodePath = dataModelNodePath;
- }
-
- public void setTargetVersion(String targetVersion) {
- this.targetVersion = targetVersion;
- }
-
- public void setMigrationCnd(URL migrationCnd) {
- this.migrationCnd = migrationCnd;
- }
-
- public void setDataModification(JcrCallback dataModification) {
- this.dataModification = dataModification;
- }
-
- public String getDataModelNodePath() {
- return dataModelNodePath;
- }
-
- public String getTargetVersion() {
- return targetVersion;
- }
-
-}
+++ /dev/null
-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));
- }
-
-}
+++ /dev/null
-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;
- }
-
-}
+++ /dev/null
-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);
- }
- }
-
-}
+++ /dev/null
-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());
- // FIXME make it configurable
- params.put(JACKRABBIT_REMOTE_DEFAULT_WORKSPACE, "sys");
-
- 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();
- }
- }
-}
+++ /dev/null
-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();
- }
-
-}
+++ /dev/null
-package org.argeo.jackrabbit.fs;
-
-import org.argeo.jcr.fs.JcrFileSystemProvider;
-
-public abstract class AbstractJackrabbitFsProvider extends JcrFileSystemProvider {
-
-}
+++ /dev/null
-package org.argeo.jackrabbit.fs;
-
-import java.io.IOException;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.nio.file.DirectoryStream;
-import java.nio.file.FileSystem;
-import java.nio.file.FileSystemAlreadyExistsException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.HashMap;
-import java.util.Map;
-
-import javax.jcr.Repository;
-import javax.jcr.RepositoryFactory;
-import javax.jcr.Session;
-
-import org.argeo.jackrabbit.client.ClientDavexRepositoryFactory;
-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 DEFAULT_JACKRABBIT_REMOTE_DEFAULT_WORKSPACE = "sys";
-
- private Map<String, JcrFileSystem> fileSystems = new HashMap<>();
-
- @Override
- public String getScheme() {
- return "davex";
- }
-
- @Override
- public FileSystem newFileSystem(URI uri, Map<String, ?> env) throws IOException {
- if (uri.getHost() == null)
- throw new IllegalArgumentException("An host should be provided");
- try {
- URI repoUri = new URI("http", uri.getUserInfo(), uri.getHost(), uri.getPort(), uri.getPath(), null, null);
- String repoKey = repoUri.toString();
- if (fileSystems.containsKey(repoKey))
- throw new FileSystemAlreadyExistsException("CMS file system already exists for " + repoKey);
- RepositoryFactory repositoryFactory = new ClientDavexRepositoryFactory();
- return tryGetRepo(repositoryFactory, repoUri, "home");
- } catch (URISyntaxException e) {
- throw new IllegalArgumentException("Cannot open file system " + uri, e);
- }
- }
-
- private JcrFileSystem tryGetRepo(RepositoryFactory repositoryFactory, URI repoUri, String workspace)
- throws IOException {
- Map<String, String> params = new HashMap<String, String>();
- params.put(ClientDavexRepositoryFactory.JACKRABBIT_DAVEX_URI, repoUri.toString());
- // TODO better integrate with OSGi or other configuration than system
- // properties.
- String remoteDefaultWorkspace = System.getProperty(
- ClientDavexRepositoryFactory.JACKRABBIT_REMOTE_DEFAULT_WORKSPACE,
- DEFAULT_JACKRABBIT_REMOTE_DEFAULT_WORKSPACE);
- params.put(ClientDavexRepositoryFactory.JACKRABBIT_REMOTE_DEFAULT_WORKSPACE, remoteDefaultWorkspace);
- Repository repository = null;
- Session session = null;
- try {
- repository = repositoryFactory.getRepository(params);
- if (repository != null)
- session = repository.login(workspace);
- } catch (Exception e) {
- // silent
- }
-
- if (session == null) {
- if (repoUri.getPath() == null || repoUri.getPath().equals("/"))
- return null;
- String repoUriStr = repoUri.toString();
- if (repoUriStr.endsWith("/"))
- repoUriStr = repoUriStr.substring(0, repoUriStr.length() - 1);
- String nextRepoUriStr = repoUriStr.substring(0, repoUriStr.lastIndexOf('/'));
- String nextWorkspace = repoUriStr.substring(repoUriStr.lastIndexOf('/') + 1);
- URI nextUri;
- try {
- nextUri = new URI(nextRepoUriStr);
- } catch (URISyntaxException e) {
- throw new IllegalArgumentException("Badly formatted URI", e);
- }
- return tryGetRepo(repositoryFactory, nextUri, nextWorkspace);
- } else {
- JcrFileSystem fileSystem = new JcrFileSystem(this, repository);
- fileSystems.put(repoUri.toString() + "/" + workspace, fileSystem);
- return fileSystem;
- }
- }
-
- @Override
- public FileSystem getFileSystem(URI uri) {
- return currentUserFileSystem(uri);
- }
-
- @Override
- public Path getPath(URI uri) {
- JcrFileSystem fileSystem = currentUserFileSystem(uri);
- if (fileSystem == null)
- try {
- fileSystem = (JcrFileSystem) newFileSystem(uri, new HashMap<String, Object>());
- if (fileSystem == null)
- throw new IllegalArgumentException("No file system found for " + uri);
- } catch (IOException e) {
- throw new JcrFsException("Could not autocreate file system", e);
- }
- URI repoUri = null;
- try {
- repoUri = new URI("http", uri.getUserInfo(), uri.getHost(), uri.getPort(), uri.getPath(), null, null);
- } catch (URISyntaxException e) {
- throw new IllegalArgumentException(e);
- }
- String uriStr = repoUri.toString();
- String localPath = null;
- for (String key : fileSystems.keySet()) {
- if (uriStr.startsWith(key)) {
- localPath = uriStr.toString().substring(key.length());
- }
- }
- if ("".equals(localPath))
- localPath = "/";
- return fileSystem.getPath(localPath);
- }
-
- private JcrFileSystem currentUserFileSystem(URI uri) {
- for (String key : fileSystems.keySet()) {
- if (uri.toString().startsWith(key))
- return fileSystems.get(key);
- }
- return null;
- }
-
- public static void main(String args[]) {
- try {
- DavexFsProvider fsProvider = new DavexFsProvider();
- 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) {
- System.out.println("- " + p);
- }
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
-}
+++ /dev/null
-package org.argeo.jackrabbit.fs;
-
-import java.io.IOException;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.net.URL;
-import java.nio.file.FileSystem;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.HashMap;
-import java.util.Map;
-
-import javax.jcr.Credentials;
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.SimpleCredentials;
-
-import org.apache.jackrabbit.core.RepositoryImpl;
-import org.apache.jackrabbit.core.config.RepositoryConfig;
-import org.argeo.jcr.fs.JcrFileSystem;
-import org.argeo.jcr.fs.JcrFsException;
-
-public class JackrabbitMemoryFsProvider extends AbstractJackrabbitFsProvider {
- private RepositoryImpl repository;
- private JcrFileSystem fileSystem;
-
- private Credentials credentials;
-
- public JackrabbitMemoryFsProvider() {
- String username = System.getProperty("user.name");
- credentials = new SimpleCredentials(username, username.toCharArray());
- }
-
- @Override
- public String getScheme() {
- return "jcr+memory";
- }
-
- @Override
- public FileSystem newFileSystem(URI uri, Map<String, ?> env) throws IOException {
- try {
- Path tempDir = Files.createTempDirectory("fs-memory");
- URL confUrl = JackrabbitMemoryFsProvider.class.getResource("fs-memory.xml");
- RepositoryConfig repositoryConfig = RepositoryConfig.create(confUrl.toURI(), tempDir.toString());
- repository = RepositoryImpl.create(repositoryConfig);
- postRepositoryCreation(repository);
- fileSystem = new JcrFileSystem(this, repository, credentials);
- return fileSystem;
- } catch (RepositoryException | URISyntaxException e) {
- throw new IOException("Cannot login to repository", e);
- }
- }
-
- @Override
- public FileSystem getFileSystem(URI uri) {
- return fileSystem;
- }
-
- @Override
- public Path getPath(URI uri) {
- String path = uri.getPath();
- if (fileSystem == null)
- try {
- newFileSystem(uri, new HashMap<String, Object>());
- } catch (IOException e) {
- throw new JcrFsException("Could not autocreate file system", e);
- }
- return fileSystem.getPath(path);
- }
-
- public Repository getRepository() {
- return repository;
- }
-
- public Session login() throws RepositoryException {
- return getRepository().login(credentials);
- }
-
- /**
- * Called after the repository has been created and before the file system is
- * created.
- */
- protected void postRepositoryCreation(RepositoryImpl repositoryImpl) throws RepositoryException {
-
- }
-}
+++ /dev/null
-<?xml version="1.0"?>
-<!DOCTYPE Repository PUBLIC "Jackrabbit 2.6" "http://jackrabbit.apache.org/dtd/repository-2.6.dtd">
-<Repository>
- <!-- File system and datastore -->
- <FileSystem
- class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
-
- <!-- Workspace templates -->
- <Workspaces rootPath="${rep.home}/workspaces"
- defaultWorkspace="main" configRootPath="/workspaces" />
- <Workspace name="${wsp.name}">
- <FileSystem
- class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
- <PersistenceManager
- class="org.apache.jackrabbit.core.persistence.bundle.BundleFsPersistenceManager">
- </PersistenceManager>
- <SearchIndex
- class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
- <param name="path" value="${wsp.home}/index" />
- <param name="directoryManagerClass"
- value="org.apache.jackrabbit.core.query.lucene.directory.RAMDirectoryManager" />
- <param name="extractorPoolSize" value="0" />
- <FileSystem
- class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
- </SearchIndex>
- </Workspace>
-
- <!-- Versioning -->
- <Versioning rootPath="${rep.home}/version">
- <FileSystem
- class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
- <PersistenceManager
- class="org.apache.jackrabbit.core.persistence.bundle.BundleFsPersistenceManager">
- </PersistenceManager>
- </Versioning>
-
- <!-- Indexing -->
- <SearchIndex
- class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
- <param name="path" value="${rep.home}/index" />
- <param name="directoryManagerClass"
- value="org.apache.jackrabbit.core.query.lucene.directory.RAMDirectoryManager" />
- <param name="extractorPoolSize" value="0" />
- <FileSystem
- class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
- </SearchIndex>
-
- <!-- Security -->
- <Security appName="Jackrabbit">
- <LoginModule
- class="org.apache.jackrabbit.core.security.SimpleLoginModule" />
- <!-- <SecurityManager class="org.argeo.security.jackrabbit.ArgeoSecurityManager" -->
- <!-- workspaceName="security" /> -->
- <!-- <AccessManager class="org.argeo.security.jackrabbit.ArgeoAccessManager"
- /> -->
- </Security>
-</Repository>
\ No newline at end of file
+++ /dev/null
-/** Java NIO file system implementation based on Jackrabbit. */
-package org.argeo.jackrabbit.fs;
\ No newline at end of file
+++ /dev/null
-/** Generic Jackrabbit utilities. */
-package org.argeo.jackrabbit;
\ No newline at end of file
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE Repository PUBLIC "Jackrabbit 2.6" "http://jackrabbit.apache.org/dtd/repository-2.6.dtd">
-<Repository>
- <!-- Shared datasource -->
- <DataSources>
- <DataSource name="dataSource">
- <param name="driver" value="org.h2.Driver" />
- <param name="url" value="${dburl}" />
- <param name="user" value="${dbuser}" />
- <param name="password" value="${dbpassword}" />
- <param name="databaseType" value="h2" />
- <param name="maxPoolSize" value="${maxPoolSize}" />
- </DataSource>
- </DataSources>
-
- <!-- File system and datastore -->
- <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
- <param name="dataSourceName" value="dataSource" />
- <param name="schema" value="default" />
- <param name="schemaObjectPrefix" value="fs_" />
- </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="${defaultWorkspace}" />
- <Workspace name="${wsp.name}">
- <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
- <param name="dataSourceName" value="dataSource" />
- <param name="schema" value="default" />
- <param name="schemaObjectPrefix" value="${wsp.name}_fs_" />
- </FileSystem>
- <PersistenceManager
- class="org.apache.jackrabbit.core.persistence.pool.H2PersistenceManager">
- <param name="dataSourceName" value="dataSource" />
- <param name="schemaObjectPrefix" value="${wsp.name}_pm_" />
- <param name="bundleCacheSize" value="${bundleCacheMB}" />
- </PersistenceManager>
- <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
- <param name="path" value="${wsp.home}/index" />
- <param name="extractorPoolSize" value="${extractorPoolSize}" />
- <param name="cacheSize" value="${searchCacheSize}" />
- <param name="maxVolatileIndexSize" value="${maxVolatileIndexSize}" />
- </SearchIndex>
- <WorkspaceSecurity>
- <AccessControlProvider
- class="org.argeo.security.jackrabbit.ArgeoAccessControlProvider" />
- </WorkspaceSecurity>
- </Workspace>
-
- <!-- Versioning -->
- <Versioning rootPath="${rep.home}/version">
- <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
- <param name="dataSourceName" value="dataSource" />
- <param name="schema" value="default" />
- <param name="schemaObjectPrefix" value="fs_ver_" />
- </FileSystem>
- <PersistenceManager
- class="org.apache.jackrabbit.core.persistence.pool.H2PersistenceManager">
- <param name="dataSourceName" value="dataSource" />
- <param name="schemaObjectPrefix" value="pm_ver_" />
- <param name="bundleCacheSize" value="${bundleCacheMB}" />
- </PersistenceManager>
- </Versioning>
-
- <!-- Indexing -->
- <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
- <param name="path" value="${rep.home}/index" />
- <param name="extractorPoolSize" value="${extractorPoolSize}" />
- <param name="cacheSize" value="${searchCacheSize}" />
- <param name="maxVolatileIndexSize" value="${maxVolatileIndexSize}" />
- </SearchIndex>
-
- <!-- Security -->
- <Security appName="Jackrabbit">
- <SecurityManager class="org.argeo.security.jackrabbit.ArgeoSecurityManager"
- workspaceName="security" />
- <AccessManager class="org.argeo.security.jackrabbit.ArgeoAccessManager" />
- </Security>
-</Repository>
\ No newline at end of file
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE Repository PUBLIC "Jackrabbit 2.6" "http://jackrabbit.apache.org/dtd/repository-2.6.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="${defaultWorkspace}" />
- <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="bundleCacheSize" value="${bundleCacheMB}" />
- </PersistenceManager>
- <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
- <param name="path" value="${wsp.home}/index" />
- <param name="extractorPoolSize" value="${extractorPoolSize}" />
- <param name="cacheSize" value="${searchCacheSize}" />
- <param name="maxVolatileIndexSize" value="${maxVolatileIndexSize}" />
- </SearchIndex>
- <WorkspaceSecurity>
- <AccessControlProvider
- class="org.argeo.security.jackrabbit.ArgeoAccessControlProvider" />
- </WorkspaceSecurity>
- </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="bundleCacheSize" value="${bundleCacheMB}" />
- </PersistenceManager>
- </Versioning>
-
- <!-- Indexing -->
- <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
- <param name="path" value="${rep.home}/index" />
- <param name="extractorPoolSize" value="${extractorPoolSize}" />
- <param name="cacheSize" value="${searchCacheSize}" />
- <param name="maxVolatileIndexSize" value="${maxVolatileIndexSize}" />
- </SearchIndex>
-
- <!-- Security -->
- <Security appName="Jackrabbit">
- <SecurityManager class="org.argeo.security.jackrabbit.ArgeoSecurityManager"
- workspaceName="security" />
- <AccessManager class="org.argeo.security.jackrabbit.ArgeoAccessManager" />
- </Security>
-</Repository>
\ No newline at end of file
+++ /dev/null
-<?xml version="1.0"?>
-<!DOCTYPE Repository PUBLIC "Jackrabbit 2.6" "http://jackrabbit.apache.org/dtd/repository-2.6.dtd">
-<Repository>
- <!-- File system and datastore -->
- <FileSystem class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
-
- <!-- Workspace templates -->
- <Workspaces rootPath="${rep.home}/workspaces"
- defaultWorkspace="${defaultWorkspace}" configRootPath="/workspaces" />
- <Workspace name="${wsp.name}">
- <FileSystem class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
- <PersistenceManager
- class="org.apache.jackrabbit.core.persistence.bundle.BundleFsPersistenceManager">
- <param name="blobFSBlockSize" value="1" />
- <param name="bundleCacheSize" value="${bundleCacheMB}" />
- </PersistenceManager>
- <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
- <param name="path" value="${wsp.home}/index" />
- <param name="directoryManagerClass"
- value="org.apache.jackrabbit.core.query.lucene.directory.RAMDirectoryManager" />
- <param name="extractorPoolSize" value="${extractorPoolSize}" />
- <param name="cacheSize" value="${searchCacheSize}" />
- <param name="maxVolatileIndexSize" value="${maxVolatileIndexSize}" />
- <FileSystem class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
- </SearchIndex>
- </Workspace>
-
- <!-- Versioning -->
- <Versioning rootPath="${rep.home}/version">
- <FileSystem class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
- <PersistenceManager
- class="org.apache.jackrabbit.core.persistence.bundle.BundleFsPersistenceManager">
- <param name="blobFSBlockSize" value="1" />
- <param name="bundleCacheSize" value="${bundleCacheMB}" />
- </PersistenceManager>
- </Versioning>
-
- <!-- Indexing -->
- <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
- <param name="path" value="${rep.home}/index" />
- <param name="directoryManagerClass"
- value="org.apache.jackrabbit.core.query.lucene.directory.RAMDirectoryManager" />
- <param name="extractorPoolSize" value="${extractorPoolSize}" />
- <param name="cacheSize" value="${searchCacheSize}" />
- <param name="maxVolatileIndexSize" value="${maxVolatileIndexSize}" />
- <FileSystem class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
- </SearchIndex>
-
- <!-- Security -->
- <Security appName="Jackrabbit">
- <SecurityManager class="org.argeo.security.jackrabbit.ArgeoSecurityManager"
- workspaceName="security" />
- <AccessManager class="org.argeo.security.jackrabbit.ArgeoAccessManager" />
- </Security>
-</Repository>
\ No newline at end of file
+++ /dev/null
-<?xml version="1.0"?>
-<!DOCTYPE Repository PUBLIC "Jackrabbit 2.6" "http://jackrabbit.apache.org/dtd/repository-2.6.dtd">
-<Repository>
- <!-- Shared datasource -->
- <DataSources>
- <DataSource name="dataSource">
- <param name="driver" value="org.postgresql.Driver" />
- <param name="url" value="${dburl}" />
- <param name="user" value="${dbuser}" />
- <param name="password" value="${dbpassword}" />
- <param name="databaseType" value="postgresql" />
- <param name="maxPoolSize" value="${maxPoolSize}" />
- </DataSource>
- </DataSources>
-
- <!-- File system and datastore -->
- <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
- <param name="dataSourceName" value="dataSource" />
- <param name="schema" value="postgresql" />
- <param name="schemaObjectPrefix" value="fs_" />
- </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="${defaultWorkspace}" />
- <Workspace name="${wsp.name}">
- <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
- <param name="dataSourceName" value="dataSource" />
- <param name="schema" value="postgresql" />
- <param name="schemaObjectPrefix" value="${wsp.name}_fs_" />
- </FileSystem>
- <PersistenceManager
- class="org.apache.jackrabbit.core.persistence.pool.PostgreSQLPersistenceManager">
- <param name="dataSourceName" value="dataSource" />
- <param name="schemaObjectPrefix" value="${wsp.name}_pm_" />
- <param name="bundleCacheSize" value="${bundleCacheMB}" />
- </PersistenceManager>
- <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
- <param name="path" value="${wsp.home}/index" />
- <param name="extractorPoolSize" value="${extractorPoolSize}" />
- <param name="cacheSize" value="${searchCacheSize}" />
- <param name="maxVolatileIndexSize" value="${maxVolatileIndexSize}" />
- </SearchIndex>
- <WorkspaceSecurity>
- <AccessControlProvider
- class="org.argeo.security.jackrabbit.ArgeoAccessControlProvider" />
- </WorkspaceSecurity>
- </Workspace>
-
- <!-- Versioning -->
- <Versioning rootPath="${rep.home}/version">
- <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
- <param name="dataSourceName" value="dataSource" />
- <param name="schema" value="postgresql" />
- <param name="schemaObjectPrefix" value="fs_ver_" />
- </FileSystem>
- <PersistenceManager
- class="org.apache.jackrabbit.core.persistence.pool.PostgreSQLPersistenceManager">
- <param name="dataSourceName" value="dataSource" />
- <param name="schemaObjectPrefix" value="pm_ver_" />
- <param name="bundleCacheSize" value="${bundleCacheMB}" />
- </PersistenceManager>
- </Versioning>
-
- <!-- Indexing -->
- <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
- <param name="path" value="${rep.home}/index" />
- <param name="extractorPoolSize" value="${extractorPoolSize}" />
- <param name="cacheSize" value="${searchCacheSize}" />
- <param name="maxVolatileIndexSize" value="${maxVolatileIndexSize}" />
- </SearchIndex>
-
- <!-- Security -->
- <Security appName="Jackrabbit">
- <SecurityManager class="org.argeo.security.jackrabbit.ArgeoSecurityManager"
- workspaceName="security" />
- <AccessManager class="org.argeo.security.jackrabbit.ArgeoAccessManager" />
- </Security>
-</Repository>
\ No newline at end of file
+++ /dev/null
-<?xml version="1.0"?>
-<!DOCTYPE Repository PUBLIC "Jackrabbit 2.6" "http://jackrabbit.apache.org/dtd/repository-2.6.dtd">
-<Repository>
- <!-- Shared datasource -->
- <DataSources>
- <DataSource name="dataSource">
- <param name="driver" value="org.postgresql.Driver" />
- <param name="url" value="${dburl}" />
- <param name="user" value="${dbuser}" />
- <param name="password" value="${dbpassword}" />
- <param name="databaseType" value="postgresql" />
- <param name="maxPoolSize" value="${maxPoolSize}" />
- </DataSource>
- </DataSources>
-
- <!-- File system and datastore -->
- <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
- <param name="dataSourceName" value="dataSource" />
- <param name="schema" value="postgresql" />
- <param name="schemaObjectPrefix" value="fs_" />
- </FileSystem>
-
- <!-- Workspace templates -->
- <Workspaces rootPath="${rep.home}/workspaces"
- defaultWorkspace="${defaultWorkspace}" />
- <Workspace name="${wsp.name}">
- <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
- <param name="dataSourceName" value="dataSource" />
- <param name="schema" value="postgresql" />
- <param name="schemaObjectPrefix" value="${wsp.name}_fs_" />
- </FileSystem>
- <PersistenceManager
- class="org.apache.jackrabbit.core.persistence.pool.PostgreSQLPersistenceManager">
- <param name="dataSourceName" value="dataSource" />
- <param name="schemaObjectPrefix" value="${wsp.name}_pm_" />
- <param name="bundleCacheSize" value="${bundleCacheMB}" />
- </PersistenceManager>
- <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
- <param name="path" value="${wsp.home}/index" />
- <param name="extractorPoolSize" value="${extractorPoolSize}" />
- <param name="cacheSize" value="${searchCacheSize}" />
- <param name="maxVolatileIndexSize" value="${maxVolatileIndexSize}" />
- </SearchIndex>
- <WorkspaceSecurity>
- <AccessControlProvider
- class="org.argeo.security.jackrabbit.ArgeoAccessControlProvider" />
- </WorkspaceSecurity>
- </Workspace>
-
- <!-- Versioning -->
- <Versioning rootPath="${rep.home}/version">
- <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
- <param name="dataSourceName" value="dataSource" />
- <param name="schema" value="postgresql" />
- <param name="schemaObjectPrefix" value="fs_ver_" />
- </FileSystem>
- <PersistenceManager
- class="org.apache.jackrabbit.core.persistence.pool.PostgreSQLPersistenceManager">
- <param name="dataSourceName" value="dataSource" />
- <param name="schemaObjectPrefix" value="pm_ver_" />
- <param name="bundleCacheSize" value="${bundleCacheMB}" />
- </PersistenceManager>
- </Versioning>
-
- <!-- Indexing -->
- <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
- <param name="path" value="${rep.home}/index" />
- <param name="extractorPoolSize" value="${extractorPoolSize}" />
- <param name="cacheSize" value="${searchCacheSize}" />
- <param name="maxVolatileIndexSize" value="${maxVolatileIndexSize}" />
- </SearchIndex>
-
- <!-- Security -->
- <Security appName="Jackrabbit">
- <SecurityManager class="org.argeo.security.jackrabbit.ArgeoSecurityManager"
- workspaceName="security" />
- <AccessManager class="org.argeo.security.jackrabbit.ArgeoAccessManager" />
- </Security>
-</Repository>
\ No newline at end of file
+++ /dev/null
-package org.argeo.jackrabbit.security;
-
-import java.security.Principal;
-import java.util.ArrayList;
-import java.util.List;
-
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.security.Privilege;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.apache.jackrabbit.api.security.JackrabbitAccessControlList;
-import org.apache.jackrabbit.api.security.JackrabbitAccessControlManager;
-import org.argeo.jcr.JcrUtils;
-
-/** Utilities around Jackrabbit security extensions. */
-public class JackrabbitSecurityUtils {
- private final static Log log = LogFactory.getLog(JackrabbitSecurityUtils.class);
-
- /**
- * Convenience method for denying a single privilege to a principal (user or
- * role), typically jcr:all
- */
- public synchronized static void denyPrivilege(Session session, String path, String principal, String privilege)
- throws RepositoryException {
- List<Privilege> privileges = new ArrayList<Privilege>();
- privileges.add(session.getAccessControlManager().privilegeFromName(privilege));
- denyPrivileges(session, path, () -> principal, privileges);
- }
-
- /**
- * Deny privileges on a path to a {@link Principal}. The path must already
- * exist. Session is saved. Synchronized to prevent concurrent modifications of
- * the same node.
- */
- public synchronized static Boolean denyPrivileges(Session session, String path, Principal principal,
- List<Privilege> privs) throws RepositoryException {
- // make sure the session is in line with the persisted state
- session.refresh(false);
- JackrabbitAccessControlManager acm = (JackrabbitAccessControlManager) session.getAccessControlManager();
- JackrabbitAccessControlList acl = (JackrabbitAccessControlList) JcrUtils.getAccessControlList(acm, path);
-
-// accessControlEntries: for (AccessControlEntry ace : acl.getAccessControlEntries()) {
-// Principal currentPrincipal = ace.getPrincipal();
-// if (currentPrincipal.getName().equals(principal.getName())) {
-// Privilege[] currentPrivileges = ace.getPrivileges();
-// if (currentPrivileges.length != privs.size())
-// break accessControlEntries;
-// for (int i = 0; i < currentPrivileges.length; i++) {
-// Privilege currP = currentPrivileges[i];
-// Privilege p = privs.get(i);
-// if (!currP.getName().equals(p.getName())) {
-// break accessControlEntries;
-// }
-// }
-// return false;
-// }
-// }
-
- Privilege[] privileges = privs.toArray(new Privilege[privs.size()]);
- acl.addEntry(principal, privileges, false);
- acm.setPolicy(path, acl);
- if (log.isDebugEnabled()) {
- StringBuffer privBuf = new StringBuffer();
- for (Privilege priv : privs)
- privBuf.append(priv.getName());
- log.debug("Denied privileges " + privBuf + " to " + principal.getName() + " on " + path + " in '"
- + session.getWorkspace().getName() + "'");
- }
- session.refresh(true);
- session.save();
- return true;
- }
-
- /** Singleton. */
- private JackrabbitSecurityUtils() {
-
- }
-}
+++ /dev/null
-/** Generic Jackrabbit security utilities. */
-package org.argeo.jackrabbit.security;
\ No newline at end of file
+++ /dev/null
-package org.argeo.jackrabbit.unit;
-
-import java.net.URL;
-
-import javax.jcr.Repository;
-
-import org.apache.commons.io.FileUtils;
-import org.apache.jackrabbit.core.RepositoryImpl;
-import org.apache.jackrabbit.core.config.RepositoryConfig;
-import org.argeo.jcr.unit.AbstractJcrTestCase;
-
-/** Factorizes configuration of an in memory transient repository */
-public abstract class AbstractJackrabbitTestCase extends AbstractJcrTestCase {
- protected RepositoryImpl repositoryImpl;
-
- // protected File getRepositoryFile() throws Exception {
- // Resource res = new ClassPathResource(
- // "org/argeo/jackrabbit/unit/repository-memory.xml");
- // return res.getFile();
- // }
-
- public AbstractJackrabbitTestCase() {
- URL url = AbstractJackrabbitTestCase.class.getResource("jaas.config");
- assert url != null;
- System.setProperty("java.security.auth.login.config", url.toString());
- }
-
- protected Repository createRepository() throws Exception {
- // Repository repository = new TransientRepository(getRepositoryFile(),
- // getHomeDir());
- RepositoryConfig repositoryConfig = RepositoryConfig.create(
- AbstractJackrabbitTestCase.class
- .getResourceAsStream(getRepositoryConfigResource()),
- getHomeDir().getAbsolutePath());
- RepositoryImpl repositoryImpl = RepositoryImpl.create(repositoryConfig);
- return repositoryImpl;
- }
-
- protected String getRepositoryConfigResource() {
- return "repository-memory.xml";
- }
-
- @Override
- protected void clearRepository(Repository repository) throws Exception {
- RepositoryImpl repositoryImpl = (RepositoryImpl) repository;
- if (repositoryImpl != null)
- repositoryImpl.shutdown();
- FileUtils.deleteDirectory(getHomeDir());
- }
-
-}
+++ /dev/null
-TEST_JACKRABBIT_ADMIN {
- org.argeo.cms.auth.DataAdminLoginModule requisite;
-};
-
-Jackrabbit {
- org.argeo.security.jackrabbit.SystemJackrabbitLoginModule requisite;
-};
+++ /dev/null
-/** Helpers for unit tests with Jackrabbit repositories. */
-package org.argeo.jackrabbit.unit;
\ No newline at end of file
+++ /dev/null
-<?xml version="1.0"?>
-<!DOCTYPE Repository PUBLIC "-//The Apache Software Foundation//DTD Jackrabbit 1.6//EN"
- "http://jackrabbit.apache.org/dtd/repository-2.0.dtd">
-<Repository>
- <!-- Shared datasource -->
- <DataSources>
- <DataSource name="dataSource">
- <param name="driver" value="org.h2.Driver" />
- <param name="url" value="jdbc:h2:mem:jackrabbit" />
- <param name="user" value="sa" />
- <param name="password" value="" />
- <param name="databaseType" value="h2" />
- <param name="maxPoolSize" value="10" />
- </DataSource>
- </DataSources>
-
- <!-- File system and datastore -->
- <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
- <param name="dataSourceName" value="dataSource" />
- <param name="schema" value="default" />
- <param name="schemaObjectPrefix" value="fs_" />
- </FileSystem>
- <DataStore class="org.apache.jackrabbit.core.data.db.DbDataStore">
- <param name="dataSourceName" value="dataSource" />
- <param name="schemaObjectPrefix" value="ds_" />
- </DataStore>
-
- <!-- Workspace templates -->
- <Workspaces rootPath="${rep.home}/workspaces"
- defaultWorkspace="dev" />
- <Workspace name="${wsp.name}">
- <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
- <param name="dataSourceName" value="dataSource" />
- <param name="schema" value="default" />
- <param name="schemaObjectPrefix" value="${wsp.name}_fs_" />
- </FileSystem>
- <PersistenceManager
- class="org.apache.jackrabbit.core.persistence.pool.H2PersistenceManager">
- <param name="dataSourceName" value="dataSource" />
- <param name="schemaObjectPrefix" value="${wsp.name}_pm_" />
- </PersistenceManager>
- <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
- <param name="path" value="${wsp.home}/index" />
- </SearchIndex>
- </Workspace>
-
- <!-- Versioning -->
- <Versioning rootPath="${rep.home}/version">
- <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
- <param name="dataSourceName" value="dataSource" />
- <param name="schema" value="default" />
- <param name="schemaObjectPrefix" value="fs_ver_" />
- </FileSystem>
- <PersistenceManager
- class="org.apache.jackrabbit.core.persistence.pool.H2PersistenceManager">
- <param name="dataSourceName" value="dataSource" />
- <param name="schemaObjectPrefix" value="pm_ver_" />
- </PersistenceManager>
- </Versioning>
-
- <!-- Indexing -->
- <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
- <param name="path" value="${rep.home}/repository/index" />
- <param name="extractorPoolSize" value="2" />
- <param name="supportHighlighting" value="true" />
- </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
+++ /dev/null
-<?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.mem.MemoryFileSystem" />
-
- <!-- Workspace templates -->
- <Workspaces rootPath="${rep.home}/workspaces"
- defaultWorkspace="main" configRootPath="/workspaces" />
- <Workspace name="${wsp.name}">
- <FileSystem class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
- <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" />
- <param name="directoryManagerClass"
- value="org.apache.jackrabbit.core.query.lucene.directory.RAMDirectoryManager" />
- <FileSystem class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
- </SearchIndex>
- </Workspace>
-
- <!-- Versioning -->
- <Versioning rootPath="${rep.home}/version">
- <FileSystem class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
- <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="directoryManagerClass"
- value="org.apache.jackrabbit.core.query.lucene.directory.RAMDirectoryManager" />
- <FileSystem class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
- </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
+++ /dev/null
-package org.argeo.jcr.proxy;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.URL;
-
-import javax.jcr.Binary;
-import javax.jcr.Node;
-import javax.jcr.Property;
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.nodetype.NodeType;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.argeo.jcr.JcrException;
-import org.argeo.jcr.JcrUtils;
-
-/** Base class for URL based proxys. */
-public abstract class AbstractUrlProxy implements ResourceProxy {
- private final static Log log = LogFactory.getLog(AbstractUrlProxy.class);
-
- private Repository jcrRepository;
- private Session jcrAdminSession;
- private String proxyWorkspace = "proxy";
-
- protected abstract Node retrieve(Session session, String path);
-
- void init() {
- try {
- jcrAdminSession = JcrUtils.loginOrCreateWorkspace(jcrRepository, proxyWorkspace);
- beforeInitSessionSave(jcrAdminSession);
- if (jcrAdminSession.hasPendingChanges())
- jcrAdminSession.save();
- } catch (RepositoryException e) {
- JcrUtils.discardQuietly(jcrAdminSession);
- throw new JcrException("Cannot initialize URL proxy", e);
- }
- }
-
- /**
- * Called before the (admin) session is saved at the end of the initialization.
- * Does nothing by default, to be overridden.
- */
- protected void beforeInitSessionSave(Session session) throws RepositoryException {
- }
-
- void destroy() {
- JcrUtils.logoutQuietly(jcrAdminSession);
- }
-
- /**
- * Called before the (admin) session is logged out when resources are released.
- * Does nothing by default, to be overridden.
- */
- protected void beforeDestroySessionLogout() throws RepositoryException {
- }
-
- public Node proxy(String path) {
- // we open a JCR session with client credentials in order not to use the
- // admin session in multiple thread or make it a bottleneck.
- Node nodeAdmin = null;
- Node nodeClient = null;
- Session clientSession = null;
- try {
- clientSession = jcrRepository.login(proxyWorkspace);
- if (!clientSession.itemExists(path) || shouldUpdate(clientSession, path)) {
- nodeAdmin = retrieveAndSave(path);
- if (nodeAdmin != null)
- nodeClient = clientSession.getNode(path);
- } else
- nodeClient = clientSession.getNode(path);
- return nodeClient;
- } catch (RepositoryException e) {
- throw new JcrException("Cannot proxy " + path, e);
- } finally {
- if (nodeClient == null)
- JcrUtils.logoutQuietly(clientSession);
- }
- }
-
- protected synchronized Node retrieveAndSave(String path) {
- try {
- Node node = retrieve(jcrAdminSession, path);
- if (node == null)
- return null;
- jcrAdminSession.save();
- return node;
- } catch (RepositoryException e) {
- JcrUtils.discardQuietly(jcrAdminSession);
- throw new JcrException("Cannot retrieve and save " + path, e);
- } finally {
- notifyAll();
- }
- }
-
- /** Session is not saved */
- protected synchronized Node proxyUrl(Session session, String remoteUrl, String path) throws RepositoryException {
- Node node = null;
- if (session.itemExists(path)) {
- // throw new ArgeoJcrException("Node " + path + " already exists");
- }
- try (InputStream in = new URL(remoteUrl).openStream()) {
- // URL u = new URL(remoteUrl);
- // in = u.openStream();
- node = importFile(session, path, in);
- } catch (IOException e) {
- if (log.isDebugEnabled()) {
- log.debug("Cannot read " + remoteUrl + ", skipping... " + e.getMessage());
- // log.trace("Cannot read because of ", e);
- }
- JcrUtils.discardQuietly(session);
- // } finally {
- // IOUtils.closeQuietly(in);
- }
- return node;
- }
-
- protected synchronized Node importFile(Session session, String path, InputStream in) throws RepositoryException {
- Binary binary = null;
- try {
- Node content = null;
- Node node = null;
- if (!session.itemExists(path)) {
- node = JcrUtils.mkdirs(session, path, NodeType.NT_FILE, NodeType.NT_FOLDER, false);
- content = node.addNode(Node.JCR_CONTENT, NodeType.NT_UNSTRUCTURED);
- } else {
- node = session.getNode(path);
- content = node.getNode(Node.JCR_CONTENT);
- }
- binary = session.getValueFactory().createBinary(in);
- content.setProperty(Property.JCR_DATA, binary);
- JcrUtils.updateLastModifiedAndParents(node, null, true);
- return node;
- } finally {
- JcrUtils.closeQuietly(binary);
- }
- }
-
- /** Whether the file should be updated. */
- protected Boolean shouldUpdate(Session clientSession, String nodePath) {
- return false;
- }
-
- public void setJcrRepository(Repository jcrRepository) {
- this.jcrRepository = jcrRepository;
- }
-
- public void setProxyWorkspace(String localWorkspace) {
- this.proxyWorkspace = localWorkspace;
- }
-
-}
+++ /dev/null
-package org.argeo.jcr.proxy;
-
-import javax.jcr.Node;
-
-/** A proxy which nows how to resolve and synchronize relative URLs */
-public interface ResourceProxy {
- /**
- * Proxy the file referenced by this relative path in the underlying
- * repository. A new session is created by each call, so the underlying
- * session of the returned node must be closed by the caller.
- *
- * @return the proxied Node, <code>null</code> if the resource was not found
- * (e.g. HTTP 404)
- */
- public Node proxy(String relativePath);
-}
+++ /dev/null
-package org.argeo.jcr.proxy;
-
-import java.io.IOException;
-import java.io.InputStream;
-
-import javax.jcr.Node;
-import javax.jcr.Property;
-import javax.jcr.RepositoryException;
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.apache.commons.io.FilenameUtils;
-import org.apache.commons.io.IOUtils;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.argeo.jcr.JcrException;
-import org.argeo.jcr.Bin;
-import org.argeo.jcr.JcrUtils;
-
-/** Wraps a proxy via HTTP */
-public class ResourceProxyServlet extends HttpServlet {
- private static final long serialVersionUID = -8886549549223155801L;
-
- private final static Log log = LogFactory
- .getLog(ResourceProxyServlet.class);
-
- private ResourceProxy proxy;
-
- private String contentTypeCharset = "UTF-8";
-
- @Override
- protected void doGet(HttpServletRequest request,
- HttpServletResponse response) throws ServletException, IOException {
- String path = request.getPathInfo();
-
- if (log.isTraceEnabled()) {
- log.trace("path=" + path);
- log.trace("UserPrincipal = " + request.getUserPrincipal().getName());
- log.trace("SessionID = " + request.getSession(false).getId());
- log.trace("ContextPath = " + request.getContextPath());
- log.trace("ServletPath = " + request.getServletPath());
- log.trace("PathInfo = " + request.getPathInfo());
- log.trace("Method = " + request.getMethod());
- log.trace("User-Agent = " + request.getHeader("User-Agent"));
- }
-
- Node node = null;
- try {
- node = proxy.proxy(path);
- if (node == null)
- response.sendError(404);
- else
- processResponse(node, response);
- } finally {
- if (node != null)
- try {
- JcrUtils.logoutQuietly(node.getSession());
- } catch (RepositoryException e) {
- // silent
- }
- }
-
- }
-
- /** Retrieve the content of the node. */
- protected void processResponse(Node node, HttpServletResponse response) {
-// Binary binary = null;
-// InputStream in = null;
- try(Bin binary = new Bin( node.getNode(Property.JCR_CONTENT)
- .getProperty(Property.JCR_DATA));InputStream in = binary.getStream()) {
- String fileName = node.getName();
- String ext = FilenameUtils.getExtension(fileName);
-
- // TODO use a more generic / standard approach
- // see http://svn.apache.org/viewvc/tomcat/trunk/conf/web.xml
- String contentType;
- if ("xml".equals(ext))
- contentType = "text/xml;charset=" + contentTypeCharset;
- else if ("jar".equals(ext))
- contentType = "application/java-archive";
- else if ("zip".equals(ext))
- contentType = "application/zip";
- else if ("gz".equals(ext))
- contentType = "application/x-gzip";
- else if ("bz2".equals(ext))
- contentType = "application/x-bzip2";
- else if ("tar".equals(ext))
- contentType = "application/x-tar";
- else if ("rpm".equals(ext))
- contentType = "application/x-redhat-package-manager";
- else
- contentType = "application/octet-stream";
- contentType = contentType + ";name=\"" + fileName + "\"";
- response.setHeader("Content-Disposition", "attachment; filename=\""
- + fileName + "\"");
- response.setHeader("Expires", "0");
- response.setHeader("Cache-Control", "no-cache, must-revalidate");
- response.setHeader("Pragma", "no-cache");
-
- response.setContentType(contentType);
-
- IOUtils.copy(in, response.getOutputStream());
- } catch (RepositoryException e) {
- throw new JcrException("Cannot download " + node, e);
- } catch (IOException e) {
- throw new RuntimeException("Cannot download " + node, e);
- }
- }
-
- public void setProxy(ResourceProxy resourceProxy) {
- this.proxy = resourceProxy;
- }
-
-}
+++ /dev/null
-/** Components to build proxys based on JCR. */
-package org.argeo.jcr.proxy;
\ No newline at end of file
+++ /dev/null
-package org.argeo.jcr.unit;
-
-import java.io.File;
-import java.security.AccessController;
-import java.security.PrivilegedAction;
-
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.SimpleCredentials;
-import javax.security.auth.Subject;
-import javax.security.auth.login.LoginContext;
-import javax.security.auth.login.LoginException;
-
-import org.apache.commons.io.FileUtils;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.argeo.jcr.JcrException;
-
-import junit.framework.TestCase;
-
-/** Base for unit tests with a JCR repository. */
-public abstract class AbstractJcrTestCase extends TestCase {
- private final static Log log = LogFactory.getLog(AbstractJcrTestCase.class);
-
- private Repository repository;
- private Session session = null;
-
- public final static String LOGIN_CONTEXT_TEST_SYSTEM = "TEST_JACKRABBIT_ADMIN";
-
- // protected abstract File getRepositoryFile() throws Exception;
-
- protected abstract Repository createRepository() throws Exception;
-
- protected abstract void clearRepository(Repository repository) throws Exception;
-
- @Override
- protected void setUp() throws Exception {
- File homeDir = getHomeDir();
- FileUtils.deleteDirectory(homeDir);
- repository = createRepository();
- }
-
- @Override
- protected void tearDown() throws Exception {
- if (session != null) {
- session.logout();
- if (log.isTraceEnabled())
- log.trace("Logout session");
- }
- clearRepository(repository);
- }
-
- protected Session session() {
- if (session != null && session.isLive())
- return session;
- Session session;
- if (getLoginContext() != null) {
- LoginContext lc;
- try {
- lc = new LoginContext(getLoginContext());
- lc.login();
- } catch (LoginException e) {
- throw new IllegalStateException("JAAS login failed", e);
- }
- session = Subject.doAs(lc.getSubject(), new PrivilegedAction<Session>() {
-
- @Override
- public Session run() {
- return login();
- }
-
- });
- } else
- session = login();
- this.session = session;
- return this.session;
- }
-
- protected String getLoginContext() {
- return null;
- }
-
- protected Session login() {
- try {
- if (log.isTraceEnabled())
- log.trace("Login session");
- Subject subject = Subject.getSubject(AccessController.getContext());
- if (subject != null)
- return getRepository().login();
- else
- return getRepository().login(new SimpleCredentials("demo", "demo".toCharArray()));
- } catch (RepositoryException e) {
- throw new JcrException("Cannot login to repository", e);
- }
- }
-
- protected Repository getRepository() {
- return repository;
- }
-
- /**
- * enables children class to set an existing repository in case it is not
- * deleted on startup, to test migration by instance
- */
- public void setRepository(Repository repository) {
- this.repository = repository;
- }
-
- protected File getHomeDir() {
- File homeDir = new File(System.getProperty("java.io.tmpdir"),
- AbstractJcrTestCase.class.getSimpleName() + "-" + System.getProperty("user.name"));
- return homeDir;
- }
-
-}
+++ /dev/null
-/** Helpers for unit tests with JCR repositories. */
-package org.argeo.jcr.unit;
\ No newline at end of file
+++ /dev/null
-package org.argeo.jcr;
-
-import java.io.IOException;
-import java.io.InputStream;
-
-import javax.jcr.Binary;
-import javax.jcr.Property;
-import javax.jcr.RepositoryException;
-
-/**
- * A {@link Binary} wrapper implementing {@link AutoCloseable} for ease of use
- * in try/catch blocks.
- */
-public class Bin implements Binary, AutoCloseable {
- private final Binary wrappedBinary;
-
- public Bin(Property property) throws RepositoryException {
- this(property.getBinary());
- }
-
- public Bin(Binary wrappedBinary) {
- if (wrappedBinary == null)
- throw new IllegalArgumentException("Wrapped binary cannot be null");
- this.wrappedBinary = wrappedBinary;
- }
-
- // private static Binary getBinary(Property property) throws IOException {
- // try {
- // return property.getBinary();
- // } catch (RepositoryException e) {
- // throw new IOException("Cannot get binary from property " + property, e);
- // }
- // }
-
- @Override
- public void close() {
- dispose();
- }
-
- @Override
- public InputStream getStream() throws RepositoryException {
- return wrappedBinary.getStream();
- }
-
- @Override
- public int read(byte[] b, long position) throws IOException, RepositoryException {
- return wrappedBinary.read(b, position);
- }
-
- @Override
- public long getSize() throws RepositoryException {
- return wrappedBinary.getSize();
- }
-
- @Override
- public void dispose() {
- wrappedBinary.dispose();
- }
-
-}
+++ /dev/null
-package org.argeo.jcr;
-
-import java.util.Collection;
-import java.util.Iterator;
-import java.util.NoSuchElementException;
-
-import javax.jcr.Node;
-import javax.jcr.NodeIterator;
-
-/** Wraps a collection of nodes in order to read it as a {@link NodeIterator} */
-public class CollectionNodeIterator implements NodeIterator {
- private final Long collectionSize;
- private final Iterator<Node> iterator;
- private Integer position = 0;
-
- public CollectionNodeIterator(Collection<Node> nodes) {
- super();
- this.collectionSize = (long) nodes.size();
- this.iterator = nodes.iterator();
- }
-
- public void skip(long skipNum) {
- if (skipNum < 0)
- throw new IllegalArgumentException(
- "Skip count has to be positive: " + skipNum);
-
- for (long i = 0; i < skipNum; i++) {
- if (!hasNext())
- throw new NoSuchElementException("Last element past (position="
- + getPosition() + ")");
- nextNode();
- }
- }
-
- public long getSize() {
- return collectionSize;
- }
-
- public long getPosition() {
- return position;
- }
-
- public boolean hasNext() {
- return iterator.hasNext();
- }
-
- public Object next() {
- return nextNode();
- }
-
- public void remove() {
- iterator.remove();
- }
-
- public Node nextNode() {
- Node node = iterator.next();
- position++;
- return node;
- }
-
-}
+++ /dev/null
-package org.argeo.jcr;
-
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.observation.Event;
-import javax.jcr.observation.EventIterator;
-import javax.jcr.observation.EventListener;
-import javax.jcr.observation.ObservationManager;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-
-/** To be overridden */
-public class DefaultJcrListener implements EventListener {
- private final static Log log = LogFactory.getLog(DefaultJcrListener.class);
- private Session session;
- private String path = "/";
- private Boolean deep = true;
-
- public void start() {
- try {
- addEventListener(session().getWorkspace().getObservationManager());
- if (log.isDebugEnabled())
- log.debug("Registered JCR event listener on " + path);
- } catch (RepositoryException e) {
- throw new JcrException("Cannot register event listener", e);
- }
- }
-
- public void stop() {
- try {
- session().getWorkspace().getObservationManager()
- .removeEventListener(this);
- if (log.isDebugEnabled())
- log.debug("Unregistered JCR event listener on " + path);
- } catch (RepositoryException e) {
- throw new JcrException("Cannot unregister event listener", e);
- }
- }
-
- /** Default is listen to all events */
- protected Integer getEvents() {
- return Event.NODE_ADDED | Event.NODE_REMOVED | Event.PROPERTY_ADDED
- | Event.PROPERTY_CHANGED | Event.PROPERTY_REMOVED;
- }
-
- /** To be overidden */
- public void onEvent(EventIterator events) {
- while (events.hasNext()) {
- Event event = events.nextEvent();
- log.debug(event);
- }
- }
-
- /** To be overidden */
- protected void addEventListener(ObservationManager observationManager)
- throws RepositoryException {
- observationManager.addEventListener(this, getEvents(), path, deep,
- null, null, false);
- }
-
- private Session session() {
- return session;
- }
-
- public void setPath(String path) {
- this.path = path;
- }
-
- public void setDeep(Boolean deep) {
- this.deep = deep;
- }
-
- public void setSession(Session session) {
- this.session = session;
- }
-
-}
+++ /dev/null
-package org.argeo.jcr;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.math.BigDecimal;
-import java.text.MessageFormat;
-import java.time.Instant;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Calendar;
-import java.util.Collections;
-import java.util.Date;
-import java.util.GregorianCalendar;
-import java.util.Iterator;
-import java.util.List;
-
-import javax.jcr.Binary;
-import javax.jcr.ItemNotFoundException;
-import javax.jcr.Node;
-import javax.jcr.NodeIterator;
-import javax.jcr.Property;
-import javax.jcr.PropertyType;
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.Value;
-import javax.jcr.Workspace;
-import javax.jcr.nodetype.NodeType;
-import javax.jcr.query.Query;
-import javax.jcr.query.QueryManager;
-import javax.jcr.security.Privilege;
-import javax.jcr.version.Version;
-import javax.jcr.version.VersionHistory;
-import javax.jcr.version.VersionIterator;
-import javax.jcr.version.VersionManager;
-
-import org.apache.commons.io.IOUtils;
-
-/**
- * Utility class whose purpose is to make using JCR less verbose by
- * systematically using unchecked exceptions and returning <code>null</code>
- * when something is not found. This is especially useful when writing user
- * interfaces (such as with SWT) where listeners and callbacks expect unchecked
- * exceptions. Loosely inspired by Java's <code>Files</code> singleton.
- */
-public class Jcr {
- /**
- * The name of a node which will be serialized as XML text, as per section 7.3.1
- * of the JCR 2.0 specifications.
- */
- public final static String JCR_XMLTEXT = "jcr:xmltext";
- /**
- * The name of a property which will be serialized as XML text, as per section
- * 7.3.1 of the JCR 2.0 specifications.
- */
- public final static String JCR_XMLCHARACTERS = "jcr:xmlcharacters";
- /**
- * <code>jcr:name</code>, when used in another context than
- * {@link Property#JCR_NAME}, typically to name a node rather than a property.
- */
- public final static String JCR_NAME = "jcr:name";
- /**
- * <code>jcr:path</code>, when used in another context than
- * {@link Property#JCR_PATH}, typically to name a node rather than a property.
- */
- public final static String JCR_PATH = "jcr:path";
- /**
- * <code>jcr:primaryType</code> with prefix instead of namespace (as in
- * {@link Property#JCR_PRIMARY_TYPE}.
- */
- public final static String JCR_PRIMARY_TYPE = "jcr:primaryType";
- /**
- * <code>jcr:mixinTypes</code> with prefix instead of namespace (as in
- * {@link Property#JCR_MIXIN_TYPES}.
- */
- public final static String JCR_MIXIN_TYPES = "jcr:mixinTypes";
- /**
- * <code>jcr:uuid</code> with prefix instead of namespace (as in
- * {@link Property#JCR_UUID}.
- */
- public final static String JCR_UUID = "jcr:uuid";
- /**
- * <code>jcr:created</code> with prefix instead of namespace (as in
- * {@link Property#JCR_CREATED}.
- */
- public final static String JCR_CREATED = "jcr:created";
- /**
- * <code>jcr:createdBy</code> with prefix instead of namespace (as in
- * {@link Property#JCR_CREATED_BY}.
- */
- public final static String JCR_CREATED_BY = "jcr:createdBy";
- /**
- * <code>jcr:lastModified</code> with prefix instead of namespace (as in
- * {@link Property#JCR_LAST_MODIFIED}.
- */
- public final static String JCR_LAST_MODIFIED = "jcr:lastModified";
- /**
- * <code>jcr:lastModifiedBy</code> with prefix instead of namespace (as in
- * {@link Property#JCR_LAST_MODIFIED_BY}.
- */
- public final static String JCR_LAST_MODIFIED_BY = "jcr:lastModifiedBy";
-
- /**
- * @see Node#isNodeType(String)
- * @throws JcrException caused by {@link RepositoryException}
- */
- public static boolean isNodeType(Node node, String nodeTypeName) {
- try {
- return node.isNodeType(nodeTypeName);
- } catch (RepositoryException e) {
- throw new JcrException("Cannot get whether " + node + " is of type " + nodeTypeName, e);
- }
- }
-
- /**
- * @see Node#hasNodes()
- * @throws JcrException caused by {@link RepositoryException}
- */
- public static boolean hasNodes(Node node) {
- try {
- return node.hasNodes();
- } catch (RepositoryException e) {
- throw new JcrException("Cannot get whether " + node + " has children.", e);
- }
- }
-
- /**
- * @see Node#getParent()
- * @throws JcrException caused by {@link RepositoryException}
- */
- public static Node getParent(Node node) {
- try {
- return isRoot(node) ? null : node.getParent();
- } catch (RepositoryException e) {
- throw new JcrException("Cannot get parent of " + node, e);
- }
- }
-
- /**
- * @see Node#getParent()
- * @throws JcrException caused by {@link RepositoryException}
- */
- public static String getParentPath(Node node) {
- return getPath(getParent(node));
- }
-
- /**
- * Whether this node is the root node.
- *
- * @throws JcrException caused by {@link RepositoryException}
- */
- public static boolean isRoot(Node node) {
- try {
- return node.getDepth() == 0;
- } catch (RepositoryException e) {
- throw new JcrException("Cannot get depth of " + node, e);
- }
- }
-
- /**
- * @see Node#getPath()
- * @throws JcrException caused by {@link RepositoryException}
- */
- public static String getPath(Node node) {
- try {
- return node.getPath();
- } catch (RepositoryException e) {
- throw new JcrException("Cannot get path of " + node, e);
- }
- }
-
- /**
- * @see Node#getSession()
- * @see Session#getWorkspace()
- * @see Workspace#getName()
- */
- public static String getWorkspaceName(Node node) {
- return session(node).getWorkspace().getName();
- }
-
- /**
- * @see Node#getIdentifier()
- * @throws JcrException caused by {@link RepositoryException}
- */
- public static String getIdentifier(Node node) {
- try {
- return node.getIdentifier();
- } catch (RepositoryException e) {
- throw new JcrException("Cannot get identifier of " + node, e);
- }
- }
-
- /**
- * @see Node#getName()
- * @throws JcrException caused by {@link RepositoryException}
- */
- public static String getName(Node node) {
- try {
- return node.getName();
- } catch (RepositoryException e) {
- throw new JcrException("Cannot get name of " + node, e);
- }
- }
-
- /**
- * Returns the node name with its current index (useful for re-ordering).
- *
- * @see Node#getName()
- * @see Node#getIndex()
- * @throws JcrException caused by {@link RepositoryException}
- */
- public static String getIndexedName(Node node) {
- try {
- return node.getName() + "[" + node.getIndex() + "]";
- } catch (RepositoryException e) {
- throw new JcrException("Cannot get name of " + node, e);
- }
- }
-
- /**
- * @see Node#getProperty(String)
- * @throws JcrException caused by {@link RepositoryException}
- */
- public static Property getProperty(Node node, String property) {
- try {
- if (node.hasProperty(property))
- return node.getProperty(property);
- else
- return null;
- } catch (RepositoryException e) {
- throw new JcrException("Cannot get property " + property + " of " + node, e);
- }
- }
-
- /**
- * @see Node#getIndex()
- * @throws JcrException caused by {@link RepositoryException}
- */
- public static int getIndex(Node node) {
- try {
- return node.getIndex();
- } catch (RepositoryException e) {
- throw new JcrException("Cannot get index of " + node, e);
- }
- }
-
- /**
- * If node has mixin {@link NodeType#MIX_TITLE}, return
- * {@link Property#JCR_TITLE}, otherwise return {@link #getName(Node)}.
- */
- public static String getTitle(Node node) {
- if (Jcr.isNodeType(node, NodeType.MIX_TITLE))
- return get(node, Property.JCR_TITLE);
- else
- return Jcr.getName(node);
- }
-
- /** Accesses a {@link NodeIterator} as an {@link Iterable}. */
- @SuppressWarnings("unchecked")
- public static Iterable<Node> iterate(NodeIterator nodeIterator) {
- return new Iterable<Node>() {
-
- @Override
- public Iterator<Node> iterator() {
- return nodeIterator;
- }
- };
- }
-
- /**
- * @return the children as an {@link Iterable} for use in for-each llops.
- * @see Node#getNodes()
- * @throws JcrException caused by {@link RepositoryException}
- */
- public static Iterable<Node> nodes(Node node) {
- try {
- return iterate(node.getNodes());
- } catch (RepositoryException e) {
- throw new JcrException("Cannot get children of " + node, e);
- }
- }
-
- /**
- * @return the children as a (possibly empty) {@link List}.
- * @see Node#getNodes()
- * @throws JcrException caused by {@link RepositoryException}
- */
- public static List<Node> getNodes(Node node) {
- List<Node> nodes = new ArrayList<>();
- try {
- if (node.hasNodes()) {
- NodeIterator nit = node.getNodes();
- while (nit.hasNext())
- nodes.add(nit.nextNode());
- return nodes;
- } else
- return nodes;
- } catch (RepositoryException e) {
- throw new JcrException("Cannot get children of " + node, e);
- }
- }
-
- /**
- * @return the child or <code>null</node> if not found
- * @see Node#getNode(String)
- * @throws JcrException caused by {@link RepositoryException}
- */
- public static Node getNode(Node node, String child) {
- try {
- if (node.hasNode(child))
- return node.getNode(child);
- else
- return null;
- } catch (RepositoryException e) {
- throw new JcrException("Cannot get child of " + node, e);
- }
- }
-
- /**
- * @return the node at this path or <code>null</node> if not found
- * @see Session#getNode(String)
- * @throws JcrException caused by {@link RepositoryException}
- */
- public static Node getNode(Session session, String path) {
- try {
- if (session.nodeExists(path))
- return session.getNode(path);
- else
- return null;
- } catch (RepositoryException e) {
- throw new JcrException("Cannot get node " + path, e);
- }
- }
-
- /**
- * Add a node to this parent, setting its primary type and its mixins.
- *
- * @param parent the parent node
- * @param name the name of the node, if <code>null</code>, the primary
- * type will be used (typically for XML structures)
- * @param primaryType the primary type, if <code>null</code>
- * {@link NodeType#NT_UNSTRUCTURED} will be used.
- * @param mixins the mixins
- * @return the created node
- * @see Node#addNode(String, String)
- * @see Node#addMixin(String)
- */
- public static Node addNode(Node parent, String name, String primaryType, String... mixins) {
- if (name == null && primaryType == null)
- throw new IllegalArgumentException("Both node name and primary type cannot be null");
- try {
- Node newNode = parent.addNode(name == null ? primaryType : name,
- primaryType == null ? NodeType.NT_UNSTRUCTURED : primaryType);
- for (String mixin : mixins) {
- newNode.addMixin(mixin);
- }
- return newNode;
- } catch (RepositoryException e) {
- throw new JcrException("Cannot add node " + name + " to " + parent, e);
- }
- }
-
- /**
- * Add an {@link NodeType#NT_BASE} node to this parent.
- *
- * @param parent the parent node
- * @param name the name of the node, cannot be <code>null</code>
- * @return the created node
- *
- * @see Node#addNode(String)
- */
- public static Node addNode(Node parent, String name) {
- if (name == null)
- throw new IllegalArgumentException("Node name cannot be null");
- try {
- Node newNode = parent.addNode(name);
- return newNode;
- } catch (RepositoryException e) {
- throw new JcrException("Cannot add node " + name + " to " + parent, e);
- }
- }
-
- /**
- * Add mixins to a node.
- *
- * @param node the node
- * @param mixins the mixins
- * @return the created node
- * @see Node#addMixin(String)
- */
- public static void addMixin(Node node, String... mixins) {
- try {
- for (String mixin : mixins) {
- node.addMixin(mixin);
- }
- } catch (RepositoryException e) {
- throw new JcrException("Cannot add mixins " + Arrays.asList(mixins) + " to " + node, e);
- }
- }
-
- /**
- * Removes this node.
- *
- * @see Node#remove()
- */
- public static void remove(Node node) {
- try {
- node.remove();
- } catch (RepositoryException e) {
- throw new JcrException("Cannot remove node " + node, e);
- }
- }
-
- /**
- * @return the node with htis id or <code>null</node> if not found
- * @see Session#getNodeByIdentifier(String)
- * @throws JcrException caused by {@link RepositoryException}
- */
- public static Node getNodeById(Session session, String id) {
- try {
- return session.getNodeByIdentifier(id);
- } catch (ItemNotFoundException e) {
- return null;
- } catch (RepositoryException e) {
- throw new JcrException("Cannot get node with id " + id, e);
- }
- }
-
- /**
- * Set a property to the given value, or remove it if the value is
- * <code>null</code>.
- *
- * @throws JcrException caused by {@link RepositoryException}
- */
- public static void set(Node node, String property, Object value) {
- try {
- if (!node.hasProperty(property)) {
- if (value != null) {
- if (value instanceof List) {// multiple
- List<?> lst = (List<?>) value;
- String[] values = new String[lst.size()];
- for (int i = 0; i < lst.size(); i++) {
- values[i] = lst.get(i).toString();
- }
- node.setProperty(property, values);
- } else {
- node.setProperty(property, value.toString());
- }
- }
- return;
- }
- Property prop = node.getProperty(property);
- if (value == null) {
- prop.remove();
- return;
- }
-
- // multiple
- if (value instanceof List) {
- List<?> lst = (List<?>) value;
- String[] values = new String[lst.size()];
- // TODO better cast?
- for (int i = 0; i < lst.size(); i++) {
- values[i] = lst.get(i).toString();
- }
- if (!prop.isMultiple())
- prop.remove();
- node.setProperty(property, values);
- return;
- }
-
- // single
- if (prop.isMultiple()) {
- prop.remove();
- node.setProperty(property, value.toString());
- return;
- }
-
- if (value instanceof String)
- prop.setValue((String) value);
- else if (value instanceof Long)
- prop.setValue((Long) value);
- else if (value instanceof Integer)
- prop.setValue(((Integer) value).longValue());
- else if (value instanceof Double)
- prop.setValue((Double) value);
- else if (value instanceof Float)
- prop.setValue(((Float) value).doubleValue());
- else if (value instanceof Calendar)
- prop.setValue((Calendar) value);
- else if (value instanceof BigDecimal)
- prop.setValue((BigDecimal) value);
- else if (value instanceof Boolean)
- prop.setValue((Boolean) value);
- else if (value instanceof byte[])
- JcrUtils.setBinaryAsBytes(prop, (byte[]) value);
- else if (value instanceof Instant) {
- Instant instant = (Instant) value;
- GregorianCalendar calendar = new GregorianCalendar();
- calendar.setTime(Date.from(instant));
- prop.setValue(calendar);
- } else // try with toString()
- prop.setValue(value.toString());
- } catch (RepositoryException e) {
- throw new JcrException("Cannot set property " + property + " of " + node + " to " + value, e);
- }
- }
-
- /**
- * Get property as {@link String}.
- *
- * @return the value of
- * {@link Node#getProperty(String)}.{@link Property#getString()} or
- * <code>null</code> if the property does not exist.
- * @throws JcrException caused by {@link RepositoryException}
- */
- public static String get(Node node, String property) {
- return get(node, property, null);
- }
-
- /**
- * Get property as a {@link String}. If the property is multiple it returns the
- * first value.
- *
- * @return the value of
- * {@link Node#getProperty(String)}.{@link Property#getString()} or
- * <code>defaultValue</code> if the property does not exist.
- * @throws JcrException caused by {@link RepositoryException}
- */
- public static String get(Node node, String property, String defaultValue) {
- try {
- if (node.hasProperty(property)) {
- Property p = node.getProperty(property);
- if (!p.isMultiple())
- return p.getString();
- else {
- Value[] values = p.getValues();
- if (values.length == 0)
- return defaultValue;
- else
- return values[0].getString();
- }
- } else
- return defaultValue;
- } catch (RepositoryException e) {
- throw new JcrException("Cannot retrieve property " + property + " from " + node, e);
- }
- }
-
- /**
- * Get property as a {@link Value}.
- *
- * @return {@link Node#getProperty(String)} or <code>null</code> if the property
- * does not exist.
- * @throws JcrException caused by {@link RepositoryException}
- */
- public static Value getValue(Node node, String property) {
- try {
- if (node.hasProperty(property))
- return node.getProperty(property).getValue();
- else
- return null;
- } catch (RepositoryException e) {
- throw new JcrException("Cannot retrieve property " + property + " from " + node, e);
- }
- }
-
- /**
- * Get property doing a best effort to cast it as the target object.
- *
- * @return the value of {@link Node#getProperty(String)} or
- * <code>defaultValue</code> if the property does not exist.
- * @throws IllegalArgumentException if the value could not be cast
- * @throws JcrException in case of unexpected
- * {@link RepositoryException}
- */
- @SuppressWarnings("unchecked")
- public static <T> T getAs(Node node, String property, T defaultValue) {
- try {
- // TODO deal with multiple
- if (node.hasProperty(property)) {
- Property p = node.getProperty(property);
- try {
- if (p.isMultiple()) {
- throw new UnsupportedOperationException("Multiple values properties are not supported");
- }
- Value value = p.getValue();
- return (T) get(value);
- } catch (ClassCastException e) {
- throw new IllegalArgumentException(
- "Cannot cast property of type " + PropertyType.nameFromValue(p.getType()), e);
- }
- } else {
- return defaultValue;
- }
- } catch (RepositoryException e) {
- throw new JcrException("Cannot retrieve property " + property + " from " + node, e);
- }
- }
-
- /**
- * Get a multiple property as a list, doing a best effort to cast it as the
- * target list.
- *
- * @return the value of {@link Node#getProperty(String)}.
- * @throws IllegalArgumentException if the value could not be cast
- * @throws JcrException in case of unexpected
- * {@link RepositoryException}
- */
- public static <T> List<T> getMultiple(Node node, String property) {
- try {
- if (node.hasProperty(property)) {
- Property p = node.getProperty(property);
- return getMultiple(p);
- } else {
- return null;
- }
- } catch (RepositoryException e) {
- throw new JcrException("Cannot retrieve multiple values property " + property + " from " + node, e);
- }
- }
-
- /**
- * Get a multiple property as a list, doing a best effort to cast it as the
- * target list.
- */
- @SuppressWarnings("unchecked")
- public static <T> List<T> getMultiple(Property p) {
- try {
- List<T> res = new ArrayList<>();
- if (!p.isMultiple()) {
- res.add((T) get(p.getValue()));
- return res;
- }
- Value[] values = p.getValues();
- for (Value value : values) {
- res.add((T) get(value));
- }
- return res;
- } catch (ClassCastException | RepositoryException e) {
- throw new IllegalArgumentException("Cannot get property " + p, e);
- }
- }
-
- /** Cast a {@link Value} to a standard Java object. */
- public static Object get(Value value) {
- Binary binary = null;
- try {
- switch (value.getType()) {
- case PropertyType.STRING:
- return value.getString();
- case PropertyType.DOUBLE:
- return (Double) value.getDouble();
- case PropertyType.LONG:
- return (Long) value.getLong();
- case PropertyType.BOOLEAN:
- return (Boolean) value.getBoolean();
- case PropertyType.DATE:
- return value.getDate();
- case PropertyType.BINARY:
- binary = value.getBinary();
- byte[] arr = null;
- try (InputStream in = binary.getStream(); ByteArrayOutputStream out = new ByteArrayOutputStream();) {
- IOUtils.copy(in, out);
- arr = out.toByteArray();
- } catch (IOException e) {
- throw new RuntimeException("Cannot read binary from " + value, e);
- }
- return arr;
- default:
- return value.getString();
- }
- } catch (RepositoryException e) {
- throw new JcrException("Cannot cast value from " + value, e);
- } finally {
- if (binary != null)
- binary.dispose();
- }
- }
-
- /**
- * Retrieves the {@link Session} related to this node.
- *
- * @deprecated Use {@link #getSession(Node)} instead.
- */
- @Deprecated
- public static Session session(Node node) {
- return getSession(node);
- }
-
- /** Retrieves the {@link Session} related to this node. */
- public static Session getSession(Node node) {
- try {
- return node.getSession();
- } catch (RepositoryException e) {
- throw new JcrException("Cannot retrieve session related to " + node, e);
- }
- }
-
- /** Retrieves the root node related to this session. */
- public static Node getRootNode(Session session) {
- try {
- return session.getRootNode();
- } catch (RepositoryException e) {
- throw new JcrException("Cannot get root node for " + session, e);
- }
- }
-
- /** Whether this item exists. */
- public static boolean itemExists(Session session, String path) {
- try {
- return session.itemExists(path);
- } catch (RepositoryException e) {
- throw new JcrException("Cannot check whether " + path + " exists", e);
- }
- }
-
- /**
- * Saves the {@link Session} related to this node. Note that all other unrelated
- * modifications in this session will also be saved.
- */
- public static void save(Node node) {
- try {
- Session session = node.getSession();
-// if (node.isNodeType(NodeType.MIX_LAST_MODIFIED)) {
-// set(node, Property.JCR_LAST_MODIFIED, Instant.now());
-// set(node, Property.JCR_LAST_MODIFIED_BY, session.getUserID());
-// }
- if (session.hasPendingChanges())
- session.save();
- } catch (RepositoryException e) {
- throw new JcrException("Cannot save session related to " + node + " in workspace "
- + session(node).getWorkspace().getName(), e);
- }
- }
-
- /** Login to a JCR repository. */
- public static Session login(Repository repository, String workspace) {
- try {
- return repository.login(workspace);
- } catch (RepositoryException e) {
- throw new IllegalArgumentException("Cannot login to repository", e);
- }
- }
-
- /** Safely and silently logs out a session. */
- public static void logout(Session session) {
- try {
- if (session != null)
- if (session.isLive())
- session.logout();
- } catch (Exception e) {
- // silent
- }
- }
-
- /** Safely and silently logs out the underlying session. */
- public static void logout(Node node) {
- Jcr.logout(session(node));
- }
-
- /*
- * SECURITY
- */
- /**
- * Add a single privilege to a node.
- *
- * @see Privilege
- */
- public static void addPrivilege(Node node, String principal, String privilege) {
- try {
- Session session = node.getSession();
- JcrUtils.addPrivilege(session, node.getPath(), principal, privilege);
- } catch (RepositoryException e) {
- throw new JcrException("Cannot add privilege " + privilege + " to " + node, e);
- }
- }
-
- /*
- * VERSIONING
- */
- /** Get checked out status. */
- public static boolean isCheckedOut(Node node) {
- try {
- return node.isCheckedOut();
- } catch (RepositoryException e) {
- throw new JcrException("Cannot retrieve checked out status of " + node, e);
- }
- }
-
- /** @see VersionManager#checkpoint(String) */
- public static void checkpoint(Node node) {
- try {
- versionManager(node).checkpoint(node.getPath());
- } catch (RepositoryException e) {
- throw new JcrException("Cannot check in " + node, e);
- }
- }
-
- /** @see VersionManager#checkin(String) */
- public static void checkin(Node node) {
- try {
- versionManager(node).checkin(node.getPath());
- } catch (RepositoryException e) {
- throw new JcrException("Cannot check in " + node, e);
- }
- }
-
- /** @see VersionManager#checkout(String) */
- public static void checkout(Node node) {
- try {
- versionManager(node).checkout(node.getPath());
- } catch (RepositoryException e) {
- throw new JcrException("Cannot check out " + node, e);
- }
- }
-
- /** Get the {@link VersionManager} related to this node. */
- public static VersionManager versionManager(Node node) {
- try {
- return node.getSession().getWorkspace().getVersionManager();
- } catch (RepositoryException e) {
- throw new JcrException("Cannot get version manager from " + node, e);
- }
- }
-
- /** Get the {@link VersionHistory} related to this node. */
- public static VersionHistory getVersionHistory(Node node) {
- try {
- return versionManager(node).getVersionHistory(node.getPath());
- } catch (RepositoryException e) {
- throw new JcrException("Cannot get version history from " + node, e);
- }
- }
-
- /**
- * The linear versions of this version history in reverse order and without the
- * root version.
- */
- public static List<Version> getLinearVersions(VersionHistory versionHistory) {
- try {
- List<Version> lst = new ArrayList<>();
- VersionIterator vit = versionHistory.getAllLinearVersions();
- while (vit.hasNext())
- lst.add(vit.nextVersion());
- lst.remove(0);
- Collections.reverse(lst);
- return lst;
- } catch (RepositoryException e) {
- throw new JcrException("Cannot get linear versions from " + versionHistory, e);
- }
- }
-
- /** The frozen node related to this {@link Version}. */
- public static Node getFrozenNode(Version version) {
- try {
- return version.getFrozenNode();
- } catch (RepositoryException e) {
- throw new JcrException("Cannot get frozen node from " + version, e);
- }
- }
-
- /** Get the base {@link Version} related to this node. */
- public static Version getBaseVersion(Node node) {
- try {
- return versionManager(node).getBaseVersion(node.getPath());
- } catch (RepositoryException e) {
- throw new JcrException("Cannot get base version from " + node, e);
- }
- }
-
- /*
- * FILES
- */
- /**
- * Returns the size of this file.
- *
- * @see NodeType#NT_FILE
- */
- public static long getFileSize(Node fileNode) {
- try {
- if (!fileNode.isNodeType(NodeType.NT_FILE))
- throw new IllegalArgumentException(fileNode + " must be a file.");
- return getBinarySize(fileNode.getNode(Node.JCR_CONTENT).getProperty(Property.JCR_DATA).getBinary());
- } catch (RepositoryException e) {
- throw new JcrException("Cannot get file size of " + fileNode, e);
- }
- }
-
- /** Returns the size of this {@link Binary}. */
- public static long getBinarySize(Binary binaryArg) {
- try {
- try (Bin binary = new Bin(binaryArg)) {
- return binary.getSize();
- }
- } catch (RepositoryException e) {
- throw new JcrException("Cannot get file size of binary " + binaryArg, e);
- }
- }
-
- // QUERY
- /** Creates a JCR-SQL2 query using {@link MessageFormat}. */
- public static Query createQuery(QueryManager qm, String sql, Object... args) {
- // fix single quotes
- sql = sql.replaceAll("'", "''");
- String query = MessageFormat.format(sql, args);
- try {
- return qm.createQuery(query, Query.JCR_SQL2);
- } catch (RepositoryException e) {
- throw new JcrException("Cannot create JCR-SQL2 query from " + query, e);
- }
- }
-
- /** Executes a JCR-SQL2 query using {@link MessageFormat}. */
- public static NodeIterator executeQuery(QueryManager qm, String sql, Object... args) {
- Query query = createQuery(qm, sql, args);
- try {
- return query.execute().getNodes();
- } catch (RepositoryException e) {
- throw new JcrException("Cannot execute query " + sql + " with arguments " + Arrays.asList(args), e);
- }
- }
-
- /** Executes a JCR-SQL2 query using {@link MessageFormat}. */
- public static NodeIterator executeQuery(Session session, String sql, Object... args) {
- QueryManager queryManager;
- try {
- queryManager = session.getWorkspace().getQueryManager();
- } catch (RepositoryException e) {
- throw new JcrException("Cannot get query manager from session " + session, e);
- }
- return executeQuery(queryManager, sql, args);
- }
-
- /**
- * Executes a JCR-SQL2 query using {@link MessageFormat}, which must return a
- * single node at most.
- *
- * @return the node or <code>null</code> if not found.
- */
- public static Node getNode(QueryManager qm, String sql, Object... args) {
- NodeIterator nit = executeQuery(qm, sql, args);
- if (nit.hasNext()) {
- Node node = nit.nextNode();
- if (nit.hasNext())
- throw new IllegalStateException(
- "Query " + sql + " with arguments " + Arrays.asList(args) + " returned more than one node.");
- return node;
- } else {
- return null;
- }
- }
-
- /**
- * Executes a JCR-SQL2 query using {@link MessageFormat}, which must return a
- * single node at most.
- *
- * @return the node or <code>null</code> if not found.
- */
- public static Node getNode(Session session, String sql, Object... args) {
- QueryManager queryManager;
- try {
- queryManager = session.getWorkspace().getQueryManager();
- } catch (RepositoryException e) {
- throw new JcrException("Cannot get query manager from session " + session, e);
- }
- return getNode(queryManager, sql, args);
- }
-
- /** Singleton. */
- private Jcr() {
-
- }
-}
+++ /dev/null
-package org.argeo.jcr;
-
-import java.security.Principal;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.security.AccessControlManager;
-import javax.jcr.security.Privilege;
-import javax.naming.InvalidNameException;
-import javax.naming.ldap.LdapName;
-
-/** Apply authorizations to a JCR repository. */
-public class JcrAuthorizations implements Runnable {
- // private final static Log log =
- // LogFactory.getLog(JcrAuthorizations.class);
-
- private Repository repository;
- private String workspace = null;
-
- private String securityWorkspace = "security";
-
- /**
- * key := privilege1,privilege2/path/to/node<br/>
- * value := group1,group2,user1
- */
- private Map<String, String> principalPrivileges = new HashMap<String, String>();
-
- public void run() {
- String currentWorkspace = workspace;
- Session session = null;
- try {
- if (workspace != null && workspace.equals("*")) {
- session = repository.login();
- String[] workspaces = session.getWorkspace().getAccessibleWorkspaceNames();
- JcrUtils.logoutQuietly(session);
- for (String wksp : workspaces) {
- currentWorkspace = wksp;
- if (currentWorkspace.equals(securityWorkspace))
- continue;
- session = repository.login(currentWorkspace);
- initAuthorizations(session);
- JcrUtils.logoutQuietly(session);
- }
- } else {
- session = repository.login(workspace);
- initAuthorizations(session);
- }
- } catch (RepositoryException e) {
- JcrUtils.discardQuietly(session);
- throw new JcrException(
- "Cannot set authorizations " + principalPrivileges + " on workspace " + currentWorkspace, e);
- } finally {
- JcrUtils.logoutQuietly(session);
- }
- }
-
- protected void processWorkspace(String workspace) {
- Session session = null;
- try {
- session = repository.login(workspace);
- initAuthorizations(session);
- } catch (RepositoryException e) {
- JcrUtils.discardQuietly(session);
- throw new JcrException(
- "Cannot set authorizations " + principalPrivileges + " on repository " + repository, e);
- } finally {
- JcrUtils.logoutQuietly(session);
- }
- }
-
- /** @deprecated call {@link #run()} instead. */
- @Deprecated
- public void init() {
- run();
- }
-
- protected void initAuthorizations(Session session) throws RepositoryException {
- AccessControlManager acm = session.getAccessControlManager();
-
- for (String privileges : principalPrivileges.keySet()) {
- String path = null;
- int slashIndex = privileges.indexOf('/');
- if (slashIndex == 0) {
- throw new IllegalArgumentException("Privilege " + privileges + " badly formatted it starts with /");
- } else if (slashIndex > 0) {
- path = privileges.substring(slashIndex);
- privileges = privileges.substring(0, slashIndex);
- }
-
- if (path == null)
- path = "/";
-
- List<Privilege> privs = new ArrayList<Privilege>();
- for (String priv : privileges.split(",")) {
- privs.add(acm.privilegeFromName(priv));
- }
-
- String principalNames = principalPrivileges.get(privileges);
- try {
- new LdapName(principalNames);
- // TODO differentiate groups and users ?
- Principal principal = getOrCreatePrincipal(session, principalNames);
- JcrUtils.addPrivileges(session, path, principal, privs);
- } catch (InvalidNameException e) {
- for (String principalName : principalNames.split(",")) {
- Principal principal = getOrCreatePrincipal(session, principalName);
- JcrUtils.addPrivileges(session, path, principal, privs);
- // if (log.isDebugEnabled()) {
- // StringBuffer privBuf = new StringBuffer();
- // for (Privilege priv : privs)
- // privBuf.append(priv.getName());
- // log.debug("Added privileges " + privBuf + " to "
- // + principal.getName() + " on " + path + " in '"
- // + session.getWorkspace().getName() + "'");
- // }
- }
- }
- }
-
- // if (log.isDebugEnabled())
- // log.debug("JCR authorizations applied on '"
- // + session.getWorkspace().getName() + "'");
- }
-
- /**
- * Returns a {@link SimplePrincipal}, does not check whether it exists since
- * such capabilities is not provided by the standard JCR API. Can be
- * overridden to provide smarter handling
- */
- protected Principal getOrCreatePrincipal(Session session, String principalName) throws RepositoryException {
- return new SimplePrincipal(principalName);
- }
-
- // public static void addPrivileges(Session session, Principal principal,
- // String path, List<Privilege> privs) throws RepositoryException {
- // AccessControlManager acm = session.getAccessControlManager();
- // // search for an access control list
- // AccessControlList acl = null;
- // AccessControlPolicyIterator policyIterator = acm
- // .getApplicablePolicies(path);
- // if (policyIterator.hasNext()) {
- // while (policyIterator.hasNext()) {
- // AccessControlPolicy acp = policyIterator
- // .nextAccessControlPolicy();
- // if (acp instanceof AccessControlList)
- // acl = ((AccessControlList) acp);
- // }
- // } else {
- // AccessControlPolicy[] existingPolicies = acm.getPolicies(path);
- // for (AccessControlPolicy acp : existingPolicies) {
- // if (acp instanceof AccessControlList)
- // acl = ((AccessControlList) acp);
- // }
- // }
- //
- // if (acl != null) {
- // acl.addAccessControlEntry(principal,
- // privs.toArray(new Privilege[privs.size()]));
- // acm.setPolicy(path, acl);
- // session.save();
- // if (log.isDebugEnabled()) {
- // StringBuffer buf = new StringBuffer("");
- // for (int i = 0; i < privs.size(); i++) {
- // if (i != 0)
- // buf.append(',');
- // buf.append(privs.get(i).getName());
- // }
- // log.debug("Added privilege(s) '" + buf + "' to '"
- // + principal.getName() + "' on " + path
- // + " from workspace '"
- // + session.getWorkspace().getName() + "'");
- // }
- // } else {
- // throw new ArgeoJcrException("Don't know how to apply privileges "
- // + privs + " to " + principal + " on " + path
- // + " from workspace '" + session.getWorkspace().getName()
- // + "'");
- // }
- // }
-
- @Deprecated
- public void setGroupPrivileges(Map<String, String> groupPrivileges) {
- this.principalPrivileges = groupPrivileges;
- }
-
- public void setPrincipalPrivileges(Map<String, String> principalPrivileges) {
- this.principalPrivileges = principalPrivileges;
- }
-
- public void setRepository(Repository repository) {
- this.repository = repository;
- }
-
- public void setWorkspace(String workspace) {
- this.workspace = workspace;
- }
-
- public void setSecurityWorkspace(String securityWorkspace) {
- this.securityWorkspace = securityWorkspace;
- }
-
-}
+++ /dev/null
-package org.argeo.jcr;
-
-import java.util.function.Function;
-
-import javax.jcr.Session;
-
-/** An arbitrary execution on a JCR session, optionally returning a result. */
-@FunctionalInterface
-public interface JcrCallback extends Function<Session, Object> {
- /** @deprecated Use {@link #apply(Session)} instead. */
- @Deprecated
- public default Object execute(Session session) {
- return apply(session);
- }
-}
+++ /dev/null
-package org.argeo.jcr;
-
-import javax.jcr.RepositoryException;
-
-/**
- * Wraps a {@link RepositoryException} in a {@link RuntimeException}.
- */
-public class JcrException extends IllegalStateException {
- private static final long serialVersionUID = -4530350094877964989L;
-
- public JcrException(String message, RepositoryException e) {
- super(message, e);
- }
-
- public JcrException(RepositoryException e) {
- super(e);
- }
-
- public RepositoryException getRepositoryCause() {
- return (RepositoryException) getCause();
- }
-}
+++ /dev/null
-package org.argeo.jcr;
-
-
-/**
- * Simple monitor abstraction. Inspired by Eclipse IProgressMOnitor, but without
- * dependency to it.
- */
-public interface JcrMonitor {
- /**
- * Constant indicating an unknown amount of work.
- */
- public final static int UNKNOWN = -1;
-
- /**
- * Notifies that the main task is beginning. This must only be called once
- * on a given progress monitor instance.
- *
- * @param name
- * the name (or description) of the main task
- * @param totalWork
- * the total number of work units into which the main task is
- * been subdivided. If the value is <code>UNKNOWN</code> the
- * implementation is free to indicate progress in a way which
- * doesn't require the total number of work units in advance.
- */
- public void beginTask(String name, int totalWork);
-
- /**
- * Notifies that the work is done; that is, either the main task is
- * completed or the user canceled it. This method may be called more than
- * once (implementations should be prepared to handle this case).
- */
- public void done();
-
- /**
- * Returns whether cancelation of current operation has been requested.
- * Long-running operations should poll to see if cancelation has been
- * requested.
- *
- * @return <code>true</code> if cancellation has been requested, and
- * <code>false</code> otherwise
- * @see #setCanceled(boolean)
- */
- public boolean isCanceled();
-
- /**
- * Sets the cancel state to the given value.
- *
- * @param value
- * <code>true</code> indicates that cancelation has been
- * requested (but not necessarily acknowledged);
- * <code>false</code> clears this flag
- * @see #isCanceled()
- */
- public void setCanceled(boolean value);
-
- /**
- * Sets the task name to the given value. This method is used to restore the
- * task label after a nested operation was executed. Normally there is no
- * need for clients to call this method.
- *
- * @param name
- * the name (or description) of the main task
- * @see #beginTask(java.lang.String, int)
- */
- public void setTaskName(String name);
-
- /**
- * Notifies that a subtask of the main task is beginning. Subtasks are
- * optional; the main task might not have subtasks.
- *
- * @param name
- * the name (or description) of the subtask
- */
- public void subTask(String name);
-
- /**
- * Notifies that a given number of work unit of the main task has been
- * completed. Note that this amount represents an installment, as opposed to
- * a cumulative amount of work done to date.
- *
- * @param work
- * a non-negative number of work units just completed
- */
- public void worked(int work);
-
-}
+++ /dev/null
-package org.argeo.jcr;
-
-import java.io.InputStream;
-import java.math.BigDecimal;
-import java.util.Arrays;
-import java.util.Calendar;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import javax.jcr.Binary;
-import javax.jcr.Credentials;
-import javax.jcr.LoginException;
-import javax.jcr.NoSuchWorkspaceException;
-import javax.jcr.PropertyType;
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.Value;
-import javax.jcr.ValueFormatException;
-
-/**
- * Wrapper around a JCR repository which allows to simplify configuration and
- * intercept some actions. It exposes itself as a {@link Repository}.
- */
-public abstract class JcrRepositoryWrapper implements Repository {
- // private final static Log log = LogFactory
- // .getLog(JcrRepositoryWrapper.class);
-
- // wrapped repository
- private Repository repository;
-
- private Map<String, String> additionalDescriptors = new HashMap<>();
-
- private Boolean autocreateWorkspaces = false;
-
- public JcrRepositoryWrapper(Repository repository) {
- setRepository(repository);
- }
-
- /**
- * Empty constructor
- */
- public JcrRepositoryWrapper() {
- }
-
- // /** Initializes */
- // public void init() {
- // }
- //
- // /** Shutdown the repository */
- // public void destroy() throws Exception {
- // }
-
- protected void putDescriptor(String key, String value) {
- if (Arrays.asList(getRepository().getDescriptorKeys()).contains(key))
- throw new IllegalArgumentException("Descriptor key " + key + " is already defined in wrapped repository");
- if (value == null)
- additionalDescriptors.remove(key);
- else
- additionalDescriptors.put(key, value);
- }
-
- /*
- * DELEGATED JCR REPOSITORY METHODS
- */
-
- public String getDescriptor(String key) {
- if (additionalDescriptors.containsKey(key))
- return additionalDescriptors.get(key);
- return getRepository().getDescriptor(key);
- }
-
- public String[] getDescriptorKeys() {
- if (additionalDescriptors.size() == 0)
- return getRepository().getDescriptorKeys();
- List<String> keys = Arrays.asList(getRepository().getDescriptorKeys());
- keys.addAll(additionalDescriptors.keySet());
- return keys.toArray(new String[keys.size()]);
- }
-
- /** Central login method */
- public Session login(Credentials credentials, String workspaceName)
- throws LoginException, NoSuchWorkspaceException, RepositoryException {
- Session session;
- try {
- session = getRepository(workspaceName).login(credentials, workspaceName);
- } catch (NoSuchWorkspaceException e) {
- if (autocreateWorkspaces && workspaceName != null)
- session = createWorkspaceAndLogsIn(credentials, workspaceName);
- else
- throw e;
- }
- processNewSession(session, workspaceName);
- return session;
- }
-
- public Session login() throws LoginException, RepositoryException {
- return login(null, null);
- }
-
- public Session login(Credentials credentials) throws LoginException, RepositoryException {
- return login(credentials, null);
- }
-
- public Session login(String workspaceName) throws LoginException, NoSuchWorkspaceException, RepositoryException {
- return login(null, workspaceName);
- }
-
- /** Called after a session has been created, does nothing by default. */
- protected void processNewSession(Session session, String workspaceName) {
- }
-
- /**
- * Wraps access to the repository, making sure it is available.
- *
- * @deprecated Use {@link #getDefaultRepository()} instead.
- */
- @Deprecated
- protected synchronized Repository getRepository() {
- return getDefaultRepository();
- }
-
- protected synchronized Repository getDefaultRepository() {
- return repository;
- }
-
- protected synchronized Repository getRepository(String workspaceName) {
- return getDefaultRepository();
- }
-
- /**
- * Logs in to the default workspace, creates the required workspace, logs out,
- * logs in to the required workspace.
- */
- protected Session createWorkspaceAndLogsIn(Credentials credentials, String workspaceName)
- throws RepositoryException {
- if (workspaceName == null)
- throw new IllegalArgumentException("No workspace specified.");
- Session session = getRepository(workspaceName).login(credentials);
- session.getWorkspace().createWorkspace(workspaceName);
- session.logout();
- return getRepository(workspaceName).login(credentials, workspaceName);
- }
-
- public boolean isStandardDescriptor(String key) {
- return getRepository().isStandardDescriptor(key);
- }
-
- public boolean isSingleValueDescriptor(String key) {
- if (additionalDescriptors.containsKey(key))
- return true;
- return getRepository().isSingleValueDescriptor(key);
- }
-
- public Value getDescriptorValue(String key) {
- if (additionalDescriptors.containsKey(key))
- return new StrValue(additionalDescriptors.get(key));
- return getRepository().getDescriptorValue(key);
- }
-
- public Value[] getDescriptorValues(String key) {
- return getRepository().getDescriptorValues(key);
- }
-
- public synchronized void setRepository(Repository repository) {
- this.repository = repository;
- }
-
- public void setAutocreateWorkspaces(Boolean autocreateWorkspaces) {
- this.autocreateWorkspaces = autocreateWorkspaces;
- }
-
- protected static class StrValue implements Value {
- private final String str;
-
- public StrValue(String str) {
- this.str = str;
- }
-
- @Override
- public String getString() throws ValueFormatException, IllegalStateException, RepositoryException {
- return str;
- }
-
- @Override
- public InputStream getStream() throws RepositoryException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public Binary getBinary() throws RepositoryException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public long getLong() throws ValueFormatException, RepositoryException {
- try {
- return Long.parseLong(str);
- } catch (NumberFormatException e) {
- throw new ValueFormatException("Cannot convert", e);
- }
- }
-
- @Override
- public double getDouble() throws ValueFormatException, RepositoryException {
- try {
- return Double.parseDouble(str);
- } catch (NumberFormatException e) {
- throw new ValueFormatException("Cannot convert", e);
- }
- }
-
- @Override
- public BigDecimal getDecimal() throws ValueFormatException, RepositoryException {
- try {
- return new BigDecimal(str);
- } catch (NumberFormatException e) {
- throw new ValueFormatException("Cannot convert", e);
- }
- }
-
- @Override
- public Calendar getDate() throws ValueFormatException, RepositoryException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public boolean getBoolean() throws ValueFormatException, RepositoryException {
- try {
- return Boolean.parseBoolean(str);
- } catch (NumberFormatException e) {
- throw new ValueFormatException("Cannot convert", e);
- }
- }
-
- @Override
- public int getType() {
- return PropertyType.STRING;
- }
-
- }
-
-}
+++ /dev/null
-package org.argeo.jcr;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.URL;
-import java.net.URLConnection;
-import java.net.URLStreamHandler;
-
-import javax.jcr.Item;
-import javax.jcr.Node;
-import javax.jcr.Property;
-import javax.jcr.PropertyType;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.nodetype.NodeType;
-
-/** URL stream handler able to deal with nt:file node and properties. NOT FINISHED */
-public class JcrUrlStreamHandler extends URLStreamHandler {
- private final Session session;
-
- public JcrUrlStreamHandler(Session session) {
- this.session = session;
- }
-
- @Override
- protected URLConnection openConnection(final URL u) throws IOException {
- // TODO Auto-generated method stub
- return new URLConnection(u) {
-
- @Override
- public void connect() throws IOException {
- String itemPath = u.getPath();
- try {
- if (!session.itemExists(itemPath))
- throw new IOException("No item under " + itemPath);
-
- Item item = session.getItem(u.getPath());
- if (item.isNode()) {
- // this should be a nt:file node
- Node node = (Node) item;
- if (!node.getPrimaryNodeType().isNodeType(
- NodeType.NT_FILE))
- throw new IOException("Node " + node + " is not a "
- + NodeType.NT_FILE);
-
- } else {
- Property property = (Property) item;
- if(property.getType()==PropertyType.BINARY){
- //Binary binary = property.getBinary();
-
- }
- }
- } catch (RepositoryException e) {
- IOException ioe = new IOException(
- "Unexpected JCR exception");
- ioe.initCause(e);
- throw ioe;
- }
- }
-
- @Override
- public InputStream getInputStream() throws IOException {
- // TODO Auto-generated method stub
- return super.getInputStream();
- }
-
- };
- }
-
-}
+++ /dev/null
-package org.argeo.jcr;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.PipedInputStream;
-import java.io.PipedOutputStream;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.security.Principal;
-import java.text.DateFormat;
-import java.text.ParseException;
-import java.time.Instant;
-import java.util.ArrayList;
-import java.util.Calendar;
-import java.util.Collections;
-import java.util.Date;
-import java.util.GregorianCalendar;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.TreeMap;
-
-import javax.jcr.Binary;
-import javax.jcr.Credentials;
-import javax.jcr.ImportUUIDBehavior;
-import javax.jcr.NamespaceRegistry;
-import javax.jcr.NoSuchWorkspaceException;
-import javax.jcr.Node;
-import javax.jcr.NodeIterator;
-import javax.jcr.Property;
-import javax.jcr.PropertyIterator;
-import javax.jcr.PropertyType;
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.Value;
-import javax.jcr.Workspace;
-import javax.jcr.nodetype.NoSuchNodeTypeException;
-import javax.jcr.nodetype.NodeType;
-import javax.jcr.observation.EventListener;
-import javax.jcr.query.Query;
-import javax.jcr.query.QueryResult;
-import javax.jcr.security.AccessControlEntry;
-import javax.jcr.security.AccessControlList;
-import javax.jcr.security.AccessControlManager;
-import javax.jcr.security.AccessControlPolicy;
-import javax.jcr.security.AccessControlPolicyIterator;
-import javax.jcr.security.Privilege;
-
-import org.apache.commons.io.IOUtils;
-
-/** Utility methods to simplify common JCR operations. */
-public class JcrUtils {
-
-// final private static Log log = LogFactory.getLog(JcrUtils.class);
-
- /**
- * Not complete yet. See
- * http://www.day.com/specs/jcr/2.0/3_Repository_Model.html#3.2.2%20Local
- * %20Names
- */
- public final static char[] INVALID_NAME_CHARACTERS = { '/', ':', '[', ']', '|', '*', /* invalid for XML: */ '<',
- '>', '&' };
-
- /** Prevents instantiation */
- private JcrUtils() {
- }
-
- /**
- * Queries one single node.
- *
- * @return one single node or null if none was found
- * @throws JcrException if more than one node was found
- */
- public static Node querySingleNode(Query query) {
- NodeIterator nodeIterator;
- try {
- QueryResult queryResult = query.execute();
- nodeIterator = queryResult.getNodes();
- } catch (RepositoryException e) {
- throw new JcrException("Cannot execute query " + query, e);
- }
- Node node;
- if (nodeIterator.hasNext())
- node = nodeIterator.nextNode();
- else
- return null;
-
- if (nodeIterator.hasNext())
- throw new IllegalArgumentException("Query returned more than one node.");
- return node;
- }
-
- /** Retrieves the node name from the provided path */
- public static String nodeNameFromPath(String path) {
- if (path.equals("/"))
- return "";
- if (path.charAt(0) != '/')
- throw new IllegalArgumentException("Path " + path + " must start with a '/'");
- String pathT = path;
- if (pathT.charAt(pathT.length() - 1) == '/')
- pathT = pathT.substring(0, pathT.length() - 2);
-
- int index = pathT.lastIndexOf('/');
- return pathT.substring(index + 1);
- }
-
- /** Retrieves the parent path of the provided path */
- public static String parentPath(String path) {
- if (path.equals("/"))
- throw new IllegalArgumentException("Root path '/' has no parent path");
- if (path.charAt(0) != '/')
- throw new IllegalArgumentException("Path " + path + " must start with a '/'");
- String pathT = path;
- if (pathT.charAt(pathT.length() - 1) == '/')
- pathT = pathT.substring(0, pathT.length() - 2);
-
- int index = pathT.lastIndexOf('/');
- return pathT.substring(0, index);
- }
-
- /** The provided data as a path ('/' at the end, not the beginning) */
- public static String dateAsPath(Calendar cal) {
- return dateAsPath(cal, false);
- }
-
- /**
- * Creates a deep path based on a URL:
- * http://subdomain.example.com/to/content?args becomes
- * com/example/subdomain/to/content
- */
- public static String urlAsPath(String url) {
- try {
- URL u = new URL(url);
- StringBuffer path = new StringBuffer(url.length());
- // invert host
- path.append(hostAsPath(u.getHost()));
- // we don't put port since it may not always be there and may change
- path.append(u.getPath());
- return path.toString();
- } catch (MalformedURLException e) {
- throw new IllegalArgumentException("Cannot generate URL path for " + url, e);
- }
- }
-
- /** Set the {@link NodeType#NT_ADDRESS} properties based on this URL. */
- public static void urlToAddressProperties(Node node, String url) {
- try {
- URL u = new URL(url);
- node.setProperty(Property.JCR_PROTOCOL, u.getProtocol());
- node.setProperty(Property.JCR_HOST, u.getHost());
- node.setProperty(Property.JCR_PORT, Integer.toString(u.getPort()));
- node.setProperty(Property.JCR_PATH, normalizePath(u.getPath()));
- } catch (RepositoryException e) {
- throw new JcrException("Cannot set URL " + url + " as nt:address properties", e);
- } catch (MalformedURLException e) {
- throw new IllegalArgumentException("Cannot set URL " + url + " as nt:address properties", e);
- }
- }
-
- /** Build URL based on the {@link NodeType#NT_ADDRESS} properties. */
- public static String urlFromAddressProperties(Node node) {
- try {
- URL u = new URL(node.getProperty(Property.JCR_PROTOCOL).getString(),
- node.getProperty(Property.JCR_HOST).getString(),
- (int) node.getProperty(Property.JCR_PORT).getLong(),
- node.getProperty(Property.JCR_PATH).getString());
- return u.toString();
- } catch (RepositoryException e) {
- throw new JcrException("Cannot get URL from nt:address properties of " + node, e);
- } catch (MalformedURLException e) {
- throw new IllegalArgumentException("Cannot get URL from nt:address properties of " + node, e);
- }
- }
-
- /*
- * PATH UTILITIES
- */
-
- /**
- * Make sure that: starts with '/', do not end with '/', do not have '//'
- */
- public static String normalizePath(String path) {
- List<String> tokens = tokenize(path);
- StringBuffer buf = new StringBuffer(path.length());
- for (String token : tokens) {
- buf.append('/');
- buf.append(token);
- }
- return buf.toString();
- }
-
- /**
- * Creates a path from a FQDN, inverting the order of the component:
- * www.argeo.org becomes org.argeo.www
- */
- public static String hostAsPath(String host) {
- StringBuffer path = new StringBuffer(host.length());
- String[] hostTokens = host.split("\\.");
- for (int i = hostTokens.length - 1; i >= 0; i--) {
- path.append(hostTokens[i]);
- if (i != 0)
- path.append('/');
- }
- return path.toString();
- }
-
- /**
- * Creates a path from a UUID (e.g. 6ebda899-217d-4bf1-abe4-2839085c8f3c becomes
- * 6ebda899-217d/4bf1/abe4/2839085c8f3c/). '/' at the end, not the beginning
- */
- public static String uuidAsPath(String uuid) {
- StringBuffer path = new StringBuffer(uuid.length());
- String[] tokens = uuid.split("-");
- for (int i = 0; i < tokens.length; i++) {
- path.append(tokens[i]);
- if (i != 0)
- path.append('/');
- }
- return path.toString();
- }
-
- /**
- * The provided data as a path ('/' at the end, not the beginning)
- *
- * @param cal the date
- * @param addHour whether to add hour as well
- */
- public static String dateAsPath(Calendar cal, Boolean addHour) {
- StringBuffer buf = new StringBuffer(14);
- buf.append('Y');
- buf.append(cal.get(Calendar.YEAR));
- buf.append('/');
-
- int month = cal.get(Calendar.MONTH) + 1;
- buf.append('M');
- if (month < 10)
- buf.append(0);
- buf.append(month);
- buf.append('/');
-
- int day = cal.get(Calendar.DAY_OF_MONTH);
- buf.append('D');
- if (day < 10)
- buf.append(0);
- buf.append(day);
- buf.append('/');
-
- if (addHour) {
- int hour = cal.get(Calendar.HOUR_OF_DAY);
- buf.append('H');
- if (hour < 10)
- buf.append(0);
- buf.append(hour);
- buf.append('/');
- }
- return buf.toString();
-
- }
-
- /** Converts in one call a string into a gregorian calendar. */
- public static Calendar parseCalendar(DateFormat dateFormat, String value) {
- try {
- Date date = dateFormat.parse(value);
- Calendar calendar = new GregorianCalendar();
- calendar.setTime(date);
- return calendar;
- } catch (ParseException e) {
- throw new IllegalArgumentException("Cannot parse " + value + " with date format " + dateFormat, e);
- }
-
- }
-
- /** The last element of a path. */
- public static String lastPathElement(String path) {
- if (path.charAt(path.length() - 1) == '/')
- throw new IllegalArgumentException("Path " + path + " cannot end with '/'");
- int index = path.lastIndexOf('/');
- if (index < 0)
- return path;
- return path.substring(index + 1);
- }
-
- /**
- * Call {@link Node#getName()} without exceptions (useful in super
- * constructors).
- */
- public static String getNameQuietly(Node node) {
- try {
- return node.getName();
- } catch (RepositoryException e) {
- throw new JcrException("Cannot get name from " + node, e);
- }
- }
-
- /**
- * Call {@link Node#getProperty(String)} without exceptions (useful in super
- * constructors).
- */
- public static String getStringPropertyQuietly(Node node, String propertyName) {
- try {
- return node.getProperty(propertyName).getString();
- } catch (RepositoryException e) {
- throw new JcrException("Cannot get name from " + node, e);
- }
- }
-
-// /**
-// * Routine that get the child with this name, adding it if it does not already
-// * exist
-// */
-// public static Node getOrAdd(Node parent, String name, String primaryNodeType) throws RepositoryException {
-// return parent.hasNode(name) ? parent.getNode(name) : parent.addNode(name, primaryNodeType);
-// }
-
- /**
- * Routine that get the child with this name, adding it if it does not already
- * exist
- */
- public static Node getOrAdd(Node parent, String name, String primaryNodeType, String... mixinNodeTypes)
- throws RepositoryException {
- Node node;
- if (parent.hasNode(name)) {
- node = parent.getNode(name);
- if (primaryNodeType != null && !node.isNodeType(primaryNodeType))
- throw new IllegalArgumentException("Node " + node + " exists but is of primary node type "
- + node.getPrimaryNodeType().getName() + ", not " + primaryNodeType);
- for (String mixin : mixinNodeTypes) {
- if (!node.isNodeType(mixin))
- node.addMixin(mixin);
- }
- return node;
- } else {
- node = primaryNodeType != null ? parent.addNode(name, primaryNodeType) : parent.addNode(name);
- for (String mixin : mixinNodeTypes) {
- node.addMixin(mixin);
- }
- return node;
- }
- }
-
- /**
- * Routine that get the child with this name, adding it if it does not already
- * exist
- */
- public static Node getOrAdd(Node parent, String name) throws RepositoryException {
- return parent.hasNode(name) ? parent.getNode(name) : parent.addNode(name);
- }
-
- /** Convert a {@link NodeIterator} to a list of {@link Node} */
- public static List<Node> nodeIteratorToList(NodeIterator nodeIterator) {
- List<Node> nodes = new ArrayList<Node>();
- while (nodeIterator.hasNext()) {
- nodes.add(nodeIterator.nextNode());
- }
- return nodes;
- }
-
- /*
- * PROPERTIES
- */
-
- /**
- * Concisely get the string value of a property or null if this node doesn't
- * have this property
- */
- public static String get(Node node, String propertyName) {
- try {
- if (!node.hasProperty(propertyName))
- return null;
- return node.getProperty(propertyName).getString();
- } catch (RepositoryException e) {
- throw new JcrException("Cannot get property " + propertyName + " of " + node, e);
- }
- }
-
- /** Concisely get the path of the given node. */
- public static String getPath(Node node) {
- try {
- return node.getPath();
- } catch (RepositoryException e) {
- throw new JcrException("Cannot get path of " + node, e);
- }
- }
-
- /** Concisely get the boolean value of a property */
- public static Boolean check(Node node, String propertyName) {
- try {
- return node.getProperty(propertyName).getBoolean();
- } catch (RepositoryException e) {
- throw new JcrException("Cannot get property " + propertyName + " of " + node, e);
- }
- }
-
- /** Concisely get the bytes array value of a property */
- public static byte[] getBytes(Node node, String propertyName) {
- try {
- return getBinaryAsBytes(node.getProperty(propertyName));
- } catch (RepositoryException e) {
- throw new JcrException("Cannot get property " + propertyName + " of " + node, e);
- }
- }
-
- /*
- * MKDIRS
- */
-
- /**
- * Create sub nodes relative to a parent node
- */
- public static Node mkdirs(Node parentNode, String relativePath) {
- return mkdirs(parentNode, relativePath, null, null);
- }
-
- /**
- * Create sub nodes relative to a parent node
- *
- * @param nodeType the type of the leaf node
- */
- public static Node mkdirs(Node parentNode, String relativePath, String nodeType) {
- return mkdirs(parentNode, relativePath, nodeType, null);
- }
-
- /**
- * Create sub nodes relative to a parent node
- *
- * @param nodeType the type of the leaf node
- */
- public static Node mkdirs(Node parentNode, String relativePath, String nodeType, String intermediaryNodeType) {
- List<String> tokens = tokenize(relativePath);
- Node currParent = parentNode;
- try {
- for (int i = 0; i < tokens.size(); i++) {
- String name = tokens.get(i);
- if (currParent.hasNode(name)) {
- currParent = currParent.getNode(name);
- } else {
- if (i != (tokens.size() - 1)) {// intermediary
- currParent = currParent.addNode(name, intermediaryNodeType);
- } else {// leaf
- currParent = currParent.addNode(name, nodeType);
- }
- }
- }
- return currParent;
- } catch (RepositoryException e) {
- throw new JcrException("Cannot mkdirs relative path " + relativePath + " from " + parentNode, e);
- }
- }
-
- /**
- * Synchronized and save is performed, to avoid race conditions in initializers
- * leading to duplicate nodes.
- */
- public synchronized static Node mkdirsSafe(Session session, String path, String type) {
- try {
- if (session.hasPendingChanges())
- throw new IllegalStateException("Session has pending changes, save them first.");
- Node node = mkdirs(session, path, type);
- session.save();
- return node;
- } catch (RepositoryException e) {
- discardQuietly(session);
- throw new JcrException("Cannot safely make directories", e);
- }
- }
-
- public synchronized static Node mkdirsSafe(Session session, String path) {
- return mkdirsSafe(session, path, null);
- }
-
- /** Creates the nodes making path, if they don't exist. */
- public static Node mkdirs(Session session, String path) {
- return mkdirs(session, path, null, null, false);
- }
-
- /**
- * @param type the type of the leaf node
- */
- public static Node mkdirs(Session session, String path, String type) {
- return mkdirs(session, path, type, null, false);
- }
-
- /**
- * Creates the nodes making path, if they don't exist. This is up to the caller
- * to save the session. Use with caution since it can create duplicate nodes if
- * used concurrently. Requires read access to the root node of the workspace.
- */
- public static Node mkdirs(Session session, String path, String type, String intermediaryNodeType,
- Boolean versioning) {
- try {
- if (path.equals("/"))
- return session.getRootNode();
-
- if (session.itemExists(path)) {
- Node node = session.getNode(path);
- // check type
- if (type != null && !node.isNodeType(type) && !node.getPath().equals("/"))
- throw new IllegalArgumentException("Node " + node + " exists but is of type "
- + node.getPrimaryNodeType().getName() + " not of type " + type);
- // TODO: check versioning
- return node;
- }
-
- // StringBuffer current = new StringBuffer("/");
- // Node currentNode = session.getRootNode();
-
- Node currentNode = findClosestExistingParent(session, path);
- String closestExistingParentPath = currentNode.getPath();
- StringBuffer current = new StringBuffer(closestExistingParentPath);
- if (!closestExistingParentPath.endsWith("/"))
- current.append('/');
- Iterator<String> it = tokenize(path.substring(closestExistingParentPath.length())).iterator();
- while (it.hasNext()) {
- String part = it.next();
- current.append(part).append('/');
- if (!session.itemExists(current.toString())) {
- if (!it.hasNext() && type != null)
- currentNode = currentNode.addNode(part, type);
- else if (it.hasNext() && intermediaryNodeType != null)
- currentNode = currentNode.addNode(part, intermediaryNodeType);
- else
- currentNode = currentNode.addNode(part);
- if (versioning)
- currentNode.addMixin(NodeType.MIX_VERSIONABLE);
-// if (log.isTraceEnabled())
-// log.debug("Added folder " + part + " as " + current);
- } else {
- currentNode = (Node) session.getItem(current.toString());
- }
- }
- return currentNode;
- } catch (RepositoryException e) {
- discardQuietly(session);
- throw new JcrException("Cannot mkdirs " + path, e);
- } finally {
- }
- }
-
- private static Node findClosestExistingParent(Session session, String path) throws RepositoryException {
- int idx = path.lastIndexOf('/');
- if (idx == 0)
- return session.getRootNode();
- String parentPath = path.substring(0, idx);
- if (session.itemExists(parentPath))
- return session.getNode(parentPath);
- else
- return findClosestExistingParent(session, parentPath);
- }
-
- /** Convert a path to the list of its tokens */
- public static List<String> tokenize(String path) {
- List<String> tokens = new ArrayList<String>();
- boolean optimized = false;
- if (!optimized) {
- String[] rawTokens = path.split("/");
- for (String token : rawTokens) {
- if (!token.equals(""))
- tokens.add(token);
- }
- } else {
- StringBuffer curr = new StringBuffer();
- char[] arr = path.toCharArray();
- chars: for (int i = 0; i < arr.length; i++) {
- char c = arr[i];
- if (c == '/') {
- if (i == 0 || (i == arr.length - 1))
- continue chars;
- if (curr.length() > 0) {
- tokens.add(curr.toString());
- curr = new StringBuffer();
- }
- } else
- curr.append(c);
- }
- if (curr.length() > 0) {
- tokens.add(curr.toString());
- curr = new StringBuffer();
- }
- }
- return Collections.unmodifiableList(tokens);
- }
-
- // /**
- // * use {@link #mkdirs(Session, String, String, String, Boolean)} instead.
- // *
- // * @deprecated
- // */
- // @Deprecated
- // public static Node mkdirs(Session session, String path, String type,
- // Boolean versioning) {
- // return mkdirs(session, path, type, type, false);
- // }
-
- /**
- * Safe and repository implementation independent registration of a namespace.
- */
- public static void registerNamespaceSafely(Session session, String prefix, String uri) {
- try {
- registerNamespaceSafely(session.getWorkspace().getNamespaceRegistry(), prefix, uri);
- } catch (RepositoryException e) {
- throw new JcrException("Cannot find namespace registry", e);
- }
- }
-
- /**
- * Safe and repository implementation independent registration of a namespace.
- */
- public static void registerNamespaceSafely(NamespaceRegistry nr, String prefix, String uri) {
- try {
- String[] prefixes = nr.getPrefixes();
- for (String pref : prefixes)
- if (pref.equals(prefix)) {
- String registeredUri = nr.getURI(pref);
- if (!registeredUri.equals(uri))
- throw new IllegalArgumentException("Prefix " + pref + " already registered for URI "
- + registeredUri + " which is different from provided URI " + uri);
- else
- return;// skip
- }
- nr.registerNamespace(prefix, uri);
- } catch (RepositoryException e) {
- throw new JcrException("Cannot register namespace " + uri + " under prefix " + prefix, e);
- }
- }
-
-// /** Recursively outputs the contents of the given node. */
-// public static void debug(Node node) {
-// debug(node, log);
-// }
-//
-// /** Recursively outputs the contents of the given node. */
-// public static void debug(Node node, Log log) {
-// try {
-// // First output the node path
-// log.debug(node.getPath());
-// // Skip the virtual (and large!) jcr:system subtree
-// if (node.getName().equals("jcr:system")) {
-// return;
-// }
-//
-// // Then the children nodes (recursive)
-// NodeIterator it = node.getNodes();
-// while (it.hasNext()) {
-// Node childNode = it.nextNode();
-// debug(childNode, log);
-// }
-//
-// // Then output the properties
-// PropertyIterator properties = node.getProperties();
-// // log.debug("Property are : ");
-//
-// properties: while (properties.hasNext()) {
-// Property property = properties.nextProperty();
-// if (property.getType() == PropertyType.BINARY)
-// continue properties;// skip
-// if (property.getDefinition().isMultiple()) {
-// // A multi-valued property, print all values
-// Value[] values = property.getValues();
-// for (int i = 0; i < values.length; i++) {
-// log.debug(property.getPath() + "=" + values[i].getString());
-// }
-// } else {
-// // A single-valued property
-// log.debug(property.getPath() + "=" + property.getString());
-// }
-// }
-// } catch (Exception e) {
-// log.error("Could not debug " + node, e);
-// }
-//
-// }
-
-// /** Logs the effective access control policies */
-// public static void logEffectiveAccessPolicies(Node node) {
-// try {
-// logEffectiveAccessPolicies(node.getSession(), node.getPath());
-// } catch (RepositoryException e) {
-// log.error("Cannot log effective access policies of " + node, e);
-// }
-// }
-//
-// /** Logs the effective access control policies */
-// public static void logEffectiveAccessPolicies(Session session, String path) {
-// if (!log.isDebugEnabled())
-// return;
-//
-// try {
-// AccessControlPolicy[] effectivePolicies = session.getAccessControlManager().getEffectivePolicies(path);
-// if (effectivePolicies.length > 0) {
-// for (AccessControlPolicy policy : effectivePolicies) {
-// if (policy instanceof AccessControlList) {
-// AccessControlList acl = (AccessControlList) policy;
-// log.debug("Access control list for " + path + "\n" + accessControlListSummary(acl));
-// }
-// }
-// } else {
-// log.debug("No effective access control policy for " + path);
-// }
-// } catch (RepositoryException e) {
-// log.error("Cannot log effective access policies of " + path, e);
-// }
-// }
-
- /** Returns a human-readable summary of this access control list. */
- public static String accessControlListSummary(AccessControlList acl) {
- StringBuffer buf = new StringBuffer("");
- try {
- for (AccessControlEntry ace : acl.getAccessControlEntries()) {
- buf.append('\t').append(ace.getPrincipal().getName()).append('\n');
- for (Privilege priv : ace.getPrivileges())
- buf.append("\t\t").append(priv.getName()).append('\n');
- }
- return buf.toString();
- } catch (RepositoryException e) {
- throw new JcrException("Cannot write summary of " + acl, e);
- }
- }
-
- /** Copy the whole workspace via a system view XML. */
- public static void copyWorkspaceXml(Session fromSession, Session toSession) {
- Workspace fromWorkspace = fromSession.getWorkspace();
- Workspace toWorkspace = toSession.getWorkspace();
- String errorMsg = "Cannot copy workspace " + fromWorkspace + " to " + toWorkspace + " via XML.";
-
- try (PipedInputStream in = new PipedInputStream(1024 * 1024);) {
- new Thread(() -> {
- try (PipedOutputStream out = new PipedOutputStream(in)) {
- fromSession.exportSystemView("/", out, false, false);
- out.flush();
- } catch (IOException e) {
- throw new RuntimeException(errorMsg, e);
- } catch (RepositoryException e) {
- throw new JcrException(errorMsg, e);
- }
- }, "Copy workspace" + fromWorkspace + " to " + toWorkspace).start();
-
- toSession.importXML("/", in, ImportUUIDBehavior.IMPORT_UUID_COLLISION_REPLACE_EXISTING);
- toSession.save();
- } catch (IOException e) {
- throw new RuntimeException(errorMsg, e);
- } catch (RepositoryException e) {
- throw new JcrException(errorMsg, e);
- }
- }
-
- /**
- * Copies recursively the content of a node to another one. Do NOT copy the
- * property values of {@link NodeType#MIX_CREATED} and
- * {@link NodeType#MIX_LAST_MODIFIED}, but update the
- * {@link Property#JCR_LAST_MODIFIED} and {@link Property#JCR_LAST_MODIFIED_BY}
- * properties if the target node has the {@link NodeType#MIX_LAST_MODIFIED}
- * mixin.
- */
- public static void copy(Node fromNode, Node toNode) {
- try {
- if (toNode.getDefinition().isProtected())
- return;
-
- // add mixins
- for (NodeType mixinType : fromNode.getMixinNodeTypes()) {
- try {
- toNode.addMixin(mixinType.getName());
- } catch (NoSuchNodeTypeException e) {
- // ignore unknown mixins
- // TODO log it
- }
- }
-
- // process properties
- PropertyIterator pit = fromNode.getProperties();
- properties: while (pit.hasNext()) {
- Property fromProperty = pit.nextProperty();
- String propertyName = fromProperty.getName();
- if (toNode.hasProperty(propertyName) && toNode.getProperty(propertyName).getDefinition().isProtected())
- continue properties;
-
- if (fromProperty.getDefinition().isProtected())
- continue properties;
-
- if (propertyName.equals("jcr:created") || propertyName.equals("jcr:createdBy")
- || propertyName.equals("jcr:lastModified") || propertyName.equals("jcr:lastModifiedBy"))
- continue properties;
-
- if (fromProperty.isMultiple()) {
- toNode.setProperty(propertyName, fromProperty.getValues());
- } else {
- toNode.setProperty(propertyName, fromProperty.getValue());
- }
- }
-
- // update jcr:lastModified and jcr:lastModifiedBy in toNode in case
- // they existed, before adding the mixins
- updateLastModified(toNode, true);
-
- // process children nodes
- NodeIterator nit = fromNode.getNodes();
- while (nit.hasNext()) {
- Node fromChild = nit.nextNode();
- Integer index = fromChild.getIndex();
- String nodeRelPath = fromChild.getName() + "[" + index + "]";
- Node toChild;
- if (toNode.hasNode(nodeRelPath))
- toChild = toNode.getNode(nodeRelPath);
- else {
- try {
- toChild = toNode.addNode(fromChild.getName(), fromChild.getPrimaryNodeType().getName());
- } catch (NoSuchNodeTypeException e) {
- // ignore unknown primary types
- // TODO log it
- return;
- }
- }
- copy(fromChild, toChild);
- }
- } catch (RepositoryException e) {
- throw new JcrException("Cannot copy " + fromNode + " to " + toNode, e);
- }
- }
-
- /**
- * Check whether all first-level properties (except jcr:* properties) are equal.
- * Skip jcr:* properties
- */
- public static Boolean allPropertiesEquals(Node reference, Node observed, Boolean onlyCommonProperties) {
- try {
- PropertyIterator pit = reference.getProperties();
- props: while (pit.hasNext()) {
- Property propReference = pit.nextProperty();
- String propName = propReference.getName();
- if (propName.startsWith("jcr:"))
- continue props;
-
- if (!observed.hasProperty(propName))
- if (onlyCommonProperties)
- continue props;
- else
- return false;
- // TODO: deal with multiple property values?
- if (!observed.getProperty(propName).getValue().equals(propReference.getValue()))
- return false;
- }
- return true;
- } catch (RepositoryException e) {
- throw new JcrException("Cannot check all properties equals of " + reference + " and " + observed, e);
- }
- }
-
- public static Map<String, PropertyDiff> diffProperties(Node reference, Node observed) {
- Map<String, PropertyDiff> diffs = new TreeMap<String, PropertyDiff>();
- diffPropertiesLevel(diffs, null, reference, observed);
- return diffs;
- }
-
- /**
- * Compare the properties of two nodes. Recursivity to child nodes is not yet
- * supported. Skip jcr:* properties.
- */
- static void diffPropertiesLevel(Map<String, PropertyDiff> diffs, String baseRelPath, Node reference,
- Node observed) {
- try {
- // check removed and modified
- PropertyIterator pit = reference.getProperties();
- props: while (pit.hasNext()) {
- Property p = pit.nextProperty();
- String name = p.getName();
- if (name.startsWith("jcr:"))
- continue props;
-
- if (!observed.hasProperty(name)) {
- String relPath = propertyRelPath(baseRelPath, name);
- PropertyDiff pDiff = new PropertyDiff(PropertyDiff.REMOVED, relPath, p.getValue(), null);
- diffs.put(relPath, pDiff);
- } else {
- if (p.isMultiple()) {
- // FIXME implement multiple
- } else {
- Value referenceValue = p.getValue();
- Value newValue = observed.getProperty(name).getValue();
- if (!referenceValue.equals(newValue)) {
- String relPath = propertyRelPath(baseRelPath, name);
- PropertyDiff pDiff = new PropertyDiff(PropertyDiff.MODIFIED, relPath, referenceValue,
- newValue);
- diffs.put(relPath, pDiff);
- }
- }
- }
- }
- // check added
- pit = observed.getProperties();
- props: while (pit.hasNext()) {
- Property p = pit.nextProperty();
- String name = p.getName();
- if (name.startsWith("jcr:"))
- continue props;
- if (!reference.hasProperty(name)) {
- if (p.isMultiple()) {
- // FIXME implement multiple
- } else {
- String relPath = propertyRelPath(baseRelPath, name);
- PropertyDiff pDiff = new PropertyDiff(PropertyDiff.ADDED, relPath, null, p.getValue());
- diffs.put(relPath, pDiff);
- }
- }
- }
- } catch (RepositoryException e) {
- throw new JcrException("Cannot diff " + reference + " and " + observed, e);
- }
- }
-
- /**
- * Compare only a restricted list of properties of two nodes. No recursivity.
- *
- */
- public static Map<String, PropertyDiff> diffProperties(Node reference, Node observed, List<String> properties) {
- Map<String, PropertyDiff> diffs = new TreeMap<String, PropertyDiff>();
- try {
- Iterator<String> pit = properties.iterator();
-
- props: while (pit.hasNext()) {
- String name = pit.next();
- if (!reference.hasProperty(name)) {
- if (!observed.hasProperty(name))
- continue props;
- Value val = observed.getProperty(name).getValue();
- try {
- // empty String but not null
- if ("".equals(val.getString()))
- continue props;
- } catch (Exception e) {
- // not parseable as String, silent
- }
- PropertyDiff pDiff = new PropertyDiff(PropertyDiff.ADDED, name, null, val);
- diffs.put(name, pDiff);
- } else if (!observed.hasProperty(name)) {
- PropertyDiff pDiff = new PropertyDiff(PropertyDiff.REMOVED, name,
- reference.getProperty(name).getValue(), null);
- diffs.put(name, pDiff);
- } else {
- Value referenceValue = reference.getProperty(name).getValue();
- Value newValue = observed.getProperty(name).getValue();
- if (!referenceValue.equals(newValue)) {
- PropertyDiff pDiff = new PropertyDiff(PropertyDiff.MODIFIED, name, referenceValue, newValue);
- diffs.put(name, pDiff);
- }
- }
- }
- } catch (RepositoryException e) {
- throw new JcrException("Cannot diff " + reference + " and " + observed, e);
- }
- return diffs;
- }
-
- /** Builds a property relPath to be used in the diff. */
- private static String propertyRelPath(String baseRelPath, String propertyName) {
- if (baseRelPath == null)
- return propertyName;
- else
- return baseRelPath + '/' + propertyName;
- }
-
- /**
- * Normalizes a name so that it can be stored in contexts not supporting names
- * with ':' (typically databases). Replaces ':' by '_'.
- */
- public static String normalize(String name) {
- return name.replace(':', '_');
- }
-
- /**
- * Replaces characters which are invalid in a JCR name by '_'. Currently not
- * exhaustive.
- *
- * @see JcrUtils#INVALID_NAME_CHARACTERS
- */
- public static String replaceInvalidChars(String name) {
- return replaceInvalidChars(name, '_');
- }
-
- /**
- * Replaces characters which are invalid in a JCR name. Currently not
- * exhaustive.
- *
- * @see JcrUtils#INVALID_NAME_CHARACTERS
- */
- public static String replaceInvalidChars(String name, char replacement) {
- boolean modified = false;
- char[] arr = name.toCharArray();
- for (int i = 0; i < arr.length; i++) {
- char c = arr[i];
- invalid: for (char invalid : INVALID_NAME_CHARACTERS) {
- if (c == invalid) {
- arr[i] = replacement;
- modified = true;
- break invalid;
- }
- }
- }
- if (modified)
- return new String(arr);
- else
- // do not create new object if unnecessary
- return name;
- }
-
- // /**
- // * Removes forbidden characters from a path, replacing them with '_'
- // *
- // * @deprecated use {@link #replaceInvalidChars(String)} instead
- // */
- // public static String removeForbiddenCharacters(String str) {
- // return str.replace('[', '_').replace(']', '_').replace('/', '_').replace('*',
- // '_');
- //
- // }
-
- /** Cleanly disposes a {@link Binary} even if it is null. */
- public static void closeQuietly(Binary binary) {
- if (binary == null)
- return;
- binary.dispose();
- }
-
- /** Retrieve a {@link Binary} as a byte array */
- public static byte[] getBinaryAsBytes(Property property) {
- try (ByteArrayOutputStream out = new ByteArrayOutputStream();
- Bin binary = new Bin(property);
- InputStream in = binary.getStream()) {
- IOUtils.copy(in, out);
- return out.toByteArray();
- } catch (RepositoryException e) {
- throw new JcrException("Cannot read binary " + property + " as bytes", e);
- } catch (IOException e) {
- throw new RuntimeException("Cannot read binary " + property + " as bytes", e);
- }
- }
-
- /** Writes a {@link Binary} from a byte array */
- public static void setBinaryAsBytes(Node node, String property, byte[] bytes) {
- Binary binary = null;
- try (InputStream in = new ByteArrayInputStream(bytes)) {
- binary = node.getSession().getValueFactory().createBinary(in);
- node.setProperty(property, binary);
- } catch (RepositoryException e) {
- throw new JcrException("Cannot set binary " + property + " as bytes", e);
- } catch (IOException e) {
- throw new RuntimeException("Cannot set binary " + property + " as bytes", e);
- } finally {
- closeQuietly(binary);
- }
- }
-
- /** Writes a {@link Binary} from a byte array */
- public static void setBinaryAsBytes(Property prop, byte[] bytes) {
- Binary binary = null;
- try (InputStream in = new ByteArrayInputStream(bytes)) {
- binary = prop.getSession().getValueFactory().createBinary(in);
- prop.setValue(binary);
- } catch (RepositoryException e) {
- throw new JcrException("Cannot set binary " + prop + " as bytes", e);
- } catch (IOException e) {
- throw new RuntimeException("Cannot set binary " + prop + " as bytes", e);
- } finally {
- closeQuietly(binary);
- }
- }
-
- /**
- * Creates depth from a string (typically a username) by adding levels based on
- * its first characters: "aBcD",2 becomes a/aB
- */
- public static String firstCharsToPath(String str, Integer nbrOfChars) {
- if (str.length() < nbrOfChars)
- throw new IllegalArgumentException("String " + str + " length must be greater or equal than " + nbrOfChars);
- StringBuffer path = new StringBuffer("");
- StringBuffer curr = new StringBuffer("");
- for (int i = 0; i < nbrOfChars; i++) {
- curr.append(str.charAt(i));
- path.append(curr);
- if (i < nbrOfChars - 1)
- path.append('/');
- }
- return path.toString();
- }
-
- /**
- * Discards the current changes in the session attached to this node. To be used
- * typically in a catch block.
- *
- * @see #discardQuietly(Session)
- */
- public static void discardUnderlyingSessionQuietly(Node node) {
- try {
- discardQuietly(node.getSession());
- } catch (RepositoryException e) {
- // silent
- }
- }
-
- /**
- * Discards the current changes in a session by calling
- * {@link Session#refresh(boolean)} with <code>false</code>, only logging
- * potential errors when doing so. To be used typically in a catch block.
- */
- public static void discardQuietly(Session session) {
- try {
- if (session != null)
- session.refresh(false);
- } catch (RepositoryException e) {
- // silent
- }
- }
-
- /**
- * 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)
- 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(credentials, workspaceName);
- } catch (NoSuchWorkspaceException e) {
- // try to create workspace
- defaultSession = repository.login(credentials);
- defaultSession.getWorkspace().createWorkspace(workspaceName);
- workspaceSession = repository.login(credentials, workspaceName);
- }
- return workspaceSession;
- } finally {
- logoutQuietly(defaultSession);
- }
- }
-
- /**
- * Logs out the session, not throwing any exception, even if it is null.
- * {@link Jcr#logout(Session)} should rather be used.
- */
- public static void logoutQuietly(Session session) {
- Jcr.logout(session);
-// try {
-// if (session != null)
-// if (session.isLive())
-// session.logout();
-// } catch (Exception e) {
-// // silent
-// }
- }
-
- /**
- * Convenient method to add a listener. uuids passed as null, deep=true,
- * local=true, only one node type
- */
- public static void addListener(Session session, EventListener listener, int eventTypes, String basePath,
- String nodeType) {
- try {
- session.getWorkspace().getObservationManager().addEventListener(listener, eventTypes, basePath, true, null,
- nodeType == null ? null : new String[] { nodeType }, true);
- } catch (RepositoryException e) {
- throw new JcrException("Cannot add JCR listener " + listener + " to session " + session, e);
- }
- }
-
- /** Removes a listener without throwing exception */
- public static void removeListenerQuietly(Session session, EventListener listener) {
- if (session == null || !session.isLive())
- return;
- try {
- session.getWorkspace().getObservationManager().removeEventListener(listener);
- } catch (RepositoryException e) {
- // silent
- }
- }
-
- /**
- * Quietly unregisters an {@link EventListener} from the udnerlying workspace of
- * this node.
- */
- public static void unregisterQuietly(Node node, EventListener eventListener) {
- try {
- unregisterQuietly(node.getSession().getWorkspace(), eventListener);
- } catch (RepositoryException e) {
- // silent
- }
- }
-
- /** Quietly unregisters an {@link EventListener} from this workspace */
- public static void unregisterQuietly(Workspace workspace, EventListener eventListener) {
- if (eventListener == null)
- return;
- try {
- workspace.getObservationManager().removeEventListener(eventListener);
- } catch (RepositoryException e) {
- // silent
- }
- }
-
- /**
- * Checks whether {@link Property#JCR_LAST_MODIFIED} or (afterwards)
- * {@link Property#JCR_CREATED} are set and returns it as an {@link Instant}.
- */
- public static Instant getModified(Node node) {
- Calendar calendar = null;
- try {
- if (node.hasProperty(Property.JCR_LAST_MODIFIED))
- calendar = node.getProperty(Property.JCR_LAST_MODIFIED).getDate();
- else if (node.hasProperty(Property.JCR_CREATED))
- calendar = node.getProperty(Property.JCR_CREATED).getDate();
- else
- throw new IllegalArgumentException("No modification time found in " + node);
- return calendar.toInstant();
- } catch (RepositoryException e) {
- throw new JcrException("Cannot get modification time for " + node, e);
- }
-
- }
-
- /**
- * Get {@link Property#JCR_CREATED} as an {@link Instant}, if it is set.
- */
- public static Instant getCreated(Node node) {
- Calendar calendar = null;
- try {
- if (node.hasProperty(Property.JCR_CREATED))
- calendar = node.getProperty(Property.JCR_CREATED).getDate();
- else
- throw new IllegalArgumentException("No created time found in " + node);
- return calendar.toInstant();
- } catch (RepositoryException e) {
- throw new JcrException("Cannot get created time for " + node, e);
- }
-
- }
-
- /**
- * Updates the {@link Property#JCR_LAST_MODIFIED} property with the current time
- * and the {@link Property#JCR_LAST_MODIFIED_BY} property with the underlying
- * session user id.
- */
- public static void updateLastModified(Node node) {
- updateLastModified(node, false);
- }
-
- /**
- * Updates the {@link Property#JCR_LAST_MODIFIED} property with the current time
- * and the {@link Property#JCR_LAST_MODIFIED_BY} property with the underlying
- * session user id. In Jackrabbit 2.x,
- * <a href="https://issues.apache.org/jira/browse/JCR-2233">these properties are
- * not automatically updated</a>, hence the need for manual update. The session
- * is not saved.
- */
- public static void updateLastModified(Node node, boolean addMixin) {
- try {
- if (addMixin && !node.isNodeType(NodeType.MIX_LAST_MODIFIED))
- node.addMixin(NodeType.MIX_LAST_MODIFIED);
- node.setProperty(Property.JCR_LAST_MODIFIED, new GregorianCalendar());
- node.setProperty(Property.JCR_LAST_MODIFIED_BY, node.getSession().getUserID());
- } catch (RepositoryException e) {
- throw new JcrException("Cannot update last modified on " + node, e);
- }
- }
-
- /**
- * Update lastModified recursively until this parent.
- *
- * @param node the node
- * @param untilPath the base path, null is equivalent to "/"
- */
- public static void updateLastModifiedAndParents(Node node, String untilPath) {
- updateLastModifiedAndParents(node, untilPath, true);
- }
-
- /**
- * Update lastModified recursively until this parent.
- *
- * @param node the node
- * @param untilPath the base path, null is equivalent to "/"
- */
- public static void updateLastModifiedAndParents(Node node, String untilPath, boolean addMixin) {
- try {
- if (untilPath != null && !node.getPath().startsWith(untilPath))
- throw new IllegalArgumentException(node + " is not under " + untilPath);
- updateLastModified(node, addMixin);
- if (untilPath == null) {
- if (!node.getPath().equals("/"))
- updateLastModifiedAndParents(node.getParent(), untilPath, addMixin);
- } else {
- if (!node.getPath().equals(untilPath))
- updateLastModifiedAndParents(node.getParent(), untilPath, addMixin);
- }
- } catch (RepositoryException e) {
- throw new JcrException("Cannot update lastModified from " + node + " until " + untilPath, e);
- }
- }
-
- /**
- * Returns a String representing the short version (see
- * <a href="http://jackrabbit.apache.org/node-type-notation.html"> Node type
- * Notation </a> attributes grammar) of the main business attributes of this
- * property definition
- *
- * @param prop
- */
- public static String getPropertyDefinitionAsString(Property prop) {
- StringBuffer sbuf = new StringBuffer();
- try {
- if (prop.getDefinition().isAutoCreated())
- sbuf.append("a");
- if (prop.getDefinition().isMandatory())
- sbuf.append("m");
- if (prop.getDefinition().isProtected())
- sbuf.append("p");
- if (prop.getDefinition().isMultiple())
- sbuf.append("*");
- } catch (RepositoryException re) {
- throw new JcrException("unexpected error while getting property definition as String", re);
- }
- return sbuf.toString();
- }
-
- /**
- * Estimate the sub tree size from current node. Computation is based on the Jcr
- * {@link Property#getLength()} method. Note : it is not the exact size used on
- * the disk by the current part of the JCR Tree.
- */
-
- public static long getNodeApproxSize(Node node) {
- long curNodeSize = 0;
- try {
- PropertyIterator pi = node.getProperties();
- while (pi.hasNext()) {
- Property prop = pi.nextProperty();
- if (prop.isMultiple()) {
- int nb = prop.getLengths().length;
- for (int i = 0; i < nb; i++) {
- curNodeSize += (prop.getLengths()[i] > 0 ? prop.getLengths()[i] : 0);
- }
- } else
- curNodeSize += (prop.getLength() > 0 ? prop.getLength() : 0);
- }
-
- NodeIterator ni = node.getNodes();
- while (ni.hasNext())
- curNodeSize += getNodeApproxSize(ni.nextNode());
- return curNodeSize;
- } catch (RepositoryException re) {
- throw new JcrException("Unexpected error while recursively determining node size.", re);
- }
- }
-
- /*
- * SECURITY
- */
-
- /**
- * Convenience method for adding a single privilege to a principal (user or
- * role), typically jcr:all
- */
- public synchronized static void addPrivilege(Session session, String path, String principal, String privilege)
- throws RepositoryException {
- List<Privilege> privileges = new ArrayList<Privilege>();
- privileges.add(session.getAccessControlManager().privilegeFromName(privilege));
- addPrivileges(session, path, new SimplePrincipal(principal), privileges);
- }
-
- /**
- * Add privileges on a path to a {@link Principal}. The path must already exist.
- * Session is saved. Synchronized to prevent concurrent modifications of the
- * same node.
- */
- public synchronized static Boolean addPrivileges(Session session, String path, Principal principal,
- List<Privilege> privs) throws RepositoryException {
- // make sure the session is in line with the persisted state
- session.refresh(false);
- AccessControlManager acm = session.getAccessControlManager();
- AccessControlList acl = getAccessControlList(acm, path);
-
- accessControlEntries: for (AccessControlEntry ace : acl.getAccessControlEntries()) {
- Principal currentPrincipal = ace.getPrincipal();
- if (currentPrincipal.getName().equals(principal.getName())) {
- Privilege[] currentPrivileges = ace.getPrivileges();
- if (currentPrivileges.length != privs.size())
- break accessControlEntries;
- for (int i = 0; i < currentPrivileges.length; i++) {
- Privilege currP = currentPrivileges[i];
- Privilege p = privs.get(i);
- if (!currP.getName().equals(p.getName())) {
- break accessControlEntries;
- }
- }
- return false;
- }
- }
-
- Privilege[] privileges = privs.toArray(new Privilege[privs.size()]);
- acl.addAccessControlEntry(principal, privileges);
- acm.setPolicy(path, acl);
-// if (log.isDebugEnabled()) {
-// StringBuffer privBuf = new StringBuffer();
-// for (Privilege priv : privs)
-// privBuf.append(priv.getName());
-// log.debug("Added privileges " + privBuf + " to " + principal.getName() + " on " + path + " in '"
-// + session.getWorkspace().getName() + "'");
-// }
- session.refresh(true);
- session.save();
- return true;
- }
-
- /**
- * Gets the first available access control list for this path, throws exception
- * if not found
- */
- public synchronized static AccessControlList getAccessControlList(AccessControlManager acm, String path)
- throws RepositoryException {
- // search for an access control list
- AccessControlList acl = null;
- AccessControlPolicyIterator policyIterator = acm.getApplicablePolicies(path);
- applicablePolicies: if (policyIterator.hasNext()) {
- while (policyIterator.hasNext()) {
- AccessControlPolicy acp = policyIterator.nextAccessControlPolicy();
- if (acp instanceof AccessControlList) {
- acl = ((AccessControlList) acp);
- break applicablePolicies;
- }
- }
- } else {
- AccessControlPolicy[] existingPolicies = acm.getPolicies(path);
- existingPolicies: for (AccessControlPolicy acp : existingPolicies) {
- if (acp instanceof AccessControlList) {
- acl = ((AccessControlList) acp);
- break existingPolicies;
- }
- }
- }
- if (acl != null)
- return acl;
- else
- throw new IllegalArgumentException("ACL not found at " + path);
- }
-
- /** Clear authorizations for a user at this path */
- public synchronized static void clearAccessControList(Session session, String path, String username)
- throws RepositoryException {
- AccessControlManager acm = session.getAccessControlManager();
- AccessControlList acl = getAccessControlList(acm, path);
- for (AccessControlEntry ace : acl.getAccessControlEntries()) {
- if (ace.getPrincipal().getName().equals(username)) {
- acl.removeAccessControlEntry(ace);
- }
- }
- // the new access control list must be applied otherwise this call:
- // acl.removeAccessControlEntry(ace); has no effect
- acm.setPolicy(path, acl);
- session.refresh(true);
- session.save();
- }
-
- /*
- * FILES UTILITIES
- */
- /**
- * Creates the nodes making the path as {@link NodeType#NT_FOLDER}
- */
- public static Node mkfolders(Session session, String path) {
- return mkdirs(session, path, NodeType.NT_FOLDER, NodeType.NT_FOLDER, false);
- }
-
- /**
- * Copy only nt:folder and nt:file, without their additional types and
- * properties.
- *
- * @param recursive if true copies folders as well, otherwise only first level
- * files
- * @return how many files were copied
- */
- public static Long copyFiles(Node fromNode, Node toNode, Boolean recursive, JcrMonitor monitor, boolean onlyAdd) {
- long count = 0l;
-
- // Binary binary = null;
- // InputStream in = null;
- try {
- NodeIterator fromChildren = fromNode.getNodes();
- children: while (fromChildren.hasNext()) {
- if (monitor != null && monitor.isCanceled())
- throw new IllegalStateException("Copy cancelled before it was completed");
-
- Node fromChild = fromChildren.nextNode();
- String fileName = fromChild.getName();
- if (fromChild.isNodeType(NodeType.NT_FILE)) {
- if (onlyAdd && toNode.hasNode(fileName)) {
- monitor.subTask("Skip existing " + fileName);
- continue children;
- }
-
- if (monitor != null)
- monitor.subTask("Copy " + fileName);
- try (Bin binary = new Bin(fromChild.getNode(Node.JCR_CONTENT).getProperty(Property.JCR_DATA));
- InputStream in = binary.getStream();) {
- copyStreamAsFile(toNode, fileName, in);
- } catch (IOException e) {
- throw new RuntimeException("Cannot copy " + fileName + " to " + toNode, e);
- }
-
- // save session
- toNode.getSession().save();
- count++;
-
-// if (log.isDebugEnabled())
-// log.debug("Copied file " + fromChild.getPath());
- if (monitor != null)
- monitor.worked(1);
- } else if (fromChild.isNodeType(NodeType.NT_FOLDER) && recursive) {
- Node toChildFolder;
- if (toNode.hasNode(fileName)) {
- toChildFolder = toNode.getNode(fileName);
- if (!toChildFolder.isNodeType(NodeType.NT_FOLDER))
- throw new IllegalArgumentException(toChildFolder + " is not of type nt:folder");
- } else {
- toChildFolder = toNode.addNode(fileName, NodeType.NT_FOLDER);
-
- // save session
- toNode.getSession().save();
- }
- count = count + copyFiles(fromChild, toChildFolder, recursive, monitor, onlyAdd);
- }
- }
- return count;
- } catch (RepositoryException e) {
- throw new JcrException("Cannot copy files between " + fromNode + " and " + toNode, e);
- } finally {
- // in case there was an exception
- // IOUtils.closeQuietly(in);
- // closeQuietly(binary);
- }
- }
-
- /**
- * Iteratively count all file nodes in subtree, inefficient but can be useful
- * when query are poorly supported, such as in remoting.
- */
- public static Long countFiles(Node node) {
- Long localCount = 0l;
- try {
- for (NodeIterator nit = node.getNodes(); nit.hasNext();) {
- Node child = nit.nextNode();
- if (child.isNodeType(NodeType.NT_FOLDER))
- localCount = localCount + countFiles(child);
- else if (child.isNodeType(NodeType.NT_FILE))
- localCount = localCount + 1;
- }
- } catch (RepositoryException e) {
- throw new JcrException("Cannot count all children of " + node, e);
- }
- return localCount;
- }
-
- /**
- * Copy a file as an nt:file, assuming an nt:folder hierarchy. The session is
- * NOT saved.
- *
- * @return the created file node
- */
- @Deprecated
- public static Node copyFile(Node folderNode, File file) {
- try (InputStream in = new FileInputStream(file)) {
- return copyStreamAsFile(folderNode, file.getName(), in);
- } catch (IOException e) {
- throw new RuntimeException("Cannot copy file " + file + " under " + folderNode, e);
- }
- }
-
- /** Copy bytes as an nt:file */
- public static Node copyBytesAsFile(Node folderNode, String fileName, byte[] bytes) {
- // InputStream in = null;
- try (InputStream in = new ByteArrayInputStream(bytes)) {
- // in = new ByteArrayInputStream(bytes);
- return copyStreamAsFile(folderNode, fileName, in);
- } catch (IOException e) {
- throw new RuntimeException("Cannot copy file " + fileName + " under " + folderNode, e);
- // } finally {
- // IOUtils.closeQuietly(in);
- }
- }
-
- /**
- * Copy a stream as an nt:file, assuming an nt:folder hierarchy. The session is
- * NOT saved.
- *
- * @return the created file node
- */
- public static Node copyStreamAsFile(Node folderNode, String fileName, InputStream in) {
- Binary binary = null;
- try {
- Node fileNode;
- Node contentNode;
- if (folderNode.hasNode(fileName)) {
- fileNode = folderNode.getNode(fileName);
- if (!fileNode.isNodeType(NodeType.NT_FILE))
- throw new IllegalArgumentException(fileNode + " is not of type nt:file");
- // we assume that the content node is already there
- contentNode = fileNode.getNode(Node.JCR_CONTENT);
- } else {
- fileNode = folderNode.addNode(fileName, NodeType.NT_FILE);
- contentNode = fileNode.addNode(Node.JCR_CONTENT, NodeType.NT_UNSTRUCTURED);
- }
- binary = contentNode.getSession().getValueFactory().createBinary(in);
- contentNode.setProperty(Property.JCR_DATA, binary);
- updateLastModified(contentNode);
- return fileNode;
- } catch (RepositoryException e) {
- throw new JcrException("Cannot create file node " + fileName + " under " + folderNode, e);
- } finally {
- closeQuietly(binary);
- }
- }
-
- /** Read an an nt:file as an {@link InputStream}. */
- public static InputStream getFileAsStream(Node fileNode) throws RepositoryException {
- return fileNode.getNode(Node.JCR_CONTENT).getProperty(Property.JCR_DATA).getBinary().getStream();
- }
-
- /**
- * Set the properties of {@link NodeType#MIX_MIMETYPE} on the content of this
- * file node.
- */
- public static void setFileMimeType(Node fileNode, String mimeType, String encoding) throws RepositoryException {
- Node contentNode = fileNode.getNode(Node.JCR_CONTENT);
- if (mimeType != null)
- contentNode.setProperty(Property.JCR_MIMETYPE, mimeType);
- if (encoding != null)
- contentNode.setProperty(Property.JCR_ENCODING, encoding);
- // TODO remove properties if args are null?
- }
-
- public static void copyFilesToFs(Node baseNode, Path targetDir, boolean recursive) {
- try {
- Files.createDirectories(targetDir);
- for (NodeIterator nit = baseNode.getNodes(); nit.hasNext();) {
- Node node = nit.nextNode();
- if (node.isNodeType(NodeType.NT_FILE)) {
- Path filePath = targetDir.resolve(node.getName());
- try (OutputStream out = Files.newOutputStream(filePath); InputStream in = getFileAsStream(node)) {
- IOUtils.copy(in, out);
- }
- } else if (recursive && node.isNodeType(NodeType.NT_FOLDER)) {
- Path dirPath = targetDir.resolve(node.getName());
- copyFilesToFs(node, dirPath, true);
- }
- }
- } catch (RepositoryException e) {
- throw new JcrException("Cannot copy " + baseNode + " to " + targetDir, e);
- } catch (IOException e) {
- throw new RuntimeException("Cannot copy " + baseNode + " to " + targetDir, e);
- }
- }
-
- /**
- * Computes the checksum of an nt:file.
- *
- * @deprecated use separate digest utilities
- */
- @Deprecated
- public static String checksumFile(Node fileNode, String algorithm) {
- try (InputStream in = fileNode.getNode(Node.JCR_CONTENT).getProperty(Property.JCR_DATA).getBinary()
- .getStream()) {
- return digest(algorithm, in);
- } catch (IOException e) {
- throw new RuntimeException("Cannot checksum file " + fileNode + " with algorithm " + algorithm, e);
- } catch (RepositoryException e) {
- throw new JcrException("Cannot checksum file " + fileNode + " with algorithm " + algorithm, e);
- }
- }
-
- @Deprecated
- private static String digest(String algorithm, InputStream in) {
- final Integer byteBufferCapacity = 100 * 1024;// 100 KB
- try {
- MessageDigest digest = MessageDigest.getInstance(algorithm);
- byte[] buffer = new byte[byteBufferCapacity];
- int read = 0;
- while ((read = in.read(buffer)) > 0) {
- digest.update(buffer, 0, read);
- }
-
- byte[] checksum = digest.digest();
- String res = encodeHexString(checksum);
- return res;
- } catch (IOException e) {
- throw new RuntimeException("Cannot digest with algorithm " + algorithm, e);
- } catch (NoSuchAlgorithmException e) {
- throw new IllegalArgumentException("Cannot digest with algorithm " + algorithm, e);
- }
- }
-
- /**
- * From
- * http://stackoverflow.com/questions/9655181/how-to-convert-a-byte-array-to
- * -a-hex-string-in-java
- */
- @Deprecated
- private static String encodeHexString(byte[] bytes) {
- final char[] hexArray = "0123456789abcdef".toCharArray();
- char[] hexChars = new char[bytes.length * 2];
- for (int j = 0; j < bytes.length; j++) {
- int v = bytes[j] & 0xFF;
- hexChars[j * 2] = hexArray[v >>> 4];
- hexChars[j * 2 + 1] = hexArray[v & 0x0F];
- }
- return new String(hexChars);
- }
-
- /** Export a subtree as a compact XML without namespaces. */
- public static void toSimpleXml(Node node, StringBuilder sb) throws RepositoryException {
- sb.append('<');
- String nodeName = node.getName();
- int colIndex = nodeName.indexOf(':');
- if (colIndex > 0) {
- nodeName = nodeName.substring(colIndex + 1);
- }
- sb.append(nodeName);
- PropertyIterator pit = node.getProperties();
- properties: while (pit.hasNext()) {
- Property p = pit.nextProperty();
- // skip multiple properties
- if (p.isMultiple())
- continue properties;
- String propertyName = p.getName();
- int pcolIndex = propertyName.indexOf(':');
- // skip properties with namespaces
- if (pcolIndex > 0)
- continue properties;
- // skip binaries
- if (p.getType() == PropertyType.BINARY) {
- continue properties;
- // TODO retrieve identifier?
- }
- sb.append(' ');
- sb.append(propertyName);
- sb.append('=');
- sb.append('\"').append(p.getString()).append('\"');
- }
-
- if (node.hasNodes()) {
- sb.append('>');
- NodeIterator children = node.getNodes();
- while (children.hasNext()) {
- toSimpleXml(children.nextNode(), sb);
- }
- sb.append("</");
- sb.append(nodeName);
- sb.append('>');
- } else {
- sb.append("/>");
- }
- }
-
-}
+++ /dev/null
-package org.argeo.jcr;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-import javax.jcr.Node;
-import javax.jcr.Property;
-import javax.jcr.RepositoryException;
-import javax.jcr.Value;
-
-/** Uilities around the JCR extensions. */
-public class JcrxApi {
- public final static String MD5 = "MD5";
- public final static String SHA1 = "SHA1";
- public final static String SHA256 = "SHA-256";
- public final static String SHA512 = "SHA-512";
-
- public final static String EMPTY_MD5 = "d41d8cd98f00b204e9800998ecf8427e";
- public final static String EMPTY_SHA1 = "da39a3ee5e6b4b0d3255bfef95601890afd80709";
- public final static String EMPTY_SHA256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
- public final static String EMPTY_SHA512 = "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e";
-
- public final static int LENGTH_MD5 = EMPTY_MD5.length();
- public final static int LENGTH_SHA1 = EMPTY_SHA1.length();
- public final static int LENGTH_SHA256 = EMPTY_SHA256.length();
- public final static int LENGTH_SHA512 = EMPTY_SHA512.length();
-
- /*
- * XML
- */
- /**
- * Get the XML text of this child node.
- */
- public static String getXmlValue(Node node, String name) {
- try {
- if (!node.hasNode(name))
- return null;
- Node child = node.getNode(name);
- return getXmlValue(child);
- } catch (RepositoryException e) {
- throw new IllegalStateException("Cannot get " + name + " as XML text", e);
- }
- }
-
- /**
- * Get the XML text of this node.
- */
- public static String getXmlValue(Node node) {
- try {
- if (!node.hasNode(Jcr.JCR_XMLTEXT))
- return null;
- Node xmlText = node.getNode(Jcr.JCR_XMLTEXT);
- if (!xmlText.hasProperty(Jcr.JCR_XMLCHARACTERS))
- throw new IllegalArgumentException(
- "Node " + xmlText + " has no " + Jcr.JCR_XMLCHARACTERS + " property");
- return xmlText.getProperty(Jcr.JCR_XMLCHARACTERS).getString();
- } catch (RepositoryException e) {
- throw new IllegalStateException("Cannot get " + node + " as XML text", e);
- }
- }
-
- /**
- * Set as a subnode which will be exported as an XML element.
- */
- public static void setXmlValue(Node node, String name, String value) {
- try {
- if (node.hasNode(name)) {
- Node child = node.getNode(name);
- setXmlValue(node, child, value);
- } else
- node.addNode(name, JcrxType.JCRX_XMLVALUE).addNode(Jcr.JCR_XMLTEXT, JcrxType.JCRX_XMLTEXT)
- .setProperty(Jcr.JCR_XMLCHARACTERS, value);
- } catch (RepositoryException e) {
- throw new JcrException("Cannot set " + name + " as XML text", e);
- }
- }
-
- public static void setXmlValue(Node node, Node child, String value) {
- try {
- if (!child.hasNode(Jcr.JCR_XMLTEXT))
- child.addNode(Jcr.JCR_XMLTEXT, JcrxType.JCRX_XMLTEXT);
- child.getNode(Jcr.JCR_XMLTEXT).setProperty(Jcr.JCR_XMLCHARACTERS, value);
- } catch (RepositoryException e) {
- throw new JcrException("Cannot set " + child + " as XML text", e);
- }
- }
-
- /**
- * Add a checksum replacing the one which was previously set with the same
- * length.
- */
- public static void addChecksum(Node node, String checksum) {
- try {
- if (!node.hasProperty(JcrxName.JCRX_SUM)) {
- node.setProperty(JcrxName.JCRX_SUM, new String[] { checksum });
- return;
- } else {
- int stringLength = checksum.length();
- Property property = node.getProperty(JcrxName.JCRX_SUM);
- List<Value> values = Arrays.asList(property.getValues());
- Integer indexToRemove = null;
- values: for (int i = 0; i < values.size(); i++) {
- Value value = values.get(i);
- if (value.getString().length() == stringLength) {
- indexToRemove = i;
- break values;
- }
- }
- if (indexToRemove != null)
- values.set(indexToRemove, node.getSession().getValueFactory().createValue(checksum));
- else
- values.add(0, node.getSession().getValueFactory().createValue(checksum));
- property.setValue(values.toArray(new Value[values.size()]));
- }
- } catch (RepositoryException e) {
- throw new JcrException("Cannot set checksum on " + node, e);
- }
- }
-
- /** Replace all checksums. */
- public static void setChecksums(Node node, List<String> checksums) {
- try {
- node.setProperty(JcrxName.JCRX_SUM, checksums.toArray(new String[checksums.size()]));
- } catch (RepositoryException e) {
- throw new JcrException("Cannot set checksums on " + node, e);
- }
- }
-
- /** Replace all checksums. */
- public static List<String> getChecksums(Node node) {
- try {
- List<String> res = new ArrayList<>();
- if (!node.hasProperty(JcrxName.JCRX_SUM))
- return res;
- Property property = node.getProperty(JcrxName.JCRX_SUM);
- for (Value value : property.getValues()) {
- res.add(value.getString());
- }
- return res;
- } catch (RepositoryException e) {
- throw new JcrException("Cannot get checksums from " + node, e);
- }
- }
-
-// /** Replace all checksums with this single one. */
-// public static void setChecksum(Node node, String checksum) {
-// setChecksums(node, Collections.singletonList(checksum));
-// }
-
- /** Retrieves the checksum with this algorithm, or null if not found. */
- public static String getChecksum(Node node, String algorithm) {
- int stringLength;
- switch (algorithm) {
- case MD5:
- stringLength = LENGTH_MD5;
- break;
- case SHA1:
- stringLength = LENGTH_SHA1;
- break;
- case SHA256:
- stringLength = LENGTH_SHA256;
- break;
- case SHA512:
- stringLength = LENGTH_SHA512;
- break;
- default:
- throw new IllegalArgumentException("Unkown algorithm " + algorithm);
- }
- return getChecksum(node, stringLength);
- }
-
- /** Retrieves the checksum with this string length, or null if not found. */
- public static String getChecksum(Node node, int stringLength) {
- try {
- if (!node.hasProperty(JcrxName.JCRX_SUM))
- return null;
- Property property = node.getProperty(JcrxName.JCRX_SUM);
- for (Value value : property.getValues()) {
- String str = value.getString();
- if (str.length() == stringLength)
- return str;
- }
- return null;
- } catch (RepositoryException e) {
- throw new IllegalStateException("Cannot get checksum for " + node, e);
- }
- }
-
-}
+++ /dev/null
-package org.argeo.jcr;
-
-/** Names declared by the JCR extensions. */
-public interface JcrxName {
- /** The multiple property holding various coherent checksums. */
- public final static String JCRX_SUM = "{http://www.argeo.org/ns/jcrx}sum";
-}
+++ /dev/null
-package org.argeo.jcr;
-
-/** Node types declared by the JCR extensions. */
-public interface JcrxType {
- /**
- * Node type for an XML value, which will be serialized in XML as an element
- * containing text.
- */
- public final static String JCRX_XMLVALUE = "{http://www.argeo.org/ns/jcrx}xmlvalue";
-
- /** Node type for the node containing the text. */
- public final static String JCRX_XMLTEXT = "{http://www.argeo.org/ns/jcrx}xmltext";
-
- /** Mixin node type for a set of checksums. */
- public final static String JCRX_CSUM = "{http://www.argeo.org/ns/jcrx}csum";
-
-}
+++ /dev/null
-package org.argeo.jcr;
-
-import javax.jcr.Value;
-
-/** The result of the comparison of two JCR properties. */
-public class PropertyDiff {
- public final static Integer MODIFIED = 0;
- public final static Integer ADDED = 1;
- public final static Integer REMOVED = 2;
-
- private final Integer type;
- private final String relPath;
- private final Value referenceValue;
- private final Value newValue;
-
- public PropertyDiff(Integer type, String relPath, Value referenceValue, Value newValue) {
- super();
-
- if (type == MODIFIED) {
- if (referenceValue == null || newValue == null)
- throw new IllegalArgumentException("Reference and new values must be specified.");
- } else if (type == ADDED) {
- if (referenceValue != null || newValue == null)
- throw new IllegalArgumentException("New value and only it must be specified.");
- } else if (type == REMOVED) {
- if (referenceValue == null || newValue != null)
- throw new IllegalArgumentException("Reference value and only it must be specified.");
- } else {
- throw new IllegalArgumentException("Unkown diff type " + type);
- }
-
- if (relPath == null)
- throw new IllegalArgumentException("Relative path must be specified");
-
- this.type = type;
- this.relPath = relPath;
- this.referenceValue = referenceValue;
- this.newValue = newValue;
- }
-
- public Integer getType() {
- return type;
- }
-
- public String getRelPath() {
- return relPath;
- }
-
- public Value getReferenceValue() {
- return referenceValue;
- }
-
- public Value getNewValue() {
- return newValue;
- }
-
-}
+++ /dev/null
-package org.argeo.jcr;
-
-import java.security.Principal;
-
-/** Canonical implementation of a {@link Principal} */
-class SimplePrincipal implements Principal {
- private final String name;
-
- public SimplePrincipal(String name) {
- if (name == null)
- throw new IllegalArgumentException("Principal name cannot be null");
- this.name = name;
- }
-
- public String getName() {
- return name;
- }
-
- @Override
- public int hashCode() {
- return name.hashCode();
- }
-
- @Override
- public boolean equals(Object obj) {
- if (obj == null)
- return false;
- if (obj instanceof Principal)
- return name.equals((((Principal) obj).getName()));
- return name.equals(obj.toString());
- }
-
- @Override
- protected Object clone() throws CloneNotSupportedException {
- return new SimplePrincipal(name);
- }
-
- @Override
- public String toString() {
- return name;
- }
-
-}
+++ /dev/null
-package org.argeo.jcr;
-
-import java.lang.reflect.InvocationHandler;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.lang.reflect.Proxy;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-
-import javax.jcr.LoginException;
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.SimpleCredentials;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-
-/** Proxy JCR sessions and attach them to calling threads. */
-@Deprecated
-public abstract class ThreadBoundJcrSessionFactory {
- private final static Log log = LogFactory.getLog(ThreadBoundJcrSessionFactory.class);
-
- private Repository repository;
- /** can be injected as list, only used if repository is null */
- private List<Repository> repositories;
-
- private ThreadLocal<Session> session = new ThreadLocal<Session>();
- private final Session proxiedSession;
- /** If workspace is null, default will be used. */
- private String workspace = null;
-
- private String defaultUsername = "demo";
- private String defaultPassword = "demo";
- private Boolean forceDefaultCredentials = false;
-
- private boolean active = true;
-
- // monitoring
- private final List<Thread> threads = Collections.synchronizedList(new ArrayList<Thread>());
- private final Map<Long, Session> activeSessions = Collections.synchronizedMap(new HashMap<Long, Session>());
- private MonitoringThread monitoringThread;
-
- public ThreadBoundJcrSessionFactory() {
- Class<?>[] interfaces = { Session.class };
- proxiedSession = (Session) Proxy.newProxyInstance(ThreadBoundJcrSessionFactory.class.getClassLoader(),
- interfaces, new JcrSessionInvocationHandler());
- }
-
- /** Logs in to the repository using various strategies. */
- protected synchronized Session login() {
- if (!isActive())
- throw new IllegalStateException("Thread bound session factory inactive");
-
- // discard session previously attached to this thread
- Thread thread = Thread.currentThread();
- if (activeSessions.containsKey(thread.getId())) {
- Session oldSession = activeSessions.remove(thread.getId());
- oldSession.logout();
- session.remove();
- }
-
- Session newSession = null;
- // first try to login without credentials, assuming the underlying login
- // module will have dealt with authentication (typically using Spring
- // Security)
- if (!forceDefaultCredentials)
- try {
- newSession = repository().login(workspace);
- } catch (LoginException e1) {
- log.warn("Cannot login without credentials: " + e1.getMessage());
- // invalid credentials, go to the next step
- } catch (RepositoryException e1) {
- // other kind of exception, fail
- throw new JcrException("Cannot log in to repository", e1);
- }
-
- // log using default username / password (useful for testing purposes)
- if (newSession == null)
- try {
- SimpleCredentials sc = new SimpleCredentials(defaultUsername, defaultPassword.toCharArray());
- newSession = repository().login(sc, workspace);
- } catch (RepositoryException e) {
- throw new JcrException("Cannot log in to repository", e);
- }
-
- session.set(newSession);
- // Log and monitor new session
- if (log.isTraceEnabled())
- log.trace("Logged in to JCR session " + newSession + "; userId=" + newSession.getUserID());
-
- // monitoring
- activeSessions.put(thread.getId(), newSession);
- threads.add(thread);
- return newSession;
- }
-
- public Object getObject() {
- return proxiedSession;
- }
-
- public void init() throws Exception {
- // log.error("SHOULD NOT BE USED ANYMORE");
- monitoringThread = new MonitoringThread();
- monitoringThread.start();
- }
-
- public void dispose() throws Exception {
- // if (activeSessions.size() == 0)
- // return;
-
- if (log.isTraceEnabled())
- log.trace("Cleaning up " + activeSessions.size() + " active JCR sessions...");
-
- deactivate();
- for (Session sess : activeSessions.values()) {
- JcrUtils.logoutQuietly(sess);
- }
- activeSessions.clear();
- }
-
- protected Boolean isActive() {
- return active;
- }
-
- protected synchronized void deactivate() {
- active = false;
- notifyAll();
- }
-
- protected synchronized void removeSession(Thread thread) {
- if (!isActive())
- return;
- activeSessions.remove(thread.getId());
- threads.remove(thread);
- }
-
- protected synchronized void cleanDeadThreads() {
- if (!isActive())
- return;
- Iterator<Thread> it = threads.iterator();
- while (it.hasNext()) {
- Thread thread = it.next();
- if (!thread.isAlive() && isActive()) {
- if (activeSessions.containsKey(thread.getId())) {
- Session session = activeSessions.get(thread.getId());
- activeSessions.remove(thread.getId());
- session.logout();
- if (log.isTraceEnabled())
- log.trace("Cleaned up JCR session (userID=" + session.getUserID() + ") from dead thread "
- + thread.getId());
- }
- it.remove();
- }
- }
- try {
- wait(1000);
- } catch (InterruptedException e) {
- // silent
- }
- }
-
- public Class<? extends Session> getObjectType() {
- return Session.class;
- }
-
- public boolean isSingleton() {
- return true;
- }
-
- /**
- * Called before a method is actually called, allowing to check the session or
- * re-login it (e.g. if authentication has changed). The default implementation
- * returns the session.
- */
- protected Session preCall(Session session) {
- return session;
- }
-
- protected Repository repository() {
- if (repository != null)
- return repository;
- if (repositories != null) {
- // hardened for OSGi dynamic services
- Iterator<Repository> it = repositories.iterator();
- if (it.hasNext())
- return it.next();
- }
- throw new IllegalStateException("No repository injected");
- }
-
- // /** Useful for declarative registration of OSGi services (blueprint) */
- // public void register(Repository repository, Map<?, ?> params) {
- // this.repository = repository;
- // }
- //
- // /** Useful for declarative registration of OSGi services (blueprint) */
- // public void unregister(Repository repository, Map<?, ?> params) {
- // this.repository = null;
- // }
-
- public void setRepository(Repository repository) {
- this.repository = repository;
- }
-
- public void setRepositories(List<Repository> repositories) {
- this.repositories = repositories;
- }
-
- public void setDefaultUsername(String defaultUsername) {
- this.defaultUsername = defaultUsername;
- }
-
- public void setDefaultPassword(String defaultPassword) {
- this.defaultPassword = defaultPassword;
- }
-
- public void setForceDefaultCredentials(Boolean forceDefaultCredentials) {
- this.forceDefaultCredentials = forceDefaultCredentials;
- }
-
- public void setWorkspace(String workspace) {
- this.workspace = workspace;
- }
-
- protected class JcrSessionInvocationHandler implements InvocationHandler {
-
- public Object invoke(Object proxy, Method method, Object[] args) throws Throwable, RepositoryException {
- Session threadSession = session.get();
- if (threadSession == null) {
- if ("logout".equals(method.getName()))// no need to login
- return Void.TYPE;
- else if ("toString".equals(method.getName()))// maybe logging
- return "Uninitialized Argeo thread bound JCR session";
- threadSession = login();
- }
-
- preCall(threadSession);
- Object ret;
- try {
- ret = method.invoke(threadSession, args);
- } catch (InvocationTargetException e) {
- Throwable cause = e.getCause();
- if (cause instanceof RepositoryException)
- throw (RepositoryException) cause;
- else
- throw cause;
- }
- if ("logout".equals(method.getName())) {
- session.remove();
- Thread thread = Thread.currentThread();
- removeSession(thread);
- if (log.isTraceEnabled())
- log.trace("Logged out JCR session (userId=" + threadSession.getUserID() + ") on thread "
- + thread.getId());
- }
- return ret;
- }
- }
-
- /** Monitors registered thread in order to clean up dead ones. */
- private class MonitoringThread extends Thread {
-
- public MonitoringThread() {
- super("ThreadBound JCR Session Monitor");
- }
-
- @Override
- public void run() {
- while (isActive()) {
- cleanDeadThreads();
- }
- }
-
- }
-}
+++ /dev/null
-package org.argeo.jcr;
-
-import java.util.Calendar;
-import java.util.Map;
-
-/**
- * Generic Object that enables the creation of history reports based on a JCR
- * versionable node. userId and creation date are added to the map of
- * PropertyDiff.
- *
- * These two fields might be null
- *
- */
-public class VersionDiff {
-
- private String userId;
- private Map<String, PropertyDiff> diffs;
- private Calendar updateTime;
-
- public VersionDiff(String userId, Calendar updateTime,
- Map<String, PropertyDiff> diffs) {
- this.userId = userId;
- this.updateTime = updateTime;
- this.diffs = diffs;
- }
-
- public String getUserId() {
- return userId;
- }
-
- public Map<String, PropertyDiff> getDiffs() {
- return diffs;
- }
-
- public Calendar getUpdateTime() {
- return updateTime;
- }
-}
+++ /dev/null
-package org.argeo.jcr.fs;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.ByteBuffer;
-import java.nio.channels.Channels;
-import java.nio.channels.FileChannel;
-import java.nio.channels.ReadableByteChannel;
-import java.nio.channels.SeekableByteChannel;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.StandardOpenOption;
-
-import javax.jcr.Binary;
-import javax.jcr.Node;
-import javax.jcr.Property;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-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;
- private boolean open = true;
-
- private long position = 0;
-
- private FileChannel fc = null;
-
- public BinaryChannel(Node file, Path path) throws RepositoryException, IOException {
- this.file = file;
- 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();
- }
- } else {
- throw new IllegalArgumentException(
- "Unsupported file node " + file + " (" + file.getPrimaryNodeType() + ")");
- }
- }
- }
-
- @Override
- public synchronized boolean isOpen() {
- return open;
- }
-
- @Override
- public synchronized void close() throws IOException {
- if (isModified()) {
- Binary newBinary = null;
- try {
- Session session = file.getSession();
- 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 {
- JcrUtils.closeQuietly(newBinary);
- // IOUtils.closeQuietly(fc);
- if (fc != null) {
- fc.close();
- }
- }
- } else {
- clearReadState();
- open = false;
- }
- }
-
- @Override
- public int read(ByteBuffer dst) throws IOException {
- if (isModified()) {
- return fc.read(dst);
- } else {
-
- try {
- int read;
- byte[] arr = dst.array();
- read = binary.read(arr, position);
-
- if (read != -1)
- position = position + read;
- return read;
- } catch (RepositoryException e) {
- throw new IOException("Cannot read into buffer", e);
- }
- }
- }
-
- @Override
- public int write(ByteBuffer src) throws IOException {
- int written = getFileChannel().write(src);
- return written;
- }
-
- @Override
- public long position() throws IOException {
- if (isModified())
- return getFileChannel().position();
- else
- return position;
- }
-
- @Override
- public SeekableByteChannel position(long newPosition) throws IOException {
- if (isModified()) {
- getFileChannel().position(position);
- } else {
- this.position = newPosition;
- }
- return this;
- }
-
- @Override
- public long size() throws IOException {
- if (isModified()) {
- return getFileChannel().size();
- } else {
- try {
- return binary.getSize();
- } catch (RepositoryException e) {
- throw new IOException("Cannot get size", e);
- }
- }
- }
-
- @Override
- public SeekableByteChannel truncate(long size) throws IOException {
- getFileChannel().truncate(size);
- return this;
- }
-
- private FileChannel getFileChannel() throws IOException {
- try {
- if (fc == null) {
- Path tempPath = Files.createTempFile(getClass().getSimpleName(), null);
- fc = FileChannel.open(tempPath, StandardOpenOption.WRITE, StandardOpenOption.READ,
- StandardOpenOption.DELETE_ON_CLOSE, StandardOpenOption.SPARSE);
- ReadableByteChannel readChannel = Channels.newChannel(binary.getStream());
- fc.transferFrom(readChannel, 0, binary.getSize());
- clearReadState();
- }
- return fc;
- } catch (RepositoryException e) {
- throw new IOException("Cannot get temp file channel", e);
- }
- }
-
- private boolean isModified() {
- return fc != null;
- }
-
- private void clearReadState() {
- position = -1;
- JcrUtils.closeQuietly(binary);
- binary = null;
- }
-}
+++ /dev/null
-package org.argeo.jcr.fs;
-
-import static javax.jcr.Property.JCR_CREATED;
-import static javax.jcr.Property.JCR_LAST_MODIFIED;
-
-import java.nio.file.attribute.FileTime;
-import java.time.Instant;
-
-import javax.jcr.Binary;
-import javax.jcr.Node;
-import javax.jcr.Property;
-import javax.jcr.RepositoryException;
-import javax.jcr.nodetype.NodeType;
-
-import org.argeo.jcr.JcrUtils;
-
-public class JcrBasicfileAttributes implements NodeFileAttributes {
- private final Node node;
-
- private final static FileTime EPOCH = FileTime.fromMillis(0);
-
- public JcrBasicfileAttributes(Node node) {
- if (node == null)
- throw new JcrFsException("Node underlying the attributes cannot be null");
- this.node = node;
- }
-
- @Override
- public FileTime lastModifiedTime() {
- try {
- if (node.hasProperty(JCR_LAST_MODIFIED)) {
- Instant instant = node.getProperty(JCR_LAST_MODIFIED).getDate().toInstant();
- return FileTime.from(instant);
- } else if (node.hasProperty(JCR_CREATED)) {
- Instant instant = node.getProperty(JCR_CREATED).getDate().toInstant();
- return FileTime.from(instant);
- }
-// if (node.isNodeType(NodeType.MIX_LAST_MODIFIED)) {
-// Instant instant = node.getProperty(Property.JCR_LAST_MODIFIED).getDate().toInstant();
-// return FileTime.from(instant);
-// }
- return EPOCH;
- } catch (RepositoryException e) {
- throw new JcrFsException("Cannot get last modified time", e);
- }
- }
-
- @Override
- public FileTime lastAccessTime() {
- return lastModifiedTime();
- }
-
- @Override
- public FileTime creationTime() {
- try {
- if (node.hasProperty(JCR_CREATED)) {
- Instant instant = node.getProperty(JCR_CREATED).getDate().toInstant();
- return FileTime.from(instant);
- } else if (node.hasProperty(JCR_LAST_MODIFIED)) {
- Instant instant = node.getProperty(JCR_LAST_MODIFIED).getDate().toInstant();
- return FileTime.from(instant);
- }
-// if (node.isNodeType(NodeType.MIX_CREATED)) {
-// Instant instant = node.getProperty(JCR_CREATED).getDate().toInstant();
-// return FileTime.from(instant);
-// }
- return EPOCH;
- } catch (RepositoryException e) {
- throw new JcrFsException("Cannot get creation time", e);
- }
- }
-
- @Override
- public boolean isRegularFile() {
- try {
- return node.isNodeType(NodeType.NT_FILE);
- } catch (RepositoryException e) {
- throw new JcrFsException("Cannot check if regular file", e);
- }
- }
-
- @Override
- public boolean isDirectory() {
- try {
- if (node.isNodeType(NodeType.NT_FOLDER))
- return true;
- // all other non file nodes
- return !(node.isNodeType(NodeType.NT_FILE) || node.isNodeType(NodeType.NT_LINKED_FILE));
- } catch (RepositoryException e) {
- throw new JcrFsException("Cannot check if directory", e);
- }
- }
-
- @Override
- public boolean isSymbolicLink() {
- try {
- return node.isNodeType(NodeType.NT_LINKED_FILE);
- } catch (RepositoryException e) {
- throw new JcrFsException("Cannot check if linked file", e);
- }
- }
-
- @Override
- public boolean isOther() {
- return !(isDirectory() || isRegularFile() || isSymbolicLink());
- }
-
- @Override
- public long size() {
- if (isRegularFile()) {
- Binary binary = null;
- try {
- binary = node.getNode(Property.JCR_CONTENT).getProperty(Property.JCR_DATA).getBinary();
- return binary.getSize();
- } catch (RepositoryException e) {
- throw new JcrFsException("Cannot check size", e);
- } finally {
- JcrUtils.closeQuietly(binary);
- }
- }
- return -1;
- }
-
- @Override
- public Object fileKey() {
- try {
- return node.getIdentifier();
- } catch (RepositoryException e) {
- throw new JcrFsException("Cannot get identifier", e);
- }
- }
-
- @Override
- public Node getNode() {
- return node;
- }
-
-}
+++ /dev/null
-package org.argeo.jcr.fs;
-
-import java.io.IOException;
-import java.nio.file.FileStore;
-import java.nio.file.FileSystem;
-import java.nio.file.Path;
-import java.nio.file.PathMatcher;
-import java.nio.file.WatchService;
-import java.nio.file.attribute.UserPrincipalLookupService;
-import java.nio.file.spi.FileSystemProvider;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.TreeMap;
-
-import javax.jcr.Credentials;
-import javax.jcr.Node;
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.nodetype.NodeType;
-
-import org.argeo.jcr.Jcr;
-import org.argeo.jcr.JcrUtils;
-
-public class JcrFileSystem extends FileSystem {
- private final JcrFileSystemProvider provider;
-
- private final Repository repository;
- private Session session;
- private WorkspaceFileStore baseFileStore;
-
- private Map<String, WorkspaceFileStore> mounts = new TreeMap<>();
-
- private String userHomePath = null;
-
- @Deprecated
- public JcrFileSystem(JcrFileSystemProvider provider, Session session) throws IOException {
- super();
- this.provider = provider;
- baseFileStore = new WorkspaceFileStore(null, session.getWorkspace());
- this.session = session;
-// Node userHome = provider.getUserHome(session);
-// if (userHome != null)
-// try {
-// userHomePath = userHome.getPath();
-// } catch (RepositoryException e) {
-// throw new IOException("Cannot retrieve user home path", e);
-// }
- this.repository = null;
- }
-
- public JcrFileSystem(JcrFileSystemProvider provider, Repository repository) throws IOException {
- this(provider, repository, null);
- }
-
- public JcrFileSystem(JcrFileSystemProvider provider, Repository repository, Credentials credentials)
- throws IOException {
- super();
- this.provider = provider;
- this.repository = repository;
- try {
- this.session = credentials == null ? repository.login() : repository.login(credentials);
- baseFileStore = new WorkspaceFileStore(null, session.getWorkspace());
- workspaces: for (String workspaceName : baseFileStore.getWorkspace().getAccessibleWorkspaceNames()) {
- if (workspaceName.equals(baseFileStore.getWorkspace().getName()))
- continue workspaces;// do not mount base
- if (workspaceName.equals("security")) {
- continue workspaces;// do not mount security workspace
- // TODO make it configurable
- }
- Session mountSession = credentials == null ? repository.login(workspaceName)
- : repository.login(credentials, workspaceName);
- String mountPath = JcrPath.separator + workspaceName;
- mounts.put(mountPath, new WorkspaceFileStore(mountPath, mountSession.getWorkspace()));
- }
- } catch (RepositoryException e) {
- throw new IOException("Cannot initialise file system", e);
- }
-
- Node userHome = provider.getUserHome(repository);
- if (userHome != null)
- try {
- userHomePath = toFsPath(userHome);
- } catch (RepositoryException e) {
- throw new IOException("Cannot retrieve user home path", e);
- } finally {
- JcrUtils.logoutQuietly(Jcr.session(userHome));
- }
- }
-
- public String toFsPath(Node node) throws RepositoryException {
- return getFileStore(node).toFsPath(node);
- }
-
- /** Whether this node should be skipped in directory listings */
- public boolean skipNode(Node node) throws RepositoryException {
- if (node.isNodeType(NodeType.NT_HIERARCHY_NODE))
- return false;
- return true;
- }
-
- public String getUserHomePath() {
- return userHomePath;
- }
-
- public WorkspaceFileStore getFileStore(String path) {
- WorkspaceFileStore res = baseFileStore;
- for (String mountPath : mounts.keySet()) {
- if (path.equals(mountPath))
- return mounts.get(mountPath);
- if (path.startsWith(mountPath + JcrPath.separator)) {
- res = mounts.get(mountPath);
- // we keep the last one
- }
- }
- assert res != null;
- return res;
- }
-
- public WorkspaceFileStore getFileStore(Node node) throws RepositoryException {
- String workspaceName = node.getSession().getWorkspace().getName();
- if (workspaceName.equals(baseFileStore.getWorkspace().getName()))
- return baseFileStore;
- for (String mountPath : mounts.keySet()) {
- WorkspaceFileStore fileStore = mounts.get(mountPath);
- if (workspaceName.equals(fileStore.getWorkspace().getName()))
- return fileStore;
- }
- throw new IllegalStateException("No workspace mount found for " + node + " in workspace " + workspaceName);
- }
-
- public Iterator<JcrPath> listDirectMounts(Path base) {
- String baseStr = base.toString();
- Set<JcrPath> res = new HashSet<>();
- mounts: for (String mountPath : mounts.keySet()) {
- if (mountPath.equals(baseStr))
- continue mounts;
- if (mountPath.startsWith(baseStr)) {
- JcrPath path = new JcrPath(this, mountPath);
- Path relPath = base.relativize(path);
- if (relPath.getNameCount() == 1)
- res.add(path);
- }
- }
- return res.iterator();
- }
-
- public WorkspaceFileStore getBaseFileStore() {
- return baseFileStore;
- }
-
- @Override
- public FileSystemProvider provider() {
- return provider;
- }
-
- @Override
- public void close() throws IOException {
- JcrUtils.logoutQuietly(session);
- for (String mountPath : mounts.keySet()) {
- WorkspaceFileStore fileStore = mounts.get(mountPath);
- try {
- fileStore.close();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
-
- @Override
- public boolean isOpen() {
- return session.isLive();
- }
-
- @Override
- public boolean isReadOnly() {
- return false;
- }
-
- @Override
- public String getSeparator() {
- return JcrPath.separator;
- }
-
- @Override
- public Iterable<Path> getRootDirectories() {
- Set<Path> single = new HashSet<>();
- single.add(new JcrPath(this, JcrPath.separator));
- return single;
- }
-
- @Override
- public Iterable<FileStore> getFileStores() {
- List<FileStore> stores = new ArrayList<>();
- stores.add(baseFileStore);
- stores.addAll(mounts.values());
- return stores;
- }
-
- @Override
- public Set<String> supportedFileAttributeViews() {
- try {
- String[] prefixes = session.getNamespacePrefixes();
- Set<String> res = new HashSet<>();
- for (String prefix : prefixes)
- res.add(prefix);
- res.add("basic");
- return res;
- } catch (RepositoryException e) {
- throw new JcrFsException("Cannot get supported file attributes views", e);
- }
- }
-
- @Override
- public Path getPath(String first, String... more) {
- StringBuilder sb = new StringBuilder(first);
- // TODO Make it more robust
- for (String part : more)
- sb.append('/').append(part);
- return new JcrPath(this, sb.toString());
- }
-
- @Override
- public PathMatcher getPathMatcher(String syntaxAndPattern) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public UserPrincipalLookupService getUserPrincipalLookupService() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public WatchService newWatchService() throws IOException {
- throw new UnsupportedOperationException();
- }
-
-// public Session getSession() {
-// return session;
-// }
-
- public Repository getRepository() {
- return repository;
- }
-
-}
+++ /dev/null
-package org.argeo.jcr.fs;
-
-import java.io.IOException;
-import java.nio.channels.SeekableByteChannel;
-import java.nio.file.AccessMode;
-import java.nio.file.CopyOption;
-import java.nio.file.DirectoryNotEmptyException;
-import java.nio.file.DirectoryStream;
-import java.nio.file.DirectoryStream.Filter;
-import java.nio.file.FileStore;
-import java.nio.file.LinkOption;
-import java.nio.file.NoSuchFileException;
-import java.nio.file.OpenOption;
-import java.nio.file.Path;
-import java.nio.file.attribute.BasicFileAttributes;
-import java.nio.file.attribute.FileAttribute;
-import java.nio.file.attribute.FileAttributeView;
-import java.nio.file.spi.FileSystemProvider;
-import java.util.Calendar;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Set;
-
-import javax.jcr.Node;
-import javax.jcr.Property;
-import javax.jcr.PropertyIterator;
-import javax.jcr.PropertyType;
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.nodetype.NodeType;
-import javax.jcr.nodetype.PropertyDefinition;
-
-import org.argeo.jcr.JcrUtils;
-
-/** Operations on a {@link JcrFileSystem}. */
-public abstract class JcrFileSystemProvider extends FileSystemProvider {
-
- @Override
- public SeekableByteChannel newByteChannel(Path path, Set<? extends OpenOption> options, FileAttribute<?>... attrs)
- throws IOException {
- Node node = toNode(path);
- try {
- if (node == null) {
- Node parent = toNode(path.getParent());
- if (parent == null)
- throw new IOException("No parent directory for " + path);
- if (parent.getPrimaryNodeType().isNodeType(NodeType.NT_FILE)
- || parent.getPrimaryNodeType().isNodeType(NodeType.NT_LINKED_FILE))
- throw new IOException(path + " parent is a file");
-
- String fileName = path.getFileName().toString();
- fileName = Text.escapeIllegalJcrChars(fileName);
- node = parent.addNode(fileName, NodeType.NT_FILE);
- node.addMixin(NodeType.MIX_CREATED);
-// node.addMixin(NodeType.MIX_LAST_MODIFIED);
- }
- if (!node.isNodeType(NodeType.NT_FILE))
- throw new UnsupportedOperationException(node + " must be a file");
- return new BinaryChannel(node, path);
- } catch (RepositoryException e) {
- discardChanges(node);
- throw new IOException("Cannot read file", e);
- }
- }
-
- @Override
- public DirectoryStream<Path> newDirectoryStream(Path dir, Filter<? super Path> filter) throws IOException {
- try {
- Node base = toNode(dir);
- if (base == null)
- throw new IOException(dir + " is not a JCR node");
- JcrFileSystem fileSystem = (JcrFileSystem) dir.getFileSystem();
- return new NodeDirectoryStream(fileSystem, base.getNodes(), fileSystem.listDirectMounts(dir), filter);
- } catch (RepositoryException e) {
- throw new IOException("Cannot list directory", e);
- }
- }
-
- @Override
- public void createDirectory(Path dir, FileAttribute<?>... attrs) throws IOException {
- Node node = toNode(dir);
- try {
- if (node == null) {
- Node parent = toNode(dir.getParent());
- if (parent == null)
- throw new IOException("Parent of " + dir + " does not exist");
- 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");
- }
- } catch (RepositoryException e) {
- discardChanges(node);
- throw new IOException("Cannot create directory " + dir, e);
- }
- }
-
- @Override
- public void delete(Path path) throws IOException {
- Node node = toNode(path);
- try {
- if (node == null)
- throw new NoSuchFileException(path + " does not exist");
- Session session = node.getSession();
- 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);
- }
- } catch (RepositoryException e) {
- discardChanges(node);
- throw new IOException("Cannot delete " + path, e);
- }
-
- }
-
- @Override
- public void copy(Path source, Path target, CopyOption... options) throws IOException {
- Node sourceNode = toNode(source);
- Node targetNode = toNode(target);
- try {
- Session targetSession = targetNode.getSession();
- synchronized (targetSession) {
- JcrUtils.copy(sourceNode, targetNode);
- save(targetSession);
- }
- } catch (RepositoryException e) {
- discardChanges(sourceNode);
- discardChanges(targetNode);
- throw new IOException("Cannot copy from " + source + " to " + target, e);
- }
- }
-
- @Override
- public void move(Path source, Path target, CopyOption... options) throws IOException {
- JcrFileSystem sourceFileSystem = (JcrFileSystem) source.getFileSystem();
- WorkspaceFileStore sourceStore = sourceFileSystem.getFileStore(source.toString());
- WorkspaceFileStore targetStore = sourceFileSystem.getFileStore(target.toString());
- try {
- if (sourceStore.equals(targetStore)) {
- sourceStore.getWorkspace().move(sourceStore.toJcrPath(source.toString()),
- targetStore.toJcrPath(target.toString()));
- } else {
- // TODO implement it
- throw new UnsupportedOperationException("Can only move paths within the same workspace.");
- }
- } catch (RepositoryException e) {
- throw new IOException("Cannot move from " + source + " to " + target, e);
- }
-
-// Node sourceNode = toNode(source);
-// try {
-// Session session = sourceNode.getSession();
-// 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);
-// }
- }
-
- @Override
- public boolean isSameFile(Path path, Path path2) throws IOException {
- if (path.getFileSystem() != path2.getFileSystem())
- return false;
- boolean equ = path.equals(path2);
- if (equ)
- return true;
- else {
- try {
- Node node = toNode(path);
- Node node2 = toNode(path2);
- return node.isSame(node2);
- } catch (RepositoryException e) {
- throw new IOException("Cannot check whether " + path + " and " + path2 + " are same", e);
- }
- }
-
- }
-
- @Override
- public boolean isHidden(Path path) throws IOException {
- return path.getFileName().toString().charAt(0) == '.';
- }
-
- @Override
- public FileStore getFileStore(Path path) throws IOException {
- JcrFileSystem fileSystem = (JcrFileSystem) path.getFileSystem();
- return fileSystem.getFileStore(path.toString());
- }
-
- @Override
- public void checkAccess(Path path, AccessMode... modes) throws IOException {
- Node node = toNode(path);
- if (node == null)
- throw new NoSuchFileException(path + " does not exist");
- // TODO check access via JCR api
- }
-
- @Override
- public <V extends FileAttributeView> V getFileAttributeView(Path path, Class<V> type, LinkOption... options) {
- throw new UnsupportedOperationException();
- }
-
- @SuppressWarnings("unchecked")
- @Override
- public <A extends BasicFileAttributes> A readAttributes(Path path, Class<A> type, LinkOption... options)
- throws IOException {
- // TODO check if assignable
- Node node = toNode(path);
- if (node == null) {
- throw new IOException("JCR node not found for " + path);
- }
- return (A) new JcrBasicfileAttributes(node);
- }
-
- @Override
- public Map<String, Object> readAttributes(Path path, String attributes, LinkOption... options) throws IOException {
- try {
- Node node = toNode(path);
- String pattern = attributes.replace(',', '|');
- Map<String, Object> res = new HashMap<String, Object>();
- PropertyIterator it = node.getProperties(pattern);
- props: while (it.hasNext()) {
- Property prop = it.nextProperty();
- PropertyDefinition pd = prop.getDefinition();
- if (pd.isMultiple())
- continue props;
- int requiredType = pd.getRequiredType();
- switch (requiredType) {
- case PropertyType.LONG:
- res.put(prop.getName(), prop.getLong());
- break;
- case PropertyType.DOUBLE:
- res.put(prop.getName(), prop.getDouble());
- break;
- case PropertyType.BOOLEAN:
- res.put(prop.getName(), prop.getBoolean());
- break;
- case PropertyType.DATE:
- res.put(prop.getName(), prop.getDate());
- break;
- case PropertyType.BINARY:
- byte[] arr = JcrUtils.getBinaryAsBytes(prop);
- res.put(prop.getName(), arr);
- break;
- default:
- res.put(prop.getName(), prop.getString());
- }
- }
- return res;
- } catch (RepositoryException e) {
- throw new IOException("Cannot read attributes of " + path, e);
- }
- }
-
- @Override
- public void setAttribute(Path path, String attribute, Object value, LinkOption... options) throws IOException {
- Node node = toNode(path);
- try {
- 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);
- }
- } catch (RepositoryException e) {
- discardChanges(node);
- throw new IOException("Cannot set attribute " + attribute + " on " + path, e);
- }
- }
-
- protected Node toNode(Path path) {
- try {
- return ((JcrPath) path).getNode();
- } catch (RepositoryException e) {
- throw new JcrFsException("Cannot convert path " + path + " to JCR Node", e);
- }
- }
-
- /** Discard changes in the underlying session */
- protected void discardChanges(Node node) {
- if (node == null)
- return;
- try {
- // discard changes
- node.getSession().refresh(false);
- } catch (RepositoryException e) {
- e.printStackTrace();
- // TODO log out session?
- // TODO use Commons logging?
- }
- }
-
- /** 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.
- *
- * @return null by default
- */
- public Node getUserHome(Repository session) {
- return null;
- }
-
-}
+++ /dev/null
-package org.argeo.jcr.fs;
-
-
-/** Exception related to the JCR FS */
-public class JcrFsException extends RuntimeException {
- private static final long serialVersionUID = -7973896038244922980L;
-
- public JcrFsException(String message, Throwable e) {
- super(message, e);
- }
-
- public JcrFsException(String message) {
- super(message);
- }
-}
+++ /dev/null
-package org.argeo.jcr.fs;
-
-import java.io.File;
-import java.io.IOException;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.nio.file.FileSystem;
-import java.nio.file.LinkOption;
-import java.nio.file.Path;
-import java.nio.file.WatchEvent.Kind;
-import java.nio.file.WatchEvent.Modifier;
-import java.nio.file.WatchKey;
-import java.nio.file.WatchService;
-import java.util.Arrays;
-import java.util.Iterator;
-import java.util.NoSuchElementException;
-
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-
-/** A {@link Path} which contains a reference to a JCR {@link Node}. */
-public class JcrPath implements Path {
- final static String separator = "/";
- final static char separatorChar = '/';
-
- private final JcrFileSystem fs;
- /** null for non absolute paths */
- private final WorkspaceFileStore fileStore;
- private final String[] path;// null means root
- private final boolean absolute;
-
- // optim
- private final int hashCode;
-
- public JcrPath(JcrFileSystem filesSystem, String path) {
- this.fs = filesSystem;
- if (path == null)
- throw new JcrFsException("Path cannot be null");
- if (path.equals(separator)) {// root
- this.path = null;
- this.absolute = true;
- this.hashCode = 0;
- this.fileStore = fs.getBaseFileStore();
- return;
- } else if (path.equals("")) {// empty path
- this.path = new String[] { "" };
- this.absolute = false;
- this.fileStore = null;
- this.hashCode = "".hashCode();
- return;
- }
-
- if (path.equals("~")) {// home
- path = filesSystem.getUserHomePath();
- if (path == null)
- throw new JcrFsException("No home directory available");
- }
-
- this.absolute = path.charAt(0) == separatorChar ? true : false;
-
- this.fileStore = absolute ? fs.getFileStore(path) : null;
-
- String trimmedPath = path.substring(absolute ? 1 : 0,
- path.charAt(path.length() - 1) == separatorChar ? path.length() - 1 : path.length());
- this.path = trimmedPath.split(separator);
- for (int i = 0; i < this.path.length; i++) {
- this.path[i] = Text.unescapeIllegalJcrChars(this.path[i]);
- }
- this.hashCode = this.path[this.path.length - 1].hashCode();
- assert !(absolute && fileStore == null);
- }
-
- public JcrPath(JcrFileSystem filesSystem, Node node) throws RepositoryException {
- this(filesSystem, filesSystem.getFileStore(node).toFsPath(node));
- }
-
- /** Internal optimisation */
- private JcrPath(JcrFileSystem filesSystem, WorkspaceFileStore fileStore, String[] path, boolean absolute) {
- this.fs = filesSystem;
- this.path = path;
- this.absolute = path == null ? true : absolute;
- if (this.absolute && fileStore == null)
- throw new IllegalArgumentException("Absolute path requires a file store");
- if (!this.absolute && fileStore != null)
- throw new IllegalArgumentException("A file store should not be provided for a relative path");
- this.fileStore = fileStore;
- this.hashCode = path == null ? 0 : path[path.length - 1].hashCode();
- assert !(absolute && fileStore == null);
- }
-
- @Override
- public FileSystem getFileSystem() {
- return fs;
- }
-
- @Override
- public boolean isAbsolute() {
- return absolute;
- }
-
- @Override
- public Path getRoot() {
- if (path == null)
- return this;
- return new JcrPath(fs, separator);
- }
-
- @Override
- public String toString() {
- return toFsPath(path);
- }
-
- private String toFsPath(String[] path) {
- if (path == null)
- return "/";
- StringBuilder sb = new StringBuilder();
- if (isAbsolute())
- sb.append('/');
- for (int i = 0; i < path.length; i++) {
- if (i != 0)
- sb.append('/');
- sb.append(path[i]);
- }
- return sb.toString();
- }
-
-// @Deprecated
-// private String toJcrPath() {
-// return toJcrPath(path);
-// }
-//
-// @Deprecated
-// private String toJcrPath(String[] path) {
-// if (path == null)
-// return "/";
-// StringBuilder sb = new StringBuilder();
-// if (isAbsolute())
-// sb.append('/');
-// for (int i = 0; i < path.length; i++) {
-// if (i != 0)
-// sb.append('/');
-// sb.append(Text.escapeIllegalJcrChars(path[i]));
-// }
-// return sb.toString();
-// }
-
- @Override
- public Path getFileName() {
- if (path == null)
- return null;
- return new JcrPath(fs, path[path.length - 1]);
- }
-
- @Override
- public Path getParent() {
- if (path == null)
- return null;
- if (path.length == 1)// root
- return new JcrPath(fs, separator);
- String[] parentPath = Arrays.copyOfRange(path, 0, path.length - 1);
- if (!absolute)
- return new JcrPath(fs, null, parentPath, absolute);
- else
- return new JcrPath(fs, toFsPath(parentPath));
- }
-
- @Override
- public int getNameCount() {
- if (path == null)
- return 0;
- return path.length;
- }
-
- @Override
- public Path getName(int index) {
- if (path == null)
- return null;
- return new JcrPath(fs, path[index]);
- }
-
- @Override
- public Path subpath(int beginIndex, int endIndex) {
- if (path == null)
- return null;
- String[] parentPath = Arrays.copyOfRange(path, beginIndex, endIndex);
- return new JcrPath(fs, null, parentPath, false);
- }
-
- @Override
- public boolean startsWith(Path other) {
- return toString().startsWith(other.toString());
- }
-
- @Override
- public boolean startsWith(String other) {
- return toString().startsWith(other);
- }
-
- @Override
- public boolean endsWith(Path other) {
- return toString().endsWith(other.toString());
- }
-
- @Override
- public boolean endsWith(String other) {
- return toString().endsWith(other);
- }
-
- @Override
- public Path normalize() {
- // always normalized
- return this;
- }
-
- @Override
- public Path resolve(Path other) {
- JcrPath otherPath = (JcrPath) other;
- if (otherPath.isAbsolute())
- return other;
- String[] newPath;
- if (path == null) {
- newPath = new String[otherPath.path.length];
- System.arraycopy(otherPath.path, 0, newPath, 0, otherPath.path.length);
- } else {
- newPath = new String[path.length + otherPath.path.length];
- System.arraycopy(path, 0, newPath, 0, path.length);
- System.arraycopy(otherPath.path, 0, newPath, path.length, otherPath.path.length);
- }
- if (!absolute)
- return new JcrPath(fs, null, newPath, absolute);
- else {
- return new JcrPath(fs, toFsPath(newPath));
- }
- }
-
- @Override
- public final Path resolve(String other) {
- return resolve(getFileSystem().getPath(other));
- }
-
- @Override
- public final Path resolveSibling(Path other) {
- if (other == null)
- throw new NullPointerException();
- Path parent = getParent();
- return (parent == null) ? other : parent.resolve(other);
- }
-
- @Override
- public final Path resolveSibling(String other) {
- return resolveSibling(getFileSystem().getPath(other));
- }
-
- @Override
- public final Iterator<Path> iterator() {
- return new Iterator<Path>() {
- private int i = 0;
-
- @Override
- public boolean hasNext() {
- return (i < getNameCount());
- }
-
- @Override
- public Path next() {
- if (i < getNameCount()) {
- Path result = getName(i);
- i++;
- return result;
- } else {
- throw new NoSuchElementException();
- }
- }
-
- @Override
- public void remove() {
- throw new UnsupportedOperationException();
- }
- };
- }
-
- @Override
- public Path relativize(Path other) {
- if (equals(other))
- return new JcrPath(fs, "");
- if (other.startsWith(this)) {
- String p1 = toString();
- String p2 = other.toString();
- String relative = p2.substring(p1.length(), p2.length());
- if (relative.charAt(0) == '/')
- relative = relative.substring(1);
- return new JcrPath(fs, relative);
- }
- throw new IllegalArgumentException(other + " cannot be relativized against " + this);
- }
-
- @Override
- public URI toUri() {
- try {
- return new URI(fs.provider().getScheme(), toString(), null);
- } catch (URISyntaxException e) {
- throw new JcrFsException("Cannot create URI for " + toString(), e);
- }
- }
-
- @Override
- public Path toAbsolutePath() {
- if (isAbsolute())
- return this;
- return new JcrPath(fs, fileStore, path, true);
- }
-
- @Override
- public Path toRealPath(LinkOption... options) throws IOException {
- return this;
- }
-
- @Override
- public File toFile() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public WatchKey register(WatchService watcher, Kind<?>[] events, Modifier... modifiers) throws IOException {
- // TODO Auto-generated method stub
- return null;
- }
-
- @Override
- public WatchKey register(WatchService watcher, Kind<?>... events) throws IOException {
- // TODO Auto-generated method stub
- return null;
- }
-
- @Override
- public int compareTo(Path other) {
- return toString().compareTo(other.toString());
- }
-
- public Node getNode() throws RepositoryException {
- if (!isAbsolute())// TODO default dir
- throw new JcrFsException("Cannot get a JCR node from a relative path");
- assert fileStore != null;
- return fileStore.toNode(path);
-// String pathStr = toJcrPath();
-// Session session = fs.getSession();
-// // TODO synchronize on the session ?
-// if (!session.itemExists(pathStr))
-// return null;
-// return session.getNode(pathStr);
- }
-
- @Override
- public boolean equals(Object obj) {
- if (!(obj instanceof JcrPath))
- return false;
- JcrPath other = (JcrPath) obj;
-
- if (path == null) {// root
- if (other.path == null)// root
- return true;
- else
- return false;
- } else {
- if (other.path == null)// root
- return false;
- }
- // non root
- if (path.length != other.path.length)
- return false;
- for (int i = 0; i < path.length; i++) {
- if (!path[i].equals(other.path[i]))
- return false;
- }
- return true;
- }
-
- @Override
- public int hashCode() {
- return hashCode;
- }
-
- @Override
- protected Object clone() throws CloneNotSupportedException {
- return new JcrPath(fs, toString());
- }
-
- @Override
- protected void finalize() throws Throwable {
- Arrays.fill(path, null);
- }
-
-}
+++ /dev/null
-package org.argeo.jcr.fs;
-
-import java.io.IOException;
-import java.nio.file.DirectoryStream;
-import java.nio.file.Path;
-import java.util.Iterator;
-
-import javax.jcr.Node;
-import javax.jcr.NodeIterator;
-
-public class NodeDirectoryStream implements DirectoryStream<Path> {
- private final JcrFileSystem fs;
- private final NodeIterator nodeIterator;
- private final Iterator<JcrPath> additionalPaths;
- private final Filter<? super Path> filter;
-
- public NodeDirectoryStream(JcrFileSystem fs, NodeIterator nodeIterator, Iterator<JcrPath> additionalPaths,
- Filter<? super Path> filter) {
- this.fs = fs;
- this.nodeIterator = nodeIterator;
- this.additionalPaths = additionalPaths;
- this.filter = filter;
- }
-
- @Override
- public void close() throws IOException {
- }
-
- @Override
- public Iterator<Path> iterator() {
- return new Iterator<Path>() {
- private JcrPath next = null;
-
- @Override
- public synchronized boolean hasNext() {
- if (next != null)
- return true;
- nodes: while (nodeIterator.hasNext()) {
- try {
- Node node = nodeIterator.nextNode();
- String nodeName = node.getName();
- if (nodeName.startsWith("rep:") || nodeName.startsWith("jcr:"))
- continue nodes;
- if (fs.skipNode(node))
- continue nodes;
- next = new JcrPath(fs, node);
- if (filter != null) {
- if (filter.accept(next))
- break nodes;
- } else
- break nodes;
- } catch (Exception e) {
- throw new JcrFsException("Could not get next path", e);
- }
- }
-
- if (next == null) {
- if (additionalPaths.hasNext())
- next = additionalPaths.next();
- }
-
- return next != null;
- }
-
- @Override
- public synchronized Path next() {
- if (!hasNext())// make sure has next has been called
- return null;
- JcrPath res = next;
- next = null;
- return res;
- }
-
- };
- }
-
-}
+++ /dev/null
-package org.argeo.jcr.fs;
-
-import java.nio.file.attribute.BasicFileAttributes;
-
-import javax.jcr.Node;
-
-public interface NodeFileAttributes extends BasicFileAttributes {
- public Node getNode();
-}
+++ /dev/null
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You 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.
- */
-package org.argeo.jcr.fs;
-
-import java.io.ByteArrayOutputStream;
-import java.io.UnsupportedEncodingException;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.util.ArrayList;
-import java.util.BitSet;
-import java.util.Properties;
-
-/**
- * <b>Hacked from org.apache.jackrabbit.util.Text in Jackrabbit JCR Commons</b>
- * This Class provides some text related utilities
- */
-class Text {
-
- /**
- * Hidden constructor.
- */
- private Text() {
- }
-
- /**
- * used for the md5
- */
- public static final char[] hexTable = "0123456789abcdef".toCharArray();
-
- /**
- * Calculate an MD5 hash of the string given.
- *
- * @param data
- * the data to encode
- * @param enc
- * the character encoding to use
- * @return a hex encoded string of the md5 digested input
- */
- public static String md5(String data, String enc) throws UnsupportedEncodingException {
- try {
- return digest("MD5", data.getBytes(enc));
- } catch (NoSuchAlgorithmException e) {
- throw new InternalError("MD5 digest not available???");
- }
- }
-
- /**
- * Calculate an MD5 hash of the string given using 'utf-8' encoding.
- *
- * @param data
- * the data to encode
- * @return a hex encoded string of the md5 digested input
- */
- public static String md5(String data) {
- try {
- return md5(data, "utf-8");
- } catch (UnsupportedEncodingException e) {
- throw new InternalError("UTF8 digest not available???");
- }
- }
-
- /**
- * Digest the plain string using the given algorithm.
- *
- * @param algorithm
- * The alogrithm for the digest. This algorithm must be supported
- * by the MessageDigest class.
- * @param data
- * The plain text String to be digested.
- * @param enc
- * The character encoding to use
- * @return The digested plain text String represented as Hex digits.
- * @throws java.security.NoSuchAlgorithmException
- * if the desired algorithm is not supported by the
- * MessageDigest class.
- * @throws java.io.UnsupportedEncodingException
- * if the encoding is not supported
- */
- public static String digest(String algorithm, String data, String enc)
- throws NoSuchAlgorithmException, UnsupportedEncodingException {
-
- return digest(algorithm, data.getBytes(enc));
- }
-
- /**
- * Digest the plain string using the given algorithm.
- *
- * @param algorithm
- * The algorithm for the digest. This algorithm must be supported
- * by the MessageDigest class.
- * @param data
- * the data to digest with the given algorithm
- * @return The digested plain text String represented as Hex digits.
- * @throws java.security.NoSuchAlgorithmException
- * if the desired algorithm is not supported by the
- * MessageDigest class.
- */
- public static String digest(String algorithm, byte[] data) throws NoSuchAlgorithmException {
-
- MessageDigest md = MessageDigest.getInstance(algorithm);
- byte[] digest = md.digest(data);
- StringBuilder res = new StringBuilder(digest.length * 2);
- for (byte b : digest) {
- res.append(hexTable[(b >> 4) & 15]);
- res.append(hexTable[b & 15]);
- }
- return res.toString();
- }
-
- /**
- * returns an array of strings decomposed of the original string, split at
- * every occurrence of 'ch'. if 2 'ch' follow each other with no
- * intermediate characters, empty "" entries are avoided.
- *
- * @param str
- * the string to decompose
- * @param ch
- * the character to use a split pattern
- * @return an array of strings
- */
- public static String[] explode(String str, int ch) {
- return explode(str, ch, false);
- }
-
- /**
- * returns an array of strings decomposed of the original string, split at
- * every occurrence of 'ch'.
- *
- * @param str
- * the string to decompose
- * @param ch
- * the character to use a split pattern
- * @param respectEmpty
- * if <code>true</code>, empty elements are generated
- * @return an array of strings
- */
- public static String[] explode(String str, int ch, boolean respectEmpty) {
- if (str == null || str.length() == 0) {
- return new String[0];
- }
-
- ArrayList<String> strings = new ArrayList<String>();
- int pos;
- int lastpos = 0;
-
- // add snipples
- while ((pos = str.indexOf(ch, lastpos)) >= 0) {
- if (pos - lastpos > 0 || respectEmpty) {
- strings.add(str.substring(lastpos, pos));
- }
- lastpos = pos + 1;
- }
- // add rest
- if (lastpos < str.length()) {
- strings.add(str.substring(lastpos));
- } else if (respectEmpty && lastpos == str.length()) {
- strings.add("");
- }
-
- // return string array
- return strings.toArray(new String[strings.size()]);
- }
-
- /**
- * Concatenates all strings in the string array using the specified
- * delimiter.
- *
- * @param arr
- * @param delim
- * @return the concatenated string
- */
- public static String implode(String[] arr, String delim) {
- StringBuilder buf = new StringBuilder();
- for (int i = 0; i < arr.length; i++) {
- if (i > 0) {
- buf.append(delim);
- }
- buf.append(arr[i]);
- }
- return buf.toString();
- }
-
- /**
- * Replaces all occurrences of <code>oldString</code> in <code>text</code>
- * with <code>newString</code>.
- *
- * @param text
- * @param oldString
- * old substring to be replaced with <code>newString</code>
- * @param newString
- * new substring to replace occurrences of <code>oldString</code>
- * @return a string
- */
- public static String replace(String text, String oldString, String newString) {
- if (text == null || oldString == null || newString == null) {
- throw new IllegalArgumentException("null argument");
- }
- int pos = text.indexOf(oldString);
- if (pos == -1) {
- return text;
- }
- int lastPos = 0;
- StringBuilder sb = new StringBuilder(text.length());
- while (pos != -1) {
- sb.append(text.substring(lastPos, pos));
- sb.append(newString);
- lastPos = pos + oldString.length();
- pos = text.indexOf(oldString, lastPos);
- }
- if (lastPos < text.length()) {
- sb.append(text.substring(lastPos));
- }
- return sb.toString();
- }
-
- /**
- * Replaces XML characters in the given string that might need escaping as
- * XML text or attribute
- *
- * @param text
- * text to be escaped
- * @return a string
- */
- public static String encodeIllegalXMLCharacters(String text) {
- return encodeMarkupCharacters(text, false);
- }
-
- /**
- * Replaces HTML characters in the given string that might need escaping as
- * HTML text or attribute
- *
- * @param text
- * text to be escaped
- * @return a string
- */
- public static String encodeIllegalHTMLCharacters(String text) {
- return encodeMarkupCharacters(text, true);
- }
-
- private static String encodeMarkupCharacters(String text, boolean isHtml) {
- if (text == null) {
- throw new IllegalArgumentException("null argument");
- }
- StringBuilder buf = null;
- int length = text.length();
- int pos = 0;
- for (int i = 0; i < length; i++) {
- int ch = text.charAt(i);
- switch (ch) {
- case '<':
- case '>':
- case '&':
- case '"':
- case '\'':
- if (buf == null) {
- buf = new StringBuilder();
- }
- if (i > 0) {
- buf.append(text.substring(pos, i));
- }
- pos = i + 1;
- break;
- default:
- continue;
- }
- if (ch == '<') {
- buf.append("<");
- } else if (ch == '>') {
- buf.append(">");
- } else if (ch == '&') {
- buf.append("&");
- } else if (ch == '"') {
- buf.append(""");
- } else if (ch == '\'') {
- buf.append(isHtml ? "'" : "'");
- }
- }
- if (buf == null) {
- return text;
- } else {
- if (pos < length) {
- buf.append(text.substring(pos));
- }
- return buf.toString();
- }
- }
-
- /**
- * The list of characters that are not encoded by the <code>escape()</code>
- * and <code>unescape()</code> METHODS. They contains the characters as
- * defined 'unreserved' in section 2.3 of the RFC 2396 'URI generic syntax':
- * <p>
- *
- * <pre>
- * unreserved = alphanum | mark
- * mark = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")"
- * </pre>
- */
- public static BitSet URISave;
-
- /**
- * Same as {@link #URISave} but also contains the '/'
- */
- public static BitSet URISaveEx;
-
- static {
- URISave = new BitSet(256);
- int i;
- for (i = 'a'; i <= 'z'; i++) {
- URISave.set(i);
- }
- for (i = 'A'; i <= 'Z'; i++) {
- URISave.set(i);
- }
- for (i = '0'; i <= '9'; i++) {
- URISave.set(i);
- }
- URISave.set('-');
- URISave.set('_');
- URISave.set('.');
- URISave.set('!');
- URISave.set('~');
- URISave.set('*');
- URISave.set('\'');
- URISave.set('(');
- URISave.set(')');
-
- URISaveEx = (BitSet) URISave.clone();
- URISaveEx.set('/');
- }
-
- /**
- * Does an URL encoding of the <code>string</code> using the
- * <code>escape</code> character. The characters that don't need encoding
- * are those defined 'unreserved' in section 2.3 of the 'URI generic syntax'
- * RFC 2396, but without the escape character.
- *
- * @param string
- * the string to encode.
- * @param escape
- * the escape character.
- * @return the escaped string
- * @throws NullPointerException
- * if <code>string</code> is <code>null</code>.
- */
- public static String escape(String string, char escape) {
- return escape(string, escape, false);
- }
-
- /**
- * Does an URL encoding of the <code>string</code> using the
- * <code>escape</code> character. The characters that don't need encoding
- * are those defined 'unreserved' in section 2.3 of the 'URI generic syntax'
- * RFC 2396, but without the escape character. If <code>isPath</code> is
- * <code>true</code>, additionally the slash '/' is ignored, too.
- *
- * @param string
- * the string to encode.
- * @param escape
- * the escape character.
- * @param isPath
- * if <code>true</code>, the string is treated as path
- * @return the escaped string
- * @throws NullPointerException
- * if <code>string</code> is <code>null</code>.
- */
- public static String escape(String string, char escape, boolean isPath) {
- try {
- BitSet validChars = isPath ? URISaveEx : URISave;
- byte[] bytes = string.getBytes("utf-8");
- StringBuilder out = new StringBuilder(bytes.length);
- for (byte aByte : bytes) {
- int c = aByte & 0xff;
- if (validChars.get(c) && c != escape) {
- out.append((char) c);
- } else {
- out.append(escape);
- out.append(hexTable[(c >> 4) & 0x0f]);
- out.append(hexTable[(c) & 0x0f]);
- }
- }
- return out.toString();
- } catch (UnsupportedEncodingException e) {
- throw new InternalError(e.toString());
- }
- }
-
- /**
- * Does a URL encoding of the <code>string</code>. The characters that don't
- * need encoding are those defined 'unreserved' in section 2.3 of the 'URI
- * generic syntax' RFC 2396.
- *
- * @param string
- * the string to encode
- * @return the escaped string
- * @throws NullPointerException
- * if <code>string</code> is <code>null</code>.
- */
- public static String escape(String string) {
- return escape(string, '%');
- }
-
- /**
- * Does a URL encoding of the <code>path</code>. The characters that don't
- * need encoding are those defined 'unreserved' in section 2.3 of the 'URI
- * generic syntax' RFC 2396. In contrast to the {@link #escape(String)}
- * method, not the entire path string is escaped, but every individual part
- * (i.e. the slashes are not escaped).
- *
- * @param path
- * the path to encode
- * @return the escaped path
- * @throws NullPointerException
- * if <code>path</code> is <code>null</code>.
- */
- public static String escapePath(String path) {
- return escape(path, '%', true);
- }
-
- /**
- * Does a URL decoding of the <code>string</code> using the
- * <code>escape</code> character. Please note that in opposite to the
- * {@link java.net.URLDecoder} it does not transform the + into spaces.
- *
- * @param string
- * the string to decode
- * @param escape
- * the escape character
- * @return the decoded string
- * @throws NullPointerException
- * if <code>string</code> is <code>null</code>.
- * @throws IllegalArgumentException
- * if the 2 characters following the escape character do not
- * represent a hex-number or if not enough characters follow an
- * escape character
- */
- public static String unescape(String string, char escape) {
- try {
- byte[] utf8 = string.getBytes("utf-8");
-
- // Check whether escape occurs at invalid position
- if ((utf8.length >= 1 && utf8[utf8.length - 1] == escape)
- || (utf8.length >= 2 && utf8[utf8.length - 2] == escape)) {
- throw new IllegalArgumentException("Premature end of escape sequence at end of input");
- }
-
- ByteArrayOutputStream out = new ByteArrayOutputStream(utf8.length);
- for (int k = 0; k < utf8.length; k++) {
- byte b = utf8[k];
- if (b == escape) {
- out.write((decodeDigit(utf8[++k]) << 4) + decodeDigit(utf8[++k]));
- } else {
- out.write(b);
- }
- }
-
- return new String(out.toByteArray(), "utf-8");
- } catch (UnsupportedEncodingException e) {
- throw new InternalError(e.toString());
- }
- }
-
- /**
- * Does a URL decoding of the <code>string</code>. Please note that in
- * opposite to the {@link java.net.URLDecoder} it does not transform the +
- * into spaces.
- *
- * @param string
- * the string to decode
- * @return the decoded string
- * @throws NullPointerException
- * if <code>string</code> is <code>null</code>.
- * @throws ArrayIndexOutOfBoundsException
- * if not enough character follow an escape character
- * @throws IllegalArgumentException
- * if the 2 characters following the escape character do not
- * represent a hex-number.
- */
- public static String unescape(String string) {
- return unescape(string, '%');
- }
-
- /**
- * Escapes all illegal JCR name characters of a string. The encoding is
- * loosely modeled after URI encoding, but only encodes the characters it
- * absolutely needs to in order to make the resulting string a valid JCR
- * name. Use {@link #unescapeIllegalJcrChars(String)} for decoding.
- * <p>
- * QName EBNF:<br>
- * <xmp> simplename ::= onecharsimplename | twocharsimplename |
- * threeormorecharname onecharsimplename ::= (* Any Unicode character
- * except: '.', '/', ':', '[', ']', '*', '|' or any whitespace character *)
- * twocharsimplename ::= '.' onecharsimplename | onecharsimplename '.' |
- * onecharsimplename onecharsimplename threeormorecharname ::= nonspace
- * string nonspace string ::= char | string char char ::= nonspace | ' '
- * nonspace ::= (* Any Unicode character except: '/', ':', '[', ']', '*',
- * '|' or any whitespace character *) </xmp>
- *
- * @param name
- * the name to escape
- * @return the escaped name
- */
- public static String escapeIllegalJcrChars(String name) {
- return escapeIllegalChars(name, "%/:[]*|\t\r\n");
- }
-
- /**
- * Escapes all illegal JCR 1.0 name characters of a string. Use
- * {@link #unescapeIllegalJcrChars(String)} for decoding.
- * <p>
- * QName EBNF:<br>
- * <xmp> simplename ::= onecharsimplename | twocharsimplename |
- * threeormorecharname onecharsimplename ::= (* Any Unicode character
- * except: '.', '/', ':', '[', ']', '*', ''', '"', '|' or any whitespace
- * character *) twocharsimplename ::= '.' onecharsimplename |
- * onecharsimplename '.' | onecharsimplename onecharsimplename
- * threeormorecharname ::= nonspace string nonspace string ::= char | string
- * char char ::= nonspace | ' ' nonspace ::= (* Any Unicode character
- * except: '/', ':', '[', ']', '*', ''', '"', '|' or any whitespace
- * character *) </xmp>
- *
- * @since Apache Jackrabbit 2.3.2 and 2.2.10
- * @see <a href=
- * "https://issues.apache.org/jira/browse/JCR-3128">JCR-3128</a>
- * @param name
- * the name to escape
- * @return the escaped name
- */
- public static String escapeIllegalJcr10Chars(String name) {
- return escapeIllegalChars(name, "%/:[]*'\"|\t\r\n");
- }
-
- private static String escapeIllegalChars(String name, String illegal) {
- StringBuilder buffer = new StringBuilder(name.length() * 2);
- for (int i = 0; i < name.length(); i++) {
- char ch = name.charAt(i);
- if (illegal.indexOf(ch) != -1 || (ch == '.' && name.length() < 3)
- || (ch == ' ' && (i == 0 || i == name.length() - 1))) {
- buffer.append('%');
- buffer.append(Character.toUpperCase(Character.forDigit(ch / 16, 16)));
- buffer.append(Character.toUpperCase(Character.forDigit(ch % 16, 16)));
- } else {
- buffer.append(ch);
- }
- }
- return buffer.toString();
- }
-
- /**
- * Escapes illegal XPath search characters at the end of a string.
- * <p>
- * Example:<br>
- * A search string like 'test?' will run into a ParseException documented in
- * http://issues.apache.org/jira/browse/JCR-1248
- *
- * @param s
- * the string to encode
- * @return the escaped string
- */
- public static String escapeIllegalXpathSearchChars(String s) {
- StringBuilder sb = new StringBuilder();
- sb.append(s.substring(0, (s.length() - 1)));
- char c = s.charAt(s.length() - 1);
- // NOTE: keep this in sync with _ESCAPED_CHAR below!
- if (c == '!' || c == '(' || c == ':' || c == '^' || c == '[' || c == ']' || c == '{' || c == '}' || c == '?') {
- sb.append('\\');
- }
- sb.append(c);
- return sb.toString();
- }
-
- /**
- * Unescapes previously escaped jcr chars.
- * <p>
- * Please note, that this does not exactly the same as the url related
- * {@link #unescape(String)}, since it handles the byte-encoding
- * differently.
- *
- * @param name
- * the name to unescape
- * @return the unescaped name
- */
- public static String unescapeIllegalJcrChars(String name) {
- StringBuilder buffer = new StringBuilder(name.length());
- int i = name.indexOf('%');
- while (i > -1 && i + 2 < name.length()) {
- buffer.append(name.toCharArray(), 0, i);
- int a = Character.digit(name.charAt(i + 1), 16);
- int b = Character.digit(name.charAt(i + 2), 16);
- if (a > -1 && b > -1) {
- buffer.append((char) (a * 16 + b));
- name = name.substring(i + 3);
- } else {
- buffer.append('%');
- name = name.substring(i + 1);
- }
- i = name.indexOf('%');
- }
- buffer.append(name);
- return buffer.toString();
- }
-
- /**
- * Returns the name part of the path. If the given path is already a name
- * (i.e. contains no slashes) it is returned.
- *
- * @param path
- * the path
- * @return the name part or <code>null</code> if <code>path</code> is
- * <code>null</code>.
- */
- public static String getName(String path) {
- return getName(path, '/');
- }
-
- /**
- * Returns the name part of the path, delimited by the given
- * <code>delim</code>. If the given path is already a name (i.e. contains no
- * <code>delim</code> characters) it is returned.
- *
- * @param path
- * the path
- * @param delim
- * the delimiter
- * @return the name part or <code>null</code> if <code>path</code> is
- * <code>null</code>.
- */
- public static String getName(String path, char delim) {
- return path == null ? null : path.substring(path.lastIndexOf(delim) + 1);
- }
-
- /**
- * Same as {@link #getName(String)} but adding the possibility to pass paths
- * that end with a trailing '/'
- *
- * @see #getName(String)
- */
- public static String getName(String path, boolean ignoreTrailingSlash) {
- if (ignoreTrailingSlash && path != null && path.endsWith("/") && path.length() > 1) {
- path = path.substring(0, path.length() - 1);
- }
- return getName(path);
- }
-
- /**
- * Returns the namespace prefix of the given <code>qname</code>. If the
- * prefix is missing, an empty string is returned. Please note, that this
- * method does not validate the name or prefix.
- * </p>
- * the qname has the format: qname := [prefix ':'] local;
- *
- * @param qname
- * a qualified name
- * @return the prefix of the name or "".
- *
- * @see #getLocalName(String)
- *
- * @throws NullPointerException
- * if <code>qname</code> is <code>null</code>
- */
- public static String getNamespacePrefix(String qname) {
- int pos = qname.indexOf(':');
- return pos >= 0 ? qname.substring(0, pos) : "";
- }
-
- /**
- * Returns the local name of the given <code>qname</code>. Please note, that
- * this method does not validate the name.
- * </p>
- * the qname has the format: qname := [prefix ':'] local;
- *
- * @param qname
- * a qualified name
- * @return the localname
- *
- * @see #getNamespacePrefix(String)
- *
- * @throws NullPointerException
- * if <code>qname</code> is <code>null</code>
- */
- public static String getLocalName(String qname) {
- int pos = qname.indexOf(':');
- return pos >= 0 ? qname.substring(pos + 1) : qname;
- }
-
- /**
- * Determines, if two paths denote hierarchical siblins.
- *
- * @param p1
- * first path
- * @param p2
- * second path
- * @return true if on same level, false otherwise
- */
- public static boolean isSibling(String p1, String p2) {
- int pos1 = p1.lastIndexOf('/');
- int pos2 = p2.lastIndexOf('/');
- return (pos1 == pos2 && pos1 >= 0 && p1.regionMatches(0, p2, 0, pos1));
- }
-
- /**
- * Determines if the <code>descendant</code> path is hierarchical a
- * descendant of <code>path</code>.
- *
- * @param path
- * the current path
- * @param descendant
- * the potential descendant
- * @return <code>true</code> if the <code>descendant</code> is a descendant;
- * <code>false</code> otherwise.
- */
- public static boolean isDescendant(String path, String descendant) {
- String pattern = path.endsWith("/") ? path : path + "/";
- return !pattern.equals(descendant) && descendant.startsWith(pattern);
- }
-
- /**
- * Determines if the <code>descendant</code> path is hierarchical a
- * descendant of <code>path</code> or equal to it.
- *
- * @param path
- * the path to check
- * @param descendant
- * the potential descendant
- * @return <code>true</code> if the <code>descendant</code> is a descendant
- * or equal; <code>false</code> otherwise.
- */
- public static boolean isDescendantOrEqual(String path, String descendant) {
- if (path.equals(descendant)) {
- return true;
- } else {
- String pattern = path.endsWith("/") ? path : path + "/";
- return descendant.startsWith(pattern);
- }
- }
-
- /**
- * Returns the n<sup>th</sup> relative parent of the path, where n=level.
- * <p>
- * Example:<br>
- * <code>
- * Text.getRelativeParent("/foo/bar/test", 1) == "/foo/bar"
- * </code>
- *
- * @param path
- * the path of the page
- * @param level
- * the level of the parent
- */
- public static String getRelativeParent(String path, int level) {
- int idx = path.length();
- while (level > 0) {
- idx = path.lastIndexOf('/', idx - 1);
- if (idx < 0) {
- return "";
- }
- level--;
- }
- return (idx == 0) ? "/" : path.substring(0, idx);
- }
-
- /**
- * Same as {@link #getRelativeParent(String, int)} but adding the
- * possibility to pass paths that end with a trailing '/'
- *
- * @see #getRelativeParent(String, int)
- */
- public static String getRelativeParent(String path, int level, boolean ignoreTrailingSlash) {
- if (ignoreTrailingSlash && path.endsWith("/") && path.length() > 1) {
- path = path.substring(0, path.length() - 1);
- }
- return getRelativeParent(path, level);
- }
-
- /**
- * Returns the n<sup>th</sup> absolute parent of the path, where n=level.
- * <p>
- * Example:<br>
- * <code>
- * Text.getAbsoluteParent("/foo/bar/test", 1) == "/foo/bar"
- * </code>
- *
- * @param path
- * the path of the page
- * @param level
- * the level of the parent
- */
- public static String getAbsoluteParent(String path, int level) {
- int idx = 0;
- int len = path.length();
- while (level >= 0 && idx < len) {
- idx = path.indexOf('/', idx + 1);
- if (idx < 0) {
- idx = len;
- }
- level--;
- }
- return level >= 0 ? "" : path.substring(0, idx);
- }
-
- /**
- * Performs variable replacement on the given string value. Each
- * <code>${...}</code> sequence within the given value is replaced with the
- * value of the named parser variable. If a variable is not found in the
- * properties an IllegalArgumentException is thrown unless
- * <code>ignoreMissing</code> is <code>true</code>. In the later case, the
- * missing variable is replaced by the empty string.
- *
- * @param value
- * the original value
- * @param ignoreMissing
- * if <code>true</code>, missing variables are replaced by the
- * empty string.
- * @return value after variable replacements
- * @throws IllegalArgumentException
- * if the replacement of a referenced variable is not found
- */
- public static String replaceVariables(Properties variables, String value, boolean ignoreMissing)
- throws IllegalArgumentException {
- StringBuilder result = new StringBuilder();
-
- // Value:
- // +--+-+--------+-+-----------------+
- // | |p|--> |q|--> |
- // +--+-+--------+-+-----------------+
- int p = 0, q = value.indexOf("${"); // Find first ${
- while (q != -1) {
- result.append(value.substring(p, q)); // Text before ${
- p = q;
- q = value.indexOf("}", q + 2); // Find }
- if (q != -1) {
- String variable = value.substring(p + 2, q);
- String replacement = variables.getProperty(variable);
- if (replacement == null) {
- if (ignoreMissing) {
- replacement = "";
- } else {
- throw new IllegalArgumentException("Replacement not found for ${" + variable + "}.");
- }
- }
- result.append(replacement);
- p = q + 1;
- q = value.indexOf("${", p); // Find next ${
- }
- }
- result.append(value.substring(p, value.length())); // Trailing text
-
- return result.toString();
- }
-
- private static byte decodeDigit(byte b) {
- if (b >= 0x30 && b <= 0x39) {
- return (byte) (b - 0x30);
- } else if (b >= 0x41 && b <= 0x46) {
- return (byte) (b - 0x37);
- } else if (b >= 0x61 && b <= 0x66) {
- return (byte) (b - 0x57);
- } else {
- throw new IllegalArgumentException("Escape sequence is not hexadecimal: " + (char) b);
- }
- }
-
-}
+++ /dev/null
-package org.argeo.jcr.fs;
-
-import java.io.IOException;
-import java.nio.file.FileStore;
-import java.nio.file.attribute.FileAttributeView;
-import java.nio.file.attribute.FileStoreAttributeView;
-import java.util.Arrays;
-
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.Workspace;
-
-import org.argeo.jcr.JcrUtils;
-
-/** A {@link FileStore} implementation based on JCR {@link Workspace}. */
-public class WorkspaceFileStore extends FileStore {
- private final String mountPath;
- private final Workspace workspace;
- private final String workspaceName;
- private final int mountDepth;
-
- public WorkspaceFileStore(String mountPath, Workspace workspace) {
- if ("/".equals(mountPath) || "".equals(mountPath))
- throw new IllegalArgumentException(
- "Mount path '" + mountPath + "' is unsupported, use null for the base file store");
- if (mountPath != null && !mountPath.startsWith(JcrPath.separator))
- throw new IllegalArgumentException("Mount path '" + mountPath + "' cannot end with /");
- if (mountPath != null && mountPath.endsWith(JcrPath.separator))
- throw new IllegalArgumentException("Mount path '" + mountPath + "' cannot end with /");
- this.mountPath = mountPath;
- if (mountPath == null)
- mountDepth = 0;
- else {
- mountDepth = mountPath.split(JcrPath.separator).length - 1;
- }
- this.workspace = workspace;
- this.workspaceName = workspace.getName();
- }
-
- public void close() {
- JcrUtils.logoutQuietly(workspace.getSession());
- }
-
- @Override
- public String name() {
- return workspace.getName();
- }
-
- @Override
- public String type() {
- return "workspace";
- }
-
- @Override
- public boolean isReadOnly() {
- return false;
- }
-
- @Override
- public long getTotalSpace() throws IOException {
- return 0;
- }
-
- @Override
- public long getUsableSpace() throws IOException {
- return 0;
- }
-
- @Override
- public long getUnallocatedSpace() throws IOException {
- return 0;
- }
-
- @Override
- public boolean supportsFileAttributeView(Class<? extends FileAttributeView> type) {
- return false;
- }
-
- @Override
- public boolean supportsFileAttributeView(String name) {
- return false;
- }
-
- @Override
- public <V extends FileStoreAttributeView> V getFileStoreAttributeView(Class<V> type) {
- return null;
- }
-
- @Override
- public Object getAttribute(String attribute) throws IOException {
- return workspace.getSession().getRepository().getDescriptor(attribute);
- }
-
- public Workspace getWorkspace() {
- return workspace;
- }
-
- public String toFsPath(Node node) throws RepositoryException {
- String nodeWorkspaceName = node.getSession().getWorkspace().getName();
- if (!nodeWorkspaceName.equals(workspace.getName()))
- throw new IllegalArgumentException("Icompatible " + node + " from workspace '" + nodeWorkspaceName
- + "' in file store '" + workspace.getName() + "'");
- return mountPath == null ? node.getPath() : mountPath + node.getPath();
- }
-
- public boolean isBase() {
- return mountPath == null;
- }
-
- Node toNode(String[] fullPath) throws RepositoryException {
- String jcrPath = toJcrPath(fullPath);
- Session session = workspace.getSession();
- if (!session.itemExists(jcrPath))
- return null;
- Node node = session.getNode(jcrPath);
- return node;
- }
-
- String toJcrPath(String fsPath) {
- if (fsPath.length() == 1)
- return toJcrPath((String[]) null);// root
- String[] arr = fsPath.substring(1).split("/");
-// if (arr.length == 0 || (arr.length == 1 && arr[0].equals("")))
-// return toJcrPath((String[]) null);// root
-// else
- return toJcrPath(arr);
- }
-
- private String toJcrPath(String[] path) {
- if (path == null)
- return "/";
- if (path.length < mountDepth)
- throw new IllegalArgumentException(
- "Path " + Arrays.asList(path) + " is no compatible with mount " + mountPath);
-
- if (!isBase()) {
- // check mount compatibility
- StringBuilder mount = new StringBuilder();
- mount.append('/');
- for (int i = 0; i < mountDepth; i++) {
- if (i != 0)
- mount.append('/');
- mount.append(Text.escapeIllegalJcrChars(path[i]));
- }
- if (!mountPath.equals(mount.toString()))
- throw new IllegalArgumentException(
- "Path " + Arrays.asList(path) + " is no compatible with mount " + mountPath);
- }
-
- StringBuilder sb = new StringBuilder();
- sb.append('/');
- for (int i = mountDepth; i < path.length; i++) {
- if (i != mountDepth)
- sb.append('/');
- sb.append(Text.escapeIllegalJcrChars(path[i]));
- }
- return sb.toString();
- }
-
- public String getMountPath() {
- return mountPath;
- }
-
- public String getWorkspaceName() {
- return workspaceName;
- }
-
- public int getMountDepth() {
- return mountDepth;
- }
-
- @Override
- public int hashCode() {
- return workspaceName.hashCode();
- }
-
- @Override
- public boolean equals(Object obj) {
- if (!(obj instanceof WorkspaceFileStore))
- return false;
- WorkspaceFileStore other = (WorkspaceFileStore) obj;
- return workspaceName.equals(other.workspaceName);
- }
-
- @Override
- public String toString() {
- return "WorkspaceFileStore " + workspaceName;
- }
-
-}
+++ /dev/null
-/** Java NIO file system implementation based on plain JCR. */
-package org.argeo.jcr.fs;
\ No newline at end of file
+++ /dev/null
-//
-// JCR EXTENSIONS
-//
-<jcrx = "http://www.argeo.org/ns/jcrx">
-
-[jcrx:xmlvalue]
-- *
-+ jcr:xmltext (jcrx:xmltext) = jcrx:xmltext
-
-[jcrx:xmltext]
- - jcr:xmlcharacters (STRING) mandatory
-
-[jcrx:csum]
-mixin
- - jcrx:sum (STRING) *
-
\ No newline at end of file
+++ /dev/null
-/** Generic JCR utilities. */
-package org.argeo.jcr;
\ No newline at end of file
+++ /dev/null
-package org.argeo.jcr.xml;
-
-import java.io.IOException;
-import java.io.Writer;
-import java.util.Map;
-import java.util.TreeMap;
-
-import javax.jcr.Node;
-import javax.jcr.NodeIterator;
-import javax.jcr.Property;
-import javax.jcr.PropertyIterator;
-import javax.jcr.RepositoryException;
-import javax.jcr.Value;
-import javax.jcr.nodetype.NodeType;
-
-import org.argeo.jcr.Jcr;
-
-/** Utilities around JCR and XML. */
-public class JcrXmlUtils {
- /**
- * Convenience method calling {@link #toXmlElements(Writer, Node, boolean)} with
- * <code>false</code>.
- */
- public static void toXmlElements(Writer writer, Node node) throws RepositoryException, IOException {
- toXmlElements(writer, node, null, false, false, false);
- }
-
- /**
- * Write JCR properties as XML elements in a tree structure whose elements are
- * named by node primary type.
- *
- * @param writer the writer to use
- * @param node the subtree
- * @param depth maximal depth, or if <code>null</code> the whole
- * subtree. It must be positive, with depth 0
- * describing just the node without its children.
- * @param withMetadata whether to write the primary type and mixins as
- * elements
- * @param withPrefix whether to keep the namespace prefixes
- * @param propertiesAsElements whether single properties should be written as
- * elements rather than attributes. If
- * <code>false</code>, multiple properties will be
- * skipped.
- */
- public static void toXmlElements(Writer writer, Node node, Integer depth, boolean withMetadata, boolean withPrefix,
- boolean propertiesAsElements) throws RepositoryException, IOException {
- if (depth != null && depth < 0)
- throw new IllegalArgumentException("Depth " + depth + " is negative.");
-
- if (node.getName().equals(Jcr.JCR_XMLTEXT)) {
- writer.write(node.getProperty(Jcr.JCR_XMLCHARACTERS).getString());
- return;
- }
-
- if (!propertiesAsElements) {
- Map<String, String> attrs = new TreeMap<>();
- PropertyIterator pit = node.getProperties();
- properties: while (pit.hasNext()) {
- Property p = pit.nextProperty();
- if (!p.isMultiple()) {
- String pName = p.getName();
- if (!withMetadata && (pName.equals(Jcr.JCR_PRIMARY_TYPE) || pName.equals(Jcr.JCR_UUID)
- || pName.equals(Jcr.JCR_CREATED) || pName.equals(Jcr.JCR_CREATED_BY)
- || pName.equals(Jcr.JCR_LAST_MODIFIED) || pName.equals(Jcr.JCR_LAST_MODIFIED_BY)))
- continue properties;
- attrs.put(withPrefix(p.getName(), withPrefix), p.getString());
- }
- }
- if (withMetadata && node.hasProperty(Property.JCR_UUID))
- attrs.put("id", "urn:uuid:" + node.getProperty(Property.JCR_UUID).getString());
- attrs.put(withPrefix ? Jcr.JCR_NAME : "name", node.getName());
- writeStart(writer, withPrefix(node.getPrimaryNodeType().getName(), withPrefix), attrs, node.hasNodes());
- } else {
- if (withMetadata && node.hasProperty(Property.JCR_UUID)) {
- writeStart(writer, withPrefix(node.getPrimaryNodeType().getName(), withPrefix), "id",
- "urn:uuid:" + node.getProperty(Property.JCR_UUID).getString());
- } else {
- writeStart(writer, withPrefix(node.getPrimaryNodeType().getName(), withPrefix));
- }
- // name
- writeStart(writer, withPrefix ? Jcr.JCR_NAME : "name");
- writer.append(node.getName());
- writeEnd(writer, withPrefix ? Jcr.JCR_NAME : "name");
- }
-
- // mixins
- if (withMetadata) {
- for (NodeType mixin : node.getMixinNodeTypes()) {
- writeStart(writer, withPrefix ? Jcr.JCR_MIXIN_TYPES : "mixinTypes");
- writer.append(mixin.getName());
- writeEnd(writer, withPrefix ? Jcr.JCR_MIXIN_TYPES : "mixinTypes");
- }
- }
-
- // properties as elements
- if (propertiesAsElements) {
- PropertyIterator pit = node.getProperties();
- properties: while (pit.hasNext()) {
- Property p = pit.nextProperty();
- if (p.isMultiple()) {
- for (Value value : p.getValues()) {
- writeStart(writer, withPrefix(p.getName(), withPrefix));
- writer.write(value.getString());
- writeEnd(writer, withPrefix(p.getName(), withPrefix));
- }
- } else {
- Value value = p.getValue();
- String pName = p.getName();
- if (!withMetadata && (pName.equals(Jcr.JCR_PRIMARY_TYPE) || pName.equals(Jcr.JCR_UUID)
- || pName.equals(Jcr.JCR_CREATED) || pName.equals(Jcr.JCR_CREATED_BY)
- || pName.equals(Jcr.JCR_LAST_MODIFIED) || pName.equals(Jcr.JCR_LAST_MODIFIED_BY)))
- continue properties;
- writeStart(writer, withPrefix(p.getName(), withPrefix));
- writer.write(value.getString());
- writeEnd(writer, withPrefix(p.getName(), withPrefix));
- }
- }
- }
-
- // children
- if (node.hasNodes()) {
- if (depth == null || depth > 0) {
- NodeIterator nit = node.getNodes();
- while (nit.hasNext()) {
- toXmlElements(writer, nit.nextNode(), depth == null ? null : depth - 1, withMetadata, withPrefix,
- propertiesAsElements);
- }
- }
- writeEnd(writer, withPrefix(node.getPrimaryNodeType().getName(), withPrefix));
- }
- }
-
- private static String withPrefix(String str, boolean withPrefix) {
- if (withPrefix)
- return str;
- int index = str.indexOf(':');
- if (index < 0)
- return str;
- return str.substring(index + 1);
- }
-
- private static void writeStart(Writer writer, String tagName) throws IOException {
- writer.append('<');
- writer.append(tagName);
- writer.append('>');
- }
-
- private static void writeStart(Writer writer, String tagName, String attr, String value) throws IOException {
- writer.append('<');
- writer.append(tagName);
- writer.append(' ');
- writer.append(attr);
- writer.append("=\"");
- writer.append(value);
- writer.append("\">");
- }
-
- private static void writeStart(Writer writer, String tagName, Map<String, String> attrs, boolean hasChildren)
- throws IOException {
- writer.append('<');
- writer.append(tagName);
- for (String attr : attrs.keySet()) {
- writer.append(' ');
- writer.append(attr);
- writer.append("=\"");
- writer.append(attrs.get(attr));
- writer.append('\"');
- }
- if (hasChildren)
- writer.append('>');
- else
- writer.append("/>");
- }
-
- private static void writeEnd(Writer writer, String tagName) throws IOException {
- writer.append("</");
- writer.append(tagName);
- writer.append('>');
- }
-
- /** Singleton. */
- private JcrXmlUtils() {
-
- }
-
-}
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8" ?>
-<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
- <xsl:output method="xml" indent="yes"/>
- <xsl:template match="/|comment()|processing-instruction()">
- <xsl:copy>
- <xsl:apply-templates/>
- </xsl:copy>
- </xsl:template>
- <xsl:template match="*">
- <xsl:element name="{local-name()}">
- <xsl:apply-templates select="@*|node()"/>
- </xsl:element>
- </xsl:template>
- <xsl:template match="@*">
- <xsl:attribute name="{local-name()}">
- <xsl:value-of select="."/>
- </xsl:attribute>
- </xsl:template>
-</xsl:stylesheet>
\ No newline at end of file
+++ /dev/null
-package org.argeo.maintenance;
-
-import java.io.IOException;
-import java.util.EnumSet;
-import java.util.HashSet;
-import java.util.Set;
-
-import javax.jcr.NoSuchWorkspaceException;
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.transaction.UserTransaction;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.argeo.api.NodeUtils;
-import org.argeo.jcr.Jcr;
-import org.argeo.jcr.JcrUtils;
-import org.argeo.naming.Distinguished;
-import org.osgi.service.useradmin.Group;
-import org.osgi.service.useradmin.Role;
-import org.osgi.service.useradmin.UserAdmin;
-
-/** Make sure roles and access rights are properly configured. */
-public abstract class AbstractMaintenanceService {
- private final static Log log = LogFactory.getLog(AbstractMaintenanceService.class);
-
- private Repository repository;
-// private UserAdminService userAdminService;
- private UserAdmin userAdmin;
- private UserTransaction userTransaction;
-
- public void init() {
- makeSureRolesExists(getRequiredRoles());
- configureStandardRoles();
-
- Set<String> workspaceNames = getWorkspaceNames();
- if (workspaceNames == null || workspaceNames.isEmpty()) {
- configureJcr(repository, null);
- } else {
- for (String workspaceName : workspaceNames)
- configureJcr(repository, workspaceName);
- }
- }
-
- /** Configures a workspace. */
- protected void configureJcr(Repository repository, String workspaceName) {
- Session adminSession;
- try {
- adminSession = NodeUtils.openDataAdminSession(repository, workspaceName);
- } catch (RuntimeException e1) {
- if (e1.getCause() != null && e1.getCause() instanceof NoSuchWorkspaceException) {
- Session defaultAdminSession = NodeUtils.openDataAdminSession(repository, null);
- try {
- defaultAdminSession.getWorkspace().createWorkspace(workspaceName);
- log.info("Created JCR workspace " + workspaceName);
- } catch (RepositoryException e) {
- throw new IllegalStateException("Cannot create workspace " + workspaceName, e);
- } finally {
- Jcr.logout(defaultAdminSession);
- }
- adminSession = NodeUtils.openDataAdminSession(repository, workspaceName);
- } else
- throw e1;
- }
- try {
- if (prepareJcrTree(adminSession)) {
- configurePrivileges(adminSession);
- }
- } catch (RepositoryException | IOException e) {
- throw new IllegalStateException("Cannot initialise JCR data layer.", e);
- } finally {
- JcrUtils.logoutQuietly(adminSession);
- }
- }
-
- /** To be overridden. */
- protected Set<String> getWorkspaceNames() {
- return null;
- }
-
- /**
- * To be overridden in order to programmatically set relationships between
- * roles. Does nothing by default.
- */
- protected void configureStandardRoles() {
- }
-
- /**
- * Creates the base JCR tree structure expected for this app if necessary.
- *
- * Expects a clean session ({@link Session#hasPendingChanges()} should return
- * false) and saves it once the changes have been done. Thus the session can be
- * rolled back if an exception occurs.
- *
- * @return true if something as been updated
- */
- public boolean prepareJcrTree(Session adminSession) throws RepositoryException, IOException {
- return false;
- }
-
- /**
- * Adds app specific default privileges.
- *
- * Expects a clean session ({@link Session#hasPendingChanges()} should return
- * false} and saves it once the changes have been done. Thus the session can be
- * rolled back if an exception occurs.
- *
- * Warning: no check is done and corresponding privileges are always added, so
- * only call this when necessary
- */
- public void configurePrivileges(Session session) throws RepositoryException {
- }
-
- /** The system roles that must be available in the system. */
- protected Set<String> getRequiredRoles() {
- return new HashSet<>();
- }
-
- public void destroy() {
-
- }
-
- /*
- * UTILITIES
- */
-
- /** Create these roles as group if they don't exist. */
- protected void makeSureRolesExists(EnumSet<? extends Distinguished> enumSet) {
- makeSureRolesExists(Distinguished.enumToDns(enumSet));
- }
-
- /** Create these roles as group if they don't exist. */
- protected void makeSureRolesExists(Set<String> requiredRoles) {
- if (requiredRoles == null)
- return;
- if (getUserAdmin() == null) {
- log.warn("No user admin service available, cannot make sure that role exists");
- return;
- }
- for (String role : requiredRoles) {
- Role systemRole = getUserAdmin().getRole(role);
- if (systemRole == null) {
- try {
- getUserTransaction().begin();
- getUserAdmin().createRole(role, Role.GROUP);
- getUserTransaction().commit();
- log.info("Created role " + role);
- } catch (Exception e) {
- try {
- getUserTransaction().rollback();
- } catch (Exception e1) {
- // silent
- }
- throw new IllegalStateException("Cannot create role " + role, e);
- }
- }
- }
- }
-
- /** Add a user or group to a group. */
- protected void addToGroup(String groupToAddDn, String groupDn) {
- if (groupToAddDn.contentEquals(groupDn)) {
- if (log.isTraceEnabled())
- log.trace("Ignore adding group " + groupDn + " to itself");
- return;
- }
-
- if (getUserAdmin() == null) {
- log.warn("No user admin service available, cannot add group " + groupToAddDn + " to " + groupDn);
- return;
- }
- Group groupToAdd = (Group) getUserAdmin().getRole(groupToAddDn);
- if (groupToAdd == null)
- throw new IllegalArgumentException("Group " + groupToAddDn + " not found");
- Group group = (Group) getUserAdmin().getRole(groupDn);
- if (group == null)
- throw new IllegalArgumentException("Group " + groupDn + " not found");
- try {
- getUserTransaction().begin();
- if (group.addMember(groupToAdd))
- log.info("Added " + groupToAddDn + " to " + group);
- getUserTransaction().commit();
- } catch (Exception e) {
- try {
- getUserTransaction().rollback();
- } catch (Exception e1) {
- // silent
- }
- throw new IllegalStateException("Cannot add " + groupToAddDn + " to " + groupDn);
- }
- }
-
- /*
- * DEPENDENCY INJECTION
- */
- public void setRepository(Repository repository) {
- this.repository = repository;
- }
-
-// public void setUserAdminService(UserAdminService userAdminService) {
-// this.userAdminService = userAdminService;
-// }
-
- protected UserTransaction getUserTransaction() {
- return userTransaction;
- }
-
- protected UserAdmin getUserAdmin() {
- return userAdmin;
- }
-
- public void setUserAdmin(UserAdmin userAdmin) {
- this.userAdmin = userAdmin;
- }
-
- public void setUserTransaction(UserTransaction userTransaction) {
- this.userTransaction = userTransaction;
- }
-
-}
+++ /dev/null
-package org.argeo.maintenance;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-
-import javax.naming.InvalidNameException;
-import javax.naming.ldap.LdapName;
-import javax.transaction.UserTransaction;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.osgi.service.useradmin.Role;
-import org.osgi.service.useradmin.UserAdmin;
-
-/**
- * Register one or many roles via a user admin service. Does nothing if the role
- * is already registered.
- */
-public class SimpleRoleRegistration implements Runnable {
- private final static Log log = LogFactory.getLog(SimpleRoleRegistration.class);
-
- private String role;
- private List<String> roles = new ArrayList<String>();
- private UserAdmin userAdmin;
- private UserTransaction userTransaction;
-
- @Override
- public void run() {
- try {
- userTransaction.begin();
- if (role != null && !roleExists(role))
- newRole(toDn(role));
-
- for (String r : roles)
- if (!roleExists(r))
- newRole(toDn(r));
- userTransaction.commit();
- } catch (Exception e) {
- try {
- userTransaction.rollback();
- } catch (Exception e1) {
- log.error("Cannot rollback", e1);
- }
- throw new IllegalArgumentException("Cannot add roles", e);
- }
- }
-
- private boolean roleExists(String role) {
- return userAdmin.getRole(toDn(role).toString()) != null;
- }
-
- protected void newRole(LdapName r) {
- userAdmin.createRole(r.toString(), Role.GROUP);
- log.info("Added role " + r + " required by application.");
- }
-
- public void register(UserAdmin userAdminService, Map<?, ?> properties) {
- this.userAdmin = userAdminService;
- run();
- }
-
- protected LdapName toDn(String name) {
- try {
- return new LdapName("cn=" + name + ",ou=roles,ou=node");
- } catch (InvalidNameException e) {
- throw new IllegalArgumentException("Badly formatted role name " + name, e);
- }
- }
-
- public void setRole(String role) {
- this.role = role;
- }
-
- public void setRoles(List<String> roles) {
- this.roles = roles;
- }
-
- public void setUserAdmin(UserAdmin userAdminService) {
- this.userAdmin = userAdminService;
- }
-
- public void setUserTransaction(UserTransaction userTransaction) {
- this.userTransaction = userTransaction;
- }
-
-}
+++ /dev/null
-package org.argeo.maintenance.backup;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.Writer;
-import java.util.Arrays;
-import java.util.Base64;
-import java.util.Set;
-import java.util.TreeSet;
-
-import javax.jcr.Binary;
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-
-import org.apache.commons.io.IOUtils;
-import org.argeo.jcr.Jcr;
-import org.argeo.jcr.JcrException;
-import org.xml.sax.Attributes;
-import org.xml.sax.SAXException;
-import org.xml.sax.helpers.DefaultHandler;
-
-/** XML handler serialising a JCR system view. */
-public class BackupContentHandler extends DefaultHandler {
- final static int MAX_DEPTH = 1024;
- final static String SV_NAMESPACE_URI = "http://www.jcp.org/jcr/sv/1.0";
- final static String SV_PREFIX = "sv";
- // elements
- final static String NODE = "node";
- final static String PROPERTY = "property";
- final static String VALUE = "value";
- // attributes
- final static String NAME = "name";
- final static String MULTIPLE = "multiple";
- final static String TYPE = "type";
-
- // values
- final static String BINARY = "Binary";
- final static String JCR_CONTENT = "jcr:content";
-
- private Writer out;
- private Session session;
- private Set<String> contentPaths = new TreeSet<>();
-
- boolean prettyPrint = true;
-
- private final String parentPath;
-
-// private boolean inSystem = false;
-
- public BackupContentHandler(Writer out, Node node) {
- super();
- this.out = out;
- this.session = Jcr.getSession(node);
- parentPath = Jcr.getParentPath(node);
- }
-
- private int currentDepth = -1;
- private String[] currentPath = new String[MAX_DEPTH];
-
- private boolean currentPropertyIsMultiple = false;
- private String currentEncoded = null;
- private Base64.Encoder base64encore = Base64.getEncoder();
-
- @Override
- public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
- boolean isNode;
- boolean isProperty;
- switch (localName) {
- case NODE:
- isNode = true;
- isProperty = false;
- break;
- case PROPERTY:
- isNode = false;
- isProperty = true;
- break;
- default:
- isNode = false;
- isProperty = false;
- }
-
- if (isNode) {
- String nodeName = attributes.getValue(SV_NAMESPACE_URI, NAME);
- currentDepth = currentDepth + 1;
-// if (currentDepth >= 0)
- currentPath[currentDepth] = nodeName;
-// System.out.println(getCurrentPath() + " , depth=" + currentDepth);
-// if ("jcr:system".equals(nodeName)) {
-// inSystem = true;
-// }
- }
-// if (inSystem)
-// return;
-
- if (SV_NAMESPACE_URI.equals(uri))
- try {
- if (prettyPrint) {
- if (isNode) {
- out.write(spaces());
- out.write("<!-- ");
- out.write(getCurrentJcrPath());
- out.write(" -->\n");
- out.write(spaces());
- } else if (isProperty)
- out.write(spaces());
- else if (currentPropertyIsMultiple)
- out.write(spaces());
- }
-
- out.write("<");
- out.write(SV_PREFIX + ":" + localName);
- if (isProperty)
- currentPropertyIsMultiple = false; // always reset
- for (int i = 0; i < attributes.getLength(); i++) {
- String ns = attributes.getURI(i);
- if (SV_NAMESPACE_URI.equals(ns)) {
- String attrName = attributes.getLocalName(i);
- String attrValue = attributes.getValue(i);
- out.write(" ");
- out.write(SV_PREFIX + ":" + attrName);
- out.write("=");
- out.write("\"");
- out.write(attrValue);
- out.write("\"");
- if (isProperty) {
- if (MULTIPLE.equals(attrName))
- currentPropertyIsMultiple = Boolean.parseBoolean(attrValue);
- else if (TYPE.equals(attrName)) {
- if (BINARY.equals(attrValue)) {
- if (JCR_CONTENT.equals(getCurrentName())) {
- contentPaths.add(getCurrentJcrPath());
- } else {
- Binary binary = session.getNode(getCurrentJcrPath()).getProperty(attrName)
- .getBinary();
- try (InputStream in = binary.getStream()) {
- currentEncoded = base64encore.encodeToString(IOUtils.toByteArray(in));
- } finally {
-
- }
- }
- }
- }
- }
- }
- }
- if (isNode && currentDepth == 0) {
- // out.write(" xmlns=\"" + SV_NAMESPACE_URI + "\"");
- out.write(" xmlns:" + SV_PREFIX + "=\"" + SV_NAMESPACE_URI + "\"");
- }
- out.write(">");
-
- if (prettyPrint)
- if (isNode)
- out.write("\n");
- else if (isProperty && currentPropertyIsMultiple)
- out.write("\n");
- } catch (IOException e) {
- throw new RuntimeException(e);
- } catch (RepositoryException e) {
- throw new JcrException(e);
- }
- }
-
- @Override
- public void endElement(String uri, String localName, String qName) throws SAXException {
- boolean isNode = localName.equals(NODE);
- boolean isValue = localName.equals(VALUE);
- if (prettyPrint)
- if (!isValue)
- try {
- if (isNode || currentPropertyIsMultiple)
- out.write(spaces());
- } catch (IOException e1) {
- throw new RuntimeException(e1);
- }
- if (isNode) {
-// System.out.println("endElement " + getCurrentPath() + " , depth=" + currentDepth);
-// if (currentDepth > 0)
- currentPath[currentDepth] = null;
- currentDepth = currentDepth - 1;
-// if (inSystem) {
-// // System.out.println("Skip " + getCurrentPath()+" ,
-// // currentDepth="+currentDepth);
-// if (currentDepth == 0) {
-// inSystem = false;
-// return;
-// }
-// }
- }
-// if (inSystem)
-// return;
- if (SV_NAMESPACE_URI.equals(uri))
- try {
- if (isValue && currentEncoded != null) {
- out.write(currentEncoded);
- }
- currentEncoded = null;
- out.write("</");
- out.write(SV_PREFIX + ":" + localName);
- out.write(">");
- if (prettyPrint)
- if (!isValue)
- out.write("\n");
- else {
- if (currentPropertyIsMultiple)
- out.write("\n");
- }
- if (currentDepth == 0)
- out.flush();
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
-
- }
-
- private char[] spaces() {
- char[] arr = new char[currentDepth];
- Arrays.fill(arr, ' ');
- return arr;
- }
-
- @Override
- public void characters(char[] ch, int start, int length) throws SAXException {
-// if (inSystem)
-// return;
- try {
- out.write(ch, start, length);
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
-
- protected String getCurrentName() {
- assert currentDepth >= 0;
-// if (currentDepth == 0)
-// return "jcr:root";
- return currentPath[currentDepth];
- }
-
- protected String getCurrentJcrPath() {
-// if (currentDepth == 0)
-// return "/";
- StringBuilder sb = new StringBuilder(parentPath.equals("/") ? "" : parentPath);
- for (int i = 0; i <= currentDepth; i++) {
-// if (i != 0)
- sb.append('/');
- sb.append(currentPath[i]);
- }
- return sb.toString();
- }
-
- public Set<String> getContentPaths() {
- return contentPaths;
- }
-
-}
+++ /dev/null
-package org.argeo.maintenance.backup;
-
-import java.io.BufferedWriter;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.OutputStreamWriter;
-import java.io.Writer;
-import java.net.URI;
-import java.net.URL;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.Dictionary;
-import java.util.Enumeration;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Set;
-import java.util.TreeMap;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.Future;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-import java.util.jar.JarOutputStream;
-import java.util.jar.Manifest;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipException;
-import java.util.zip.ZipOutputStream;
-
-import javax.jcr.Binary;
-import javax.jcr.Node;
-import javax.jcr.NodeIterator;
-import javax.jcr.Property;
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.RepositoryFactory;
-import javax.jcr.Session;
-
-import org.apache.commons.io.IOUtils;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.apache.jackrabbit.api.JackrabbitSession;
-import org.apache.jackrabbit.api.JackrabbitValue;
-import org.argeo.api.NodeConstants;
-import org.argeo.api.NodeUtils;
-import org.argeo.jackrabbit.client.ClientDavexRepositoryFactory;
-import org.argeo.jcr.Jcr;
-import org.argeo.jcr.JcrException;
-import org.argeo.jcr.JcrUtils;
-import org.osgi.framework.Bundle;
-import org.osgi.framework.BundleContext;
-
-/**
- * Performs a backup of the data based only on programmatic interfaces. Useful
- * for migration or live backup. Physical backups of the underlying file
- * systems, databases, LDAP servers, etc. should be performed for disaster
- * recovery.
- */
-public class LogicalBackup implements Runnable {
- private final static Log log = LogFactory.getLog(LogicalBackup.class);
-
- public final static String WORKSPACES_BASE = "workspaces/";
- public final static String FILES_BASE = "files/";
- public final static String OSGI_BASE = "share/osgi/";
-
- public final static String JCR_SYSTEM = "jcr:system";
- public final static String JCR_VERSION_STORAGE_PATH = "/jcr:system/jcr:versionStorage";
-
- private final Repository repository;
- private String defaultWorkspace;
- private final BundleContext bundleContext;
-
- private final ZipOutputStream zout;
- private final Path basePath;
-
- private ExecutorService executorService;
-
- private boolean performSoftwareBackup = false;
-
- private Map<String, String> checksums = new TreeMap<>();
-
- private int threadCount = 5;
-
- private boolean backupFailed = false;
-
- public LogicalBackup(BundleContext bundleContext, Repository repository, Path basePath) {
- this.repository = repository;
- this.zout = null;
- this.basePath = basePath;
- this.bundleContext = bundleContext;
- }
-
- @Override
- public void run() {
- try {
- log.info("Start logical backup to " + basePath);
- perform();
- } catch (Exception e) {
- log.error("Unexpected exception when performing logical backup", e);
- throw new IllegalStateException("Logical backup failed", e);
- }
-
- }
-
- public void perform() throws RepositoryException, IOException {
- if (executorService != null && !executorService.isTerminated())
- throw new IllegalStateException("Another backup is running");
- executorService = Executors.newFixedThreadPool(threadCount);
- long begin = System.currentTimeMillis();
- // software backup
- if (bundleContext != null && performSoftwareBackup)
- executorService.submit(() -> performSoftwareBackup(bundleContext));
-
- // data backup
- Session defaultSession = login(null);
- defaultWorkspace = defaultSession.getWorkspace().getName();
- try {
- String[] workspaceNames = defaultSession.getWorkspace().getAccessibleWorkspaceNames();
- workspaces: for (String workspaceName : workspaceNames) {
- if ("security".equals(workspaceName))
- continue workspaces;
- performDataBackup(workspaceName);
- }
- } finally {
- JcrUtils.logoutQuietly(defaultSession);
- executorService.shutdown();
- try {
- executorService.awaitTermination(24, TimeUnit.HOURS);
- } catch (InterruptedException e) {
- // silent
- throw new IllegalStateException("Backup was interrupted before completion", e);
- }
- }
- // versions
- executorService = Executors.newFixedThreadPool(threadCount);
- try {
- performVersionsBackup();
- } finally {
- executorService.shutdown();
- try {
- executorService.awaitTermination(24, TimeUnit.HOURS);
- } catch (InterruptedException e) {
- // silent
- throw new IllegalStateException("Backup was interrupted before completion", e);
- }
- }
- long duration = System.currentTimeMillis() - begin;
- if (isBackupFailed())
- log.info("System logical backup failed after " + (duration / 60000) + "min " + (duration / 1000) + "s");
- else
- log.info("System logical backup completed in " + (duration / 60000) + "min " + (duration / 1000) + "s");
- }
-
- protected void performDataBackup(String workspaceName) throws RepositoryException, IOException {
- Session session = login(workspaceName);
- try {
- nodes: for (NodeIterator nit = session.getRootNode().getNodes(); nit.hasNext();) {
- if (isBackupFailed())
- return;
- Node nodeToExport = nit.nextNode();
- if (JCR_SYSTEM.equals(nodeToExport.getName()))
- continue nodes;
- String nodePath = nodeToExport.getPath();
- Future<Set<String>> contentPathsFuture = executorService
- .submit(() -> performNodeBackup(workspaceName, nodePath));
- executorService.submit(() -> performFilesBackup(workspaceName, contentPathsFuture));
- }
- } finally {
- Jcr.logout(session);
- }
- }
-
- protected void performVersionsBackup() throws RepositoryException, IOException {
- Session session = login(defaultWorkspace);
- Node versionStorageNode = session.getNode(JCR_VERSION_STORAGE_PATH);
- try {
- for (NodeIterator nit = versionStorageNode.getNodes(); nit.hasNext();) {
- Node nodeToExport = nit.nextNode();
- String nodePath = nodeToExport.getPath();
- if (isBackupFailed())
- return;
- Future<Set<String>> contentPathsFuture = executorService
- .submit(() -> performNodeBackup(defaultWorkspace, nodePath));
- executorService.submit(() -> performFilesBackup(defaultWorkspace, contentPathsFuture));
- }
- } finally {
- Jcr.logout(session);
- }
-
- }
-
- protected Set<String> performNodeBackup(String workspaceName, String nodePath) {
- Session session = login(workspaceName);
- try {
- Node nodeToExport = session.getNode(nodePath);
-// String nodeName = nodeToExport.getName();
-// if (nodeName.startsWith("jcr:") || nodeName.startsWith("rep:"))
-// continue nodes;
-// // TODO make it more robust / configurable
-// if (nodeName.equals("user"))
-// continue nodes;
- String relativePath = WORKSPACES_BASE + workspaceName + nodePath + ".xml";
- OutputStream xmlOut = openOutputStream(relativePath);
- BackupContentHandler contentHandler;
- try (Writer writer = new BufferedWriter(new OutputStreamWriter(xmlOut, StandardCharsets.UTF_8))) {
- contentHandler = new BackupContentHandler(writer, nodeToExport);
- session.exportSystemView(nodeToExport.getPath(), contentHandler, true, false);
- if (log.isDebugEnabled())
- log.debug(workspaceName + ":" + nodePath + " metadata exported to " + relativePath);
- }
-
- // Files
- Set<String> contentPaths = contentHandler.getContentPaths();
- return contentPaths;
- } catch (Exception e) {
- markBackupFailed("Cannot backup node " + workspaceName + ":" + nodePath, e);
- throw new ThreadDeath();
- } finally {
- Jcr.logout(session);
- }
- }
-
- protected void performFilesBackup(String workspaceName, Future<Set<String>> contentPathsFuture) {
- Set<String> contentPaths;
- try {
- contentPaths = contentPathsFuture.get(24, TimeUnit.HOURS);
- } catch (InterruptedException | ExecutionException | TimeoutException e1) {
- markBackupFailed("Cannot retrieve content paths for workspace " + workspaceName, e1);
- return;
- }
- if (contentPaths == null || contentPaths.size() == 0)
- return;
- Session session = login(workspaceName);
- try {
- String workspacesFilesBasePath = FILES_BASE + workspaceName;
- for (String path : contentPaths) {
- if (isBackupFailed())
- return;
- Node contentNode = session.getNode(path);
- Binary binary = null;
- try {
- binary = contentNode.getProperty(Property.JCR_DATA).getBinary();
- String fileRelativePath = workspacesFilesBasePath + contentNode.getParent().getPath();
-
- // checksum
- boolean skip = false;
- String checksum = null;
- if (session instanceof JackrabbitSession) {
- JackrabbitValue value = (JackrabbitValue) contentNode.getProperty(Property.JCR_DATA).getValue();
-// ReferenceBinary referenceBinary = (ReferenceBinary) binary;
- checksum = value.getContentIdentity();
- }
- if (checksum != null) {
- if (!checksums.containsKey(checksum)) {
- checksums.put(checksum, fileRelativePath);
- } else {
- skip = true;
- String sourcePath = checksums.get(checksum);
- if (log.isTraceEnabled())
- log.trace(fileRelativePath + " : already " + sourcePath + " with checksum " + checksum);
- createLink(sourcePath, fileRelativePath);
- try (Writer writerSum = new OutputStreamWriter(
- openOutputStream(fileRelativePath + ".sha256"), StandardCharsets.UTF_8)) {
- writerSum.write(checksum);
- }
- }
- }
-
- // copy file
- if (!skip)
- try (InputStream in = binary.getStream();
- OutputStream out = openOutputStream(fileRelativePath)) {
- IOUtils.copy(in, out);
- if (log.isTraceEnabled())
- log.trace("Workspace " + workspaceName + ": file content exported to "
- + fileRelativePath);
- }
- } finally {
- JcrUtils.closeQuietly(binary);
- }
- }
- if (log.isDebugEnabled())
- log.debug(workspaceName + ":" + contentPaths.size() + " files exported to " + workspacesFilesBasePath);
- } catch (Exception e) {
- markBackupFailed("Cannot backup files from " + workspaceName + ":", e);
- } finally {
- Jcr.logout(session);
- }
- }
-
- protected OutputStream openOutputStream(String relativePath) throws IOException {
- if (zout != null) {
- ZipEntry entry = new ZipEntry(relativePath);
- zout.putNextEntry(entry);
- return zout;
- } else if (basePath != null) {
- Path targetPath = basePath.resolve(Paths.get(relativePath));
- Files.createDirectories(targetPath.getParent());
- return Files.newOutputStream(targetPath);
- } else {
- throw new UnsupportedOperationException();
- }
- }
-
- protected void createLink(String source, String target) throws IOException {
- if (zout != null) {
- // TODO implement for zip
- throw new UnsupportedOperationException();
- } else if (basePath != null) {
- Path sourcePath = basePath.resolve(Paths.get(source));
- Path targetPath = basePath.resolve(Paths.get(target));
- Path relativeSource = targetPath.getParent().relativize(sourcePath);
- Files.createDirectories(targetPath.getParent());
- Files.createSymbolicLink(targetPath, relativeSource);
- } else {
- throw new UnsupportedOperationException();
- }
- }
-
- protected void closeOutputStream(String relativePath, OutputStream out) throws IOException {
- if (zout != null) {
- zout.closeEntry();
- } else if (basePath != null) {
- out.close();
- } else {
- throw new UnsupportedOperationException();
- }
- }
-
- protected Session login(String workspaceName) {
- if (bundleContext != null) {// local
- return NodeUtils.openDataAdminSession(repository, workspaceName);
- } else {// remote
- try {
- return repository.login(workspaceName);
- } catch (RepositoryException e) {
- throw new JcrException(e);
- }
- }
- }
-
- public final static void main(String[] args) throws Exception {
- if (args.length == 0) {
- printUsage("No argument");
- System.exit(1);
- }
- URI uri = new URI(args[0]);
- Repository repository = createRemoteRepository(uri);
- Path basePath = args.length > 1 ? Paths.get(args[1]) : Paths.get(System.getProperty("user.dir"));
- if (!Files.exists(basePath))
- Files.createDirectories(basePath);
- LogicalBackup backup = new LogicalBackup(null, repository, basePath);
- backup.run();
- }
-
- private static void printUsage(String errorMessage) {
- if (errorMessage != null)
- System.err.println(errorMessage);
- System.out.println("Usage: LogicalBackup <remote URL> [<target directory>]");
-
- }
-
- protected static 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());
- // TODO make it configurable
- params.put(ClientDavexRepositoryFactory.JACKRABBIT_REMOTE_DEFAULT_WORKSPACE, NodeConstants.SYS_WORKSPACE);
- return repositoryFactory.getRepository(params);
- }
-
- public void performSoftwareBackup(BundleContext bundleContext) {
- String bootBasePath = OSGI_BASE + "boot";
- Bundle[] bundles = bundleContext.getBundles();
- for (Bundle bundle : bundles) {
- String relativePath = bootBasePath + "/" + bundle.getSymbolicName() + ".jar";
- Dictionary<String, String> headers = bundle.getHeaders();
- Manifest manifest = new Manifest();
- Enumeration<String> headerKeys = headers.keys();
- while (headerKeys.hasMoreElements()) {
- String headerKey = headerKeys.nextElement();
- String headerValue = headers.get(headerKey);
- manifest.getMainAttributes().putValue(headerKey, headerValue);
- }
- try (JarOutputStream jarOut = new JarOutputStream(openOutputStream(relativePath), manifest)) {
- Enumeration<URL> resourcePaths = bundle.findEntries("/", "*", true);
- resources: while (resourcePaths.hasMoreElements()) {
- URL entryUrl = resourcePaths.nextElement();
- String entryPath = entryUrl.getPath();
- if (entryPath.equals(""))
- continue resources;
- if (entryPath.endsWith("/"))
- continue resources;
- String entryName = entryPath.substring(1);// remove first '/'
- if (entryUrl.getPath().equals("/META-INF/"))
- continue resources;
- if (entryUrl.getPath().equals("/META-INF/MANIFEST.MF"))
- continue resources;
- // dev
- if (entryUrl.getPath().startsWith("/target"))
- continue resources;
- if (entryUrl.getPath().startsWith("/src"))
- continue resources;
- if (entryUrl.getPath().startsWith("/ext"))
- continue resources;
-
- if (entryName.startsWith("bin/")) {// dev
- entryName = entryName.substring("bin/".length());
- }
-
- ZipEntry entry = new ZipEntry(entryName);
- try (InputStream in = entryUrl.openStream()) {
- try {
- jarOut.putNextEntry(entry);
- } catch (ZipException e) {// duplicate
- continue resources;
- }
- IOUtils.copy(in, jarOut);
- jarOut.closeEntry();
-// log.info(entryUrl);
- } catch (FileNotFoundException e) {
- log.warn(entryUrl + ": " + e.getMessage());
- }
- }
- } catch (IOException e1) {
- throw new RuntimeException("Cannot export bundle " + bundle, e1);
- }
- }
- if (log.isDebugEnabled())
- log.debug(bundles.length + " OSGi bundles exported to " + bootBasePath);
-
- }
-
- protected synchronized void markBackupFailed(Object message, Exception e) {
- log.error(message, e);
- backupFailed = true;
- notifyAll();
- if (executorService != null)
- executorService.shutdownNow();
- }
-
- protected boolean isBackupFailed() {
- return backupFailed;
- }
-}
+++ /dev/null
-package org.argeo.maintenance.backup;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.file.DirectoryStream;
-import java.nio.file.Files;
-import java.nio.file.Path;
-
-import javax.jcr.ImportUUIDBehavior;
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.argeo.api.NodeConstants;
-import org.argeo.api.NodeUtils;
-import org.argeo.jcr.Jcr;
-import org.argeo.jcr.JcrException;
-import org.argeo.jcr.JcrUtils;
-import org.osgi.framework.BundleContext;
-
-/** Restores a backup in the format defined by {@link LogicalBackup}. */
-public class LogicalRestore implements Runnable {
- private final static Log log = LogFactory.getLog(LogicalRestore.class);
-
- private final Repository repository;
- private final BundleContext bundleContext;
- private final Path basePath;
-
- public LogicalRestore(BundleContext bundleContext, Repository repository, Path basePath) {
- this.repository = repository;
- this.basePath = basePath;
- this.bundleContext = bundleContext;
- }
-
- @Override
- public void run() {
- Path workspaces = basePath.resolve(LogicalBackup.WORKSPACES_BASE);
- try {
- // import jcr:system first
-// Session defaultSession = NodeUtils.openDataAdminSession(repository, null);
-// try (DirectoryStream<Path> xmls = Files.newDirectoryStream(
-// workspaces.resolve(NodeConstants.SYS_WORKSPACE + LogicalBackup.JCR_VERSION_STORAGE_PATH),
-// "*.xml")) {
-// for (Path xml : xmls) {
-// try (InputStream in = Files.newInputStream(xml)) {
-// defaultSession.getWorkspace().importXML(LogicalBackup.JCR_VERSION_STORAGE_PATH, in,
-// ImportUUIDBehavior.IMPORT_UUID_COLLISION_REPLACE_EXISTING);
-// if (log.isDebugEnabled())
-// log.debug("Restored " + xml + " to " + defaultSession.getWorkspace().getName() + ":");
-// }
-// }
-// } finally {
-// Jcr.logout(defaultSession);
-// }
-
- // non-system content
- try (DirectoryStream<Path> workspaceDirs = Files.newDirectoryStream(workspaces)) {
- for (Path workspacePath : workspaceDirs) {
- String workspaceName = workspacePath.getFileName().toString();
- Session session = JcrUtils.loginOrCreateWorkspace(repository, workspaceName);
- try (DirectoryStream<Path> xmls = Files.newDirectoryStream(workspacePath, "*.xml")) {
- xmls: for (Path xml : xmls) {
- if (xml.getFileName().toString().startsWith("rep:"))
- continue xmls;
- try (InputStream in = Files.newInputStream(xml)) {
- session.getWorkspace().importXML("/", in,
- ImportUUIDBehavior.IMPORT_UUID_COLLISION_REPLACE_EXISTING);
- if (log.isDebugEnabled())
- log.debug("Restored " + xml + " to workspace " + workspaceName);
- }
- }
- } finally {
- Jcr.logout(session);
- }
- }
- }
- } catch (IOException e) {
- throw new RuntimeException("Cannot restore backup from " + basePath, e);
- } catch (RepositoryException e) {
- throw new JcrException("Cannot restore backup from " + basePath, e);
- }
- }
-
-}
+++ /dev/null
-/** Argeo Node backup utilities. */
-package org.argeo.maintenance.backup;
\ No newline at end of file
+++ /dev/null
-package org.argeo.maintenance.internal;
-
-import java.nio.file.Path;
-import java.nio.file.Paths;
-
-import javax.jcr.Repository;
-
-import org.argeo.maintenance.backup.LogicalBackup;
-import org.osgi.framework.BundleActivator;
-import org.osgi.framework.BundleContext;
-
-public class Activator implements BundleActivator {
-
- @Override
- public void start(BundleContext context) throws Exception {
- // Start backup
- Repository repository = context.getService(context.getServiceReference(Repository.class));
- Path basePath = Paths.get(System.getProperty("user.dir"), "backup");
- LogicalBackup backup = new LogicalBackup(context, repository, basePath);
- backup.run();
- }
-
- @Override
- public void stop(BundleContext context) throws Exception {
- }
-
-}
+++ /dev/null
-/** Utilities for the maintenance of an Argeo Node. */
-package org.argeo.maintenance;
\ No newline at end of file
<modules>
<!-- Base -->
<module>org.argeo.enterprise</module>
- <module>org.argeo.jcr</module>
+<!-- <module>org.argeo.jcr</module> -->
<module>org.argeo.osgi.boot</module>
<module>org.argeo.core</module>
<!-- Eclipse -->
<module>org.argeo.eclipse.ui.rap</module>
<!-- CMS -->
<module>org.argeo.api</module>
- <module>org.argeo.maintenance</module>
+<!-- <module>org.argeo.maintenance</module> -->
<module>org.argeo.cms</module>
+ <module>org.argeo.cms.jcr</module>
<module>org.argeo.cms.ui.theme</module>
<module>org.argeo.cms.ui</module>
<module>org.argeo.cms.ui.rap</module>