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.json;
014
015import static org.apache.juneau.internal.ClassUtils.*;
016
017import java.util.*;
018
019import org.apache.juneau.*;
020import org.apache.juneau.serializer.*;
021import org.apache.juneau.transform.*;
022
023/**
024 * Session object that lives for the duration of a single use of {@link JsonSchemaSerializer}.
025 * 
026 * <p>
027 * This class is NOT thread safe.
028 * It is typically discarded after one-time use although it can be reused within the same thread.
029 */
030public class JsonSchemaSerializerSession extends JsonSerializerSession {
031
032   /**
033    * Create a new session using properties specified in the context.
034    * 
035    * @param ctx
036    *    The context creating this session object.
037    *    The context contains all the configuration settings for this object.
038    * @param args
039    *    Runtime arguments.
040    *    These specify session-level information such as locale and URI context.
041    *    It also include session-level properties that override the properties defined on the bean and
042    *    serializer contexts.
043    */
044   protected JsonSchemaSerializerSession(JsonSerializer ctx, SerializerSessionArgs args) {
045      super(ctx, args);
046   }
047
048   @Override /* SerializerSession */
049   protected void doSerialize(SerializerPipe out, Object o) throws Exception {
050      ObjectMap schema = getSchema(getClassMetaForObject(o), "root", null);
051      serializeAnything(getJsonWriter(out), schema, getExpectedRootType(o), "root", null);
052   }
053
054   /*
055    * Creates a schema representation of the specified class type.
056    * 
057    * @param eType The class type to get the schema of.
058    * @param ctx Serialize context used to prevent infinite loops.
059    * @param attrName The name of the current attribute.
060    * @return A schema representation of the specified class.
061    * @throws SerializeException If a problem occurred trying to convert the output.
062    */
063   @SuppressWarnings({ "unchecked", "rawtypes" })
064   private ObjectMap getSchema(ClassMeta<?> eType, String attrName, String[] pNames) throws Exception {
065      ObjectMap out = new ObjectMap();
066
067      if (eType == null)
068         eType = object();
069
070      ClassMeta<?> aType;        // The actual type (will be null if recursion occurs)
071      ClassMeta<?> sType;        // The serialized type
072
073      aType = push(attrName, eType, null);
074
075      sType = eType.getSerializedClassMeta(this);
076      String type = null;
077
078      if (sType.isEnum() || sType.isCharSequence() || sType.isChar())
079         type = "string";
080      else if (sType.isNumber())
081         type = "number";
082      else if (sType.isBoolean())
083         type = "boolean";
084      else if (sType.isMapOrBean())
085         type = "object";
086      else if (sType.isCollectionOrArray())
087         type = "array";
088      else
089         type = "any";
090
091      out.put("type", type);
092      out.put("description", eType.toString());
093      PojoSwap f = eType.getPojoSwap(this);
094      if (f != null)
095         out.put("transform", f);
096
097      if (aType != null) {
098         if (sType.isEnum())
099            out.put("enum", getEnumStrings((Class<Enum<?>>)sType.getInnerClass()));
100         else if (sType.isCollectionOrArray()) {
101            ClassMeta componentType = sType.getElementType();
102            if (sType.isCollection() && isParentClass(Set.class, sType.getInnerClass()))
103               out.put("uniqueItems", true);
104            out.put("items", getSchema(componentType, "items", pNames));
105         } else if (sType.isBean()) {
106            ObjectMap properties = new ObjectMap();
107            BeanMeta bm = getBeanMeta(sType.getInnerClass());
108            if (pNames != null)
109               bm = new BeanMetaFiltered(bm, pNames);
110            for (Iterator<BeanPropertyMeta> i = bm.getPropertyMetas().iterator(); i.hasNext();) {
111               BeanPropertyMeta p = i.next();
112               if (p.canRead())
113                  properties.put(p.getName(), getSchema(p.getClassMeta(), p.getName(), p.getProperties()));
114            }
115            out.put("properties", properties);
116         }
117      }
118      pop();
119      return out;
120   }
121
122   @SuppressWarnings({ "unchecked", "rawtypes" })
123   private static List<String> getEnumStrings(Class<? extends Enum> c) {
124      List<String> l = new LinkedList<>();
125      for (Object e : EnumSet.allOf(c))
126         l.add(e.toString());
127      return l;
128   }
129}