This is an automated email from the ASF dual-hosted git repository. rombert pushed a commit to annotated tag org.apache.sling.fsresource-2.0.0 in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-fsresource.git
commit 1816787009b9791647fb67e72711a63fa31c9851 Author: Stefan Seifert <[email protected]> AuthorDate: Mon Mar 6 21:48:18 2017 +0000 SLING-6537 FileVault XML support git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/fsresource@1785771 13f79535-47bb-0310-9956-ffa450edef68 --- .../fsprovider/internal/ContentFileExtensions.java | 3 +- .../sling/fsprovider/internal/FileMonitor.java | 98 ++++++--- .../apache/sling/fsprovider/internal/FsMode.java | 41 ++++ .../fsprovider/internal/FsResourceProvider.java | 147 +++++++++---- .../internal/InitialContentImportOptions.java | 76 +++++++ .../fsprovider/internal/mapper/ContentFile.java | 53 +++-- .../internal/mapper/ContentFileResourceMapper.java | 16 +- .../internal/mapper/FileVaultResourceMapper.java | 201 ++++++++++++++++++ .../fsprovider/internal/mapper/jcr/FsNode.java | 20 +- .../internal/mapper/jcr/FsNodeIterator.java | 9 +- .../internal/parser/ContentFileParserUtil.java | 6 +- .../sling/fsprovider/internal/FileMonitorTest.java | 60 ++---- .../fsprovider/internal/FileVaultContentTest.java | 147 +++++++++++++ .../internal/FileVaultFileMonitorTest.java | 232 +++++++++++++++++++++ .../internal/InitialContentImportOptionsTest.java | 59 ++++++ .../fsprovider/internal/JcrXmlContentTest.java | 5 +- .../sling/fsprovider/internal/JsonContentTest.java | 7 +- .../sling/fsprovider/internal/TestUtils.java | 28 +++ .../vaultfs-test/META-INF/vault/filter.xml | 4 +- .../jcr_root/content/dam/talk.png/.content.xml | 2 - 20 files changed, 1053 insertions(+), 161 deletions(-) diff --git a/src/main/java/org/apache/sling/fsprovider/internal/ContentFileExtensions.java b/src/main/java/org/apache/sling/fsprovider/internal/ContentFileExtensions.java index 5097750..f9cacec 100644 --- a/src/main/java/org/apache/sling/fsprovider/internal/ContentFileExtensions.java +++ b/src/main/java/org/apache/sling/fsprovider/internal/ContentFileExtensions.java @@ -41,8 +41,9 @@ public final class ContentFileExtensions { * @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.endsWith(file.getName(), suffix)) { + if (StringUtils.endsWith(fileName, suffix)) { return suffix; } } diff --git a/src/main/java/org/apache/sling/fsprovider/internal/FileMonitor.java b/src/main/java/org/apache/sling/fsprovider/internal/FileMonitor.java index 7d77482..319e712 100644 --- a/src/main/java/org/apache/sling/fsprovider/internal/FileMonitor.java +++ b/src/main/java/org/apache/sling/fsprovider/internal/FileMonitor.java @@ -18,6 +18,8 @@ */ package org.apache.sling.fsprovider.internal; +import static org.apache.jackrabbit.vault.util.Constants.ROOT_DIR; + import java.io.File; import java.util.ArrayList; import java.util.List; @@ -26,6 +28,7 @@ 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.api.resource.observation.ResourceChange; import org.apache.sling.api.resource.observation.ResourceChange.ChangeType; import org.apache.sling.fsprovider.internal.mapper.ContentFile; @@ -50,7 +53,7 @@ public final class FileMonitor extends TimerTask { private final Monitorable root; private final FsResourceProvider provider; - + private final FsMode fsMode; private final ContentFileExtensions contentFileExtensions; private final ContentFileCache contentFileCache; @@ -59,12 +62,19 @@ public final class FileMonitor extends TimerTask { * @param provider The resource provider. * @param interval The interval between executions of the task, in milliseconds. */ - public FileMonitor(final FsResourceProvider provider, final long interval, + 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; - this.root = new Monitorable(this.provider.getProviderRoot(), this.provider.getRootFile(), null); + + File rootFile = this.provider.getRootFile(); + if (fsMode == FsMode.FILEVAULT_XML) { + rootFile = new File(this.provider.getRootFile(), ROOT_DIR + 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); @@ -142,6 +152,13 @@ public final class FileMonitor extends TimerTask { // new file and reset status createStatus(monitorable, contentFileExtensions, contentFileCache); sendEvents(monitorable, ChangeType.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 @@ -170,45 +187,50 @@ public final class FileMonitor extends TimerTask { // if the dir changed we have to update if ( changed ) { // and now update - final File[] files = monitorable.file.listFiles(); - if (files != null) { - final Monitorable[] children = new Monitorable[files.length]; - for (int i = 0; i < files.length; i++) { - // search in old list - for (int m = 0; m < ds.children.length; m++) { - if (ds.children[m].file.equals(files[i])) { - children[i] = ds.children[m]; - break; - } - } - if (children[i] == null) { - children[i] = new Monitorable(monitorable.path + '/' + files[i].getName(), files[i], - contentFileExtensions.getSuffix(files[i])); - children[i].status = NonExistingStatus.SINGLETON; - check(children[i], reporter); - } - } - ds.children = children; - } else { - ds.children = new Monitorable[0]; - } + checkDirStatusChildren(monitorable, reporter); } } } } } + + private void checkDirStatusChildren(final Monitorable dirMonitorable, final ObservationReporter reporter) { + final DirStatus ds = (DirStatus)dirMonitorable.status; + final File[] files = dirMonitorable.file.listFiles(); + if (files != null) { + final Monitorable[] children = new Monitorable[files.length]; + for (int i = 0; i < files.length; i++) { + // search in old list + for (int m = 0; m < ds.children.length; m++) { + if (ds.children[m].file.equals(files[i])) { + children[i] = ds.children[m]; + break; + } + } + if (children[i] == null) { + children[i] = new Monitorable(dirMonitorable.path + '/' + files[i].getName(), files[i], + contentFileExtensions.getSuffix(files[i])); + children[i].status = NonExistingStatus.SINGLETON; + check(children[i], reporter); + } + } + ds.children = children; + } else { + ds.children = new Monitorable[0]; + } + } /** * Send the event async via the event admin. */ private void sendEvents(final Monitorable monitorable, final ChangeType changeType, final ObservationReporter reporter) { if (log.isDebugEnabled()) { - log.debug("Detected change for resource {} : {}", monitorable.path, changeType); + log.debug("Detected change for resource {} : {}", transformPath(monitorable.path), changeType); } List<ResourceChange> changes = null; for (final ObserverConfiguration config : reporter.getObserverConfigurations()) { - if (config.matches(monitorable.path)) { + if (config.matches(transformPath(monitorable.path))) { if (changes == null) { changes = collectResourceChanges(monitorable, changeType); } @@ -224,6 +246,20 @@ public final class FileMonitor extends TimerTask { } } + /** + * 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; + } + } + @SuppressWarnings("unchecked") private List<ResourceChange> collectResourceChanges(final Monitorable monitorable, final ChangeType changeType) { List<ResourceChange> changes = new ArrayList<>(); @@ -233,15 +269,15 @@ public final class FileMonitor extends TimerTask { Map<String,Object> content = (Map<String,Object>)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(ChangeType.REMOVED, monitorable.path)); - addContentResourceChanges(changes, ChangeType.ADDED, content, monitorable.path); + changes.add(buildContentResourceChange(ChangeType.REMOVED, transformPath(monitorable.path))); + addContentResourceChanges(changes, ChangeType.ADDED, content, transformPath(monitorable.path)); } else { - addContentResourceChanges(changes, changeType, (Map<String,Object>)contentFile.getContent(), monitorable.path); + addContentResourceChanges(changes, changeType, (Map<String,Object>)contentFile.getContent(), transformPath(monitorable.path)); } } else { - changes.add(buildContentResourceChange(changeType, monitorable.path)); + changes.add(buildContentResourceChange(changeType, transformPath(monitorable.path))); } return changes; } diff --git a/src/main/java/org/apache/sling/fsprovider/internal/FsMode.java b/src/main/java/org/apache/sling/fsprovider/internal/FsMode.java new file mode 100644 index 0000000..15af91f --- /dev/null +++ b/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 Filesystem provider support and filesystem layouts. + */ +public enum FsMode { + + /** + * Sling-Initial-Content filesystem layout, with full support for JSON and jcr.xml content files. + */ + INITIAL_CONTENT, + + /** + * Sling-Initial-Content filesystem layout, support only files and folders (classic mode). + */ + INITIAL_CONTENT_FILES_FOLDERS, + + /** + * FileVault XML format (expanded content package). + */ + FILEVAULT_XML + +} diff --git a/src/main/java/org/apache/sling/fsprovider/internal/FsResourceProvider.java b/src/main/java/org/apache/sling/fsprovider/internal/FsResourceProvider.java index d214987..2b55028 100644 --- a/src/main/java/org/apache/sling/fsprovider/internal/FsResourceProvider.java +++ b/src/main/java/org/apache/sling/fsprovider/internal/FsResourceProvider.java @@ -24,13 +24,16 @@ import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; +import static org.apache.jackrabbit.vault.util.Constants.DOT_CONTENT_XML; 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.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.spi.resource.provider.ObservationReporter; @@ -47,6 +50,7 @@ import org.osgi.service.component.annotations.Deactivate; 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 <code>FsResourceProvider</code> is a resource provider which maps @@ -106,17 +110,25 @@ public final class FsResourceProvider extends ResourceProvider<Object> { "filesystem resources are mapped in. This property must not be an empty string.") String provider_root(); - @AttributeDefinition(name = "Mount json", - description = "Mount .json files as content in the resource hierarchy.") - boolean provider_json_content(); - - @AttributeDefinition(name = "Mount jcr.xml", - description = "Mount .jcr.xml files as content in the resource hierarchy.") - boolean provider_jcrxml_content(); + @AttributeDefinition(name = "Filesystem layout", + description = "Filesystem layout mode for files, folders and content.", + options={ + @Option(value="INITIAL_CONTENT", label="INITIAL_CONTENT - " + + "Sling-Initial-Content filesystem layout, with full support for JSON and jcr.xml content files."), + @Option(value="INITIAL_CONTENT_FILES_FOLDERS", label="INITIAL_CONTENT_FILES_FOLDERS - " + + "Sling-Initial-Content filesystem layout, support only files and folders (classic mode)."), + @Option(value="FILEVAULT_XML", label="FILEVAULT_XML - " + + "FileVault XML format (expanded content package)."), + }) + FsMode provider_fs_mode() default FsMode.INITIAL_CONTENT; + + @AttributeDefinition(name = "Init. Content Options", + description = "Import options for Sling-Initial-Content filesystem layout. Supported options: overwrite, ignoreImportProviders.") + String provider_initial_content_import_options(); @AttributeDefinition(name = "Cache Size", description = "Max. number of content files cached in memory.") - int provider_cache_size() default 1000; + int provider_cache_size() default 10000; /** * Internal Name hint for web console. @@ -134,8 +146,10 @@ public final class FsResourceProvider extends ResourceProvider<Object> { private FileMonitor monitor; // maps filesystem to resources + private FsMode fsMode; private FsResourceMapper fileMapper; private FsResourceMapper contentFileMapper; + private FileVaultResourceMapper fileVaultMapper; // if true resources from filesystem are only "overlayed" to JCR resources, serving JCR as fallback within the same path private boolean overlayParentResourceProvider; @@ -160,12 +174,30 @@ public final class FsResourceProvider extends ResourceProvider<Object> { final Resource parent) { ResourceResolver resolver = ctx.getResourceResolver(); - Resource rsrc = contentFileMapper.getResource(resolver, path); - if (rsrc == null) { - rsrc = fileMapper.getResource(resolver, path); + + boolean askParentResourceProvider; + Resource rsrc = null; + + if (fsMode == FsMode.FILEVAULT_XML) { + // filevault: check if path matches, if not fallback to parent resource provider + if (fileVaultMapper.pathMatches(path)) { + askParentResourceProvider = false; + rsrc = fileVaultMapper.getResource(resolver, path); + } + else { + askParentResourceProvider = true; + } + } + else { + // Sling-Initial-Content: mount folder/files an content files + askParentResourceProvider = this.overlayParentResourceProvider; + rsrc = contentFileMapper.getResource(resolver, path); + if (rsrc == null) { + rsrc = fileMapper.getResource(resolver, path); + } } - if (this.overlayParentResourceProvider) { + if (askParentResourceProvider) { // make sure directory resources from parent resource provider have higher precedence than from this provider // this allows properties like sling:resourceSuperType to take effect if ( rsrc == null || rsrc.getResourceMetadata().containsKey(RESOURCE_METADATA_FILE_DIRECTORY) ) { @@ -181,7 +213,7 @@ public final class FsResourceProvider extends ResourceProvider<Object> { } } } - + return rsrc; } @@ -195,24 +227,48 @@ public final class FsResourceProvider extends ResourceProvider<Object> { List<Iterator<Resource>> allChildren = new ArrayList<>(); Iterator<Resource> children; + boolean askParentResourceProvider; - children = contentFileMapper.getChildren(resolver, parent); - if (children != null) { - allChildren.add(children); + if (fsMode == FsMode.FILEVAULT_XML) { + // filevault: always ask provider, it checks itself if children matches the filters + askParentResourceProvider = true; + children = fileVaultMapper.getChildren(resolver, parent); + if (children != null) { + allChildren.add(children); + } } - - children = fileMapper.getChildren(resolver, parent); - if (children != null) { - allChildren.add(children); + else { + // Sling-Initial-Content: get all matchind folders/files and content files + askParentResourceProvider = this.overlayParentResourceProvider; + children = contentFileMapper.getChildren(resolver, parent); + if (children != null) { + allChildren.add(children); + } + children = fileMapper.getChildren(resolver, parent); + if (children != null) { + allChildren.add(children); + } } // get children from from shadowed provider - if (this.overlayParentResourceProvider) { + if (askParentResourceProvider) { final ResourceProvider parentResourceProvider = ctx.getParentResourceProvider(); if (parentResourceProvider != null) { children = parentResourceProvider.listChildren(ctx.getParentResolveContext(), parent); if (children != null) { - allChildren.add(children); + if (fsMode == FsMode.FILEVAULT_XML) { + // filevault: include all children from parent resource provider that do not match the filters + allChildren.add(IteratorUtils.filteredIterator(children, new Predicate() { + @Override + public boolean evaluate(Object object) { + Resource child = (Resource)object; + return !fileVaultMapper.pathMatches(child.getPath()); + } + })); + } + else { + allChildren.add(children); + } } } } @@ -239,39 +295,54 @@ public final class FsResourceProvider extends ResourceProvider<Object> { // ---------- SCR Integration @Activate protected void activate(BundleContext bundleContext, final Config config) { + fsMode = config.provider_fs_mode(); String providerRoot = config.provider_root(); - if (providerRoot == null || providerRoot.length() == 0) { + if (StringUtils.isBlank(providerRoot)) { throw new IllegalArgumentException("provider.root property must be set"); } String providerFileName = config.provider_file(); - if (providerFileName == null || providerFileName.length() == 0) { + if (StringUtils.isBlank(providerFileName)) { throw new IllegalArgumentException("provider.file property must be set"); } this.providerRoot = providerRoot; this.providerFile = getProviderFile(providerFileName, bundleContext); - this.overlayParentResourceProvider = true; + this.overlayParentResourceProvider = false; + InitialContentImportOptions options = new InitialContentImportOptions(config.provider_initial_content_import_options()); + List<String> contentFileSuffixes = new ArrayList<>(); - if (config.provider_json_content()) { - contentFileSuffixes.add(ContentFileTypes.JSON_SUFFIX); - this.overlayParentResourceProvider = false; + if (fsMode == FsMode.FILEVAULT_XML) { + contentFileSuffixes.add("/" + DOT_CONTENT_XML); + } + else if (fsMode == FsMode.INITIAL_CONTENT_FILES_FOLDERS) { + overlayParentResourceProvider = true; } - if (config.provider_jcrxml_content()) { - contentFileSuffixes.add(ContentFileTypes.JCR_XML_SUFFIX); - this.overlayParentResourceProvider = false; + else if (fsMode == FsMode.INITIAL_CONTENT) { + overlayParentResourceProvider = !options.isOverwrite(); + if (!options.getIgnoreImportProviders().contains("json")) { + contentFileSuffixes.add(ContentFileTypes.JSON_SUFFIX); + } + if (!options.getIgnoreImportProviders().contains("jcr.xml")) { + contentFileSuffixes.add(ContentFileTypes.JCR_XML_SUFFIX); + } } ContentFileExtensions contentFileExtensions = new ContentFileExtensions(contentFileSuffixes); this.contentFileCache = new ContentFileCache(config.provider_cache_size()); - this.fileMapper = new FileResourceMapper(this.providerRoot, this.providerFile, contentFileExtensions); - this.contentFileMapper = new ContentFileResourceMapper(this.providerRoot, this.providerFile, - contentFileExtensions, this.contentFileCache); + if (fsMode == FsMode.FILEVAULT_XML) { + this.fileVaultMapper = new FileVaultResourceMapper(this.providerFile, this.contentFileCache); + } + else { + this.fileMapper = new FileResourceMapper(this.providerRoot, this.providerFile, contentFileExtensions); + 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(), + if (config.provider_checkinterval() > 100) { + this.monitor = new FileMonitor(this, config.provider_checkinterval(), fsMode, contentFileExtensions, this.contentFileCache); } } @@ -287,10 +358,12 @@ public final class FsResourceProvider extends ResourceProvider<Object> { this.overlayParentResourceProvider = false; this.fileMapper = null; this.contentFileMapper = null; + this.fileVaultMapper = null; if (this.contentFileCache != null) { this.contentFileCache.clear(); this.contentFileCache = null; } + this.fsMode = null; } File getRootFile() { @@ -332,7 +405,7 @@ public final class FsResourceProvider extends ResourceProvider<Object> { public ObservationReporter getObservationReporter() { final ProviderContext ctx = this.getProviderContext(); - if ( ctx != null ) { + if (ctx != null) { return ctx.getObservationReporter(); } return null; diff --git a/src/main/java/org/apache/sling/fsprovider/internal/InitialContentImportOptions.java b/src/main/java/org/apache/sling/fsprovider/internal/InitialContentImportOptions.java new file mode 100644 index 0000000..5353d0f --- /dev/null +++ b/src/main/java/org/apache/sling/fsprovider/internal/InitialContentImportOptions.java @@ -0,0 +1,76 @@ +/* + * 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.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.lang3.BooleanUtils; +import org.apache.commons.lang3.StringUtils; + +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<String> ignoreImportProviders; + + public InitialContentImportOptions(String optionsString) { + Map<String,String> options = parseOptions(optionsString); + overwrite = BooleanUtils.toBoolean(options.get(OVERWRITE_DIRECTIVE)); + ignoreImportProviders = new HashSet<>(Arrays.asList(StringUtils.split(StringUtils.defaultString(options.get(IGNORE_CONTENT_READERS_DIRECTIVE))))); + } + + private static Map<String,String> parseOptions(String optionsString) { + Map<String,String> options = new HashMap<>(); + String[] optionsList = StringUtils.split(optionsString, ";"); + if (optionsList != null) { + for (String keyValueString : optionsList) { + String[] keyValue = StringUtils.splitByWholeSeparator(keyValueString, ":="); + if (keyValue.length == 2) { + options.put(StringUtils.trim(keyValue[0]), StringUtils.trim(keyValue[1])); + } + } + } + return options; + } + + public boolean isOverwrite() { + return overwrite; + } + + public Set<String> getIgnoreImportProviders() { + return ignoreImportProviders; + } + +} diff --git a/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFile.java b/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFile.java index d31a851..27e09d5 100644 --- a/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFile.java +++ b/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFile.java @@ -19,8 +19,11 @@ package org.apache.sling.fsprovider.internal.mapper; import java.io.File; +import java.util.Iterator; import java.util.Map; +import org.apache.commons.collections.IteratorUtils; +import org.apache.commons.collections.Predicate; import org.apache.sling.api.resource.ValueMap; import org.apache.sling.fsprovider.internal.parser.ContentFileCache; @@ -51,19 +54,6 @@ public final class ContentFile { } /** - * @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 - * @param content Content - */ - public ContentFile(File file, String path, String subPath, ContentFileCache contentFileCache, Object content) { - this(file, path, subPath, contentFileCache); - this.contentInitialized = true; - this.content = content; - } - - /** * @return File with content fragment */ public File getFile() { @@ -129,14 +119,47 @@ public final class ContentFile { } /** + * @return Child maps. + */ + @SuppressWarnings("unchecked") + public Iterator<Map.Entry<String,Map<String,Object>>> getChildren() { + if (!isResource()) { + return IteratorUtils.emptyIterator(); + } + return IteratorUtils.filteredIterator(((Map)getContent()).entrySet().iterator(), new Predicate() { + @Override + public boolean evaluate(Object object) { + Map.Entry<String,Object> entry = (Map.Entry<String,Object>)object; + return entry.getValue() instanceof Map; + } + }); + } + + /** * Navigate to another sub path position in content file. - * @param newSubPath New sub path + * @param newSubPath New sub path related to root path of content file * @return Content file */ - public ContentFile navigateTo(String newSubPath) { + 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); + } + @SuppressWarnings("unchecked") private static Object getDeepContent(Object object, String subPath) { if (object == null) { diff --git a/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFileResourceMapper.java b/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFileResourceMapper.java index e6edd04..09b6ffe 100644 --- a/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFileResourceMapper.java +++ b/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFileResourceMapper.java @@ -106,18 +106,10 @@ public final class ContentFileResourceMapper implements FsResourceMapper { // get child resources from content fragments in content file List<ContentFile> children = new ArrayList<>(); if (parentContentFile.hasContent() && parentContentFile.isResource()) { - Map<String,Object> content = (Map<String,Object>)parentContentFile.getContent(); - for (Map.Entry<String, Object> entry: content.entrySet()) { - if (entry.getValue() instanceof Map) { - String subPath; - if (parentContentFile.getSubPath() == null) { - subPath = entry.getKey(); - } - else { - subPath = parentContentFile.getSubPath() + "/" + entry.getKey(); - } - children.add(new ContentFile(parentContentFile.getFile(), parentContentFile.getPath(), subPath, contentFileCache, entry.getValue())); - } + Iterator<Map.Entry<String,Map<String,Object>>> childMaps = parentContentFile.getChildren(); + while (childMaps.hasNext()) { + Map.Entry<String,Map<String,Object>> entry = childMaps.next(); + children.add(parentContentFile.navigateToRelative(entry.getKey())); } } if (children.isEmpty()) { diff --git a/src/main/java/org/apache/sling/fsprovider/internal/mapper/FileVaultResourceMapper.java b/src/main/java/org/apache/sling/fsprovider/internal/mapper/FileVaultResourceMapper.java new file mode 100644 index 0000000..88e001e --- /dev/null +++ b/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 static org.apache.jackrabbit.vault.util.Constants.FILTER_XML; +import static org.apache.jackrabbit.vault.util.Constants.META_DIR; +import static org.apache.jackrabbit.vault.util.Constants.ROOT_DIR; + +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.FsResourceMapper; +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 ContentFileCache contentFileCache; + private final WorkspaceFilter workspaceFilter; + + private static final Logger log = LoggerFactory.getLogger(FileVaultResourceMapper.class); + + public FileVaultResourceMapper(File providerFile, ContentFileCache contentFileCache) { + this.providerFile = providerFile; + 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); + } + + // 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); + } + + return null; + } + + @SuppressWarnings("unchecked") + @Override + public Iterator<Resource> getChildren(final ResourceResolver resolver, final Resource parent) { + String parentPath = parent.getPath(); + + Set<String> childPaths = new LinkedHashSet<>(); + + // get children from content resource of parent + ContentFile parentContentFile = getContentFile(parentPath, null); + if (parentContentFile != null) { + Iterator<Map.Entry<String,Map<String,Object>>> childMaps = parentContentFile.getChildren(); + while (childMaps.hasNext()) { + Map.Entry<String,Map<String,Object>> entry = childMaps.next(); + String childPath = parentPath + "/" + entry.getKey(); + if (pathMatches(childPath)) { + childPaths.add(childPath); + } + } + } + + // additional check for children in filesystem + 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() { + File filter = new File(providerFile, META_DIR + "/" + FILTER_XML); + if (filter.exists()) { + try { + DefaultWorkspaceFilter workspaceFilter = new DefaultWorkspaceFilter(); + workspaceFilter.load(filter); + return workspaceFilter; + } catch (IOException | ConfigurationException ex) { + log.error("Unable to parse workspace filter: " + filter.getPath(), ex); + } + } + else { + log.warn("Workspace filter not found: " + filter.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 false; + } + 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, ROOT_DIR + PlatformNameFormat.getPlatformPath(path)); + if (file.exists()) { + return file; + } + return null; + } + + private ContentFile getContentFile(String path, String subPath) { + File file = new File(providerFile, ROOT_DIR + 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/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsNode.java b/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsNode.java index 6f52691..23abe66 100644 --- a/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsNode.java +++ b/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsNode.java @@ -110,22 +110,22 @@ public final class FsNode extends FsItem implements Node { else { subPath = path.substring(contentFile.getPath().length() + 1); } - ContentFile referencedFile = contentFile.navigateTo(subPath); + ContentFile referencedFile = contentFile.navigateToAbsolute(subPath); if (referencedFile.hasContent()) { return new FsNode(referencedFile, resolver); } } - else { - // 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; - } + + // 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); } diff --git a/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsNodeIterator.java b/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsNodeIterator.java index f03b0a7..09db7d4 100644 --- a/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsNodeIterator.java +++ b/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsNodeIterator.java @@ -63,14 +63,7 @@ class FsNodeIterator implements NodeIterator { @Override public Node nextNode() { Map.Entry<String,Map<String,Object>> nextEntry = children.next(); - String subPath; - if (contentFile.getSubPath() == null) { - subPath = nextEntry.getKey(); - } - else { - subPath = contentFile.getSubPath() + "/" + nextEntry.getKey(); - } - return new FsNode(contentFile.navigateTo(subPath), resolver); + return new FsNode(contentFile.navigateToRelative(nextEntry.getKey()), resolver); } diff --git a/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileParserUtil.java b/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileParserUtil.java index 4b46157..6ac72c5 100644 --- a/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileParserUtil.java +++ b/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileParserUtil.java @@ -18,6 +18,7 @@ */ 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.JSON_SUFFIX; @@ -62,11 +63,14 @@ class ContentFileParserUtil { * @return Content or null if content could not be parsed. */ public static Map<String,Object> parse(File file) { + if (!file.exists()) { + return null; + } try { if (StringUtils.endsWith(file.getName(), JSON_SUFFIX)) { return JSON_PARSER.parse(file); } - else if (StringUtils.endsWith(file.getName(), JCR_XML_SUFFIX)) { + else if (StringUtils.equals(file.getName(), DOT_CONTENT_XML) || StringUtils.endsWith(file.getName(), JCR_XML_SUFFIX)) { return JCR_XML_PARSER.parse(file); } } diff --git a/src/test/java/org/apache/sling/fsprovider/internal/FileMonitorTest.java b/src/test/java/org/apache/sling/fsprovider/internal/FileMonitorTest.java index 020405d..bc4892f 100644 --- a/src/test/java/org/apache/sling/fsprovider/internal/FileMonitorTest.java +++ b/src/test/java/org/apache/sling/fsprovider/internal/FileMonitorTest.java @@ -18,19 +18,19 @@ */ package org.apache.sling.fsprovider.internal; +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.ArrayList; import java.util.List; import org.apache.commons.io.FileUtils; -import org.apache.commons.lang3.StringUtils; import org.apache.sling.api.resource.observation.ResourceChange; import org.apache.sling.api.resource.observation.ResourceChange.ChangeType; import org.apache.sling.api.resource.observation.ResourceChangeListener; +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; @@ -39,10 +39,13 @@ import org.junit.Rule; import org.junit.Test; /** - * Test events when changing filesystem content. + * Test events when changing filesystem 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(); @@ -64,8 +67,9 @@ public class FileMonitorTest { context.registerInjectActivateService(new FsResourceProvider(), "provider.file", tempDir.getPath(), "provider.root", "/fs-test", - "provider.checkinterval", 120, - "provider.json.content", true); + "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(ResourceChangeListener.class, resourceListener, @@ -89,7 +93,7 @@ public class FileMonitorTest { File file1a = new File(tempDir, "folder1/file1a.txt"); FileUtils.touch(file1a); - Thread.sleep(250); + Thread.sleep(WAIT_INTERVAL); assertEquals(1, changes.size()); assertChange(changes, "/fs-test/folder1/file1a.txt", ChangeType.CHANGED); @@ -103,7 +107,7 @@ public class FileMonitorTest { File file1c = new File(tempDir, "folder1/file1c.txt"); FileUtils.write(file1c, "newcontent"); - Thread.sleep(250); + Thread.sleep(WAIT_INTERVAL); assertEquals(2, changes.size()); assertChange(changes, "/fs-test/folder1", ChangeType.CHANGED); @@ -118,7 +122,7 @@ public class FileMonitorTest { File file1a = new File(tempDir, "folder1/file1a.txt"); file1a.delete(); - Thread.sleep(250); + Thread.sleep(WAIT_INTERVAL); assertEquals(2, changes.size()); assertChange(changes, "/fs-test/folder1", ChangeType.CHANGED); @@ -133,7 +137,7 @@ public class FileMonitorTest { File folder99 = new File(tempDir, "folder99"); folder99.mkdir(); - Thread.sleep(250); + Thread.sleep(WAIT_INTERVAL); assertEquals(2, changes.size()); assertChange(changes, "/fs-test", ChangeType.CHANGED); @@ -148,7 +152,7 @@ public class FileMonitorTest { File folder1 = new File(tempDir, "folder1"); FileUtils.deleteDirectory(folder1); - Thread.sleep(250); + Thread.sleep(WAIT_INTERVAL); assertEquals(2, changes.size()); assertChange(changes, "/fs-test", ChangeType.CHANGED); @@ -156,30 +160,29 @@ public class FileMonitorTest { } @Test - public void testUpdateJsonContent() throws Exception { + public void testUpdateContent() throws Exception { List<ResourceChange> changes = resourceListener.getChanges(); assertTrue(changes.isEmpty()); File file1a = new File(tempDir, "folder2/content.json"); FileUtils.touch(file1a); - Thread.sleep(250); + Thread.sleep(WAIT_INTERVAL); - assertTrue(changes.size() > 1); assertChange(changes, "/fs-test/folder2/content", ChangeType.REMOVED); assertChange(changes, "/fs-test/folder2/content", ChangeType.ADDED); assertChange(changes, "/fs-test/folder2/content/jcr:content", ChangeType.ADDED); } @Test - public void testAddJsonContent() throws Exception { + public void testAddContent() throws Exception { List<ResourceChange> changes = resourceListener.getChanges(); assertTrue(changes.isEmpty()); File file1c = new File(tempDir, "folder1/file1c.json"); FileUtils.write(file1c, "{\"prop1\":\"value1\",\"child1\":{\"prop2\":\"value1\"}}"); - Thread.sleep(250); + Thread.sleep(WAIT_INTERVAL); assertEquals(3, changes.size()); assertChange(changes, "/fs-test/folder1", ChangeType.CHANGED); @@ -188,41 +191,18 @@ public class FileMonitorTest { } @Test - public void testRemoveJsonContent() throws Exception { + public void testRemoveContent() throws Exception { List<ResourceChange> changes = resourceListener.getChanges(); assertTrue(changes.isEmpty()); File file1a = new File(tempDir, "folder2/content.json"); file1a.delete(); - Thread.sleep(250); + Thread.sleep(WAIT_INTERVAL); assertEquals(2, changes.size()); assertChange(changes, "/fs-test/folder2", ChangeType.CHANGED); assertChange(changes, "/fs-test/folder2/content", ChangeType.REMOVED); } - - private void assertChange(List<ResourceChange> changes, String path, ChangeType changeType) { - boolean found = false; - for (ResourceChange change : changes) { - if (StringUtils.equals(change.getPath(), path) && change.getType() == changeType) { - found = true; - break; - } - } - assertTrue("Change with path=" + path + ", changeType=" + changeType, found); - } - - static class ResourceListener implements ResourceChangeListener { - private final List<ResourceChange> allChanges = new ArrayList<>(); - @Override - public void onChange(List<ResourceChange> changes) { - allChanges.addAll(changes); - } - public List<ResourceChange> getChanges() { - return allChanges; - } - } - } diff --git a/src/test/java/org/apache/sling/fsprovider/internal/FileVaultContentTest.java b/src/test/java/org/apache/sling/fsprovider/internal/FileVaultContentTest.java new file mode 100644 index 0000000..a6f6744 --- /dev/null +++ b/src/test/java/org/apache/sling/fsprovider/internal/FileVaultContentTest.java @@ -0,0 +1,147 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.sling.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.assertNull; +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.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", + "provider.root", "/content/dam/talk.png" + )) + .plugin(new RegisterFsResourcePlugin( + "provider.fs.mode", FsMode.FILEVAULT_XML.name(), + "provider.file", "src/test/resources/vaultfs-test", + "provider.root", "/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 = metadata.getValueMap(); + 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" }, enContent.getValueMap().get("app:deviceGroups", String[].class)); + } + + @Test + public void testListChildren() { + Resource en = sampleContent.getChild("en"); + List<Resource> 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")); + + // hidden because overlayed by resource provider + assertNull(context.resourceResolver().getResource("/content/samples/it")); + assertNull(sampleContent.getChild("it")); + + // list children with mixed content + Resource enResource = sampleContent.getChild("en"); + assertThat(enResource, ResourceMatchers.containsChildren("jcr:content", "tools", "conference")); + } + +} diff --git a/src/test/java/org/apache/sling/fsprovider/internal/FileVaultFileMonitorTest.java b/src/test/java/org/apache/sling/fsprovider/internal/FileVaultFileMonitorTest.java new file mode 100644 index 0000000..8fe5f3b --- /dev/null +++ b/src/test/java/org/apache/sling/fsprovider/internal/FileVaultFileMonitorTest.java @@ -0,0 +1,232 @@ +/* + * 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.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.api.resource.observation.ResourceChange; +import org.apache.sling.api.resource.observation.ResourceChange.ChangeType; +import org.apache.sling.api.resource.observation.ResourceChangeListener; +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; + +/** + * Test events when changing filesystem 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(), + "provider.root", "/content/dam/talk.png", + "provider.checkinterval", CHECK_INTERVAL, + "provider.fs.mode", FsMode.FILEVAULT_XML.name()); + context.registerInjectActivateService(new FsResourceProvider(), + "provider.file", tempDir.getPath(), + "provider.root", "/content/samples", + "provider.checkinterval", CHECK_INTERVAL, + "provider.fs.mode", FsMode.FILEVAULT_XML.name()); + + // register resource change listener + context.registerService(ResourceChangeListener.class, resourceListener, + ResourceChangeListener.PATHS, new String[] { "/content/dam/talk.png", "/content/samples" }); + } + }) + .afterTearDown(new SlingContextCallback() { + @Override + public void execute(SlingContext context) throws Exception { + // remove temp directory + tempDir.delete(); + } + }) + .build(); + + @Test + public void testUpdateFile() throws Exception { + List<ResourceChange> 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", ChangeType.CHANGED); + } + + @Test + public void testAddFile() throws Exception { + List<ResourceChange> 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", ChangeType.CHANGED); + assertChange(changes, "/content/dam/talk.png/jcr:content/renditions/text.txt", ChangeType.ADDED); + } + + @Test + public void testRemoveFile() throws Exception { + List<ResourceChange> 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", ChangeType.CHANGED); + assertChange(changes, "/content/dam/talk.png/jcr:content/renditions/web.1280.1280.png", ChangeType.REMOVED); + } + + @Test + public void testAddFolder() throws Exception { + List<ResourceChange> 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", ChangeType.CHANGED); + assertChange(changes, "/content/dam/talk.png/jcr:content/newfolder", ChangeType.ADDED); + } + + @Test + public void testRemoveFolder() throws Exception { + List<ResourceChange> 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", ChangeType.CHANGED); + assertChange(changes, "/content/dam/talk.png/jcr:content/renditions", ChangeType.REMOVED); + } + + @Test + public void testUpdateContent() throws Exception { + List<ResourceChange> 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", ChangeType.REMOVED); + assertChange(changes, "/content/samples/en", ChangeType.ADDED); + assertChange(changes, "/content/samples/en/jcr:content", ChangeType.ADDED); + } + + @Test + public void testAddContent() throws Exception { + List<ResourceChange> changes = resourceListener.getChanges(); + assertTrue(changes.isEmpty()); + + File file = new File(tempDir, "jcr_root/content/samples/fr/.content.xml"); + file.getParentFile().mkdir(); + FileUtils.write(file, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + + "<jcr:root xmlns:jcr=\"http://www.jcp.org/jcr/1.0\" xmlns:app=\"http://sample.com/jcr/app/1.0\" " + + "xmlns:sling=\"http://sling.apache.org/jcr/sling/1.0\" jcr:primaryType=\"app:Page\">\n" + + "<jcr:content jcr:primaryType=\"app:PageContent\"/>\n" + + "</jcr:root>"); + + Thread.sleep(WAIT_INTERVAL); + + assertChange(changes, "/content/samples", ChangeType.CHANGED); + assertChange(changes, "/content/samples/fr", ChangeType.ADDED); + assertChange(changes, "/content/samples/fr/jcr:content", ChangeType.ADDED); + } + + @Test + public void testRemoveContent() throws Exception { + List<ResourceChange> 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", ChangeType.CHANGED); + assertChange(changes, "/content/samples/en", ChangeType.REMOVED); + } + + @Test + public void testRemoveContentDotXmlOnly() throws Exception { + List<ResourceChange> 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", ChangeType.CHANGED); + // this second event is not fully correct, but this is a quite special case, accept it for now + assertChange(changes, "/content/samples/en", ChangeType.REMOVED); + } + +} diff --git a/src/test/java/org/apache/sling/fsprovider/internal/InitialContentImportOptionsTest.java b/src/test/java/org/apache/sling/fsprovider/internal/InitialContentImportOptionsTest.java new file mode 100644 index 0000000..513c51f --- /dev/null +++ b/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/src/test/java/org/apache/sling/fsprovider/internal/JcrXmlContentTest.java b/src/test/java/org/apache/sling/fsprovider/internal/JcrXmlContentTest.java index ded7f29..c1dc1c2 100644 --- a/src/test/java/org/apache/sling/fsprovider/internal/JcrXmlContentTest.java +++ b/src/test/java/org/apache/sling/fsprovider/internal/JcrXmlContentTest.java @@ -55,7 +55,10 @@ public class JcrXmlContentTest { @Rule public SlingContext context = new SlingContextBuilder(ResourceResolverType.JCR_MOCK) - .plugin(new RegisterFsResourcePlugin("provider.jcrxml.content", true)) + .plugin(new RegisterFsResourcePlugin( + "provider.fs.mode", FsMode.INITIAL_CONTENT.name(), + "provider.initial.content.import.options", "overwrite:=true;ignoreImportProviders:=json" + )) .build(); @Before diff --git a/src/test/java/org/apache/sling/fsprovider/internal/JsonContentTest.java b/src/test/java/org/apache/sling/fsprovider/internal/JsonContentTest.java index 031812f..74c7d81 100644 --- a/src/test/java/org/apache/sling/fsprovider/internal/JsonContentTest.java +++ b/src/test/java/org/apache/sling/fsprovider/internal/JsonContentTest.java @@ -56,7 +56,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; /** - * Test access to files and folders from filesystem. + * Test access to files and folders and JSON content from filesystem. */ public class JsonContentTest { @@ -65,7 +65,10 @@ public class JsonContentTest { @Rule public SlingContext context = new SlingContextBuilder(ResourceResolverType.JCR_MOCK) - .plugin(new RegisterFsResourcePlugin("provider.json.content", true)) + .plugin(new RegisterFsResourcePlugin( + "provider.fs.mode", FsMode.INITIAL_CONTENT.name(), + "provider.initial.content.import.options", "overwrite:=true;ignoreImportProviders:=jcr.xml" + )) .build(); @Before diff --git a/src/test/java/org/apache/sling/fsprovider/internal/TestUtils.java b/src/test/java/org/apache/sling/fsprovider/internal/TestUtils.java index 9ee6162..bbaf34b 100644 --- a/src/test/java/org/apache/sling/fsprovider/internal/TestUtils.java +++ b/src/test/java/org/apache/sling/fsprovider/internal/TestUtils.java @@ -28,13 +28,18 @@ 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.resource.Resource; +import org.apache.sling.api.resource.observation.ResourceChange; +import org.apache.sling.api.resource.observation.ResourceChangeListener; +import org.apache.sling.api.resource.observation.ResourceChange.ChangeType; import org.apache.sling.hamcrest.ResourceMatchers; import org.apache.sling.testing.mock.osgi.MapUtil; import org.apache.sling.testing.mock.osgi.context.AbstractContextPlugin; @@ -53,6 +58,7 @@ class TestUtils { config.put("provider.file", "src/test/resources/fs-test"); config.put("provider.root", "/fs-test"); config.put("provider.checkinterval", 0); + config.put("provider.fs.mode", FsMode.INITIAL_CONTENT_FILES_FOLDERS.name()); config.putAll(props); context.registerInjectActivateService(new FsResourceProvider(), config); } @@ -94,4 +100,26 @@ class TestUtils { } } + public static void assertChange(List<ResourceChange> changes, String path, ChangeType changeType) { + boolean found = false; + for (ResourceChange change : changes) { + if (StringUtils.equals(change.getPath(), path) && change.getType() == changeType) { + found = true; + break; + } + } + assertTrue("Change with path=" + path + ", changeType=" + changeType + " expected", found); + } + + public static class ResourceListener implements ResourceChangeListener { + private final List<ResourceChange> allChanges = new ArrayList<>(); + @Override + public void onChange(List<ResourceChange> changes) { + allChanges.addAll(changes); + } + public List<ResourceChange> getChanges() { + return allChanges; + } + } + } diff --git a/src/test/resources/vaultfs-test/META-INF/vault/filter.xml b/src/test/resources/vaultfs-test/META-INF/vault/filter.xml index 20be2d8..a56ee24 100644 --- a/src/test/resources/vaultfs-test/META-INF/vault/filter.xml +++ b/src/test/resources/vaultfs-test/META-INF/vault/filter.xml @@ -19,5 +19,7 @@ --> <workspaceFilter version="1.0"> <filter root="/content/dam/talk.png" /> - <filter root="/content/samples" /> + <filter root="/content/samples"> + <exclude pattern="/content/samples/en/conference(/.*)?"/> + </filter> </workspaceFilter> diff --git a/src/test/resources/vaultfs-test/jcr_root/content/dam/talk.png/.content.xml b/src/test/resources/vaultfs-test/jcr_root/content/dam/talk.png/.content.xml index 4f8312a..e207a1e 100644 --- a/src/test/resources/vaultfs-test/jcr_root/content/dam/talk.png/.content.xml +++ b/src/test/resources/vaultfs-test/jcr_root/content/dam/talk.png/.content.xml @@ -39,8 +39,6 @@ dc:format="image/png" dc:modified="{Date}2014-09-19T21:20:26.812+02:00" jcr:primaryType="nt:unstructured" - tiff:ImageLength="{Long}270" - tiff:ImageWidth="{Long}480" writebackEnable="{Boolean}true" /> </jcr:content> </jcr:root> -- To stop receiving notification emails like this one, please contact "[email protected]" <[email protected]>.
