blob: bf37584736e0b5338a53581bcef73c1e5543508f [file] [log] [blame]
/*
* 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.bundleresource.impl;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.TreeSet;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ResourceUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The <code>BundleResourceIterator</code> class is a resource iterator, which
* returns resources for each Bundle entry of an underlying enumeration of
* Bundle entry paths.
*/
class BundleResourceIterator implements Iterator<Resource> {
/** default log */
private final Logger log = LoggerFactory.getLogger(getClass());
/** The bundle resource resolver */
private final ResourceResolver resourceResolver;
/** Bundle providing the entry resources */
private final BundleResourceCache cache;
private final PathMapping mappedPath;
/** Underlying bundle entry path enumeration */
private final Iterator<String> entries;
/** The length of the parent entry path, see seek() */
private final int prefixLength;
/** The prefetched next iterator entry, null at the end of iterating */
private Resource nextResult;
private final Map<String, Map<String, Object>> subResources;
/**
* Creates an instance using the given parent bundle resource.
*/
BundleResourceIterator(final BundleResource parent) {
// trailing slash to enumerate children
final String parentEntryPath = parent.getMappedPath().getEntryPath(parent.getPath().concat("/"));
this.prefixLength = parentEntryPath.length();
this.resourceResolver = parent.getResourceResolver();
this.cache = parent.getBundle();
this.subResources = parent.getSubResources() != null ? new HashMap<>(parent.getSubResources()) : null;
this.mappedPath = parent.getMappedPath();
this.entries = getFilteredEntries(parentEntryPath);
this.nextResult = (entries != null) ? seek() : null;
}
BundleResourceIterator(ResourceResolver resourceResolver, BundleResourceCache bundle,
PathMapping mappedPath, String parentPath) {
// trailing slash to enumerate children
if (!parentPath.endsWith("/")) {
parentPath = parentPath.concat("/");
}
final String parentEntryPath = mappedPath.getEntryPath(parentPath);
this.prefixLength = parentEntryPath.length();
this.resourceResolver = resourceResolver;
this.cache = bundle;
this.subResources = null;
this.mappedPath = mappedPath;
this.entries = getFilteredEntries(parentEntryPath);
this.nextResult = (entries != null) ? seek() : null;
}
private Iterator<String> getFilteredEntries(final String parentEntryPath) {
final Set<String> bundleEntries = new TreeSet<>(cache.getEntryPaths(parentEntryPath));
if ( this.mappedPath.getJSONPropertiesExtension() != null ) {
final Set<String> add = new HashSet<>();
final Iterator<String> iter = bundleEntries.iterator();
while ( iter.hasNext() ) {
final String path = iter.next();
if ( path.endsWith(this.mappedPath.getJSONPropertiesExtension()) ) {
iter.remove();
add.add(path.substring(0, path.length() - this.mappedPath.getJSONPropertiesExtension().length()));
}
}
bundleEntries.addAll(add);
if ( subResources != null ) {
for(final String name : subResources.keySet()) {
final String fullPath = parentEntryPath.concat(name);
if ( !bundleEntries.contains(fullPath) ) {
bundleEntries.add(fullPath);
} else {
subResources.remove(name);
}
}
}
}
return (bundleEntries.isEmpty() ? null : bundleEntries.iterator());
}
/** Returns true if there is another Resource available */
@Override
public boolean hasNext() {
return nextResult != null;
}
/** Returns the next resource in the iterator */
@Override
public Resource next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
final Resource result = nextResult;
nextResult = seek();
return result;
}
/**
* Throws <code>UnsupportedOperationException</code> as this method is not
* supported by this implementation.
*/
@Override
public void remove() {
throw new UnsupportedOperationException();
}
/**
* Scans the entry path enumeration looking for the next entry being a
* direct child of the parent resource.
*/
private Resource seek() {
while (entries.hasNext()) {
String entry = entries.next();
// require leading slash (sanity check, should always be the case)
if (!entry.startsWith("/")) {
entry = "/".concat(entry);
}
// another sanity check if the prefix is correct
final int slash = entry.indexOf('/', prefixLength);
if (slash < 0 || slash == entry.length() - 1) {
log.debug("seek: Using entry {}", entry);
final boolean isFolder = entry.endsWith("/");
final String entryPath = isFolder ? entry.substring(0, entry.length()-1) : entry;
return new BundleResource(resourceResolver, cache, mappedPath,
mappedPath.getResourcePath(entryPath),
this.subResources != null ? this.subResources.get(ResourceUtil.getName(entryPath)) : null,
isFolder);
}
log.debug("seek: Ignoring entry {}", entry);
}
// no more results
log.debug("seek: No more nodes, iterator exhausted");
return null;
}
}