This is an automated email from the ASF dual-hosted git repository. olli pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-commons-jcr-file.git
commit 2a64cccdff2d7267b76f2659e3cc725b6d8d6e22 Author: Oliver Lietz <[email protected]> AuthorDate: Wed Aug 22 23:06:56 2018 +0200 SLING-7846 Provide a NIO.2 file system implementation for JCR --- .gitignore | 17 + .sling-module.xml | 24 ++ LICENSE | 202 +++++++++++ README.md | 20 ++ bnd.bnd | 3 + pom.xml | 211 +++++++++++ .../commons/jcr/file/JcrFileSupportService.java | 41 +++ .../internal/DefaultJcrFileSupportService.java | 139 ++++++++ .../DefaultJcrFileSupportServiceConfiguration.java | 60 ++++ .../jcr/file/internal/JcrDirectoryStream.java | 116 ++++++ .../sling/commons/jcr/file/internal/JcrFile.java | 394 +++++++++++++++++++++ .../jcr/file/internal/JcrFileAttributes.java | 98 +++++ .../commons/jcr/file/internal/JcrFileChannel.java | 189 ++++++++++ .../commons/jcr/file/internal/JcrFileStore.java | 79 +++++ .../commons/jcr/file/internal/JcrFileSystem.java | 139 ++++++++ .../jcr/file/internal/JcrFileSystemProvider.java | 283 +++++++++++++++ .../sling/commons/jcr/file/internal/JcrPath.java | 284 +++++++++++++++ .../sling/commons/jcr/file/internal/PathUtil.java | 258 ++++++++++++++ .../sling/commons/jcr/file/package-info.java | 22 ++ .../sling/commons/jcr/file/it/JcrFileIT.java | 80 +++++ .../jcr/file/it/JcrFileSystemProviderIT.java | 147 ++++++++ .../commons/jcr/file/it/JcrFileTestSupport.java | 102 ++++++ 22 files changed, 2908 insertions(+) diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5b783ed --- /dev/null +++ b/.gitignore @@ -0,0 +1,17 @@ +/target +.idea +.classpath +.metadata +.project +.settings +.externalToolBuilders +maven-eclipse.xml +*.swp +*.iml +*.ipr +*.iws +*.bak +.vlt +.DS_Store +jcr.log +atlassian-ide-plugin.xml diff --git a/.sling-module.xml b/.sling-module.xml new file mode 100644 index 0000000..a402095 --- /dev/null +++ b/.sling-module.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + 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. +--> +<sling-module> + <jenkins> + <enabled>false</enabled> + </jenkins> +</sling-module> diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..6dcbfa4 --- /dev/null +++ b/README.md @@ -0,0 +1,20 @@ +# Apache Sling Commons JCR File + +This module is part of the [Apache Sling](https://sling.apache.org) project. + +This module provides a basic NIO.2 file system implementation for JCR. + +## JCR File System Properties + +| Property Name (of Type `java.lang.String`) | Allowed Values | Property Description | +| ---- | ---- | ---- | +| `javax.jcr.Session` | a valid (_live_) `Session` | Session which is used to access (read/write) the JCR | + +## Limitations + +* Getting an existing `FileSystem` or `Path` via `URI` (`FileSystemProvider#getFileSystem(URI):FileSystem`) is not supported due to underlying JCR `Session`s from environments (`env`). +* Getting total space, free space and usable space always returns `Long.MAX_VALUE` + +## Notes + +* Most classes in this module log errors **and** throw exceptions afterwards due to the sparingly used logging in MINA's SFTP subsystem. diff --git a/bnd.bnd b/bnd.bnd new file mode 100644 index 0000000..e36a0d6 --- /dev/null +++ b/bnd.bnd @@ -0,0 +1,3 @@ +-removeheaders:\ + Include-Resource,\ + Private-Package diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..0fb0daa --- /dev/null +++ b/pom.xml @@ -0,0 +1,211 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + 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. +--> +<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/xsd/maven-4.0.0.xsd"> + + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.apache.sling</groupId> + <artifactId>sling</artifactId> + <version>34</version> + <relativePath/> + </parent> + + <artifactId>org.apache.sling.commons.jcr.file</artifactId> + <version>1.0.0-SNAPSHOT</version> + + <name>Apache Sling Commons JCR File</name> + <description>NIO.2 file system implementation for JCR</description> + + <properties> + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> + <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> + <sling.java.version>8</sling.java.version> + <org.ops4j.pax.exam.version>4.12.0</org.ops4j.pax.exam.version> + </properties> + + <scm> + <connection>scm:git:https://gitbox.apache.org/repos/asf/sling-org-apache-sling-commons-jcr-file.git</connection> + <developerConnection>scm:git:https://gitbox.apache.org/repos/asf/sling-org-apache-sling-commons-jcr-file.git</developerConnection> + <url>https://gitbox.apache.org/repos/asf?p=sling-org-apache-sling-commons-jcr-file.git</url> + </scm> + + <build> + <plugins> + <plugin> + <groupId>biz.aQute.bnd</groupId> + <artifactId>bnd-maven-plugin</artifactId> + </plugin> + <plugin> + <groupId>biz.aQute.bnd</groupId> + <artifactId>bnd-baseline-maven-plugin</artifactId> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-failsafe-plugin</artifactId> + <executions> + <execution> + <goals> + <goal>integration-test</goal> + <goal>verify</goal> + </goals> + </execution> + </executions> + <configuration> + <redirectTestOutputToFile>true</redirectTestOutputToFile> + <systemProperties> + <property> + <name>bundle.filename</name> + <value>${basedir}/target/${project.build.finalName}.jar</value> + </property> + </systemProperties> + </configuration> + </plugin> + <plugin> + <groupId>org.apache.servicemix.tooling</groupId> + <artifactId>depends-maven-plugin</artifactId> + </plugin> + <plugin> + <groupId>org.apache.sling</groupId> + <artifactId>maven-sling-plugin</artifactId> + <configuration> + <slingUrl>http://localhost:8181/system/console</slingUrl> + <user>karaf</user> + <password>karaf</password> + </configuration> + </plugin> + </plugins> + </build> + + <dependencies> + <!-- javax --> + <dependency> + <groupId>javax.inject</groupId> + <artifactId>javax.inject</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>javax.jcr</groupId> + <artifactId>jcr</artifactId> + <scope>provided</scope> + </dependency> + <!-- OSGi --> + <dependency> + <groupId>org.osgi</groupId> + <artifactId>org.osgi.annotation.versioning</artifactId> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.osgi</groupId> + <artifactId>osgi.cmpn</artifactId> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.osgi</groupId> + <artifactId>osgi.core</artifactId> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.osgi</groupId> + <artifactId>org.osgi.service.component.annotations</artifactId> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.osgi</groupId> + <artifactId>org.osgi.service.metatype.annotations</artifactId> + <scope>provided</scope> + </dependency> + <!-- Apache Felix --> + <dependency> + <groupId>org.apache.felix</groupId> + <artifactId>org.apache.felix.framework</artifactId> + <version>5.6.10</version> + <scope>test</scope> + </dependency> + <!-- Apache Sling --> + <dependency> + <groupId>org.apache.sling</groupId> + <artifactId>org.apache.sling.resource.presence</artifactId> + <version>0.0.2</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.apache.sling</groupId> + <artifactId>org.apache.sling.testing.paxexam</artifactId> + <version>2.0.0</version> + <scope>test</scope> + </dependency> + <!-- nullability --> + <dependency> + <groupId>org.jetbrains</groupId> + <artifactId>annotations</artifactId> + <scope>provided</scope> + </dependency> + <!-- logging --> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-api</artifactId> + <scope>provided</scope> + </dependency> + <!-- testing --> + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.apache.servicemix.bundles</groupId> + <artifactId>org.apache.servicemix.bundles.hamcrest</artifactId> + <version>1.3_1</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.ops4j.pax.exam</groupId> + <artifactId>pax-exam</artifactId> + <version>${org.ops4j.pax.exam.version}</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.ops4j.pax.exam</groupId> + <artifactId>pax-exam-cm</artifactId> + <version>${org.ops4j.pax.exam.version}</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.ops4j.pax.exam</groupId> + <artifactId>pax-exam-container-forked</artifactId> + <version>${org.ops4j.pax.exam.version}</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.ops4j.pax.exam</groupId> + <artifactId>pax-exam-junit4</artifactId> + <version>${org.ops4j.pax.exam.version}</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.ops4j.pax.exam</groupId> + <artifactId>pax-exam-link-mvn</artifactId> + <version>${org.ops4j.pax.exam.version}</version> + <scope>test</scope> + </dependency> + </dependencies> + +</project> diff --git a/src/main/java/org/apache/sling/commons/jcr/file/JcrFileSupportService.java b/src/main/java/org/apache/sling/commons/jcr/file/JcrFileSupportService.java new file mode 100644 index 0000000..17207b3 --- /dev/null +++ b/src/main/java/org/apache/sling/commons/jcr/file/JcrFileSupportService.java @@ -0,0 +1,41 @@ +/* + * 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.apache.sling.commons.jcr.file; + +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.attribute.BasicFileAttributes; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; + +import org.jetbrains.annotations.NotNull; +import org.osgi.annotation.versioning.ProviderType; + +@ProviderType +public interface JcrFileSupportService { + + boolean isFile(@NotNull final Node node) throws RepositoryException; + + boolean isDirectory(@NotNull final Node node) throws RepositoryException; + + @NotNull + BasicFileAttributes fromPath(@NotNull final Path path) throws IOException; + +} diff --git a/src/main/java/org/apache/sling/commons/jcr/file/internal/DefaultJcrFileSupportService.java b/src/main/java/org/apache/sling/commons/jcr/file/internal/DefaultJcrFileSupportService.java new file mode 100644 index 0000000..9398ab8 --- /dev/null +++ b/src/main/java/org/apache/sling/commons/jcr/file/internal/DefaultJcrFileSupportService.java @@ -0,0 +1,139 @@ +/* + * 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.apache.sling.commons.jcr.file.internal; + +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.attribute.FileTime; +import java.util.Calendar; + +import javax.jcr.Node; +import javax.jcr.Property; +import javax.jcr.RepositoryException; +import javax.jcr.Session; + +import org.apache.sling.commons.jcr.file.JcrFileSupportService; +import org.jetbrains.annotations.NotNull; +import org.osgi.framework.Constants; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Deactivate; +import org.osgi.service.component.annotations.Modified; +import org.osgi.service.metatype.annotations.Designate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@Component( + property = { + Constants.SERVICE_DESCRIPTION + "=Apache Sling Commons JCR File Support Service", + Constants.SERVICE_VENDOR + "=The Apache Software Foundation" + } +) +@Designate( + ocd = DefaultJcrFileSupportServiceConfiguration.class +) +public class DefaultJcrFileSupportService implements JcrFileSupportService { + + private DefaultJcrFileSupportServiceConfiguration configuration; + + private final Logger logger = LoggerFactory.getLogger(DefaultJcrFileSupportService.class); + + public DefaultJcrFileSupportService() { + } + + @Activate + public void activate(final DefaultJcrFileSupportServiceConfiguration configuration) { + this.configuration = configuration; + } + + @Modified + public void modified(final DefaultJcrFileSupportServiceConfiguration configuration) { + this.configuration = configuration; + } + + @Deactivate + public void deactivate() { + this.configuration = null; + } + + public boolean isFile(@NotNull final Node node) throws RepositoryException { + for (final String type : configuration.file_node_types()) { + if (node.isNodeType(type)) { + return true; + } + } + return false; + } + + public boolean isDirectory(@NotNull final Node node) throws RepositoryException { + for (final String type : configuration.directory_node_types()) { + if (node.isNodeType(type)) { + return true; + } + } + return false; + } + + @NotNull + public JcrFileAttributes fromPath(@NotNull final Path path) throws IOException { + try { + final JcrFileSystem fileSystem = (JcrFileSystem) path.getFileSystem(); + final Session session = fileSystem.getSession(); + final Node node = session.getNode(path.toString()); + final FileTime lastModifiedTime = timeFromProperty(node, "jcr:lastModified"); + final FileTime lastAccessTime = FileTime.fromMillis(0L); + final FileTime creationTime = timeFromProperty(node, "jcr:created"); + final boolean isRegularFile = isFile(node); + final boolean isDirectory = isDirectory(node); + final boolean isSymbolicLink = false; + final boolean isOther = !isRegularFile && !isDirectory; + final long size; + if (isRegularFile) { + size = lengthOfFileContent(node); + } else { + size = 0; + } + final JcrFileAttributes jcrFileAttributes = new JcrFileAttributes(lastModifiedTime, lastAccessTime, creationTime, isRegularFile, isDirectory, isSymbolicLink, isOther, size); + logger.info("from path {}: ", jcrFileAttributes); + return jcrFileAttributes; + } catch (Exception e) { + logger.error(e.getMessage(), e); + throw new IOException(e); + } + } + + private static FileTime timeFromProperty(final Node node, final String name) { + try { + final Property property = node.getProperty(name); + final Calendar date = property.getDate(); + return FileTime.fromMillis(date.getTimeInMillis()); + } catch (Exception e) { + return FileTime.fromMillis(0L); + } + } + + private long lengthOfFileContent(final Node node) { + try { + return node.getNode("jcr:content").getProperty("jcr:data").getLength(); + } catch (Exception e) { + return -1L; + } + } + +} diff --git a/src/main/java/org/apache/sling/commons/jcr/file/internal/DefaultJcrFileSupportServiceConfiguration.java b/src/main/java/org/apache/sling/commons/jcr/file/internal/DefaultJcrFileSupportServiceConfiguration.java new file mode 100644 index 0000000..23d2e9f --- /dev/null +++ b/src/main/java/org/apache/sling/commons/jcr/file/internal/DefaultJcrFileSupportServiceConfiguration.java @@ -0,0 +1,60 @@ +/* + * 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.apache.sling.commons.jcr.file.internal; + +import org.osgi.service.metatype.annotations.AttributeDefinition; +import org.osgi.service.metatype.annotations.ObjectClassDefinition; + +@ObjectClassDefinition( + name = "Apache Sling Commons JCR File Support Service", + description = "Provides support for JCR File tasks" +) +@interface DefaultJcrFileSupportServiceConfiguration { + + @AttributeDefinition( + name = "service ranking", + description = "service ranking" + ) + int service_ranking() default 0; + + @AttributeDefinition( + name = "file node type", + description = "node type used for new files" + ) + String file_node_type() default "nt:file"; + + @AttributeDefinition( + name = "directory node typ", + description = "node type used for new directories" + ) + String directory_node_type() default "nt:folder"; + + @AttributeDefinition( + name = "file node types", + description = "file node types" + ) + String[] file_node_types() default {"nt:file"}; + + @AttributeDefinition( + name = "directory node types", + description = "directory node types" + ) + String[] directory_node_types() default {"rep:root", "nt:folder", "sling:Folder"}; + +} diff --git a/src/main/java/org/apache/sling/commons/jcr/file/internal/JcrDirectoryStream.java b/src/main/java/org/apache/sling/commons/jcr/file/internal/JcrDirectoryStream.java new file mode 100644 index 0000000..337242e --- /dev/null +++ b/src/main/java/org/apache/sling/commons/jcr/file/internal/JcrDirectoryStream.java @@ -0,0 +1,116 @@ +/* + * 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.apache.sling.commons.jcr.file.internal; + +import java.io.IOException; +import java.nio.file.DirectoryStream; +import java.nio.file.NotDirectoryException; +import java.nio.file.Path; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +import javax.jcr.Node; +import javax.jcr.NodeIterator; +import javax.jcr.RepositoryException; + +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class JcrDirectoryStream implements DirectoryStream<Path> { + + private final JcrPath directory; + + private final JcrFileSystem fileSystem; + + private final DirectoryStream.Filter<? super Path> filter; // TODO + + private final Node node; + + private final Logger logger = LoggerFactory.getLogger(JcrDirectoryStream.class); + + // TODO take filter into account + JcrDirectoryStream(@NotNull final JcrPath directory, final Filter<? super Path> filter) throws NotDirectoryException { + this.directory = directory; + this.filter = filter; + final String path = directory.toString(); + try { + fileSystem = (JcrFileSystem) directory.getFileSystem(); + node = fileSystem.getSession().getNode(path); + logger.info("node: {} {}", node.getPath(), node.getPrimaryNodeType().getName()); + if (!fileSystem.provider().isDirectory(node)) { + logger.error("{} is not a directory", path); + throw new NotDirectoryException(path); + } + } catch (RepositoryException e) { + logger.error(e.getMessage(), e); + throw new RuntimeException("Error reading from path " + path); + } + } + + @Override + @NotNull + public Iterator<Path> iterator() { + logger.info("iterator for {}", directory); + + final Set<String> paths = new HashSet<>(); + + try { + final NodeIterator nodes = node.getNodes(); + while (nodes.hasNext()) { + final Node node = nodes.nextNode(); + if (fileSystem.provider().isDirectory(node) || fileSystem.provider().isFile(node)) { + final String path = node.getPath(); + paths.add(path); + logger.info("node {} added", path); + } + } + } catch (Exception e) { + logger.error(e.getMessage(), e); + } + + return new Iterator<Path>() { + + final Iterator<String> iterator = paths.iterator(); + + @Override + public boolean hasNext() { + return iterator.hasNext(); + } + + @Override + public Path next() { + return new JcrPath(fileSystem, iterator.next()); + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + + }; + + } + + @Override + public void close() throws IOException { + } + +} diff --git a/src/main/java/org/apache/sling/commons/jcr/file/internal/JcrFile.java b/src/main/java/org/apache/sling/commons/jcr/file/internal/JcrFile.java new file mode 100644 index 0000000..76dbf18 --- /dev/null +++ b/src/main/java/org/apache/sling/commons/jcr/file/internal/JcrFile.java @@ -0,0 +1,394 @@ +/* + * 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.apache.sling.commons.jcr.file.internal; + +import java.io.File; +import java.io.FileFilter; +import java.io.FilenameFilter; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URL; +import java.nio.file.Path; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; + +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class JcrFile extends File { + + private final String path; + + private final JcrFileSystem fileSystem; + + private final Logger logger = LoggerFactory.getLogger(JcrFile.class); + + JcrFile(@NotNull final JcrFileSystem fileSystem, @NotNull String pathname) { + super(pathname); + this.fileSystem = fileSystem; + this.path = PathUtil.normalize(pathname); // TODO + } + + @Override + @NotNull + public String getName() { + logger.info("getting name for {}", path); + return PathUtil.getName(path); + } + + @Override + public String getParent() { + logger.info("getting parent for {}", path); + return PathUtil.getParent(path); + } + + @Override + public File getParentFile() { + logger.info("getting parent file for {}", path); + final String path = PathUtil.getParent(this.path); + if (path != null) { + return new JcrFile(fileSystem, path); + } else { + return null; + } + } + + @Override + @NotNull + public String getPath() { + logger.info("getting path {}", path); + return path; + } + + @Override + public boolean isAbsolute() { + logger.info("is absolute {}", path); + return PathUtil.isAbsolute(path); + } + + // TODO + @Override + @NotNull + public String getAbsolutePath() { + logger.info("getting absolute path {}", path); + throw new UnsupportedOperationException("getAbsolutePath"); + } + + @Override + @NotNull + public File getAbsoluteFile() { + logger.info("getting absolute file for {}", path); + return new JcrFile(fileSystem, getAbsolutePath()); + } + + // TODO + @Override + @NotNull + public String getCanonicalPath() throws IOException { + logger.info("getCanonicalPath"); + throw new UnsupportedOperationException("getCanonicalPath"); + } + + // TODO + @Override + @NotNull + public File getCanonicalFile() throws IOException { + logger.info("getCanonicalFile"); + throw new UnsupportedOperationException("getCanonicalFile"); + } + + // TODO + @Override + public URL toURL() throws MalformedURLException { + logger.info("toURL"); + throw new UnsupportedOperationException("toURL"); + } + + // TODO + @Override + @NotNull + public URI toURI() { + logger.info("toURI"); + throw new UnsupportedOperationException("toURI"); + } + + // TODO + @Override + public boolean canRead() { + logger.info("canRead"); + return true; + } + + // TODO + @Override + public boolean canWrite() { + logger.info("canWrite"); + return true; + } + + // TODO + @Override + public boolean exists() { + logger.info("exists"); + return true; + } + + @Override + public boolean isDirectory() { + logger.info("is directory {}", path); + try { + return fileSystem.provider().isDirectory(getNode()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Override + public boolean isFile() { + logger.info("is file {}", path); + try { + return fileSystem.provider().isFile(getNode()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + // TODO + @Override + public boolean isHidden() { + logger.info("isHidden"); + return false; + } + + // TODO + @Override + public long lastModified() { + logger.info("lastModified"); + throw new UnsupportedOperationException("lastModified"); + } + + // TODO + @Override + public long length() { + logger.info("length"); + throw new UnsupportedOperationException("length"); + } + + // TODO + @Override + public boolean createNewFile() throws IOException { + logger.info("createNewFile"); + throw new UnsupportedOperationException("createNewFile"); + } + + // TODO + @Override + public boolean delete() { + logger.info("delete"); + throw new UnsupportedOperationException("delete"); + } + + // TODO + @Override + public void deleteOnExit() { + logger.info("deleteOnExit"); + throw new UnsupportedOperationException("deleteOnExit"); + } + + // TODO + @Override + public String[] list() { + logger.info("list"); + throw new UnsupportedOperationException("list"); + } + + // TODO + @Override + public String[] list(FilenameFilter filter) { + logger.info("list"); + throw new UnsupportedOperationException("list"); + } + + // TODO + @Override + public File[] listFiles() { + logger.info("listFiles"); + throw new UnsupportedOperationException("listFiles"); + } + + // TODO + @Override + public File[] listFiles(FilenameFilter filter) { + logger.info("listFiles"); + throw new UnsupportedOperationException("listFiles"); + } + + // TODO + @Override + public File[] listFiles(FileFilter filter) { + logger.info("listFiles"); + throw new UnsupportedOperationException("listFiles"); + } + + // TODO + @Override + public boolean mkdir() { + logger.info("mkdir"); + throw new UnsupportedOperationException("mkdir"); + } + + // TODO + @Override + public boolean mkdirs() { + logger.info("mkdirs"); + throw new UnsupportedOperationException("mkdirs"); + } + + // TODO + @Override + public boolean renameTo(File dest) { + logger.info("renameTo"); + throw new UnsupportedOperationException("renameTo"); + } + + // TODO + @Override + public boolean setLastModified(long time) { + logger.info("setLastModified"); + throw new UnsupportedOperationException("setLastModified"); + } + + // TODO + @Override + public boolean setReadOnly() { + logger.info("setReadOnly"); + throw new UnsupportedOperationException("setReadOnly"); + } + + // TODO + @Override + public boolean setWritable(boolean writable, boolean ownerOnly) { + logger.info("setWritable"); + throw new UnsupportedOperationException("setWritable"); + } + + // TODO + @Override + public boolean setWritable(boolean writable) { + logger.info("setWritable"); + throw new UnsupportedOperationException("setWritable"); + } + + // TODO + @Override + public boolean setReadable(boolean readable, boolean ownerOnly) { + logger.info("setReadable"); + throw new UnsupportedOperationException("setReadable"); + } + + // TODO + @Override + public boolean setReadable(boolean readable) { + logger.info("setReadable"); + throw new UnsupportedOperationException("setReadable"); + } + + // TODO + @Override + public boolean setExecutable(boolean executable, boolean ownerOnly) { + logger.info("setExecutable"); + throw new UnsupportedOperationException("setExecutable"); + } + + // TODO + @Override + public boolean setExecutable(boolean executable) { + logger.info("setExecutable"); + throw new UnsupportedOperationException("setExecutable"); + } + + // TODO + @Override + public boolean canExecute() { + logger.info("canExecute"); + return false; + } + + @Override + public long getTotalSpace() { + logger.info("getting total space ({})", path); + return Long.MAX_VALUE; + } + + @Override + public long getFreeSpace() { + logger.info("getting free space ({})", path); + return Long.MAX_VALUE; + } + + @Override + public long getUsableSpace() { + logger.info("getting usable space ({})", path); + return Long.MAX_VALUE; + } + + // TODO + @Override + public int compareTo(final File pathname) { + logger.info("compareTo"); + return path.compareTo(pathname.getPath()); + } + + // TODO + @Override + public boolean equals(final Object obj) { + logger.info("equals"); + if ((obj != null) && (obj instanceof JcrFile)) { + return compareTo((JcrFile) obj) == 0; + } + return false; + } + + @Override + public int hashCode() { + logger.info("hash code ({})", path); + return path.hashCode() ^ 1234321; + } + + @Override + public String toString() { + logger.info("to string ({})", path); + return path; + } + + @Override + @NotNull + public Path toPath() { + logger.info("to path ({})", path); + return new JcrPath(fileSystem, path); + } + + Node getNode() throws RepositoryException { + return fileSystem.getSession().getNode(path); + } + +} diff --git a/src/main/java/org/apache/sling/commons/jcr/file/internal/JcrFileAttributes.java b/src/main/java/org/apache/sling/commons/jcr/file/internal/JcrFileAttributes.java new file mode 100644 index 0000000..a508ffd --- /dev/null +++ b/src/main/java/org/apache/sling/commons/jcr/file/internal/JcrFileAttributes.java @@ -0,0 +1,98 @@ +/* + * 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.apache.sling.commons.jcr.file.internal; + +import java.nio.file.attribute.BasicFileAttributes; +import java.nio.file.attribute.FileTime; + +public class JcrFileAttributes implements BasicFileAttributes { + + private final FileTime lastModifiedTime; + + private final FileTime lastAccessTime; + + private final FileTime creationTime; + + private final boolean isRegularFile; + + private final boolean isDirectory; + + private final boolean isSymbolicLink; + + private final boolean isOther; + + private final long size; + + JcrFileAttributes(final FileTime lastModifiedTime, final FileTime lastAccessTime, final FileTime creationTime, final boolean isRegularFile, final boolean isDirectory, final boolean isSymbolicLink, final boolean isOther, final long size) { + this.lastModifiedTime = lastModifiedTime; + this.lastAccessTime = lastAccessTime; + this.creationTime = creationTime; + this.isRegularFile = isRegularFile; + this.isDirectory = isDirectory; + this.isSymbolicLink = isSymbolicLink; + this.isOther = isOther; + this.size = size; + } + + @Override + public FileTime lastModifiedTime() { + return lastModifiedTime; + } + + @Override + public FileTime lastAccessTime() { + return lastAccessTime; + } + + @Override + public FileTime creationTime() { + return creationTime; + } + + @Override + public boolean isRegularFile() { + return isRegularFile; + } + + @Override + public boolean isDirectory() { + return isDirectory; + } + + @Override + public boolean isSymbolicLink() { + return isSymbolicLink; + } + + @Override + public boolean isOther() { + return isOther; + } + + @Override + public long size() { + return size; + } + + @Override + public Object fileKey() { + return null; + } + +} diff --git a/src/main/java/org/apache/sling/commons/jcr/file/internal/JcrFileChannel.java b/src/main/java/org/apache/sling/commons/jcr/file/internal/JcrFileChannel.java new file mode 100644 index 0000000..9f8e7dd --- /dev/null +++ b/src/main/java/org/apache/sling/commons/jcr/file/internal/JcrFileChannel.java @@ -0,0 +1,189 @@ +/* + * 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.apache.sling.commons.jcr.file.internal; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.MappedByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.channels.FileLock; +import java.nio.channels.ReadableByteChannel; +import java.nio.channels.WritableByteChannel; + +import javax.jcr.Binary; +import javax.jcr.Node; +import javax.jcr.RepositoryException; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * https://wiki.apache.org/jackrabbit/JCR%20Binary%20Usecase + */ +public class JcrFileChannel extends FileChannel { + + private long position; + + private final Node node; + + private final Logger logger = LoggerFactory.getLogger(JcrFileChannel.class); + + JcrFileChannel(final Node node) throws RepositoryException { + logger.info("JcrFileChannel: {}", node.getPath()); + this.node = node; + } + + private Binary binary() throws RepositoryException { + return node.getNode("jcr:content").getProperty("jcr:data").getBinary(); + } + + // TODO + @Override + public int read(ByteBuffer dst) throws IOException { + logger.info("read"); + /* + try { + int read = binary().read(dst.array(), position); + position = position + read; + logger.info("read {} bytes into array of length {}, new position is {}", read, dst.array().length, position); + return read; + } catch (Exception e) { + logger.error(e.getMessage(), e); + throw new IOException(e); + } + */ + return 0; + } + + // TODO + @Override + public long read(ByteBuffer[] dsts, int offset, int length) throws IOException { + logger.info("read {} {}", offset, length); + return 0; + } + + // TODO + @Override + public int write(ByteBuffer src) throws IOException { + logger.info("write"); + return 0; + } + + // TODO + @Override + public long write(ByteBuffer[] srcs, int offset, int length) throws IOException { + logger.info("write {} {}", offset, length); + return 0; + } + + @Override + public long position() throws IOException { + logger.info("position: {}", position); + return position; + } + + // TODO + @Override + public FileChannel position(long newPosition) throws IOException { + logger.info("setting position ({}) to new position {}", position, newPosition); + this.position = newPosition; + return this; + } + + // TODO + @Override + public long size() throws IOException { + logger.info("size"); + try { + return binary().getSize(); + } catch (Exception e) { + logger.error(e.getMessage(), e); + throw new IOException(e); + } + } + + // TODO + @Override + public FileChannel truncate(long size) throws IOException { + logger.info("truncate", size); + return this; + } + + // TODO + @Override + public void force(boolean metaData) throws IOException { + logger.info("force {}", metaData); + } + + // TODO + @Override + public long transferTo(long position, long count, WritableByteChannel target) throws IOException { + logger.info("transferTo {} {}", position, count); + return 0; + } + + // TODO + @Override + public long transferFrom(ReadableByteChannel src, long position, long count) throws IOException { + logger.info("transferFrom {} {}", position, count); + return 0; + } + + // TODO + @Override + public int read(ByteBuffer dst, long position) throws IOException { + logger.info("read {}", position); + return 0; + } + + // TODO + @Override + public int write(ByteBuffer src, long position) throws IOException { + logger.info("write {}", position); + return 0; + } + + // TODO + @Override + public MappedByteBuffer map(MapMode mode, long position, long size) throws IOException { + logger.info("map {} {}", position, size); + return null; + } + + // TODO + @Override + public FileLock lock(long position, long size, boolean shared) throws IOException { + logger.info("lock {} {} {}", position, size, shared); + return null; + } + + // TODO + @Override + public FileLock tryLock(long position, long size, boolean shared) throws IOException { + logger.info("tryLock {} {} {}", position, size, shared); + return null; + } + + // TODO + @Override + protected void implCloseChannel() throws IOException { + logger.info("implCloseChannel"); + } + +} diff --git a/src/main/java/org/apache/sling/commons/jcr/file/internal/JcrFileStore.java b/src/main/java/org/apache/sling/commons/jcr/file/internal/JcrFileStore.java new file mode 100644 index 0000000..a99206e --- /dev/null +++ b/src/main/java/org/apache/sling/commons/jcr/file/internal/JcrFileStore.java @@ -0,0 +1,79 @@ +/* + * 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.apache.sling.commons.jcr.file.internal; + +import java.io.IOException; +import java.nio.file.FileStore; +import java.nio.file.attribute.FileAttributeView; +import java.nio.file.attribute.FileStoreAttributeView; + +public class JcrFileStore extends FileStore { + + // TODO + @Override + public String name() { + return null; + } + + @Override + public String type() { + return "jcr"; + } + + @Override + public boolean isReadOnly() { + return false; + } + + @Override + public long getTotalSpace() throws IOException { + return Long.MAX_VALUE; + } + + @Override + public long getUsableSpace() throws IOException { + return Long.MAX_VALUE; + } + + @Override + public long getUnallocatedSpace() throws IOException { + return Long.MAX_VALUE; + } + + @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 null; + } + +} diff --git a/src/main/java/org/apache/sling/commons/jcr/file/internal/JcrFileSystem.java b/src/main/java/org/apache/sling/commons/jcr/file/internal/JcrFileSystem.java new file mode 100644 index 0000000..722f8c3 --- /dev/null +++ b/src/main/java/org/apache/sling/commons/jcr/file/internal/JcrFileSystem.java @@ -0,0 +1,139 @@ +/* + * 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.apache.sling.commons.jcr.file.internal; + +import java.io.IOException; +import java.net.URI; +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.Collections; +import java.util.Set; + +import javax.jcr.Node; +import javax.jcr.Session; + +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class JcrFileSystem extends FileSystem { + + private final JcrFileSystemProvider provider; + + private final URI uri; + + private final Session session; + + private final Iterable<Path> rootDirectories; + + static final String SEPARATOR = "/"; + + private final Logger logger = LoggerFactory.getLogger(JcrFileSystem.class); + + JcrFileSystem(final JcrFileSystemProvider provider, final URI uri, final Session session) { + this.provider = provider; + this.uri = uri; + this.session = session; + rootDirectories = Collections.singleton(new JcrPath(this, "/")); + } + + @Override + public JcrFileSystemProvider provider() { + logger.trace("provider"); + return provider; + } + + @Override + public void close() throws IOException { + logger.info("close"); + provider.removeFromCache(this); + session.logout(); + } + + @Override + public boolean isOpen() { + logger.info("isOpen"); + return session.isLive(); + } + + @Override + public boolean isReadOnly() { + logger.info("isReadOnly"); + return false; + } + + @Override + public String getSeparator() { + logger.info("getSeparator"); + return SEPARATOR; + } + + @Override + public Iterable<Path> getRootDirectories() { + logger.info("getRootDirectories"); + return rootDirectories; + } + + @Override + public Iterable<FileStore> getFileStores() { + logger.info("getFileStores"); + return null; + } + + @Override + public Set<String> supportedFileAttributeViews() { + logger.info("supportedFileAttributeViews"); + return Collections.singleton("basic"); + } + + @Override + @NotNull + public Path getPath(final String first, final String... more) { + logger.info("getPath: {}, {}", first, more); + return new JcrPath(this, first, more); + } + + @Override + public PathMatcher getPathMatcher(String syntaxAndPattern) { + logger.info("getPathMatcher"); + throw new UnsupportedOperationException(); // TODO implement? + } + + @Override + public UserPrincipalLookupService getUserPrincipalLookupService() { + logger.info("getUserPrincipalLookupService"); + throw new UnsupportedOperationException(); // TODO implement? + } + + @Override + public WatchService newWatchService() throws IOException { + logger.info("newWatchService"); + throw new UnsupportedOperationException(); // TODO implement? + } + + Session getSession() { + return session; + } + +} diff --git a/src/main/java/org/apache/sling/commons/jcr/file/internal/JcrFileSystemProvider.java b/src/main/java/org/apache/sling/commons/jcr/file/internal/JcrFileSystemProvider.java new file mode 100644 index 0000000..0de4955 --- /dev/null +++ b/src/main/java/org/apache/sling/commons/jcr/file/internal/JcrFileSystemProvider.java @@ -0,0 +1,283 @@ +/* + * 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.apache.sling.commons.jcr.file.internal; + +import java.io.File; +import java.io.IOException; +import java.net.URI; +import java.nio.channels.FileChannel; +import java.nio.channels.SeekableByteChannel; +import java.nio.file.AccessMode; +import java.nio.file.CopyOption; +import java.nio.file.DirectoryStream; +import java.nio.file.FileStore; +import java.nio.file.FileSystem; +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.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; +import javax.jcr.Session; + +import org.apache.sling.commons.jcr.file.JcrFileSupportService; +import org.osgi.framework.Constants; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@Component( + service = FileSystemProvider.class, + property = { + Constants.SERVICE_DESCRIPTION + "=Apache Sling Commons JCR File System Provider", + Constants.SERVICE_VENDOR + "=The Apache Software Foundation", + "type=jcr" + } +) +public class JcrFileSystemProvider extends FileSystemProvider { + + private final Map<Session, JcrFileSystem> cache = new HashMap<>(); + + private final JcrFileStore jcrFileStore = new JcrFileStore(); + + @Reference + private volatile JcrFileSupportService jcrFileSupportService; + + private final Object lock = new Object(); + + static final String SCHEME = "jcr"; + + private final Logger logger = LoggerFactory.getLogger(JcrFileSystemProvider.class); + + public JcrFileSystemProvider() { + } + + @Override + public String getScheme() { + logger.info("getting scheme"); + return SCHEME; + } + + @Override + public FileSystem newFileSystem(final URI uri, final Map<String, ?> env) throws IOException { + logger.info("new file system: {}, {}", uri, env); + + if (!getScheme().equalsIgnoreCase(uri.getScheme())) { + throw new IllegalArgumentException("URI scheme is not " + getScheme()); + } + + if (env == null) { + throw new IllegalArgumentException("env is null"); + } + + if (!env.containsKey(Session.class.getName())) { + throw new IllegalArgumentException("no session in env"); + } + + final Object object = env.get(Session.class.getName()); + if (object == null) { + throw new IllegalArgumentException("session in env is null"); + } + + if (!(object instanceof Session)) { + throw new IllegalArgumentException("session in env is not a " + Session.class.getName()); + } + + final Session session = (Session) object; + if (!session.isLive()) { + throw new IllegalArgumentException("session in env is not live"); + } + + synchronized (lock) { + if (isInCache(session)) { + throw new IllegalArgumentException("session is already in use"); + } + final JcrFileSystem fileSystem = new JcrFileSystem(this, uri, session); + putIntoCache(fileSystem); + return fileSystem; + } + } + + @Override + public FileSystem getFileSystem(final URI uri) { + final String message = String.format("getting file system by URI is not supported: %s", uri.toString()); + logger.error(message); + throw new UnsupportedOperationException(message); + } + + @Override + public Path getPath(final URI uri) { + final String message = String.format("getting path by URI is not supported: %s", uri.toString()); + logger.error(message); + throw new UnsupportedOperationException(message); + } + + // TODO + public FileChannel newFileChannel(Path path, Set<? extends OpenOption> options, FileAttribute<?>... attrs) throws IOException { + logger.info("newFileChannel"); + try { + final Node node = PathUtil.toNode(path); + return new JcrFileChannel(node); + } catch (Exception e) { + throw new IOException(e); + } + } + + // TODO + @Override + public SeekableByteChannel newByteChannel(Path path, Set<? extends OpenOption> options, FileAttribute<?>... attrs) throws IOException { + logger.info("newByteChannel"); + try { + final Node node = PathUtil.toNode(path); + return new JcrFileChannel(node); + } catch (Exception e) { + throw new IOException(e); + } + } + + @Override + public DirectoryStream<Path> newDirectoryStream(final Path dir, final DirectoryStream.Filter<? super Path> filter) throws IOException { + logger.info("new directory stream for {}", dir.toString()); + if (dir instanceof JcrPath) { + return new JcrDirectoryStream((JcrPath) dir, filter); + } else { + throw new IllegalArgumentException("directory is not a JCR path"); + } + } + + // TODO + @Override + public void createDirectory(Path dir, FileAttribute<?>... attrs) throws IOException { + logger.info("createDirectory"); + } + + // TODO + @Override + public void delete(Path path) throws IOException { + logger.info("delete: {}", path); + final File file = path.toFile(); + if (!file.exists()) { + throw new NoSuchFileException(path.toString()); + } else { + // TODO delete + } + } + + // TODO + @Override + public void copy(Path source, Path target, CopyOption... options) throws IOException { + logger.info("copy"); + } + + // TODO + @Override + public void move(Path source, Path target, CopyOption... options) throws IOException { + logger.info("move"); + } + + // TODO + @Override + public boolean isSameFile(Path path, Path path2) throws IOException { + logger.info("isSameFile"); + return false; + } + + // TODO + @Override + public boolean isHidden(Path path) throws IOException { + logger.info("isHidden"); + return false; + } + + @Override + public FileStore getFileStore(final Path path) throws IOException { + logger.info("getting file store for {}", path.toString()); + return jcrFileStore; + } + + // TODO + @Override + public void checkAccess(final Path path, final AccessMode... modes) throws IOException { + logger.info("checking access: {}", path.toString()); + } + + // TODO + @Override + public <V extends FileAttributeView> V getFileAttributeView(final Path path, final Class<V> type, final LinkOption... options) { + logger.info("getting file attribute view"); + return null; + } + + // TODO + @Override + public <A extends BasicFileAttributes> A readAttributes(final Path path, final Class<A> type, final LinkOption... options) throws IOException { + logger.info("reading attributes: {}, {}, {}", path, type, options); + if (type == BasicFileAttributes.class) { + final BasicFileAttributes basicFileAttributes = jcrFileSupportService.fromPath(path); + logger.info("basic file attributes: {}", basicFileAttributes); + return (A) basicFileAttributes; + } else { + throw new IOException("Unsupported file attributes type: " + type); + } + } + + // TODO + @Override + public Map<String, Object> readAttributes(Path path, String attributes, LinkOption... options) throws IOException { + logger.info("readAttributes: {}, {}, {}", path, attributes, options); + return Collections.emptyMap(); + } + + // TODO + @Override + public void setAttribute(Path path, String attribute, Object value, LinkOption... options) throws IOException { + logger.info("setAttribute"); + } + + boolean isFile(final Node node) throws RepositoryException { + return jcrFileSupportService.isFile(node); + } + + boolean isDirectory(final Node node) throws RepositoryException { + return jcrFileSupportService.isDirectory(node); + } + + boolean isInCache(final Session session) { + return cache.containsKey(session); + } + + void putIntoCache(final JcrFileSystem fileSystem) { + cache.put(fileSystem.getSession(), fileSystem); + } + + void removeFromCache(final JcrFileSystem fileSystem) { + cache.remove(fileSystem.getSession()); + } + +} diff --git a/src/main/java/org/apache/sling/commons/jcr/file/internal/JcrPath.java b/src/main/java/org/apache/sling/commons/jcr/file/internal/JcrPath.java new file mode 100644 index 0000000..7108186 --- /dev/null +++ b/src/main/java/org/apache/sling/commons/jcr/file/internal/JcrPath.java @@ -0,0 +1,284 @@ +/* + * 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.apache.sling.commons.jcr.file.internal; + +import java.io.File; +import java.io.IOException; +import java.net.URI; +import java.nio.file.FileSystem; +import java.nio.file.LinkOption; +import java.nio.file.Path; +import java.nio.file.WatchEvent; +import java.nio.file.WatchKey; +import java.nio.file.WatchService; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class JcrPath implements Path { + + private final JcrFileSystem fileSystem; + + private final String path; + + private final List<String> names; + + private final Logger logger = LoggerFactory.getLogger(JcrPath.class); + + JcrPath(final JcrFileSystem fileSystem, final String path) { + this.fileSystem = fileSystem; + this.path = PathUtil.normalize(path); + this.names = names(this.path); + logger.info("new path: {}", path); + } + + JcrPath(final JcrFileSystem fileSystem, final String first, final String... more) { + this.fileSystem = fileSystem; + this.path = PathUtil.normalize(first + JcrFileSystem.SEPARATOR + String.join(JcrFileSystem.SEPARATOR, more)); + this.names = names(this.path); + logger.info("new path: {}", path); + } + + private List<String> names(final String path) { + final List<String> names = new ArrayList<>(); + if (path != null) { + final String[] strings = path.split("/"); + for (final String string : strings) { + if (!string.isEmpty()) { + names.add(string); + } + } + } + return names; + } + + @Override + public FileSystem getFileSystem() { + logger.info("getting file system for {}", path); + return fileSystem; + } + + @Override + public boolean isAbsolute() { + logger.info("isAbsolute: {}", path); + return path.startsWith("/"); + } + + @Override + public Path getRoot() { + logger.info("getting root for {}", path); + return new JcrPath(fileSystem, "/"); + } + + @Override + public Path getFileName() { + logger.info("getting file name for {}", path); + if (names.size() == 0) { + return null; + } else { + final String name = names.get(names.size() - 1); + logger.info("file name: {}", name); + return new JcrPath(fileSystem, name); + } + } + + @Override + public Path getParent() { + logger.info("getting parent for {}", path); + final String parent = PathUtil.getParent(path); + if (parent == null) { + return null; + } + return new JcrPath(fileSystem, parent); + } + + @Override + public int getNameCount() { + logger.info("getting name count: {}", path); + return names.size(); + } + + @Override + public Path getName(int index) { + logger.info("getting name: {}", path); + if (names.size() == 0) { + throw new IllegalArgumentException("TODO"); + } + if (index < 0) { + throw new IllegalArgumentException("TODO"); + } + if (index >= names.size()) { + throw new IllegalArgumentException("TODO"); + } + return new JcrPath(fileSystem, names.get(index)); + } + + // TODO + @Override + public Path subpath(int beginIndex, int endIndex) { + logger.info("subpath: {}", path); + return null; + } + + // TODO + @Override + public boolean startsWith(Path other) { + logger.info("startsWith: {}", path); + return false; + } + + // TODO + @Override + public boolean startsWith(String other) { + logger.info("startsWith: {}", path); + return false; + } + + // TODO + @Override + public boolean endsWith(Path other) { + logger.info("endsWith: {}", path); + return false; + } + + // TODO + @Override + public boolean endsWith(String other) { + logger.info("endsWith: {}", path); + return false; + } + + @Override + public Path normalize() { + logger.info("normalizing path {}", path); + return new JcrPath(fileSystem, PathUtil.normalize(path)); + } + + // TODO + @Override + public Path resolve(Path other) { + logger.info("resolving given path {} against this path {}", other, this); + if (other.isAbsolute()) { + return other; + } + if (this.path.endsWith("/")) { + final String path = this.path.concat(other.toString()); + return new JcrPath(fileSystem, path); + } else { + final String path = String.format("%s/%s", this.path, other.toString()); + return new JcrPath(fileSystem, path); + } + } + + // TODO + @Override + public Path resolve(String other) { + logger.info("resolving given path {} against this path {}", other, this); + final Path path = new JcrPath(fileSystem, other); // TODO InvalidPathException + return resolve(path); + } + + // TODO + @Override + public Path resolveSibling(Path other) { + logger.info("resolveSibling: {}", other); + return null; + } + + // TODO + @Override + public Path resolveSibling(String other) { + logger.info("resolveSibling: {}", other); + return null; + } + + // TODO + @Override + public Path relativize(Path other) { + logger.info("relativize: {}", other); + return null; + } + + // TODO + @Override + public URI toUri() { + logger.info("toUri: {}", path); + return null; + } + + @Override + public Path toAbsolutePath() { + logger.info("toAbsolutePath: {}", path); + if (isAbsolute()) { + return this; + } else { + return new JcrPath(fileSystem, "/".concat(path)); + } + } + + // TODO + @Override + public Path toRealPath(LinkOption... options) throws IOException { + logger.info("toRealPath: {}", path); + return null; + } + + @Override + public File toFile() { + logger.info("to file: {}", path); + return new JcrFile(fileSystem, path); + } + + // TODO + @Override + public WatchKey register(WatchService watcher, WatchEvent.Kind<?>[] events, WatchEvent.Modifier... modifiers) throws IOException { + logger.info("register1"); + return null; + } + + // TODO + @Override + public WatchKey register(WatchService watcher, WatchEvent.Kind<?>... events) throws IOException { + logger.info("register2"); + return null; + } + + // TODO + @Override + public Iterator<Path> iterator() { + logger.info("iterator"); + return null; + } + + // TODO + @Override + public int compareTo(Path other) { + logger.info("compareTo"); + return 0; + } + + @Override + public String toString() { + return path; + } + +} diff --git a/src/main/java/org/apache/sling/commons/jcr/file/internal/PathUtil.java b/src/main/java/org/apache/sling/commons/jcr/file/internal/PathUtil.java new file mode 100644 index 0000000..1db1a09 --- /dev/null +++ b/src/main/java/org/apache/sling/commons/jcr/file/internal/PathUtil.java @@ -0,0 +1,258 @@ +/* + * 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.apache.sling.commons.jcr.file.internal; + +import java.nio.file.Path; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; +import javax.jcr.Session; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * see org.apache.sling.api.resource.ResourceUtil + */ +public class PathUtil { + + /** + * Resolves relative path segments '.' and '..' in the absolute path. + * Returns {@code null} if not possible (.. points above root) or if path is not + * absolute. + * + * @param path The path to normalize + * @return The normalized path or {@code null}. + */ + @Nullable + static String normalize(@NotNull String path) { + + // don't care for empty paths + if (path.length() == 0) { + return path; + } + + // prepare the path buffer with trailing slash (simplifies impl) + int absOffset = (path.charAt(0) == '/') ? 0 : 1; + char[] buf = new char[path.length() + 1 + absOffset]; + if (absOffset == 1) { + buf[0] = '/'; + } + path.getChars(0, path.length(), buf, absOffset); + buf[buf.length - 1] = '/'; + + int lastSlash = 0; // last slash in path + int numDots = 0; // number of consecutive dots after last slash + + int bufPos = 0; + for (int bufIdx = lastSlash; bufIdx < buf.length; bufIdx++) { + char c = buf[bufIdx]; + if (c == '/') { + if (numDots == 2) { + if (bufPos == 0) { + return null; + } + + do { + bufPos--; + } while (bufPos > 0 && buf[bufPos] != '/'); + } + + lastSlash = bufIdx; + numDots = 0; + } else if (c == '.' && useDot(buf, bufIdx) && numDots < 2) { + numDots++; + } else { + // find the next slash + int nextSlash = bufIdx + 1; + while (nextSlash < buf.length && buf[nextSlash] != '/') { + nextSlash++; + } + + // append up to the next slash (or end of path) + if (bufPos < lastSlash) { + int segLen = nextSlash - bufIdx + 1; + System.arraycopy(buf, lastSlash, buf, bufPos, segLen); + bufPos += segLen; + } else { + bufPos = nextSlash; + } + + numDots = 0; + lastSlash = nextSlash; + bufIdx = nextSlash; + } + } + + String resolved; + if (bufPos == 0 && numDots == 0) { + resolved = (absOffset == 0) ? "/" : ""; + } else if ((bufPos - absOffset) == path.length()) { + resolved = path; + } else { + resolved = new String(buf, absOffset, bufPos - absOffset); + } + + return resolved; + } + + // use this dot only if followed by / + // don't use if followed by neither . nor / + // keep checking till a non-dot is found + private static boolean useDot(char[] buf, int bufIdx) { + while(bufIdx < buf.length -1) { + if(buf[bufIdx] == '/') { + return true; + } + else if(buf[bufIdx] != '.') { + return false; + } + bufIdx++; + } + return true; + } + /** + * Utility method returns the parent path of the given <code>path</code>, + * which is normalized by {@link #normalize(String)} before resolving the + * parent. + * + * @param path The path whose parent is to be returned. + * @return <code>null</code> if <code>path</code> is the root path ( + * <code>/</code>) or if <code>path</code> is a single name + * containing no slash (<code>/</code>) characters. + * @throws IllegalArgumentException If the path cannot be normalized by the + * {@link #normalize(String)} method. + * @throws NullPointerException If <code>path</code> is <code>null</code>. + */ + @Nullable + static String getParent(@NotNull String path) { + if ("/".equals(path)) { + return null; + } + + // normalize path (remove . and ..) + path = normalize(path); + + // if normalized to root, there is no parent + if (path == null || "/".equals(path)) { + return null; + } + + String workspaceName = null; + + final int wsSepPos = path.indexOf(":/"); + if (wsSepPos != -1) { + workspaceName = path.substring(0, wsSepPos); + path = path.substring(wsSepPos + 1); + } + + // find the last slash, after which to cut off + int lastSlash = path.lastIndexOf('/'); + if (lastSlash < 0) { + // no slash in the path + return null; + } else if (lastSlash == 0) { + // parent is root + if (workspaceName != null) { + return workspaceName + ":/"; + } + return "/"; + } + + String parentPath = path.substring(0, lastSlash); + if (workspaceName != null) { + return workspaceName + ":" + parentPath; + } + return parentPath; + } + + /** + * Utility method returns the ancestor's path at the given <code>level</code> + * relative to <code>path</code>, which is normalized by {@link #normalize(String)} + * before resolving the ancestor. + * + * <ul> + * <li><code>level</code> = 0 returns the <code>path</code>.</li> + * <li><code>level</code> = 1 returns the parent of <code>path</code>, if it exists, <code>null</code> otherwise.</li> + * <li><code>level</code> = 2 returns the grandparent of <code>path</code>, if it exists, <code>null</code> otherwise.</li> + * </ul> + * + * @param path The path whose ancestor is to be returned. + * @param level The relative level of the ancestor, relative to <code>path</code>. + * @return <code>null</code> if <code>path</code> doesn't have an ancestor at the + * specified <code>level</code>. + * @throws IllegalArgumentException If the path cannot be normalized by the + * {@link #normalize(String)} method or if <code>level</code> < 0. + * @throws NullPointerException If <code>path</code> is <code>null</code>. + * @since 2.2 (Sling API Bundle 2.2.0) + */ + static String getParent(final String path, final int level) { + if ( level < 0 ) { + throw new IllegalArgumentException("level must be non-negative"); + } + String result = path; + for(int i=0; i<level; i++) { + result = getParent(result); + if ( result == null ) { + break; + } + } + return result; + } + + /** + * Utility method returns the name of the given <code>path</code>, which is + * normalized by {@link #normalize(String)} before resolving the name. + * + * @param path The path whose name (the last path element) is to be + * returned. + * @return The empty string if <code>path</code> is the root path ( + * <code>/</code>) or if <code>path</code> is a single name + * containing no slash (<code>/</code>) characters. + * @throws IllegalArgumentException If the path cannot be normalized by the + * {@link #normalize(String)} method. + * @throws NullPointerException If <code>path</code> is <code>null</code>. + */ + @NotNull + static String getName(@NotNull String path) { + if ("/".equals(path)) { + return ""; + } + + // normalize path (remove . and ..) + path = normalize(path); + if ("/".equals(path)) { + return ""; + } + + // find the last slash + return path.substring(path.lastIndexOf('/') + 1); + } + + static boolean isAbsolute(final String path) { + return path.startsWith("/"); + } + + static Node toNode(final Path path) throws RepositoryException { + final JcrFileSystem fileSystem = (JcrFileSystem) path.getFileSystem(); + final Session session = fileSystem.getSession(); + return session.getNode(path.toString()); + } + +} diff --git a/src/main/java/org/apache/sling/commons/jcr/file/package-info.java b/src/main/java/org/apache/sling/commons/jcr/file/package-info.java new file mode 100644 index 0000000..2d0f34c --- /dev/null +++ b/src/main/java/org/apache/sling/commons/jcr/file/package-info.java @@ -0,0 +1,22 @@ +/* + * 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. + */ +@Version("0.0.1") +package org.apache.sling.commons.jcr.file; + +import org.osgi.annotation.versioning.Version; diff --git a/src/test/java/org/apache/sling/commons/jcr/file/it/JcrFileIT.java b/src/test/java/org/apache/sling/commons/jcr/file/it/JcrFileIT.java new file mode 100644 index 0000000..efb01b3 --- /dev/null +++ b/src/test/java/org/apache/sling/commons/jcr/file/it/JcrFileIT.java @@ -0,0 +1,80 @@ +/* + * 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.apache.sling.commons.jcr.file.it; + +import java.io.File; +import java.io.IOException; +import java.nio.file.FileSystem; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.ops4j.pax.exam.junit.PaxExam; +import org.ops4j.pax.exam.spi.reactors.ExamReactorStrategy; +import org.ops4j.pax.exam.spi.reactors.PerClass; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +@RunWith(PaxExam.class) +@ExamReactorStrategy(PerClass.class) +public final class JcrFileIT extends JcrFileTestSupport { + + private FileSystem fileSystem; + + // /content/starter + private File starter; + + // /content/starter/sling-logo.png + private File sling_logo_png; + + @Before + public void setUp() throws Exception { + fileSystem = fileSystem("admin", "/"); + starter = fileSystem.getPath("/content/starter").toFile(); + sling_logo_png = fileSystem.getPath("/content/starter/sling-logo.png").toFile(); + } + + @After + public void tearDown() throws IOException { + fileSystem.close(); + } + + @Test + public void test_sling_logo_png_getName() { + assertThat(sling_logo_png.getName(), is("sling-logo.png")); + } + + @Test + public void test_sling_logo_png_isFile() { + assertThat(sling_logo_png.isFile(), is(true)); + } + + @Test + public void test_sling_logo_png_isDirecory() { + assertThat(sling_logo_png.isDirectory(), is(false)); + } + + @Test + public void test_sling_logo_png_getParentFile() { + assertThat(sling_logo_png.getParentFile(), is(starter)); + } + +} diff --git a/src/test/java/org/apache/sling/commons/jcr/file/it/JcrFileSystemProviderIT.java b/src/test/java/org/apache/sling/commons/jcr/file/it/JcrFileSystemProviderIT.java new file mode 100644 index 0000000..53014e1 --- /dev/null +++ b/src/test/java/org/apache/sling/commons/jcr/file/it/JcrFileSystemProviderIT.java @@ -0,0 +1,147 @@ +/* + * 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.apache.sling.commons.jcr.file.it; + +import java.net.URI; +import java.nio.file.FileSystem; +import java.util.HashMap; +import java.util.Map; + +import javax.jcr.Session; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.ops4j.pax.exam.junit.PaxExam; +import org.ops4j.pax.exam.spi.reactors.ExamReactorStrategy; +import org.ops4j.pax.exam.spi.reactors.PerClass; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.junit.Assert.fail; + +@RunWith(PaxExam.class) +@ExamReactorStrategy(PerClass.class) +public class JcrFileSystemProviderIT extends JcrFileTestSupport { + + @Test + public void testFileSystemProvider() { + assertThat(fileSystemProvider, notNullValue()); + } + + @Test + public void testGetScheme() { + assertThat("jcr", equalTo(fileSystemProvider.getScheme())); + } + + @Test + public void testNewFileSystem() throws Exception { + final URI uri = new URI("jcr", null, "/", null); + final Map<String, Object> env = new HashMap<>(); + final Session session = userSession("admin"); + env.put(Session.class.getName(), session); + final FileSystem fileSystem = fileSystemProvider.newFileSystem(uri, env); + assertThat(fileSystem, notNullValue()); + fileSystem.close(); + assertThat(session.isLive(), is(false)); + } + + @Test + public void testNewFileSystem_InvalidScheme() throws Exception { + final URI uri = new URI("/"); + final Map<String, Object> env = new HashMap<>(); + final Session session = userSession("admin"); + env.put(Session.class.getName(), session); + try { + fileSystemProvider.newFileSystem(uri, env); + fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException e) { + assertThat(e.getMessage(), is("URI scheme is not " + fileSystemProvider.getScheme())); + } + } + + @Test + public void testNewFileSystem_EnvIsNull() throws Exception { + final URI uri = new URI("jcr", null, "/", null); + try { + fileSystemProvider.newFileSystem(uri, null); + fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException e) { + assertThat(e.getMessage(), is("env is null")); + } + } + + @Test + public void testNewFileSystem_NoSession() throws Exception { + final URI uri = new URI("jcr", null, "/", null); + final Map<String, Object> env = new HashMap<>(); + try { + fileSystemProvider.newFileSystem(uri, env); + fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException e) { + assertThat(e.getMessage(), is("no session in env")); + } + } + + @Test + public void testNewFileSystem_SessionIsNull() throws Exception { + final URI uri = new URI("jcr", null, "/", null); + final Map<String, Object> env = new HashMap<>(); + env.put(Session.class.getName(), null); + try { + fileSystemProvider.newFileSystem(uri, env); + fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException e) { + assertThat(e.getMessage(), is("session in env is null")); + } + } + + @Test + public void testNewFileSystem_InvalidSession() throws Exception { + final URI uri = new URI("jcr", null, "/", null); + final Map<String, Object> env = new HashMap<>(); + env.put(Session.class.getName(), new Object()); + try { + fileSystemProvider.newFileSystem(uri, env); + fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException e) { + assertThat(e.getMessage(), is("session in env is not a " + Session.class.getName())); + } + } + + @Test + public void testNewFileSystem_SessionInUse() throws Exception { + final URI uri = new URI("jcr", null, "/", null); + final Map<String, Object> env = new HashMap<>(); + final Session session = userSession("admin"); + env.put(Session.class.getName(), session); + final FileSystem fileSystem = fileSystemProvider.newFileSystem(uri, env); + assertThat(fileSystem, notNullValue()); + try { + fileSystemProvider.newFileSystem(uri, env); + fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException e) { + assertThat(e.getMessage(), is("session is already in use")); + } finally { + fileSystem.close(); + } + } + +} diff --git a/src/test/java/org/apache/sling/commons/jcr/file/it/JcrFileTestSupport.java b/src/test/java/org/apache/sling/commons/jcr/file/it/JcrFileTestSupport.java new file mode 100644 index 0000000..e38c648 --- /dev/null +++ b/src/test/java/org/apache/sling/commons/jcr/file/it/JcrFileTestSupport.java @@ -0,0 +1,102 @@ +/* + * 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.apache.sling.commons.jcr.file.it; + +import java.net.URI; +import java.nio.file.FileSystem; +import java.nio.file.spi.FileSystemProvider; +import java.util.Collections; +import java.util.Map; + +import javax.inject.Inject; +import javax.jcr.Credentials; +import javax.jcr.Repository; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.jcr.SimpleCredentials; + +import org.apache.sling.resource.presence.ResourcePresence; +import org.apache.sling.testing.paxexam.TestSupport; +import org.ops4j.pax.exam.Configuration; +import org.ops4j.pax.exam.Option; +import org.ops4j.pax.exam.util.Filter; + +import static org.apache.sling.testing.paxexam.SlingOptions.slingQuickstartOakTar; +import static org.apache.sling.testing.paxexam.SlingOptions.slingResourcePresence; +import static org.apache.sling.testing.paxexam.SlingVersionResolver.SLING_GROUP_ID; +import static org.ops4j.pax.exam.CoreOptions.composite; +import static org.ops4j.pax.exam.CoreOptions.junitBundles; +import static org.ops4j.pax.exam.CoreOptions.mavenBundle; +import static org.ops4j.pax.exam.cm.ConfigurationAdminOptions.factoryConfiguration; + +public abstract class JcrFileTestSupport extends TestSupport { + + @Inject + protected Repository repository; + + @Inject + @Filter(value = "(type=jcr)") + protected FileSystemProvider fileSystemProvider; + + @Inject + @Filter(value = "(path=/content/starter/sling-logo.png)") + private ResourcePresence resourcePresence; + + protected static final Credentials ADMIN_CREDENTIALS = new SimpleCredentials("admin", "admin".toCharArray()); + + @Configuration + public Option[] configuration() { + return new Option[]{ + baseConfiguration(), + quickstart(), + // Sling Commons JCR File + testBundle("bundle.filename"), + // testing + slingResourcePresence(), + factoryConfiguration("org.apache.sling.resource.presence.internal.ResourcePresenter") + .put("path", "/content/starter/sling-logo.png") + .asOption(), + mavenBundle().groupId(SLING_GROUP_ID).artifactId("org.apache.sling.starter.content").version("1.0.0"), + mavenBundle().groupId("org.apache.servicemix.bundles").artifactId("org.apache.servicemix.bundles.hamcrest").versionAsInProject(), + junitBundles() + }; + } + + protected Option quickstart() { + final int httpPort = findFreePort(); + final String workingDirectory = workingDirectory(); + return composite( + slingQuickstartOakTar(workingDirectory, httpPort) + ); + } + + protected Session userSession(final String username) throws RepositoryException { + final Session session = repository.login(ADMIN_CREDENTIALS); + final Credentials credentials = new SimpleCredentials(username, "".toCharArray()); + return session.impersonate(credentials); + } + + protected FileSystem fileSystem(final String username, final String path) throws Exception { + final URI uri = new URI("jcr", null, path, null); + final Session session = userSession(username); + final Map<String, Object> env = Collections.singletonMap(Session.class.getName(), session); + return fileSystemProvider.newFileSystem(uri, env); + } + +}
