001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.juneau.marshall.jcs;
018
019import static org.apache.juneau.commons.utils.Shorts.*;
020
021import java.io.*;
022
023import org.apache.juneau.marshall.json.*;
024import org.apache.juneau.marshall.marshaller.*;
025import org.apache.juneau.marshall.serializer.*;
026import org.apache.juneau.marshall.stream.*;
027
028/**
029 * Serializes POJO models to canonical JSON per RFC 8785 (JCS).
030 *
031 * <h5 class='topic'>Media types</h5>
032 * <p>
033 *    Produces media type: <bc>application/jcs+json</bc>
034 * </p>
035 *
036 * <h5 class='topic'>Description</h5>
037 * <p>
038 *    JCS (JSON Canonicalization Scheme) produces a deterministic, byte-for-byte canonical
039 *    representation of JSON, enabling reliable cryptographic operations such as hashing and
040 *    digital signing. All canonicalization rules are defined in
041 *    <a class="doclink" href="https://www.rfc-editor.org/rfc/rfc8785">RFC 8785</a>.
042 * </p>
043 * <ul class='spaced-list'>
044 *    <li>No whitespace between tokens.
045 *    <li>Object properties sorted by UTF-16 code unit order, applied recursively to all nested objects.
046 *    <li>Numbers serialized using ECMAScript-compatible rules: shortest round-trip representation,
047 *        no trailing zeros, lowercase <code>e</code>, positive exponent sign included (e.g. <code>1e+30</code>).
048 *    <li>Negative zero serialized as <code>0</code>.
049 *    <li>Non-ASCII characters emitted as literal UTF-8 bytes (no unnecessary unicode escape sequences).
050 *    <li>Array element order is preserved (not sorted).
051 * </ul>
052 *
053 * <h5 class='section'>Example:</h5>
054 * <p class='bjava'>
055 *    <jc>// Create a bean.</jc>
056 *    MyBean <jv>bean</jv> = <jk>new</jk> MyBean().name(<js>"Alice"</js>).age(30);
057 *
058 *    <jc>// Serialize to canonical JSON.</jc>
059 *    String <jv>json</jv> = JcsSerializer.<jsf>DEFAULT</jsf>.write(<jv>bean</jv>);
060 *
061 *    <jc>// Or use the Jcs marshaller convenience method.</jc>
062 *    String <jv>json</jv> = Jcs.<jsm>of</jsm>(<jv>bean</jv>);
063 * </p>
064 *
065 * <h5 class='figure'>Example output (bean with name/age):</h5>
066 * <p class='bjson'>
067 *    {<jok>"age"</jok>:<jov>30</jov>,<jok>"name"</jok>:<jov>"Alice"</jov>}
068 * </p>
069 *
070 * <h5 class='figure'>Example output with nested object:</h5>
071 * <p class='bjson'>
072 *    {<jok>"address"</jok>:{<jok>"city"</jok>:<jov>"Denver"</jov>,<jok>"zip"</jok>:<jov>"80201"</jov>},<jok>"name"</jok>:<jov>"Alice"</jov>}
073 * </p>
074 *
075 * <h5 class='section'>Notes:</h5><ul>
076 *    <li class='note'>This class is thread safe and reusable.
077 *    <li class='note'>
078 *       JCS output is valid JSON and can be parsed using the standard {@link JsonParser}.
079 *       The {@link Jcs} marshaller pairs this serializer with {@link JsonParser} for full round-trip support.
080 *    <li class='note'>
081 *       {@link java.math.BigDecimal} and {@link java.math.BigInteger} values beyond IEEE 754 double
082 *       precision range will lose precision or throw a {@link SerializeException}.
083 *       This is a spec-level constraint defined by RFC 8785.
084 *    <li class='note'>
085 *       {@link Double#NaN}, {@link Double#POSITIVE_INFINITY}, and {@link Double#NEGATIVE_INFINITY}
086 *       are not permitted and will throw a {@link SerializeException}.
087 *    <li class='note'>
088 *       Strings containing lone UTF-16 surrogate code units will throw {@link SerializeException}.
089 *    <li class='note'>
090 *       Uses <code>application/jcs+json</code> to avoid content-negotiation collision with
091 *       {@link JsonSerializer} (<code>application/json</code>). Request explicitly via
092 *       <c>Accept: application/jcs+json</c> for canonical output.
093 * </ul>
094 *
095 * <h5 class='section'>See Also:</h5><ul>
096 *    <li class='link'><a class="doclink" href="https://www.rfc-editor.org/rfc/rfc8785">RFC 8785 — JSON Canonicalization Scheme</a>
097 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/Jcs">JCS</a>
098 *    <li class='jc'>{@link Jcs}
099 *    <li class='jc'>{@link JsonParser}
100 * </ul>
101 */
102@SuppressWarnings({
103   "java:S110", // Inheritance depth acceptable for this class hierarchy
104   "java:S1192", // Duplicated literals (argument/property names) read more clearly inline than as constants
105   "java:S9149", // Per-format static factories intentionally shadow the parent's.
106   "resource" // Closeable resources are owned by the caller's serializer session; Eclipse JDT @Owning warning is by design.
107})
108public class JcsSerializer extends JsonSerializer {
109
110   /**
111    * Builder class.
112    */
113   public static class Builder extends JsonSerializer.Builder<Builder> {
114
115      /**
116       * Constructor, default settings for JCS canonical output.
117       */
118      protected Builder() {
119         super();
120         produces("application/jcs+json");
121         accept("application/jcs+json");
122         quoteChar('"');
123         useWhitespace(false);
124      }
125
126      /**
127       * Copy constructor.
128       *
129       * @param copyFrom The builder to copy from.
130       *    <br>Cannot be <jk>null</jk>.
131       */
132      protected Builder(Builder copyFrom) {
133         super(reqnn("copyFrom", copyFrom));
134      }
135
136      /**
137       * Copy constructor.
138       *
139       * @param copyFrom The serializer to copy from.
140       *    <br>Cannot be <jk>null</jk>.
141       */
142      protected Builder(JcsSerializer copyFrom) {
143         super(reqnn("copyFrom", copyFrom));
144      }
145
146      @Override /* Overridden from Context.Builder<?> */
147      public Builder copy() {
148         return new Builder(this);
149      }
150
151      @Override /* Overridden from Context.Builder<?> */
152      public JcsSerializer build() {
153         return new JcsSerializer(this);
154      }
155   }
156
157   /** Default serializer. */
158   public static final JcsSerializer DEFAULT = new JcsSerializer(create());
159
160   /**
161    * Creates a new builder for this object.
162    *
163    * @return A new builder.
164    */
165   public static Builder create() {
166      return new Builder();
167   }
168
169   /**
170    * Constructor.
171    *
172    * @param builder The builder for this object.
173    */
174   public JcsSerializer(Builder builder) {
175      super(builder);
176   }
177
178   @Override /* Overridden from Context */
179   public Builder copy() {
180      return new Builder(this);
181   }
182
183   @Override /* Overridden from JsonSerializer */
184   public JcsSerializerSession.Builder createSession() {
185      return JcsSerializerSession.create(this);
186   }
187
188   /**
189    * Convenience delegator (default session args) for the raw-JSON token writer.  Real impl on
190    * {@link JcsSerializerSession#writeTokens(Object)}.
191    *
192    * <p>
193    * <b>Note:</b> the cursor's structural methods ({@link TokenWriter#startObject() startObject},
194    * {@link TokenWriter#fieldName(String) fieldName}, {@link TokenWriter#string(String) string}, ...)
195    * produce ordinary JSON; they do <em>not</em> apply the JCS canonicalization rules (sorted
196    * keys, normalized number formatting, etc.) that {@link #write(Object)} produces.  The
197    * {@link TokenWriter#object(Object) object(Object)} bridge is <b>disabled</b> on this writer.
198    * Use {@link #write(Object)} for canonical output.
199    *
200    * @param output The output.
201    * @return A new {@link JsonTokenWriter} with {@code object(...)} disabled.
202    * @throws IOException If the output type is not supported or could not be opened.
203    */
204   @Override /* TokenWritable */
205   public TokenWriter writeTokens(Object output) throws IOException {
206      return getSession().writeTokens(output);
207   }
208
209   /**
210    * Convenience delegator (default session args) for a record writer that emits canonical JCS
211    * for the value passed to {@link RecordWriter#write(Object) write(...)}.  Real impl on
212    * {@link JcsSerializerSession#writeRecords(Object)}.
213    *
214    * @param output The output.
215    * @return A new {@link RecordWriter}.
216    * @throws IOException If the output type is not supported or could not be opened.
217    */
218   @Override /* RecordWritable */
219   public RecordWriter writeRecords(Object output) throws IOException {
220      return getSession().writeRecords(output);
221   }
222
223   @Override /* RecordWritable */
224   public boolean isRecordStreaming() { return false; }
225}