Java tutorial
/** * This file Copyright (c) 2012-2015 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.ui.vaadin.integration.jcr; import info.magnolia.cms.core.Path; import info.magnolia.jcr.RuntimeRepositoryException; import info.magnolia.jcr.util.NodeUtil; import info.magnolia.jcr.util.PropertyUtil; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import javax.jcr.Item; import javax.jcr.Node; import javax.jcr.NodeIterator; import javax.jcr.RepositoryException; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.vaadin.data.Property; /** * Abstract implementation of an {@link com.vaadin.data.Item} wrapping/representing a {@link javax.jcr.Node}. Implements {Property.ValueChangeListener} in order to inform/change JCR * property when a Vaadin property has changed. Access JCR repository for all read Jcr Property. */ public abstract class AbstractJcrNodeAdapter extends AbstractJcrAdapter { private static final Logger log = LoggerFactory.getLogger(AbstractJcrNodeAdapter.class); private String primaryNodeType; private final Map<String, AbstractJcrNodeAdapter> children = new LinkedHashMap<String, AbstractJcrNodeAdapter>(); private final Map<String, AbstractJcrNodeAdapter> removedChildren = new HashMap<String, AbstractJcrNodeAdapter>(); private AbstractJcrNodeAdapter parent; private String nodeName; private boolean childItemChanges = false; public AbstractJcrNodeAdapter(Node jcrNode) { super(jcrNode); } @Override public boolean isNode() { return true; } @Override protected void initCommonAttributes(Item jcrItem) { super.initCommonAttributes(jcrItem); Node node = (Node) jcrItem; try { if (StringUtils.isBlank(primaryNodeType)) { primaryNodeType = node.getPrimaryNodeType().getName(); } } catch (RepositoryException e) { log.error("Could not determine primaryNodeType name of JCR node", e); primaryNodeType = UNIDENTIFIED; } } protected void setPrimaryNodeTypeName(String primaryNodeTypeName) { this.primaryNodeType = primaryNodeTypeName; } /** * Return the Primary node type Name. This Node type is defined based on the related JCR Node. * In case of new Node, the Type is passed during the construction of the new Item or if not * defined, the Type is equivalent to the Parent Node Type. */ public String getPrimaryNodeTypeName() { return primaryNodeType; } protected Map<String, AbstractJcrNodeAdapter> getRemovedChildren() { return removedChildren; } /** * Return the corresponding node directly from the JCR repository. <b> The returned Node does * not contains all changes done on the current Item, but it's a representation of the current * stored Jcr node. </b> To get the Jcr Node including the changes done on the current Item, use * applyChanges(). */ @Override public Node getJcrItem() { return (Node) super.getJcrItem(); } /** * Add a new JCR Property. */ @Override public boolean addItemProperty(Object id, Property property) { // REMOVE ME: Never called as overrides by sub class. log.debug("Add new Property Item name " + id + " with value " + property.getValue()); try { Node node = getJcrItem(); String propertyName = (String) id; if (!node.hasProperty(propertyName)) { // Create Property. node.setProperty(propertyName, (String) property.getValue()); getChangedProperties().put((String) id, property); return true; } else { log.warn("Property " + id + " already exist."); return false; } } catch (RepositoryException e) { log.error("Unable to add JCR property", e); return false; } } /** * @return the property if it already exist on the JCR Item a new property if this property * refer to the JCR Node name null if the property doesn't exist yet. */ @Override public Property getItemProperty(Object id) { Object value; Class type = String.class; try { final Node jcrNode = getJcrItem(); if (!jcrNode.hasProperty((String) id)) { if (ModelConstants.JCR_NAME.equals(id)) { value = jcrNode.getName(); } else { return null; } } else { value = PropertyUtil.getPropertyValueObject(jcrNode, String.valueOf(id)); type = value.getClass(); } } catch (RepositoryException e) { throw new RuntimeRepositoryException(e); } DefaultProperty property = new DefaultProperty(type, value); getChangedProperties().put((String) id, property); return property; } /** * Returns the JCR Node represented by this Item with changes applied. Updates both properties and child nodes. Will * create new properties, set new values and remove those requested for removal. Child nodes will also be added, * updated or removed. */ @Override public Node applyChanges() throws RepositoryException { // get Node from repository Node node = getJcrItem(); // Update Node properties and children updateProperties(node); updateChildren(node); return node; } /** * Updates and removes children based on the {@link #children} and {@link #removedChildren} maps. * * TODO: Has been made public as of MGNLUI-3124 resolution. Needs further API improvement (e.g. no-arg version or possibly some other better way to update the JCR node internals). */ public void updateChildren(Node node) throws RepositoryException { if (!children.isEmpty()) { List<String> sortedChildIdentifiers = new LinkedList<>(); for (AbstractJcrNodeAdapter child : children.values()) { // Update child node as well child.applyChanges(); sortedChildIdentifiers.add(child.getJcrItem().getIdentifier()); } if (node.getPrimaryNodeType().hasOrderableChildNodes()) { sortChildren(node, sortedChildIdentifiers); } } // Remove child node if needed if (!removedChildren.isEmpty()) { for (AbstractJcrNodeAdapter removedChild : removedChildren.values()) { if (node.hasNode(removedChild.getNodeName())) { node.getNode(removedChild.getNodeName()).remove(); } } } } /** * Sorts the child nodes of {@code node} according to the passed list of identifiers {@code sortedIdentifiers}. */ private void sortChildren(Node node, List<String> sortedIdentifiers) throws RepositoryException { List<String> unsortedIdentifiers = new ArrayList<>(); for (NodeIterator it = node.getNodes(); it.hasNext();) { unsortedIdentifiers.add(it.nextNode().getIdentifier()); } for (int pos = 0; pos < sortedIdentifiers.size(); pos++) { String current = sortedIdentifiers.get(pos); int nodePos = unsortedIdentifiers.indexOf(current); if (nodePos != -1 || nodePos != pos) { Node nodeToMove = node.getSession().getNodeByIdentifier(current); Node target = node.getSession().getNodeByIdentifier(unsortedIdentifiers.get(pos)); NodeUtil.moveNodeBefore(nodeToMove, target); String movedId = unsortedIdentifiers.remove(nodePos); unsortedIdentifiers.add(pos, movedId); } } } @Override public void updateProperties(Item item) throws RepositoryException { super.updateProperties(item); if (item instanceof Node) { // Remove Properties Node node = (Node) item; for (Entry<String, Property> entry : getRemovedProperties().entrySet()) { if (node.hasProperty(entry.getKey())) { node.getProperty(entry.getKey()).remove(); } } getRemovedProperties().clear(); } } /** * Update or remove property. Property with flag saveInfo to false will not be updated. Property * can refer to node property (like name, title) or node.MetaData property like * (MetaData/template). Also handle the specific case of node renaming. If property JCR_NAME is * present, Rename the node. * In case the value has changed to null, it will be removed. When being called from {@link #updateProperties} * we have to make sure it is removed before running in here as {@link #removeItemProperty(java.lang.Object)} * is manipulating the {@link #changedProperties} list directly. */ @Override protected void updateProperty(Item item, String propertyId, Property property) { if (!(item instanceof Node)) { return; } Node node = (Node) item; if (ModelConstants.JCR_NAME.equals(propertyId)) { String jcrName = (String) property.getValue(); try { if (jcrName != null && !jcrName.isEmpty() && !jcrName.equals(node.getName())) { // make sure new path is available jcrName = Path.getValidatedLabel(jcrName); jcrName = Path.getUniqueLabel(node.getSession(), node.getParent().getPath(), jcrName); NodeUtil.renameNode(node, jcrName); setItemId(JcrItemUtil.getItemId(node)); } } catch (RepositoryException e) { log.error("Could not rename JCR Node.", e); } } else if (propertyId != null && !propertyId.isEmpty()) { if (property.getValue() != null) { try { PropertyUtil.setProperty(node, propertyId, property.getValue()); } catch (RepositoryException e) { log.error("Could not set JCR Property {}", propertyId, e); } } else { removeItemProperty(propertyId); log.debug("Property '{}' has a null value: Will be removed", propertyId); } } } /** * @param nodeName name of the child node * @return child if part of the children, or null if not defined. */ public AbstractJcrNodeAdapter getChild(String nodeName) { return children.get(nodeName); } public Map<String, AbstractJcrNodeAdapter> getChildren() { return children; } /** * Add a child adapter to the current Item. <b>Only Child Nodes part of this Map will * be persisted into Jcr.</b> */ public AbstractJcrNodeAdapter addChild(AbstractJcrNodeAdapter child) { childItemChanges = true; if (removedChildren.containsKey(child.getNodeName())) { removedChildren.remove(child.getNodeName()); } child.setParent(this); if (children.containsKey(child.getNodeName())) { children.remove(child.getNodeName()); } return children.put(child.getNodeName(), child); } /** * Remove a child Node from the child list. <b>When removing an JcrNodeAdapter, this child * will be added to the Remove Child List even if this Item was not part of the current children * list. All Item part from the removed list are removed from the Jcr repository.</b> */ public boolean removeChild(AbstractJcrNodeAdapter toRemove) { childItemChanges = true; removedChildren.put(toRemove.getNodeName(), toRemove); return children.remove(toRemove.getNodeName()) != null; } /** * Return the current Parent Item (If Item is a child). Parent is set by calling addChild(... */ public AbstractJcrNodeAdapter getParent() { return parent; } public void setParent(AbstractJcrNodeAdapter parent) { this.parent = parent; } /** * Return the current Node Name. For new Item, this is the name set in the new Item constructor * or null if not yet defined. */ public String getNodeName() { return this.nodeName; } public void setNodeName(String nodeName) { this.nodeName = nodeName; } /** * @return true if an {@link com.vaadin.data.Item} was added or removed, false otherwise. */ public boolean hasChildItemChanges() { return childItemChanges; } }