Remove opendaylight directory
[netconf.git] / restconf / sal-rest-docgen / src / main / java / org / opendaylight / netconf / sal / rest / doc / impl / ModelGenerator.java
1 /*
2  * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.netconf.sal.rest.doc.impl;
9
10 import static org.opendaylight.netconf.sal.rest.doc.util.RestDocgenUtil.resolveNodesName;
11 import com.google.common.base.Preconditions;
12 import java.io.IOException;
13 import java.util.ArrayList;
14 import java.util.List;
15 import java.util.Set;
16 import javax.annotation.concurrent.NotThreadSafe;
17 import org.json.JSONArray;
18 import org.json.JSONException;
19 import org.json.JSONObject;
20 import org.opendaylight.netconf.sal.rest.doc.model.builder.OperationBuilder;
21 import org.opendaylight.netconf.sal.rest.doc.model.builder.OperationBuilder.Post;
22 import org.opendaylight.yangtools.yang.common.QName;
23 import org.opendaylight.yangtools.yang.model.api.AnyXmlSchemaNode;
24 import org.opendaylight.yangtools.yang.model.api.ChoiceCaseNode;
25 import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode;
26 import org.opendaylight.yangtools.yang.model.api.ConstraintDefinition;
27 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
28 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
29 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
30 import org.opendaylight.yangtools.yang.model.api.IdentitySchemaNode;
31 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
32 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
33 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
34 import org.opendaylight.yangtools.yang.model.api.Module;
35 import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
36 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
37 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
38 import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
39 import org.opendaylight.yangtools.yang.model.api.type.BinaryTypeDefinition;
40 import org.opendaylight.yangtools.yang.model.api.type.BitsTypeDefinition;
41 import org.opendaylight.yangtools.yang.model.api.type.BitsTypeDefinition.Bit;
42 import org.opendaylight.yangtools.yang.model.api.type.BooleanTypeDefinition;
43 import org.opendaylight.yangtools.yang.model.api.type.DecimalTypeDefinition;
44 import org.opendaylight.yangtools.yang.model.api.type.EnumTypeDefinition;
45 import org.opendaylight.yangtools.yang.model.api.type.EnumTypeDefinition.EnumPair;
46 import org.opendaylight.yangtools.yang.model.api.type.IdentityrefTypeDefinition;
47 import org.opendaylight.yangtools.yang.model.api.type.IntegerTypeDefinition;
48 import org.opendaylight.yangtools.yang.model.api.type.LengthConstraint;
49 import org.opendaylight.yangtools.yang.model.api.type.StringTypeDefinition;
50 import org.opendaylight.yangtools.yang.model.api.type.UnionTypeDefinition;
51 import org.opendaylight.yangtools.yang.model.api.type.UnsignedIntegerTypeDefinition;
52 import org.opendaylight.yangtools.yang.model.util.ExtendedType;
53 import org.slf4j.Logger;
54 import org.slf4j.LoggerFactory;
55
56 /**
57  * Generates JSON Schema for data defined in Yang
58  */
59 @NotThreadSafe
60 public class ModelGenerator {
61
62     private static final Logger LOG = LoggerFactory.getLogger(ModelGenerator.class);
63
64     private static final String BASE_64 = "base64";
65     private static final String BINARY_ENCODING_KEY = "binaryEncoding";
66     private static final String MEDIA_KEY = "media";
67     private static final String ONE_OF_KEY = "oneOf";
68     private static final String UNIQUE_ITEMS_KEY = "uniqueItems";
69     private static final String MAX_ITEMS = "maxItems";
70     private static final String MIN_ITEMS = "minItems";
71     private static final String SCHEMA_URL = "http://json-schema.org/draft-04/schema";
72     private static final String SCHEMA_KEY = "$schema";
73     private static final String MAX_LENGTH_KEY = "maxLength";
74     private static final String MIN_LENGTH_KEY = "minLength";
75     private static final String REQUIRED_KEY = "required";
76     private static final String REF_KEY = "$ref";
77     private static final String ITEMS_KEY = "items";
78     private static final String TYPE_KEY = "type";
79     private static final String PROPERTIES_KEY = "properties";
80     private static final String DESCRIPTION_KEY = "description";
81     private static final String OBJECT_TYPE = "object";
82     private static final String ARRAY_TYPE = "array";
83     private static final String ENUM = "enum";
84     private static final String INTEGER = "integer";
85     private static final String NUMBER = "number";
86     private static final String BOOLEAN = "boolean";
87     private static final String STRING = "string";
88     private static final String ID_KEY = "id";
89     private static final String SUB_TYPES_KEY = "subTypes";
90
91     private Module topLevelModule;
92
93     public ModelGenerator() {
94     }
95
96     private static String jsonTypeFor(final TypeDefinition<?> type) {
97         if (type instanceof BooleanTypeDefinition) {
98             return BOOLEAN;
99         } else if (type instanceof DecimalTypeDefinition) {
100             return NUMBER;
101         } else if (type instanceof EnumTypeDefinition) {
102             return ENUM;
103         } else if (type instanceof IntegerTypeDefinition) {
104             return INTEGER;
105         } else if (type instanceof UnsignedIntegerTypeDefinition) {
106             return INTEGER;
107         } else if (type instanceof StringTypeDefinition) {
108             return STRING;
109         }
110
111         // TODO: Binary type
112         return null;
113     }
114
115     public JSONObject convertToJsonSchema(final Module module, final SchemaContext schemaContext) throws IOException, JSONException {
116         JSONObject models = new JSONObject();
117         topLevelModule = module;
118         processModules(module, models);
119         processContainersAndLists(module, models, schemaContext);
120         processRPCs(module, models, schemaContext);
121         processIdentities(module, models);
122         return models;
123     }
124
125     private void processModules(final Module module, final JSONObject models) throws JSONException {
126         createConcreteModelForPost(models, module.getName()+ BaseYangSwaggerGenerator.MODULE_NAME_SUFFIX, createPropertiesForPost(module));
127     }
128
129     private void processContainersAndLists(final Module module, final JSONObject models, final SchemaContext schemaContext)
130             throws IOException, JSONException {
131
132         String moduleName = module.getName();
133
134         for (DataSchemaNode childNode : module.getChildNodes()) {
135             // For every container and list in the module
136             if (childNode instanceof ContainerSchemaNode || childNode instanceof ListSchemaNode) {
137                 processDataNodeContainer((DataNodeContainer) childNode, moduleName, models, true, schemaContext);
138                 processDataNodeContainer((DataNodeContainer) childNode, moduleName, models, false, schemaContext);
139             }
140         }
141
142     }
143
144     /**
145      * Process the RPCs for a Module Spits out a file each of the name <rpcName>-input.json and <rpcName>-output.json
146      * for each RPC that contains input & output elements
147      *
148      * @param module
149      * @throws JSONException
150      * @throws IOException
151      */
152     private void processRPCs(final Module module, final JSONObject models, final SchemaContext schemaContext) throws JSONException,
153             IOException {
154
155         Set<RpcDefinition> rpcs = module.getRpcs();
156         String moduleName = module.getName();
157         for (RpcDefinition rpc : rpcs) {
158
159             ContainerSchemaNode input = rpc.getInput();
160             if (input != null) {
161                 JSONObject inputJSON = processDataNodeContainer(input, moduleName, models, schemaContext);
162                 String filename = "(" + rpc.getQName().getLocalName() + ")input";
163                 inputJSON.put("id", filename);
164                 // writeToFile(filename, inputJSON.toString(2), moduleName);
165                 models.put(filename, inputJSON);
166             }
167
168             ContainerSchemaNode output = rpc.getOutput();
169             if (output != null) {
170                 JSONObject outputJSON = processDataNodeContainer(output, moduleName, models, schemaContext);
171                 String filename = "(" + rpc.getQName().getLocalName() + ")output";
172                 outputJSON.put("id", filename);
173                 models.put(filename, outputJSON);
174             }
175         }
176     }
177
178     /**
179      * Processes the 'identity' statement in a yang model and maps it to a 'model' in the Swagger JSON spec.
180      *
181      * @param module
182      *            The module from which the identity stmt will be processed
183      * @param models
184      *            The JSONObject in which the parsed identity will be put as a 'model' obj
185      * @throws JSONException
186      */
187     private static void processIdentities(final Module module, final JSONObject models) throws JSONException {
188
189         String moduleName = module.getName();
190         Set<IdentitySchemaNode> idNodes = module.getIdentities();
191         LOG.debug("Processing Identities for module {} . Found {} identity statements", moduleName, idNodes.size());
192
193         for (IdentitySchemaNode idNode : idNodes) {
194             JSONObject identityObj = new JSONObject();
195             String identityName = idNode.getQName().getLocalName();
196             LOG.debug("Processing Identity: {}", identityName);
197
198             identityObj.put(ID_KEY, identityName);
199             identityObj.put(DESCRIPTION_KEY, idNode.getDescription());
200
201             JSONObject props = new JSONObject();
202             IdentitySchemaNode baseId = idNode.getBaseIdentity();
203
204             if (baseId == null) {
205                 /**
206                  * This is a base identity. So lets see if it has sub types. If it does, then add them to the model
207                  * definition.
208                  */
209                 Set<IdentitySchemaNode> derivedIds = idNode.getDerivedIdentities();
210
211                 if (derivedIds != null) {
212                     JSONArray subTypes = new JSONArray();
213                     for (IdentitySchemaNode derivedId : derivedIds) {
214                         subTypes.put(derivedId.getQName().getLocalName());
215                     }
216                     identityObj.put(SUB_TYPES_KEY, subTypes);
217                 }
218             } else {
219                 /**
220                  * This is a derived entity. Add it's base type & move on.
221                  */
222                 props.put(TYPE_KEY, baseId.getQName().getLocalName());
223             }
224
225             // Add the properties. For a base type, this will be an empty object as required by the Swagger spec.
226             identityObj.put(PROPERTIES_KEY, props);
227             models.put(identityName, identityObj);
228         }
229     }
230
231     /**
232      * Processes the container and list nodes and populates the moduleJSON
233      *
234      * @param container
235      * @param moduleName
236      * @param isConfig
237      * @throws JSONException
238      * @throws IOException
239      */
240     private JSONObject processDataNodeContainer(final DataNodeContainer dataNode, final String moduleName, final JSONObject models,
241             final SchemaContext schemaContext) throws JSONException, IOException {
242         return processDataNodeContainer(dataNode, moduleName, models, true, schemaContext);
243     }
244
245     private JSONObject processDataNodeContainer(final DataNodeContainer dataNode, final String moduleName, final JSONObject models,
246             final boolean isConfig, final SchemaContext schemaContext) throws JSONException, IOException {
247         if (dataNode instanceof ListSchemaNode || dataNode instanceof ContainerSchemaNode) {
248             Preconditions.checkArgument(dataNode instanceof SchemaNode, "Data node should be also schema node");
249             Iterable<DataSchemaNode> containerChildren = dataNode.getChildNodes();
250             JSONObject properties = processChildren(containerChildren, ((SchemaNode) dataNode).getQName(), moduleName,
251                     models, isConfig, schemaContext);
252
253             String nodeName = (isConfig ? OperationBuilder.CONFIG : OperationBuilder.OPERATIONAL)
254                     + ((SchemaNode) dataNode).getQName().getLocalName();
255
256             JSONObject childSchema = getSchemaTemplate();
257             childSchema.put(TYPE_KEY, OBJECT_TYPE);
258             childSchema.put(PROPERTIES_KEY, properties);
259             childSchema.put("id", nodeName);
260             models.put(nodeName, childSchema);
261
262             if (isConfig) {
263                 createConcreteModelForPost(models, ((SchemaNode) dataNode).getQName().getLocalName(),
264                         createPropertiesForPost(dataNode));
265             }
266
267             JSONObject items = new JSONObject();
268             items.put(REF_KEY, nodeName);
269             JSONObject dataNodeProperties = new JSONObject();
270             dataNodeProperties.put(TYPE_KEY, dataNode instanceof ListSchemaNode ? ARRAY_TYPE : OBJECT_TYPE);
271             dataNodeProperties.put(ITEMS_KEY, items);
272
273             return dataNodeProperties;
274         }
275         return null;
276     }
277
278     private static void createConcreteModelForPost(final JSONObject models, final String localName,
279             final JSONObject properties) throws JSONException {
280         String nodePostName = OperationBuilder.CONFIG + localName + Post.METHOD_NAME;
281         JSONObject postSchema = getSchemaTemplate();
282         postSchema.put(TYPE_KEY, OBJECT_TYPE);
283         postSchema.put("id", nodePostName);
284         postSchema.put(PROPERTIES_KEY, properties);
285         models.put(nodePostName, postSchema);
286     }
287
288     private JSONObject createPropertiesForPost(final DataNodeContainer dataNodeContainer) throws JSONException {
289         JSONObject properties = new JSONObject();
290         for (DataSchemaNode childNode : dataNodeContainer.getChildNodes()) {
291             if (childNode instanceof ListSchemaNode || childNode instanceof ContainerSchemaNode) {
292                 JSONObject items = new JSONObject();
293                 items.put(REF_KEY, "(config)" + childNode.getQName().getLocalName());
294                 JSONObject property = new JSONObject();
295                 property.put(TYPE_KEY, childNode instanceof ListSchemaNode ? ARRAY_TYPE : OBJECT_TYPE);
296                 property.put(ITEMS_KEY, items);
297                 properties.put(childNode.getQName().getLocalName(), property);
298             } else if (childNode instanceof LeafSchemaNode){
299                 JSONObject property = processLeafNode((LeafSchemaNode)childNode);
300                 properties.put(childNode.getQName().getLocalName(), property);
301             }
302         }
303         return properties;
304     }
305
306     private JSONObject processChildren(final Iterable<DataSchemaNode> nodes, final QName parentQName, final String moduleName,
307             final JSONObject models, final SchemaContext schemaContext) throws JSONException, IOException {
308         return processChildren(nodes, parentQName, moduleName, models, true, schemaContext);
309     }
310
311     /**
312      * Processes the nodes
313      *
314      * @param nodes
315      * @param parentQName
316      * @param moduleName
317      * @param isConfig
318      * @return
319      * @throws JSONException
320      * @throws IOException
321      */
322     private JSONObject processChildren(final Iterable<DataSchemaNode> nodes, final QName parentQName, final String moduleName,
323             final JSONObject models, final boolean isConfig, final SchemaContext schemaContext) throws JSONException, IOException {
324
325         JSONObject properties = new JSONObject();
326
327         for (DataSchemaNode node : nodes) {
328             if (node.isConfiguration() == isConfig) {
329
330                 String name = resolveNodesName(node, topLevelModule, schemaContext);
331                 JSONObject property = null;
332                 if (node instanceof LeafSchemaNode) {
333                     property = processLeafNode((LeafSchemaNode) node);
334                 } else if (node instanceof ListSchemaNode) {
335                     property = processDataNodeContainer((ListSchemaNode) node, moduleName, models, isConfig,
336                             schemaContext);
337
338                 } else if (node instanceof LeafListSchemaNode) {
339                     property = processLeafListNode((LeafListSchemaNode) node);
340
341                 } else if (node instanceof ChoiceSchemaNode) {
342                     property = processChoiceNode((ChoiceSchemaNode) node, moduleName, models, schemaContext);
343
344                 } else if (node instanceof AnyXmlSchemaNode) {
345                     property = processAnyXMLNode((AnyXmlSchemaNode) node);
346
347                 } else if (node instanceof ContainerSchemaNode) {
348                     property = processDataNodeContainer((ContainerSchemaNode) node, moduleName, models, isConfig,
349                             schemaContext);
350
351                 } else {
352                     throw new IllegalArgumentException("Unknown DataSchemaNode type: " + node.getClass());
353                 }
354
355                 property.putOpt(DESCRIPTION_KEY, node.getDescription());
356                 properties.put(name, property);
357             }
358         }
359         return properties;
360     }
361
362     /**
363      *
364      * @param listNode
365      * @throws JSONException
366      */
367     private JSONObject processLeafListNode(final LeafListSchemaNode listNode) throws JSONException {
368         JSONObject props = new JSONObject();
369         props.put(TYPE_KEY, ARRAY_TYPE);
370
371         JSONObject itemsVal = new JSONObject();
372         processTypeDef(listNode.getType(), itemsVal);
373         props.put(ITEMS_KEY, itemsVal);
374
375         ConstraintDefinition constraints = listNode.getConstraints();
376         processConstraints(constraints, props);
377
378         return props;
379     }
380
381     /**
382      *
383      * @param choiceNode
384      * @param moduleName
385      * @throws JSONException
386      * @throws IOException
387      */
388     private JSONObject processChoiceNode(final ChoiceSchemaNode choiceNode, final String moduleName, final JSONObject models,
389             final SchemaContext schemaContext) throws JSONException, IOException {
390
391         Set<ChoiceCaseNode> cases = choiceNode.getCases();
392
393         JSONArray choiceProps = new JSONArray();
394         for (ChoiceCaseNode choiceCase : cases) {
395             String choiceName = choiceCase.getQName().getLocalName();
396             JSONObject choiceProp = processChildren(choiceCase.getChildNodes(), choiceCase.getQName(), moduleName,
397                     models, schemaContext);
398             JSONObject choiceObj = new JSONObject();
399             choiceObj.put(choiceName, choiceProp);
400             choiceObj.put(TYPE_KEY, OBJECT_TYPE);
401             choiceProps.put(choiceObj);
402         }
403
404         JSONObject oneOfProps = new JSONObject();
405         oneOfProps.put(ONE_OF_KEY, choiceProps);
406         oneOfProps.put(TYPE_KEY, OBJECT_TYPE);
407
408         return oneOfProps;
409     }
410
411     /**
412      *
413      * @param constraints
414      * @param props
415      * @throws JSONException
416      */
417     private static void processConstraints(final ConstraintDefinition constraints, final JSONObject props) throws JSONException {
418         boolean isMandatory = constraints.isMandatory();
419         props.put(REQUIRED_KEY, isMandatory);
420
421         Integer minElements = constraints.getMinElements();
422         Integer maxElements = constraints.getMaxElements();
423         if (minElements != null) {
424             props.put(MIN_ITEMS, minElements);
425         }
426         if (maxElements != null) {
427             props.put(MAX_ITEMS, maxElements);
428         }
429     }
430
431     /**
432      *
433      * @param leafNode
434      * @return
435      * @throws JSONException
436      */
437     private JSONObject processLeafNode(final LeafSchemaNode leafNode) throws JSONException {
438         JSONObject property = new JSONObject();
439
440         String leafDescription = leafNode.getDescription();
441         property.put(DESCRIPTION_KEY, leafDescription);
442
443         processConstraints(leafNode.getConstraints(), property);
444         processTypeDef(leafNode.getType(), property);
445
446         return property;
447     }
448
449     /**
450      *
451      * @param leafNode
452      * @return
453      * @throws JSONException
454      */
455     private static JSONObject processAnyXMLNode(final AnyXmlSchemaNode leafNode) throws JSONException {
456         JSONObject property = new JSONObject();
457
458         String leafDescription = leafNode.getDescription();
459         property.put(DESCRIPTION_KEY, leafDescription);
460
461         processConstraints(leafNode.getConstraints(), property);
462
463         return property;
464     }
465
466     /**
467      * @param property
468      * @throws JSONException
469      */
470     private void processTypeDef(final TypeDefinition<?> leafTypeDef, final JSONObject property) throws JSONException {
471
472         if (leafTypeDef instanceof ExtendedType) {
473             processExtendedType(leafTypeDef, property);
474         } else if (leafTypeDef instanceof BinaryTypeDefinition) {
475             processBinaryType((BinaryTypeDefinition) leafTypeDef, property);
476         } else if (leafTypeDef instanceof BitsTypeDefinition) {
477             processBitsType((BitsTypeDefinition) leafTypeDef, property);
478         } else if (leafTypeDef instanceof EnumTypeDefinition) {
479             processEnumType((EnumTypeDefinition) leafTypeDef, property);
480         } else if (leafTypeDef instanceof IdentityrefTypeDefinition) {
481             property.putOpt(TYPE_KEY, ((IdentityrefTypeDefinition) leafTypeDef).getIdentity().getQName().getLocalName());
482         } else if (leafTypeDef instanceof StringTypeDefinition) {
483             processStringType((StringTypeDefinition) leafTypeDef, property);
484         } else if (leafTypeDef instanceof UnionTypeDefinition) {
485             processUnionType((UnionTypeDefinition) leafTypeDef, property);
486         } else {
487             String jsonType = jsonTypeFor(leafTypeDef);
488             if (jsonType == null) {
489                 jsonType = "object";
490             }
491             property.putOpt(TYPE_KEY, jsonType);
492         }
493     }
494
495     /**
496      *
497      * @param leafTypeDef
498      * @param property
499      * @throws JSONException
500      */
501     private void processExtendedType(final TypeDefinition<?> leafTypeDef, final JSONObject property) throws JSONException {
502         TypeDefinition<?> leafBaseType = leafTypeDef.getBaseType();
503         if (leafBaseType instanceof ExtendedType) {
504             // recursively process an extended type until we hit a base type
505             processExtendedType(leafBaseType, property);
506         } else {
507             List<LengthConstraint> lengthConstraints = ((ExtendedType) leafTypeDef).getLengthConstraints();
508             for (LengthConstraint lengthConstraint : lengthConstraints) {
509                 Number min = lengthConstraint.getMin();
510                 Number max = lengthConstraint.getMax();
511                 property.putOpt(MIN_LENGTH_KEY, min);
512                 property.putOpt(MAX_LENGTH_KEY, max);
513             }
514             String jsonType = jsonTypeFor(leafBaseType);
515             property.putOpt(TYPE_KEY, jsonType);
516         }
517
518     }
519
520     private static void processBinaryType(final BinaryTypeDefinition binaryType, final JSONObject property) throws JSONException {
521         property.put(TYPE_KEY, STRING);
522         JSONObject media = new JSONObject();
523         media.put(BINARY_ENCODING_KEY, BASE_64);
524         property.put(MEDIA_KEY, media);
525     }
526
527     /**
528      *
529      * @param enumLeafType
530      * @param property
531      * @throws JSONException
532      */
533     private static void processEnumType(final EnumTypeDefinition enumLeafType, final JSONObject property) throws JSONException {
534         List<EnumPair> enumPairs = enumLeafType.getValues();
535         List<String> enumNames = new ArrayList<String>();
536         for (EnumPair enumPair : enumPairs) {
537             enumNames.add(enumPair.getName());
538         }
539         property.putOpt(ENUM, new JSONArray(enumNames));
540     }
541
542     /**
543      *
544      * @param bitsType
545      * @param property
546      * @throws JSONException
547      */
548     private static void processBitsType(final BitsTypeDefinition bitsType, final JSONObject property) throws JSONException {
549         property.put(TYPE_KEY, ARRAY_TYPE);
550         property.put(MIN_ITEMS, 0);
551         property.put(UNIQUE_ITEMS_KEY, true);
552         JSONArray enumValues = new JSONArray();
553
554         List<Bit> bits = bitsType.getBits();
555         for (Bit bit : bits) {
556             enumValues.put(bit.getName());
557         }
558         JSONObject itemsValue = new JSONObject();
559         itemsValue.put(ENUM, enumValues);
560         property.put(ITEMS_KEY, itemsValue);
561     }
562
563     private static void processStringType(final StringTypeDefinition stringType, final JSONObject property) throws JSONException {
564         StringTypeDefinition type = stringType;
565         List<LengthConstraint> lengthConstraints = stringType.getLengthConstraints();
566         while (lengthConstraints.isEmpty() && type.getBaseType() != null) {
567            type = type.getBaseType();
568            lengthConstraints = type.getLengthConstraints();
569         }
570
571         // FIXME: json-schema is not expressive enough to capture min/max laternatives. We should find the true minimum
572         //        and true maximum implied by the constraints and use that.
573         for (LengthConstraint lengthConstraint : lengthConstraints) {
574             Number min = lengthConstraint.getMin();
575             Number max = lengthConstraint.getMax();
576             property.putOpt(MIN_LENGTH_KEY, min);
577             property.putOpt(MAX_LENGTH_KEY, max);
578         }
579
580         property.put(TYPE_KEY, STRING);
581     }
582
583     /**
584      *
585      * @param unionType
586      * @param property
587      * @throws JSONException
588      */
589     private static void processUnionType(final UnionTypeDefinition unionType, final JSONObject property) throws JSONException {
590
591         StringBuilder type = new StringBuilder();
592         for (TypeDefinition<?> typeDef : unionType.getTypes()) {
593             if (type.length() > 0) {
594                 type.append(" or ");
595             }
596             type.append(jsonTypeFor(typeDef));
597         }
598
599         property.put(TYPE_KEY, type);
600     }
601
602     /**
603      * Helper method to generate a pre-filled JSON schema object.
604      *
605      * @return
606      * @throws JSONException
607      */
608     private static JSONObject getSchemaTemplate() throws JSONException {
609         JSONObject schemaJSON = new JSONObject();
610         schemaJSON.put(SCHEMA_KEY, SCHEMA_URL);
611
612         return schemaJSON;
613     }
614
615 }