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