info.magnolia.jcr.util.PropertiesImportExport.java Source code

Java tutorial

Introduction

Here is the source code for info.magnolia.jcr.util.PropertiesImportExport.java

Source

/**
 * 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.jcr.util;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Properties;
import javax.jcr.Node;
import javax.jcr.Property;
import javax.jcr.PropertyIterator;
import javax.jcr.PropertyType;
import javax.jcr.RepositoryException;
import javax.jcr.Value;

import org.apache.commons.beanutils.ConvertUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.jackrabbit.util.ISO8601;

import info.magnolia.cms.util.OrderedProperties;
import info.magnolia.jcr.predicate.AbstractPredicate;

/**
 * Utility class providing support for properties-like format to import/export jcr data. Useful when data regularly
 * needs to be bootstrapped, for instance, and the jcr xml format is too cumbersome to maintain.
 *
 * Caution: Binary data is represented as ByteArrayInputStream because of the lack of a proper javax.jcr.Binary implementation
 *
 * TODO : handle conflicts (already existing nodes, properties, what to do with existing properties if we don't create
 * new nodes, ...)
 */
public class PropertiesImportExport {

    /**
     * Each property is one item in the properties varargs passed in.
     */
    public void createNodes(Node root, String... properties) throws IOException, RepositoryException {
        createNodes(root, IOUtils.toInputStream(StringUtils.join(Arrays.asList(properties), "\n")));
    }

    /**
     * Each property or node in the stream has to be separated by the \n.
     */
    public void createNodes(Node root, InputStream propertiesStream) throws IOException, RepositoryException {
        Properties properties = new OrderedProperties();

        properties.load(propertiesStream);

        properties = keysToInnerFormat(properties);

        for (Object o : properties.keySet()) {
            String key = (String) o;
            String valueStr = properties.getProperty(key);

            String propertyName = StringUtils.substringAfterLast(key, ".");
            String path = StringUtils.substringBeforeLast(key, ".");

            String type = null;
            if (propertyName.equals("@type")) {
                type = valueStr;
            } else if (properties.containsKey(path + ".@type")) {
                type = properties.getProperty(path + ".@type");
            }

            type = StringUtils.defaultIfEmpty(type, NodeTypes.ContentNode.NAME);
            Node c = NodeUtil.createPath(root, path, type);
            populateNode(c, propertyName, valueStr);
        }
    }

    /**
     * Transforms the keys to the following inner notation: <code>/some/path/node.prop</code> or
     * <code>/some/path/node.@type</code>.
     */
    private Properties keysToInnerFormat(Properties properties) {
        Properties cleaned = new OrderedProperties();

        for (Object o : properties.keySet()) {
            String orgKey = (String) o;
            // explicitly enforce certain syntax
            if (!orgKey.startsWith("/")) {
                throw new IllegalArgumentException("Missing trailing '/' for key: " + orgKey);
            }
            if (StringUtils.countMatches(orgKey, ".") > 1) {
                throw new IllegalArgumentException("Key must not contain more than one '.': " + orgKey);
            }
            if (orgKey.contains("@") && !orgKey.contains(".@")) {
                throw new IllegalArgumentException("Key containing '@' must be preceded by a '.': " + orgKey);
            }
            // if this is a node definition (no property)
            String newKey = orgKey;

            String propertyName = StringUtils.substringAfterLast(newKey, ".");
            String keySuffix = StringUtils.substringBeforeLast(newKey, ".");
            String path = StringUtils.removeStart(keySuffix, "/");

            // if this is a path (no property)
            if (StringUtils.isEmpty(propertyName)) {
                // no value --> is a node
                if (StringUtils.isEmpty(properties.getProperty(orgKey))) {
                    // make this the type property if not defined otherwise
                    if (!properties.containsKey(orgKey + ".@type")) {
                        cleaned.put(path + ".@type", NodeTypes.ContentNode.NAME);
                    }
                    continue;
                }
                throw new IllegalArgumentException(
                        "Key for a path (everything without a '.' is considered to be a path) must not contain a value ('='): "
                                + orgKey);
            }
            cleaned.put(path + "." + propertyName, properties.get(orgKey));
        }
        return cleaned;
    }

    protected void populateNode(Node node, String name, String valueStr) throws RepositoryException {
        if (StringUtils.isEmpty(name) && StringUtils.isEmpty(valueStr)) {
            // happens if the input properties file just created a node with no properties
            return;
        }
        if (name.equals("@type")) {
            // do nothing, this has been taken into account when creating the node.
        } else if (name.equals("@uuid") || name.equals("uuid")) {
            setIdentifier(node, valueStr);
        } else {
            Object valueObj = convertPropertyStringToObject(valueStr);
            PropertyUtil.setProperty(node, name, valueObj);
        }
    }

    /**
     * Intentionally created this method to allow simple creation of subclasses actually setting the identifier (e.g. in
     * tests).
     */
    protected void setIdentifier(Node ignoredNode, String ignoredString) {
        throw new UnsupportedOperationException("Can't see UUIDs on real node.");
    }

    protected Object convertPropertyStringToObject(String valueStr) {
        if (contains(valueStr, ':')) {
            final String type = StringUtils.substringBefore(valueStr, ":");
            final String value = StringUtils.substringAfter(valueStr, ":");

            // there is no beanUtils converter for Calendar
            if (type.equalsIgnoreCase("date")) {
                return ISO8601.parse(value);
            } else if (type.equalsIgnoreCase("binary")) {
                return new ByteArrayInputStream(value.getBytes());
            } else {
                try {
                    final Class<?> typeCl;
                    if (type.equals("int")) {
                        typeCl = Integer.class;
                    } else {
                        typeCl = Class.forName("java.lang." + StringUtils.capitalize(type));
                    }
                    return ConvertUtils.convert(value, typeCl);
                } catch (ClassNotFoundException e) {
                    // possibly a stray :, let's ignore it for now
                    return valueStr;
                }
            }
        }
        // no type specified, we assume it's a string, no conversion
        return valueStr;
    }

    private static boolean contains(String s, char ch) {
        return s.indexOf(ch) > -1;
    }

    public Properties toProperties(Node node, final AbstractPredicate<Node> nodePredicate)
            throws RepositoryException {
        final Properties out = new OrderedProperties();
        NodeUtil.visit(node, new NodeVisitor() {
            @Override
            public void visit(Node node) throws RepositoryException {
                appendNodeTypeAndIdentifier(node, out);
                appendNodeProperties(node, out);
            }
        }, nodePredicate);
        return out;
    }

    private void appendNodeTypeAndIdentifier(Node node, Properties out) throws RepositoryException {

        // we don't need to export the JCR root node.
        if (node.getDepth() == 0) {
            return;
        }

        String path = getExportPath(node);

        String nodeTypeName = node.getPrimaryNodeType().getName();
        if (nodeTypeName != null && StringUtils.isNotEmpty(nodeTypeName)) {
            out.put(path + ".@type", nodeTypeName);
        }

        String nodeIdentifier = node.getIdentifier();
        if (nodeIdentifier != null && StringUtils.isNotEmpty(nodeIdentifier)) {
            out.put(path + ".@uuid", nodeIdentifier);
        }
    }

    private void appendNodeProperties(Node node, Properties out) throws RepositoryException {
        PropertyIterator propertyIterator = node.getProperties();
        while (propertyIterator.hasNext()) {
            Property property = propertyIterator.nextProperty();
            String path = getExportPath(node) + "." + property.getName();

            String propertyValue = getPropertyString(property);

            if (propertyValue != null) {
                out.setProperty(path, propertyValue);
            }
        }
    }

    private String getExportPath(Node node) throws RepositoryException {
        return node.getPath();
    }

    private String getPropertyString(Property property) throws RepositoryException {

        switch (property.getType()) {
        case (PropertyType.STRING): {
            return property.getString();
        }
        case (PropertyType.BOOLEAN): {
            return convertBooleanToExportString(property.getBoolean());
        }
        case (PropertyType.BINARY): {
            return convertBinaryToExportString(property.getValue());
        }
        case (PropertyType.PATH): {
            return property.getString();
        }
        case (PropertyType.DATE): {
            return convertCalendarToExportString(property.getDate());
        }
        case (PropertyType.LONG): {
            return "" + property.getLong();
        }
        case (PropertyType.DOUBLE): {
            return "" + property.getDouble();
        }
        default: {
            return property.getString();
        }
        }
    }

    private String convertBooleanToExportString(boolean b) {
        return "boolean:" + (b ? "true" : "false");
    }

    private String convertBinaryToExportString(Value value) throws RepositoryException {
        return "binary:" + ConvertUtils.convert(value.getString());
    }

    private String convertCalendarToExportString(Calendar calendar) {
        return "date:" + ISO8601.format(calendar);
    }
}