/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright (c) 2010-2012 Oracle and/or its affiliates. All rights reserved. * * The contents of this file are subject to the terms of either the GNU * General Public License Version 2 only ("GPL") or the Common Development * and Distribution License("CDDL") (collectively, the "License"). You * may not use this file except in compliance with the License. You can * obtain a copy of the License at * http://glassfish.java.net/public/CDDL+GPL_1_1.html * or packager/legal/LICENSE.txt. See the License for the specific * language governing permissions and limitations under the License. * * When distributing the software, include this License Header Notice in each * file and include the License file at packager/legal/LICENSE.txt. * * GPL Classpath Exception: * Oracle designates this particular file as subject to the "Classpath" * exception as provided by Oracle in the GPL Version 2 section of the License * file that accompanied this code. * * Modifications: * If applicable, add the following below the License Header, with the fields * enclosed by brackets [] replaced by your own identifying information: * "Portions Copyright [year] [name of copyright owner]" * * Contributor(s): * If you wish your version of this file to be governed by only the CDDL or * only the GPL Version 2, indicate your decision by adding "[Contributor] * elects to include this software in this distribution under the [CDDL or GPL * Version 2] license." If you don't indicate a single choice of license, a * recipient has the option to distribute your version of this file under * either the CDDL, the GPL Version 2 or to extend the choice of license to * its licensees as provided above. However, if you add GPL Version 2 code * and therefore, elected the GPL Version 2 license, then the option applies * only if the new code is made subject to such option by the copyright * holder. */ package com.sun.jersey.api.json; import com.sun.jersey.json.impl.ImplMessages; import com.sun.jersey.json.impl.JSONHelper; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; /** * An immutable configuration of JSON notation and options. JSONConfiguration could be used * for configuring the JSON notation on {@link JSONJAXBContext} * * @author Jakub.Podlesak@Sun.COM */ public class JSONConfiguration { /** * A ResourceConfig feature, which allows you to enable JSON/POJO mapping functionality * in Jersey. If set to true, your application will be capable of transforming JSON * data to and out of POJOs. This also includes any JAXB beans existing in your * application. I.e. all those beans would not be processed via XML, but rather * directly marshaled and un-marshaled to and from JSON using the POJO mapping functionality. */ public final static String FEATURE_POJO_MAPPING = "com.sun.jersey.api.json.POJOMappingFeature"; /** * Enumeration of supported JSON notations. */ public enum Notation { /** * The mapped (default) JSON notation. * <p>Example JSON expression:<pre> * {"columns":[{"id":"userid","label":"UserID"},{"id":"name","label":"User Name"}],"rows":{"userid":"1621","name":"Grotefend"}} * </pre> */ MAPPED, /** * The mapped Jettison JSON notation. * <p>Example JSON expression:<pre> * {"userTable":{"columns":[{"id":"userid","label":"UserID"},{"id":"name","label":"User Name"}],"rows":{"userid":1621,"name":"Grotefend"}}} * </pre> */ MAPPED_JETTISON, /** * The mapped Badgerfish JSON notation. * <p>Example JSON expression:<pre> * {"userTable":{"columns":[{"id":{"$":"userid"},"label":{"$":"UserID"}},{"id":{"$":"name"},"label":{"$":"User Name"}}],"rows":{"userid":{"$":"1621"},"name":{"$":"Grotefend"}}}} * </pre> */ BADGERFISH, /** * The natural JSON notation, leveraging closely-coupled JAXB RI integration. * <p>Example JSON expression:<pre> * {"columns":[{"id":"userid","label":"UserID"},{"id":"name","label":"User Name"}],"rows":[{"userid":1621,"name":"Grotefend"}]} * </pre> */ NATURAL } private final Notation notation; private final Collection<String> arrays; private final Collection<String> attrsAsElems; private final Collection<String> nonStrings; private final boolean rootUnwrapping; private final boolean humanReadableFormatting; private final Map<String, String> jsonXml2JsonNs; private final boolean usePrefixAtNaturalAttributes; private final Character namespaceSeparator; /** * Builder class for constructing {@link JSONConfiguration} options */ public static class Builder { private final Notation notation; protected Collection<String> arrays = new HashSet<String>(0); protected Collection<String> attrsAsElems = new HashSet<String>(0); protected Collection<String> nonStrings = new HashSet<String>(0); protected boolean rootUnwrapping = true; protected boolean humanReadableFormatting = false; protected Map<String, String> jsonXml2JsonNs = new HashMap<String, String>(0); protected boolean usePrefixAtNaturalAttributes = false; protected Character namespaceSeparator = '.'; private Builder(Notation notation) { this.notation = notation; } /** * Constructs a new immutable {@link JSONConfiguration} object based on options set on this Builder * * @return a non-null {@link JSONConfiguration} instance */ public JSONConfiguration build() { return new JSONConfiguration(this); } private void copyAttributes(JSONConfiguration jc) { arrays.addAll(jc.getArrays()); attrsAsElems.addAll(jc.getAttributeAsElements()); nonStrings.addAll(jc.getNonStrings()); rootUnwrapping = jc.isRootUnwrapping(); humanReadableFormatting = jc.isHumanReadableFormatting(); jsonXml2JsonNs.putAll(jc.getXml2JsonNs()); usePrefixAtNaturalAttributes = jc.isUsingPrefixesAtNaturalAttributes(); namespaceSeparator = jc.getNsSeparator(); } } /** * Builder class for constructing {@link JSONConfiguration} options * for the {@link Notation#NATURAL} convention. */ public static class NaturalBuilder extends Builder { private NaturalBuilder(Notation notation) { super(notation); } /** * Setter for XML root element unwrapping. * This property is valid for the {@link JSONConfiguration.Notation#MAPPED} * and {@link JSONConfiguration.Notation#NATURAL} notations only. * <p> * If set to true, JSON code corresponding to the XML root element will be stripped out * <p> * The default value is false. * @param rootUnwrapping if set to true, JSON code corresponding to the * XML root element will be stripped out. * @return the natural builder. */ public NaturalBuilder rootUnwrapping(boolean rootUnwrapping) { this.rootUnwrapping = rootUnwrapping; return this; } /** * If set to true, generated JSON will contain new-line characters and indentation, so that * the output is easy to read for people. * This property is valid for the {@link JSONConfiguration.Notation#NATURAL} notation only. * <p/> * The default value is false. * * @param humanReadableFormatting whether the output should be formatted to be readable by humans easily. * @return the natural builder. */ public NaturalBuilder humanReadableFormatting(boolean humanReadableFormatting) { this.humanReadableFormatting = humanReadableFormatting; return this; } /** * JSON names corresponding to XML attributes will be written using a '@' prefix * This property is valid for the {@link JSONConfiguration.Notation#NATURAL} notation only. * @return the natural builder. */ public NaturalBuilder usePrefixesAtNaturalAttributes() { this.usePrefixAtNaturalAttributes = true; return this; } } /** * Builder class for constructing {@link JSONConfiguration} options * for the {@link Notation#MAPPED_JETTISON} convention. */ public static class MappedJettisonBuilder extends Builder { private MappedJettisonBuilder(Notation notation) { super(notation); rootUnwrapping = false; } /** * Setter for XML to JSON namespace mapping. * This property is valid for the {@link JSONConfiguration.Notation#MAPPED_JETTISON} * and {@link JSONConfiguration.Notation#MAPPED} notations only. * <p> * The value is a map with zero or more * key/value pairs, where the key is an XML namespace and the value * is the prefix to use as the replacement for the XML namespace. * <p> * The default value is a map with zero key/value pairs. */ public MappedJettisonBuilder xml2JsonNs(Map<String, String> jsonXml2JsonNs) { this.jsonXml2JsonNs = jsonXml2JsonNs; return this; } } /** * Builder class for constructing {@link JSONConfiguration} options * for the {@link Notation#MAPPED} convention. */ public static class MappedBuilder extends Builder { private MappedBuilder(Notation notation) { super(notation); } /** * Adds name(s) to JSON arrays configuration property. * This property is valid for the {@link JSONConfiguration.Notation#MAPPED} notation only. * <p> * The property value is a collection of strings representing JSON object names. * Those objects will be declared as arrays in JSON document. * <p> * For example, consider that the property value is not set and the * JSON document is <code>{ ..., "arr1":"single element", ... }</code>. * If the property value is set to contain <code>"arr1"</code> then * the JSON document would become <code>{ ..., "arr1":["single element"], ... }</code>. * <p> * The default value is an empty collection. * @param arrays an array of strings representing JSON object names. * @return the mapped builder. */ public MappedBuilder arrays(String... arrays) { this.arrays.addAll(Arrays.asList(arrays)); return this; } /** * Adds name(s) toJSON attributes as elements property. * This property is valid for the {@link JSONConfiguration.Notation#MAPPED} notation only. * <p> * The value is a collection of string values that are * object names that correspond to XML attribute information items. * The value of an object name in the JSON document that exists in the collection * of object names will be declared as an element as not as an attribute if * the object corresponds to an XML attribute information item. * <p> * For example, consider that the property value is not set and the * JSON document is <code>{ ..., "@number":"12", ... }</code>. * If the property value is set to contain <code>"number"</code> * then the JSON document would be <code>{ ..., "number":"12", ... }</code>. * <p> * The default value is an empty collection. * @param attributeAsElements an array of string values that are * object names that correspond to XML attribute information items. * @return the mapped builder. */ public MappedBuilder attributeAsElement(String... attributeAsElements) { this.attrsAsElems.addAll(Arrays.asList(attributeAsElements)); return this; } /** * Adds name(s) JSON non-string values property. * This property is valid for the {@link JSONConfiguration.Notation#MAPPED} notation only. * <p> * The value is collection of string values that are * object names. * The value of an object name in the JSON document that exists in the collection * of object names will be declared as non-string value, which is not surrounded * by double quotes. * <p> * For example, consider that the property value is not set and the * JSON document is <code>{ ..., "anumber":"12", ... }</code>. * If the property value is set to contain <code>"anumber"</code> * then the JSON document would be <code>{ ..., "anumber":12, ... }</code>. * <p> * The default value is an empty collection. * @param nonStrings an array of string values that are * object names * @return the mapped builder. */ public MappedBuilder nonStrings(String... nonStrings) { this.nonStrings.addAll(Arrays.asList(nonStrings)); return this; } /** * Setter for XML to JSON namespace mapping. * This property is valid for the {@link JSONConfiguration.Notation#MAPPED_JETTISON} * and {@link JSONConfiguration.Notation#MAPPED} notations only. * <p> * The value is a map with zero or more * key/value pairs, where the key is an XML namespace and the value * is the prefix to use as the replacement for the XML namespace. * <p> * The default value is a map with zero key/value pairs. */ public MappedBuilder xml2JsonNs(Map<String, String> jsonXml2JsonNs) { this.jsonXml2JsonNs = jsonXml2JsonNs; return this; } /** * Setter for XML namespace separator. * This property is valid for the {@link JSONConfiguration.Notation#MAPPED} notation only. * <p> * The value is a character used to separate a namespace identifier from the name * of a XML attribute/element in JSON. * <p> * The default value is dot character ('.'). */ public MappedBuilder nsSeparator(Character separator) { if (separator == null) { throw new NullPointerException("Namespace separator can not be null!"); } this.namespaceSeparator = separator; return this; } /** * Setter for XML root element unwrapping. * This property is valid for the {@link JSONConfiguration.Notation#MAPPED} * and {@link JSONConfiguration.Notation#NATURAL} notations only. * <p> * If set to true, JSON code corresponding to the XML root element will be stripped out * <p> * The default value is false. * @param rootUnwrapping if set to true, JSON code corresponding to the * XML root element will be stripped out. * @return the mapped builder. */ public MappedBuilder rootUnwrapping(boolean rootUnwrapping) { this.rootUnwrapping = rootUnwrapping; return this; } } private JSONConfiguration(Builder b) { notation = b.notation; arrays = b.arrays; attrsAsElems = b.attrsAsElems; nonStrings = b.nonStrings; rootUnwrapping = b.rootUnwrapping; humanReadableFormatting = b.humanReadableFormatting; jsonXml2JsonNs = b.jsonXml2JsonNs; usePrefixAtNaturalAttributes = b.usePrefixAtNaturalAttributes; namespaceSeparator = b.namespaceSeparator; } /** * A static method for obtaining {@link JSONConfiguration} instance with humanReadableFormatting * set according to formatted parameter. * * @param c original instance of {@link JSONConfiguration}, can't be null * @param formatted whether the output should be formatted. * @return copy of provided {@link JSONConfiguration} with humanReadableFormatting set to formatted. * @throws IllegalArgumentException when provided JSONConfiguration is null. */ public static JSONConfiguration createJSONConfigurationWithFormatted(JSONConfiguration c, boolean formatted) throws IllegalArgumentException { if(c == null) { throw new IllegalArgumentException("JSONConfiguration can't be null"); } if (c.isHumanReadableFormatting() == formatted) { return c; } Builder b = copyBuilder(c); b.humanReadableFormatting = formatted; return b.build(); } /** * A static method for obtaining {@link JSONConfiguration} instance with rootUnwrapping * set according to formatted parameter. * * @param c original instance of {@link JSONConfiguration}, can't be null * @param rootUnwrapping * @return copy of provided {@link JSONConfiguration} with humanReadableFormatting set to formatted. * @throws IllegalArgumentException when provided JSONConfiguration is null. */ public static JSONConfiguration createJSONConfigurationWithRootUnwrapping(JSONConfiguration c, boolean rootUnwrapping) throws IllegalArgumentException { if(c == null){ throw new IllegalArgumentException("JSONConfiguration can't be null"); } if (c.isRootUnwrapping() == rootUnwrapping) { return c; } Builder b = copyBuilder(c); b.rootUnwrapping = rootUnwrapping; return b.build(); } /** * The default JSONConfiguration uses {@link JSONConfiguration.Notation#MAPPED} notation with root unwrapping option set to true. */ public static final JSONConfiguration DEFAULT = mapped().rootUnwrapping(true).build(); /** * A static method for obtaining a builder of {@link JSONConfiguration} instance, which will use {@link Notation#NATURAL} JSON notation. * After getting the builder, you can set configuration options on it, and finally get an immutable JSONConfiguration * instance using the {@link Builder#build() } method. * * @return a builder for JSONConfiguration instance */ public static NaturalBuilder natural() { // this is to make sure people trying to use NATURAL notation will get clear message what is missing, when an old JAXB RI version is used if (!JSONHelper.isNaturalNotationEnabled()) { Logger.getLogger(JSONConfiguration.class.getName()).log(Level.SEVERE, ImplMessages.ERROR_JAXB_RI_2_1_10_MISSING()); throw new RuntimeException(ImplMessages.ERROR_JAXB_RI_2_1_10_MISSING()); } return new NaturalBuilder(Notation.NATURAL); } /** * A static method for obtaining a builder of {@link JSONConfiguration} instance, which will use {@link Notation#MAPPED} JSON notation. * After getting the builder, you can set configuration options on it and finally get an immutable JSONConfiguration * instance the using {@link Builder#build() } method. * * @return a builder for JSONConfiguration instance */ public static MappedBuilder mapped() { return new MappedBuilder(Notation.MAPPED); } /** * A static method for obtaining a builder of {@link JSONConfiguration} instance, which will use {@link Notation#MAPPED_JETTISON} JSON notation. * After getting the builder, you can set configuration options on it and finally get an immutable JSONConfiguration * instance using the {@link Builder#build() } method. * * @return a builder for JSONConfiguration instance */ public static MappedJettisonBuilder mappedJettison() { return new MappedJettisonBuilder(Notation.MAPPED_JETTISON); } /** * A static method for obtaining a builder of {@link JSONConfiguration} instance, which will use {@link Notation#BADGERFISH} JSON notation. * After getting the builder, you can set configuration options on it and finally get an immutable JSONConfiguration * instance using the {@link Builder#build() } method. * * @return a builder for JSONConfiguration instance */ public static Builder badgerFish() { Builder badgerFishBuilder = new Builder(Notation.BADGERFISH); badgerFishBuilder.rootUnwrapping = false; return badgerFishBuilder; } public static Builder copyBuilder(final JSONConfiguration jc) { Builder result = new Builder(jc.getNotation()); switch (jc.notation) { case BADGERFISH: result = new Builder(jc.getNotation()); break; case MAPPED_JETTISON: result = new MappedJettisonBuilder(jc.getNotation()); break; case MAPPED: result = new MappedBuilder(jc.getNotation()); break; case NATURAL: result = new NaturalBuilder(jc.getNotation()); break; } result.copyAttributes(jc); return result; } /** * Returns JSON notation selected for this configuration * @return JSON notation */ public Notation getNotation() { return notation; } /** * Returns JSON array names property * This property is valid for the {@link JSONConfiguration.Notation#MAPPED} notation only. * @return collection of array names * @see MappedBuilder#arrays(java.lang.String...) */ public Collection<String> getArrays() { return (arrays != null) ? Collections.unmodifiableCollection(arrays) : null; } /** * Returns names of attributes, which will be handled as elements * This property is valid for the {@link JSONConfiguration.Notation#MAPPED} notation only. * @return attribute as element names collection * @see MappedBuilder#attributeAsElement(java.lang.String...) */ public Collection<String> getAttributeAsElements() { return (attrsAsElems != null) ? Collections.unmodifiableCollection(attrsAsElems) : null; } /** * Returns a map for XML to JSON namespace mapping * This property is valid for the {@link JSONConfiguration.Notation#MAPPED} notation only. * @return a map for XML to JSON namespace mapping * @see MappedBuilder#xml2JsonNs(java.util.Map) */ public Map<String, String> getXml2JsonNs() { return (jsonXml2JsonNs != null) ? Collections.unmodifiableMap(jsonXml2JsonNs) : null; } /** * Returns XML namespace separator, which is used when constructing JSON identifiers * for XML elements/attributes in other than default namespace * This property is valid for the {@link JSONConfiguration.Notation#MAPPED} notation only. * @return XML namespace separator character * @see MappedBuilder#nsSeparator(java.lang.Character) */ public Character getNsSeparator() { return namespaceSeparator; } /** * Returns names of JSON objects, which will be serialized out as non-strings, i.e. without delimiting their values with double quotes * This property is valid for the {@link JSONConfiguration.Notation#MAPPED} notation only. * @return name of non-string JSON objects * @see MappedBuilder#nonStrings(java.lang.String...) */ public Collection<String> getNonStrings() { return (nonStrings != null) ? Collections.unmodifiableCollection(nonStrings) : null; } /** * Says if the root element will be stripped off * This property is valid for the {@link JSONConfiguration.Notation#MAPPED} * and {@link Notation#NATURAL} notations. * @return true, if root element has to be stripped off * @see MappedBuilder#rootUnwrapping(boolean) */ public boolean isRootUnwrapping() { return rootUnwrapping; } /** * Says if the JSON names corresponding to XML attributes should use a '@' prefix. * This property is valid for the {@link JSONConfiguration.Notation#NATURAL} notation only. * @return true, if prefixes are added * @see NaturalBuilder#usePrefixesAtNaturalAttributes() */ public boolean isUsingPrefixesAtNaturalAttributes() { return usePrefixAtNaturalAttributes; } /** * Says if the output JSON will be formatted with new-line characters * and indentation so that it is easy to read for people. * This property is valid for the {@link JSONConfiguration.Notation#NATURAL} notation only. * @return true, if formatting is applied on the output JSON * @see NaturalBuilder#humanReadableFormatting(boolean) */ public boolean isHumanReadableFormatting() { return humanReadableFormatting; } @Override public String toString() { return String.format("{notation:%s,rootStripping:%b}", notation, rootUnwrapping); } }