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

Java tutorial

Introduction

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

Source

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

import info.magnolia.link.LinkException;
import info.magnolia.link.LinkTransformerManager;

import java.lang.reflect.Method;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import javax.jcr.Node;
import javax.jcr.PathNotFoundException;
import javax.jcr.Property;
import javax.jcr.PropertyIterator;
import javax.jcr.PropertyType;
import javax.jcr.RepositoryException;
import javax.jcr.Value;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Map based representation of JCR content. This class is for instance used in template scripts to allow notations like
 * <code>content.propName</code>. It first tries to read a property with name (key) and if not present checks for the
 * presence of child node. Few special property names map to the JCR methods: \@name, \@id, \@path, \@level, \@nodeType
 *
 * @version $Id$
 */
public class ContentMap implements Map<String, Object> {

    private final static Logger log = LoggerFactory.getLogger(ContentMap.class);

    private final Node content;

    /**
     * Represents getters of the node itself.
     */
    private final Map<String, Method> specialProperties = new HashMap<String, Method>();

    public ContentMap(Node content) {
        if (content == null) {
            throw new NullPointerException("ContentMap doesn't accept null content");
        }

        this.content = content;

        // Supported special types are: @nodeType @name, @path @depth (and their deprecated forms - see
        // convertDeprecatedProps() for details)
        Class<? extends Node> clazz = content.getClass();
        try {
            specialProperties.put("name", clazz.getMethod("getName", (Class<?>[]) null));
            specialProperties.put("id", clazz.getMethod("getIdentifier", (Class<?>[]) null));
            specialProperties.put("path", clazz.getMethod("getPath", (Class<?>[]) null));
            specialProperties.put("depth", clazz.getMethod("getDepth", (Class<?>[]) null));
            specialProperties.put("nodeType", clazz.getMethod("getPrimaryNodeType", (Class<?>[]) null));
        } catch (SecurityException e) {
            log.debug("Failed to gain access to Node get***() method. Check VM security settings. "
                    + e.getLocalizedMessage(), e);
        } catch (NoSuchMethodException e) {
            log.debug(
                    "Failed to retrieve get***() method of Node class. Check the classpath for conflicting version of JCR classes. "
                            + e.getLocalizedMessage(),
                    e);
        }
    }

    @Override
    public boolean containsKey(Object key) {

        String strKey = convertKey(key);

        if (!isValidKey(strKey)) {
            return false;
        }

        if (isSpecialProperty(strKey)) {
            return true;
        }

        try {
            return content.hasProperty(strKey);
        } catch (RepositoryException e) {
            // ignore, most likely invalid name
        }
        return false;
    }

    private String convertKey(Object key) {
        if (key == null) {
            return null;
        }
        try {
            return (String) key;
        } catch (ClassCastException e) {
            log.debug("Invalid key. Expected String, but got {}.", key.getClass().getName());
        }
        return null;
    }

    private boolean isValidKey(String strKey) {
        return !StringUtils.isBlank(strKey);
    }

    private boolean isSpecialProperty(String strKey) {
        if (!strKey.startsWith("@")) {
            return false;
        }
        strKey = convertDeprecatedProps(strKey);
        return specialProperties.containsKey(StringUtils.removeStart(strKey, "@"));
    }

    /**
     * @return a property name - in case the one handed in is known to be deprecated it'll be converted, else the
     *         original one is returned.
     */
    private String convertDeprecatedProps(String strKey) {
        // in the past we allowed both lower and upper case notation ...
        if ("@UUID".equals(strKey) || "@uuid".equals(strKey)) {
            return "@id";
        } else if ("@handle".equals(strKey)) {
            return "@path";
        } else if ("@level".equals(strKey)) {
            return "@depth";
        }
        return strKey;
    }

    @Override
    public Object get(Object key) {
        String keyStr;
        try {
            keyStr = (String) key;
        } catch (ClassCastException e) {
            throw new ClassCastException(
                    "ContentMap accepts only String as a parameters, provided object was of type "
                            + (key == null ? "null" : key.getClass().getName()));
        }

        Object prop = getNodeProperty(keyStr);
        if (prop == null) {
            keyStr = convertDeprecatedProps(keyStr);
            return getSpecialProperty(keyStr);
        }
        return prop;
    }

    private Object getSpecialProperty(String strKey) {
        if (isSpecialProperty(strKey)) {
            final Method method = specialProperties.get(StringUtils.removeStart(strKey, "@"));
            try {
                return method.invoke(content, null);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
        return null;

    }

    private Object getNodeProperty(String keyStr) {
        try {
            if (content.hasProperty(keyStr)) {
                Property prop = content.getProperty(keyStr);
                int type = prop.getType();
                if (type == PropertyType.DATE) {
                    return prop.getDate();
                } else if (type == PropertyType.BINARY) {
                    // this should actually never happen. there is no reason why anyone should stream binary data into
                    // template ... or is there?
                } else if (type == PropertyType.BOOLEAN) {
                    return prop.getBoolean();
                } else if (type == PropertyType.LONG) {
                    return prop.getLong();
                } else if (type == PropertyType.DOUBLE) {
                    return prop.getDouble();
                } else if (prop.isMultiple()) {

                    Value[] values = prop.getValues();

                    String[] valueStrings = new String[values.length];

                    for (int j = 0; j < values.length; j++) {
                        try {
                            valueStrings[j] = values[j].getString();
                        } catch (RepositoryException e) {
                            log.debug(e.getMessage());
                        }
                    }

                    return valueStrings;
                } else {
                    try {
                        return info.magnolia.link.LinkUtil.convertLinksFromUUIDPattern(prop.getString(),
                                LinkTransformerManager.getInstance().getBrowserLink(content.getPath()));
                    } catch (LinkException e) {
                        log.warn("Failed to parse links with from " + prop.getName(), e);
                    }
                }
                // don't we want to honor other types (e.g. numbers? )
                return prop.getString();
            }
            // property doesn't exist, but maybe child of that name does
            if (content.hasNode(keyStr)) {
                return new ContentMap(content.getNode(keyStr));
            }

        } catch (PathNotFoundException e) {
            // ignore, property doesn't exist
        } catch (RepositoryException e) {
            log.warn("Failed to retrieve {} on {} with {}", new Object[] { keyStr, content, e.getMessage() });
        }

        return null;
    }

    @Override
    public int size() {
        try {
            return (int) (content.getProperties().getSize() + specialProperties.size());
        } catch (RepositoryException e) {
            // ignore ... no rights to read properties.
        }
        return specialProperties.size();
    }

    @Override
    public Set<String> keySet() {
        Set<String> keys = new HashSet<String>();
        try {
            PropertyIterator props = content.getProperties();
            while (props.hasNext()) {
                keys.add(props.nextProperty().getName());
            }
        } catch (RepositoryException e) {
            // ignore - has no access
        }
        for (String name : specialProperties.keySet()) {
            keys.add(name);
        }
        return keys;
    }

    @Override
    public Set<java.util.Map.Entry<String, Object>> entrySet() {
        throw new UnsupportedOperationException("Entry collections are not supported");
    }

    @Override
    public Collection<Object> values() {
        throw new UnsupportedOperationException("Value collections are not supported");
    }

    @Override
    public boolean containsValue(Object arg0) {
        throw new UnsupportedOperationException("Value checks are not supported");
    }

    @Override
    public boolean isEmpty() {
        // can never be empty because of the node props themselves (name, uuid, ...)
        return false;
    }

    @Override
    public void clear() {
        // ignore, read only
    }

    @Override
    public Object put(String arg0, Object arg1) {
        // ignore, read only
        return null;
    }

    @Override
    public void putAll(Map<? extends String, ? extends Object> arg0) {
        // ignore, read only
    }

    @Override
    public Object remove(Object arg0) {
        // ignore, read only
        return null;
    }

    public Node getJCRNode() {
        return content;
    }
}