/** * This file Copyright (c) 2003-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.core; import info.magnolia.cms.core.version.ContentVersion; import info.magnolia.cms.core.version.VersionManager; import info.magnolia.cms.security.AccessDeniedException; import info.magnolia.cms.util.Rule; import info.magnolia.context.MgnlContext; import info.magnolia.context.MgnlContext.Op; import info.magnolia.jcr.RuntimeRepositoryException; import info.magnolia.jcr.util.NodeUtil; import info.magnolia.jcr.wrapper.JCRPropertiesFilteringNodeWrapper; import info.magnolia.logging.AuditLoggingUtil; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.List; import javax.jcr.ItemNotFoundException; import javax.jcr.Node; import javax.jcr.NodeIterator; import javax.jcr.PathNotFoundException; import javax.jcr.Property; import javax.jcr.PropertyIterator; import javax.jcr.PropertyType; import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.UnsupportedRepositoryOperationException; import javax.jcr.Workspace; import javax.jcr.lock.Lock; import javax.jcr.lock.LockException; import javax.jcr.nodetype.NodeType; import javax.jcr.version.Version; import javax.jcr.version.VersionException; import javax.jcr.version.VersionHistory; import javax.jcr.version.VersionIterator; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Default, JCR-based, implementation of {@link Content}. * * @version $Id$ * * @deprecated since 4.5 use jcr.Node instead. */ @Deprecated public class DefaultContent extends AbstractContent { private static final Logger log = LoggerFactory.getLogger(DefaultContent.class); /** * Wrapped jcr node. */ protected Node node; /** * node metadata. */ private MetaData metaData; /** * Empty constructor. Should NEVER be used for standard use, test only. */ protected DefaultContent() { } /** * Constructor to get existing node. * @param rootNode node to start with * @param path absolute (primary) path to this <code>Node</code> * @throws PathNotFoundException if the node at <code>path</code> does not exist * @throws AccessDeniedException if the current session does not have sufficient access rights to complete the * operation * @throws RepositoryException if an error occurs */ protected DefaultContent(Node rootNode, String path) throws PathNotFoundException, RepositoryException, AccessDeniedException { this.setNode(rootNode.getNode(path)); } /** * Constructor to get existing node. * @param elem initialized node object * @throws AccessDeniedException if the current session does not have sufficient access rights to complete the * operation * @throws RepositoryException if an error occurs */ public DefaultContent(Node node){ try { this.setNode(node); } catch (RepositoryException e) { throw new RuntimeRepositoryException(e); } } /** * Creates contentNode of type <b>contentType </b> contentType must be defined in item type definition of Magnolia * as well as JCR implementation. * @param rootNode node to start with * @param path absolute (primary) path to this <code>Node</code> * @param contentType JCR node type as configured * @throws PathNotFoundException if the node at <code>path</code> does not exist * @throws RepositoryException if an error occurs * @throws AccessDeniedException if the current session does not have sufficient access rights to complete the * operation */ protected DefaultContent(Node rootNode, String path, String contentType) throws PathNotFoundException, RepositoryException, AccessDeniedException { this.setNode(rootNode.addNode(path, contentType)); // add mix:lockable as default for all nodes created using this manager // for version 3.5 we cannot change node type definitions because of compatibility reasons // MAGNOLIA-1518 this.addMixin(ItemType.MIX_LOCKABLE); AuditLoggingUtil.log( AuditLoggingUtil.ACTION_CREATE, rootNode.getSession().getWorkspace().getName(), this.getItemType(), Path.getAbsolutePath(node.getPath())); } /** * @param node */ protected void setNode(Node node) throws RepositoryException { // Default content takes care of filtering jcr properties on its own this.node = NodeUtil.deepUnwrap(node, JCRPropertiesFilteringNodeWrapper.class); } @Override public Content getContent(String name) throws PathNotFoundException, RepositoryException, AccessDeniedException { return wrapAsContent(this.node, name); } @Override public Content createContent(String name, String contentType) throws PathNotFoundException, RepositoryException, AccessDeniedException { Content content = wrapAsContent(this.node, name, contentType); MetaData metaData = content.getMetaData(); metaData.setCreationDate(); return content; } @Override public boolean hasNodeData(String name) throws RepositoryException { if (this.node.hasProperty(name)) { return true; } if (hasBinaryNode(name)) { return true; } return false; } @Override public NodeData newNodeDataInstance(String name, int type, boolean createIfNotExisting) throws AccessDeniedException, RepositoryException { // create an empty dummy if(!hasNodeData(name) && !createIfNotExisting){ // TODO: This could also mean that user just doesn't have permission return new NonExistingNodeData(this, name); } if(type == PropertyType.UNDEFINED){ type = determineNodeDataType(name); } if(type == PropertyType.BINARY){ return addBinaryNodeData(name); } return new DefaultNodeData(this, name); } protected int determineNodeDataType(String name) { // FIXME: maybe delegate to NodeDataImplementations? try { if (this.node.hasProperty(name)) { return this.node.getProperty(name).getType(); } if (hasBinaryNode(name)) { return PropertyType.BINARY; } } catch (RepositoryException e) { throw new IllegalStateException("Can't determine property type of [" + getHandle() + "/" + name + "]", e); } return PropertyType.UNDEFINED; } @Override public MetaData getMetaData() { if (this.metaData == null) { this.metaData = new MetaData(this.node); } return this.metaData; } @Override public String getName() { try { return this.node.getName(); } catch (RepositoryException e) { log.error(e.getMessage(), e); } return StringUtils.EMPTY; } @Override public Collection<Content> getChildren(ContentFilter filter, String namePattern, Comparator<Content> orderCriteria) { List<Content> children; children = new ArrayList<Content>(); try { final NodeIterator nodeIterator; if (namePattern == null) { nodeIterator = this.node.getNodes(); } else { nodeIterator = this.node.getNodes(namePattern); } while (nodeIterator.hasNext()) { Node subNode = (Node) nodeIterator.next(); Content content = wrapAsContent(subNode); if (filter.accept(content)) { children.add(content); } } } catch (RepositoryException re) { log.error("Exception caught", re); } if (orderCriteria != null) { // stable sort - do not reorder or remove equal items Collections.sort(children, orderCriteria); } return children; } protected Content wrapAsContent(Node node) { return new DefaultContent(node); } protected Content wrapAsContent(Node node, String name) throws AccessDeniedException, PathNotFoundException, RepositoryException { return new DefaultContent(node, name); } protected Content wrapAsContent(Node node, String name, String contentType) throws AccessDeniedException, PathNotFoundException, RepositoryException { return new DefaultContent(node, name, contentType); } @Override public Collection<NodeData> getNodeDataCollection(String namePattern) { final ArrayList<NodeData> all = new ArrayList<NodeData>(); try { all.addAll(getPrimitiveNodeDatas(namePattern)); all.addAll(getBinaryNodeDatas(namePattern)); } catch (RepositoryException e) { throw new IllegalStateException("Can't read node datas of " + toString(), e); } return all; } protected Collection<NodeData> getPrimitiveNodeDatas(String namePattern) throws RepositoryException { final Collection<NodeData> nodeDatas = new ArrayList<NodeData>(); final PropertyIterator propertyIterator; if (namePattern == null) { propertyIterator = this.node.getProperties(); } else { propertyIterator = this.node.getProperties(namePattern); } while (propertyIterator.hasNext()) { Property property = (Property) propertyIterator.next(); try { if (!property.getName().startsWith("jcr:") && !property.getName().startsWith("mgnl:")) { nodeDatas.add(getNodeData(property.getName())); } } catch (PathNotFoundException e) { log.error("Exception caught", e); } catch (AccessDeniedException e) { // ignore, simply wont add content in a list } } return nodeDatas; } @Override public boolean hasContent(String name) throws RepositoryException { return this.node.hasNode(name); } @Override public String getHandle() { try { return this.node.getPath(); } catch (RepositoryException e) { log.error("Failed to get handle: " + e.getMessage(), e); return StringUtils.EMPTY; } } @Override public Content getParent() throws PathNotFoundException, RepositoryException, AccessDeniedException { return wrapAsContent(this.node.getParent()); } @Override public Content getAncestor(int level) throws PathNotFoundException, RepositoryException, AccessDeniedException { if (level > this.getLevel()) { throw new PathNotFoundException(); } return wrapAsContent((Node)this.node.getAncestor(level)); } @Override public Collection<Content> getAncestors() throws PathNotFoundException, RepositoryException { List<Content> allAncestors = new ArrayList<Content>(); int level = this.getLevel(); while (level != 0) { try { allAncestors.add(getAncestor(--level)); } catch (AccessDeniedException e) { // valid } } return allAncestors; } @Override public int getLevel() throws PathNotFoundException, RepositoryException { return this.node.getDepth(); } @Override public void orderBefore(String srcName, String beforeName) throws RepositoryException { this.node.orderBefore(srcName, beforeName); } @Override public int getIndex() throws RepositoryException { return this.node.getIndex(); } @Override public Node getJCRNode() { return this.node; } @Override public boolean isNodeType(String type) { return isNodeType(this.node, type); } /** * private Helper method to evaluate primary node type of the given node. * @param node * @param type */ protected boolean isNodeType(Node node, String type) { try { return NodeUtil.isNodeType(node, type); } catch (RepositoryException re) { log.error(re.getMessage()); log.debug(re.getMessage(), re); return false; } } @Override public NodeType getNodeType() throws RepositoryException { return this.node.getPrimaryNodeType(); } @Override public String getNodeTypeName() throws RepositoryException { if (this.node.hasProperty(ItemType.JCR_FROZEN_PRIMARY_TYPE)) { return this.node.getProperty(ItemType.JCR_FROZEN_PRIMARY_TYPE).getString(); } return this.node.getProperty(ItemType.JCR_PRIMARY_TYPE).getString(); } @Override public ItemType getItemType() throws RepositoryException { return new ItemType(getNodeTypeName()); } @Override public void restore(String versionName, boolean removeExisting) throws VersionException, UnsupportedRepositoryOperationException, RepositoryException { Version version = this.getVersionHistory().getVersion(versionName); this.restore(version, removeExisting); } @Override public void restore(Version version, boolean removeExisting) throws VersionException, UnsupportedRepositoryOperationException, RepositoryException { VersionManager.getInstance().restore(this, version, removeExisting); } @Override public void restore(Version version, String relPath, boolean removeExisting) throws VersionException, UnsupportedRepositoryOperationException, RepositoryException { throw new UnsupportedRepositoryOperationException("Not implemented since 3.0 Beta"); } @Override public void restoreByLabel(String versionLabel, boolean removeExisting) throws VersionException, UnsupportedRepositoryOperationException, RepositoryException { // FIXME: !!! why does it do something and then throws exception anyway? this.node.restoreByLabel(versionLabel, removeExisting); throw new UnsupportedRepositoryOperationException("Not implemented since 3.0 Beta"); } @Override public Version addVersion() throws UnsupportedRepositoryOperationException, RepositoryException { return VersionManager.getInstance().addVersion(this.getJCRNode()); } @Override public Version addVersion(Rule rule) throws UnsupportedRepositoryOperationException, RepositoryException { return VersionManager.getInstance().addVersion(this.getJCRNode(), rule); } public BinaryNodeData addBinaryNodeData(String name) { return new BinaryNodeData(this, name); } /** * Returns true if this node is either * <ul> * <li/>versionable and currently checked-out, <li/>non-versionable and its nearest versionable ancestor is * checked-out or <li/>non-versionable and it has no versionable ancestor. * </ul> * Returns false if this node is either * <ul> * <li/>versionable and currently checked-in or <li/>non-versionable and its nearest versionable ancestor is * checked-in. * </ul> * @return true if the node is checked out * @throws RepositoryException */ protected boolean isCheckedOut() throws RepositoryException { return this.node.isCheckedOut(); } @Override public boolean isModified() { return this.node.isModified(); } @Override public VersionHistory getVersionHistory() throws UnsupportedRepositoryOperationException, RepositoryException { return VersionManager.getInstance().getVersionHistory(this.getJCRNode()); } @Override public VersionIterator getAllVersions() throws UnsupportedRepositoryOperationException, RepositoryException { return VersionManager.getInstance().getAllVersions(this.getJCRNode()); } @Override public ContentVersion getBaseVersion() throws UnsupportedRepositoryOperationException, RepositoryException { return new ContentVersion(VersionManager.getInstance().getBaseVersion(this.getJCRNode()), this); } @Override public ContentVersion getVersionedContent(Version version) throws RepositoryException { return new ContentVersion(version, this); } @Override public ContentVersion getVersionedContent(String versionName) throws RepositoryException { return new ContentVersion(VersionManager.getInstance().getVersion(this.getJCRNode(), versionName), this); } @Override public void removeVersionHistory() throws AccessDeniedException, RepositoryException { VersionManager.getInstance().removeVersionHistory(this.node); } @Override public void save() throws RepositoryException { this.node.save(); } @Override public void delete() throws RepositoryException { final String nodePath = Path.getAbsolutePath(this.node.getPath()); final String workspaceName = this.node.getSession().getWorkspace().getName(); log.debug("removing {} from {}", this.node.getPath(), workspaceName); final ItemType nodeType = this.getItemType(); if (!workspaceName.startsWith("mgnl")) { MgnlContext.doInSystemContext(new Op<Void, RepositoryException>() { @Override public Void exec() throws RepositoryException { try { final String uuid = node.getUUID(); Session session = MgnlContext.getJCRSession("mgnlVersion"); Node versionedNode = session.getNodeByIdentifier(uuid); log.debug("Located versioned node {}({})", uuid, nodePath); VersionHistory history = versionedNode.getVersionHistory(); log.debug("Removing versioned node {}({})", uuid, nodePath); versionedNode.remove(); session.save(); VersionIterator iter = history.getAllVersions(); // skip root version. It can't be deleted manually, but will be cleaned up automatically after removing all other versions (see JCR-134) iter.nextVersion(); while (iter.hasNext()) { Version version = iter.nextVersion(); log.debug("removing version {}", version.getName()); history.removeVersion(version.getName()); } // at this point history should be deleted automatically (at least on JR) } catch (ItemNotFoundException e) { // doesn't exist in version store, ignore } catch (UnsupportedRepositoryOperationException e) { // not versionable or not referenceable ... either way ignore } return null; } }); } this.node.remove(); AuditLoggingUtil.log(AuditLoggingUtil.ACTION_DELETE, workspaceName, nodeType, nodePath); } @Override public void refresh(boolean keepChanges) throws RepositoryException { this.node.refresh(keepChanges); } @Override public String getUUID() { try { return this.node.getUUID(); } catch (UnsupportedOperationException e) { log.error(e.getMessage()); } catch (RepositoryException re) { log.error("Exception caught", re); } return StringUtils.EMPTY; } @Override public void addMixin(String type) throws RepositoryException { // TODO: there seems to be bug somewhere as we are able to add mixins even when the method below returns false if (!this.node.canAddMixin(type)) { log.debug("Node - " + this.node.getPath() + " does not allow mixin type - " + type); } try { this.node.addMixin(type); } catch (Exception e) { log.error("Failed to add mixin type - " + type + " to a node " + this.node.getPath()); } } @Override public void removeMixin(String type) throws RepositoryException { this.node.removeMixin(type); } @Override public NodeType[] getMixinNodeTypes() throws RepositoryException { return this.node.getMixinNodeTypes(); } @Override public Lock lock(boolean isDeep, boolean isSessionScoped) throws LockException, RepositoryException { return this.node.lock(isDeep, isSessionScoped); } @Override public Lock lock(boolean isDeep, boolean isSessionScoped, long yieldFor) throws LockException, RepositoryException { long finalTime = System.currentTimeMillis() + yieldFor; LockException lockException = null; while (System.currentTimeMillis() <= finalTime) { try { return this.node.lock(isDeep, isSessionScoped); } catch (LockException e) { // its not an exception yet, still got time lockException = e; } Thread.yield(); } // could not get lock throw lockException; } @Override public Lock getLock() throws LockException, RepositoryException { return this.node.getLock(); } @Override public void unlock() throws LockException, RepositoryException { this.node.unlock(); } @Override public boolean holdsLock() throws RepositoryException { return this.node.holdsLock(); } @Override public boolean isLocked() throws RepositoryException { return this.node.isLocked(); } @Override public boolean hasMetaData() { return true; } @Override public boolean hasMixin(String mixinName) throws RepositoryException { if (StringUtils.isBlank(mixinName)) { throw new IllegalArgumentException("Mixin name can't be empty."); } for (NodeType type : getMixinNodeTypes()) { if (mixinName.equals(type.getName())) { return true; } } return false; } @Override public HierarchyManager getHierarchyManager() { try { return createHierarchyManager(node.getSession()); } catch (RepositoryException e) { throw new RuntimeException(e); } } @Override public Workspace getWorkspace() throws RepositoryException { return node.getSession().getWorkspace(); } @Override public boolean equals(Object obj) { if (obj == null || !(obj instanceof DefaultContent)) { return false; } DefaultContent otherContent = (DefaultContent) obj; return getJCRNode().equals(otherContent.getJCRNode()); } protected HierarchyManager createHierarchyManager(Session session) { return new DefaultHierarchyManager(session); } protected boolean hasBinaryNode(String name) throws RepositoryException { return this.node.hasNode(name) && (this.node.getNode(name).isNodeType(ItemType.NT_RESOURCE) || (this.node.hasProperty("jcr:frozenPrimaryType") && this.node.getNode(name).getProperty("jcr:frozenPrimaryType").getValue().getString().equals(ItemType.NT_RESOURCE))); } }