blob: 2795c94ddf76af90f34a0333ea0daa0711b53b79 [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.pipes.internal;
import org.apache.commons.collections.IteratorUtils;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ValueMap;
import org.apache.sling.pipes.BasePipe;
import org.apache.sling.pipes.PipeBindings;
import org.apache.sling.pipes.Plumber;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.jcr.Node;
import javax.jcr.Property;
import javax.jcr.PropertyIterator;
import javax.jcr.RepositoryException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* Traverse either nodes or properties, in breadth first or depth first, for properties, they can be white listed
*/
public class TraversePipe extends BasePipe {
private static final Logger LOGGER = LoggerFactory.getLogger(TraversePipe.class);
public static final String RESOURCE_TYPE = RT_PREFIX + "traverse";
/**
* Pipe Constructor
*
* @param plumber plumber
* @param resource configuration resource
* @param upperBindings super pipe's bindings
*/
public TraversePipe(Plumber plumber, Resource resource, PipeBindings upperBindings) {
super(plumber, resource, upperBindings);
}
@Override
protected Iterator<Resource> computeOutput() {
return new TraversingIterator(getInput(), getResource().getValueMap());
}
/**
* iterative DFS or BFS jcr node tree iterator, transforming each visited node in a configured set of resources
*/
public class TraversingIterator implements Iterator<Resource>{
protected static final String PN_PROPERTIES = "properties";
protected static final String PN_NAMEGLOBS = "nameGlobs";
protected static final String PN_BREADTH = "breadthFirst";
protected static final String PN_DEPTH = "depth";
boolean properties;
int initialLevel;
int maxLevel;
String[] nameGlobs;
boolean breadthFirst;
Iterator<Resource> currentResources;
List<Node> nodesToVisit = new ArrayList<>();
/**
* From a given node, refresh resources extracted out of it depending on configuration
* @param node
* @throws RepositoryException
*/
void refreshResourceIterator(Node node) throws RepositoryException {
if (properties){
PropertyIterator it = nameGlobs != null ? node.getProperties(nameGlobs) : node.getProperties();
currentResources = IteratorUtils.transformedIterator(it, o -> {
try {
return resolver.getResource(((Property) o).getPath());
} catch (RepositoryException e) {
LOGGER.error("unable to read property", e);
}
return null;
});
} else {
currentResources = IteratorUtils.singletonIterator(resolver.getResource(node.getPath()));
}
}
int getDepth(String path) {
return path.split("/").length;
}
boolean isBeforeLastLevel(Node node) throws RepositoryException {
return maxLevel < 0 || getDepth(node.getPath()) < maxLevel;
}
/**
* Constructor with root node, & configuration
* @param root
* @param configuration
*/
TraversingIterator(Resource root, ValueMap configuration){
properties = configuration.get(PN_PROPERTIES, false);
if (properties) {
nameGlobs = configuration.get(PN_NAMEGLOBS, String[].class);
}
breadthFirst = configuration.get(PN_BREADTH, false);
maxLevel = configuration.get(PN_DEPTH, -1);
if (maxLevel > 0){
initialLevel = getDepth(root.getPath());
maxLevel = initialLevel + maxLevel;
}
nodesToVisit.add(root.adaptTo(Node.class));
}
/**
* Navigate up to the next node that have resources out of it
* @return
*/
boolean goToNextElligibleNode() {
try {
while ((currentResources == null || !currentResources.hasNext()) && !nodesToVisit.isEmpty()) {
Node node = nodesToVisit.remove(0);
LOGGER.debug("visiting {}", node.getPath());
refreshResourceIterator(node);
int indexAdd = breadthFirst ? nodesToVisit.size() : 0;
if (isBeforeLastLevel(node)) {
nodesToVisit.addAll(indexAdd, IteratorUtils.toList(node.getNodes()));
}
}
} catch (RepositoryException e) {
LOGGER.error("unable to read node subpipes", e);
}
return currentResources != null && currentResources.hasNext();
}
@Override
public boolean hasNext() {
return (currentResources != null && currentResources.hasNext()) || goToNextElligibleNode();
}
@Override
public Resource next() {
return currentResources.next();
}
}
}