View Javadoc
1   /*
2    * The contents of this file are subject to the terms of the Common Development and
3    * Distribution License (the License). You may not use this file except in compliance with the
4    * License.
5    *
6    * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
7    * specific language governing permission and limitations under the License.
8    *
9    * When distributing Covered Software, include this CDDL Header Notice in each file and include
10   * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
11   * Header, with the fields enclosed by brackets [] replaced by your own identifying
12   * information: "Portions copyright [year] [name of copyright owner]".
13   *
14   * Copyright 2016 ForgeRock AS.
15   */
16  
17  package org.forgerock.api.jackson;
18  
19  import static org.forgerock.api.jackson.JacksonUtils.OBJECT_MAPPER;
20  
21  import java.io.IOException;
22  import java.util.ArrayList;
23  import java.util.HashMap;
24  import java.util.Iterator;
25  import java.util.List;
26  
27  import javax.validation.ValidationException;
28  
29  import org.forgerock.api.enums.ReadPolicy;
30  import org.forgerock.json.JsonValue;
31  
32  import com.fasterxml.jackson.databind.JavaType;
33  import com.fasterxml.jackson.module.jsonSchema.JsonSchema;
34  import com.fasterxml.jackson.module.jsonSchema.types.ArraySchema;
35  import org.forgerock.api.enums.WritePolicy;
36  
37  /**
38   * An extension to the Jackson {@code ArraySchema} that includes the custom CREST JSON Schema attributes.
39   */
40  public class CrestArraySchema extends ArraySchema implements CrestReadWritePoliciesSchema, OrderedFieldSchema,
41          ValidatableSchema, WithExampleSchema<List<Object>> {
42      private static final JavaType EXAMPLE_VALUE_TYPE = OBJECT_MAPPER.getTypeFactory()
43              .constructParametrizedType(ArrayList.class, List.class, Object.class);
44  
45      private WritePolicy writePolicy;
46      private ReadPolicy readPolicy;
47      private Boolean errorOnWritePolicyFailure;
48      private Boolean returnOnDemand;
49      private Integer propertyOrder;
50      private List<Object> example;
51  
52      @Override
53      public WritePolicy getWritePolicy() {
54          return writePolicy;
55      }
56  
57      @Override
58      public void setWritePolicy(WritePolicy policy) {
59          this.writePolicy = policy;
60      }
61  
62      @Override
63      public ReadPolicy getReadPolicy() {
64          return readPolicy;
65      }
66  
67      @Override
68      public void setReadPolicy(ReadPolicy readPolicy) {
69          this.readPolicy = readPolicy;
70      }
71  
72      @Override
73      public Boolean getErrorOnWritePolicyFailure() {
74          return errorOnWritePolicyFailure;
75      }
76  
77      @Override
78      public void setErrorOnWritePolicyFailure(Boolean errorOnWritePolicyFailure) {
79          this.errorOnWritePolicyFailure = errorOnWritePolicyFailure;
80      }
81  
82      @Override
83      public Boolean getReturnOnDemand() {
84          return returnOnDemand;
85      }
86  
87      @Override
88      public void setReturnOnDemand(Boolean returnOnDemand) {
89          this.returnOnDemand = returnOnDemand;
90      }
91  
92      @Override
93      public Integer getPropertyOrder() {
94          return propertyOrder;
95      }
96  
97      @Override
98      public void setPropertyOrder(Integer order) {
99          this.propertyOrder = order;
100     }
101 
102     @Override
103     public void validate(JsonValue object) throws ValidationException {
104         if (!object.isCollection()) {
105             throw new ValidationException("Array expected, but got: " + object.getObject());
106         }
107         if (maxItems != null && object.size() > maxItems) {
108             throw new ValidationException("Array has too many items. Maximum permitted: " + maxItems);
109         }
110         if (minItems != null && object.size() < minItems) {
111             throw new ValidationException("Array has too few items. Minimum permitted: " + minItems);
112         }
113         if (items.isSingleItems()) {
114             if (items.asSingleItems().getSchema() instanceof ValidatableSchema) {
115                 ValidatableSchema itemSchema = (ValidatableSchema) items.asSingleItems().getSchema();
116                 for (JsonValue item : object) {
117                     itemSchema.validate(item);
118                 }
119             }
120         } else {
121             Iterator<JsonValue> arrayItems = object.iterator();
122             for (JsonSchema itemSchema : items.asArrayItems().getJsonSchemas()) {
123                 if (!arrayItems.hasNext()) {
124                     throw new ValidationException("Not enough items. Expecting " + itemSchema);
125                 }
126                 if (itemSchema instanceof ValidatableSchema) {
127                     ((ValidatableSchema) itemSchema).validate(arrayItems.next());
128                 }
129             }
130         }
131     }
132 
133     @Override
134     public List<Object> getExample() {
135         List<Object> example = this.example;
136         if (example == null) {
137             example = new ArrayList<>();
138             boolean foundExample = items.isSingleItems()
139                     ? applySingleSchemaExample(example)
140                     : applyMultipleSchemasExamples(example);
141             if (!foundExample) {
142                 example = null;
143             }
144         }
145         return example;
146     }
147 
148     private boolean applySingleSchemaExample(List<Object> example) {
149         boolean foundExample = false;
150         if (items.asSingleItems().getSchema() instanceof WithExampleSchema) {
151             Object itemsExample = ((WithExampleSchema) items.asSingleItems().getSchema()).getExample();
152             if (itemsExample != null) {
153                 int count = minItems != null && minItems > 1 ? minItems : 1;
154                 foundExample = true;
155                 for (int i = 0; i < count; i++) {
156                     example.add(itemsExample);
157                 }
158             }
159         }
160         return foundExample;
161     }
162 
163     private boolean applyMultipleSchemasExamples(List<Object> example) {
164         boolean foundExample = false;
165         for (JsonSchema schema : items.asArrayItems().getJsonSchemas()) {
166             if (schema instanceof WithExampleSchema) {
167                 Object propertyExample = ((WithExampleSchema) schema).getExample();
168                 if (propertyExample != null) {
169                     foundExample = true;
170                     example.add(propertyExample);
171                 } else {
172                     example.add(new HashMap<>());
173                 }
174             }
175         }
176         return foundExample;
177     }
178 
179     @Override
180     public void setExample(String example) throws IOException {
181         this.example = OBJECT_MAPPER.readValue(example, EXAMPLE_VALUE_TYPE);
182     }
183 }