All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.rexsl.page.JaxbBundle Maven / Gradle / Ivy

There is a newer version: 0.13.3
Show newest version
/**
 * Copyright (c) 2011-2013, ReXSL.com
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met: 1) Redistributions of source code must retain the above
 * copyright notice, this list of conditions and the following
 * disclaimer. 2) Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following
 * disclaimer in the documentation and/or other materials provided
 * with the distribution. 3) Neither the name of the ReXSL.com nor
 * the names of its contributors may be used to endorse or promote
 * products derived from this software without specific prior written
 * permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT
 * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
 * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package com.rexsl.page;

import com.jcabi.aspects.Loggable;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArrayList;
import javax.validation.constraints.NotNull;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

/**
 * JAXB bundle.
 *
 * 

It's a convenient instrument that enables on-fly creation of DOM/XML * structures, for example * (fluent * interface): * *

 Element element = new JaxbBundle("root")
 *   .add("employee")
 *     .attr("age", "28")
 *     .add("dept", "Software")
 *       .attr("country", "DE")
 *     .up()
 *     .add("salary", "> \u20AC 50,000")
 *     .up()
 *     .add("rank", "high")
 *   .up()
 *   .attr("time", new Date())
 *   .element();
* *

If you convert this {@code element} to XML this is how it will look: * *

 <?xml version="1.0" ?>
 * <root time="Sun Jul 20 16:17:00 EDT 1969">
 *   <employee age="28">
 *     <dept country="DE">Software</dept>
 *     <salary>&gt; € 50,000</salary>
 *     <rank>high</rank>
 *   </employee>
 * </root>
* *

Then, you can add this {@link Element} to your JAXB object, and return * it from a method annotated with {@link javax.xml.bind.annotation.XmlElement}, * for example: * *

 @XmlRootElement
 * public class Page {
 *   @XmlElement
 *   public Object getEmployee() {
 *     return new JaxbBundle("employee")
 *       .attr("age", "35")
 *       .attr("country", "DE")
 *       .add("salary", "> \u20AC 50,000")
 *       .up()
 *       .element();
 *   }
 * }
* *

This mechanism, very often, is much more convenient and shorter than * a declaration of a new POJO every time you need to return a small piece * of XML data. * *

Since version 0.4.10 it's possible to add links, groups, and other * bundles. For example: * *

 final Element element = new JaxbBundle("garage")
 *   .link(new Link("add", "/add-car"))
 *   .add("cars").add(
 *     new JaxbBundle.Group<String>(cars) {
 *       @Override
 *       public JaxbBundle bundle(Car car) {
 *         return new JaxbBundle("car").add("make", car.make()).up();
 *       }
 *     }
 *   ).up()
 *   .add(new JaxbBundle("owner").add("email", "...").up())
 *   .element();
* *

The class is mutable and thread-safe. * * @todo #430 A new design would be required for this class.
* The problems: *

    *
  • this.content and this.parent are used as flags, not only as data * storage variables. *
  • there are three different "types" combined in * one class: root element (parent == null), node element (content == * null), and leaf element. *
*
* The solution: A new hierarchy of classes:
* * interface TreeNode
*
* abstract AbstractNode implements TreeNode
* +name: String
* +attributes: ConcurrentMap
*
* abstract SubNode extends AbstractNode
* +parent: TreeNode
*
* InnerNode extends SubNode
* +children: List
*
* LeafNode extends SubNode
* +content: String
*
* DeadNode implements TreeNode
* // this is the node we set as parent in root node
*

* @author Yegor Bugayenko ([email protected]) * @version $Id$ * @since 0.3.7 */ @SuppressWarnings({ "PMD.NullAssignment", "PMD.TooManyMethods" }) @ToString(of = { "name", "content" }) @EqualsAndHashCode(of = { "name", "content", "parent" }) @Loggable(Loggable.DEBUG) public final class JaxbBundle { /** * Parent bundle, if exists. */ private final transient JaxbBundle parent; /** * Name of it. */ @NotNull private final transient String name; /** * Text content of it. */ private final transient String content; /** * Children. */ private final transient List children = new CopyOnWriteArrayList(); /** * DOM children. */ private final transient List elements = new CopyOnWriteArrayList(); /** * Links. */ private final transient List links = new CopyOnWriteArrayList(); /** * Attributes. */ private final transient ConcurrentMap attrs = new ConcurrentHashMap(0); /** * Default ctor, for JAXB (always throws a runtime exception). * *

You're not supposed to use this constructor. Instead, use either * {@link JaxbBundle(String)} or {@link JaxbBundle(String,String)}. */ public JaxbBundle() { throw new IllegalStateException( "JaxbBundle() ctor can't be used, use JaxbBundle(String) instead" ); } /** * Public ctor, with just a name of XML element and no text content. * @param nam The name of XML element */ public JaxbBundle(@NotNull final String nam) { this.parent = null; this.name = nam; this.content = ""; } /** * Public ctor, with XML element name and its content. * @param nam The name of XML element * @param text Text content of the XML document */ public JaxbBundle(@NotNull final String nam, @NotNull final String text) { this.parent = null; this.name = nam; this.content = text; } /** * Public ctor. * @param prnt Not-null parent bundle * @param nam The name of XML element * @param text Text content of the XML element */ private JaxbBundle(@NotNull final JaxbBundle prnt, @NotNull final String nam, @NotNull final String text) { this.parent = prnt; this.name = nam; this.content = text; } /** * Add new child XML element. * @param nam The name of child element * @return The child bundle (use {@link #up()} on it in order to get back to * the parent bundle) */ @NotNull public JaxbBundle add(@NotNull final String nam) { return this.add(nam, ""); } /** * Add new child XML element. * @param element The DOM element to add * @return The same bundle * @since 0.4.15 */ @NotNull public JaxbBundle add(@NotNull final Element element) { this.elements.add(element); return this; } /** * Add new child with text value. * @param nam The name of child * @param txt The text * @return The child bundle (use {@link #up()} on it in order to get back to * the parent bundle) */ @NotNull public JaxbBundle add(@NotNull final String nam, @NotNull final String txt) { final JaxbBundle child = new JaxbBundle(this, nam, txt); this.children.add(child); return child; } /** * Add XML attribute to this bundle. * @param nam The name of attribute * @param val The plain text value * @return New bundle with set attribute */ @NotNull public JaxbBundle attr(@NotNull final String nam, @NotNull final String val) { this.attrs.put(nam, val); return this; } /** * Add new link into {@code <links>} section. * @param link The link to attach * @return New bundle with a newly added link * @since 0.4.10 */ @NotNull public JaxbBundle link(@NotNull final Link link) { this.links.add(link); return this; } /** * Add new bundle. * @param bundle The bundle to add * @return New bundle with a newly added element * @since 0.4.10 */ @NotNull public JaxbBundle add(@NotNull final JaxbBundle bundle) { this.children.add(bundle); return this; } /** * Add new group. * @param group The group * @return New bundle with a newly added group of elements * @since 0.4.10 */ @NotNull public JaxbBundle add(@NotNull final JaxbBundle.Group group) { JaxbBundle holder = this; for (final JaxbBundle bundle : group.bundles()) { holder = holder.add(bundle); } return holder; } /** * Return parent bundle. * @return The parent bundle * @checkstyle MethodName (4 lines) */ @NotNull @SuppressWarnings("PMD.ShortMethodName") public JaxbBundle up() { if (this.parent == null) { throw new IllegalArgumentException( "Already at the top, can't go #up()" ); } return this.parent; } /** * Convert this bundle into DOM/XML {@link Element}. * @return The element */ @NotNull public Element element() { if (this.parent != null) { throw new IllegalArgumentException( "You can convert only top level JaxbBundle to DOM" ); } try { return this.element( DocumentBuilderFactory.newInstance() .newDocumentBuilder() .newDocument() ); } catch (ParserConfigurationException ex) { throw new IllegalStateException(ex); } } /** * Create and return a DOM element, inside the document provided. * @param doc The document * @return The element just created */ private Element element(final Document doc) { final Element element = doc.createElement(this.name); for (final Map.Entry attr : this.attrs.entrySet()) { element.setAttribute(attr.getKey(), attr.getValue()); } for (final JaxbBundle child : this.children) { element.appendChild(child.element(doc)); } for (final Element child : this.elements) { element.appendChild(doc.importNode(child, true)); } if (!this.links.isEmpty()) { final Element lnks = doc.createElement("links"); for (final Link link : this.links) { final Element lnk = doc.createElement("link"); lnk.setAttribute("rel", link.getRel()); lnk.setAttribute("href", link.getHref().toString()); lnk.setAttribute("type", link.getType()); lnks.appendChild(lnk); } element.appendChild(lnks); } element.appendChild(doc.createTextNode(this.content)); return element; } /** * Group. * @param Type of encapsulated elements * @since 0.4.10 * @checkstyle AbstractClassName (2 lines) */ @SuppressWarnings("PMD.AbstractNaming") public abstract static class Group { /** * Collection of objects. */ private final transient Iterable objects; /** * Public ctor. * @param objs All objects */ public Group(final Iterable objs) { this.objects = objs; } /** * Convert an object to JaxbBundle. * @param object The object to convert * @return Bundle produced */ public abstract JaxbBundle bundle(T object); /** * Fetch all bundles. * @return All bundles */ private Collection bundles() { final Collection bundles = new LinkedList(); for (final T object : this.objects) { bundles.add(this.bundle(object)); } return bundles; } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy