/**
* This file Copyright (c) 2011-2012 Magnolia International
* Ltd. (http://www.magnolia-cms.com). All rights reserved.
*
*
* This file is dual-licensed under both the Magnolia
* Network Agreement and the GNU General Public License.
* You may elect to use one or the other of these licenses.
*
* This file is distributed in the hope that it will be
* useful, but AS-IS and WITHOUT ANY WARRANTY; without even the
* implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT.
* Redistribution, except as permitted by whichever of the GPL
* or MNA you select, is prohibited.
*
* 1. For the GPL license (GPL), you can redistribute and/or
* modify this file under the terms of the GNU General
* Public License, Version 3, as published by the Free Software
* Foundation. You should have received a copy of the GNU
* General Public License, Version 3 along with this program;
* if not, write to the Free Software Foundation, Inc., 51
* Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* 2. For the Magnolia Network Agreement (MNA), this file
* and the accompanying materials are made available under the
* terms of the MNA which accompanies this distribution, and
* is available at http://www.magnolia-cms.com/mna.html
*
* Any modifications to this file must keep this entire header
* intact.
*
*/
package info.magnolia.jcr.inheritance;
import info.magnolia.jcr.RuntimeRepositoryException;
import info.magnolia.jcr.decoration.AbstractContentDecorator;
import info.magnolia.jcr.decoration.ContentDecoratorNodeWrapper;
import info.magnolia.jcr.iterator.ChainedNodeIterator;
import info.magnolia.jcr.iterator.FilteringNodeIterator;
import info.magnolia.jcr.iterator.RangeIteratorImpl;
import info.magnolia.jcr.predicate.AbstractPredicate;
import info.magnolia.jcr.util.NodeUtil;
import javax.jcr.Node;
import javax.jcr.NodeIterator;
import javax.jcr.PathNotFoundException;
import javax.jcr.Property;
import javax.jcr.PropertyIterator;
import javax.jcr.RepositoryException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
/**
* Provides inheritance on JCR level by applying wrapper objects. The inheritance destination node gets nodes and
* properties from a list of source nodes. Subclasses can customize which nodes are inherited and how the inherited
* nodes are ordered.
*
* <h3>Limitations:</h3>
* <ul><li>The inherited nodes are returned while querying for them on the destination node. The will not be visible when
* queried for on other nodes or the session.</li>
* <li>The inherited nodes will *not* have their path or depth adjusted to match the destination node</li>
* <li>When inheritance results in multiple nodes that have the same name the index on nodes are not adjusted</li>
* </ul>
*
* @version $Id$
*/
public class InheritanceContentDecorator extends AbstractContentDecorator {
// The node to which we inherit
private final Node destination;
// The nodes we inherit from in bottom-up order
private List<Node> sources = new ArrayList<Node>();
private AbstractPredicate<Node> childInheritancePredicate = new AbstractPredicate<Node>() {
@Override
public boolean evaluateTyped(Node node) {
try {
return isSourceChildInherited(node);
} catch (RepositoryException e) {
throw new RuntimeRepositoryException(e);
}
}
};
public InheritanceContentDecorator(Node destination) throws RepositoryException {
this.destination = destination;
}
@Override
public Node wrapNode(Node node) {
try {
if (NodeUtil.isSame(destination, node)) {
return new DestinationNodeInheritanceNodeWrapper(node);
} else {
return new OtherNodeInheritanceNodeWrapper(node);
}
} catch (RepositoryException e) {
throw new RuntimeRepositoryException(e);
}
}
public Node getDestination() {
return destination;
}
/**
* Returns a predicate that delegates to {@link #isSourceChildInherited(Node)}.
*/
private AbstractPredicate<Node> getChildInheritancePredicate() {
if (childInheritancePredicate != null) {
return childInheritancePredicate;
}
childInheritancePredicate = new AbstractPredicate<Node>() {
@Override
public boolean evaluateTyped(Node node) {
try {
return isSourceChildInherited(node);
} catch (RepositoryException e) {
throw new RuntimeRepositoryException(e);
}
}
};
return childInheritancePredicate;
}
public void addSource(Node source) {
this.sources.add(source);
}
/**
* Decides if a node inherits child nodes. By default always returns true.
*
* @param node the destination node or a source node
* @return true if the node inherits nodes
* @throws RepositoryException
*/
protected boolean inheritsNodes(Node node) throws RepositoryException {
return true;
}
/**
* Decides if a node inherits properties. By default always returns true.
*
* @param node the destination node or a source node
* @return true if the node inherits properties
* @throws RepositoryException
*/
protected boolean inheritsProperties(Node node) throws RepositoryException {
return true;
}
/**
* Decides if a specific child node of one of the source should be inherited. By default always returns true.
*
* @param node a child of one of the source nodes
* @return true if the node is inherited
* @throws RepositoryException
*/
protected boolean isSourceChildInherited(Node node) throws RepositoryException {
return true;
}
/**
* Sorts the inherited nodes and provides a {@link NodeIterator} representing that order. By default orders nodes
* from the top-most source first and nodes from the destination last.
*
* @param destinationChildren children of the destination node
* @param sourceChildren children of each of the source nodes in bottom-up order
* @return
* @throws javax.jcr.RepositoryException
*/
protected NodeIterator sortInheritedNodes(NodeIterator destinationChildren, List<NodeIterator> sourceChildren) throws RepositoryException {
Collections.reverse(sourceChildren);
sourceChildren.add(destinationChildren);
return new FilteringNodeIterator(new ChainedNodeIterator(sourceChildren), getChildInheritancePredicate());
}
/**
* Combines the inherited properties and provides them as a {@link PropertyIterator}. By default properties in the
* destination node overrides properties from source nodes. Properties on source node have preference in a bottom to
* top order.
*
* @param destinationProperties properties of the destination node
* @param sourceProperties properties of the source nodes in bottom-up order
* @return
* @throws RepositoryException
*/
protected PropertyIterator combinePropertyIterators(PropertyIterator destinationProperties, List<PropertyIterator> sourceProperties) throws RepositoryException {
HashSet<String> names = new HashSet<String>();
ArrayList<Property> properties = new ArrayList<Property>();
while (destinationProperties.hasNext()) {
Property property = destinationProperties.nextProperty();
names.add(property.getName());
properties.add(property);
}
for (PropertyIterator propertyIterator : sourceProperties) {
while (propertyIterator.hasNext()) {
Property property = (Property) propertyIterator.next();
if (!names.contains(property.getName())) {
names.add(property.getName());
properties.add(property);
}
}
}
return new PropertyIteratorImpl(properties);
}
/**
* Wrapper applied to nodes other than the destination node.
*
* @version $Id$
*/
private class OtherNodeInheritanceNodeWrapper extends ContentDecoratorNodeWrapper implements InheritanceNodeWrapper {
public OtherNodeInheritanceNodeWrapper(Node node) {
super(node, InheritanceContentDecorator.this);
}
@Override
public boolean isInherited() {
try {
Node wrappedNode = getWrappedNode();
if (isChildOf(wrappedNode, destination)) {
return false;
}
Node n = destination;
Iterator<Node> iterator = sources.iterator();
while (iterator.hasNext() && inheritsNodes(n)) {
n = iterator.next();
if (isChildOf(wrappedNode, n) && isSourceChildInherited(wrappedNode)) {
return true;
}
}
return false;
} catch (RepositoryException e) {
throw new RuntimeRepositoryException(e);
}
}
private boolean isChildOf(Node child, Node parent) {
try {
return parent.getDepth() == 0 || child.getPath().startsWith(parent.getPath() + "/");
} catch (RepositoryException e) {
throw new RuntimeRepositoryException(e);
}
}
}
/**
* Wrapper applied to the destination node. Uses
*
* @version $Id$
*/
public class DestinationNodeInheritanceNodeWrapper extends ContentDecoratorNodeWrapper implements InheritanceNodeWrapper {
public DestinationNodeInheritanceNodeWrapper(Node node) {
super(node, InheritanceContentDecorator.this);
}
@Override
public boolean hasNode(String relPath) throws RepositoryException {
if (super.hasNode(relPath)) {
return true;
}
Node current = getWrappedNode();
Iterator<Node> iterator = sources.iterator();
while (iterator.hasNext() && inheritsNodes(current)) {
current = iterator.next();
if (current.hasNode(relPath) && isSourceChildInherited(current.getNode(relPath))) {
return true;
}
}
return false;
}
@Override
public Node getNode(String relPath) throws PathNotFoundException, RepositoryException {
if (super.hasNode(relPath)) {
return super.getNode(relPath);
}
Node current = getWrappedNode();
Iterator<Node> iterator = sources.iterator();
while (iterator.hasNext() && inheritsNodes(current)) {
current = iterator.next();
if (current.hasNode(relPath)) {
Node node = current.getNode(relPath);
if (isSourceChildInherited(node)) {
return wrapNode(node);
}
}
}
throw new PathNotFoundException(relPath);
}
@Override
public NodeIterator getNodes() throws RepositoryException {
List<NodeIterator> nodes = new ArrayList<NodeIterator>();
Node current = getWrappedNode();
Iterator<Node> iterator = sources.iterator();
while (iterator.hasNext() && inheritsNodes(current)) {
current = iterator.next();
nodes.add(current.getNodes());
}
return super.wrapNodeIterator(sortInheritedNodes(getWrappedNode().getNodes(), nodes));
}
@Override
public NodeIterator getNodes(String namePattern) throws RepositoryException {
List<NodeIterator> nodes = new ArrayList<NodeIterator>();
Node current = getWrappedNode();
Iterator<Node> iterator = sources.iterator();
while (iterator.hasNext() && inheritsNodes(current)) {
current = iterator.next();
nodes.add(current.getNodes(namePattern));
}
return super.wrapNodeIterator(sortInheritedNodes(getWrappedNode().getNodes(namePattern), nodes));
}
@Override
public NodeIterator getNodes(String[] nameGlobs) throws RepositoryException {
List<NodeIterator> nodes = new ArrayList<NodeIterator>();
Node current = getWrappedNode();
Iterator<Node> iterator = sources.iterator();
while (iterator.hasNext() && inheritsNodes(current)) {
current = iterator.next();
nodes.add(current.getNodes(nameGlobs));
}
return super.wrapNodeIterator(sortInheritedNodes(getWrappedNode().getNodes(nameGlobs), nodes));
}
@Override
public boolean hasProperty(String relPath) throws RepositoryException {
if (super.hasProperty(relPath)) {
return true;
}
Node current = getWrappedNode();
Iterator<Node> iterator = sources.iterator();
while (iterator.hasNext() && inheritsProperties(current)) {
current = iterator.next();
if (current.hasProperty(relPath)) {
return true;
}
}
return false;
}
@Override
public Property getProperty(String relPath) throws PathNotFoundException, RepositoryException {
if (super.hasProperty(relPath)) {
return super.getProperty(relPath);
}
Node current = getWrappedNode();
Iterator<Node> iterator = sources.iterator();
while (iterator.hasNext() && inheritsProperties(current)) {
current = iterator.next();
if (current.hasProperty(relPath)) {
return wrapProperty(current.getProperty(relPath));
}
}
throw new PathNotFoundException(relPath);
}
@Override
public PropertyIterator getProperties() throws RepositoryException {
ArrayList<PropertyIterator> properties = new ArrayList<PropertyIterator>();
Node current = getWrappedNode();
Iterator<Node> iterator = sources.iterator();
while (iterator.hasNext() && inheritsProperties(current)) {
current = iterator.next();
properties.add(current.getProperties());
}
return super.wrapPropertyIterator(combinePropertyIterators(super.getProperties(), properties));
}
@Override
public PropertyIterator getProperties(String namePattern) throws RepositoryException {
ArrayList<PropertyIterator> properties = new ArrayList<PropertyIterator>();
Node current = getWrappedNode();
Iterator<Node> iterator = sources.iterator();
while (iterator.hasNext() && inheritsProperties(current)) {
current = iterator.next();
properties.add(current.getProperties(namePattern));
}
return super.wrapPropertyIterator(combinePropertyIterators(super.getProperties(namePattern), properties));
}
@Override
public PropertyIterator getProperties(String[] nameGlobs) throws RepositoryException {
ArrayList<PropertyIterator> properties = new ArrayList<PropertyIterator>();
Node current = getWrappedNode();
Iterator<Node> iterator = sources.iterator();
while (iterator.hasNext() && inheritsProperties(current)) {
current = iterator.next();
properties.add(current.getProperties(nameGlobs));
}
return super.wrapPropertyIterator(combinePropertyIterators(super.getProperties(nameGlobs), properties));
}
@Override
public boolean isInherited() {
return false;
}
}
private static class PropertyIteratorImpl extends RangeIteratorImpl implements PropertyIterator {
public PropertyIteratorImpl(Collection<Property> collection) {
super(collection);
}
@Override
public Property nextProperty() {
return (Property) super.next();
}
}
}