001/*
002 * The contents of this file are subject to the terms of the Common Development and
003 * Distribution License (the License). You may not use this file except in compliance with the
004 * License.
005 *
006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
007 * specific language governing permission and limitations under the License.
008 *
009 * When distributing Covered Software, include this CDDL Header Notice in each file and include
010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
011 * Header, with the fields enclosed by brackets [] replaced by your own identifying
012 * information: "Portions copyright [year] [name of copyright owner]".
013 *
014 * Copyright 2016 ForgeRock AS.
015 */
016
017package org.forgerock.api.jackson;
018
019import static org.forgerock.api.jackson.JacksonUtils.OBJECT_MAPPER;
020
021import java.io.IOException;
022import java.util.ArrayList;
023import java.util.HashMap;
024import java.util.Iterator;
025import java.util.List;
026
027import javax.validation.ValidationException;
028
029import org.forgerock.api.enums.ReadPolicy;
030import org.forgerock.json.JsonValue;
031
032import com.fasterxml.jackson.databind.JavaType;
033import com.fasterxml.jackson.module.jsonSchema.JsonSchema;
034import com.fasterxml.jackson.module.jsonSchema.types.ArraySchema;
035import org.forgerock.api.enums.WritePolicy;
036
037/**
038 * An extension to the Jackson {@code ArraySchema} that includes the custom CREST JSON Schema attributes.
039 */
040public class CrestArraySchema extends ArraySchema implements CrestReadWritePoliciesSchema, OrderedFieldSchema,
041        ValidatableSchema, WithExampleSchema<List<Object>> {
042    private static final JavaType EXAMPLE_VALUE_TYPE = OBJECT_MAPPER.getTypeFactory()
043            .constructParametrizedType(ArrayList.class, List.class, Object.class);
044
045    private WritePolicy writePolicy;
046    private ReadPolicy readPolicy;
047    private Boolean errorOnWritePolicyFailure;
048    private Boolean returnOnDemand;
049    private Integer propertyOrder;
050    private List<Object> example;
051
052    @Override
053    public WritePolicy getWritePolicy() {
054        return writePolicy;
055    }
056
057    @Override
058    public void setWritePolicy(WritePolicy policy) {
059        this.writePolicy = policy;
060    }
061
062    @Override
063    public ReadPolicy getReadPolicy() {
064        return readPolicy;
065    }
066
067    @Override
068    public void setReadPolicy(ReadPolicy readPolicy) {
069        this.readPolicy = readPolicy;
070    }
071
072    @Override
073    public Boolean getErrorOnWritePolicyFailure() {
074        return errorOnWritePolicyFailure;
075    }
076
077    @Override
078    public void setErrorOnWritePolicyFailure(Boolean errorOnWritePolicyFailure) {
079        this.errorOnWritePolicyFailure = errorOnWritePolicyFailure;
080    }
081
082    @Override
083    public Boolean getReturnOnDemand() {
084        return returnOnDemand;
085    }
086
087    @Override
088    public void setReturnOnDemand(Boolean returnOnDemand) {
089        this.returnOnDemand = returnOnDemand;
090    }
091
092    @Override
093    public Integer getPropertyOrder() {
094        return propertyOrder;
095    }
096
097    @Override
098    public void setPropertyOrder(Integer order) {
099        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}