001// *************************************************************************************************************************** 002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file * 003// * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file * 004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance * 005// * with the License. You may obtain a copy of the License at * 006// * * 007// * http://www.apache.org/licenses/LICENSE-2.0 * 008// * * 009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an * 010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * 011// * specific language governing permissions and limitations under the License. * 012// *************************************************************************************************************************** 013package org.apache.juneau.oapi; 014 015import static org.apache.juneau.httppart.HttpPartSchema.CollectionFormat.*; 016import static org.apache.juneau.httppart.HttpPartSchema.Format.*; 017import static org.apache.juneau.httppart.HttpPartSchema.Type.*; 018import static org.apache.juneau.internal.StringUtils.*; 019 020import java.io.IOException; 021import java.lang.reflect.*; 022import java.util.*; 023 024import org.apache.juneau.*; 025import org.apache.juneau.httppart.*; 026import org.apache.juneau.internal.*; 027import org.apache.juneau.serializer.*; 028import org.apache.juneau.uon.*; 029 030/** 031 * Session object that lives for the duration of a single use of {@link OpenApiSerializer}. 032 * 033 * <p> 034 * This class is NOT thread safe. 035 * It is typically discarded after one-time use although it can be reused within the same thread. 036 */ 037public class OpenApiSerializerSession extends UonSerializerSession { 038 039 //------------------------------------------------------------------------------------------------------------------- 040 // Predefined instances 041 //------------------------------------------------------------------------------------------------------------------- 042 043 // Cache these for faster lookup 044 private static final BeanContext BC = BeanContext.DEFAULT; 045 private static final ClassMeta<byte[]> CM_ByteArray = BC.getClassMeta(byte[].class); 046 private static final ClassMeta<String[]> CM_StringArray = BC.getClassMeta(String[].class); 047 private static final ClassMeta<Calendar> CM_Calendar = BC.getClassMeta(Calendar.class); 048 private static final ClassMeta<Long> CM_Long = BC.getClassMeta(Long.class); 049 private static final ClassMeta<Integer> CM_Integer = BC.getClassMeta(Integer.class); 050 private static final ClassMeta<Double> CM_Double = BC.getClassMeta(Double.class); 051 private static final ClassMeta<Float> CM_Float = BC.getClassMeta(Float.class); 052 private static final ClassMeta<Boolean> CM_Boolean = BC.getClassMeta(Boolean.class); 053 054 private static final HttpPartSchema DEFAULT_SCHEMA = HttpPartSchema.DEFAULT; 055 056 //------------------------------------------------------------------------------------------------------------------- 057 // Instance 058 //------------------------------------------------------------------------------------------------------------------- 059 060 private final OpenApiSerializer ctx; 061 062 /** 063 * Create a new session using properties specified in the context. 064 * 065 * @param ctx 066 * The context creating this session object. 067 * The context contains all the configuration settings for this object. 068 * @param args 069 * Runtime session arguments. 070 */ 071 protected OpenApiSerializerSession(OpenApiSerializer ctx, SerializerSessionArgs args) { 072 super(ctx, false, args); 073 this.ctx = ctx; 074 } 075 076 //----------------------------------------------------------------------------------------------------------------- 077 // Entry point methods 078 //----------------------------------------------------------------------------------------------------------------- 079 080 @Override /* Serializer */ 081 protected void doSerialize(SerializerPipe out, Object o) throws IOException, SerializeException { 082 try { 083 out.getWriter().write(serialize(HttpPartType.BODY, getSchema(), o)); 084 } catch (SchemaValidationException e) { 085 throw new SerializeException(e); 086 } 087 } 088 089 @Override /* PartSerializer */ 090 public String serialize(HttpPartSchema schema, Object value) throws SerializeException, SchemaValidationException { 091 return serialize(null, schema, value); 092 } 093 094 @Override /* PartSerializer */ 095 public String serialize(HttpPartType partType, HttpPartSchema schema, Object value) throws SerializeException, SchemaValidationException { 096 097 schema = ObjectUtils.firstNonNull(schema, DEFAULT_SCHEMA); 098 ClassMeta<?> type = getClassMetaForObject(value); 099 if (type == null) 100 type = object(); 101 HttpPartSchema.Type t = schema.getType(type); 102 HttpPartSchema.Format f = schema.getFormat(type); 103 HttpPartSchema.CollectionFormat cf = schema.getCollectionFormat(); 104 105 String out = null; 106 107 schema.validateOutput(value, ctx); 108 109 if (type.hasMutaterTo(schema.getParsedType()) || schema.getParsedType().hasMutaterFrom(type)) { 110 value = toType(value, schema.getParsedType()); 111 type = schema.getParsedType(); 112 } 113 114 if (type.isUri()) { 115 value = getUriResolver().resolve(value); 116 type = string(); 117 } 118 119 if (value != null) { 120 121 if (t == STRING) { 122 123 if (f == BYTE) 124 out = base64Encode(toType(value, CM_ByteArray)); 125 else if (f == BINARY) 126 out = toHex(toType(value, CM_ByteArray)); 127 else if (f == BINARY_SPACED) 128 out = toSpacedHex(toType(value, CM_ByteArray)); 129 else if (f == DATE) 130 out = toIsoDate(toType(value, CM_Calendar)); 131 else if (f == DATE_TIME) 132 out = toIsoDateTime(toType(value, CM_Calendar)); 133 else if (f == HttpPartSchema.Format.UON) 134 out = super.serialize(partType, schema, value); 135 else 136 out = toType(value, string()); 137 138 } else if (t == ARRAY) { 139 140 if (cf == HttpPartSchema.CollectionFormat.UON) 141 out = super.serialize(partType, null, toList(partType, type, value, schema)); 142 else { 143 List<String> l = new ArrayList<>(); 144 145 HttpPartSchema items = schema.getItems(); 146 ClassMeta<?> vt = getClassMetaForObject(value); 147 148 if (type.isArray()) { 149 for (int i = 0; i < Array.getLength(value); i++) 150 l.add(serialize(partType, items, Array.get(value, i))); 151 } else if (type.isCollection()) { 152 for (Object o : (Collection<?>)value) 153 l.add(serialize(partType, items, o)); 154 } else if (vt.hasMutaterTo(String[].class)) { 155 String[] ss = toType(value, CM_StringArray); 156 for (int i = 0; i < ss.length; i++) 157 l.add(serialize(partType, items, ss[i])); 158 } 159 160 if (cf == PIPES) 161 out = joine(l, '|'); 162 else if (cf == SSV) 163 out = join(l, ' '); 164 else if (cf == TSV) 165 out = join(l, '\t'); 166 else 167 out = joine(l, ','); 168 } 169 170 } else if (t == BOOLEAN) { 171 172 if (f == HttpPartSchema.Format.UON) 173 out = super.serialize(partType, null, value); 174 else 175 out = stringify(toType(value, CM_Boolean)); 176 177 } else if (t == INTEGER) { 178 179 if (f == HttpPartSchema.Format.UON) 180 out = super.serialize(partType, null, value); 181 else if (f == INT64) 182 out = stringify(toType(value, CM_Long)); 183 else 184 out = stringify(toType(value, CM_Integer)); 185 186 } else if (t == NUMBER) { 187 188 if (f == HttpPartSchema.Format.UON) 189 out = super.serialize(partType, null, value); 190 else if (f == DOUBLE) 191 out = stringify(toType(value, CM_Double)); 192 else 193 out = stringify(toType(value, CM_Float)); 194 195 } else if (t == OBJECT) { 196 197 if (f == HttpPartSchema.Format.UON) { 198 out = super.serialize(partType, null, value); 199 } else if (schema.hasProperties() && type.isMapOrBean()) { 200 out = super.serialize(partType, null, toMap(partType, type, value, schema)); 201 } else { 202 out = super.serialize(partType, null, value); 203 } 204 205 } else if (t == FILE) { 206 throw new SerializeException("File part not supported."); 207 208 } else if (t == NO_TYPE) { 209 // This should never be returned by HttpPartSchema.getType(ClassMeta). 210 throw new SerializeException("Invalid type."); 211 } 212 } 213 214 schema.validateInput(out); 215 if (out == null) 216 out = schema.getDefault(); 217 if (out == null) 218 out = "null"; 219 return out; 220 } 221 222 @SuppressWarnings({ "rawtypes" }) 223 private Map toMap(HttpPartType partType, ClassMeta<?> type, Object o, HttpPartSchema s) throws SerializeException, SchemaValidationException { 224 if (s == null) 225 s = DEFAULT_SCHEMA; 226 ObjectMap m = new ObjectMap(); 227 if (type.isBean()) { 228 for (BeanPropertyValue p : toBeanMap(o).getValues(isTrimNullProperties())) { 229 if (p.getMeta().canRead()) { 230 Throwable t = p.getThrown(); 231 if (t == null) 232 m.put(p.getName(), toObject(partType, p.getValue(), s.getProperty(p.getName()))); 233 } 234 } 235 } else { 236 for (Map.Entry e : (Set<Map.Entry>)((Map)o).entrySet()) 237 m.put(stringify(e.getKey()), toObject(partType, e.getValue(), s.getProperty(stringify(e.getKey())))); 238 } 239 if (isSortMaps()) 240 return sort(m); 241 return m; 242 } 243 244 @SuppressWarnings("rawtypes") 245 private Collection toList(HttpPartType partType, ClassMeta<?> type, Object o, HttpPartSchema s) throws SerializeException, SchemaValidationException { 246 if (s == null) 247 s = DEFAULT_SCHEMA; 248 ObjectList l = new ObjectList(); 249 HttpPartSchema items = s.getItems(); 250 if (type.isArray()) { 251 for (int i = 0; i < Array.getLength(o); i++) 252 l.add(toObject(partType, Array.get(o, i), items)); 253 } else if (type.isCollection()) { 254 for (Object o2 : (Collection<?>)o) 255 l.add(toObject(partType, o2, items)); 256 } else { 257 l.add(toObject(partType, o, items)); 258 } 259 if (isSortCollections()) 260 return sort(l); 261 return l; 262 } 263 264 @SuppressWarnings("rawtypes") 265 private Object toObject(HttpPartType partType, Object o, HttpPartSchema s) throws SerializeException, SchemaValidationException { 266 if (o == null) 267 return null; 268 if (s == null) 269 s = DEFAULT_SCHEMA; 270 ClassMeta cm = getClassMetaForObject(o); 271 HttpPartSchema.Type t = s.getType(cm); 272 HttpPartSchema.Format f = s.getFormat(cm); 273 HttpPartSchema.CollectionFormat cf = s.getCollectionFormat(); 274 275 if (t == STRING) { 276 if (f == BYTE) 277 return base64Encode(toType(o, CM_ByteArray)); 278 if (f == BINARY) 279 return toHex(toType(o, CM_ByteArray)); 280 if (f == BINARY_SPACED) 281 return toSpacedHex(toType(o, CM_ByteArray)); 282 if (f == DATE) 283 return toIsoDate(toType(o, CM_Calendar)); 284 if (f == DATE_TIME) 285 return toIsoDateTime(toType(o, CM_Calendar)); 286 return o; 287 } else if (t == ARRAY) { 288 Collection l = toList(partType, getClassMetaForObject(o), o, s); 289 if (cf == CSV) 290 return joine(l, ','); 291 if (cf == PIPES) 292 return joine(l, '|'); 293 if (cf == SSV) 294 return join(l, ' '); 295 if (cf == TSV) 296 return join(l, '\t'); 297 return l; 298 } else if (t == OBJECT) { 299 return toMap(partType, getClassMetaForObject(o), o, s); 300 } 301 302 return o; 303 } 304 305 private <T> T toType(Object in, ClassMeta<T> type) throws SerializeException { 306 try { 307 return convertToType(in, type); 308 } catch (InvalidDataConversionException e) { 309 throw new SerializeException(e.getMessage()); 310 } 311 } 312 313 //----------------------------------------------------------------------------------------------------------------- 314 // Other methods 315 //----------------------------------------------------------------------------------------------------------------- 316 317 @Override /* Session */ 318 public ObjectMap toMap() { 319 return super.toMap() 320 .append("OpenApiSerializerSession", new DefaultFilteringObjectMap() 321 ); 322 } 323}