info.magnolia.importexport.PropertiesImportExport.java Source code

Java tutorial

Introduction

Here is the source code for info.magnolia.importexport.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.importexport;

import info.magnolia.cms.core.Content;
import info.magnolia.cms.core.HierarchyManager;
import info.magnolia.cms.core.ItemType;
import info.magnolia.cms.core.NodeData;
import info.magnolia.cms.util.ContentUtil;
import info.magnolia.cms.util.OrderedProperties;
import org.apache.commons.beanutils.ConvertUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.jackrabbit.util.ISO8601;

import javax.jcr.PropertyType;
import javax.jcr.RepositoryException;
import javax.jcr.Value;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Calendar;
import java.util.Collection;
import java.util.Properties;
import java.util.Set;

/**
 * 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.
 *
 * TODO : handle conflicts (already existing nodes, properties, what to do with existing properties if we don't create new nodes, ...)
 * TODO : consolidate syntax
 *
 * @author gjoseph
 * @version $Revision: $ ($Author: $)
 */
public class PropertiesImportExport {

    public void createContent(Content 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, ItemType.CONTENTNODE.getSystemName());
            Content c = ContentUtil.createPath(root, path, new ItemType(type));
            populateContent(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;

            //if this is a node definition (no property)
            String newKey = orgKey;

            // make sure we have a dot as a property separator
            newKey = StringUtils.replace(newKey, "@", ".@");
            // avoid double dots
            newKey = StringUtils.replace(newKey, "..@", ".@");

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

            // 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", ItemType.CONTENTNODE.getSystemName());
                    }
                    continue;
                }
                propertyName = StringUtils.substringAfterLast(path, "/");
                path = StringUtils.substringBeforeLast(path, "/");
            }
            cleaned.put(path + "." + propertyName, properties.get(orgKey));
        }
        return cleaned;
    }

    protected void populateContent(Content c, 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")) {
            throw new UnsupportedOperationException(
                    "Can't see UUIDs on real node. Use MockUtil if you are using MockContent instances.");
        } else {
            Object valueObj = convertNodeDataStringToObject(valueStr);
            c.setNodeData(name, valueObj);
        }
    }

    protected Object convertNodeDataStringToObject(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;
    }

    /**
     * This method is deprecated, it returns results in a format that does not match
     * the format that the import method uses (doesn't include @uuid or @type properties)
     *
     * It is kept here to support existing test and applications that might break
     * as a result of these changes (i.e. unit tests that are expecting a specific number of
     * properties returned, etc)
     *
     * @deprecated since 4.3 - use {@link info.magnolia.jcr.util.PropertiesImportExport#toProperties(javax.jcr.Node, info.magnolia.jcr.predicate.AbstractPredicate)}
     */
    @Deprecated
    public static Properties toProperties(HierarchyManager hm) throws Exception {
        return toProperties(hm.getRoot());
    }

    /**
     * @deprecated since 5.0 - use {@link info.magnolia.jcr.util.PropertiesImportExport#toProperties(javax.jcr.Node, info.magnolia.jcr.predicate.AbstractPredicate)}
     */
    public static Properties toProperties(Content rootContent) throws Exception {
        return toProperties(rootContent, ContentUtil.EXCLUDE_META_DATA_CONTENT_FILTER, true);
    }

    /**
     * @deprecated since 5.0 - use {@link info.magnolia.jcr.util.PropertiesImportExport#toProperties(javax.jcr.Node, info.magnolia.jcr.predicate.AbstractPredicate)}
     */
    public static Properties contentToProperties(HierarchyManager hm) throws Exception {
        return contentToProperties(hm.getRoot());
    }

    /**
     * @deprecated since 5.0 - use {@link info.magnolia.jcr.util.PropertiesImportExport#toProperties(javax.jcr.Node, info.magnolia.jcr.predicate.AbstractPredicate)}
     */
    public static Properties contentToProperties(Content rootContent) throws Exception {
        return toProperties(rootContent, ContentUtil.EXCLUDE_META_DATA_CONTENT_FILTER, false);
    }

    /**
     * @deprecated since 5.0 - use {@link info.magnolia.jcr.util.PropertiesImportExport#toProperties(javax.jcr.Node, info.magnolia.jcr.predicate.AbstractPredicate)}
     */
    public static Properties contentToProperties(Content rootContent, Content.ContentFilter filter)
            throws Exception {
        return toProperties(rootContent, filter, false);
    }

    /**
     * This method is private because it includes the boolean "legacymode" filter which
     * shouldn't be exposed as part of the API because when "legacymode" is removed, it will
     * force an API change.
     *
     * @param rootContent root node to convert into properties
     * @param contentFilter a content filter to use in selecting what content to export
     * @param legacyMode if true, will not include @uuid and @type nodes
     * @return a Properties object representing the content starting at rootContent
     * @throws Exception
     * @deprecated since 5.0 - use {@link info.magnolia.jcr.util.PropertiesImportExport#toProperties(javax.jcr.Node, info.magnolia.jcr.predicate.AbstractPredicate)}
     */
    private static Properties toProperties(Content rootContent, Content.ContentFilter contentFilter,
            final boolean legacyMode) throws Exception {
        final Properties out = new OrderedProperties();
        ContentUtil.visit(rootContent, new ContentUtil.Visitor() {
            @Override
            public void visit(Content node) throws Exception {
                if (!legacyMode) {
                    appendNodeTypeAndUUID(node, out, true);
                }
                appendNodeProperties(node, out);
            }
        }, contentFilter);
        return out;
    }

    private static void appendNodeTypeAndUUID(Content node, Properties out, final boolean dumpMetaData)
            throws RepositoryException {
        String path = getExportPath(node);
        // we don't need to export the JCR root node.
        if (path.equals("/jcr:root")) {
            return;
        }

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

    private static void addBooleanProeprty(Properties out, String path, boolean prop) {
        out.put(path, convertBooleanToExportString(prop));
    }

    private static void addDateProperty(Properties out, String path, Calendar date) {
        if (date != null) {
            out.put(path, convertCalendarToExportString(date));
        }
    }

    private static void addStringProperty(Properties out, String path, String stringProperty) {
        if (StringUtils.isNotEmpty(stringProperty)) {
            out.put(path, stringProperty);
        }
    }

    public static void appendNodeProperties(Content node, Properties out) {
        final Collection<NodeData> props = node.getNodeDataCollection();
        for (NodeData prop : props) {
            final String path = getExportPath(node) + "." + prop.getName();

            String propertyValue = getPropertyString(prop);

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

    private static String getExportPath(Content node) {
        return node.getHandle();
    }

    private static String getPropertyString(NodeData prop) {
        int propType = prop.getType();

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

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

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

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

    /**
     * Dumps content starting at the content node out to a string in the format that matches the
     * import method.
     */
    public static String dumpPropertiesToString(Content content, Content.ContentFilter filter) throws Exception {
        Properties properties = PropertiesImportExport.contentToProperties(content, filter);
        return dumpPropertiesToString(properties);
    }

    public static String dumpPropertiesToString(Properties properties) {
        final StringBuilder sb = new StringBuilder();
        final Set<Object> propertyNames = properties.keySet();
        for (Object propertyKey : propertyNames) {
            final String name = propertyKey.toString();
            final String value = properties.getProperty(name);
            sb.append(name);
            sb.append("=");
            sb.append(value);
            sb.append("\n");
        }
        return sb.toString();
    }

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