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.msgpack; 018 019import static org.apache.juneau.commons.utils.Shorts.*; 020import static org.apache.juneau.commons.utils.SystemUtils.*; 021 022import java.io.*; 023import java.time.*; 024import java.util.*; 025import java.util.concurrent.*; 026 027import org.apache.juneau.commons.bean.*; 028import org.apache.juneau.commons.collections.*; 029import org.apache.juneau.marshall.*; 030import org.apache.juneau.marshall.serializer.*; 031import org.apache.juneau.marshall.stream.*; 032 033/** 034 * Serializes POJO models to MessagePack. 035 * 036 * <h5 class='section'>Media types:</h5> 037 * 038 * Handles <c>Accept</c> types: <bc>application/msgpack, octal/msgpack</bc> 039 * <p> 040 * Produces <c>Content-Type</c> types: <bc>application/msgpack</bc> 041 * 042 * <p> 043 * The legacy media type <bc>octal/msgpack</bc> is retained in the <c>Accept</c> list as a 044 * backward-compatibility alias. The <c>Content-Type</c> emitted by the serializer is always the 045 * RFC-standard <bc>application/msgpack</bc> value. 046 * 047 * <h5 class='topic'>Description</h5> 048 * <p> 049 * Java-to-MessagePack type mapping: 050 * <ul class='spaced-list'> 051 * <li>Maps and beans → MessagePack map type 052 * <li>Collections and arrays → MessagePack array type 053 * <li>{@link String} / {@link Enum} → MessagePack string type 054 * <li>{@link Boolean} → MessagePack boolean type 055 * <li>{@link Number} (int, long, float, double, etc.) → MessagePack number type (variable-length int or float) 056 * <li>{@link Date} / {@link Calendar} / {@link Instant} → ISO 8601 string 057 * <li>{@link Duration} → ISO 8601 duration string 058 * <li>{@code byte[]} → MessagePack binary type 059 * <li>{@code null} → MessagePack nil type 060 * <li>All other types → fallback to {@code toString()} as string 061 * </ul> 062 * 063 * <h5 class='section'>Notes:</h5><ul> 064 * <li class='note'>This class is thread safe and reusable. 065 * </ul> 066 * 067 * <h5 class='section'>See Also:</h5><ul> 068 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/MessagePackSupport">MessagePack</a> 069 070 * </ul> 071 */ 072@SuppressWarnings({ 073 "java:S110", // Inheritance depth acceptable for this class hierarchy 074 "java:S1192", // Duplicated literals (argument/property names) read more clearly inline than as constants 075 "java:S9149", // Per-format static factories intentionally shadow the parent's. 076 "resource" // Closeable resources are owned by the caller's serializer session; Eclipse JDT @Owning warning is by design. 077}) 078public class MsgPackSerializer extends OutputStreamSerializer implements MsgPackMetaProvider, TokenWritable, ArrayRecordWritable { 079 080 /** Default serializer, BASE64 string output. */ 081 public static class Base64 extends MsgPackSerializer { 082 083 /** 084 * Constructor. 085 * 086 * @param builder The builder for this object. 087 * <br>Cannot be <jk>null</jk>. 088 */ 089 public Base64(Builder builder) { 090 super(reqnn("builder", builder).binaryFormat(BinaryFormat.BASE64)); 091 } 092 } 093 094 /** 095 * Builder class. 096 */ 097 public static class Builder extends OutputStreamSerializer.Builder<Builder> { 098 099 private static final Cache<HashKey,MsgPackSerializer> CACHE = Cache.of(HashKey.class, MsgPackSerializer.class).build(); 100 101 private boolean addBeanTypesMsgPack; 102 103 /** 104 * Constructor, default settings. 105 */ 106 protected Builder() { 107 produces("application/msgpack"); 108 accept("application/msgpack,octal/msgpack"); 109 addBeanTypesMsgPack = env("MsgPackSerializer.addBeanTypesMsgPack", false); 110 } 111 112 /** 113 * Copy constructor. 114 * 115 * @param copyFrom The builder to copy from. 116 * <br>Cannot be <jk>null</jk>. 117 */ 118 protected Builder(Builder copyFrom) { 119 super(reqnn("copyFrom", copyFrom)); 120 addBeanTypesMsgPack = copyFrom.addBeanTypesMsgPack; 121 } 122 123 /** 124 * Copy constructor. 125 * 126 * @param copyFrom The bean to copy from. 127 * <br>Cannot be <jk>null</jk>. 128 */ 129 protected Builder(MsgPackSerializer copyFrom) { 130 super(reqnn("copyFrom", copyFrom)); 131 addBeanTypesMsgPack = copyFrom.addBeanTypesMsgPack; 132 } 133 134 /** 135 * Add <js>"_type"</js> properties when needed. 136 * 137 * <p> 138 * If <jk>true</jk>, then <js>"_type"</js> properties will be added to beans if their type cannot be inferred 139 * through reflection. 140 * 141 * <p> 142 * When present, this value overrides the {@link org.apache.juneau.marshall.serializer.Serializer.Builder#addBeanTypes() Serializer.Builder#addBeanTypes()} setting and is 143 * provided to customize the behavior of specific serializers in a {@link SerializerSet}. 144 * 145 * @return This object. 146 */ 147 public Builder addBeanTypesMsgPack() { 148 return addBeanTypesMsgPack(true); 149 } 150 151 /** 152 * Same as {@link #addBeanTypesMsgPack()} but allows you to explicitly specify the value. 153 * 154 * @param value The value for this setting. 155 * @return This object. 156 */ 157 public Builder addBeanTypesMsgPack(boolean value) { 158 addBeanTypesMsgPack = value; 159 return this; 160 } 161 162 @Override /* Overridden from Context.Builder<?> */ 163 public MsgPackSerializer build() { 164 return cache(CACHE).build(MsgPackSerializer.class); 165 } 166 167 @Override /* Overridden from Context.Builder<?> */ 168 public Builder copy() { 169 return new Builder(this); 170 } 171 172 @Override /* Overridden from Context.Builder<?> */ 173 public HashKey hashKey() { 174 return HashKey.of(super.hashKey(), addBeanTypesMsgPack); 175 } 176 177 178 } 179 180 /** Default serializer, spaced-hex string output. */ 181 public static class SpacedHex extends MsgPackSerializer { 182 183 /** 184 * Constructor. 185 * 186 * @param builder The builder for this object. 187 */ 188 public SpacedHex(Builder builder) { 189 super(builder.binaryFormat(BinaryFormat.SPACED_HEX)); 190 } 191 } 192 193 /** Default serializer, all default settings.*/ 194 public static final MsgPackSerializer DEFAULT = new MsgPackSerializer(create()); 195 /** Default serializer, all default settings, spaced-hex string output.*/ 196 public static final MsgPackSerializer DEFAULT_SPACED_HEX = new SpacedHex(create()); 197 198 /** Default serializer, all default settings, spaced-hex string output.*/ 199 public static final MsgPackSerializer DEFAULT_BASE64 = new Base64(create()); 200 201 /** 202 * Creates a new builder for this object. 203 * 204 * @return A new builder. 205 */ 206 public static Builder create() { 207 return new Builder(); 208 } 209 210 protected final boolean addBeanTypesMsgPack; 211 212 private final Map<BeanPropertyMeta,MsgPackBeanPropertyMeta> msgPackBeanPropertyMetas = new ConcurrentHashMap<>(); 213 private final Map<ClassMeta<?>,MsgPackClassMeta> msgPackClassMetas = new ConcurrentHashMap<>(); 214 215 /** 216 * Constructor. 217 * 218 * @param builder The builder for this object. 219 */ 220 public MsgPackSerializer(Builder builder) { 221 super(builder); 222 this.addBeanTypesMsgPack = builder.addBeanTypesMsgPack; 223 } 224 225 @Override /* Overridden from Context */ 226 public Builder copy() { 227 return new Builder(this); 228 } 229 230 @Override /* Overridden from Context */ 231 public MsgPackSerializerSession.Builder createSession() { 232 return MsgPackSerializerSession.create(this); 233 } 234 235 @Override /* Overridden from MsgPackMetaProvider */ 236 public MsgPackBeanPropertyMeta getMsgPackBeanPropertyMeta(BeanPropertyMeta bpm) { 237 if (bpm == null) 238 return MsgPackBeanPropertyMeta.DEFAULT; 239 return msgPackBeanPropertyMetas.computeIfAbsent(bpm, k -> new MsgPackBeanPropertyMeta(k.getDelegateFor(), this)); 240 } 241 242 @Override /* Overridden from MsgPackMetaProvider */ 243 public MsgPackClassMeta getMsgPackClassMeta(ClassMeta<?> cm) { 244 return msgPackClassMetas.computeIfAbsent(cm, k -> new MsgPackClassMeta(k, this)); 245 } 246 247 @Override /* Overridden from Context */ 248 public MsgPackSerializerSession getSession() { return createSession().build(); } 249 250 /** 251 * Convenience delegator that opens a {@link MsgPackTokenWriter} over the output using 252 * <b>default session arguments</b> (mirrors {@link #write(Object)}). 253 * 254 * <p> 255 * The real implementation lives on {@link MsgPackSerializerSession#writeTokens(Object)}. 256 * Callers that need request-derived configuration (locale, timezone, schema, swaps) should 257 * call {@link #createSession()} and invoke 258 * {@link MsgPackSerializerSession#writeTokens(Object)} on the built session instead. 259 * 260 * @param output The output. 261 * @return A new {@link MsgPackTokenWriter}. 262 * @throws IOException If the output type is not supported or could not be opened. 263 */ 264 @Override /* TokenWritable */ 265 public TokenWriter writeTokens(Object output) throws IOException { 266 return getSession().writeTokens(output); 267 } 268 269 @Override 270 protected final boolean isAddBeanTypes() { return addBeanTypesMsgPack || super.isAddBeanTypes(); } 271 272 @Override /* Overridden from OutputStreamSerializer */ 273 protected FluentMap<String,Object> properties() { 274 return super.properties() 275 .a("addBeanTypesMsgPack", addBeanTypesMsgPack); 276 } 277 278 /** 279 * Convenience delegator for the buffered array-element {@link RecordWriter} (uses default 280 * session args; see {@link #writeTokens(Object)}). Real impl on 281 * {@link MsgPackSerializerSession#writeArrayRecords(Object)}. 282 * 283 * @param output The output. 284 * @return A buffered {@link RecordWriter}. 285 * @throws IOException If a problem occurred opening the underlying output. 286 */ 287 @Override /* ArrayRecordWritable */ 288 public RecordWriter writeArrayRecords(Object output) throws IOException { 289 return getSession().writeArrayRecords(output); 290 } 291 292 @Override /* ArrayRecordWritable */ 293 public boolean isArrayRecordStreaming() { return false; } 294 295 /** 296 * Convenience delegator for the streaming, count-prefixed array-element {@link RecordWriter} 297 * (uses default session args; see {@link #writeTokens(Object)}). Real impl on 298 * {@link MsgPackSerializerSession#writeArrayRecords(Object, int)}. 299 * 300 * @param output The output (must be an {@link OutputStream}). 301 * @param expectedCount The number of elements that will be written. 302 * @return A streaming {@link RecordWriter}. 303 * @throws IOException If a problem occurred opening the underlying output. 304 */ 305 @Override /* ArrayRecordWritable */ 306 public RecordWriter writeArrayRecords(Object output, int expectedCount) throws IOException { 307 return getSession().writeArrayRecords(output, expectedCount); 308 } 309}