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  package org.forgerock.api.jackson;
17  
18  import static org.forgerock.api.jackson.JacksonUtils.OBJECT_MAPPER;
19  
20  import com.fasterxml.jackson.databind.JavaType;
21  import com.fasterxml.jackson.module.jsonSchema.jakarta.JsonSchema;
22  import com.fasterxml.jackson.module.jsonSchema.jakarta.types.ArraySchema;
23  import jakarta.validation.ValidationException;
24  import java.io.IOException;
25  import java.util.ArrayList;
26  import java.util.HashMap;
27  import java.util.Iterator;
28  import java.util.List;
29  import org.forgerock.api.enums.ReadPolicy;
30  import org.forgerock.api.enums.WritePolicy;
31  import org.forgerock.json.JsonValue;
32  
33  /**
34   * An extension to the Jackson {@code ArraySchema} that includes the custom CREST JSON Schema attributes.
35   */
36  public class CrestArraySchema extends ArraySchema implements CrestReadWritePoliciesSchema, OrderedFieldSchema,
37          ValidatableSchema, WithExampleSchema<List<Object>> {
38      private static final JavaType EXAMPLE_VALUE_TYPE = OBJECT_MAPPER.getTypeFactory()
39              .constructParametrizedType(ArrayList.class, List.class, Object.class);
40  
41      private WritePolicy writePolicy;
42      private ReadPolicy readPolicy;
43      private Boolean errorOnWritePolicyFailure;
44      private Boolean returnOnDemand;
45      private Integer propertyOrder;
46      private List<Object> example;
47  
48      @Override
49      public WritePolicy getWritePolicy() {
50          return writePolicy;
51      }
52  
53      @Override
54      public void setWritePolicy(WritePolicy policy) {
55          this.writePolicy = policy;
56      }
57  
58      @Override
59      public ReadPolicy getReadPolicy() {
60          return readPolicy;
61      }
62  
63      @Override
64      public void setReadPolicy(ReadPolicy readPolicy) {
65          this.readPolicy = readPolicy;
66      }
67  
68      @Override
69      public Boolean getErrorOnWritePolicyFailure() {
70          return errorOnWritePolicyFailure;
71      }
72  
73      @Override
74      public void setErrorOnWritePolicyFailure(Boolean errorOnWritePolicyFailure) {
75          this.errorOnWritePolicyFailure = errorOnWritePolicyFailure;
76      }
77  
78      @Override
79      public Boolean getReturnOnDemand() {
80          return returnOnDemand;
81      }
82  
83      @Override
84      public void setReturnOnDemand(Boolean returnOnDemand) {
85          this.returnOnDemand = returnOnDemand;
86      }
87  
88      @Override
89      public Integer getPropertyOrder() {
90          return propertyOrder;
91      }
92  
93      @Override
94      public void setPropertyOrder(Integer order) {
95          this.propertyOrder = order;
96      }
97  
98      @Override
99      public void validate(JsonValue object) throws ValidationException {
100         if (!object.isCollection()) {
101             throw new ValidationException("Array expected, but got: " + object.getObject());
102         }
103         if (maxItems != null && object.size() > maxItems) {
104             throw new ValidationException("Array has too many items. Maximum permitted: " + maxItems);
105         }
106         if (minItems != null && object.size() < minItems) {
107             throw new ValidationException("Array has too few items. Minimum permitted: " + minItems);
108         }
109         if (items.isSingleItems()) {
110             if (items.asSingleItems().getSchema() instanceof ValidatableSchema) {
111                 ValidatableSchema itemSchema = (ValidatableSchema) items.asSingleItems().getSchema();
112                 for (JsonValue item : object) {
113                     itemSchema.validate(item);
114                 }
115             }
116         } else {
117             Iterator<JsonValue> arrayItems = object.iterator();
118             for (JsonSchema itemSchema : items.asArrayItems().getJsonSchemas()) {
119                 if (!arrayItems.hasNext()) {
120                     throw new ValidationException("Not enough items. Expecting " + itemSchema);
121                 }
122                 if (itemSchema instanceof ValidatableSchema) {
123                     ((ValidatableSchema) itemSchema).validate(arrayItems.next());
124                 }
125             }
126         }
127     }
128 
129     @Override
130     public List<Object> getExample() {
131         List<Object> example = this.example;
132         if (example == null) {
133             example = new ArrayList<>();
134             boolean foundExample = items.isSingleItems()
135                     ? applySingleSchemaExample(example)
136                     : applyMultipleSchemasExamples(example);
137             if (!foundExample) {
138                 example = null;
139             }
140         }
141         return example;
142     }
143 
144     private boolean applySingleSchemaExample(List<Object> example) {
145         boolean foundExample = false;
146         if (items.asSingleItems().getSchema() instanceof WithExampleSchema) {
147             Object itemsExample = ((WithExampleSchema) items.asSingleItems().getSchema()).getExample();
148             if (itemsExample != null) {
149                 int count = minItems != null && minItems > 1 ? minItems : 1;
150                 foundExample = true;
151                 for (int i = 0; i < count; i++) {
152                     example.add(itemsExample);
153                 }
154             }
155         }
156         return foundExample;
157     }
158 
159     private boolean applyMultipleSchemasExamples(List<Object> example) {
160         boolean foundExample = false;
161         for (JsonSchema schema : items.asArrayItems().getJsonSchemas()) {
162             if (schema instanceof WithExampleSchema) {
163                 Object propertyExample = ((WithExampleSchema) schema).getExample();
164                 if (propertyExample != null) {
165                     foundExample = true;
166                     example.add(propertyExample);
167                 } else {
168                     example.add(new HashMap<>());
169                 }
170             }
171         }
172         return foundExample;
173     }
174 
175     @Override
176     public void setExample(String example) throws IOException {
177         this.example = OBJECT_MAPPER.readValue(example, EXAMPLE_VALUE_TYPE);
178     }
179 }