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 eadcbb2fe9cd27feb4e1e6049ef78e2eef4ff3da Author: Stefan Seifert <[email protected]> AuthorDate: Fri Feb 24 20:45:26 2017 +0000 SLING-6440 further improvements for content file/json support: - enhance jcr api layer - make sure jcr primary type is always defined - send events for all nodes in content files - introduce simple LRU memory cache for parsed content files git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/fsresource@1784324 13f79535-47bb-0310-9956-ffa450edef68 --- .../sling/fsprovider/internal/FileMonitor.java | 130 +++++++++++++---- .../fsprovider/internal/FsResourceProvider.java | 25 +++- .../fsprovider/internal/mapper/ContentFile.java | 26 +++- .../internal/mapper/ContentFileResource.java | 7 +- .../internal/mapper/ContentFileResourceMapper.java | 19 +-- .../fsprovider/internal/mapper/ValueMapUtil.java | 9 ++ .../fsprovider/internal/mapper/jcr/FsNode.java | 35 +++-- .../fsprovider/internal/mapper/jcr/FsNodeType.java | 157 +++++++++++++++++++++ .../fsprovider/internal/mapper/jcr/FsProperty.java | 10 +- .../internal/mapper/jcr/FsPropertyDefinition.java | 100 +++++++++++++ .../internal/parser/ContentFileCache.java | 107 ++++++++++++++ .../internal/parser/ContentFileParser.java | 20 ++- ...ontentFileParser.java => ContentFileTypes.java} | 25 +--- .../sling/fsprovider/internal/FileMonitorTest.java | 20 ++- .../sling/fsprovider/internal/FilesFolderTest.java | 1 + .../sling/fsprovider/internal/JsonContentTest.java | 23 ++- .../internal/mapper/ContentFileTest.java | 13 +- .../internal/parser/ContentFileCacheTest.java | 92 ++++++++++++ src/test/resources/fs-test/folder2/content.json | 2 +- .../fs-test/folder2/content/file2content.txt | 1 + 20 files changed, 713 insertions(+), 109 deletions(-) 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 7249912..05d01b7 100644 --- a/src/main/java/org/apache/sling/fsprovider/internal/FileMonitor.java +++ b/src/main/java/org/apache/sling/fsprovider/internal/FileMonitor.java @@ -19,13 +19,19 @@ package org.apache.sling.fsprovider.internal; import java.io.File; -import java.util.Collections; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.Timer; import java.util.TimerTask; 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.fsprovider.internal.mapper.ContentFile; +import org.apache.sling.fsprovider.internal.parser.ContentFileCache; import org.apache.sling.spi.resource.provider.ObservationReporter; import org.apache.sling.spi.resource.provider.ObserverConfiguration; import org.slf4j.Logger; @@ -37,8 +43,7 @@ import org.slf4j.LoggerFactory; */ public final class FileMonitor extends TimerTask { - /** The logger. */ - private final Logger logger = LoggerFactory.getLogger(this.getClass()); + private final Logger log = LoggerFactory.getLogger(this.getClass()); private final Timer timer = new Timer(); private boolean stop = false; @@ -49,18 +54,21 @@ public final class FileMonitor extends TimerTask { private final FsResourceProvider provider; 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. */ - public FileMonitor(final FsResourceProvider provider, final long interval, final ContentFileExtensions contentFileExtensions) { + public FileMonitor(final FsResourceProvider provider, final long interval, + final ContentFileExtensions contentFileExtensions, final ContentFileCache contentFileCache) { this.provider = provider; this.contentFileExtensions = contentFileExtensions; + this.contentFileCache = contentFileCache; this.root = new Monitorable(this.provider.getProviderRoot(), this.provider.getRootFile(), null); - createStatus(this.root, contentFileExtensions); - logger.debug("Starting file monitor for {} with an interval of {}ms", this.root.file, interval); + 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); } @@ -90,7 +98,7 @@ public final class FileMonitor extends TimerTask { Thread.currentThread().interrupt(); } } - logger.debug("Stopped file monitor for {}", this.root.file); + log.debug("Stopped file monitor for {}", this.root.file); } /** @@ -129,24 +137,21 @@ public final class FileMonitor extends TimerTask { * @param reporter The ObservationReporter */ private void check(final Monitorable monitorable, final ObservationReporter reporter) { - logger.debug("Checking {}", monitorable.file); + 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); - sendEvents(monitorable, - ChangeType.ADDED, - reporter); + createStatus(monitorable, contentFileExtensions, contentFileCache); + sendEvents(monitorable, ChangeType.ADDED, reporter); } } else { // check if the file has been removed if ( !monitorable.file.exists() ) { // removed file and update status - sendEvents(monitorable, - ChangeType.REMOVED, - reporter); + sendEvents(monitorable, ChangeType.REMOVED, reporter); monitorable.status = NonExistingStatus.SINGLETON; + contentFileCache.remove(monitorable.path); } else { // check for changes final FileStatus fs = (FileStatus)monitorable.status; @@ -154,10 +159,9 @@ public final class FileMonitor extends TimerTask { if ( fs.lastModified < monitorable.file.lastModified() ) { fs.lastModified = monitorable.file.lastModified(); // changed - sendEvents(monitorable, - ChangeType.CHANGED, - reporter); + sendEvents(monitorable, ChangeType.CHANGED, reporter); changed = true; + contentFileCache.remove(monitorable.path); } if ( fs instanceof DirStatus ) { // directory @@ -200,28 +204,86 @@ public final class FileMonitor extends TimerTask { * Send the event async via the event admin. */ private void sendEvents(final Monitorable monitorable, final ChangeType changeType, final ObservationReporter reporter) { - if ( logger.isDebugEnabled() ) { - logger.debug("Detected change for resource {} : {}", monitorable.path, changeType); + if (log.isDebugEnabled()) { + log.debug("Detected change for resource {} : {}", monitorable.path, changeType); } - for(final ObserverConfiguration config : reporter.getObserverConfigurations()) { + for (final ObserverConfiguration config : reporter.getObserverConfigurations()) { if ( config.matches(monitorable.path) ) { - final ResourceChange change = new ResourceChange(changeType, monitorable.path, false, null, null, null); - reporter.reportChanges(Collections.singleton(change), false); + List<ResourceChange> changes = collectResourceChanges(monitorable, changeType); + if (log.isTraceEnabled()) { + for (ResourceChange change : changes) { + log.debug("Send change for resource {}: {} to {}", change.getPath(), change.getType(), config); + } + } + reporter.reportChanges(changes, false); + } + } + } + + @SuppressWarnings("unchecked") + private List<ResourceChange> collectResourceChanges(final Monitorable monitorable, final ChangeType changeType) { + List<ResourceChange> changes = new ArrayList<>(); + if (monitorable.status instanceof ContentFileStatus) { + ContentFile contentFile = ((ContentFileStatus)monitorable.status).contentFile; + if (changeType == ChangeType.CHANGED) { + 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, content, monitorable.path)); + addContentResourceChanges(changes, ChangeType.ADDED, content, monitorable.path); + } + else { + addContentResourceChanges(changes, changeType, (Map<String,Object>)contentFile.getContent(), monitorable.path); + } + } + else { + changes.add(new ResourceChange(changeType, monitorable.path, false, null, null, null)); + } + return changes; + } + @SuppressWarnings("unchecked") + private void addContentResourceChanges(final List<ResourceChange> changes, final ChangeType changeType, + final Map<String,Object> content, final String path) { + changes.add(buildContentResourceChange(changeType, content, path)); + if (content != null) { + for (Map.Entry<String,Object> entry : content.entrySet()) { + if (entry.getValue() instanceof Map) { + String childPath = path + "/" + entry.getKey(); + addContentResourceChanges(changes, changeType, (Map<String,Object>)entry.getValue(), childPath); + } } } } + private ResourceChange buildContentResourceChange(final ChangeType changeType, final Map<String,Object> content, final String path) { + Set<String> addedPropertyNames = null; + if (content != null && changeType == ChangeType.ADDED) { + addedPropertyNames = new HashSet<>(); + for (Map.Entry<String,Object> entry : content.entrySet()) { + if (!(entry.getValue() instanceof Map)) { + addedPropertyNames.add(entry.getKey()); + } + } + } + return new ResourceChange(changeType, path, false, addedPropertyNames, null, null); + } /** * Create a status object for the monitorable */ - private static void createStatus(final Monitorable monitorable, ContentFileExtensions contentFileExtensions) { + private static void createStatus(final Monitorable monitorable, ContentFileExtensions contentFileExtensions, ContentFileCache contentFileCache) { if ( !monitorable.file.exists() ) { monitorable.status = NonExistingStatus.SINGLETON; } else if ( monitorable.file.isFile() ) { - monitorable.status = new FileStatus(monitorable.file); + 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); + monitorable.status = new DirStatus(monitorable.file, monitorable.path, contentFileExtensions, contentFileCache); } } @@ -248,12 +310,22 @@ public final class FileMonitor extends TimerTask { 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) { + public DirStatus(final File dir, final String path, + final ContentFileExtensions contentFileExtensions, final ContentFileCache contentFileCache) { super(dir); final File[] files = dir.listFiles(); if (files != null) { @@ -261,7 +333,7 @@ public final class FileMonitor extends TimerTask { 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); + FileMonitor.createStatus(this.children[i], contentFileExtensions, contentFileCache); } } else { this.children = new Monitorable[0]; 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 f379bad..5d1ca9b 100644 --- a/src/main/java/org/apache/sling/fsprovider/internal/FsResourceProvider.java +++ b/src/main/java/org/apache/sling/fsprovider/internal/FsResourceProvider.java @@ -31,7 +31,8 @@ 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.parser.ContentFileParser; +import org.apache.sling.fsprovider.internal.parser.ContentFileCache; +import org.apache.sling.fsprovider.internal.parser.ContentFileTypes; import org.apache.sling.spi.resource.provider.ObservationReporter; import org.apache.sling.spi.resource.provider.ProviderContext; import org.apache.sling.spi.resource.provider.ResolveContext; @@ -108,7 +109,11 @@ public final class FsResourceProvider extends ResourceProvider<Object> { @AttributeDefinition(name = "Mount JSON", description = "Mount .json files as content in the resource hierarchy.") boolean provider_json_content(); - + + @AttributeDefinition(name = "Cache Size", + description = "Max. number of content files cached in memory.") + int provider_cache_size() default 1000; + /** * Internal Name hint for web console. */ @@ -130,6 +135,9 @@ public final class FsResourceProvider extends ResourceProvider<Object> { // if true resources from filesystem are only "overlayed" to JCR resources, serving JCR as fallback within the same path private boolean overlayParentResourceProvider; + + // cache for parsed content files + private ContentFileCache contentFileCache; /** * Returns a resource wrapping a file system file or folder for the given @@ -243,17 +251,20 @@ public final class FsResourceProvider extends ResourceProvider<Object> { List<String> contentFileSuffixes = new ArrayList<>(); if (config.provider_json_content()) { - contentFileSuffixes.add(ContentFileParser.JSON_SUFFIX); + contentFileSuffixes.add(ContentFileTypes.JSON_SUFFIX); this.overlayParentResourceProvider = false; } 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.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(), contentFileExtensions); + this.monitor = new FileMonitor(this, config.provider_checkinterval(), + contentFileExtensions, this.contentFileCache); } } @@ -268,6 +279,10 @@ public final class FsResourceProvider extends ResourceProvider<Object> { this.overlayParentResourceProvider = false; this.fileMapper = null; this.contentFileMapper = null; + if (this.contentFileCache != null) { + this.contentFileCache.clear(); + this.contentFileCache = null; + } } File getRootFile() { 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 319a3de..2e60b1f 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 @@ -22,7 +22,7 @@ import java.io.File; import java.util.Map; import org.apache.sling.api.resource.ValueMap; -import org.apache.sling.fsprovider.internal.parser.ContentFileParser; +import org.apache.sling.fsprovider.internal.parser.ContentFileCache; /** * Reference to a file that contains a content fragment (e.g. JSON, JCR XML). @@ -30,28 +30,35 @@ import org.apache.sling.fsprovider.internal.parser.ContentFileParser; public final class ContentFile { private final File file; + private final String path; private final String subPath; + private final ContentFileCache contentFileCache; private boolean contentInitialized; private Object 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 subPath) { + public ContentFile(File file, String path, String subPath, ContentFileCache contentFileCache) { this.file = file; + this.path = path; this.subPath = subPath; + this.contentFileCache = contentFileCache; } /** * @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 subPath, Object content) { - this.file = file; - this.subPath = subPath; + public ContentFile(File file, String path, String subPath, ContentFileCache contentFileCache, Object content) { + this(file, path, subPath, contentFileCache); this.contentInitialized = true; this.content = content; } @@ -62,6 +69,13 @@ public final class ContentFile { public File getFile() { return file; } + + /** + * @return Root path of content file + */ + public String getPath() { + return path; + } /** * @return Relative path addressing content fragment inside file @@ -76,7 +90,7 @@ public final class ContentFile { */ public Object getContent() { if (!contentInitialized) { - Map<String,Object> rootContent = ContentFileParser.parse(file); + Map<String,Object> rootContent = contentFileCache.get(path, file); content = getDeepContent(rootContent, subPath); contentInitialized = true; } diff --git a/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFileResource.java b/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFileResource.java index 11bfcbb..410419e 100644 --- a/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFileResource.java +++ b/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFileResource.java @@ -54,10 +54,11 @@ public final class ContentFileResource extends AbstractResource { * @param resourcePath The resource path in the resource tree * @param contentFile Content file with sub path */ - ContentFileResource(ResourceResolver resolver, String resourcePath, ContentFile contentFile) { + ContentFileResource(ResourceResolver resolver, ContentFile contentFile) { this.resolver = resolver; - this.resourcePath = resourcePath; this.contentFile = contentFile; + this.resourcePath = contentFile.getPath() + + (contentFile.getSubPath() != null ? "/" + contentFile.getSubPath() : ""); } public String getPath() { @@ -79,7 +80,7 @@ public final class ContentFileResource extends AbstractResource { public String getResourceSuperType() { if (resourceSuperType == null) { - resourceSuperType = contentFile.getValueMap().get("sling:resourceSuperType", String.class); + resourceSuperType = getValueMap().get("sling:resourceSuperType", String.class); } return resourceSuperType; } 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 1fe257c..b5306b9 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 @@ -32,6 +32,7 @@ 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.ContentFileCache; public final class ContentFileResourceMapper implements FsResourceMapper { @@ -42,18 +43,21 @@ public final class ContentFileResourceMapper implements FsResourceMapper { private final File providerFile; private final ContentFileExtensions contentFileExtensions; + private final ContentFileCache contentFileCache; - public ContentFileResourceMapper(String providerRoot, File providerFile, ContentFileExtensions contentFileExtensions) { + 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) { ContentFile contentFile = getFile(resourcePath, null); if (contentFile != null && contentFile.hasContent()) { - return new ContentFileResource(resolver, resourcePath, contentFile); + return new ContentFileResource(resolver, contentFile); } else { return null; @@ -78,9 +82,9 @@ public final class ContentFileResourceMapper implements FsResourceMapper { for (File file : parentFile.listFiles()) { String filenameSuffix = contentFileExtensions.getSuffix(file); if (filenameSuffix != null) { - ContentFile contentFile = new ContentFile(file, null); String path = parentPath + "/" + StringUtils.substringBeforeLast(file.getName(), filenameSuffix); - childResources.add(new ContentFileResource(resolver, path, contentFile)); + ContentFile contentFile = new ContentFile(file, path, null, contentFileCache); + childResources.add(new ContentFileResource(resolver, contentFile)); } } if (!childResources.isEmpty()) { @@ -106,7 +110,7 @@ public final class ContentFileResourceMapper implements FsResourceMapper { else { subPath = parentContentFile.getSubPath() + "/" + entry.getKey(); } - children.add(new ContentFile(parentContentFile.getFile(), subPath, entry.getValue())); + children.add(new ContentFile(parentContentFile.getFile(), parentContentFile.getPath(), subPath, contentFileCache, entry.getValue())); } } } @@ -118,8 +122,7 @@ public final class ContentFileResourceMapper implements FsResourceMapper { @Override public Object transform(Object input) { ContentFile contentFile = (ContentFile)input; - String path = parentPath + "/" + ResourceUtil.getName(contentFile.getSubPath()); - return new ContentFileResource(resolver, path, contentFile); + return new ContentFileResource(resolver, contentFile); } }); } @@ -133,7 +136,7 @@ public final class ContentFileResourceMapper implements FsResourceMapper { for (String filenameSuffix : contentFileExtensions.getSuffixes()) { File file = new File(providerFile, relPath + filenameSuffix); if (file.exists()) { - return new ContentFile(file, subPath); + return new ContentFile(file, path, subPath, contentFileCache); } } // try to find in parent path which contains content fragment diff --git a/src/main/java/org/apache/sling/fsprovider/internal/mapper/ValueMapUtil.java b/src/main/java/org/apache/sling/fsprovider/internal/mapper/ValueMapUtil.java index 67122b1..8ddbda7 100644 --- a/src/main/java/org/apache/sling/fsprovider/internal/mapper/ValueMapUtil.java +++ b/src/main/java/org/apache/sling/fsprovider/internal/mapper/ValueMapUtil.java @@ -22,6 +22,8 @@ import java.util.Collection; import java.util.HashMap; import java.util.Map; +import javax.jcr.nodetype.NodeType; + import org.apache.sling.api.resource.ValueMap; import org.apache.sling.api.wrappers.ValueMapDecorator; @@ -38,6 +40,7 @@ final class ValueMapUtil { */ public static ValueMap toValueMap(Map<String,Object> content) { Map<String,Object> props = new HashMap<>(); + for (Map.Entry<String, Object> entry : ((Map<String,Object>)content).entrySet()) { if (entry.getValue() instanceof Map) { // skip child resources @@ -51,6 +54,12 @@ final class ValueMapUtil { props.put(entry.getKey(), entry.getValue()); } } + + // fallback to default jcr:primaryType is none is set + if (!props.containsKey("jcr:primaryType")) { + props.put("jcr:primaryType", NodeType.NT_UNSTRUCTURED); + } + return new ValueMapDecorator(props); } 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 308701c..1d4b842 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 @@ -63,6 +63,14 @@ public final class FsNode extends FsItem implements Node { super(resource); } + private String getPrimaryTypeName() { + return props.get("jcr:primaryType", String.class); + } + + private String[] getMixinTypeNames() { + return props.get("jcr:mixinTypes", new String[0]); + } + @Override public Node getNode(String relPath) throws PathNotFoundException, RepositoryException { Resource child = resource.getChild(relPath); @@ -123,7 +131,7 @@ public final class FsNode extends FsItem implements Node { @Override public boolean isNodeType(String nodeTypeName) throws RepositoryException { - return StringUtils.equals(nodeTypeName, props.get("jcr:primaryType", String.class)); + return StringUtils.equals(nodeTypeName, getPrimaryTypeName()); } @Override @@ -146,6 +154,21 @@ public final class FsNode extends FsItem implements Node { 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<mixinTypeNames.length; i++) { + mixinTypes[i] = new FsNodeType(mixinTypeNames[i], true); + } + return mixinTypes; + } + // --- unsupported methods --- @@ -444,11 +467,6 @@ public final class FsNode extends FsItem implements Node { } @Override - public NodeType getPrimaryNodeType() throws RepositoryException { - throw new UnsupportedOperationException(); - } - - @Override public String getIdentifier() throws RepositoryException { throw new UnsupportedOperationException(); } @@ -459,11 +477,6 @@ public final class FsNode extends FsItem implements Node { } @Override - public NodeType[] getMixinNodeTypes() throws RepositoryException { - throw new UnsupportedOperationException(); - } - - @Override public Item getPrimaryItem() throws ItemNotFoundException, RepositoryException { throw new UnsupportedOperationException(); } diff --git a/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsNodeType.java b/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsNodeType.java new file mode 100644 index 0000000..8d60812 --- /dev/null +++ b/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/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsProperty.java b/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsProperty.java index 4d1d138..469bd11 100644 --- a/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsProperty.java +++ b/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsProperty.java @@ -143,7 +143,12 @@ class FsProperty extends FsItem implements Property { return getValue().getType(); } + @Override + public PropertyDefinition getDefinition() throws RepositoryException { + return new FsPropertyDefinition(propertyName); + } + // --- unsupported methods --- @Override @@ -219,11 +224,6 @@ class FsProperty extends FsItem implements Property { } @Override - public PropertyDefinition getDefinition() throws RepositoryException { - throw new UnsupportedOperationException(); - } - - @Override public Property getProperty() throws ItemNotFoundException, ValueFormatException, RepositoryException { throw new UnsupportedOperationException(); } diff --git a/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsPropertyDefinition.java b/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsPropertyDefinition.java new file mode 100644 index 0000000..85920cb --- /dev/null +++ b/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsPropertyDefinition.java @@ -0,0 +1,100 @@ +/* + * 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.PropertyType; +import javax.jcr.Value; +import javax.jcr.nodetype.NodeType; +import javax.jcr.nodetype.PropertyDefinition; +import javax.jcr.version.OnParentVersionAction; + +class FsPropertyDefinition implements PropertyDefinition { + + private final String name; + + public FsPropertyDefinition(String name) { + this.name = name; + } + + @Override + public String getName() { + return name; + } + + @Override + public NodeType getDeclaringNodeType() { + return null; + } + + @Override + public boolean isAutoCreated() { + return false; + } + + @Override + public boolean isMandatory() { + return false; + } + + @Override + public int getOnParentVersion() { + return OnParentVersionAction.COPY; + } + + @Override + public boolean isProtected() { + return false; + } + + @Override + public int getRequiredType() { + return PropertyType.UNDEFINED; + } + + @Override + public String[] getValueConstraints() { + return new String[0]; + } + + @Override + public Value[] getDefaultValues() { + return new Value[0]; + } + + @Override + public boolean isMultiple() { + return false; + } + + @Override + public String[] getAvailableQueryOperators() { + return new String[0]; + } + + @Override + public boolean isFullTextSearchable() { + return false; + } + + @Override + public boolean isQueryOrderable() { + return false; + } + +} diff --git a/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileCache.java b/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileCache.java new file mode 100644 index 0000000..289fb67 --- /dev/null +++ b/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<String,Map<String,Object>> contentCache; + private final Map<String,Object> NULL_MAP = 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 Map<String,Object> get(String path, File file) { + Map<String,Object> content = null; + if (contentCache != null) { + content = contentCache.get(path); + } + if (content == null) { + content = ContentFileParser.parse(file); + if (content == null) { + content = NULL_MAP; + } + if (contentCache != null) { + contentCache.put(path, content); + } + } + if (content == NULL_MAP) { + 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/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileParser.java b/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileParser.java index 46d5b85..8f7a11f 100644 --- a/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileParser.java +++ b/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileParser.java @@ -18,20 +18,21 @@ */ package org.apache.sling.fsprovider.internal.parser; +import static org.apache.sling.fsprovider.internal.parser.ContentFileTypes.JSON_SUFFIX; + import java.io.File; import java.util.Map; import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Parses file that contains content fragments (e.g. JSON, JCR XML). */ -public final class ContentFileParser { +class ContentFileParser { - /** - * JSON content files. - */ - public static final String JSON_SUFFIX = ".json"; + private static final Logger log = LoggerFactory.getLogger(ContentFileParser.class); private ContentFileParser() { // static methods only @@ -43,8 +44,13 @@ public final class ContentFileParser { * @return Content or null if content could not be parsed. */ public static Map<String,Object> parse(File file) { - if (StringUtils.endsWith(file.getName(), JSON_SUFFIX)) { - return JsonFileParser.parse(file); + try { + if (StringUtils.endsWith(file.getName(), JSON_SUFFIX)) { + return JsonFileParser.parse(file); + } + } + catch (Throwable ex) { + log.warn("Error parsing content from " + file.getPath(), ex); } return null; } diff --git a/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileParser.java b/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileTypes.java similarity index 62% copy from src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileParser.java copy to src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileTypes.java index 46d5b85..9d995e6 100644 --- a/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileParser.java +++ b/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileTypes.java @@ -18,35 +18,18 @@ */ package org.apache.sling.fsprovider.internal.parser; -import java.io.File; -import java.util.Map; - -import org.apache.commons.lang3.StringUtils; - /** - * Parses file that contains content fragments (e.g. JSON, JCR XML). + * Content file types. */ -public final class ContentFileParser { +public final class ContentFileTypes { /** * JSON content files. */ public static final String JSON_SUFFIX = ".json"; - - private ContentFileParser() { + + private ContentFileTypes() { // 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 Map<String,Object> parse(File file) { - if (StringUtils.endsWith(file.getName(), JSON_SUFFIX)) { - return JsonFileParser.parse(file); - } - return null; - } - } 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 a6f7568..30012ef 100644 --- a/src/test/java/org/apache/sling/fsprovider/internal/FileMonitorTest.java +++ b/src/test/java/org/apache/sling/fsprovider/internal/FileMonitorTest.java @@ -37,6 +37,8 @@ import org.apache.sling.testing.mock.sling.junit.SlingContextCallback; import org.junit.Rule; import org.junit.Test; +import com.google.common.collect.ImmutableSet; + /** * Test events when changing filesystem content. */ @@ -164,8 +166,10 @@ public class FileMonitorTest { Thread.sleep(250); - assertEquals(1, changes.size()); - assertChange(changes, 0, "/fs-test/folder2/content", ChangeType.CHANGED); + assertTrue(changes.size() > 1); + assertChange(changes, 0, "/fs-test/folder2/content", ChangeType.REMOVED); + assertChange(changes, 1, "/fs-test/folder2/content", ChangeType.ADDED); + assertChange(changes, 2, "/fs-test/folder2/content/jcr:content", ChangeType.ADDED); } @Test @@ -174,13 +178,14 @@ public class FileMonitorTest { assertTrue(changes.isEmpty()); File file1c = new File(tempDir, "folder1/file1c.json"); - FileUtils.write(file1c, "{'prop1':'value1'}"); + FileUtils.write(file1c, "{\"prop1\":\"value1\",\"child1\":{\"prop2\":\"value1\"}}"); Thread.sleep(250); - assertEquals(2, changes.size()); + assertEquals(3, changes.size()); assertChange(changes, 0, "/fs-test/folder1", ChangeType.CHANGED); - assertChange(changes, 1, "/fs-test/folder1/file1c", ChangeType.ADDED); + assertChange(changes, 1, "/fs-test/folder1/file1c", ChangeType.ADDED, "prop1"); + assertChange(changes, 2, "/fs-test/folder1/file1c/child1", ChangeType.ADDED, "prop2"); } @Test @@ -199,10 +204,13 @@ public class FileMonitorTest { } - private void assertChange(List<ResourceChange> changes, int index, String path, ChangeType changeType) { + private void assertChange(List<ResourceChange> changes, int index, String path, ChangeType changeType, String... addedPropertyNames) { ResourceChange change = changes.get(index); assertEquals(path, change.getPath()); assertEquals(changeType, change.getType()); + if (addedPropertyNames.length > 0) { + assertEquals(ImmutableSet.copyOf(addedPropertyNames), change.getAddedPropertyNames()); + } } static class ResourceListener implements ResourceChangeListener { diff --git a/src/test/java/org/apache/sling/fsprovider/internal/FilesFolderTest.java b/src/test/java/org/apache/sling/fsprovider/internal/FilesFolderTest.java index 578befd..403592a 100644 --- a/src/test/java/org/apache/sling/fsprovider/internal/FilesFolderTest.java +++ b/src/test/java/org/apache/sling/fsprovider/internal/FilesFolderTest.java @@ -65,6 +65,7 @@ public class FilesFolderTest { 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"); } @Test 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 1dcf7a4..34240a8 100644 --- a/src/test/java/org/apache/sling/fsprovider/internal/JsonContentTest.java +++ b/src/test/java/org/apache/sling/fsprovider/internal/JsonContentTest.java @@ -38,6 +38,7 @@ 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.ValueMap; @@ -85,6 +86,7 @@ public class JsonContentTest { 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"); } @Test @@ -144,11 +146,11 @@ public class JsonContentTest { assertEquals("/fs-test/folder2/content/toolbar/profiles/jcr:content", node.getPath()); assertEquals(6, node.getDepth()); - assertTrue(node.isNodeType("app:PageContent")); 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()); @@ -196,10 +198,27 @@ public class JsonContentTest { Node parent = rightpar.getParent(); assertTrue(node.isSame(parent)); Node ancestor = (Node)rightpar.getAncestor(4); - assertEquals(underTest.getParent().getPath(), ancestor.getPath()); + assertEquals(underTest.getParent().getPath(), ancestor.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(NodeType.NT_UNSTRUCTURED, underTest.adaptTo(Node.class).getPrimaryNodeType().getName()); + } + + @Test public void testJsonContent_InvalidPath() { Resource underTest = fsroot.getChild("folder2/content/jcr:content/xyz"); assertNull(underTest); diff --git a/src/test/java/org/apache/sling/fsprovider/internal/mapper/ContentFileTest.java b/src/test/java/org/apache/sling/fsprovider/internal/mapper/ContentFileTest.java index b3c18ee..4659991 100644 --- a/src/test/java/org/apache/sling/fsprovider/internal/mapper/ContentFileTest.java +++ b/src/test/java/org/apache/sling/fsprovider/internal/mapper/ContentFileTest.java @@ -27,16 +27,19 @@ import java.io.File; import java.util.Map; import org.apache.sling.api.resource.ValueMap; +import org.apache.sling.fsprovider.internal.parser.ContentFileCache; import org.junit.Test; public class ContentFileTest { + + private ContentFileCache contentFileCache = new ContentFileCache(0); @SuppressWarnings("unchecked") @Test public void testRootContent() { File file = new File("src/test/resources/fs-test/folder2/content.json"); - ContentFile underTest = new ContentFile(file, null); + ContentFile underTest = new ContentFile(file, "/fs-test/folder2/content", null, contentFileCache); assertEquals(file, underTest.getFile()); assertNull(underTest.getSubPath()); @@ -56,7 +59,7 @@ public class ContentFileTest { public void testContentLevel1() { File file = new File("src/test/resources/fs-test/folder2/content.json"); - ContentFile underTest = new ContentFile(file, "jcr:content"); + ContentFile underTest = new ContentFile(file, "/fs-test/folder2/content", "jcr:content", contentFileCache); assertEquals(file, underTest.getFile()); assertEquals("jcr:content", underTest.getSubPath()); @@ -74,7 +77,7 @@ public class ContentFileTest { public void testContentLevel5() { File file = new File("src/test/resources/fs-test/folder2/content.json"); - ContentFile underTest = new ContentFile(file, "jcr:content/par/image/file/jcr:content"); + 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()); @@ -91,7 +94,7 @@ public class ContentFileTest { public void testContentProperty() { File file = new File("src/test/resources/fs-test/folder2/content.json"); - ContentFile underTest = new ContentFile(file, "jcr:content/jcr:title"); + 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()); @@ -105,7 +108,7 @@ public class ContentFileTest { @Test public void testInvalidFile() { File file = new File("src/test/resources/fs-test/folder1/file1a.txt"); - ContentFile underTest = new ContentFile(file, null); + ContentFile underTest = new ContentFile(file, "/fs-test/folder1/file1a", null, contentFileCache); assertFalse(underTest.hasContent()); } diff --git a/src/test/java/org/apache/sling/fsprovider/internal/parser/ContentFileCacheTest.java b/src/test/java/org/apache/sling/fsprovider/internal/parser/ContentFileCacheTest.java new file mode 100644 index 0000000..1eaf1e1 --- /dev/null +++ b/src/test/java/org/apache/sling/fsprovider/internal/parser/ContentFileCacheTest.java @@ -0,0 +1,92 @@ +/* + * 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 java.util.Map; + +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); + + Map<String,Object> 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; + } + + Map<String,Object> 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/src/test/resources/fs-test/folder2/content.json b/src/test/resources/fs-test/folder2/content.json index e808ef8..f35baf8 100644 --- a/src/test/resources/fs-test/folder2/content.json +++ b/src/test/resources/fs-test/folder2/content.json @@ -102,7 +102,6 @@ } }, "title_2": { - "jcr:primaryType": "nt:unstructured", "jcr:createdBy": "admin", "jcr:title": "Shape Technology", "jcr:lastModifiedBy": "admin", @@ -220,6 +219,7 @@ "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": "/apps/sample/templates/contentpage", diff --git a/src/test/resources/fs-test/folder2/content/file2content.txt b/src/test/resources/fs-test/folder2/content/file2content.txt new file mode 100644 index 0000000..667b547 --- /dev/null +++ b/src/test/resources/fs-test/folder2/content/file2content.txt @@ -0,0 +1 @@ +file2content \ No newline at end of file -- To stop receiving notification emails like this one, please contact "[email protected]" <[email protected]>.
