Return-Path: X-Original-To: archive-asf-public-internal@cust-asf2.ponee.io Delivered-To: archive-asf-public-internal@cust-asf2.ponee.io Received: from cust-asf.ponee.io (cust-asf.ponee.io [163.172.22.183]) by cust-asf2.ponee.io (Postfix) with ESMTP id 4D229200D35 for ; Tue, 7 Nov 2017 10:35:25 +0100 (CET) Received: by cust-asf.ponee.io (Postfix) id 4B2161609C8; Tue, 7 Nov 2017 09:35:25 +0000 (UTC) Delivered-To: archive-asf-public@cust-asf.ponee.io Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by cust-asf.ponee.io (Postfix) with SMTP id AACF2160C01 for ; Tue, 7 Nov 2017 10:35:20 +0100 (CET) Received: (qmail 85452 invoked by uid 500); 7 Nov 2017 09:35:19 -0000 Mailing-List: contact commits-help@sling.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@sling.apache.org Delivered-To: mailing list commits@sling.apache.org Received: (qmail 85432 invoked by uid 99); 7 Nov 2017 09:35:19 -0000 Received: from ec2-52-202-80-70.compute-1.amazonaws.com (HELO gitbox.apache.org) (52.202.80.70) by apache.org (qpsmtpd/0.29) with ESMTP; Tue, 07 Nov 2017 09:35:19 +0000 Received: by gitbox.apache.org (ASF Mail Server at gitbox.apache.org, from userid 33) id 21CE2879C4; Tue, 7 Nov 2017 09:35:18 +0000 (UTC) Date: Tue, 07 Nov 2017 09:38:09 +0000 To: "commits@sling.apache.org" Subject: [sling-org-apache-sling-fsresource] 11/17: [maven-release-plugin] copy for tag org.apache.sling.fsresource-1.4.4 MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit From: rombert@apache.org In-Reply-To: <151004729939.16359.2502387015652560005@gitbox.apache.org> References: <151004729939.16359.2502387015652560005@gitbox.apache.org> X-Git-Host: gitbox.apache.org X-Git-Repo: sling-org-apache-sling-fsresource X-Git-Refname: refs/tags/org.apache.sling.fsresource-1.4.4 X-Git-Reftype: annotated tag X-Git-Rev: 588bf22896112df609fd05791d2493b490b8cc46 X-Git-NotificationType: diff X-Git-Multimail-Version: 1.5.dev Auto-Submitted: auto-generated Message-Id: <20171107093518.21CE2879C4@gitbox.apache.org> archived-at: Tue, 07 Nov 2017 09:35:25 -0000 This is an automated email from the ASF dual-hosted git repository. rombert pushed a commit to annotated tag org.apache.sling.fsresource-1.4.4 in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-fsresource.git commit 588bf22896112df609fd05791d2493b490b8cc46 Author: Stefan Seifert AuthorDate: Tue May 30 08:22:32 2017 +0000 [maven-release-plugin] copy for tag org.apache.sling.fsresource-1.4.4 git-svn-id: https://svn.apache.org/repos/asf/sling/tags/org.apache.sling.fsresource-1.4.4@1796783 13f79535-47bb-0310-9956-ffa450edef68 --- fsresource-1.x/pom.xml | 184 +++++++ .../fsprovider/internal/ContentFileExtensions.java | 86 ++++ .../sling/fsprovider/internal/FileMonitor.java | 395 +++++++++++++++ .../apache/sling/fsprovider/internal/FsMode.java | 41 ++ .../fsprovider/internal/FsResourceMapper.java | 47 ++ .../fsprovider/internal/FsResourceProvider.java | 362 ++++++++++++++ .../internal/InitialContentImportOptions.java | 71 +++ .../fsprovider/internal/mapper/ContentFile.java | 150 ++++++ .../internal/mapper/ContentFileResource.java | 127 +++++ .../internal/mapper/ContentFileResourceMapper.java | 158 ++++++ .../fsprovider/internal/mapper/FileResource.java | 272 ++++++++++ .../internal/mapper/FileResourceMapper.java | 152 ++++++ .../internal/mapper/FileVaultResourceMapper.java | 201 ++++++++ .../fsprovider/internal/mapper/jcr/FsItem.java | 161 ++++++ .../fsprovider/internal/mapper/jcr/FsNode.java | 549 +++++++++++++++++++++ .../internal/mapper/jcr/FsNodeIterator.java | 83 ++++ .../fsprovider/internal/mapper/jcr/FsNodeType.java | 157 ++++++ .../fsprovider/internal/mapper/jcr/FsProperty.java | 242 +++++++++ .../internal/mapper/jcr/FsPropertyDefinition.java | 100 ++++ .../internal/mapper/jcr/FsPropertyIterator.java | 82 +++ .../fsprovider/internal/mapper/jcr/FsValue.java | 162 ++++++ .../internal/mapper/valuemap/DateUtils.java | 99 ++++ .../internal/mapper/valuemap/ObjectConverter.java | 185 +++++++ .../mapper/valuemap/ValueMapDecorator.java | 179 +++++++ .../internal/mapper/valuemap/ValueMapUtil.java | 54 ++ .../fsprovider/internal/parser/ContentElement.java | 52 ++ .../internal/parser/ContentElementHandler.java | 69 +++ .../internal/parser/ContentElementImpl.java | 68 +++ .../internal/parser/ContentFileCache.java | 107 ++++ .../internal/parser/ContentFileParserUtil.java | 104 ++++ .../internal/parser/ContentFileTypes.java | 47 ++ .../sling/fsprovider/internal/FileMonitorTest.java | 215 ++++++++ .../fsprovider/internal/FileVaultContentTest.java | 145 ++++++ .../internal/FileVaultFileMonitorTest.java | 241 +++++++++ .../sling/fsprovider/internal/FilesFolderTest.java | 82 +++ .../internal/InitialContentImportOptionsTest.java | 59 +++ .../fsprovider/internal/InvalidRootFolderTest.java | 75 +++ .../sling/fsprovider/internal/JcrMixedTest.java | 103 ++++ .../fsprovider/internal/JcrXmlContentTest.java | 173 +++++++ .../sling/fsprovider/internal/JsonContentTest.java | 288 +++++++++++ .../internal/ResourcePathComparator.java | 32 ++ .../sling/fsprovider/internal/TestUtils.java | 130 +++++ .../internal/mapper/ContentFileTest.java | 108 ++++ .../internal/mapper/valuemap/Convert.java | 242 +++++++++ .../mapper/valuemap/ObjectConverterTest.java | 233 +++++++++ .../mapper/valuemap/ValueMapDecoratorTest.java | 157 ++++++ .../internal/mapper/valuemap/ValueMapUtilTest.java | 51 ++ .../internal/parser/ContentFileCacheTest.java | 91 ++++ .../internal/parser/ContentFileParserUtilTest.java | 71 +++ .../src/test/resources/fs-test/folder1/file1a.txt | 1 + .../src/test/resources/fs-test/folder1/file1b.txt | 1 + .../resources/fs-test/folder1/folder11/file11a.txt | 1 + .../test/resources/fs-test/folder2/content.json | 262 ++++++++++ .../fs-test/folder2/content/content2.json | 4 + .../fs-test/folder2/content/file2content.txt | 1 + .../test/resources/fs-test/folder2/folder21.xml | 4 + .../resources/fs-test/folder2/folder21/file21a.txt | 1 + .../fs-test/folder2/folder21/file21a.txt.xml | 15 + .../test/resources/fs-test/folder3/content.jcr.xml | 192 +++++++ .../fs-test/folder3/content/content2.jcr.xml | 29 ++ .../resources/fs-test/folder3/folder31/file31a.txt | 1 + .../test/resources/invalid-test/invalid.jcr.xml | 1 + .../src/test/resources/invalid-test/invalid.json | 1 + .../src/test/resources/simplelogger.properties | 19 + .../vaultfs-test/META-INF/vault/filter.xml | 25 + .../vaultfs-test/META-INF/vault/settings.xml | 23 + .../resources/vaultfs-test/jcr_root/.content.xml | 24 + .../vaultfs-test/jcr_root/content/.content.xml | 28 ++ .../vaultfs-test/jcr_root/content/dam/.content.xml | 24 + .../jcr_root/content/dam/talk.png/.content.xml | 44 ++ .../dam/talk.png/_jcr_content/renditions/original | Bin 0 -> 8668 bytes .../renditions/original.dir/.content.xml | 25 + .../_jcr_content/renditions/web.1280.1280.png | Bin 0 -> 5252 bytes .../jcr_root/content/samples/.content.xml | 23 + .../jcr_root/content/samples/en/.content.xml | 191 +++++++ .../content/samples/en/conference/.content.xml | 89 ++++ .../jcr_root/content/samples/en/tools/.content.xml | 29 ++ .../samples/en/tools/navigation/.content.xml | 27 + 78 files changed, 8327 insertions(+) diff --git a/fsresource-1.x/pom.xml b/fsresource-1.x/pom.xml new file mode 100644 index 0000000..a61f476 --- /dev/null +++ b/fsresource-1.x/pom.xml @@ -0,0 +1,184 @@ + + + + 4.0.0 + + org.apache.sling + sling + 30 + + + + org.apache.sling.fsresource + bundle + 1.4.4 + + Apache Sling File System Resource Provider + + Provides a ResourceProvider implementation supporting file system based resources. + + + + scm:svn:http://svn.apache.org/repos/asf/sling/tags/org.apache.sling.fsresource-1.4.4 + scm:svn:https://svn.apache.org/repos/asf/sling/tags/org.apache.sling.fsresource-1.4.4 + http://svn.apache.org/viewvc/sling/tags/org.apache.sling.fsresource-1.4.4 + + + + + + org.apache.sling + maven-sling-plugin + + + generate-adapter-metadata + process-classes + + generate-adapter-metadata + + + + + + org.apache.felix + maven-bundle-plugin + true + + + + + johnzon-core;scope=compile;inline=false, + geronimo-json_1.0_spec;scope=compile;inline=false, + org.apache.sling.jcr.contentparser;scope=compile;inline=false + + + + + + org.apache.rat + apache-rat-plugin + + + src/test/resources/fs-test/** + src/test/resources/invalid-test/** + src/test/resources/vaultfs-test/**/original + + + + + + + + javax.servlet + servlet-api + 2.4 + provided + + + org.apache.sling + org.apache.sling.api + 2.4.0 + + + org.apache.sling + org.apache.sling.adapter + 2.0.4 + + + org.osgi + osgi.core + + + org.osgi + osgi.cmpn + + + org.apache.commons + commons-lang3 + 3.3.2 + compile + + + commons-collections + commons-collections + 3.2.1 + compile + + + org.apache.johnzon + johnzon-core + 1.0.0 + compile + + + org.apache.geronimo.specs + geronimo-json_1.0_spec + 1.0-alpha-1 + compile + + + org.apache.sling + adapter-annotations + 1.0.0 + provided + + + org.apache.sling + org.apache.sling.commons.osgi + 2.2.0 + compile + + + org.apache.sling + org.apache.sling.jcr.contentparser + 1.2.2 + compile + + + junit + junit + test + + + org.apache.sling + org.apache.sling.testing.sling-mock + 1.9.6 + test + + + org.apache.sling + org.apache.sling.testing.osgi-mock + 2.2.4 + test + + + org.apache.sling + org.apache.sling.testing.logging-mock + 1.0.0 + test + + + org.apache.sling + org.apache.sling.testing.hamcrest + 1.0.2 + test + + + diff --git a/fsresource-1.x/src/main/java/org/apache/sling/fsprovider/internal/ContentFileExtensions.java b/fsresource-1.x/src/main/java/org/apache/sling/fsprovider/internal/ContentFileExtensions.java new file mode 100644 index 0000000..2de233e --- /dev/null +++ b/fsresource-1.x/src/main/java/org/apache/sling/fsprovider/internal/ContentFileExtensions.java @@ -0,0 +1,86 @@ +/* + * 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.fsprovider.internal; + +import static org.apache.sling.fsprovider.internal.parser.ContentFileTypes.JCR_XML_SUFFIX; +import static org.apache.sling.fsprovider.internal.parser.ContentFileTypes.XML_SUFFIX; + +import java.io.File; +import java.util.Collection; +import java.util.List; + +import org.apache.commons.lang3.StringUtils; + +/** + * Matches file names for content file extensions. + */ +public final class ContentFileExtensions { + + private final List contentFileSuffixes; + + public ContentFileExtensions(List contentFileSuffixes) { + this.contentFileSuffixes = contentFileSuffixes; + } + + /** + * Get suffix from file name. + * @param file File + * @return Content file name suffix or null if not a context file. + */ + public String getSuffix(File file) { + String fileName = "/" + file.getName(); + for (String suffix : contentFileSuffixes) { + if (StringUtils.equals(suffix, XML_SUFFIX)) { + if (StringUtils.endsWith(fileName, XML_SUFFIX) && !StringUtils.endsWith(fileName, JCR_XML_SUFFIX)) { + return suffix; + } + } + else { + if (StringUtils.endsWith(fileName, suffix)) { + return suffix; + } + } + } + return null; + } + + /** + * Checks suffix from file name. + * @param file File + * @return true if content file + */ + public boolean matchesSuffix(File file) { + return getSuffix(file) != null; + } + + /** + * @return Content file suffixes. + */ + public Collection getSuffixes() { + return contentFileSuffixes; + } + + /** + * @return true if not suffixes are defined. + */ + public boolean isEmpty() { + return contentFileSuffixes.isEmpty(); + } + +} diff --git a/fsresource-1.x/src/main/java/org/apache/sling/fsprovider/internal/FileMonitor.java b/fsresource-1.x/src/main/java/org/apache/sling/fsprovider/internal/FileMonitor.java new file mode 100644 index 0000000..88c9d6f --- /dev/null +++ b/fsresource-1.x/src/main/java/org/apache/sling/fsprovider/internal/FileMonitor.java @@ -0,0 +1,395 @@ +/* + * 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.fsprovider.internal; + +import static org.apache.sling.api.SlingConstants.PROPERTY_PATH; +import static org.apache.sling.api.SlingConstants.PROPERTY_RESOURCE_TYPE; +import static org.apache.sling.api.SlingConstants.TOPIC_RESOURCE_ADDED; +import static org.apache.sling.api.SlingConstants.TOPIC_RESOURCE_CHANGED; +import static org.apache.sling.api.SlingConstants.TOPIC_RESOURCE_REMOVED; + +import java.io.File; +import java.util.ArrayList; +import java.util.Dictionary; +import java.util.HashMap; +import java.util.Hashtable; +import java.util.List; +import java.util.Map; +import java.util.Timer; +import java.util.TimerTask; + +import org.apache.commons.lang3.StringUtils; +import org.apache.jackrabbit.vault.util.PlatformNameFormat; +import org.apache.sling.fsprovider.internal.mapper.ContentFile; +import org.apache.sling.fsprovider.internal.mapper.FileResource; +import org.apache.sling.fsprovider.internal.parser.ContentElement; +import org.apache.sling.fsprovider.internal.parser.ContentElementImpl; +import org.apache.sling.fsprovider.internal.parser.ContentFileCache; +import org.osgi.service.event.EventAdmin; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This class is a monitor for the file system + * that periodically checks for changes. + */ +public final class FileMonitor extends TimerTask { + + private final Logger log = LoggerFactory.getLogger(this.getClass()); + + private final Timer timer = new Timer(); + private boolean stop = false; + private boolean stopped = true; + + private final Monitorable root; + + private final FsResourceProvider provider; + private final FsMode fsMode; + private final ContentFileExtensions contentFileExtensions; + private final ContentFileCache contentFileCache; + + /** + * Creates a new instance of this class. + * @param provider The resource provider. + * @param interval The interval between executions of the task, in milliseconds. + * @param fsMode FS mode + * @param contentFileExtensions Content file extensions + * @param contentFileCache Content file cache + */ + public FileMonitor(final FsResourceProvider provider, final long interval, FsMode fsMode, + final ContentFileExtensions contentFileExtensions, final ContentFileCache contentFileCache) { + this.provider = provider; + this.fsMode = fsMode; + this.contentFileExtensions = contentFileExtensions; + this.contentFileCache = contentFileCache; + + File rootFile = this.provider.getRootFile(); + if (fsMode == FsMode.FILEVAULT_XML) { + rootFile = new File(this.provider.getRootFile(), "." + PlatformNameFormat.getPlatformPath(this.provider.getProviderRoot())); + } + this.root = new Monitorable(this.provider.getProviderRoot(), rootFile, null); + + createStatus(this.root, contentFileExtensions, contentFileCache); + log.debug("Starting file monitor for {} with an interval of {}ms", this.root.file, interval); + timer.schedule(this, 0, interval); + } + + /** + * Stop periodically executing this task. If the task is currently executing it + * will never be run again after the current execution, otherwise it will simply + * never run (again). + */ + void stop() { + synchronized (timer) { + if (!stop) { + stop = true; + cancel(); + timer.cancel(); + } + + boolean interrupted = false; + while (!stopped) { + try { + timer.wait(); + } + catch (InterruptedException e) { + interrupted = true; + } + } + if (interrupted) { + Thread.currentThread().interrupt(); + } + } + log.debug("Stopped file monitor for {}", this.root.file); + } + + /** + * @see java.util.TimerTask#run() + */ + @Override + public void run() { + synchronized (timer) { + stopped = false; + if (stop) { + stopped = true; + timer.notifyAll(); + return; + } + } + synchronized ( this ) { + try { + // if we don't have an event admin, we just skip the check + final EventAdmin reporter = this.provider.getEventAdmin(); + if ( reporter != null ) { + this.check(this.root, reporter); + } + } catch (Exception e) { + // ignore this + } + } + synchronized (timer) { + stopped = true; + timer.notifyAll(); + } + } + + /** + * Check the monitorable + * @param monitorable The monitorable to check + * @param reporter The EventAdmin + */ + private void check(final Monitorable monitorable, final EventAdmin reporter) { + log.trace("Checking {}", monitorable.file); + // if the file is non existing, check if it has been readded + if ( monitorable.status instanceof NonExistingStatus ) { + if ( monitorable.file.exists() ) { + // new file and reset status + createStatus(monitorable, contentFileExtensions, contentFileCache); + sendEvents(monitorable, TOPIC_RESOURCE_ADDED, reporter); + final FileStatus fs = (FileStatus)monitorable.status; + if ( fs instanceof DirStatus ) { + final DirStatus ds = (DirStatus)fs; + // remove monitorables for new folder and update folder children to send events for directory contents + ds.children = new Monitorable[0]; + checkDirStatusChildren(monitorable, reporter); + } + } + } else { + // check if the file has been removed + if ( !monitorable.file.exists() ) { + // removed file and update status + sendEvents(monitorable, TOPIC_RESOURCE_REMOVED, reporter); + monitorable.status = NonExistingStatus.SINGLETON; + contentFileCache.remove(transformPath(monitorable.path)); + } else { + // check for changes + final FileStatus fs = (FileStatus)monitorable.status; + boolean changed = false; + if ( fs.lastModified < monitorable.file.lastModified() ) { + fs.lastModified = monitorable.file.lastModified(); + // changed + sendEvents(monitorable, TOPIC_RESOURCE_CHANGED, reporter); + changed = true; + contentFileCache.remove(transformPath(monitorable.path)); + } + if ( fs instanceof DirStatus ) { + // directory + final DirStatus ds = (DirStatus)fs; + for(int i=0; i changes = collectResourceChanges(monitorable, changeType); + for (ResourceChange change : changes) { + if (log.isTraceEnabled()) { + log.debug("Send change for resource {}: {}", transformPath(change.path), change.topic); + } + final Dictionary properties = new Hashtable<>(); + properties.put(PROPERTY_PATH, transformPath(change.path)); + if (change.resourceType != null) { + properties.put(PROPERTY_RESOURCE_TYPE, change.resourceType); + } + reporter.postEvent(new org.osgi.service.event.Event(change.topic, properties)); + } + } + + /** + * Transform path for resource event. + * @param path Path + * @return Transformed path + */ + private String transformPath(String path) { + if (fsMode == FsMode.FILEVAULT_XML) { + return PlatformNameFormat.getRepositoryPath(path); + } + else { + return path; + } + } + + private List collectResourceChanges(final Monitorable monitorable, final String changeType) { + List changes = new ArrayList<>(); + if (monitorable.status instanceof ContentFileStatus) { + ContentFile contentFile = ((ContentFileStatus)monitorable.status).contentFile; + if (StringUtils.equals(changeType, TOPIC_RESOURCE_CHANGED)) { + ContentElement content = contentFile.getContent(); + // we cannot easily report the diff of resource changes between two content files + // so we simulate a removal of the toplevel node and then add all nodes contained in the current content file again. + changes.add(buildContentResourceChange(TOPIC_RESOURCE_REMOVED, content, transformPath(monitorable.path))); + addContentResourceChanges(changes, TOPIC_RESOURCE_ADDED, content, transformPath(monitorable.path)); + } + else { + addContentResourceChanges(changes, changeType, contentFile.getContent(), transformPath(monitorable.path)); + } + } + else { + Map props = new HashMap<>(); + props.put("sling:resourceType", monitorable.status instanceof FileStatus ? + FileResource.RESOURCE_TYPE_FILE : FileResource.RESOURCE_TYPE_FOLDER); + ContentElement content = new ContentElementImpl(null, props); + changes.add(buildContentResourceChange(changeType, content, transformPath(monitorable.path))); + } + return changes; + } + private void addContentResourceChanges(final List changes, final String changeType, + final ContentElement content, final String path) { + changes.add(buildContentResourceChange(changeType, content, path)); + if (content != null) { + for (Map.Entry entry : content.getChildren().entrySet()) { + String childPath = path + "/" + entry.getKey(); + addContentResourceChanges(changes, changeType, entry.getValue(), childPath); + } + } + } + private ResourceChange buildContentResourceChange(final String changeType, final ContentElement content, final String path) { + ResourceChange change = new ResourceChange(); + change.path = path; + change.resourceType = content != null ? (String)content.getProperties().get("sling:resourceType") : null; + change.topic = changeType; + return change; + } + + /** + * Create a status object for the monitorable + */ + private static void createStatus(final Monitorable monitorable, ContentFileExtensions contentFileExtensions, ContentFileCache contentFileCache) { + if ( !monitorable.file.exists() ) { + monitorable.status = NonExistingStatus.SINGLETON; + } else if ( monitorable.file.isFile() ) { + if (contentFileExtensions.matchesSuffix(monitorable.file)) { + monitorable.status = new ContentFileStatus(monitorable.file, + new ContentFile(monitorable.file, monitorable.path, null, contentFileCache)); + } + else { + monitorable.status = new FileStatus(monitorable.file); + } + } else { + monitorable.status = new DirStatus(monitorable.file, monitorable.path, contentFileExtensions, contentFileCache); + } + } + + /** The monitorable to hold the resource path, the file and the status. */ + private static final class Monitorable { + public final String path; + public final File file; + public Object status; + public Monitorable(final String path, final File file, String contentFileSuffix) { + this.file = file; + if (contentFileSuffix != null) { + this.path = StringUtils.substringBeforeLast(path, contentFileSuffix); + } + else { + this.path = path; + } + } + } + + /** Status for files. */ + private static class FileStatus { + public long lastModified; + public FileStatus(final File file) { + this.lastModified = file.lastModified(); + } + } + + /** Status for content files */ + private static class ContentFileStatus extends FileStatus { + public final ContentFile contentFile; + public ContentFileStatus(final File file, final ContentFile contentFile) { + super(file); + this.contentFile = contentFile; + } + } + + /** Status for directories. */ + private static final class DirStatus extends FileStatus { + public Monitorable[] children; + + public DirStatus(final File dir, final String path, + final ContentFileExtensions contentFileExtensions, final ContentFileCache contentFileCache) { + super(dir); + final File[] files = dir.listFiles(); + if (files != null) { + this.children = new Monitorable[files.length]; + for (int i = 0; i < files.length; i++) { + this.children[i] = new Monitorable(path + '/' + files[i].getName(), files[i], + contentFileExtensions.getSuffix(files[i])); + FileMonitor.createStatus(this.children[i], contentFileExtensions, contentFileCache); + } + } else { + this.children = new Monitorable[0]; + } + } + } + + /** Status for non existing files. */ + private static final class NonExistingStatus { + public static NonExistingStatus SINGLETON = new NonExistingStatus(); + } + + static class ResourceChange { + public String path; + public String resourceType; + public String topic; + } + +} diff --git a/fsresource-1.x/src/main/java/org/apache/sling/fsprovider/internal/FsMode.java b/fsresource-1.x/src/main/java/org/apache/sling/fsprovider/internal/FsMode.java new file mode 100644 index 0000000..b255bb2 --- /dev/null +++ b/fsresource-1.x/src/main/java/org/apache/sling/fsprovider/internal/FsMode.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.fsprovider.internal; + +/** + * Different modes for File system provider support and file system layouts. + */ +public enum FsMode { + + /** + * Support only files and folders (classic mode). + */ + FILES_FOLDERS, + + /** + * Sling-Initial-Content file system layout, supports file and folders ant content files in JSON and jcr.xml format. + */ + INITIAL_CONTENT, + + /** + * FileVault XML format (expanded content package). + */ + FILEVAULT_XML + +} diff --git a/fsresource-1.x/src/main/java/org/apache/sling/fsprovider/internal/FsResourceMapper.java b/fsresource-1.x/src/main/java/org/apache/sling/fsprovider/internal/FsResourceMapper.java new file mode 100644 index 0000000..4cb2c17 --- /dev/null +++ b/fsresource-1.x/src/main/java/org/apache/sling/fsprovider/internal/FsResourceMapper.java @@ -0,0 +1,47 @@ +/* + * 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.fsprovider.internal; + +import java.util.Iterator; + +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ResourceResolver; + +/** + * Maps files to resources. + */ +public interface FsResourceMapper { + + /** + * Get single resource. + * @param resolver Resource resolver + * @param resourcePath Resource path + * @return Resource or null if not exists + */ + Resource getResource(ResourceResolver resolver, String resourcePath); + + /** + * Get children of resource. + * @param resolver Resource resolver. + * @param parent Parent resource. + * @return Child resources or null if no children exist + */ + Iterator getChildren(ResourceResolver resolver, Resource parent); + +} diff --git a/fsresource-1.x/src/main/java/org/apache/sling/fsprovider/internal/FsResourceProvider.java b/fsresource-1.x/src/main/java/org/apache/sling/fsprovider/internal/FsResourceProvider.java new file mode 100644 index 0000000..14a7dec --- /dev/null +++ b/fsresource-1.x/src/main/java/org/apache/sling/fsprovider/internal/FsResourceProvider.java @@ -0,0 +1,362 @@ +/* + * 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.fsprovider.internal; + +import static org.apache.jackrabbit.vault.util.Constants.DOT_CONTENT_XML; + +import java.io.File; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +import javax.servlet.http.HttpServletRequest; + +import org.apache.commons.collections.IteratorUtils; +import org.apache.commons.collections.Predicate; +import org.apache.commons.lang3.StringUtils; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ResourceProvider; +import org.apache.sling.api.resource.ResourceResolver; +import org.apache.sling.fsprovider.internal.mapper.ContentFileResourceMapper; +import org.apache.sling.fsprovider.internal.mapper.FileResourceMapper; +import org.apache.sling.fsprovider.internal.mapper.FileVaultResourceMapper; +import org.apache.sling.fsprovider.internal.parser.ContentFileCache; +import org.apache.sling.fsprovider.internal.parser.ContentFileTypes; +import org.apache.sling.jcr.contentparser.ContentType; +import org.osgi.framework.BundleContext; +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.ConfigurationPolicy; +import org.osgi.service.component.annotations.Deactivate; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.component.annotations.ReferenceCardinality; +import org.osgi.service.component.annotations.ReferencePolicy; +import org.osgi.service.event.EventAdmin; +import org.osgi.service.metatype.annotations.AttributeDefinition; +import org.osgi.service.metatype.annotations.Designate; +import org.osgi.service.metatype.annotations.ObjectClassDefinition; +import org.osgi.service.metatype.annotations.Option; + +/** + * The FsResourceProvider is a resource provider which maps + * file system files and folders into the virtual resource tree. The provider is + * implemented in terms of a component factory, that is multiple instances of + * this provider may be created by creating respective configuration. + *

+ * Each provider instance is configured with two properties: The location in the + * resource tree where resources are provided (provider.root) + * and the file system path from where files and folders are mapped into the + * resource (provider.file). + */ +@Component(name="org.apache.sling.fsprovider.internal.FsResourceProvider", + service=ResourceProvider.class, + configurationPolicy=ConfigurationPolicy.REQUIRE, + property={ + Constants.SERVICE_DESCRIPTION + "=Sling File System Resource Provider", + Constants.SERVICE_VENDOR + "=The Apache Software Foundation" + }) +@Designate(ocd=FsResourceProvider.Config.class, factory=true) +public final class FsResourceProvider implements ResourceProvider { + + /** + * Resource metadata property set by {@link org.apache.sling.fsprovider.internal.mapper.FileResource} + * if the underlying file reference is a directory. + */ + public static final String RESOURCE_METADATA_FILE_DIRECTORY = ":org.apache.sling.fsprovider.file.directory"; + + @ObjectClassDefinition(name = "Apache Sling File System Resource Provider", + description = "Configure an instance of the file system " + + "resource provider in terms of provider root and file system location") + public @interface Config { + + @AttributeDefinition(name = "File System Root", + description = "File system directory mapped to the virtual " + + "resource tree. This property must not be an empty string. If the path is " + + "relative it is resolved against sling.home or the current working directory. " + + "The path may be a file or folder. If the path does not address an existing " + + "file or folder, an empty folder is created.") + String provider_file(); + + @AttributeDefinition(name = "Provider Root", + description = "Location in the virtual resource tree where the " + + "file system resources are mapped in. This property must not be an empty string. Only one path is supported.") + String[] provider_roots(); + + @AttributeDefinition(name = "File system layout", + description = "File system layout mode for files, folders and content.", + options={ + @Option(value="FILES_FOLDERS", label="FILES_FOLDERS - " + + "Support only files and folders (classic mode)"), + @Option(value="INITIAL_CONTENT", label="INITIAL_CONTENT - " + + "Sling-Initial-Content file system layout, supports file and folders ant content files in JSON and jcr.xml format"), + @Option(value="FILEVAULT_XML", label="FILEVAULT_XML - " + + "FileVault XML format (expanded content package)"), + }) + FsMode provider_fs_mode() default FsMode.FILES_FOLDERS; + + @AttributeDefinition(name = "Init. Content Options", + description = "Import options for Sling-Initial-Content file system layout. Supported options: overwrite, ignoreImportProviders.") + String provider_initial_content_import_options(); + + @AttributeDefinition(name = "FileVault Filter", + description = "Path to META-INF/vault/filter.xml when using FileVault XML file system layout.") + String provider_filevault_filterxml_path(); + + + @AttributeDefinition(name = "Check Interval", + description = "If the interval has a value higher than 100, the provider will " + + "check the file system for changes periodically. This interval defines the period in milliseconds " + + "(the default is 1000). If a change is detected, resource events are sent through the event admin.") + long provider_checkinterval() default 1000; + + @AttributeDefinition(name = "Cache Size", + description = "Max. number of content files cached in memory.") + int provider_cache_size() default 10000; + + // Internal Name hint for web console. + String webconsole_configurationFactory_nameHint() default "{provider.fs.mode}: {" + ResourceProvider.ROOTS + "}"; + } + + // The location in the resource tree where the resources are mapped + private String providerRoot; + + // The "root" file or folder in the file system + private File providerFile; + + // The monitor to detect file changes. + private FileMonitor monitor; + + // maps file system to resources + private FsMode fsMode; + private FsResourceMapper fileMapper; + private FsResourceMapper contentFileMapper; + private FileVaultResourceMapper fileVaultMapper; + + // cache for parsed content files + private ContentFileCache contentFileCache; + + @Reference(cardinality=ReferenceCardinality.OPTIONAL, policy=ReferencePolicy.DYNAMIC) + private volatile EventAdmin eventAdmin; + + @Override + public Resource getResource(ResourceResolver resourceResolver, HttpServletRequest request, String path) { + return getResource(resourceResolver, path); + } + + /** + * Returns a resource wrapping a file system file or folder for the given + * path. If the path is equal to the configured resource tree + * location of this provider, the configured file system file or folder is + * used for the resource. Otherwise the configured resource tree location + * prefix is removed from the path and the remaining relative path is used + * to access the file or folder. If no such file or folder exists, this + * method returns null. + */ + @SuppressWarnings("rawtypes") + @Override + public Resource getResource(ResourceResolver resolver, String path) { + + Resource rsrc = null; + + if (fsMode == FsMode.FILEVAULT_XML) { + // filevault: check if path matches, if not fallback to parent resource provider + if (fileVaultMapper.pathMatches(path)) { + rsrc = fileVaultMapper.getResource(resolver, path); + } + } + else { + // Sling-Initial-Content: mount folder/files an content files + rsrc = fileMapper.getResource(resolver, path); + if (rsrc == null) { + rsrc = contentFileMapper.getResource(resolver, path); + } + } + + return rsrc; + } + + /** + * Returns an iterator of resources. + */ + @SuppressWarnings("unchecked") + public Iterator listChildren(Resource parent) { + ResourceResolver resolver = parent.getResourceResolver(); + + List> allChildren = new ArrayList<>(); + Iterator children; + + if (fsMode == FsMode.FILEVAULT_XML) { + // filevault: always ask provider, it checks itself if children matches the filters + children = fileVaultMapper.getChildren(resolver, parent); + if (children != null) { + allChildren.add(children); + } + } + else { + // Sling-Initial-Content: get all matching folders/files and content files + children = contentFileMapper.getChildren(resolver, parent); + if (children != null) { + allChildren.add(children); + } + children = fileMapper.getChildren(resolver, parent); + if (children != null) { + allChildren.add(children); + } + } + + if (allChildren.isEmpty()) { + return null; + } + else if (allChildren.size() == 1) { + return allChildren.get(0); + } + else { + // merge all children from the different iterators, but filter out potential duplicates with same resource name + return IteratorUtils.filteredIterator(IteratorUtils.chainedIterator(allChildren), new Predicate() { + private Set names = new HashSet<>(); + @Override + public boolean evaluate(Object object) { + Resource resource = (Resource)object; + return names.add(resource.getName()); + } + }); + } + } + + // ---------- SCR Integration + @Activate + protected void activate(BundleContext bundleContext, final Config config) { + fsMode = config.provider_fs_mode(); + String[] providerRoots = config.provider_roots(); + if (providerRoots == null || providerRoots.length != 1 || StringUtils.isBlank(providerRoots[0])) { + throw new IllegalArgumentException("provider.roots property must be set to exactly one entry."); + } + String providerRoot = config.provider_roots()[0]; + + String providerFileName = config.provider_file(); + if (StringUtils.isBlank(providerFileName)) { + throw new IllegalArgumentException("provider.file property must be set"); + } + + this.providerRoot = providerRoot; + this.providerFile = getProviderFile(providerFileName, bundleContext); + + InitialContentImportOptions options = new InitialContentImportOptions(config.provider_initial_content_import_options()); + File filterXmlFile = null; + + List contentFileSuffixes = new ArrayList<>(); + if (fsMode == FsMode.FILEVAULT_XML) { + contentFileSuffixes.add("/" + DOT_CONTENT_XML); + if (StringUtils.isNotBlank(config.provider_filevault_filterxml_path())) { + filterXmlFile = new File(config.provider_filevault_filterxml_path()); + } + } + else if (fsMode == FsMode.INITIAL_CONTENT) { + if (!options.getIgnoreImportProviders().contains(ContentType.JSON.getExtension())) { + contentFileSuffixes.add(ContentFileTypes.JSON_SUFFIX); + } + if (!options.getIgnoreImportProviders().contains(ContentType.JCR_XML.getExtension())) { + contentFileSuffixes.add(ContentFileTypes.JCR_XML_SUFFIX); + } + if (!options.getIgnoreImportProviders().contains(ContentType.XML.getExtension())) { + contentFileSuffixes.add(ContentFileTypes.XML_SUFFIX); + } + } + ContentFileExtensions contentFileExtensions = new ContentFileExtensions(contentFileSuffixes); + + this.contentFileCache = new ContentFileCache(config.provider_cache_size()); + if (fsMode == FsMode.FILEVAULT_XML) { + this.fileVaultMapper = new FileVaultResourceMapper(this.providerFile, filterXmlFile, this.contentFileCache); + } + else { + this.fileMapper = new FileResourceMapper(this.providerRoot, this.providerFile, contentFileExtensions, this.contentFileCache, this.fsMode); + this.contentFileMapper = new ContentFileResourceMapper(this.providerRoot, this.providerFile, + contentFileExtensions, this.contentFileCache); + } + + // start background monitor if check interval is higher than 100 + if (config.provider_checkinterval() > 100) { + this.monitor = new FileMonitor(this, config.provider_checkinterval(), fsMode, + contentFileExtensions, this.contentFileCache); + } + } + + @Deactivate + protected void deactivate() { + if ( this.monitor != null ) { + this.monitor.stop(); + this.monitor = null; + } + this.providerRoot = null; + this.providerFile = null; + this.fileMapper = null; + this.contentFileMapper = null; + this.fileVaultMapper = null; + if (this.contentFileCache != null) { + this.contentFileCache.clear(); + this.contentFileCache = null; + } + this.fsMode = null; + } + + EventAdmin getEventAdmin() { + return this.eventAdmin; + } + + File getRootFile() { + return this.providerFile; + } + + String getProviderRoot() { + return this.providerRoot; + } + + // ---------- internal + + private File getProviderFile(String providerFileName, + BundleContext bundleContext) { + + // the file object from the plain name + File providerFile = new File(providerFileName); + + // resolve relative file name against sling.home or current + // working directory + if (!providerFile.isAbsolute()) { + String home = bundleContext.getProperty("sling.home"); + if (home != null && home.length() > 0) { + providerFile = new File(home, providerFileName); + } + } + + // resolve the path + providerFile = providerFile.getAbsoluteFile(); + + // if the provider file does not exist, create an empty new folder + if (!providerFile.exists() && !providerFile.mkdirs()) { + throw new IllegalArgumentException( + "Cannot create provider file root " + providerFile); + } + + return providerFile; + } + +} diff --git a/fsresource-1.x/src/main/java/org/apache/sling/fsprovider/internal/InitialContentImportOptions.java b/fsresource-1.x/src/main/java/org/apache/sling/fsprovider/internal/InitialContentImportOptions.java new file mode 100644 index 0000000..52de538 --- /dev/null +++ b/fsresource-1.x/src/main/java/org/apache/sling/fsprovider/internal/InitialContentImportOptions.java @@ -0,0 +1,71 @@ +/* + * 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.fsprovider.internal; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import org.apache.commons.lang3.BooleanUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.sling.commons.osgi.ManifestHeader; +import org.apache.sling.commons.osgi.ManifestHeader.Entry; + +class InitialContentImportOptions { + + /** + * The overwrite directive specifying if content should be overwritten or + * just initially added. + */ + private static final String OVERWRITE_DIRECTIVE = "overwrite"; + + /** + * The ignore content readers directive specifying whether the available ContentReaders + * should be used during content loading. + */ + private static final String IGNORE_CONTENT_READERS_DIRECTIVE = "ignoreImportProviders"; + + + private final boolean overwrite; + private final Set ignoreImportProviders; + + public InitialContentImportOptions(String optionsString) { + ManifestHeader header = ManifestHeader.parse("/dummy/path;" + optionsString); + Entry[] entries = header.getEntries(); + if (entries.length > 0) { + overwrite = BooleanUtils.toBoolean(entries[0].getDirectiveValue(OVERWRITE_DIRECTIVE)); + String ignoreImportProvidersString = StringUtils.defaultString(entries[0].getDirectiveValue(IGNORE_CONTENT_READERS_DIRECTIVE)); + ignoreImportProviders = new HashSet<>(Arrays.asList(StringUtils.split(ignoreImportProvidersString, ","))); + } + else { + overwrite = false; + ignoreImportProviders = Collections.emptySet(); + } + } + + public boolean isOverwrite() { + return overwrite; + } + + public Set getIgnoreImportProviders() { + return ignoreImportProviders; + } + +} diff --git a/fsresource-1.x/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFile.java b/fsresource-1.x/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFile.java new file mode 100644 index 0000000..184d184 --- /dev/null +++ b/fsresource-1.x/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFile.java @@ -0,0 +1,150 @@ +/* + * 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.fsprovider.internal.mapper; + +import java.io.File; +import java.util.Iterator; +import java.util.Map; + +import org.apache.sling.api.resource.ValueMap; +import org.apache.sling.fsprovider.internal.mapper.valuemap.ValueMapUtil; +import org.apache.sling.fsprovider.internal.parser.ContentElement; +import org.apache.sling.fsprovider.internal.parser.ContentFileCache; + +/** + * Reference to a file that contains a content fragment (e.g. JSON, JCR XML). + */ +public final class ContentFile { + + private final File file; + private final String path; + private final String subPath; + private final ContentFileCache contentFileCache; + private boolean contentInitialized; + private ContentElement content; + private ValueMap valueMap; + + /** + * @param file File with content fragment + * @param path Root path of the content file + * @param subPath Relative path addressing content fragment inside file + * @param contentFileCache Content file cache + */ + public ContentFile(File file, String path, String subPath, ContentFileCache contentFileCache) { + this.file = file; + this.path = path; + this.subPath = subPath; + this.contentFileCache = contentFileCache; + } + + /** + * @return File with content fragment + */ + public File getFile() { + return file; + } + + /** + * @return Root path of content file + */ + public String getPath() { + return path; + } + + /** + * @return Relative path addressing content fragment inside file + */ + public String getSubPath() { + return subPath; + } + + /** + * Content object referenced by sub path. + * @return Map if resource, property value if property. + */ + public ContentElement getContent() { + if (!contentInitialized) { + ContentElement rootContent = contentFileCache.get(path, file); + if (subPath == null) { + content = rootContent; + } + else if (rootContent != null) { + content = rootContent.getChild(subPath); + } + contentInitialized = true; + } + return content; + } + + /** + * @return true if any content was found. + */ + public boolean hasContent() { + return getContent() != null; + } + + /** + * @return ValueMap for resource. Never null. + */ + public ValueMap getValueMap() { + if (valueMap == null) { + ContentElement currentContent = getContent(); + if (currentContent != null) { + valueMap = ValueMapUtil.toValueMap(currentContent.getProperties()); + } + else { + valueMap = ValueMap.EMPTY; + } + } + return valueMap; + } + + /** + * @return Child maps. + */ + public Iterator> getChildren() { + return getContent().getChildren().entrySet().iterator(); + } + + /** + * Navigate to another sub path position in content file. + * @param newSubPath New sub path related to root path of content file + * @return Content file + */ + public ContentFile navigateToAbsolute(String newSubPath) { + return new ContentFile(file, path, newSubPath, contentFileCache); + } + + /** + * Navigate to another sub path position in content file. + * @param newSubPath New sub path relative to current sub path in content file + * @return Content file + */ + public ContentFile navigateToRelative(String newSubPath) { + String absoluteSubPath; + if (newSubPath == null) { + absoluteSubPath = this.subPath; + } + else { + absoluteSubPath = (this.subPath != null ? this.subPath + "/" : "") + newSubPath; + } + return new ContentFile(file, path, absoluteSubPath, contentFileCache); + } + +} diff --git a/fsresource-1.x/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFileResource.java b/fsresource-1.x/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFileResource.java new file mode 100644 index 0000000..f65422d --- /dev/null +++ b/fsresource-1.x/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFileResource.java @@ -0,0 +1,127 @@ +/* + * 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.fsprovider.internal.mapper; + +import javax.jcr.Node; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import org.apache.sling.api.resource.AbstractResource; +import org.apache.sling.api.resource.ResourceMetadata; +import org.apache.sling.api.resource.ResourceResolver; +import org.apache.sling.api.resource.ResourceUtil; +import org.apache.sling.api.resource.ValueMap; +import org.apache.sling.fsprovider.internal.mapper.jcr.FsNode; + +/** + * Represents a JSON File with resource content. + */ +public final class ContentFileResource extends AbstractResource { + + // the owning resource resolver + private final ResourceResolver resolver; + + // the path of this resource in the resource tree + private final String resourcePath; + + // the file wrapped by this instance + private final ContentFile contentFile; + + // the resource type, assigned on demand + private String resourceType; + private String resourceSuperType; + + // the resource metadata, assigned on demand + private ResourceMetadata metaData; + + /** + * @param resolver The owning resource resolver + * @param resourcePath The resource path in the resource tree + * @param contentFile Content file with sub path + */ + ContentFileResource(ResourceResolver resolver, ContentFile contentFile) { + this.resolver = resolver; + this.contentFile = contentFile; + this.resourcePath = contentFile.getPath() + + (contentFile.getSubPath() != null ? "/" + contentFile.getSubPath() : ""); + } + + public String getPath() { + return resourcePath; + } + + public ResourceMetadata getResourceMetadata() { + if (metaData == null) { + metaData = new ResourceMetadata(); + metaData.setModificationTime(contentFile.getFile().lastModified()); + metaData.setResolutionPath(resourcePath); + } + return metaData; + } + + public ResourceResolver getResourceResolver() { + return resolver; + } + + public String getResourceSuperType() { + if (resourceSuperType == null) { + resourceSuperType = ResourceUtil.getValueMap(this).get("sling:resourceSuperType", String.class); + } + return resourceSuperType; + } + + public String getResourceType() { + if (resourceType == null) { + ValueMap props = ResourceUtil.getValueMap(this); + resourceType = props.get("sling:resourceType", String.class); + if (resourceType == null) { + // fallback to jcr:primaryType when resource type not set + resourceType = props.get("jcr:primaryType", String.class); + } + } + return resourceType; + } + + @Override + @SuppressWarnings("unchecked") + public AdapterType adaptTo(Class type) { + if (type == ContentFile.class) { + return (AdapterType)this.contentFile; + } + else if (type == ValueMap.class) { + return (AdapterType)contentFile.getValueMap(); + } + else if (type == Node.class) { + // support a subset of JCR API for content file resources + return (AdapterType)new FsNode(contentFile, getResourceResolver()); + } + return super.adaptTo(type); + } + + @Override + public String toString() { + return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE) + .append("path", resourcePath) + .append("file", contentFile.getFile().getPath()) + .append("subPath", contentFile.getSubPath()) + .append("resourceType", getResourceType()) + .build(); + } + +} diff --git a/fsresource-1.x/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFileResourceMapper.java b/fsresource-1.x/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFileResourceMapper.java new file mode 100644 index 0000000..20739d6 --- /dev/null +++ b/fsresource-1.x/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFileResourceMapper.java @@ -0,0 +1,158 @@ +/* + * 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.fsprovider.internal.mapper; + +import java.io.File; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.apache.commons.collections.IteratorUtils; +import org.apache.commons.collections.Transformer; +import org.apache.commons.lang3.StringUtils; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ResourceResolver; +import org.apache.sling.api.resource.ResourceUtil; +import org.apache.sling.fsprovider.internal.ContentFileExtensions; +import org.apache.sling.fsprovider.internal.FsResourceMapper; +import org.apache.sling.fsprovider.internal.parser.ContentElement; +import org.apache.sling.fsprovider.internal.parser.ContentFileCache; + +public final class ContentFileResourceMapper implements FsResourceMapper { + + // providerRoot + "/" to be used for prefix matching of paths + private final String providerRootPrefix; + + // The "root" file or folder in the file system + private final File providerFile; + + private final ContentFileExtensions contentFileExtensions; + private final ContentFileCache contentFileCache; + + public ContentFileResourceMapper(String providerRoot, File providerFile, + ContentFileExtensions contentFileExtensions, ContentFileCache contentFileCache) { + this.providerRootPrefix = providerRoot.concat("/"); + this.providerFile = providerFile; + this.contentFileExtensions = contentFileExtensions; + this.contentFileCache = contentFileCache; + } + + @Override + public Resource getResource(final ResourceResolver resolver, final String resourcePath) { + if (contentFileExtensions.isEmpty()) { + return null; + } + ContentFile contentFile = getFile(resourcePath, null); + if (contentFile != null && contentFile.hasContent()) { + return new ContentFileResource(resolver, contentFile); + } + else { + return null; + } + } + + @SuppressWarnings("unchecked") + @Override + public Iterator getChildren(final ResourceResolver resolver, final Resource parent) { + if (contentFileExtensions.isEmpty()) { + return null; + } + final String parentPath = parent.getPath(); + ContentFile parentContentFile = parent.adaptTo(ContentFile.class); + + // not a FsResource, try to create one from the resource + if (parentContentFile == null) { + parentContentFile = getFile(parentPath, null); + if (parentContentFile == null) { + + // check if parent is a file resource that contains a file content resource + File parentFile = parent.adaptTo(File.class); + if (parentFile != null && parentFile.isDirectory()) { + List childResources = new ArrayList<>(); + for (File file : parentFile.listFiles()) { + String filenameSuffix = contentFileExtensions.getSuffix(file); + if (filenameSuffix != null && !isNodeDescriptor(file)) { + String path = parentPath + "/" + StringUtils.substringBeforeLast(file.getName(), filenameSuffix); + ContentFile contentFile = new ContentFile(file, path, null, contentFileCache); + childResources.add(new ContentFileResource(resolver, contentFile)); + } + } + if (!childResources.isEmpty()) { + return childResources.iterator(); + } + } + + // no children here + return null; + } + } + + // get child resources from content fragments in content file + List children = new ArrayList<>(); + if (parentContentFile.hasContent()) { + Iterator> childMaps = parentContentFile.getChildren(); + while (childMaps.hasNext()) { + Map.Entry entry = childMaps.next(); + children.add(parentContentFile.navigateToRelative(entry.getKey())); + } + } + if (children.isEmpty()) { + return null; + } + else { + return IteratorUtils.transformedIterator(children.iterator(), new Transformer() { + @Override + public Object transform(Object input) { + ContentFile contentFile = (ContentFile)input; + return new ContentFileResource(resolver, contentFile); + } + }); + } + } + + private ContentFile getFile(String path, String subPath) { + if (!StringUtils.startsWith(path, providerRootPrefix)) { + return null; + } + String relPath = path.substring(providerRootPrefix.length()); + for (String filenameSuffix : contentFileExtensions.getSuffixes()) { + File file = new File(providerFile, relPath + filenameSuffix); + if (file.exists()) { + return new ContentFile(file, path, subPath, contentFileCache); + } + } + // try to find in parent path which contains content fragment + String parentPath = ResourceUtil.getParent(path); + String nextSubPath = path.substring(parentPath.length() + 1) + + (subPath != null ? "/" + subPath : ""); + return getFile(parentPath, nextSubPath); + } + + private boolean isNodeDescriptor(File file) { + for (String filenameSuffix : contentFileExtensions.getSuffixes()) { + if (StringUtils.endsWith(file.getPath(), filenameSuffix)) { + File fileWithoutSuffix = new File(StringUtils.substringBeforeLast(file.getPath(), filenameSuffix)); + return fileWithoutSuffix.exists(); + } + } + return false; + } + +} diff --git a/fsresource-1.x/src/main/java/org/apache/sling/fsprovider/internal/mapper/FileResource.java b/fsresource-1.x/src/main/java/org/apache/sling/fsprovider/internal/mapper/FileResource.java new file mode 100644 index 0000000..83516c9 --- /dev/null +++ b/fsresource-1.x/src/main/java/org/apache/sling/fsprovider/internal/mapper/FileResource.java @@ -0,0 +1,272 @@ +/* + * 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.fsprovider.internal.mapper; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Calendar; +import java.util.HashMap; +import java.util.Map; + +import javax.jcr.Node; + +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import org.apache.sling.adapter.annotations.Adaptable; +import org.apache.sling.adapter.annotations.Adapter; +import org.apache.sling.api.resource.AbstractResource; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ResourceMetadata; +import org.apache.sling.api.resource.ResourceResolver; +import org.apache.sling.api.resource.ValueMap; +import org.apache.sling.fsprovider.internal.ContentFileExtensions; +import org.apache.sling.fsprovider.internal.FsMode; +import org.apache.sling.fsprovider.internal.mapper.jcr.FsNode; +import org.apache.sling.fsprovider.internal.mapper.valuemap.ValueMapDecorator; +import org.apache.sling.fsprovider.internal.parser.ContentFileCache; +import org.apache.sling.jcr.contentparser.ParserOptions; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The FsResource represents a file system file or folder as + * a Sling Resource. + */ +@Adaptable(adaptableClass=Resource.class, adapters={ + @Adapter({File.class, URL.class}), + @Adapter(condition="If the resource is an FsResource and is a readable file.", value=InputStream.class) +}) +public final class FileResource extends AbstractResource { + + /** + * The resource type for file system files mapped into the resource tree by + * the {@link org.apache.sling.fsprovider.internal.FsResourceProvider} (value is "nt:file"). + */ + public static final String RESOURCE_TYPE_FILE = "nt:file"; + + /** + * The resource type for file system folders mapped into the resource tree + * by the {@link org.apache.sling.fsprovider.internal.FsResourceProvider} (value is "nt:folder"). + */ + public static final String RESOURCE_TYPE_FOLDER = "nt:folder"; + + // the owning resource resolver + private final ResourceResolver resolver; + + // the path of this resource in the resource tree + private final String resourcePath; + + // the file wrapped by this instance + private final File file; + + // the resource metadata, assigned on demand + private ResourceMetadata metaData; + + // the resource type, assigned on demand + private String resourceType; + private String resourceSuperType; + + // valuemap is build on demand + private ValueMap valueMap; + + private final ContentFileExtensions contentFileExtensions; + private final ContentFileCache contentFileCache; + private final FsMode fsMode; + + private static final Logger log = LoggerFactory.getLogger(FileResource.class); + + /** + * Creates an instance of this File system resource. + * + * @param resolver The owning resource resolver + * @param resourcePath The resource path in the resource tree + * @param file The wrapped file + */ + FileResource(ResourceResolver resolver, String resourcePath, File file, FsMode fsMode) { + this(resolver, resourcePath, file, null, null, fsMode); + } + + FileResource(ResourceResolver resolver, String resourcePath, File file, + ContentFileExtensions contentFileExtensions, ContentFileCache contentFileCache, + FsMode fsMode) { + this.resolver = resolver; + this.resourcePath = resourcePath; + this.file = file; + this.contentFileExtensions = contentFileExtensions; + this.contentFileCache = contentFileCache; + this.fsMode = fsMode; + } + + /** + * Returns the path of this resource + */ + public String getPath() { + return resourcePath; + } + + /** + * Returns the resource meta data for this resource containing the file + * length, last modification time and the resource path (same as + * {@link #getPath()}). + */ + public ResourceMetadata getResourceMetadata() { + if (metaData == null) { + metaData = new ResourceMetadata(); + metaData.setContentLength(file.length()); + metaData.setModificationTime(file.lastModified()); + metaData.setResolutionPath(resourcePath); + if (fsMode == FsMode.FILES_FOLDERS && this.file.isDirectory()) { + metaData.put(ResourceMetadata.INTERNAL_CONTINUE_RESOLVING, Boolean.TRUE); + } + } + return metaData; + } + + /** + * Returns the resource resolver which cause this resource object to be + * created. + */ + public ResourceResolver getResourceResolver() { + return resolver; + } + + public String getResourceSuperType() { + if (resourceSuperType == null) { + resourceSuperType = getValueMap().get("sling:resourceSuperType", String.class); + } + return resourceSuperType; + } + + public String getResourceType() { + if (resourceType == null) { + ValueMap props = getValueMap(); + resourceType = props.get("sling:resourceType", String.class); + if (resourceType == null) { + // fallback to jcr:primaryType when resource type not set + resourceType = props.get("jcr:primaryType", String.class); + } + } + return resourceType; + } + + /** + * Returns an adapter for this resource. This implementation supports + * File, InputStream and URL + * plus those supported by the adapter manager. + */ + @Override + @SuppressWarnings("unchecked") + public AdapterType adaptTo(Class type) { + if (type == File.class) { + return (AdapterType) file; + } + else if (type == InputStream.class) { + if (!file.isDirectory() && file.canRead()) { + try { + return (AdapterType) new FileInputStream(file); + } + catch (IOException ioe) { + log.info("adaptTo: Cannot open a stream on the file " + file, ioe); + } + } + else { + log.debug("adaptTo: File {} is not a readable file", file); + } + } + else if (type == URL.class) { + try { + return (AdapterType) file.toURI().toURL(); + } + catch (MalformedURLException mue) { + log.info("adaptTo: Cannot convert the file path " + file + " to an URL", mue); + } + } + else if (type == ValueMap.class) { + return (AdapterType) getValueMap(); + } + else if (type == Node.class) { + ContentFile contentFile = getNodeDescriptorContentFile(); + if (contentFile != null) { + // support a subset of JCR API for content file resources + return (AdapterType)new FsNode(contentFile, getResourceResolver()); + } + } + return super.adaptTo(type); + } + + @Override + public String toString() { + return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE) + .append("path", resourcePath) + .append("file", file.getPath()) + .append("resourceType", getResourceType()) + .build(); + } + + public ValueMap getValueMap() { + if (valueMap == null) { + // this resource simulates nt:file/nt:folder behavior by returning it as resource type + // we should simulate the corresponding JCR properties in a value map as well + if (file.exists() && file.canRead()) { + Map props = new HashMap(); + props.put("jcr:primaryType", file.isFile() ? RESOURCE_TYPE_FILE : RESOURCE_TYPE_FOLDER); + props.put("jcr:createdBy", "system"); + + Calendar lastModifed = Calendar.getInstance(); + lastModifed.setTimeInMillis(file.lastModified()); + props.put("jcr:created", lastModifed); + + // overlay properties with those from node descriptor content file, if it exists + ContentFile contentFile = getNodeDescriptorContentFile(); + if (contentFile != null) { + for (Map.Entry entry : contentFile.getValueMap().entrySet()) { + // skip primary type if it is the default type assigned by contentparser when none is defined + if (StringUtils.equals(entry.getKey(), "jcr:primaryType") + && StringUtils.equals((String)entry.getValue(), ParserOptions.DEFAULT_PRIMARY_TYPE)) { + continue; + } + props.put(entry.getKey(), entry.getValue()); + } + } + + valueMap = new ValueMapDecorator(props); + } + } + return valueMap; + } + + private ContentFile getNodeDescriptorContentFile() { + if (contentFileExtensions == null || contentFileCache == null) { + return null; + } + for (String fileNameSuffix : contentFileExtensions.getSuffixes()) { + File fileWithSuffix = new File(file.getPath() + fileNameSuffix); + if (fileWithSuffix.exists() && fileWithSuffix.canRead()) { + return new ContentFile(fileWithSuffix, resourcePath, null, contentFileCache); + } + } + return null; + } + +} diff --git a/fsresource-1.x/src/main/java/org/apache/sling/fsprovider/internal/mapper/FileResourceMapper.java b/fsresource-1.x/src/main/java/org/apache/sling/fsprovider/internal/mapper/FileResourceMapper.java new file mode 100644 index 0000000..47404f8 --- /dev/null +++ b/fsresource-1.x/src/main/java/org/apache/sling/fsprovider/internal/mapper/FileResourceMapper.java @@ -0,0 +1,152 @@ +/* + * 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.fsprovider.internal.mapper; + +import java.io.File; +import java.util.Iterator; + +import org.apache.commons.collections.IteratorUtils; +import org.apache.commons.collections.Predicate; +import org.apache.commons.collections.Transformer; +import org.apache.commons.lang3.StringUtils; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ResourceResolver; +import org.apache.sling.fsprovider.internal.ContentFileExtensions; +import org.apache.sling.fsprovider.internal.FsMode; +import org.apache.sling.fsprovider.internal.FsResourceMapper; +import org.apache.sling.fsprovider.internal.parser.ContentFileCache; + +public final class FileResourceMapper implements FsResourceMapper { + + // The location in the resource tree where the resources are mapped + private final String providerRoot; + + // providerRoot + "/" to be used for prefix matching of paths + private final String providerRootPrefix; + + // The "root" file or folder in the file system + private final File providerFile; + + private final ContentFileExtensions contentFileExtensions; + private final ContentFileCache contentFileCache; + private final FsMode fsMode; + + public FileResourceMapper(String providerRoot, File providerFile, + ContentFileExtensions contentFileExtensions, ContentFileCache contentFileCache, FsMode fsMode) { + this.providerRoot = providerRoot; + this.providerRootPrefix = providerRoot.concat("/"); + this.providerFile = providerFile; + this.contentFileExtensions = contentFileExtensions; + this.contentFileCache = contentFileCache; + this.fsMode = fsMode; + } + + @Override + public Resource getResource(final ResourceResolver resolver, final String resourcePath) { + File file = getFile(resourcePath); + if (file != null) { + return new FileResource(resolver, resourcePath, file, contentFileExtensions, contentFileCache, fsMode); + } + else { + return null; + } + } + + @SuppressWarnings("unchecked") + @Override + public Iterator getChildren(final ResourceResolver resolver, final Resource parent) { + final String parentPath = parent.getPath(); + File parentFile = parent.adaptTo(File.class); + + // not a FsResource, try to create one from the resource + if (parentFile == null) { + // if the parent path is at or below the provider root, get + // the respective file + parentFile = getFile(parentPath); + + // if the parent path is actually the parent of the provider + // root, return a single element iterator just containing the + // provider file, unless the provider file is a directory and + // a repository item with the same path actually exists + if (parentFile == null) { + + if (providerFile.exists() && !StringUtils.startsWith(parentPath, providerRoot)) { + String parentPathPrefix = parentPath.concat("/"); + if (providerRoot.startsWith(parentPathPrefix)) { + String relPath = providerRoot.substring(parentPathPrefix.length()); + if (relPath.indexOf('/') < 0) { + Resource res = new FileResource(resolver, providerRoot, providerFile, contentFileExtensions, contentFileCache, fsMode); + return IteratorUtils.singletonIterator(res); + } + } + } + + // no children here + return null; + } + } + + // ensure parent is a directory + if (!parentFile.isDirectory()) { + return null; + } + + Iterator children = IteratorUtils.filteredIterator(IteratorUtils.arrayIterator(parentFile.listFiles()), new Predicate() { + @Override + public boolean evaluate(Object object) { + File file = (File)object; + return !contentFileExtensions.matchesSuffix(file); + } + }); + if (!children.hasNext()) { + return null; + } + return IteratorUtils.transformedIterator(children, new Transformer() { + @Override + public Object transform(Object input) { + File file = (File)input; + String path = parentPath + "/" + file.getName(); + return new FileResource(resolver, path, file, contentFileExtensions, contentFileCache, fsMode); + } + }); + } + + /** + * Returns a file corresponding to the given absolute resource tree path. If + * the path equals the configured provider root, the provider root file is + * returned. If the path starts with the configured provider root, a file is + * returned relative to the provider root file whose relative path is the + * remains of the resource tree path without the provider root path. + * Otherwise null is returned. + */ + private File getFile(String path) { + if (path.equals(providerRoot)) { + return providerFile; + } + if (path.startsWith(providerRootPrefix)) { + String relPath = path.substring(providerRootPrefix.length()); + File file = new File(providerFile, relPath); + if (file.exists() && !contentFileExtensions.matchesSuffix(file)) { + return file; + } + } + return null; + } + +} diff --git a/fsresource-1.x/src/main/java/org/apache/sling/fsprovider/internal/mapper/FileVaultResourceMapper.java b/fsresource-1.x/src/main/java/org/apache/sling/fsprovider/internal/mapper/FileVaultResourceMapper.java new file mode 100644 index 0000000..c772e40 --- /dev/null +++ b/fsresource-1.x/src/main/java/org/apache/sling/fsprovider/internal/mapper/FileVaultResourceMapper.java @@ -0,0 +1,201 @@ +/* + * 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.fsprovider.internal.mapper; + +import static org.apache.jackrabbit.vault.util.Constants.DOT_CONTENT_XML; + +import java.io.File; +import java.io.IOException; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.collections.IteratorUtils; +import org.apache.commons.collections.Transformer; +import org.apache.commons.lang3.StringUtils; +import org.apache.jackrabbit.vault.fs.api.WorkspaceFilter; +import org.apache.jackrabbit.vault.fs.config.ConfigurationException; +import org.apache.jackrabbit.vault.fs.config.DefaultWorkspaceFilter; +import org.apache.jackrabbit.vault.util.PlatformNameFormat; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ResourceResolver; +import org.apache.sling.api.resource.ResourceUtil; +import org.apache.sling.fsprovider.internal.FsMode; +import org.apache.sling.fsprovider.internal.FsResourceMapper; +import org.apache.sling.fsprovider.internal.parser.ContentElement; +import org.apache.sling.fsprovider.internal.parser.ContentFileCache; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public final class FileVaultResourceMapper implements FsResourceMapper { + + private static final String DOT_CONTENT_XML_SUFFIX = "/" + DOT_CONTENT_XML; + private static final String DOT_DIR_SUFFIX = "/.dir"; + + private final File providerFile; + private final File filterXmlFile; + private final ContentFileCache contentFileCache; + private final WorkspaceFilter workspaceFilter; + + private static final Logger log = LoggerFactory.getLogger(FileVaultResourceMapper.class); + + public FileVaultResourceMapper(File providerFile, File filterXmlFile, ContentFileCache contentFileCache) { + this.providerFile = providerFile; + this.filterXmlFile = filterXmlFile; + this.contentFileCache = contentFileCache; + this.workspaceFilter = getWorkspaceFilter(); + } + + @Override + public Resource getResource(final ResourceResolver resolver, final String resourcePath) { + + // direct file + File file = getFile(resourcePath); + if (file != null && file.isFile()) { + return new FileResource(resolver, resourcePath, file, FsMode.FILEVAULT_XML); + } + + // content file + ContentFile contentFile = getContentFile(resourcePath, null); + if (contentFile != null) { + return new ContentFileResource(resolver, contentFile); + } + + // fallback to directory resource if folder was found but nothing else + if (file != null && file.isDirectory()) { + return new FileResource(resolver, resourcePath, file, FsMode.FILEVAULT_XML); + } + + return null; + } + + @SuppressWarnings("unchecked") + @Override + public Iterator getChildren(final ResourceResolver resolver, final Resource parent) { + String parentPath = parent.getPath(); + + Set childPaths = new LinkedHashSet<>(); + + // get children from content resource of parent + ContentFile parentContentFile = getContentFile(parentPath, null); + if (parentContentFile != null) { + Iterator> childMaps = parentContentFile.getChildren(); + while (childMaps.hasNext()) { + Map.Entry entry = childMaps.next(); + String childPath = parentPath + "/" + entry.getKey(); + if (pathMatches(childPath)) { + childPaths.add(childPath); + } + } + } + + // additional check for children in file system + File parentFile = getFile(parentPath); + if (parentFile != null && parentFile.isDirectory()) { + for (File childFile : parentFile.listFiles()) { + String childPath = parentPath + "/" + PlatformNameFormat.getRepositoryName(childFile.getName()); + if (pathMatches(childPath) && !childPaths.contains(childPath)) { + childPaths.add(childPath); + } + } + } + + if (childPaths.isEmpty()) { + return null; + } + else { + return IteratorUtils.transformedIterator(childPaths.iterator(), new Transformer() { + @Override + public Object transform(Object input) { + String path = (String)input; + return getResource(resolver, path); + } + }); + } + } + + /** + * @return Workspace filter or null if none found. + */ + private WorkspaceFilter getWorkspaceFilter() { + if (filterXmlFile != null && filterXmlFile.exists()) { + try { + DefaultWorkspaceFilter workspaceFilter = new DefaultWorkspaceFilter(); + workspaceFilter.load(filterXmlFile); + return workspaceFilter; + } catch (IOException | ConfigurationException ex) { + log.error("Unable to parse workspace filter: " + filterXmlFile.getPath(), ex); + } + } + else { + log.debug("Workspace filter not found: " + filterXmlFile.getPath()); + } + return null; + } + + /** + * Checks if the given path matches the workspace filter. + * @param path Path + * @return true if path matches + */ + public boolean pathMatches(String path) { + // ignore .dir folder + if (StringUtils.endsWith(path, DOT_DIR_SUFFIX) || StringUtils.endsWith(path, DOT_CONTENT_XML_SUFFIX)) { + return false; + } + if (workspaceFilter == null) { + return true; + } + else { + return workspaceFilter.contains(path); + } + } + + private File getFile(String path) { + if (StringUtils.endsWith(path, DOT_CONTENT_XML_SUFFIX)) { + return null; + } + File file = new File(providerFile, "." + PlatformNameFormat.getPlatformPath(path)); + if (file.exists()) { + return file; + } + return null; + } + + private ContentFile getContentFile(String path, String subPath) { + File file = new File(providerFile, "." + PlatformNameFormat.getPlatformPath(path) + DOT_CONTENT_XML_SUFFIX); + if (file.exists()) { + ContentFile contentFile = new ContentFile(file, path, subPath, contentFileCache); + if (contentFile.hasContent()) { + return contentFile; + } + } + + // try to find in parent path which contains content fragment + String parentPath = ResourceUtil.getParent(path); + if (parentPath == null) { + return null; + } + String nextSubPath = path.substring(parentPath.length() + 1) + + (subPath != null ? "/" + subPath : ""); + return getContentFile(parentPath, nextSubPath); + } + +} diff --git a/fsresource-1.x/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsItem.java b/fsresource-1.x/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsItem.java new file mode 100644 index 0000000..d5689a9 --- /dev/null +++ b/fsresource-1.x/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsItem.java @@ -0,0 +1,161 @@ +/* + * 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.fsprovider.internal.mapper.jcr; + +import javax.jcr.AccessDeniedException; +import javax.jcr.InvalidItemStateException; +import javax.jcr.Item; +import javax.jcr.ItemExistsException; +import javax.jcr.ItemNotFoundException; +import javax.jcr.ItemVisitor; +import javax.jcr.Node; +import javax.jcr.ReferentialIntegrityException; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.jcr.lock.LockException; +import javax.jcr.nodetype.ConstraintViolationException; +import javax.jcr.nodetype.NoSuchNodeTypeException; +import javax.jcr.version.VersionException; + +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ResourceResolver; +import org.apache.sling.api.resource.ValueMap; +import org.apache.sling.fsprovider.internal.mapper.ContentFile; + +/** + * Simplified implementation of read-only content access via the JCR API. + */ +abstract class FsItem implements Item { + + protected final ContentFile contentFile; + protected final ResourceResolver resolver; + protected final ValueMap props; + + public FsItem(ContentFile contentFile, ResourceResolver resolver) { + this.contentFile = contentFile; + this.resolver = resolver; + this.props = contentFile.getValueMap(); + } + + @Override + public String getPath() throws RepositoryException { + if (contentFile.getSubPath() == null) { + return contentFile.getPath(); + } + else { + return contentFile.getPath() + "/" + contentFile.getSubPath(); + } + } + + @Override + public Item getAncestor(int depth) throws ItemNotFoundException, AccessDeniedException, RepositoryException { + String path; + if (depth == 0) { + path = "/"; + } + else { + String[] pathParts = StringUtils.splitPreserveAllTokens(getPath(), "/"); + path = StringUtils.join(pathParts, "/", 0, depth + 1); + } + Resource resource = resolver.getResource(path); + if (resource != null) { + Node refNode = resource.adaptTo(Node.class); + if (refNode != null) { + return refNode; + } + } + throw new ItemNotFoundException(); + } + + @Override + public int getDepth() throws RepositoryException { + if (StringUtils.equals("/", getPath())) { + return 0; + } else { + return StringUtils.countMatches(getPath(), "/"); + } + } + + @Override + public Session getSession() throws RepositoryException { + return resolver.adaptTo(Session.class); + } + + @Override + public boolean isNode() { + return (this instanceof Node); + } + + @Override + public boolean isNew() { + return false; + } + + @Override + public boolean isModified() { + return false; + } + + @Override + public boolean isSame(Item otherItem) throws RepositoryException { + return StringUtils.equals(getPath(), otherItem.getPath()); + } + + @Override + public void accept(ItemVisitor visitor) throws RepositoryException { + // do nothing + } + + @Override + public String toString() { + try { + return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE) + .append("path", getPath()) + .build(); + } + catch (RepositoryException ex) { + throw new RuntimeException(ex); + } + } + + + // --- unsupported methods --- + + @Override + public void save() throws AccessDeniedException, ItemExistsException, ConstraintViolationException, + InvalidItemStateException, ReferentialIntegrityException, VersionException, LockException, + NoSuchNodeTypeException, RepositoryException { + throw new UnsupportedOperationException(); + } + + @Override + public void refresh(boolean keepChanges) throws InvalidItemStateException, RepositoryException { + throw new UnsupportedOperationException(); + } + + @Override + public void remove() throws VersionException, LockException, ConstraintViolationException, AccessDeniedException, + RepositoryException { + throw new UnsupportedOperationException(); + } + +} diff --git a/fsresource-1.x/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsNode.java b/fsresource-1.x/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsNode.java new file mode 100644 index 0000000..23abe66 --- /dev/null +++ b/fsresource-1.x/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsNode.java @@ -0,0 +1,549 @@ +/* + * 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.fsprovider.internal.mapper.jcr; + +import java.io.InputStream; +import java.math.BigDecimal; +import java.util.Calendar; + +import javax.jcr.AccessDeniedException; +import javax.jcr.Binary; +import javax.jcr.InvalidItemStateException; +import javax.jcr.InvalidLifecycleTransitionException; +import javax.jcr.Item; +import javax.jcr.ItemExistsException; +import javax.jcr.ItemNotFoundException; +import javax.jcr.MergeException; +import javax.jcr.NoSuchWorkspaceException; +import javax.jcr.Node; +import javax.jcr.NodeIterator; +import javax.jcr.PathNotFoundException; +import javax.jcr.Property; +import javax.jcr.PropertyIterator; +import javax.jcr.RepositoryException; +import javax.jcr.UnsupportedRepositoryOperationException; +import javax.jcr.Value; +import javax.jcr.ValueFormatException; +import javax.jcr.lock.Lock; +import javax.jcr.lock.LockException; +import javax.jcr.nodetype.ConstraintViolationException; +import javax.jcr.nodetype.NoSuchNodeTypeException; +import javax.jcr.nodetype.NodeDefinition; +import javax.jcr.nodetype.NodeType; +import javax.jcr.version.ActivityViolationException; +import javax.jcr.version.Version; +import javax.jcr.version.VersionException; +import javax.jcr.version.VersionHistory; + +import org.apache.commons.lang3.StringUtils; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ResourceResolver; +import org.apache.sling.api.resource.ResourceUtil; +import org.apache.sling.fsprovider.internal.mapper.ContentFile; + +/** + * Simplified implementation of read-only content access via the JCR API. + */ +public final class FsNode extends FsItem implements Node { + + public FsNode(ContentFile contentFile, ResourceResolver resolver) { + super(contentFile, resolver); + } + + private String getPrimaryTypeName() { + return props.get("jcr:primaryType", String.class); + } + + private String[] getMixinTypeNames() { + return props.get("jcr:mixinTypes", new String[0]); + } + + @Override + public String getName() throws RepositoryException { + if (contentFile.getSubPath() == null) { + return ResourceUtil.getName(contentFile.getPath()); + } + else { + return ResourceUtil.getName(contentFile.getSubPath()); + } + } + + @Override + public Node getParent() throws ItemNotFoundException, AccessDeniedException, RepositoryException { + return getNode(ResourceUtil.getParent(getPath())); + } + + @Override + public Node getNode(String relPath) throws PathNotFoundException, RepositoryException { + if (relPath == null) { + throw new PathNotFoundException(); + } + + // get absolute node path + String path = relPath; + if (!StringUtils.startsWith(path, "/")) { + path = ResourceUtil.normalize(getPath() + "/" + relPath); + } + + if (StringUtils.equals(path, contentFile.getPath()) || StringUtils.startsWith(path, contentFile.getPath() + "/")) { + // node is contained in content file + String subPath; + if (StringUtils.equals(path, contentFile.getPath())) { + subPath = null; + } + else { + subPath = path.substring(contentFile.getPath().length() + 1); + } + ContentFile referencedFile = contentFile.navigateToAbsolute(subPath); + if (referencedFile.hasContent()) { + return new FsNode(referencedFile, resolver); + } + } + + // check if node is outside content file + Node refNode = null; + Resource resource = resolver.getResource(path); + if (resource != null) { + refNode = resource.adaptTo(Node.class); + if (refNode != null) { + return refNode; + } + } + + throw new PathNotFoundException(relPath); + } + + @Override + public NodeIterator getNodes() throws RepositoryException { + return new FsNodeIterator(contentFile, resolver); + } + + @Override + public Property getProperty(String relPath) throws PathNotFoundException, RepositoryException { + if (props.containsKey(relPath)) { + return new FsProperty(contentFile, resolver, relPath, this); + } + throw new PathNotFoundException(relPath); + } + + @Override + public PropertyIterator getProperties() throws RepositoryException { + return new FsPropertyIterator(props.keySet().iterator(), contentFile, resolver, this); + } + + @Override + public String getUUID() throws UnsupportedRepositoryOperationException, RepositoryException { + String uuid = props.get("jcr:uuid", String.class); + if (uuid != null) { + return uuid; + } + else { + throw new UnsupportedRepositoryOperationException(); + } + } + + @Override + public boolean hasNode(String relPath) throws RepositoryException { + try { + getNode(relPath); + return true; + } + catch (RepositoryException ex) { + return false; + } + } + + @Override + public boolean hasProperty(String relPath) throws RepositoryException { + return props.containsKey(relPath); + } + + @Override + public boolean hasNodes() throws RepositoryException { + return getNodes().hasNext(); + } + + @Override + public boolean hasProperties() throws RepositoryException { + return !props.isEmpty(); + } + + @Override + public boolean isNodeType(String nodeTypeName) throws RepositoryException { + return StringUtils.equals(nodeTypeName, getPrimaryTypeName()); + } + + @Override + public boolean canAddMixin(String mixinName) throws NoSuchNodeTypeException, RepositoryException { + return false; + } + + @Override + public boolean isCheckedOut() throws RepositoryException { + return false; + } + + @Override + public boolean holdsLock() throws RepositoryException { + return false; + } + + @Override + public boolean isLocked() throws RepositoryException { + return false; + } + + @Override + public NodeType getPrimaryNodeType() throws RepositoryException { + return new FsNodeType(getPrimaryTypeName(), false); + } + + @Override + public NodeType[] getMixinNodeTypes() throws RepositoryException { + String[] mixinTypeNames = getMixinTypeNames(); + NodeType[] mixinTypes = new NodeType[mixinTypeNames.length]; + for (int i=0; i> children; + + public FsNodeIterator(ContentFile contentFile, ResourceResolver resolver) { + this.contentFile = contentFile; + this.resolver = resolver; + ContentElement content = contentFile.getContent(); + this.children = content.getChildren().entrySet().iterator(); + } + + public boolean hasNext() { + return children.hasNext(); + } + + public Object next() { + return nextNode(); + } + + @Override + public Node nextNode() { + Map.Entry nextEntry = children.next(); + return new FsNode(contentFile.navigateToRelative(nextEntry.getKey()), resolver); + } + + + // --- unsupported methods --- + + public void remove() { + throw new UnsupportedOperationException(); + } + + @Override + public void skip(long skipNum) { + throw new UnsupportedOperationException(); + } + + @Override + public long getSize() { + throw new UnsupportedOperationException(); + } + + @Override + public long getPosition() { + throw new UnsupportedOperationException(); + } + +} diff --git a/fsresource-1.x/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsNodeType.java b/fsresource-1.x/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsNodeType.java new file mode 100644 index 0000000..8d60812 --- /dev/null +++ b/fsresource-1.x/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsNodeType.java @@ -0,0 +1,157 @@ +/* + * 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.fsprovider.internal.mapper.jcr; + +import javax.jcr.Value; +import javax.jcr.nodetype.NodeDefinition; +import javax.jcr.nodetype.NodeType; +import javax.jcr.nodetype.NodeTypeIterator; +import javax.jcr.nodetype.PropertyDefinition; + +import org.apache.commons.lang3.StringUtils; + +class FsNodeType implements NodeType { + + private final String name; + private final boolean mixin; + + public FsNodeType(String name, boolean mixin) { + this.name = name; + this.mixin = mixin; + } + + @Override + public String getName() { + return name; + } + + @Override + public String[] getDeclaredSupertypeNames() { + return new String[0]; + } + + @Override + public boolean isAbstract() { + return false; + } + + @Override + public boolean isMixin() { + return mixin; + } + + @Override + public boolean hasOrderableChildNodes() { + return false; + } + + @Override + public boolean isQueryable() { + return false; + } + + @Override + public String getPrimaryItemName() { + return null; + } + + @Override + public NodeType[] getSupertypes() { + return new NodeType[0]; + } + + @Override + public NodeType[] getDeclaredSupertypes() { + return new NodeType[0]; + } + + @Override + public boolean isNodeType(String nodeTypeName) { + return StringUtils.equals(name, nodeTypeName); + } + + + // --- unsupported methods --- + + @Override + public PropertyDefinition[] getDeclaredPropertyDefinitions() { + throw new UnsupportedOperationException(); + } + + @Override + public NodeDefinition[] getDeclaredChildNodeDefinitions() { + throw new UnsupportedOperationException(); + } + + @Override + public NodeTypeIterator getSubtypes() { + throw new UnsupportedOperationException(); + } + + @Override + public NodeTypeIterator getDeclaredSubtypes() { + throw new UnsupportedOperationException(); + } + + @Override + public PropertyDefinition[] getPropertyDefinitions() { + throw new UnsupportedOperationException(); + } + + @Override + public NodeDefinition[] getChildNodeDefinitions() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean canSetProperty(String propertyName, Value value) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean canSetProperty(String propertyName, Value[] values) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean canAddChildNode(String childNodeName) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean canAddChildNode(String childNodeName, String nodeTypeName) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean canRemoveItem(String itemName) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean canRemoveNode(String nodeName) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean canRemoveProperty(String propertyName) { + throw new UnsupportedOperationException(); + } + +} diff --git a/fsresource-1.x/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsProperty.java b/fsresource-1.x/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsProperty.java new file mode 100644 index 0000000..bf24aa7 --- /dev/null +++ b/fsresource-1.x/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsProperty.java @@ -0,0 +1,242 @@ +/* + * 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.fsprovider.internal.mapper.jcr; + +import java.io.InputStream; +import java.lang.reflect.Array; +import java.math.BigDecimal; +import java.util.Calendar; + +import javax.jcr.AccessDeniedException; +import javax.jcr.Binary; +import javax.jcr.ItemNotFoundException; +import javax.jcr.Node; +import javax.jcr.Property; +import javax.jcr.RepositoryException; +import javax.jcr.Value; +import javax.jcr.ValueFormatException; +import javax.jcr.lock.LockException; +import javax.jcr.nodetype.ConstraintViolationException; +import javax.jcr.nodetype.PropertyDefinition; +import javax.jcr.version.VersionException; + +import org.apache.sling.api.resource.ResourceResolver; +import org.apache.sling.fsprovider.internal.mapper.ContentFile; + +/** + * Simplified implementation of read-only content access via the JCR API. + */ +class FsProperty extends FsItem implements Property { + + private final String propertyName; + private final Node node; + + public FsProperty(ContentFile contentFile, ResourceResolver resolver, String propertyName, Node node) { + super(contentFile, resolver); + this.propertyName = propertyName; + this.node = node; + } + + @Override + public String getName() throws RepositoryException { + return propertyName; + } + + @Override + public Node getParent() throws ItemNotFoundException, AccessDeniedException, RepositoryException { + return getNode(); + } + + @Override + public Node getNode() throws ItemNotFoundException, ValueFormatException, RepositoryException { + return node; + } + + @Override + public String getPath() throws RepositoryException { + return super.getPath() + "/" + propertyName; + } + + @Override + public Value getValue() throws ValueFormatException, RepositoryException { + return new FsValue(props, propertyName); + } + + @Override + public String getString() throws ValueFormatException, RepositoryException { + return getValue().getString(); + } + + @SuppressWarnings("deprecation") + @Override + public InputStream getStream() throws ValueFormatException, RepositoryException { + return getValue().getStream(); + } + + @Override + public Binary getBinary() throws ValueFormatException, RepositoryException { + return getValue().getBinary(); + } + + @Override + public long getLong() throws ValueFormatException, RepositoryException { + return getValue().getLong(); + } + + @Override + public double getDouble() throws ValueFormatException, RepositoryException { + return getValue().getDouble(); + } + + @Override + public BigDecimal getDecimal() throws ValueFormatException, RepositoryException { + return getValue().getDecimal(); + } + + @Override + public Calendar getDate() throws ValueFormatException, RepositoryException { + return getValue().getDate(); + } + + @Override + public boolean getBoolean() throws ValueFormatException, RepositoryException { + return getValue().getBoolean(); + } + + @Override + public Value[] getValues() throws ValueFormatException, RepositoryException { + if (!isMultiple()) { + throw new ValueFormatException(); + } + Object value = props.get(propertyName); + int size = Array.getLength(value); + Value[] result = new Value[size]; + for (int i=0; i propertyNames; + private final ContentFile contentFile; + private final ResourceResolver resolver; + private final Node node; + + public FsPropertyIterator(Iterator propertyNames, ContentFile contentFile, ResourceResolver resolver, Node node) { + this.propertyNames = propertyNames; + this.contentFile = contentFile; + this.resolver = resolver; + this.node = node; + } + + public boolean hasNext() { + return propertyNames.hasNext(); + } + + public Object next() { + return nextProperty(); + } + + @Override + public Property nextProperty() { + return new FsProperty(contentFile, resolver, propertyNames.next(), node); + } + + + // --- unsupported methods --- + + public void remove() { + throw new UnsupportedOperationException(); + } + + @Override + public void skip(long skipNum) { + throw new UnsupportedOperationException(); + } + + @Override + public long getSize() { + throw new UnsupportedOperationException(); + } + + @Override + public long getPosition() { + throw new UnsupportedOperationException(); + } + +} diff --git a/fsresource-1.x/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsValue.java b/fsresource-1.x/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsValue.java new file mode 100644 index 0000000..4ca2873 --- /dev/null +++ b/fsresource-1.x/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsValue.java @@ -0,0 +1,162 @@ +/* + * 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.fsprovider.internal.mapper.jcr; + +import java.io.InputStream; +import java.lang.reflect.Array; +import java.math.BigDecimal; +import java.util.Calendar; + +import javax.jcr.Binary; +import javax.jcr.PropertyType; +import javax.jcr.RepositoryException; +import javax.jcr.Value; +import javax.jcr.ValueFormatException; + +import org.apache.sling.api.resource.ValueMap; + +/** + * Simplified implementation of read-only content access via the JCR API. + */ +class FsValue implements Value { + + private final ValueMap props; + private final String propertyName; + private final int arrayIndex; + + public FsValue(ValueMap props, String propertyName) { + this.props = props; + this.propertyName = propertyName; + this.arrayIndex = -1; + } + + public FsValue(ValueMap props, String propertyName, int arrayIndex) { + this.props = props; + this.propertyName = propertyName; + this.arrayIndex = arrayIndex; + } + + @Override + public String getString() throws ValueFormatException, IllegalStateException, RepositoryException { + if (arrayIndex >= 0) { + return props.get(propertyName, String[].class)[arrayIndex]; + } + else { + return props.get(propertyName, String.class); + } + } + + @Override + public long getLong() throws ValueFormatException, RepositoryException { + if (arrayIndex >= 0) { + return props.get(propertyName, Long[].class)[arrayIndex]; + } + else { + return props.get(propertyName, 0L); + } + } + + @Override + public double getDouble() throws ValueFormatException, RepositoryException { + if (arrayIndex >= 0) { + return props.get(propertyName, Double[].class)[arrayIndex]; + } + else { + return props.get(propertyName, 0d); + } + } + + @Override + public BigDecimal getDecimal() throws ValueFormatException, RepositoryException { + if (arrayIndex >= 0) { + return props.get(propertyName, BigDecimal[].class)[arrayIndex]; + } + else { + return props.get(propertyName, BigDecimal.ZERO); + } + } + + @Override + public Calendar getDate() throws ValueFormatException, RepositoryException { + if (arrayIndex >= 0) { + return props.get(propertyName, Calendar[].class)[arrayIndex]; + } + else { + return props.get(propertyName, Calendar.class); + } + } + + @Override + public boolean getBoolean() throws ValueFormatException, RepositoryException { + if (arrayIndex >= 0) { + return props.get(propertyName, Boolean[].class)[arrayIndex]; + } + else { + return props.get(propertyName, false); + } + } + + @Override + public int getType() { + Object value = props.get(propertyName); + if (value == null) { + return PropertyType.UNDEFINED; + } + Class type = value.getClass(); + if (type.isArray() && Array.getLength(value) > 0) { + Object firstItem = Array.get(value, 0); + if (firstItem != null) { + type = firstItem.getClass(); + } + } + if (type == String.class) { + return PropertyType.STRING; + } + if (type == Boolean.class || type == boolean.class) { + return PropertyType.BOOLEAN; + } + if (type == BigDecimal.class) { + return PropertyType.DECIMAL; + } + if (type == Double.class || type == double.class || type == Float.class || type == float.class) { + return PropertyType.DOUBLE; + } + if (Number.class.isAssignableFrom(type)) { + return PropertyType.LONG; + } + if (type == Calendar.class) { + return PropertyType.DATE; + } + return PropertyType.UNDEFINED; + } + + + // --- unsupported methods --- + + @Override + public InputStream getStream() throws RepositoryException { + throw new UnsupportedOperationException(); + } + + @Override + public Binary getBinary() throws RepositoryException { + throw new UnsupportedOperationException(); + } + +} diff --git a/fsresource-1.x/src/main/java/org/apache/sling/fsprovider/internal/mapper/valuemap/DateUtils.java b/fsresource-1.x/src/main/java/org/apache/sling/fsprovider/internal/mapper/valuemap/DateUtils.java new file mode 100644 index 0000000..cf985f5 --- /dev/null +++ b/fsresource-1.x/src/main/java/org/apache/sling/fsprovider/internal/mapper/valuemap/DateUtils.java @@ -0,0 +1,99 @@ +/* + * 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.fsprovider.internal.mapper.valuemap; + +import java.util.Calendar; +import java.util.Date; + +import org.apache.jackrabbit.util.ISO8601; + +/** + * This is copied from org.apache.sling.api.wrappers.impl.DateUtils + * to avoid dependency to latest Sling API. + * This can be removed when Sling API 2.17.0 or higher is referenced. + */ +final class DateUtils { + + private DateUtils() { + // static methods only + } + + /** + * @param date Date value + * @return Calendar value or null + */ + public static Calendar toCalendar(Date input) { + if (input == null) { + return null; + } + Calendar result = Calendar.getInstance(); + result.setTime(input); + return result; + } + + /** + * @param calendar Calendar value + * @return Date value or null + */ + public static Date toDate(Calendar input) { + if (input == null) { + return null; + } + return input.getTime(); + } + + /** + * @param input Date value + * @return ISO8601 string representation or null + */ + public static String dateToString(Date input) { + return calendarToString(toCalendar(input)); + } + + /** + * @param input Calendar value + * @return ISO8601 string representation or null + */ + public static String calendarToString(Calendar input) { + if (input == null) { + return null; + } + return ISO8601.format(input); + } + + /** + * @param input ISO8601 string representation + * @return Date value or null + */ + public static Date dateFromString(String input) { + return toDate(calendarFromString(input)); + } + + /** + * @param input ISO8601 string representation + * @return Calendar value or null + */ + public static Calendar calendarFromString(String input) { + if (input == null) { + return null; + } + return ISO8601.parse(input); + } + +} diff --git a/fsresource-1.x/src/main/java/org/apache/sling/fsprovider/internal/mapper/valuemap/ObjectConverter.java b/fsresource-1.x/src/main/java/org/apache/sling/fsprovider/internal/mapper/valuemap/ObjectConverter.java new file mode 100644 index 0000000..5d91e06 --- /dev/null +++ b/fsresource-1.x/src/main/java/org/apache/sling/fsprovider/internal/mapper/valuemap/ObjectConverter.java @@ -0,0 +1,185 @@ +/* + * 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.fsprovider.internal.mapper.valuemap; + +import java.lang.reflect.Array; +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.List; + +/** + * This is copied from org.apache.sling.api.wrappers.impl.ObjectConverter + * to avoid dependency to latest Sling API. + * This can be removed when Sling API 2.17.0 or higher is referenced. + */ +final class ObjectConverter { + + private ObjectConverter() { + // static methods only + } + + /** + * Converts the object to the given type. + * @param obj object + * @param type type + * @return the converted object + */ + @SuppressWarnings("unchecked") + public static T convert(Object obj, Class type) { + if (obj == null) { + return null; + } + + // check if direct assignment is possible + if (type.isAssignableFrom(obj.getClass())) { + return (T)obj; + } + + // convert array elements individually + if (type.isArray()) { + return (T)convertToArray(obj, type.getComponentType()); + } + + // convert Calendar in Date and vice versa + if (Calendar.class.isAssignableFrom(type) && obj instanceof Date) { + return (T)DateUtils.toCalendar((Date)obj); + } + if (type == Date.class && obj instanceof Calendar) { + return (T)DateUtils.toDate((Calendar)obj); + } + + // no direct conversion - format to string and try to parse to target type + String result = getSingleValue(obj); + if (result == null) { + return null; + } + if (type == String.class) { + return (T)result.toString(); + } + if (type == Boolean.class) { + // do not rely on Boolean.parseBoolean to avoid converting nonsense to "false" without noticing + if ("true".equalsIgnoreCase(result)) { + return (T)Boolean.TRUE; + } + else if ("false".equalsIgnoreCase(result)) { + return (T)Boolean.FALSE; + } + else { + return null; + } + } + try { + if (type == Byte.class) { + return (T)(Byte)Byte.parseByte(result); + } + if (type == Short.class) { + return (T)(Short)Short.parseShort(result); + } + if (type == Integer.class) { + return (T)(Integer)Integer.parseInt(result); + } + if (type == Long.class) { + return (T)(Long)Long.parseLong(result); + } + if (type == Float.class) { + return (T)(Float)Float.parseFloat(result); + } + if (type == Double.class) { + return (T)(Double)Double.parseDouble(result); + } + if (type == BigDecimal.class) { + return (T)new BigDecimal(result); + } + } + catch (NumberFormatException e) { + return null; + } + if (Calendar.class.isAssignableFrom(type)) { + return (T)DateUtils.calendarFromString(result); + } + if (type == Date.class) { + return (T)DateUtils.dateFromString(result); + } + return null; + } + + /** + * Gets a single value of String from the object. If the object is an array it returns it's first element. + * @param obj object or object array. + * @return result of toString() on object or first element of an object array. If @param obj is null + * or it's an array with first element that is null, then null is returned. + */ + private static String getSingleValue(Object obj) { + final String result; + if (obj == null) { + result = null; + } + else if (obj.getClass().isArray()) { + if (Array.getLength(obj) == 0) { + result = null; + } + else { + result = getSingleValue(Array.get(obj, 0)); + } + } + else if (obj instanceof Calendar) { + result = DateUtils.calendarToString((Calendar)obj); + } + else if (obj instanceof Date) { + result = DateUtils.dateToString((Date)obj); + } + else { + result = obj.toString(); + } + return result; + } + + /** + * Converts the object to an array of the given type + * @param obj the object or object array + * @param type the component type of the array + * @return and array of type T + */ + @SuppressWarnings("unchecked") + private static T[] convertToArray(Object obj, Class type) { + if (obj.getClass().isArray()) { + List resultList = new ArrayList(); + for (int i = 0; i < Array.getLength(obj); i++) { + T singleValueResult = convert(Array.get(obj, i), type); + if (singleValueResult != null) { + resultList.add(singleValueResult); + } + } + return resultList.toArray((T[])Array.newInstance(type, resultList.size())); + } + else { + final T singleValueResult = convert(obj, type); + // return null for type conversion errors instead of single element array with value null + if (singleValueResult == null) { + return (T[])Array.newInstance(type, 0); + } + final T[] arrayResult = (T[])Array.newInstance(type, 1); + arrayResult[0] = singleValueResult; + return arrayResult; + } + } + +} diff --git a/fsresource-1.x/src/main/java/org/apache/sling/fsprovider/internal/mapper/valuemap/ValueMapDecorator.java b/fsresource-1.x/src/main/java/org/apache/sling/fsprovider/internal/mapper/valuemap/ValueMapDecorator.java new file mode 100644 index 0000000..4b0b5a5 --- /dev/null +++ b/fsresource-1.x/src/main/java/org/apache/sling/fsprovider/internal/mapper/valuemap/ValueMapDecorator.java @@ -0,0 +1,179 @@ +/* + * 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.fsprovider.internal.mapper.valuemap; + +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +import org.apache.sling.api.resource.ValueMap; + +/** + * This is copied from org.apache.sling.api.wrappers.ValueMapDectorator + * to avoid dependency to latest Sling API. + * This can be removed when Sling API 2.17.0 or higher is referenced. + */ +public final class ValueMapDecorator implements ValueMap { + + /** + * underlying map + */ + private final Map base; + + /** + * Creates a new wrapper around a given map. + * @param base wrapped object + */ + public ValueMapDecorator(Map base) { + this.base = base; + } + + /** + * {@inheritDoc} + */ + public T get(String name, Class type) { + if (base instanceof ValueMap) { + // shortcut if decorated map is ValueMap + return ((ValueMap)base).get(name, type); + } + return ObjectConverter.convert(get(name), type); + } + + /** + * {@inheritDoc} + */ + @SuppressWarnings("unchecked") + public T get(String name, T defaultValue) { + if (base instanceof ValueMap) { + // shortcut if decorated map is ValueMap + return ((ValueMap)base).get(name, defaultValue); + } + if (defaultValue == null) { + return (T)get(name); + } + T value = get(name, (Class) defaultValue.getClass()); + return value == null ? defaultValue : value; + } + + /** + * {@inheritDoc} + */ + public int size() { + return base.size(); + } + + /** + * {@inheritDoc} + */ + public boolean isEmpty() { + return base.isEmpty(); + } + + /** + * {@inheritDoc} + */ + public boolean containsKey(Object key) { + return base.containsKey(key); + } + + /** + * {@inheritDoc} + */ + public boolean containsValue(Object value) { + return base.containsValue(value); + } + + /** + * {@inheritDoc} + */ + public Object get(Object key) { + return base.get(key); + } + + /** + * {@inheritDoc} + */ + public Object put(String key, Object value) { + return base.put(key, value); + } + + /** + * {@inheritDoc} + */ + public Object remove(Object key) { + return base.remove(key); + } + + /** + * {@inheritDoc} + */ + public void putAll(Map t) { + base.putAll(t); + } + + /** + * {@inheritDoc} + */ + public void clear() { + base.clear(); + } + + /** + * {@inheritDoc} + */ + public Set keySet() { + return base.keySet(); + } + + /** + * {@inheritDoc} + */ + public Collection values() { + return base.values(); + } + + /** + * {@inheritDoc} + */ + public Set> entrySet() { + return base.entrySet(); + } + + @Override + public String toString() { + return super.toString() + " : " + this.base.toString(); + } + + @Override + /** + * {@inheritDoc} + */ + public int hashCode() { + return base.hashCode(); + } + + @Override + /** + * {@inheritDoc} + */ + public boolean equals(Object obj) { + return base.equals(obj); + } + +} diff --git a/fsresource-1.x/src/main/java/org/apache/sling/fsprovider/internal/mapper/valuemap/ValueMapUtil.java b/fsresource-1.x/src/main/java/org/apache/sling/fsprovider/internal/mapper/valuemap/ValueMapUtil.java new file mode 100644 index 0000000..ce69941 --- /dev/null +++ b/fsresource-1.x/src/main/java/org/apache/sling/fsprovider/internal/mapper/valuemap/ValueMapUtil.java @@ -0,0 +1,54 @@ +/* + * 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.fsprovider.internal.mapper.valuemap; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import org.apache.sling.api.resource.ValueMap; + +public final class ValueMapUtil { + + private ValueMapUtil() { + // static methods only + } + + /** + * Convert map to value map. + * @param content Content map. + * @return Value map. + */ + public static ValueMap toValueMap(Map content) { + Map props = new HashMap<>(); + + for (Map.Entry entry : content.entrySet()) { + if (entry.getValue() instanceof Collection) { + // convert lists to arrays + props.put(entry.getKey(), ((Collection)entry.getValue()).toArray()); + } + else { + props.put(entry.getKey(), entry.getValue()); + } + } + + return new ValueMapDecorator(props); + } + +} diff --git a/fsresource-1.x/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentElement.java b/fsresource-1.x/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentElement.java new file mode 100644 index 0000000..85ead62 --- /dev/null +++ b/fsresource-1.x/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentElement.java @@ -0,0 +1,52 @@ +/* + * 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.fsprovider.internal.parser; + +import java.util.Map; + +/** + * Represents a resource or node in the content hierarchy. + */ +public interface ContentElement { + + /** + * @return Resource name. The root resource has no name (null). + */ + String getName(); + + /** + * Properties of this resource. + * @return Properties (keys, values) + */ + Map getProperties(); + + /** + * Get children of current resource. The Map preserves the ordering of children. + * @return Children (child names, child objects) + */ + Map getChildren(); + + /** + * Get child or descendant + * @param path Relative path to address child or one of it's descendants (use "/" as hierarchy separator). + * @return Child or null if no child found with this path + */ + ContentElement getChild(String path); + +} diff --git a/fsresource-1.x/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentElementHandler.java b/fsresource-1.x/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentElementHandler.java new file mode 100644 index 0000000..e128943 --- /dev/null +++ b/fsresource-1.x/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentElementHandler.java @@ -0,0 +1,69 @@ +/* + * 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.fsprovider.internal.parser; + +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.commons.lang3.StringUtils; +import org.apache.sling.jcr.contentparser.ContentHandler; + +/** + * {@link ContentHandler} implementation that produces a tree of {@link ContentElement} items. + */ +final class ContentElementHandler implements ContentHandler { + + private ContentElement root; + private Pattern PATH_PATTERN = Pattern.compile("^((/[^/]+)*)(/([^/]+))$"); + + @Override + public void resource(String path, Map properties) { + if (StringUtils.equals(path, "/")) { + root = new ContentElementImpl(null, properties); + } + else { + if (root == null) { + throw new RuntimeException("Root resource not set."); + } + Matcher matcher = PATH_PATTERN.matcher(path); + if (!matcher.matches()) { + throw new RuntimeException("Unexpected path:" + path); + } + String relativeParentPath = StringUtils.stripStart(matcher.group(1), "/"); + String name = matcher.group(4); + ContentElement parent; + if (StringUtils.isEmpty(relativeParentPath)) { + parent = root; + } + else { + parent = root.getChild(relativeParentPath); + } + if (parent == null) { + throw new RuntimeException("Parent '" + relativeParentPath + "' does not exist."); + } + parent.getChildren().put(name, new ContentElementImpl(name, properties)); + } + } + + public ContentElement getRoot() { + return root; + } + +} diff --git a/fsresource-1.x/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentElementImpl.java b/fsresource-1.x/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentElementImpl.java new file mode 100644 index 0000000..5205c6e --- /dev/null +++ b/fsresource-1.x/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentElementImpl.java @@ -0,0 +1,68 @@ +/* + * 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.fsprovider.internal.parser; + +import java.util.LinkedHashMap; +import java.util.Map; + +import org.apache.commons.lang3.StringUtils; + +public final class ContentElementImpl implements ContentElement { + + private final String name; + private final Map properties; + private final Map children = new LinkedHashMap<>(); + + public ContentElementImpl(String name, Map properties) { + this.name = name; + this.properties = properties; + } + + @Override + public String getName() { + return name; + } + + @Override + public Map getProperties() { + return properties; + } + + @Override + public Map getChildren() { + return children; + } + + @Override + public ContentElement getChild(String path) { + String name = StringUtils.substringBefore(path, "/"); + ContentElement child = children.get(name); + if (child == null) { + return null; + } + String remainingPath = StringUtils.substringAfter(path, "/"); + if (StringUtils.isEmpty(remainingPath)) { + return child; + } + else { + return child.getChild(remainingPath); + } + } + +} diff --git a/fsresource-1.x/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileCache.java b/fsresource-1.x/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileCache.java new file mode 100644 index 0000000..4222e98 --- /dev/null +++ b/fsresource-1.x/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileCache.java @@ -0,0 +1,107 @@ +/* + * 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.fsprovider.internal.parser; + +import java.io.File; +import java.util.Collections; +import java.util.Map; + +import org.apache.commons.collections.map.LRUMap; + +/** + * Cache for parsed content from content files (e.g. JSON, JCR XML). + */ +public final class ContentFileCache { + + private final Map contentCache; + private final ContentElement NULL_ELEMENT = new ContentElementImpl(null, Collections.emptyMap()); + + /** + * @param maxSize Cache size. 0 = caching disabled. + */ + @SuppressWarnings("unchecked") + public ContentFileCache(int maxSize) { + if (maxSize > 0) { + this.contentCache = Collections.synchronizedMap(new LRUMap(maxSize)); + } + else { + this.contentCache = null; + } + } + + /** + * Get content. + * @param path Path (used as cache key). + * @param file File + * @return Content or null + */ + public ContentElement get(String path, File file) { + ContentElement content = null; + if (contentCache != null) { + content = contentCache.get(path); + } + if (content == null) { + content = ContentFileParserUtil.parse(file); + if (content == null) { + content = NULL_ELEMENT; + } + if (contentCache != null) { + contentCache.put(path, content); + } + } + if (content == NULL_ELEMENT) { + return null; + } + else { + return content; + } + } + + /** + * Remove content from cache. + * @param path Path (used as cache key) + */ + public void remove(String path) { + if (contentCache != null) { + contentCache.remove(path); + } + } + + /** + * Clear whole cache + */ + public void clear() { + if (contentCache != null) { + contentCache.clear(); + } + } + + /** + * @return Current cache size + */ + public int size() { + if (contentCache != null) { + return contentCache.size(); + } + else { + return 0; + } + } + +} diff --git a/fsresource-1.x/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileParserUtil.java b/fsresource-1.x/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileParserUtil.java new file mode 100644 index 0000000..a105dad --- /dev/null +++ b/fsresource-1.x/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileParserUtil.java @@ -0,0 +1,104 @@ +/* + * 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.fsprovider.internal.parser; + +import static org.apache.jackrabbit.vault.util.Constants.DOT_CONTENT_XML; +import static org.apache.sling.fsprovider.internal.parser.ContentFileTypes.JCR_XML_SUFFIX; +import static org.apache.sling.fsprovider.internal.parser.ContentFileTypes.XML_SUFFIX; +import static org.apache.sling.fsprovider.internal.parser.ContentFileTypes.JSON_SUFFIX; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.EnumSet; + +import org.apache.commons.lang3.StringUtils; +import org.apache.sling.jcr.contentparser.ContentParser; +import org.apache.sling.jcr.contentparser.ContentParserFactory; +import org.apache.sling.jcr.contentparser.ContentType; +import org.apache.sling.jcr.contentparser.JsonParserFeature; +import org.apache.sling.jcr.contentparser.ParserOptions; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Parses files that contains content fragments (e.g. JSON, JCR XML). + */ +class ContentFileParserUtil { + + private static final Logger log = LoggerFactory.getLogger(ContentFileParserUtil.class); + + private static final ContentParser JSON_PARSER; + static { + // workaround for JsonProvider classloader issue until https://issues.apache.org/jira/browse/GERONIMO-6560 is fixed + ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader(); + try { + Thread.currentThread().setContextClassLoader(ContentFileParserUtil.class.getClassLoader()); + // support comments and tick quotes for JSON parsing - same as in JCR content loader + JSON_PARSER = ContentParserFactory.create(ContentType.JSON, new ParserOptions() + .jsonParserFeatures(EnumSet.of(JsonParserFeature.COMMENTS, JsonParserFeature.QUOTE_TICK))); + } + finally { + Thread.currentThread().setContextClassLoader(oldClassLoader); + } + } + private static final ContentParser JCR_XML_PARSER = ContentParserFactory.create(ContentType.JCR_XML); + private static final ContentParser XML_PARSER = ContentParserFactory.create(ContentType.XML); + + private ContentFileParserUtil() { + // static methods only + } + + /** + * Parse content from file. + * @param file File. Type is detected automatically. + * @return Content or null if content could not be parsed. + */ + public static ContentElement parse(File file) { + if (!file.exists()) { + return null; + } + try { + if (StringUtils.endsWith(file.getName(), JSON_SUFFIX)) { + return parse(JSON_PARSER, file); + } + else if (StringUtils.equals(file.getName(), DOT_CONTENT_XML) || StringUtils.endsWith(file.getName(), JCR_XML_SUFFIX)) { + return parse(JCR_XML_PARSER, file); + } + else if (StringUtils.endsWith(file.getName(), XML_SUFFIX) && !StringUtils.endsWith(file.getName(), JCR_XML_SUFFIX)) { + return parse(XML_PARSER, file); + } + } + catch (Throwable ex) { + log.warn("Error parsing content from " + file.getPath(), ex); + } + return null; + } + + private static ContentElement parse(ContentParser contentParser, File file) throws IOException { + try (FileInputStream fis = new FileInputStream(file); + BufferedInputStream bis = new BufferedInputStream(fis)) { + ContentElementHandler handler = new ContentElementHandler(); + contentParser.parse(handler, bis); + return handler.getRoot(); + } + } + +} diff --git a/fsresource-1.x/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileTypes.java b/fsresource-1.x/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileTypes.java new file mode 100644 index 0000000..9cd9931 --- /dev/null +++ b/fsresource-1.x/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileTypes.java @@ -0,0 +1,47 @@ +/* + * 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.fsprovider.internal.parser; + +import org.apache.sling.jcr.contentparser.ContentType; + +/** + * Content file types. + */ +public final class ContentFileTypes { + + /** + * JSON content files. + */ + public static final String JSON_SUFFIX = "." + ContentType.JSON.getExtension(); + + /** + * XML content files. + */ + public static final String XML_SUFFIX = "." + ContentType.XML.getExtension(); + + /** + * JCR XML content files. + */ + public static final String JCR_XML_SUFFIX = "." + ContentType.JCR_XML.getExtension(); + + private ContentFileTypes() { + // static methods only + } + +} diff --git a/fsresource-1.x/src/test/java/org/apache/sling/fsprovider/internal/FileMonitorTest.java b/fsresource-1.x/src/test/java/org/apache/sling/fsprovider/internal/FileMonitorTest.java new file mode 100644 index 0000000..978419b --- /dev/null +++ b/fsresource-1.x/src/test/java/org/apache/sling/fsprovider/internal/FileMonitorTest.java @@ -0,0 +1,215 @@ +/* + * 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.fsprovider.internal; + +import static org.apache.sling.api.SlingConstants.TOPIC_RESOURCE_ADDED; +import static org.apache.sling.api.SlingConstants.TOPIC_RESOURCE_CHANGED; +import static org.apache.sling.api.SlingConstants.TOPIC_RESOURCE_REMOVED; +import static org.apache.sling.fsprovider.internal.TestUtils.assertChange; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.nio.file.Files; +import java.util.List; + +import org.apache.commons.io.FileUtils; +import org.apache.sling.fsprovider.internal.FileMonitor.ResourceChange; +import org.apache.sling.fsprovider.internal.TestUtils.ResourceListener; +import org.apache.sling.testing.mock.sling.ResourceResolverType; +import org.apache.sling.testing.mock.sling.junit.SlingContext; +import org.apache.sling.testing.mock.sling.junit.SlingContextBuilder; +import org.apache.sling.testing.mock.sling.junit.SlingContextCallback; +import org.junit.Rule; +import org.junit.Test; +import org.osgi.service.event.EventConstants; +import org.osgi.service.event.EventHandler; + +/** + * Test events when changing file system content (Sling-Initial-Content). + */ +public class FileMonitorTest { + + private static final int CHECK_INTERVAL = 120; + private static final int WAIT_INTERVAL = 250; + + private final File tempDir; + private final ResourceListener resourceListener = new ResourceListener(); + + public FileMonitorTest() throws Exception { + tempDir = Files.createTempDirectory(getClass().getName()).toFile(); + } + + @Rule + public SlingContext context = new SlingContextBuilder(ResourceResolverType.JCR_MOCK) + .beforeSetUp(new SlingContextCallback() { + @Override + public void execute(SlingContext context) throws Exception { + // copy test content to temp. directory + tempDir.mkdirs(); + File sourceDir = new File("src/test/resources/fs-test"); + FileUtils.copyDirectory(sourceDir, tempDir); + + // mount temp. directory + context.registerInjectActivateService(new FsResourceProvider(), + "provider.file", tempDir.getPath(), + "provider.roots", "/fs-test", + "provider.checkinterval", CHECK_INTERVAL, + "provider.fs.mode", FsMode.INITIAL_CONTENT.name(), + "provider.initial.content.import.options", "overwrite:=true;ignoreImportProviders:=jcr.xml"); + + // register resource change listener + context.registerService(EventHandler.class, resourceListener, + EventConstants.EVENT_TOPIC, new String[] { + TOPIC_RESOURCE_ADDED, + TOPIC_RESOURCE_CHANGED, + TOPIC_RESOURCE_REMOVED + }); + } + }) + .afterTearDown(new SlingContextCallback() { + @Override + public void execute(SlingContext context) throws Exception { + // remove temp directory + tempDir.delete(); + } + }) + .build(); + + @Test + public void testUpdateFile() throws Exception { + List changes = resourceListener.getChanges(); + assertTrue(changes.isEmpty()); + + File file1a = new File(tempDir, "folder1/file1a.txt"); + FileUtils.touch(file1a); + + Thread.sleep(WAIT_INTERVAL); + + assertEquals(1, changes.size()); + assertChange(changes, "/fs-test/folder1/file1a.txt", TOPIC_RESOURCE_CHANGED); + } + + @Test + public void testAddFile() throws Exception { + List changes = resourceListener.getChanges(); + assertTrue(changes.isEmpty()); + + File file1c = new File(tempDir, "folder1/file1c.txt"); + FileUtils.write(file1c, "newcontent"); + + Thread.sleep(WAIT_INTERVAL); + + assertEquals(2, changes.size()); + assertChange(changes, "/fs-test/folder1", TOPIC_RESOURCE_CHANGED); + assertChange(changes, "/fs-test/folder1/file1c.txt", TOPIC_RESOURCE_ADDED); + } + + @Test + public void testRemoveFile() throws Exception { + List changes = resourceListener.getChanges(); + assertTrue(changes.isEmpty()); + + File file1a = new File(tempDir, "folder1/file1a.txt"); + file1a.delete(); + + Thread.sleep(WAIT_INTERVAL); + + assertEquals(2, changes.size()); + assertChange(changes, "/fs-test/folder1", TOPIC_RESOURCE_CHANGED); + assertChange(changes, "/fs-test/folder1/file1a.txt", TOPIC_RESOURCE_REMOVED); + } + + @Test + public void testAddFolder() throws Exception { + List changes = resourceListener.getChanges(); + assertTrue(changes.isEmpty()); + + File folder99 = new File(tempDir, "folder99"); + folder99.mkdir(); + + Thread.sleep(WAIT_INTERVAL); + + assertEquals(2, changes.size()); + assertChange(changes, "/fs-test", TOPIC_RESOURCE_CHANGED); + assertChange(changes, "/fs-test/folder99", TOPIC_RESOURCE_ADDED); + } + + @Test + public void testRemoveFolder() throws Exception { + List changes = resourceListener.getChanges(); + assertTrue(changes.isEmpty()); + + File folder1 = new File(tempDir, "folder1"); + FileUtils.deleteDirectory(folder1); + + Thread.sleep(WAIT_INTERVAL); + + assertEquals(2, changes.size()); + assertChange(changes, "/fs-test", TOPIC_RESOURCE_CHANGED); + assertChange(changes, "/fs-test/folder1", TOPIC_RESOURCE_REMOVED); + } + + @Test + public void testUpdateContent() throws Exception { + List changes = resourceListener.getChanges(); + assertTrue(changes.isEmpty()); + + File file1a = new File(tempDir, "folder2/content.json"); + FileUtils.touch(file1a); + + Thread.sleep(WAIT_INTERVAL); + + assertChange(changes, "/fs-test/folder2/content", TOPIC_RESOURCE_REMOVED); + assertChange(changes, "/fs-test/folder2/content", TOPIC_RESOURCE_ADDED); + assertChange(changes, "/fs-test/folder2/content/jcr:content", TOPIC_RESOURCE_ADDED); + } + + @Test + public void testAddContent() throws Exception { + List changes = resourceListener.getChanges(); + assertTrue(changes.isEmpty()); + + File file1c = new File(tempDir, "folder1/file1c.json"); + FileUtils.write(file1c, "{\"prop1\":\"value1\",\"child1\":{\"prop2\":\"value1\"}}"); + + Thread.sleep(WAIT_INTERVAL); + + assertEquals(3, changes.size()); + assertChange(changes, "/fs-test/folder1", TOPIC_RESOURCE_CHANGED); + assertChange(changes, "/fs-test/folder1/file1c", TOPIC_RESOURCE_ADDED); + assertChange(changes, "/fs-test/folder1/file1c/child1", TOPIC_RESOURCE_ADDED); + } + + @Test + public void testRemoveContent() throws Exception { + List changes = resourceListener.getChanges(); + assertTrue(changes.isEmpty()); + + File file1a = new File(tempDir, "folder2/content.json"); + file1a.delete(); + + Thread.sleep(WAIT_INTERVAL); + + assertEquals(2, changes.size()); + assertChange(changes, "/fs-test/folder2", TOPIC_RESOURCE_CHANGED); + assertChange(changes, "/fs-test/folder2/content", TOPIC_RESOURCE_REMOVED); + } + +} diff --git a/fsresource-1.x/src/test/java/org/apache/sling/fsprovider/internal/FileVaultContentTest.java b/fsresource-1.x/src/test/java/org/apache/sling/fsprovider/internal/FileVaultContentTest.java new file mode 100644 index 0000000..13a0bcf --- /dev/null +++ b/fsresource-1.x/src/test/java/org/apache/sling/fsprovider/internal/FileVaultContentTest.java @@ -0,0 +1,145 @@ +/* + * 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.fsprovider.internal; + +import static org.apache.sling.fsprovider.internal.TestUtils.assertFile; +import static org.apache.sling.fsprovider.internal.TestUtils.assertFolder; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; + +import java.util.List; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; +import javax.jcr.Session; + +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ResourceUtil; +import org.apache.sling.api.resource.ValueMap; +import org.apache.sling.fsprovider.internal.TestUtils.RegisterFsResourcePlugin; +import org.apache.sling.hamcrest.ResourceMatchers; +import org.apache.sling.testing.mock.sling.ResourceResolverType; +import org.apache.sling.testing.mock.sling.junit.SlingContext; +import org.apache.sling.testing.mock.sling.junit.SlingContextBuilder; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +import com.google.common.collect.ImmutableList; + +/** + * Test access FileFault XML files, folders and content. + */ +public class FileVaultContentTest { + + private Resource damAsset; + private Resource sampleContent; + + @Rule + public SlingContext context = new SlingContextBuilder(ResourceResolverType.JCR_MOCK) + .plugin(new RegisterFsResourcePlugin( + "provider.fs.mode", FsMode.FILEVAULT_XML.name(), + "provider.file", "src/test/resources/vaultfs-test/jcr_root", + "provider.filevault.filterxml.path", "src/test/resources/vaultfs-test/META-INF/vault/filter.xml", + "provider.roots", "/content/dam/talk.png" + )) + .plugin(new RegisterFsResourcePlugin( + "provider.fs.mode", FsMode.FILEVAULT_XML.name(), + "provider.file", "src/test/resources/vaultfs-test/jcr_root", + "provider.filevault.filterxml.path", "src/test/resources/vaultfs-test/META-INF/vault/filter.xml", + "provider.roots", "/content/samples" + )) + .build(); + + @Before + public void setUp() { + damAsset = context.resourceResolver().getResource("/content/dam/talk.png"); + sampleContent = context.resourceResolver().getResource("/content/samples"); + } + + @Test + public void testDamAsset() { + assertNotNull(damAsset); + assertEquals("app:Asset", damAsset.getResourceType()); + + Resource content = damAsset.getChild("jcr:content"); + assertNotNull(content); + assertEquals("app:AssetContent", content.getResourceType()); + + Resource metadata = content.getChild("metadata"); + assertNotNull(metadata); + ValueMap props = ResourceUtil.getValueMap(metadata); + assertEquals((Integer)4, props.get("app:Bitsperpixel", Integer.class)); + + assertFolder(content, "renditions"); + assertFile(content, "renditions/original", null); + assertFile(content, "renditions/web.1280.1280.png", null); + } + + @Test + public void testSampleContent() { + assertNotNull(sampleContent); + assertEquals("sling:OrderedFolder", sampleContent.getResourceType()); + + Resource enContent = sampleContent.getChild("en/jcr:content"); + assertArrayEquals(new String[] { "/etc/mobile/groups/responsive" }, ResourceUtil.getValueMap(enContent).get("app:deviceGroups", String[].class)); + } + + @Test + public void testListChildren() { + Resource en = sampleContent.getChild("en"); + List children = ImmutableList.copyOf(en.listChildren()); + assertEquals(2, children.size()); + + Resource child1 = children.get(0); + assertEquals("jcr:content", child1.getName()); + assertEquals("samples/sample-app/components/content/page/homepage", child1.getResourceType()); + + Resource child2 = children.get(1); + assertEquals("tools", child2.getName()); + assertEquals("app:Page", child2.getResourceType()); + + // child3 (conference) is hidden because of filter + } + + @Test + public void testJcrMixedContent() throws RepositoryException { + // prepare mixed JCR content + Node root = context.resourceResolver().adaptTo(Session.class).getNode("/"); + Node content = root.addNode("content", "nt:folder"); + Node samples = content.addNode("samples", "nt:folder"); + Node en = samples.addNode("en", "nt:folder"); + Node conference = en.addNode("conference", "nt:folder"); + conference.addNode("page2", "nt:folder"); + samples.addNode("it", "nt:folder"); + + // pass-through because of filter + assertNotNull(context.resourceResolver().getResource("/content/samples/en/conference")); + assertNotNull(sampleContent.getChild("en/conference")); + assertNotNull(context.resourceResolver().getResource("/content/samples/en/conference/page2")); + assertNotNull(sampleContent.getChild("en/conference/page2")); + + // list children with mixed content + Resource enResource = sampleContent.getChild("en"); + assertThat(enResource, ResourceMatchers.containsChildren("jcr:content", "tools", "conference")); + } + +} diff --git a/fsresource-1.x/src/test/java/org/apache/sling/fsprovider/internal/FileVaultFileMonitorTest.java b/fsresource-1.x/src/test/java/org/apache/sling/fsprovider/internal/FileVaultFileMonitorTest.java new file mode 100644 index 0000000..0475f77 --- /dev/null +++ b/fsresource-1.x/src/test/java/org/apache/sling/fsprovider/internal/FileVaultFileMonitorTest.java @@ -0,0 +1,241 @@ +/* + * 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.fsprovider.internal; + +import static org.apache.sling.api.SlingConstants.TOPIC_RESOURCE_ADDED; +import static org.apache.sling.api.SlingConstants.TOPIC_RESOURCE_CHANGED; +import static org.apache.sling.api.SlingConstants.TOPIC_RESOURCE_REMOVED; +import static org.apache.sling.fsprovider.internal.TestUtils.assertChange; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.nio.file.Files; +import java.util.List; + +import org.apache.commons.io.FileUtils; +import org.apache.sling.fsprovider.internal.FileMonitor.ResourceChange; +import org.apache.sling.fsprovider.internal.TestUtils.ResourceListener; +import org.apache.sling.testing.mock.sling.ResourceResolverType; +import org.apache.sling.testing.mock.sling.junit.SlingContext; +import org.apache.sling.testing.mock.sling.junit.SlingContextBuilder; +import org.apache.sling.testing.mock.sling.junit.SlingContextCallback; +import org.junit.Rule; +import org.junit.Test; +import org.osgi.service.event.EventConstants; +import org.osgi.service.event.EventHandler; + +/** + * Test events when changing file system content (FileVault XML). + */ +public class FileVaultFileMonitorTest { + + private static final int CHECK_INTERVAL = 120; + private static final int WAIT_INTERVAL = 250; + + private final File tempDir; + private final ResourceListener resourceListener = new ResourceListener(); + + public FileVaultFileMonitorTest() throws Exception { + tempDir = Files.createTempDirectory(getClass().getName()).toFile(); + } + + @Rule + public SlingContext context = new SlingContextBuilder(ResourceResolverType.JCR_MOCK) + .beforeSetUp(new SlingContextCallback() { + @Override + public void execute(SlingContext context) throws Exception { + // copy test content to temp. directory + tempDir.mkdirs(); + File sourceDir = new File("src/test/resources/vaultfs-test"); + FileUtils.copyDirectory(sourceDir, tempDir); + + // mount temp. directory + context.registerInjectActivateService(new FsResourceProvider(), + "provider.file", tempDir.getPath() + "/jcr_root", + "provider.filevault.filterxml.path", tempDir.getPath() + "/META-INF/vault/filter.xml", + "provider.roots", "/content/dam/talk.png", + "provider.checkinterval", CHECK_INTERVAL, + "provider.fs.mode", FsMode.FILEVAULT_XML.name()); + context.registerInjectActivateService(new FsResourceProvider(), + "provider.file", tempDir.getPath() + "/jcr_root", + "provider.filevault.filterxml.path", tempDir.getPath() + "/META-INF/vault/filter.xml", + "provider.roots", "/content/samples", + "provider.checkinterval", CHECK_INTERVAL, + "provider.fs.mode", FsMode.FILEVAULT_XML.name()); + + // register resource change listener + context.registerService(EventHandler.class, resourceListener, + EventConstants.EVENT_TOPIC, new String[] { + TOPIC_RESOURCE_ADDED, + TOPIC_RESOURCE_CHANGED, + TOPIC_RESOURCE_REMOVED + }); + } + }) + .afterTearDown(new SlingContextCallback() { + @Override + public void execute(SlingContext context) throws Exception { + // remove temp directory + tempDir.delete(); + } + }) + .build(); + + @Test + public void testUpdateFile() throws Exception { + List changes = resourceListener.getChanges(); + assertTrue(changes.isEmpty()); + + File file = new File(tempDir, "jcr_root/content/dam/talk.png/_jcr_content/renditions/web.1280.1280.png"); + FileUtils.touch(file); + + Thread.sleep(WAIT_INTERVAL); + + assertEquals(1, changes.size()); + assertChange(changes, "/content/dam/talk.png/jcr:content/renditions/web.1280.1280.png", TOPIC_RESOURCE_CHANGED); + } + + @Test + public void testAddFile() throws Exception { + List changes = resourceListener.getChanges(); + assertTrue(changes.isEmpty()); + + File file = new File(tempDir, "jcr_root/content/dam/talk.png/_jcr_content/renditions/text.txt"); + FileUtils.write(file, "newcontent"); + + Thread.sleep(WAIT_INTERVAL); + + assertEquals(2, changes.size()); + assertChange(changes, "/content/dam/talk.png/jcr:content/renditions", TOPIC_RESOURCE_CHANGED); + assertChange(changes, "/content/dam/talk.png/jcr:content/renditions/text.txt", TOPIC_RESOURCE_ADDED); + } + + @Test + public void testRemoveFile() throws Exception { + List changes = resourceListener.getChanges(); + assertTrue(changes.isEmpty()); + + File file = new File(tempDir, "jcr_root/content/dam/talk.png/_jcr_content/renditions/web.1280.1280.png"); + file.delete(); + + Thread.sleep(WAIT_INTERVAL); + + assertEquals(2, changes.size()); + assertChange(changes, "/content/dam/talk.png/jcr:content/renditions", TOPIC_RESOURCE_CHANGED); + assertChange(changes, "/content/dam/talk.png/jcr:content/renditions/web.1280.1280.png", TOPIC_RESOURCE_REMOVED); + } + + @Test + public void testAddFolder() throws Exception { + List changes = resourceListener.getChanges(); + assertTrue(changes.isEmpty()); + + File folder = new File(tempDir, "jcr_root/content/dam/talk.png/_jcr_content/newfolder"); + folder.mkdir(); + + Thread.sleep(WAIT_INTERVAL); + + assertEquals(2, changes.size()); + assertChange(changes, "/content/dam/talk.png/jcr:content", TOPIC_RESOURCE_CHANGED); + assertChange(changes, "/content/dam/talk.png/jcr:content/newfolder", TOPIC_RESOURCE_ADDED); + } + + @Test + public void testRemoveFolder() throws Exception { + List changes = resourceListener.getChanges(); + assertTrue(changes.isEmpty()); + + File folder = new File(tempDir, "jcr_root/content/dam/talk.png/_jcr_content/renditions"); + FileUtils.deleteDirectory(folder); + + Thread.sleep(WAIT_INTERVAL); + + assertEquals(2, changes.size()); + assertChange(changes, "/content/dam/talk.png/jcr:content", TOPIC_RESOURCE_CHANGED); + assertChange(changes, "/content/dam/talk.png/jcr:content/renditions", TOPIC_RESOURCE_REMOVED); + } + + @Test + public void testUpdateContent() throws Exception { + List changes = resourceListener.getChanges(); + assertTrue(changes.isEmpty()); + + File file = new File(tempDir, "jcr_root/content/samples/en/.content.xml"); + FileUtils.touch(file); + + Thread.sleep(WAIT_INTERVAL); + + assertChange(changes, "/content/samples/en", TOPIC_RESOURCE_REMOVED); + assertChange(changes, "/content/samples/en", TOPIC_RESOURCE_ADDED); + assertChange(changes, "/content/samples/en/jcr:content", TOPIC_RESOURCE_ADDED); + } + + @Test + public void testAddContent() throws Exception { + List changes = resourceListener.getChanges(); + assertTrue(changes.isEmpty()); + + File file = new File(tempDir, "jcr_root/content/samples/fr/.content.xml"); + file.getParentFile().mkdir(); + FileUtils.write(file, "\n" + + "\n" + + "\n" + + ""); + + Thread.sleep(WAIT_INTERVAL); + + assertChange(changes, "/content/samples", TOPIC_RESOURCE_CHANGED); + assertChange(changes, "/content/samples/fr", TOPIC_RESOURCE_ADDED); + assertChange(changes, "/content/samples/fr/jcr:content", TOPIC_RESOURCE_ADDED); + } + + @Test + public void testRemoveContent() throws Exception { + List changes = resourceListener.getChanges(); + assertTrue(changes.isEmpty()); + + File file = new File(tempDir, "jcr_root/content/samples/en"); + FileUtils.deleteDirectory(file); + + Thread.sleep(WAIT_INTERVAL); + + assertEquals(2, changes.size()); + assertChange(changes, "/content/samples", TOPIC_RESOURCE_CHANGED); + assertChange(changes, "/content/samples/en", TOPIC_RESOURCE_REMOVED); + } + + @Test + public void testRemoveContentDotXmlOnly() throws Exception { + List changes = resourceListener.getChanges(); + assertTrue(changes.isEmpty()); + + File file = new File(tempDir, "jcr_root/content/samples/en/.content.xml"); + file.delete(); + + Thread.sleep(WAIT_INTERVAL); + + assertEquals(2, changes.size()); + assertChange(changes, "/content/samples/en", TOPIC_RESOURCE_CHANGED); + // this second event is not fully correct, but this is a quite special case, accept it for now + assertChange(changes, "/content/samples/en", TOPIC_RESOURCE_REMOVED); + } + +} diff --git a/fsresource-1.x/src/test/java/org/apache/sling/fsprovider/internal/FilesFolderTest.java b/fsresource-1.x/src/test/java/org/apache/sling/fsprovider/internal/FilesFolderTest.java new file mode 100644 index 0000000..c1f3470 --- /dev/null +++ b/fsresource-1.x/src/test/java/org/apache/sling/fsprovider/internal/FilesFolderTest.java @@ -0,0 +1,82 @@ +/* + * 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.fsprovider.internal; + +import static org.apache.sling.fsprovider.internal.TestUtils.assertFile; +import static org.apache.sling.fsprovider.internal.TestUtils.assertFolder; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; + +import org.apache.sling.api.resource.Resource; +import org.apache.sling.fsprovider.internal.TestUtils.RegisterFsResourcePlugin; +import org.apache.sling.hamcrest.ResourceMatchers; +import org.apache.sling.testing.mock.sling.ResourceResolverType; +import org.apache.sling.testing.mock.sling.junit.SlingContext; +import org.apache.sling.testing.mock.sling.junit.SlingContextBuilder; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +/** + * Test access to files and folders from file system. + */ +public class FilesFolderTest { + + private Resource root; + private Resource fsroot; + + @Rule + public SlingContext context = new SlingContextBuilder(ResourceResolverType.JCR_MOCK) + .plugin(new RegisterFsResourcePlugin()) + .build(); + + @Before + public void setUp() { + root = context.resourceResolver().getResource("/"); + fsroot = context.resourceResolver().getResource("/fs-test"); + } + + @Test + public void testFolders() { + assertFolder(fsroot, "folder1"); + assertFolder(fsroot, "folder1/folder11"); + assertFolder(fsroot, "folder2"); + assertFolder(fsroot, "folder3"); + } + + @Test + public void testFiles() { + assertFile(fsroot, "folder1/file1a.txt", "file1a"); + assertFile(fsroot, "folder1/file1b.txt", "file1b"); + assertFile(fsroot, "folder1/folder11/file11a.txt", "file11a"); + assertFile(fsroot, "folder2/content.json", null); + assertFile(fsroot, "folder2/content/file2content.txt", "file2content"); + assertFile(fsroot, "folder3/content.jcr.xml", null); + } + + @Test + public void testListChildren() { + assertThat(root, ResourceMatchers.containsChildren("fs-test")); + assertThat(fsroot, ResourceMatchers.hasChildren("folder1", "folder2", "folder3")); + assertThat(fsroot.getChild("folder1"), ResourceMatchers.hasChildren("folder11", "file1a.txt", "file1b.txt")); + assertThat(fsroot.getChild("folder2"), ResourceMatchers.hasChildren("folder21", "content.json")); + assertFalse(fsroot.getChild("folder1/file1a.txt").listChildren().hasNext()); + } + +} diff --git a/fsresource-1.x/src/test/java/org/apache/sling/fsprovider/internal/InitialContentImportOptionsTest.java b/fsresource-1.x/src/test/java/org/apache/sling/fsprovider/internal/InitialContentImportOptionsTest.java new file mode 100644 index 0000000..3a0d166 --- /dev/null +++ b/fsresource-1.x/src/test/java/org/apache/sling/fsprovider/internal/InitialContentImportOptionsTest.java @@ -0,0 +1,59 @@ +/* + * 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.fsprovider.internal; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +import com.google.common.collect.ImmutableSet; + +public class InitialContentImportOptionsTest { + + @Test + public void testNull() { + InitialContentImportOptions underTest = new InitialContentImportOptions(null); + assertFalse(underTest.isOverwrite()); + assertTrue(underTest.getIgnoreImportProviders().isEmpty()); + } + + @Test + public void testBlank() { + InitialContentImportOptions underTest = new InitialContentImportOptions(" "); + assertFalse(underTest.isOverwrite()); + assertTrue(underTest.getIgnoreImportProviders().isEmpty()); + } + + @Test + public void testOptions1() { + InitialContentImportOptions underTest = new InitialContentImportOptions("overwrite:=true;ignoreImportProviders:=\"xml,json\""); + assertTrue(underTest.isOverwrite()); + assertEquals(ImmutableSet.of("xml","json"), underTest.getIgnoreImportProviders()); + } + + @Test + public void testOptions2() { + InitialContentImportOptions underTest = new InitialContentImportOptions(" overwrite := false ; ignoreImportProviders := xml "); + assertFalse(underTest.isOverwrite()); + assertEquals(ImmutableSet.of("xml"), underTest.getIgnoreImportProviders()); + } + +} diff --git a/fsresource-1.x/src/test/java/org/apache/sling/fsprovider/internal/InvalidRootFolderTest.java b/fsresource-1.x/src/test/java/org/apache/sling/fsprovider/internal/InvalidRootFolderTest.java new file mode 100644 index 0000000..6d080b1 --- /dev/null +++ b/fsresource-1.x/src/test/java/org/apache/sling/fsprovider/internal/InvalidRootFolderTest.java @@ -0,0 +1,75 @@ +/* + * 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.fsprovider.internal; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; + +import java.io.File; + +import org.apache.sling.api.resource.Resource; +import org.apache.sling.fsprovider.internal.TestUtils.RegisterFsResourcePlugin; +import org.apache.sling.testing.mock.sling.ResourceResolverType; +import org.apache.sling.testing.mock.sling.junit.SlingContext; +import org.apache.sling.testing.mock.sling.junit.SlingContextBuilder; +import org.apache.sling.testing.mock.sling.junit.SlingContextCallback; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +/** + * Test with invalid fs folder. + */ +public class InvalidRootFolderTest { + + private Resource fsroot; + + @Rule + public SlingContext context = new SlingContextBuilder(ResourceResolverType.JCR_MOCK) + .plugin(new RegisterFsResourcePlugin("provider.file", "target/temp/invalid-folder")) + .afterTearDown(new SlingContextCallback() { + @Override + public void execute(SlingContext context) throws Exception { + File file = new File("target/temp/invalid-folder"); + file.delete(); + } + }) + .build(); + + @Before + public void setUp() { + fsroot = context.resourceResolver().getResource("/fs-test"); + } + + @Test + public void testFolders() { + assertNull(fsroot.getChild("folder1")); + } + + @Test + public void testFiles() { + assertNull(fsroot.getChild("folder1/file1a.txt")); + } + + @Test + public void testListChildren() { + assertFalse(fsroot.listChildren().hasNext()); + } + +} diff --git a/fsresource-1.x/src/test/java/org/apache/sling/fsprovider/internal/JcrMixedTest.java b/fsresource-1.x/src/test/java/org/apache/sling/fsprovider/internal/JcrMixedTest.java new file mode 100644 index 0000000..9a220d9 --- /dev/null +++ b/fsresource-1.x/src/test/java/org/apache/sling/fsprovider/internal/JcrMixedTest.java @@ -0,0 +1,103 @@ +/* + * 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.fsprovider.internal; + +import static org.apache.sling.fsprovider.internal.TestUtils.assertFile; +import static org.hamcrest.Matchers.not; +import static org.junit.Assert.assertThat; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; + +import org.apache.sling.api.resource.Resource; +import org.apache.sling.fsprovider.internal.TestUtils.RegisterFsResourcePlugin; +import org.apache.sling.hamcrest.ResourceMatchers; +import org.apache.sling.testing.mock.sling.ResourceResolverType; +import org.apache.sling.testing.mock.sling.junit.SlingContext; +import org.apache.sling.testing.mock.sling.junit.SlingContextBuilder; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +/** + * Test access mixed with JCR content on same path. + */ +public class JcrMixedTest { + + private Resource root; + private Resource fsroot; + + @Rule + public SlingContext context = new SlingContextBuilder(ResourceResolverType.JCR_MOCK) + .plugin(new RegisterFsResourcePlugin()) + .build(); + + @Before + public void setUp() throws RepositoryException { + root = context.resourceResolver().getResource("/"); + fsroot = context.resourceResolver().getResource("/fs-test"); + + // prepare mixed JCR content + Node node = root.adaptTo(Node.class); + Node fstest = node.addNode("fs-test", "nt:folder"); + // folder1 + Node folder1 = fstest.addNode("folder1", "nt:folder"); + folder1.setProperty("prop1", "value1"); + folder1.setProperty("prop2", 123L); + // folder1/file1a.txt + Node file1a = folder1.addNode("file1a.txt", "nt:file"); + file1a.setProperty("prop1", "value2"); + file1a.setProperty("prop2", 234L); + // folder1/file1c.txt + folder1.addNode("file1c.txt", "nt:file"); + // folder99 + fstest.addNode("folder99", "nt:folder"); + } + + @Test + public void testFolders() { + // expected properties from JCR for folders + Resource folder1 = fsroot.getChild("folder1"); + assertThat(folder1, ResourceMatchers.props("jcr:primaryType", "nt:folder", + "prop1", "value1", + "prop2", 123L)); + } + + @Test + public void testFiles() { + assertFile(fsroot, "folder1/file1a.txt", "file1a"); + assertFile(fsroot, "folder1/file1b.txt", "file1b"); + assertFile(fsroot, "folder1/folder11/file11a.txt", "file11a"); + assertFile(fsroot, "folder2/content.json", null); + + // do not expected properties from JCR for files + Resource file1a = fsroot.getChild("folder1/file1a.txt"); + assertThat(file1a, not(ResourceMatchers.props( + "prop1", "value2", + "prop2", 234L))); + } + + @Test + public void testListChildren() { + assertThat(root, ResourceMatchers.containsChildren("fs-test")); + assertThat(fsroot, ResourceMatchers.hasChildren("folder1", "folder2", "folder99")); + assertThat(fsroot.getChild("folder1"), ResourceMatchers.hasChildren("file1a.txt", "file1b.txt", "file1c.txt")); + } + +} diff --git a/fsresource-1.x/src/test/java/org/apache/sling/fsprovider/internal/JcrXmlContentTest.java b/fsresource-1.x/src/test/java/org/apache/sling/fsprovider/internal/JcrXmlContentTest.java new file mode 100644 index 0000000..7bb6cee --- /dev/null +++ b/fsresource-1.x/src/test/java/org/apache/sling/fsprovider/internal/JcrXmlContentTest.java @@ -0,0 +1,173 @@ +/* + * 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.fsprovider.internal; + +import static org.apache.sling.fsprovider.internal.TestUtils.assertFile; +import static org.apache.sling.fsprovider.internal.TestUtils.assertFolder; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThat; + +import java.util.Collections; +import java.util.List; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; + +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ResourceUtil; +import org.apache.sling.api.resource.ValueMap; +import org.apache.sling.fsprovider.internal.TestUtils.RegisterFsResourcePlugin; +import org.apache.sling.hamcrest.ResourceMatchers; +import org.apache.sling.testing.mock.sling.ResourceResolverType; +import org.apache.sling.testing.mock.sling.junit.SlingContext; +import org.apache.sling.testing.mock.sling.junit.SlingContextBuilder; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Rule; +import org.junit.Test; + +import com.google.common.collect.Lists; + +/** + * Test access to files and folders from file system. + */ +public class JcrXmlContentTest { + + private Resource root; + private Resource fsroot; + + @Rule + public SlingContext context = new SlingContextBuilder(ResourceResolverType.JCR_MOCK) + .plugin(new RegisterFsResourcePlugin( + "provider.fs.mode", FsMode.INITIAL_CONTENT.name(), + "provider.initial.content.import.options", "overwrite:=true;ignoreImportProviders:=json" + )) + .build(); + + @Before + public void setUp() { + root = context.resourceResolver().getResource("/"); + fsroot = context.resourceResolver().getResource("/fs-test"); + } + + @Test + public void testFolders() { + assertFolder(fsroot, "folder1"); + assertFolder(fsroot, "folder1/folder11"); + assertFolder(fsroot, "folder2"); + assertFolder(fsroot, "folder3"); + } + + @Test + public void testFiles() { + assertFile(fsroot, "folder1/file1a.txt", "file1a"); + assertFile(fsroot, "folder1/file1b.txt", "file1b"); + assertFile(fsroot, "folder1/folder11/file11a.txt", "file11a"); + assertFile(fsroot, "folder2/content.json", null); + assertNull(fsroot.getChild("folder3/content.jcr.xml")); + } + + @Test + public void testListChildren() { + assertThat(root, ResourceMatchers.containsChildren("fs-test")); + assertThat(fsroot, ResourceMatchers.hasChildren("folder1", "folder2")); + assertThat(fsroot.getChild("folder1"), ResourceMatchers.hasChildren("folder11", "file1a.txt", "file1b.txt")); + assertThat(fsroot.getChild("folder2"), ResourceMatchers.hasChildren("folder21", "content")); + } + + @Test + public void testContent_Root() { + Resource underTest = fsroot.getChild("folder3/content"); + assertNotNull(underTest); + assertEquals("app:Page", ResourceUtil.getValueMap(underTest).get("jcr:primaryType", String.class)); + assertEquals("app:Page", underTest.getResourceType()); + assertThat(underTest, ResourceMatchers.hasChildren("jcr:content")); + } + + @Test + public void testContent_Level1() { + Resource underTest = fsroot.getChild("folder3/content/jcr:content"); + assertNotNull(underTest); + assertEquals("app:PageContent", ResourceUtil.getValueMap(underTest).get("jcr:primaryType", String.class)); + assertEquals("samples/sample-app/components/content/page/homepage", underTest.getResourceType()); + assertThat(underTest, ResourceMatchers.hasChildren("teaserbar", "aside", "content")); + } + + @Test + public void testContent_Level3() { + Resource underTest = fsroot.getChild("folder3/content/jcr:content/content/contentheadline"); + assertNotNull(underTest); + assertEquals("nt:unstructured", ResourceUtil.getValueMap(underTest).get("jcr:primaryType", String.class)); + assertEquals("samples/sample-app/components/content/common/contentHeadline", underTest.getResourceType()); + assertFalse(underTest.listChildren().hasNext()); + } + + @Test + public void testContent_Datatypes() { + Resource underTest = fsroot.getChild("folder3/content/jcr:content"); + ValueMap props = ResourceUtil.getValueMap(underTest); + + assertEquals("en", props.get("jcr:title", String.class)); + assertEquals(true, props.get("includeAside", false)); + assertEquals((Long)1234567890123L, props.get("longProp", Long.class)); + assertEquals((Double)1.2345d, props.get("decimalProp", Double.class), 0.00001d); + + assertArrayEquals(new String[] { "aa", "bb", "cc" }, props.get("stringPropMulti", String[].class)); + assertArrayEquals(new Long[] { 1234567890123L, 55L }, props.get("longPropMulti", Long[].class)); + } + + @Test + public void testContent_InvalidPath() { + Resource underTest = fsroot.getChild("folder2/content/jcr:content/xyz"); + assertNull(underTest); + } + + @Test + @Ignore // jcr overlay is always active with the old sling resource provider API + public void testJcrMixedContent() throws RepositoryException { + // prepare mixed JCR content + Node node = root.adaptTo(Node.class); + Node fstest = node.addNode("fs-test", "nt:folder"); + fstest.addNode("folder99", "nt:folder"); + + assertNull(fsroot.getChild("folder99")); + } + + @Test + public void testFolder3ChildNodes() throws RepositoryException { + Resource folder3 = fsroot.getChild("folder3"); + List children = Lists.newArrayList(folder3.listChildren()); + Collections.sort(children, new ResourcePathComparator()); + + assertEquals(2, children.size()); + Resource child1 = children.get(0); + assertEquals("content", child1.getName()); + assertEquals("app:Page", child1.getResourceType()); + assertEquals("app:Page", ResourceUtil.getValueMap(child1).get("jcr:primaryType", String.class)); + + Resource child2 = children.get(1); + assertEquals("folder31", child2.getName()); + assertEquals("nt:folder", ResourceUtil.getValueMap(child2).get("jcr:primaryType", String.class)); + } + +} diff --git a/fsresource-1.x/src/test/java/org/apache/sling/fsprovider/internal/JsonContentTest.java b/fsresource-1.x/src/test/java/org/apache/sling/fsprovider/internal/JsonContentTest.java new file mode 100644 index 0000000..553080f --- /dev/null +++ b/fsresource-1.x/src/test/java/org/apache/sling/fsprovider/internal/JsonContentTest.java @@ -0,0 +1,288 @@ +/* + * 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.fsprovider.internal; + +import static org.apache.sling.fsprovider.internal.TestUtils.assertFile; +import static org.apache.sling.fsprovider.internal.TestUtils.assertFolder; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +import java.math.BigDecimal; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import javax.jcr.Node; +import javax.jcr.NodeIterator; +import javax.jcr.PropertyIterator; +import javax.jcr.PropertyType; +import javax.jcr.RepositoryException; +import javax.jcr.Value; +import javax.jcr.nodetype.NodeType; + +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ResourceUtil; +import org.apache.sling.api.resource.ValueMap; +import org.apache.sling.fsprovider.internal.TestUtils.RegisterFsResourcePlugin; +import org.apache.sling.hamcrest.ResourceMatchers; +import org.apache.sling.jcr.contentparser.ParserOptions; +import org.apache.sling.testing.mock.sling.ResourceResolverType; +import org.apache.sling.testing.mock.sling.junit.SlingContext; +import org.apache.sling.testing.mock.sling.junit.SlingContextBuilder; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Rule; +import org.junit.Test; + +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Lists; + +/** + * Test access to files and folders and JSON content from file system. + */ +public class JsonContentTest { + + private Resource root; + private Resource fsroot; + + @Rule + public SlingContext context = new SlingContextBuilder(ResourceResolverType.JCR_MOCK) + .plugin(new RegisterFsResourcePlugin( + "provider.fs.mode", FsMode.INITIAL_CONTENT.name(), + "provider.initial.content.import.options", "overwrite:=true;ignoreImportProviders:=jcr.xml" + )) + .build(); + + @Before + public void setUp() { + root = context.resourceResolver().getResource("/"); + fsroot = context.resourceResolver().getResource("/fs-test"); + } + + @Test + public void testFolders() { + assertFolder(fsroot, "folder1"); + assertFolder(fsroot, "folder1/folder11"); + assertFolder(fsroot, "folder2"); + } + + @Test + public void testFiles() { + assertFile(fsroot, "folder1/file1a.txt", "file1a"); + assertFile(fsroot, "folder1/file1b.txt", "file1b"); + assertFile(fsroot, "folder1/folder11/file11a.txt", "file11a"); + assertNull(fsroot.getChild("folder2/content.json")); + assertFile(fsroot, "folder2/content/file2content.txt", "file2content"); + assertFile(fsroot, "folder3/content.jcr.xml", null); + } + + @Test + public void testListChildren() { + assertThat(root, ResourceMatchers.containsChildren("fs-test")); + assertThat(fsroot, ResourceMatchers.hasChildren("folder1", "folder2")); + assertThat(fsroot.getChild("folder1"), ResourceMatchers.hasChildren("folder11", "file1a.txt", "file1b.txt")); + assertThat(fsroot.getChild("folder2"), ResourceMatchers.hasChildren("folder21", "content")); + } + + @Test + public void testContent_Root() { + Resource underTest = fsroot.getChild("folder2/content"); + assertNotNull(underTest); + assertEquals("app:Page", ResourceUtil.getValueMap(underTest).get("jcr:primaryType", String.class)); + assertEquals("app:Page", underTest.getResourceType()); + assertThat(underTest, ResourceMatchers.hasChildren("jcr:content")); + } + + @Test + public void testContent_Level1() { + Resource underTest = fsroot.getChild("folder2/content/jcr:content"); + assertNotNull(underTest); + assertEquals("app:PageContent", ResourceUtil.getValueMap(underTest).get("jcr:primaryType", String.class)); + assertEquals("sample/components/homepage", underTest.getResourceType()); + assertEquals("sample/components/supertype", underTest.getResourceSuperType()); + assertThat(underTest, ResourceMatchers.hasChildren("par", "header", "newslist", "lead", "image", "carousel", "rightpar")); + } + + @Test + public void testContent_Level5() { + Resource underTest = fsroot.getChild("folder2/content/jcr:content/par/image/file/jcr:content"); + assertNotNull(underTest); + assertEquals("nt:resource", ResourceUtil.getValueMap(underTest).get("jcr:primaryType", String.class)); + assertFalse(underTest.listChildren().hasNext()); + } + + @Test + public void testContent_Datatypes() { + Resource underTest = fsroot.getChild("folder2/content/toolbar/profiles/jcr:content"); + ValueMap props = ResourceUtil.getValueMap(underTest); + + assertEquals("Profiles", props.get("jcr:title", String.class)); + assertEquals(true, props.get("booleanProp", false)); + assertEquals((Long)1234567890123L, props.get("longProp", Long.class)); + assertEquals((Double)1.2345d, props.get("decimalProp", Double.class), 0.00001d); + assertEquals(new BigDecimal("1.2345"), props.get("decimalProp", BigDecimal.class)); + + assertArrayEquals(new String[] { "aa", "bb", "cc" }, props.get("stringPropMulti", String[].class)); + assertArrayEquals(new Long[] { 1234567890123L, 55L }, props.get("longPropMulti", Long[].class)); + } + + @Test + public void testContent_Datatypes_JCR() throws RepositoryException { + Resource underTest = fsroot.getChild("folder2/content/toolbar/profiles/jcr:content"); + ValueMap props = ResourceUtil.getValueMap(underTest); + Node node = underTest.adaptTo(Node.class); + + assertEquals("/fs-test/folder2/content/toolbar/profiles/jcr:content", node.getPath()); + assertEquals(6, node.getDepth()); + + assertTrue(node.hasProperty("jcr:title")); + assertEquals(PropertyType.STRING, node.getProperty("jcr:title").getType()); + assertFalse(node.getProperty("jcr:title").isMultiple()); + assertEquals("jcr:title", node.getProperty("jcr:title").getDefinition().getName()); + assertEquals("/fs-test/folder2/content/toolbar/profiles/jcr:content/jcr:title", node.getProperty("jcr:title").getPath()); + assertEquals("Profiles", node.getProperty("jcr:title").getString()); + assertEquals(PropertyType.BOOLEAN, node.getProperty("booleanProp").getType()); + assertEquals(true, node.getProperty("booleanProp").getBoolean()); + assertEquals(PropertyType.LONG, node.getProperty("longProp").getType()); + assertEquals(1234567890123L, node.getProperty("longProp").getLong()); + assertEquals(PropertyType.DECIMAL, node.getProperty("decimalProp").getType()); + assertEquals(1.2345d, node.getProperty("decimalProp").getDouble(), 0.00001d); + assertEquals(new BigDecimal("1.2345"), node.getProperty("decimalProp").getDecimal()); + + assertEquals(PropertyType.STRING, node.getProperty("stringPropMulti").getType()); + assertTrue(node.getProperty("stringPropMulti").isMultiple()); + Value[] stringPropMultiValues = node.getProperty("stringPropMulti").getValues(); + assertEquals(3, stringPropMultiValues.length); + assertEquals("aa", stringPropMultiValues[0].getString()); + assertEquals("bb", stringPropMultiValues[1].getString()); + assertEquals("cc", stringPropMultiValues[2].getString()); + + assertEquals(PropertyType.LONG, node.getProperty("longPropMulti").getType()); + assertTrue(node.getProperty("longPropMulti").isMultiple()); + Value[] longPropMultiValues = node.getProperty("longPropMulti").getValues(); + assertEquals(2, longPropMultiValues.length); + assertEquals(1234567890123L, longPropMultiValues[0].getLong()); + assertEquals(55L, longPropMultiValues[1].getLong()); + + // assert property iterator + Set propertyNames = new HashSet<>(); + PropertyIterator propertyIterator = node.getProperties(); + while (propertyIterator.hasNext()) { + propertyNames.add(propertyIterator.nextProperty().getName()); + } + assertTrue(props.keySet().containsAll(propertyNames)); + + // assert node iterator + Set nodeNames = new HashSet<>(); + NodeIterator nodeIterator = node.getNodes(); + while (nodeIterator.hasNext()) { + nodeNames.add(nodeIterator.nextNode().getName()); + } + assertEquals(ImmutableSet.of("par", "rightpar"), nodeNames); + + // node hierarchy + assertTrue(node.hasNode("rightpar")); + Node rightpar = node.getNode("rightpar"); + assertEquals(7, rightpar.getDepth()); + Node parent = rightpar.getParent(); + assertTrue(node.isSame(parent)); + Node ancestor = (Node)rightpar.getAncestor(5); + assertEquals(underTest.getParent().getPath(), ancestor.getPath()); + Node root = (Node)rightpar.getAncestor(0); + assertEquals("/", root.getPath()); + + // node types + assertTrue(node.isNodeType("app:PageContent")); + assertEquals("app:PageContent", node.getPrimaryNodeType().getName()); + assertFalse(node.getPrimaryNodeType().isMixin()); + NodeType[] mixinTypes = node.getMixinNodeTypes(); + assertEquals(2, mixinTypes.length); + assertEquals("type1", mixinTypes[0].getName()); + assertEquals("type2", mixinTypes[1].getName()); + assertTrue(mixinTypes[0].isMixin()); + assertTrue(mixinTypes[1].isMixin()); + } + + @Test + public void testFallbackNodeType() throws RepositoryException { + Resource underTest = fsroot.getChild("folder2/content/jcr:content/par/title_2"); + assertEquals(ParserOptions.DEFAULT_PRIMARY_TYPE, underTest.adaptTo(Node.class).getPrimaryNodeType().getName()); + } + + @Test + public void testContent_InvalidPath() { + Resource underTest = fsroot.getChild("folder2/content/jcr:content/xyz"); + assertNull(underTest); + } + + @Test + @Ignore // jcr overlay is always active with the old sling resource provider API + public void testJcrMixedContent() throws RepositoryException { + // prepare mixed JCR content + Node node = root.adaptTo(Node.class); + Node fstest = node.addNode("fs-test", "nt:folder"); + fstest.addNode("folder99", "nt:folder"); + + assertNull(fsroot.getChild("folder99")); + } + + @Test + public void testFolder2ChildNodes() throws RepositoryException { + Resource folder2 = fsroot.getChild("folder2"); + List children = Lists.newArrayList(folder2.listChildren()); + Collections.sort(children, new ResourcePathComparator()); + + assertEquals(2, children.size()); + Resource child1 = children.get(0); + assertEquals("content", child1.getName()); + assertEquals("app:Page", child1.getResourceType()); + assertEquals("app:Page", ResourceUtil.getValueMap(child1).get("jcr:primaryType", String.class)); + + Resource child2 = children.get(1); + assertEquals("folder21", child2.getName()); + assertEquals("sling:OrderedFolder", ResourceUtil.getValueMap(child2).get("jcr:primaryType", String.class)); + } + + @Test + public void testFile21aNodeDescriptor() throws RepositoryException { + Resource file21a = fsroot.getChild("folder2/folder21/file21a.txt"); + assertEquals("nt:file", file21a.getResourceType()); + assertEquals("/my/super/type", file21a.getResourceSuperType()); + + ValueMap props = ResourceUtil.getValueMap(file21a); + assertEquals("nt:file", props.get("jcr:primaryType", String.class)); + assertEquals("/my/super/type", props.get("sling:resourceSuperType", String.class)); + assertEquals("en", props.get("jcr:language", String.class)); + assertArrayEquals(new String[] { "mix:language" }, props.get("jcr:mixinTypes", String[].class)); + + assertNull(fsroot.getChild("folder2/folder21/file21a.txt.xml")); + + Node node = file21a.adaptTo(Node.class); + assertNotNull(node); + assertEquals("/my/super/type", node.getProperty("sling:resourceSuperType").getString()); + assertEquals("en", node.getProperty("jcr:language").getString()); + } + +} diff --git a/fsresource-1.x/src/test/java/org/apache/sling/fsprovider/internal/ResourcePathComparator.java b/fsresource-1.x/src/test/java/org/apache/sling/fsprovider/internal/ResourcePathComparator.java new file mode 100644 index 0000000..f1c6272 --- /dev/null +++ b/fsresource-1.x/src/test/java/org/apache/sling/fsprovider/internal/ResourcePathComparator.java @@ -0,0 +1,32 @@ +/* + * 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.fsprovider.internal; + +import java.util.Comparator; + +import org.apache.sling.api.resource.Resource; + +class ResourcePathComparator implements Comparator { + + @Override + public int compare(Resource o1, Resource o2) { + return o1.getPath().compareTo(o2.getPath()); + } + +} diff --git a/fsresource-1.x/src/test/java/org/apache/sling/fsprovider/internal/TestUtils.java b/fsresource-1.x/src/test/java/org/apache/sling/fsprovider/internal/TestUtils.java new file mode 100644 index 0000000..d077b34 --- /dev/null +++ b/fsresource-1.x/src/test/java/org/apache/sling/fsprovider/internal/TestUtils.java @@ -0,0 +1,130 @@ +/* + * 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.fsprovider.internal; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.CharEncoding; +import org.apache.commons.lang3.StringUtils; +import org.apache.sling.api.SlingConstants; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.fsprovider.internal.FileMonitor.ResourceChange; +import org.apache.sling.hamcrest.ResourceMatchers; +import org.apache.sling.testing.mock.osgi.MapUtil; +import org.apache.sling.testing.mock.osgi.context.AbstractContextPlugin; +import org.apache.sling.testing.mock.sling.context.SlingContextImpl; +import org.osgi.service.event.Event; +import org.osgi.service.event.EventHandler; + +class TestUtils { + + public static class RegisterFsResourcePlugin extends AbstractContextPlugin { + private final Map props; + public RegisterFsResourcePlugin(Object... props) { + this.props = MapUtil.toMap(props); + } + @Override + public void beforeSetUp(SlingContextImpl context) throws Exception { + Map config = new HashMap<>(); + config.put("provider.file", "src/test/resources/fs-test"); + config.put("provider.roots", "/fs-test"); + config.put("provider.checkinterval", 0); + config.put("provider.fs.mode", FsMode.FILES_FOLDERS.name()); + config.putAll(props); + context.registerInjectActivateService(new FsResourceProvider(), config); + } + }; + + public static void assertFolder(Resource resource, String path) { + Resource folder = resource.getChild(path); + assertNotNull(path, folder); + + assertThat(folder, ResourceMatchers.props("jcr:primaryType", "nt:folder")); + assertEquals("nt:folder", folder.getResourceType()); + + assertNull(folder.getResourceSuperType()); + assertEquals(folder.getName(), folder.adaptTo(File.class).getName()); + assertTrue(StringUtils.contains(folder.adaptTo(URL.class).toString(), folder.getName())); + } + + public static void assertFile(Resource resource, String path, String content) { + Resource file = resource.getChild(path); + assertNotNull(path, file); + + assertThat(file, ResourceMatchers.props("jcr:primaryType", "nt:file")); + assertEquals("nt:file", file.getResourceType()); + + assertNull(file.getResourceSuperType()); + assertEquals(file.getName(), file.adaptTo(File.class).getName()); + assertTrue(StringUtils.contains(file.adaptTo(URL.class).toString(), file.getName())); + + if (content != null) { + try { + try (InputStream is = file.adaptTo(InputStream.class)) { + String data = IOUtils.toString(is, CharEncoding.UTF_8); + assertEquals(content, data); + } + } + catch (IOException ex) { + throw new RuntimeException(ex); + } + } + } + + public static void assertChange(List changes, String path, String topic) { + boolean found = false; + for (ResourceChange change : changes) { + if (StringUtils.equals(change.path, path) && StringUtils.equals(change.topic, topic)) { + found = true; + break; + } + } + assertTrue("Change with path=" + path + ", topic=" + topic + " expected", found); + } + + public static class ResourceListener implements EventHandler { + private final List allChanges = new ArrayList<>(); + public List getChanges() { + return allChanges; + } + @Override + public void handleEvent(Event event) { + ResourceChange change = new ResourceChange(); + change.path = (String)event.getProperty(SlingConstants.PROPERTY_PATH); + change.resourceType = (String)event.getProperty(SlingConstants.PROPERTY_RESOURCE_TYPE); + change.topic = event.getTopic(); + allChanges.add(change); + } + } + +} diff --git a/fsresource-1.x/src/test/java/org/apache/sling/fsprovider/internal/mapper/ContentFileTest.java b/fsresource-1.x/src/test/java/org/apache/sling/fsprovider/internal/mapper/ContentFileTest.java new file mode 100644 index 0000000..3a6a747 --- /dev/null +++ b/fsresource-1.x/src/test/java/org/apache/sling/fsprovider/internal/mapper/ContentFileTest.java @@ -0,0 +1,108 @@ +/* + * 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.fsprovider.internal.mapper; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.io.File; + +import org.apache.sling.api.resource.ValueMap; +import org.apache.sling.fsprovider.internal.parser.ContentElement; +import org.apache.sling.fsprovider.internal.parser.ContentFileCache; +import org.junit.Test; + +public class ContentFileTest { + + private ContentFileCache contentFileCache = new ContentFileCache(0); + + @Test + public void testRootContent() { + File file = new File("src/test/resources/fs-test/folder2/content.json"); + + ContentFile underTest = new ContentFile(file, "/fs-test/folder2/content", null, contentFileCache); + assertEquals(file, underTest.getFile()); + assertNull(underTest.getSubPath()); + + assertTrue(underTest.hasContent()); + + ContentElement content = underTest.getContent(); + assertEquals("app:Page", content.getProperties().get("jcr:primaryType")); + assertEquals("app:PageContent", content.getChild("jcr:content").getProperties().get("jcr:primaryType")); + + ValueMap props = underTest.getValueMap(); + assertEquals("app:Page", props.get("jcr:primaryType")); + assertNull(props.get("jcr:content")); + } + + @Test + public void testContentLevel1() { + File file = new File("src/test/resources/fs-test/folder2/content.json"); + + ContentFile underTest = new ContentFile(file, "/fs-test/folder2/content", "jcr:content", contentFileCache); + assertEquals(file, underTest.getFile()); + assertEquals("jcr:content", underTest.getSubPath()); + + assertTrue(underTest.hasContent()); + + ContentElement content = underTest.getContent(); + assertEquals("app:PageContent", content.getProperties().get("jcr:primaryType")); + + ValueMap props = underTest.getValueMap(); + assertEquals("app:PageContent", props.get("jcr:primaryType")); + } + + @Test + public void testContentLevel5() { + File file = new File("src/test/resources/fs-test/folder2/content.json"); + + ContentFile underTest = new ContentFile(file, "/fs-test/folder2/content", "jcr:content/par/image/file/jcr:content", contentFileCache); + assertEquals(file, underTest.getFile()); + assertEquals("jcr:content/par/image/file/jcr:content", underTest.getSubPath()); + + assertTrue(underTest.hasContent()); + + ContentElement content = underTest.getContent(); + assertEquals("nt:resource", content.getProperties().get("jcr:primaryType")); + + ValueMap props = underTest.getValueMap(); + assertEquals("nt:resource", props.get("jcr:primaryType")); + } + + @Test + public void testContentProperty() { + File file = new File("src/test/resources/fs-test/folder2/content.json"); + + ContentFile underTest = new ContentFile(file, "/fs-test/folder2/content", "jcr:content/jcr:title", contentFileCache); + assertEquals(file, underTest.getFile()); + assertEquals("jcr:content/jcr:title", underTest.getSubPath()); + + assertFalse(underTest.hasContent()); + } + + @Test + public void testInvalidFile() { + File file = new File("src/test/resources/fs-test/folder1/file1a.txt"); + ContentFile underTest = new ContentFile(file, "/fs-test/folder1/file1a", null, contentFileCache); + assertFalse(underTest.hasContent()); + } + +} diff --git a/fsresource-1.x/src/test/java/org/apache/sling/fsprovider/internal/mapper/valuemap/Convert.java b/fsresource-1.x/src/test/java/org/apache/sling/fsprovider/internal/mapper/valuemap/Convert.java new file mode 100644 index 0000000..15657cc --- /dev/null +++ b/fsresource-1.x/src/test/java/org/apache/sling/fsprovider/internal/mapper/valuemap/Convert.java @@ -0,0 +1,242 @@ +/* + * 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.fsprovider.internal.mapper.valuemap; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import java.lang.reflect.Array; +import java.util.Calendar; +import java.util.Date; + +import org.apache.commons.lang3.ClassUtils; + +/** + * This is copied from org.apache.sling.api.wrappers.impl.Convert + */ +final class Convert { + + private Convert() { + // static methods only + } + + @SuppressWarnings("unchecked") + public static class ConversionAssert { + private final T input1; + private final T input2; + private Class inputType; + private U expected1; + private U expected2; + private U nullValue; + private Class expectedType; + + private ConversionAssert(T input1, T input2, boolean inputTypePrimitive) { + this.input1 = input1; + this.input2 = input2; + this.inputType = (Class)input1.getClass(); + if (inputTypePrimitive) { + this.inputType = (Class)ClassUtils.wrapperToPrimitive(this.inputType); + } + } + + private void expected(U expected1, U expected2, boolean expectedTypePrimitive) { + this.expected1 = expected1; + this.expected2 = expected2; + this.expectedType = (Class)expected1.getClass(); + if (expectedTypePrimitive) { + expectedType = (Class)ClassUtils.wrapperToPrimitive(this.expectedType); + } + } + + /** + * @param expected1 Singleton or first array expected result value + * @param expected2 Second array expected result value + * @return this + */ + public ConversionAssert to(U expected1, U expected2) { + expected(expected1, expected2, false); + return this; + } + + /** + * @param expected1 Singleton or first array expected result value + * @param expected2 Second array expected result value + * @return this + */ + public ConversionAssert toPrimitive(U expected1, U expected2) { + expected(expected1, expected2, true); + return this; + } + + /** + * @param expected1 Singleton or first array expected result value + * @param expected2 Second array expected result value + * @return this + */ + public ConversionAssert toNull(Class expectedType) { + expected1 = null; + expected2 = null; + this.expectedType = expectedType; + return this; + } + + /** + * @param nullValue Result value in case of null + */ + public ConversionAssert nullValue(U nullValue) { + this.nullValue = nullValue; + return this; + } + + /** + * Do assertion + */ + public void test() { + Class expectedArrayType = (Class)Array.newInstance(this.expectedType, 0).getClass(); + assertPermuations(input1, input2, inputType, expected1, expected2, nullValue, expectedType, expectedArrayType); + } + } + + /** + * @param input1 Singleton or first array input value + * @param input2 Second array input value + */ + public static ConversionAssert from(T input1, T input2) { + return new ConversionAssert(input1, input2, false); + } + + /** + * @param input1 Singleton or first array input value + * @param input2 Second array input value + */ + public static ConversionAssert fromPrimitive(T input1, T input2) { + return new ConversionAssert(input1, input2, true); + } + + private static void assertPermuations(T input1, T input2, Class inputType, + U expected1, U expected2, U nullValue, Class expectedType, Class expectedArrayType) { + + // single value to single value + assertConversion(expected1, input1, expectedType); + + // single value to array + Object expectedSingletonArray; + if (expected1 == null && expected2 == null) { + expectedSingletonArray = Array.newInstance(expectedType, 0); + } + else { + expectedSingletonArray = Array.newInstance(expectedType, 1); + Array.set(expectedSingletonArray, 0, expected1); + } + assertConversion(expectedSingletonArray, input1, expectedArrayType); + + // array to array + Object inputDoubleArray = Array.newInstance(inputType, 2); + Array.set(inputDoubleArray, 0, input1); + Array.set(inputDoubleArray, 1, input2); + Object expectedDoubleArray; + if (expected1 == null && expected2 == null) { + expectedDoubleArray = Array.newInstance(expectedType, 0); + } + else { + expectedDoubleArray = Array.newInstance(expectedType, 2); + Array.set(expectedDoubleArray, 0, expected1); + Array.set(expectedDoubleArray, 1, expected2); + } + assertConversion(expectedDoubleArray, inputDoubleArray, expectedArrayType); + + // array to single (first) value + assertConversion(expected1, inputDoubleArray, expectedType); + + // null to single value + assertConversion(nullValue, null, expectedType); + + // null to array + assertConversion(null, null, expectedArrayType); + + // empty array to single value + Object inputEmptyArray = Array.newInstance(inputType, 0); + assertConversion(nullValue, inputEmptyArray, expectedType); + + // empty array to array + Object expectedEmptyArray = Array.newInstance(expectedType, 0); + assertConversion(expectedEmptyArray, inputEmptyArray, expectedArrayType); + } + + @SuppressWarnings("unchecked") + private static void assertConversion(Object expected, Object input, Class type) { + U result = ObjectConverter.convert(input, type); + String msg = "Convert '" + toString(input) + "' to " + type.getSimpleName(); + if (expected == null) { + assertNull(msg, result); + } + else if (expected.getClass().isArray()) { + assertArrayEquals(msg, (U[])toStringIfDate(expected), (U[])toStringIfDate(result)); + } + else { + assertEquals(msg, toStringIfDate(expected), toStringIfDate(result)); + } + } + + private static String toString(Object input) { + if (input == null) { + return "null"; + } + else if (input.getClass().isArray()) { + StringBuilder sb = new StringBuilder(); + sb.append("["); + for (int i=0; i 0) { + sb.append(","); + } + sb.append(toString(Array.get(input, i))); + } + sb.append("]"); + return sb.toString(); + } + else { + return toStringIfDate(input).toString(); + } + } + + private static Object toStringIfDate(Object input) { + if (input == null) { + return null; + } + if (input instanceof Calendar) { + return "(Calendar)" + DateUtils.calendarToString((Calendar)input); + } + if (input instanceof Date) { + return "(Date)" + DateUtils.dateToString((Date)input); + } + if (input.getClass().isArray()) { + if (Calendar.class.isAssignableFrom(input.getClass().getComponentType()) + || input.getClass().getComponentType() == Date.class) { + Object[] resultArray = new String[Array.getLength(input)]; + for (int i=0; ifrom(INT_1, INT_2).toNull(Boolean.class).test(); + Convert.from(DATE_1, DATE_2).toNull(Boolean.class).test(); + } + + @Test + public void testToByte() { + Convert.fromPrimitive(BYTE_1, BYTE_2).to(BYTE_1, BYTE_2).test(); + Convert.from(BYTE_1, BYTE_2).to(BYTE_1, BYTE_2).test(); + Convert.from(Byte.toString(BYTE_1), Byte.toString(BYTE_2)).to(BYTE_1, BYTE_2).test(); + + // test conversion from other number types + Convert.from(INT_1, INT_2).to((byte)INT_1, (byte)INT_2).test(); + Convert.fromPrimitive(INT_1, INT_2).to((byte)INT_1, (byte)INT_2).test(); + + // test other types that should not be converted + Convert.from(DATE_1, DATE_2).toNull(Byte.class).test(); + } + + @Test + public void testToShort() { + Convert.fromPrimitive(SHORT_1, SHORT_2).to(SHORT_1, SHORT_2).test(); + Convert.from(SHORT_1, SHORT_2).to(SHORT_1, SHORT_2).test(); + Convert.from(Short.toString(SHORT_1), Short.toString(SHORT_2)).to(SHORT_1, SHORT_2).test(); + + // test conversion from other number types + Convert.from(INT_1, INT_2).to((short)INT_1, (short)INT_2).test(); + Convert.fromPrimitive(INT_1, INT_2).to((short)INT_1, (short)INT_2).test(); + + // test other types that should not be converted + Convert.from(DATE_1, DATE_2).toNull(Short.class).test(); + } + + @Test + public void testToInteger() { + Convert.fromPrimitive(INT_1, INT_2).to(INT_1, INT_2).test(); + Convert.from(INT_1, INT_2).to(INT_1, INT_2).test(); + Convert.from(Integer.toString(INT_1), Integer.toString(INT_2)).to(INT_1, INT_2).test(); + + // test conversion from other number types + Convert.from(SHORT_1, SHORT_2).to((int)SHORT_1, (int)SHORT_2).test(); + Convert.fromPrimitive(SHORT_1, SHORT_2).to((int)SHORT_1, (int)SHORT_2).test(); + + // test other types that should not be converted + Convert.from(DATE_1, DATE_2).toNull(Integer.class).test(); + } + + @Test + public void testToLong() { + Convert.fromPrimitive(LONG_1, LONG_2).to(LONG_1, LONG_2).test(); + Convert.from(LONG_1, LONG_2).to(LONG_1, LONG_2).test(); + Convert.from(Long.toString(LONG_1), Long.toString(LONG_2)).to(LONG_1, LONG_2).test(); + + // test conversion from other number types + Convert.from(SHORT_1, SHORT_2).to((long)SHORT_1, (long)SHORT_2).test(); + Convert.fromPrimitive(SHORT_1, SHORT_2).to((long)SHORT_1, (long)SHORT_2).test(); + + // test other types that should not be converted + Convert.from(DATE_1, DATE_2).toNull(Long.class).test(); + } + + @Test + public void testToFloat() { + Convert.fromPrimitive(FLOAT_1, FLOAT_2).to(FLOAT_1, FLOAT_2).test(); + Convert.from(FLOAT_1, FLOAT_2).to(FLOAT_1, FLOAT_2).test(); + Convert.from(Float.toString(FLOAT_1), Float.toString(FLOAT_2)).to(FLOAT_1, FLOAT_2).test(); + + // test conversion from other number types + Convert.from(SHORT_1, SHORT_2).to((float)SHORT_1, (float)SHORT_2).test(); + Convert.fromPrimitive(SHORT_1, SHORT_2).to((float)SHORT_1, (float)SHORT_2).test(); + + // test other types that should not be converted + Convert.from(DATE_1, DATE_2).toNull(Float.class).test(); + } + + @Test + public void testToDouble() { + Convert.fromPrimitive(DOUBLE_1, DOUBLE_2).to(DOUBLE_1, DOUBLE_2).test(); + Convert.from(DOUBLE_1, DOUBLE_2).to(DOUBLE_1, DOUBLE_2).test(); + Convert.from(Double.toString(DOUBLE_1), Double.toString(DOUBLE_2)).to(DOUBLE_1, DOUBLE_2).test(); + + // test conversion from other number types + Convert.from(SHORT_1, SHORT_2).to((double)SHORT_1, (double)SHORT_2).test(); + Convert.fromPrimitive(SHORT_1, SHORT_2).to((double)SHORT_1, (double)SHORT_2).test(); + + // test other types that should not be converted + Convert.from(DATE_1, DATE_2).toNull(Double.class).test(); + } + + @Test + public void testToBigDecimal() { + Convert.from(BIGDECIMAL_1, BIGDECIMAL_2).to(BIGDECIMAL_1, BIGDECIMAL_2).test(); + Convert.from(BIGDECIMAL_1.toString(), BIGDECIMAL_2.toString()).to(BIGDECIMAL_1, BIGDECIMAL_2).test(); + + // test conversion from other number types + Convert.from(LONG_1, LONG_2).to(BigDecimal.valueOf(LONG_1), BigDecimal.valueOf(LONG_2)).test(); + Convert.fromPrimitive(LONG_1, LONG_2).to(BigDecimal.valueOf(LONG_1), BigDecimal.valueOf(LONG_2)).test(); + Convert.from(DOUBLE_1, DOUBLE_2).to(BigDecimal.valueOf(DOUBLE_1), BigDecimal.valueOf(DOUBLE_2)).test(); + Convert.fromPrimitive(DOUBLE_1, DOUBLE_2).to(BigDecimal.valueOf(DOUBLE_1), BigDecimal.valueOf(DOUBLE_2)).test(); + + // test other types that should not be converted + Convert.from(DATE_1, DATE_2).toNull(BigDecimal.class).test(); + } + + @Test + public void testToCalendar() { + Convert.from(CALENDAR_1, CALENDAR_2).to(CALENDAR_1, CALENDAR_2).test(); + Convert.from(DateUtils.calendarToString(CALENDAR_1), DateUtils.calendarToString(CALENDAR_2)).to(CALENDAR_1, CALENDAR_2).test(); + + // test conversion from other date types + Convert.from(DATE_1, DATE_2).to(toCalendar(DATE_1), toCalendar(DATE_2)).test(); + + // test other types that should not be converted + Convert.from(STRING_1, STRING_2).toNull(Calendar.class).test(); + Convert.from(BOOLEAN_1, BOOLEAN_2).toNull(Calendar.class).test(); + } + + @Test + public void testToDate() { + Convert.from(DATE_1, DATE_2).to(DATE_1, DATE_2).test(); + Convert.from(DateUtils.dateToString(DATE_1), DateUtils.dateToString(DATE_2)).to(DATE_1, DATE_2).test(); + + // test conversion from other date types + Convert.from(CALENDAR_1, CALENDAR_2).to(toDate(CALENDAR_1), toDate(CALENDAR_2)).test(); + + // test other types that should not be converted + Convert.from(STRING_1, STRING_2).toNull(Date.class).test(); + Convert.from(BOOLEAN_1, BOOLEAN_2).toNull(Date.class).test(); + } + + @Test + public void testPrimitiveByteArray() { + byte[] array = new byte[] { 0x01, 0x02, 0x03 }; + assertArrayEquals(array, ObjectConverter.convert(array, byte[].class)); + assertArrayEquals(new byte[0], ObjectConverter.convert(new byte[0], byte[].class)); + assertNull(ObjectConverter.convert(null, byte[].class)); + } + +} diff --git a/fsresource-1.x/src/test/java/org/apache/sling/fsprovider/internal/mapper/valuemap/ValueMapDecoratorTest.java b/fsresource-1.x/src/test/java/org/apache/sling/fsprovider/internal/mapper/valuemap/ValueMapDecoratorTest.java new file mode 100644 index 0000000..88827e0 --- /dev/null +++ b/fsresource-1.x/src/test/java/org/apache/sling/fsprovider/internal/mapper/valuemap/ValueMapDecoratorTest.java @@ -0,0 +1,157 @@ +/* + * 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.fsprovider.internal.mapper.valuemap; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.sling.api.resource.ValueMap; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; + +/** + * This is copied from org.apache.sling.api.wrappers.ValueMapDecoratorTest + */ +public class ValueMapDecoratorTest { + + private Map map; + private ValueMap valueMap; + + @Before + public void setUp() { + map = new HashMap(); + valueMap = new ValueMapDecorator(map); + } + + // SLING-4178 + @Test + public void testIncompatibleTypeInArray() { + map.put("prop1", new String[] { "test", "test2" }); + map.put("prop2", "test"); + Assert.assertArrayEquals("Not convertible type should return empty array", new Integer[0], valueMap.get("prop1", Integer[].class)); + Assert.assertArrayEquals("Not convertible type should return empt array", new Integer[0], valueMap.get("prop2", Integer[].class)); + } + + // SLING-662 + @Test + public void testGettingArraysFromSingleValueEntries() { + map.put("prop1", "test"); + map.put("prop2", "1"); + Assert.assertArrayEquals( + "Even though the underlying entry is single-value if should be enclosed in a single element array", + new String[] { "test" }, valueMap.get("prop1", String[].class)); + Assert.assertArrayEquals( + "Even though the underlying entry is single-value if should be enclosed in a single element array", + new Integer[] { 1 }, valueMap.get("prop2", Integer[].class)); + } + + @Test + public void testGettingArraysFromMultiValueEntries() { + map.put("prop1", new String[] { "test", "test2" }); + map.put("prop2", new String[] { "1", "2" }); + Assert.assertArrayEquals("Could not get values from underlying array", new String[] { "test", "test2" }, + valueMap.get("prop1", String[].class)); + Assert.assertArrayEquals("Conversion to Integer was not possible", new Integer[] { 1, 2 }, + valueMap.get("prop2", Integer[].class)); + } + + @Test + public void testGettingSingleValuesFromMultiValueEntries() { + map.put("prop1", new String[] { "test", "test2" }); + map.put("prop2", new String[] { "1", "2" }); + Assert.assertEquals("First element from underlying array should be returned", "test", + valueMap.get("prop1", String.class)); + Assert.assertEquals("First element from underlying array should be returned", Integer.valueOf(1), + valueMap.get("prop2", Integer.class)); + } + + @Test + public void testGettingInvalidEntryWithDefaultValue() { + Assert.assertEquals(Integer.valueOf(1), valueMap.get("prop1", 1)); + Assert.assertEquals("test", valueMap.get("prop1", "test")); + } + + @Test + public void testPrimitiveTypes() { + map.put("prop1", new String[] { "1", "2" }); + Assert.assertNull("ValueMap should not support conversion to primitive type", valueMap.get("prop1", int.class)); + } + @Test(expected=ClassCastException.class) + public void testPrimitiveTypesArray() { + map.put("prop1", new String[] { "1", "2" }); + Assert.assertArrayEquals("ValueMap should not support conversion to array of primitive type", + new int[0], valueMap.get("prop1", int[].class)); + } + + @Test + public void testEqualsAndHashCodeOfEqualValueMapsWithNonArrayTypes() { + map.put("prop1", "some string"); + ValueMapDecorator valueMap2 = new ValueMapDecorator(map); + Assert.assertTrue("Two ValueMapDecorators based on the same map should be equal", valueMap.equals(valueMap2)); + Assert.assertEquals("Two equal ValueMapDecorators should have the same hash code", valueMap.hashCode(), + valueMap2.hashCode()); + + ValueMapDecorator valueMap3 = new ValueMapDecorator(new HashMap()); + valueMap3.put("prop1", "some string"); + Assert.assertEquals(valueMap, valueMap3); + Assert.assertEquals("Two equal ValueMapDecorators should have the same hash code", valueMap.hashCode(), + valueMap3.hashCode()); + + Assert.assertEquals(map, valueMap); + Assert.assertEquals(valueMap, map); + } + + @Ignore("SLING-4784") + @Test + public void testEqualsAndHashCodeOfEqualValueMapsWithArrayTypes() { + map.put("prop1", new String[] { "1", "2" }); + ValueMapDecorator valueMap2 = new ValueMapDecorator(map); + Assert.assertTrue("Two ValueMapDecorators based on the same map should be equal", valueMap.equals(valueMap2)); + Assert.assertEquals("Two equal ValueMapDecorators should have the same hash code", valueMap.hashCode(), + valueMap2.hashCode()); + + ValueMapDecorator valueMap3 = new ValueMapDecorator(new HashMap()); + valueMap3.put("prop1", new String[] { "1", "2" }); + Assert.assertEquals(valueMap, valueMap3); + Assert.assertEquals("Two equal ValueMapDecorators should have the same hash code", valueMap.hashCode(), + valueMap3.hashCode()); + } + + @Test + public void testEqualsOfInequalValueMapsWithNonArrayTypes() { + valueMap.put("prop", "value"); + ValueMapDecorator valueMap2 = new ValueMapDecorator(new HashMap()); + valueMap2.put("prop", "value2"); + Assert.assertFalse("Two ValueMapDecorators based on maps with different entries should not be equal", + valueMap.equals(valueMap2)); + + } + + @Test + public void testEqualsOfInequalValueMapsWithArrayTypes() { + valueMap.put("prop", new String[] { "1", "2" }); + ValueMapDecorator valueMap2 = new ValueMapDecorator(new HashMap()); + valueMap2.put("prop", new String[] { "3", "4" }); + Assert.assertFalse("Two ValueMapDecorators based on maps with different entries should not be equal", + valueMap.equals(valueMap2)); + } + +} diff --git a/fsresource-1.x/src/test/java/org/apache/sling/fsprovider/internal/mapper/valuemap/ValueMapUtilTest.java b/fsresource-1.x/src/test/java/org/apache/sling/fsprovider/internal/mapper/valuemap/ValueMapUtilTest.java new file mode 100644 index 0000000..841ef00 --- /dev/null +++ b/fsresource-1.x/src/test/java/org/apache/sling/fsprovider/internal/mapper/valuemap/ValueMapUtilTest.java @@ -0,0 +1,51 @@ +/* + * 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.fsprovider.internal.mapper.valuemap; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.sling.api.resource.ValueMap; +import org.junit.Test; + +import com.google.common.collect.ImmutableList; + +public class ValueMapUtilTest { + + @Test + public void testToValueMap() { + Map content = new HashMap<>(); + content.put("stringProp", "abc"); + content.put("intProp", 123); + content.put("stringArray", new String[] { "a", "b", "c" }); + content.put("stringList", ImmutableList.of("ab", "cd")); + content.put("intList", ImmutableList.of(12, 34)); + + ValueMap props = ValueMapUtil.toValueMap(content); + assertEquals("abc", props.get("stringProp", String.class)); + assertEquals((Integer)123, props.get("intProp", 0)); + assertArrayEquals(new String[] { "a", "b", "c" }, props.get("stringArray", String[].class)); + assertArrayEquals(new String[] { "ab", "cd" }, props.get("stringList", String[].class)); + assertArrayEquals(new Integer[] { 12, 34 }, props.get("intList", Integer[].class)); + } + +} diff --git a/fsresource-1.x/src/test/java/org/apache/sling/fsprovider/internal/parser/ContentFileCacheTest.java b/fsresource-1.x/src/test/java/org/apache/sling/fsprovider/internal/parser/ContentFileCacheTest.java new file mode 100644 index 0000000..236959a --- /dev/null +++ b/fsresource-1.x/src/test/java/org/apache/sling/fsprovider/internal/parser/ContentFileCacheTest.java @@ -0,0 +1,91 @@ +/* + * 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.fsprovider.internal.parser; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +import java.io.File; + +import org.junit.experimental.theories.DataPoint; +import org.junit.experimental.theories.Theories; +import org.junit.experimental.theories.Theory; +import org.junit.runner.RunWith; + +@RunWith(Theories.class) +public class ContentFileCacheTest { + + @DataPoint + public static final int NO_CACHE = 0; + @DataPoint + public static final int SMALL_CACHE = 1; + @DataPoint + public static final int HUGE_CACHE = 1000; + + @Theory + public void testCache(int cacheSize) { + ContentFileCache underTest = new ContentFileCache(cacheSize); + + ContentElement content1 = underTest.get("/fs-test/folder2/content", new File("src/test/resources/fs-test/folder2/content.json")); + assertNotNull(content1); + + switch (cacheSize) { + case NO_CACHE: + assertEquals(0, underTest.size()); + break; + case SMALL_CACHE: + case HUGE_CACHE: + assertEquals(1, underTest.size()); + break; + } + + ContentElement content2 = underTest.get("/fs-test/folder1/file1a", new File("src/test/resources/fs-test/folder1/file1a.txt")); + assertNull(content2); + + switch (cacheSize) { + case NO_CACHE: + assertEquals(0, underTest.size()); + break; + case SMALL_CACHE: + assertEquals(1, underTest.size()); + break; + case HUGE_CACHE: + assertEquals(2, underTest.size()); + break; + } + + underTest.remove("/fs-test/folder1/file1a"); + + switch (cacheSize) { + case NO_CACHE: + case SMALL_CACHE: + assertEquals(0, underTest.size()); + break; + case HUGE_CACHE: + assertEquals(1, underTest.size()); + break; + } + + underTest.clear(); + + assertEquals(0, underTest.size()); + } + +} diff --git a/fsresource-1.x/src/test/java/org/apache/sling/fsprovider/internal/parser/ContentFileParserUtilTest.java b/fsresource-1.x/src/test/java/org/apache/sling/fsprovider/internal/parser/ContentFileParserUtilTest.java new file mode 100644 index 0000000..0d5671c --- /dev/null +++ b/fsresource-1.x/src/test/java/org/apache/sling/fsprovider/internal/parser/ContentFileParserUtilTest.java @@ -0,0 +1,71 @@ +/* + * 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.fsprovider.internal.parser; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +import java.io.File; + +import org.junit.Test; + +public class ContentFileParserUtilTest { + + @Test + public void testParseJson() { + File file = new File("src/test/resources/fs-test/folder2/content.json"); + ContentElement content = ContentFileParserUtil.parse(file); + assertNotNull(content); + assertEquals("app:Page", content.getProperties().get("jcr:primaryType")); + assertEquals("app:PageContent", content.getChild("jcr:content").getProperties().get("jcr:primaryType")); + } + + @Test + public void testParseInvalidJson() { + File file = new File("src/test/resources/invalid-test/invalid.json"); + ContentElement content = ContentFileParserUtil.parse(file); + assertNull(content); + } + + @Test + public void testParseJcrXml() { + File file = new File("src/test/resources/fs-test/folder3/content.jcr.xml"); + ContentElement content = ContentFileParserUtil.parse(file); + assertNotNull(content); + assertEquals("app:Page", content.getProperties().get("jcr:primaryType")); + assertEquals("app:PageContent", content.getChild("jcr:content").getProperties().get("jcr:primaryType")); + } + + @Test + public void testParseInvalidJcrXml() { + File file = new File("src/test/resources/invalid-test/invalid.jcr.xml"); + ContentElement content = ContentFileParserUtil.parse(file); + assertNull(content); + } + + @Test + public void testParseXml() { + File file = new File("src/test/resources/fs-test/folder2/folder21.xml"); + ContentElement content = ContentFileParserUtil.parse(file); + assertNotNull(content); + assertEquals("sling:OrderedFolder", content.getProperties().get("jcr:primaryType")); + } + +} diff --git a/fsresource-1.x/src/test/resources/fs-test/folder1/file1a.txt b/fsresource-1.x/src/test/resources/fs-test/folder1/file1a.txt new file mode 100644 index 0000000..9f0f26f --- /dev/null +++ b/fsresource-1.x/src/test/resources/fs-test/folder1/file1a.txt @@ -0,0 +1 @@ +file1a \ No newline at end of file diff --git a/fsresource-1.x/src/test/resources/fs-test/folder1/file1b.txt b/fsresource-1.x/src/test/resources/fs-test/folder1/file1b.txt new file mode 100644 index 0000000..518f63d --- /dev/null +++ b/fsresource-1.x/src/test/resources/fs-test/folder1/file1b.txt @@ -0,0 +1 @@ +file1b \ No newline at end of file diff --git a/fsresource-1.x/src/test/resources/fs-test/folder1/folder11/file11a.txt b/fsresource-1.x/src/test/resources/fs-test/folder1/folder11/file11a.txt new file mode 100644 index 0000000..4940b5d --- /dev/null +++ b/fsresource-1.x/src/test/resources/fs-test/folder1/folder11/file11a.txt @@ -0,0 +1 @@ +file11a \ No newline at end of file diff --git a/fsresource-1.x/src/test/resources/fs-test/folder2/content.json b/fsresource-1.x/src/test/resources/fs-test/folder2/content.json new file mode 100644 index 0000000..27a76a6 --- /dev/null +++ b/fsresource-1.x/src/test/resources/fs-test/folder2/content.json @@ -0,0 +1,262 @@ +/* Comment example */ +{ + "jcr:primaryType": 'app:Page', + 'jcr:createdBy': "admin", + "jcr:created": "Thu Aug 07 2014 16:32:59 GMT+0200", + /* Comment example */ + "jcr:content": { + "jcr:primaryType": "app:PageContent", /* Comment example */ + "jcr:createdBy": "admin", + "jcr:title": "English", + "app:template": "sample/templates/homepage", + "jcr:created": "Thu Aug 07 2014 16:32:59 GMT+0200", + "app:lastModified": "Tue Apr 22 2014 15:11:24 GMT+0200", + "dateISO8601String": "2014-04-22T15:11:24.000+02:00", + "pageTitle": "Sample Homepage", + "sling:resourceType": "sample/components/homepage", + "sling:resourceSuperType": "sample/components/supertype", + "app:designPath": "/etc/designs/sample", + "app:lastModifiedBy": "admin", + "utf8Property": "äöü߀", + "par": { + "jcr:primaryType": "nt:unstructured", + "sling:resourceType": "foundation/components/parsys", + "colctrl": { + "jcr:primaryType": "nt:unstructured", + "jcr:createdBy": "admin", + "jcr:lastModifiedBy": "admin", + "layout": "2;colctrl-lt0", + "jcr:created": "Mon Aug 23 2010 22:02:24 GMT+0200", + "jcr:lastModified": "Mon Aug 23 2010 22:02:35 GMT+0200", + "sling:resourceType": "foundation/components/parsys/colctrl" + }, + "image": { + "jcr:primaryType": "nt:unstructured", + "jcr:createdBy": "admin", + "fileReference": "/content/dam/sample/portraits/jane_doe.jpg", + "jcr:lastModifiedBy": "admin", + "jcr:created": "Mon Aug 23 2010 22:03:39 GMT+0200", + "width": "340", + "jcr:lastModified": "Sun Oct 31 2010 21:39:50 GMT+0100", + "sling:resourceType": "foundation/components/image", + "file": { + "jcr:primaryType": "nt:file", + "jcr:createdBy": "admin", + "jcr:created": "Thu Aug 07 2014 16:32:59 GMT+0200", + "jcr:content": { + "jcr:primaryType": "nt:resource", + "jcr:lastModifiedBy": "anonymous", + "jcr:mimeType": "image/jpeg", + "jcr:lastModified": "Thu Aug 07 2014 16:32:59 GMT+0200", + ":jcr:data": 24377, + "jcr:uuid": "eda76d00-b2cd-4b59-878f-c33f71ceaddc" + } + } + }, + "title_1": { + "jcr:primaryType": "nt:unstructured", + "jcr:createdBy": "admin", + "jcr:title": "Strategic Consulting", + "jcr:lastModifiedBy": "admin", + "jcr:created": "Mon Aug 23 2010 22:12:08 GMT+0200", + "jcr:lastModified": "Wed Oct 27 2010 21:33:24 GMT+0200", + "sling:resourceType": "sample/components/title" + }, + "text_1": { + "jcr:primaryType": "nt:unstructured", + "jcr:createdBy": "admin", + "jcr:lastModifiedBy": "admin", + "jcr:created": "Sun Oct 31 2010 21:48:04 GMT+0100", + "text": "

In today's competitive market, organizations can face several key geometric challenges:<\/span><\/p>\n

    \n
  • Polyhedral Sectioning<\/span><\/li>\n
  • Triangulation <\/span><\/li>\n
  • Trigonometric Calculation<\/span><\ [...] + "jcr:lastModified": "Sun Oct 31 2010 21:49:06 GMT+0100", + "sling:resourceType": "foundation/components/text", + "textIsRich": "true" + }, + "col_break12825937554040": { + "jcr:primaryType": "nt:unstructured", + "controlType": "break", + "sling:resourceType": "foundation/components/parsys/colctrl" + }, + "image_0": { + "jcr:primaryType": "nt:unstructured", + "jcr:createdBy": "admin", + "fileReference": "/content/dam/sample/offices/clean_room.jpg", + "height": "226", + "jcr:lastModifiedBy": "admin", + "jcr:created": "Mon Aug 23 2010 22:04:46 GMT+0200", + "jcr:lastModified": "Fri Nov 05 2010 10:38:15 GMT+0100", + "sling:resourceType": "foundation/components/image", + "imageRotate": "0", + "file": { + "jcr:primaryType": "nt:file", + "jcr:createdBy": "admin", + "jcr:created": "Thu Aug 07 2014 16:32:59 GMT+0200", + "jcr:content": { + "jcr:primaryType": "nt:resource", + "jcr:lastModifiedBy": "anonymous", + "jcr:mimeType": "image/jpeg", + "jcr:lastModified": "Thu Aug 07 2014 16:32:59 GMT+0200", + ":jcr:data": 21142, + "jcr:uuid": "6139077f-191f-4337-aaef-55456ebe6784" + } + } + }, + "title_2": { + "jcr:createdBy": "admin", + "jcr:title": "Shape Technology", + "jcr:lastModifiedBy": "admin", + "jcr:created": "Mon Aug 23 2010 22:12:13 GMT+0200", + "jcr:lastModified": "Tue Oct 26 2010 21:16:29 GMT+0200", + "sling:resourceType": "sample/components/title" + }, + "text_0": { + "jcr:primaryType": "nt:unstructured", + "jcr:createdBy": "admin", + "jcr:lastModifiedBy": "admin", + "jcr:created": "Mon Aug 23 2010 22:16:30 GMT+0200", + "text": "

    The Sample investment in R&D has done more than solidify our industry leadership role, we have now outpaced our competitors to such an extent that we are in an altogether new space.<\/p>\n

    This is why our high quality polygons and polyhedra provide the only turnkey solutions across the whole range of euclidean geometry. And our mathematicians are working on the next generation of fractal curves to bring you shapes that are unthinkable today.<\/p>\n

    <\/p>\n

    < [...] + "jcr:lastModified": "Mon Nov 08 2010 20:39:00 GMT+0100", + "sling:resourceType": "foundation/components/text", + "textIsRich": "true" + }, + "col_end12825937444810": { + "jcr:primaryType": "nt:unstructured", + "controlType": "end", + "sling:resourceType": "foundation/components/parsys/colctrl" + } + }, + "header": { + "jcr:primaryType": "nt:unstructured", + "jcr:title": "trust our experience\r\nto manage your business", + "imageReference": "/content/dam/sample/header.png", + "text": "Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Nunc eget neque. Nunc condimentum ipsum et orci. Aenean est. Cras eget diam. read more", + "sling:resourceType": "sample/components/header" + }, + "newslist": { + "jcr:primaryType": "nt:unstructured", + "headline": "trust our experience\nto manage your business", + "text": "Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Nunc eget neque. Nunc condimentum ipsum et orci. Aenean est. Cras eget diam. read more", + "sling:resourceType": "sample/components/listchildren", + "listroot": "/content/sample/en/about/news" + }, + "lead": { + "jcr:primaryType": "nt:unstructured", + "jcr:title": "World Leader in Applied Geometry ", + "jcr:lastModifiedBy": "admin", + "text": "Lead Text", + "title": "Lead Title", + "jcr:description": "Sample has been selling and servicing shapes for over 2000 years. From our beginnings as a small vendor of squares and rectangles we have grown our business into a leading global provider of platonic solids and fractals. Join us as we lead geometry into the future.", + "jcr:lastModified": "Wed Jan 19 2011 14:35:29 GMT+0100", + "sling:resourceType": "sample/components/lead", + "app:annotations": {"jcr:primaryType": "nt:unstructured"} + }, + "image": { + "jcr:primaryType": "nt:unstructured", + "jcr:lastModifiedBy": "admin", + "jcr:lastModified": "Wed Oct 27 2010 21:30:59 GMT+0200", + "imageRotate": "0" + }, + "carousel": { + "jcr:primaryType": "nt:unstructured", + "playSpeed": "6000", + "jcr:lastModifiedBy": "admin", + "pages": [ + "/content/sample/en/events/techsummit", + "/content/sample/en/events/userconf", + "/content/sample/en/events/shapecon", + "/content/sample/en/events/dsc" + ], + "jcr:lastModified": "Tue Oct 05 2010 14:14:27 GMT+0200", + "transTime": "1000", + "sling:resourceType": "foundation/components/carousel", + "listFrom": "static" + }, + "rightpar": { + "jcr:primaryType": "nt:unstructured", + "sling:resourceType": "foundation/components/parsys", + "teaser": { + "jcr:primaryType": "nt:unstructured", + "jcr:createdBy": "admin", + "jcr:lastModifiedBy": "admin", + "jcr:created": "Tue Jan 25 2011 11:30:09 GMT+0100", + "campaignpath": "/content/campaigns/sample", + "jcr:lastModified": "Wed Feb 02 2011 08:40:30 GMT+0100", + "sling:resourceType": "personalization/components/teaser" + } + } + }, + "toolbar": { + "jcr:primaryType": "app:Page", + "jcr:createdBy": "admin", + "jcr:created": "Thu Aug 07 2014 16:33:00 GMT+0200", + "jcr:content": { + "jcr:primaryType": "app:PageContent", + "subtitle": "Contains the toolbar", + "jcr:createdBy": "admin", + "jcr:title": "Toolbar", + "app:template": "sample/templates/contentpage", + "jcr:created": "Thu Aug 07 2014 16:33:00 GMT+0200", + "app:lastModified": "Wed Aug 25 2010 22:51:02 GMT+0200", + "hideInNav": "true", + "sling:resourceType": "sample/components/contentpage", + "app:lastModifiedBy": "admin", + "par": { + "jcr:primaryType": "nt:unstructured", + "sling:resourceType": "foundation/components/parsys" + }, + "rightpar": { + "jcr:primaryType": "nt:unstructured", + "sling:resourceType": "foundation/components/iparsys", + "iparsys_fake_par": { + "jcr:primaryType": "nt:unstructured", + "sling:resourceType": "foundation/components/iparsys/par" + } + } + }, + "profiles": { + "jcr:primaryType": "app:Page", + "jcr:createdBy": "admin", + "jcr:created": "Thu Aug 07 2014 16:33:00 GMT+0200", + "jcr:content": { + "jcr:primaryType": "app:PageContent", + "jcr:mixinTypes": ["type1","type2"], + "jcr:createdBy": "admin", + "jcr:title": "Profiles", + "app:template": "sample/templates/contentpage", + "jcr:created": "Thu Aug 07 2014 16:33:00 GMT+0200", + "app:lastModified": "Thu Nov 05 2009 20:27:13 GMT+0100", + "hideInNav": true, + "sling:resourceType": "sample/components/contentpage", + "app:lastModifiedBy": "admin", + "longProp": 1234567890123, + "decimalProp": 1.2345, + "booleanProp": true, + "longPropMulti": [1234567890123,55], + "decimalPropMulti": [1.2345,1.1], + "booleanPropMulti": [true,false], + "stringPropMulti": ["aa","bb","cc"], + "par": { + "jcr:primaryType": "nt:unstructured", + "sling:resourceType": "foundation/components/parsys", + "textimage": { + "jcr:primaryType": "nt:unstructured", + "sling:resourceType": "foundation/components/textimage" + }, + "mygadgets": { + "jcr:primaryType": "nt:unstructured", + "gadgets": "http://customer.meteogroup.de/meteogroup/gadgets/wetter24.xml\nhttp://germanweatherradar.googlecode.com/svn/trunk/german-weather-radar.xml\nhttp://www.digitalpowered.info/gadget/ski.pictures.xml\nhttp://www.canbuffi.de/gadgets/clock/clock.xml", + "sling:resourceType": "personalization/components/mygadgets" + } + }, + "rightpar": { + "jcr:primaryType": "nt:unstructured", + "sling:resourceType": "foundation/components/iparsys", + "iparsys_fake_par": { + "jcr:primaryType": "nt:unstructured", + "sling:resourceType": "foundation/components/iparsys/par" + } + } + } + } + } +} diff --git a/fsresource-1.x/src/test/resources/fs-test/folder2/content/content2.json b/fsresource-1.x/src/test/resources/fs-test/folder2/content/content2.json new file mode 100644 index 0000000..261509a --- /dev/null +++ b/fsresource-1.x/src/test/resources/fs-test/folder2/content/content2.json @@ -0,0 +1,4 @@ +{ + "jcr:primaryType": "app:Page", + "jcr:createdBy": "admin" +} diff --git a/fsresource-1.x/src/test/resources/fs-test/folder2/content/file2content.txt b/fsresource-1.x/src/test/resources/fs-test/folder2/content/file2content.txt new file mode 100644 index 0000000..667b547 --- /dev/null +++ b/fsresource-1.x/src/test/resources/fs-test/folder2/content/file2content.txt @@ -0,0 +1 @@ +file2content \ No newline at end of file diff --git a/fsresource-1.x/src/test/resources/fs-test/folder2/folder21.xml b/fsresource-1.x/src/test/resources/fs-test/folder2/folder21.xml new file mode 100644 index 0000000..77ab5ff --- /dev/null +++ b/fsresource-1.x/src/test/resources/fs-test/folder2/folder21.xml @@ -0,0 +1,4 @@ + + + sling:OrderedFolder + diff --git a/fsresource-1.x/src/test/resources/fs-test/folder2/folder21/file21a.txt b/fsresource-1.x/src/test/resources/fs-test/folder2/folder21/file21a.txt new file mode 100644 index 0000000..3d5becc --- /dev/null +++ b/fsresource-1.x/src/test/resources/fs-test/folder2/folder21/file21a.txt @@ -0,0 +1 @@ +file21a \ No newline at end of file diff --git a/fsresource-1.x/src/test/resources/fs-test/folder2/folder21/file21a.txt.xml b/fsresource-1.x/src/test/resources/fs-test/folder2/folder21/file21a.txt.xml new file mode 100644 index 0000000..4cf1495 --- /dev/null +++ b/fsresource-1.x/src/test/resources/fs-test/folder2/folder21/file21a.txt.xml @@ -0,0 +1,15 @@ + + + file21a.txt + mix:language + + jcr:language + en + String + + + sling:resourceSuperType + /my/super/type + String + + diff --git a/fsresource-1.x/src/test/resources/fs-test/folder3/content.jcr.xml b/fsresource-1.x/src/test/resources/fs-test/folder3/content.jcr.xml new file mode 100644 index 0000000..da111ee --- /dev/null +++ b/fsresource-1.x/src/test/resources/fs-test/folder3/content.jcr.xml @@ -0,0 +1,192 @@ + + + + + + + + + + +

    + + + + + + + + + + + + + + + diff --git a/fsresource-1.x/src/test/resources/fs-test/folder3/content/content2.jcr.xml b/fsresource-1.x/src/test/resources/fs-test/folder3/content/content2.jcr.xml new file mode 100644 index 0000000..3964ca4 --- /dev/null +++ b/fsresource-1.x/src/test/resources/fs-test/folder3/content/content2.jcr.xml @@ -0,0 +1,29 @@ + + + + + + diff --git a/fsresource-1.x/src/test/resources/fs-test/folder3/folder31/file31a.txt b/fsresource-1.x/src/test/resources/fs-test/folder3/folder31/file31a.txt new file mode 100644 index 0000000..3d5becc --- /dev/null +++ b/fsresource-1.x/src/test/resources/fs-test/folder3/folder31/file31a.txt @@ -0,0 +1 @@ +file21a \ No newline at end of file diff --git a/fsresource-1.x/src/test/resources/invalid-test/invalid.jcr.xml b/fsresource-1.x/src/test/resources/invalid-test/invalid.jcr.xml new file mode 100644 index 0000000..7df8a70 --- /dev/null +++ b/fsresource-1.x/src/test/resources/invalid-test/invalid.jcr.xml @@ -0,0 +1 @@ +This is invalid xml. diff --git a/fsresource-1.x/src/test/resources/invalid-test/invalid.json b/fsresource-1.x/src/test/resources/invalid-test/invalid.json new file mode 100644 index 0000000..59fc86c --- /dev/null +++ b/fsresource-1.x/src/test/resources/invalid-test/invalid.json @@ -0,0 +1 @@ +This is invalid json. diff --git a/fsresource-1.x/src/test/resources/simplelogger.properties b/fsresource-1.x/src/test/resources/simplelogger.properties new file mode 100644 index 0000000..e62c1ea --- /dev/null +++ b/fsresource-1.x/src/test/resources/simplelogger.properties @@ -0,0 +1,19 @@ +# 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. + +org.slf4j.simpleLogger.defaultLogLevel=warn +org.slf4j.simpleLogger.log.org.apache.sling.fsprovider.internal=warn diff --git a/fsresource-1.x/src/test/resources/vaultfs-test/META-INF/vault/filter.xml b/fsresource-1.x/src/test/resources/vaultfs-test/META-INF/vault/filter.xml new file mode 100644 index 0000000..a56ee24 --- /dev/null +++ b/fsresource-1.x/src/test/resources/vaultfs-test/META-INF/vault/filter.xml @@ -0,0 +1,25 @@ + + + + + + + + diff --git a/fsresource-1.x/src/test/resources/vaultfs-test/META-INF/vault/settings.xml b/fsresource-1.x/src/test/resources/vaultfs-test/META-INF/vault/settings.xml new file mode 100644 index 0000000..3f8e13a --- /dev/null +++ b/fsresource-1.x/src/test/resources/vaultfs-test/META-INF/vault/settings.xml @@ -0,0 +1,23 @@ + + + + + + diff --git a/fsresource-1.x/src/test/resources/vaultfs-test/jcr_root/.content.xml b/fsresource-1.x/src/test/resources/vaultfs-test/jcr_root/.content.xml new file mode 100644 index 0000000..b264022 --- /dev/null +++ b/fsresource-1.x/src/test/resources/vaultfs-test/jcr_root/.content.xml @@ -0,0 +1,24 @@ + + + diff --git a/fsresource-1.x/src/test/resources/vaultfs-test/jcr_root/content/.content.xml b/fsresource-1.x/src/test/resources/vaultfs-test/jcr_root/content/.content.xml new file mode 100644 index 0000000..115c72c --- /dev/null +++ b/fsresource-1.x/src/test/resources/vaultfs-test/jcr_root/content/.content.xml @@ -0,0 +1,28 @@ + + + + + + diff --git a/fsresource-1.x/src/test/resources/vaultfs-test/jcr_root/content/dam/.content.xml b/fsresource-1.x/src/test/resources/vaultfs-test/jcr_root/content/dam/.content.xml new file mode 100644 index 0000000..64f25b1 --- /dev/null +++ b/fsresource-1.x/src/test/resources/vaultfs-test/jcr_root/content/dam/.content.xml @@ -0,0 +1,24 @@ + + + + + diff --git a/fsresource-1.x/src/test/resources/vaultfs-test/jcr_root/content/dam/talk.png/.content.xml b/fsresource-1.x/src/test/resources/vaultfs-test/jcr_root/content/dam/talk.png/.content.xml new file mode 100644 index 0000000..e207a1e --- /dev/null +++ b/fsresource-1.x/src/test/resources/vaultfs-test/jcr_root/content/dam/talk.png/.content.xml @@ -0,0 +1,44 @@ + + + + + + + diff --git a/fsresource-1.x/src/test/resources/vaultfs-test/jcr_root/content/dam/talk.png/_jcr_content/renditions/original b/fsresource-1.x/src/test/resources/vaultfs-test/jcr_root/content/dam/talk.png/_jcr_content/renditions/original new file mode 100644 index 0000000..0d42760 Binary files /dev/null and b/fsresource-1.x/src/test/resources/vaultfs-test/jcr_root/content/dam/talk.png/_jcr_content/renditions/original differ diff --git a/fsresource-1.x/src/test/resources/vaultfs-test/jcr_root/content/dam/talk.png/_jcr_content/renditions/original.dir/.content.xml b/fsresource-1.x/src/test/resources/vaultfs-test/jcr_root/content/dam/talk.png/_jcr_content/renditions/original.dir/.content.xml new file mode 100644 index 0000000..1813b25 --- /dev/null +++ b/fsresource-1.x/src/test/resources/vaultfs-test/jcr_root/content/dam/talk.png/_jcr_content/renditions/original.dir/.content.xml @@ -0,0 +1,25 @@ + + + + + diff --git a/fsresource-1.x/src/test/resources/vaultfs-test/jcr_root/content/dam/talk.png/_jcr_content/renditions/web.1280.1280.png b/fsresource-1.x/src/test/resources/vaultfs-test/jcr_root/content/dam/talk.png/_jcr_content/renditions/web.1280.1280.png new file mode 100644 index 0000000..27b0374 Binary files /dev/null and b/fsresource-1.x/src/test/resources/vaultfs-test/jcr_root/content/dam/talk.png/_jcr_content/renditions/web.1280.1280.png differ diff --git a/fsresource-1.x/src/test/resources/vaultfs-test/jcr_root/content/samples/.content.xml b/fsresource-1.x/src/test/resources/vaultfs-test/jcr_root/content/samples/.content.xml new file mode 100644 index 0000000..c96141e --- /dev/null +++ b/fsresource-1.x/src/test/resources/vaultfs-test/jcr_root/content/samples/.content.xml @@ -0,0 +1,23 @@ + + + + + diff --git a/fsresource-1.x/src/test/resources/vaultfs-test/jcr_root/content/samples/en/.content.xml b/fsresource-1.x/src/test/resources/vaultfs-test/jcr_root/content/samples/en/.content.xml new file mode 100644 index 0000000..83b8626 --- /dev/null +++ b/fsresource-1.x/src/test/resources/vaultfs-test/jcr_root/content/samples/en/.content.xml @@ -0,0 +1,191 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/fsresource-1.x/src/test/resources/vaultfs-test/jcr_root/content/samples/en/conference/.content.xml b/fsresource-1.x/src/test/resources/vaultfs-test/jcr_root/content/samples/en/conference/.content.xml new file mode 100644 index 0000000..2c2ef86 --- /dev/null +++ b/fsresource-1.x/src/test/resources/vaultfs-test/jcr_root/content/samples/en/conference/.content.xml @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/fsresource-1.x/src/test/resources/vaultfs-test/jcr_root/content/samples/en/tools/.content.xml b/fsresource-1.x/src/test/resources/vaultfs-test/jcr_root/content/samples/en/tools/.content.xml new file mode 100644 index 0000000..3964ca4 --- /dev/null +++ b/fsresource-1.x/src/test/resources/vaultfs-test/jcr_root/content/samples/en/tools/.content.xml @@ -0,0 +1,29 @@ + + + + + + diff --git a/fsresource-1.x/src/test/resources/vaultfs-test/jcr_root/content/samples/en/tools/navigation/.content.xml b/fsresource-1.x/src/test/resources/vaultfs-test/jcr_root/content/samples/en/tools/navigation/.content.xml new file mode 100644 index 0000000..676d8b9 --- /dev/null +++ b/fsresource-1.x/src/test/resources/vaultfs-test/jcr_root/content/samples/en/tools/navigation/.content.xml @@ -0,0 +1,27 @@ + + + + + -- To stop receiving notification emails like this one, please contact "commits@sling.apache.org" .