/** * This file Copyright (c) 2010-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.cms.util; import info.magnolia.cms.core.AbstractContent; import info.magnolia.cms.core.Content; import info.magnolia.cms.core.DefaultNodeData; import info.magnolia.cms.core.NodeData; import java.util.Collection; import java.util.Comparator; import java.util.LinkedHashMap; import java.util.SortedMap; import java.util.TreeMap; import javax.jcr.RepositoryException; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * This wrapper allows extending other nodes (mainly useful to extend configurations). A content node can define * a nodeData with the name 'extends'. Its value is either an absolute or relative path. The merge is then performed as follows: * * <ul> * <li>nodeDatas are merged and values are overwritten * <li>sub content nodes are merged, the original order is guaranteed, new nodes are added at the * end of the list * </ul> * * The mechanism supports multiple inheritances as such: * <ul> * <li>the node the current node inherits from can again extend a node * <li>nodes laying deeper in the hierarchy can extend an other node * </ul> * * @deprecated since Magnolia 5.0 - use {@link info.magnolia.jcr.wrapper.ExtendingNodeWrapper} instead. * @version $Id$ * @see InheritanceContentWrapper a class supporting content inheritance. */ public class ExtendingContentWrapper extends ContentWrapper { protected static final String EXTENDING_NODE_DATA = "extends"; protected static final String EXTENDING_NODE_DATA_OVERRIDE = "override"; private boolean extending; private Content extendedContent; private static final Logger log = LoggerFactory.getLogger(ExtendingContentWrapper.class); //This is just for test purposes. Use ExtendingContentWrapper(Content wrappedContent) as was here before. ExtendingContentWrapper(Content wrappedContent, boolean failOnError) { super(wrappedContent); try { extending = getWrappedContent().hasNodeData(EXTENDING_NODE_DATA); if (extending) { NodeData extendingNodeData = getWrappedContent().getNodeData(EXTENDING_NODE_DATA); // check if override is not forced String extendedNode = extendingNodeData.getString(); Content parent = extendingNodeData.getParent(); if (StringUtils.isBlank(extendedNode)) { // there is nothing to do, extending node is not defined ... probably caught in middle of config extending = false; } else if (EXTENDING_NODE_DATA_OVERRIDE.equals(extendedNode)) { extending = false; } if (extending) { if (isExists(extendedNode, parent)) { // support multiple inheritance extendedContent = wrapIfNeeded(extendingNodeData.getReferencedContent()); } else { String message = "Can't find referenced node for value: " + wrappedContent; log.error(message); extending = false; if (failOnError) { throw new RuntimeException(message); } } } } } catch (RepositoryException e) { throw new RuntimeException("Can't wrap node [" + wrappedContent + "]", e); } } public ExtendingContentWrapper(Content wrappedContent) { this(wrappedContent, false); } /** * Does not support the extends nodedata but chains the two nodes directly. Each node is * wrapped internally to ensure that each of them support the extends nodedata for themselves. */ protected ExtendingContentWrapper(Content wrappedContent, Content extendedContent) { super(wrapIfNeeded(wrappedContent)); extending = true; try { if (getWrappedContent().hasNodeData(EXTENDING_NODE_DATA)) { NodeData extendingNodeData = getWrappedContent().getNodeData(EXTENDING_NODE_DATA); // check if override is not forced extending = !EXTENDING_NODE_DATA_OVERRIDE.equals(extendingNodeData.getString()); } } catch (RepositoryException e) { throw new RuntimeException("Can't determine extends point for node [" + wrappedContent + "]", e); } // might extend further more this.extendedContent = wrapIfNeeded(extendedContent); } private boolean isExists(String extendedNode, Content parent) throws RepositoryException { if (extendedNode.startsWith("/")){ return getWrappedContent().getHierarchyManager().isExist(extendedNode); } return parent.hasContent(extendedNode); } private static Content wrapIfNeeded(Content content) { if (content instanceof ExtendingContentWrapper) { return content; } return new ExtendingContentWrapper(content); } public boolean isExtending() { return this.extending; } @Override public Content getWrappedContent() { Content wrapped = super.getWrappedContent(); if (wrapped instanceof ExtendingContentWrapper) { ExtendingContentWrapper wrappedECW = (ExtendingContentWrapper) wrapped; if (!wrappedECW.extending) { // wrapped but not extending ==> should not be wrapped in the first place but decision is made too late - in init<> of the ECW return ((ExtendingContentWrapper) wrapped).getWrappedContent(); } } return super.getWrappedContent(); } @Override public boolean hasContent(String name) throws RepositoryException { if (getWrappedContent().hasContent(name)) { return true; } else if (extending && extendedContent.hasContent(name)) { return true; } return false; } @Override public Content getContent(String name) throws RepositoryException { Content content; if (getWrappedContent().hasContent(name)) { content = getWrappedContent().getContent(name); } else if (extending && extendedContent.hasContent(name)) { content = extendedContent.getContent(name); } else { // this will throw a PathNotFoundException content = getWrappedContent().getContent(name); } return wrap(content); } @Override public Collection<Content> getChildren(ContentFilter filter, String namePattern, Comparator<Content> orderCriteria) { Collection<Content> directChildren = ((AbstractContent)getWrappedContent()).getChildren(filter, namePattern, orderCriteria); if (extending) { Collection<Content> inheritedChildren = ((AbstractContent)extendedContent).getChildren(filter, namePattern, orderCriteria); // keep order, add new elements at the end of the collection LinkedHashMap<String, Content> merged = new LinkedHashMap<String, Content>(); for (Content content : inheritedChildren) { merged.put(content.getName(), content); } for (Content content : directChildren) { merged.put(content.getName(), content); } return wrapContentNodes(merged.values()); } return wrapContentNodes(directChildren); } @Override public NodeData getNodeData(String name) { if (EXTENDING_NODE_DATA.equals(name)) { return new DefaultNodeData(extendedContent, name) { @Override public boolean isExist() { return false; } @Override public void save() throws RepositoryException { // do nothing } }; } try { if (getWrappedContent().hasNodeData(name)) { return wrap(getWrappedContent().getNodeData(name)); } else if (extending && extendedContent.hasNodeData(name)) { return wrap(extendedContent.getNodeData(name)); } else { return wrap(getWrappedContent().getNodeData(name)); } } catch (RepositoryException e) { throw new RuntimeException("Can't read nodedata from extended node [" + extendedContent + "]", e); } } @Override public boolean hasNodeData(String name) throws RepositoryException { if (EXTENDING_NODE_DATA.equals(name)) { return false; } return super.hasNodeData(name); } @Override public Collection<NodeData> getNodeDataCollection() { final Content wrapped = getWrappedContent(); Collection<NodeData> directChildren = wrapped.getNodeDataCollection(); try { if (wrapped.hasNodeData(EXTENDING_NODE_DATA)) { for (NodeData child : directChildren) { if (EXTENDING_NODE_DATA.equals(child.getName())) { directChildren.remove(child); break; } } } } catch (RepositoryException e) { throw new RuntimeException("Can't read nodedata collection from node [" + wrapped.getHandle() + "]", e); } if (extending) { Collection<NodeData> inheritedChildren = extendedContent.getNodeDataCollection(); // sort by name SortedMap<String, NodeData> merged = new TreeMap<String, NodeData>(); for (NodeData nodeData : inheritedChildren) { merged.put(nodeData.getName(), nodeData); } for (NodeData nodeData : directChildren) { merged.put(nodeData.getName(), nodeData); } return wrapNodeDatas(merged.values()); } return wrapNodeDatas(directChildren); } @Override protected Content wrap(Content node) { // get the same subnode of the extended content try { if (extending && extendedContent.hasContent(node.getName())) { // FIXME we have to calculate the relative path Content extendedSubContent = extendedContent.getContent(node.getName()); return new ExtendingContentWrapper(node, extendedSubContent); } } catch (RepositoryException e) { throw new RuntimeException("Can't wrap " + node, e); } return wrapIfNeeded(node); } }