org.axonframework.mongo.serialization.BSONNode.java Source code

Java tutorial

Introduction

Here is the source code for org.axonframework.mongo.serialization.BSONNode.java

Source

/*
 * Copyright (c) 2010-2016. Axon Framework
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.axonframework.mongo.serialization;

import com.mongodb.BasicDBList;
import com.mongodb.BasicDBObject;
import com.mongodb.DBObject;
import org.axonframework.common.Assert;

import java.util.*;
import java.util.stream.Collectors;

/**
 * Represents a node in a BSON structure. This class provides for an XML like abstraction around BSON for use by the
 * XStream Serializer.
 *
 * @author Allard Buijze
 * @since 2.0
 */
public class BSONNode {

    private static final String ATTRIBUTE_PREFIX = "attr_";
    private static final String VALUE_KEY = "_value";

    private final List<BSONNode> childNodes = new ArrayList<>();
    private String value;
    private final String encodedName;

    /**
     * Creates a node with given "code" name. Generally, this constructor is used for the root node. For child nodes,
     * see the convenience method {@link #addChildNode(String)}.
     * <p/>
     * Note that the given {@code name} is encoded in a BSON compatible way. That means that periods (".") are
     * replaced by forward slashes "/", and any slashes are prefixes with additional slash. For example, the String
     * "some.period/slash" would become "some/period//slash". This is only imporant when querying BSON structures
     * directly.
     *
     * @param name The name of this node
     */
    public BSONNode(String name) {
        this.encodedName = encode(name);
    }

    /**
     * Constructrs a BSONNode structure from the given DBObject structure. The node returned is the root node.
     *
     * @param node The root DBObject node containing the BSON Structure
     * @return The BSONNode representing the root of the BSON structure
     */
    public static BSONNode fromDBObject(DBObject node) {
        if (node.keySet().size() != 1) {
            throw new IllegalArgumentException("Given node should have exactly one attribute");
        }
        String rootName = node.keySet().iterator().next();
        BSONNode rootNode = new BSONNode(decode(rootName));
        Object rootContents = node.get(rootName);
        if (rootContents instanceof List) {
            ((List<?>) rootContents).stream().filter(childElement -> childElement instanceof DBObject)
                    .forEach(childElement -> {
                        DBObject dbChild = (DBObject) childElement;
                        if (dbChild.containsField(VALUE_KEY)) {
                            rootNode.setValue((String) dbChild.get(VALUE_KEY));
                        } else {
                            rootNode.addChildNode(fromDBObject(dbChild));
                        }
                    });
        } else if (rootContents instanceof DBObject) {
            rootNode.addChildNode(fromDBObject((DBObject) rootContents));
        } else if (rootContents instanceof String) {
            rootNode.setValue((String) rootContents);
        } else {
            throw new IllegalArgumentException(
                    "Node in " + rootName + " contains child " + rootContents + " which cannot be parsed");
        }
        return rootNode;
    }

    private void addChildNode(BSONNode bsonNode) {
        this.childNodes.add(bsonNode);
    }

    /**
     * Returns the current BSON structure as DBObject.
     *
     * @return the current BSON structure as DBObject
     */
    public DBObject asDBObject() {
        if (childNodes.isEmpty() && value != null) {
            // only a value
            return new BasicDBObject(encodedName, value);
        } else if (childNodes.size() == 1 && value == null) {
            return new BasicDBObject(encodedName, childNodes.get(0).asDBObject());
        } else {
            BasicDBList subNodes = new BasicDBList();
            BasicDBObject thisNode = new BasicDBObject(encodedName, subNodes);
            if (value != null) {
                subNodes.add(new BasicDBObject(VALUE_KEY, value));
            }
            subNodes.addAll(childNodes.stream().map(BSONNode::asDBObject).collect(Collectors.toList()));
            return thisNode;
        }
    }

    /**
     * Sets the value of this node. Note that a node can contain either a value, or child nodes
     *
     * @param value The value to set for this node
     */
    public void setValue(String value) {
        Assert.isFalse(children().hasNext(), () -> "A child node was already present. "
                + "A node cannot contain a value as well as child nodes.");
        this.value = value;
    }

    /**
     * Sets an attribute to this node. Since JSON (and BSON) do not have the notion of attributes, these are modelled
     * as child nodes with their name prefixed with "attr_". Attributes are represented as nodes that <em>always</em>
     * exclusively contain a value.
     *
     * @param attributeName  The name of the attribute to add
     * @param attributeValue The value of the attribute
     */
    public void setAttribute(String attributeName, String attributeValue) {
        addChild(ATTRIBUTE_PREFIX + attributeName, attributeValue);
    }

    /**
     * Adds a child node to the current node. Note that the name of a child node must not be {@code null} and may
     * not start with "attr_", in order to differentiate between child nodes and attributes.
     *
     * @param name The name of the child node
     * @return A BSONNode representing the newly created child node.
     */
    public BSONNode addChildNode(String name) {
        Assert.isTrue(value == null,
                () -> "A value was already present." + "A node cannot contain a value as well as child nodes");
        Assert.notNull(name, () -> "Node name must not be null");
        Assert.isFalse(name.startsWith(ATTRIBUTE_PREFIX),
                () -> "Node names may not start with '" + ATTRIBUTE_PREFIX + "'");

        return addChild(name, null);
    }

    private BSONNode addChild(String name, String nodeValue) {
        BSONNode childNode = new BSONNode(name);
        childNode.setValue(nodeValue);
        this.childNodes.add(childNode);
        return childNode;
    }

    /**
     * Returns an iterator providing access to the child nodes of the current node. Changes to the node structure made
     * after this iterator is returned may not be reflected in that iterator.
     *
     * @return an iterator providing access to the child nodes of the current node
     */
    public Iterator<BSONNode> children() {
        List<BSONNode> children = childNodes.stream()
                .filter(child -> !child.encodedName.startsWith(ATTRIBUTE_PREFIX)).collect(Collectors.toList());
        return children.iterator();
    }

    /**
     * Returns a map containing the attributes of the current node. Changes to the node's attributes made
     * after this iterator is returned may not be reflected in that iterator.
     *
     * @return a Map containing the attributes of the current node
     */
    public Map<String, String> attributes() {
        Map<String, String> attrs = new HashMap<>();
        childNodes.stream().filter(
                child -> !VALUE_KEY.equals(child.encodedName) && child.encodedName.startsWith(ATTRIBUTE_PREFIX))
                .forEach(child -> attrs.put(child.encodedName, child.value));
        return attrs;
    }

    /**
     * Returns the value of the attribute with given {@code name}.
     *
     * @param name The name of the attribute to get the value for
     * @return the value of the attribute, or {@code null} if the attribute was not found
     */
    public String getAttribute(String name) {
        String attr = ATTRIBUTE_PREFIX + name;
        return getChildValue(attr);
    }

    private String getChildValue(String name) {
        for (BSONNode node : childNodes) {
            if (name.equals(node.encodedName)) {
                return node.value;
            }
        }
        return null;
    }

    /**
     * Returns the value of the current node
     *
     * @return the value of the current node, or {@code null} if this node has no value.
     */
    public String getValue() {
        return value;
    }

    /**
     * Returns the name of the current node
     *
     * @return the name of the current node
     */
    public String getName() {
        return decode(encodedName);
    }

    private static String encode(String name) {
        return name.replaceAll("\\/", "\\/\\/").replaceAll("\\.", "/");
    }

    private static String decode(String name) {
        return name.replaceAll("\\/([^/]])?", ".$1").replaceAll("\\/\\/", "/");
    }
}