CrestArraySchema.java

/*
 * The contents of this file are subject to the terms of the Common Development and
 * Distribution License (the License). You may not use this file except in compliance with the
 * License.
 *
 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
 * specific language governing permission and limitations under the License.
 *
 * When distributing Covered Software, include this CDDL Header Notice in each file and include
 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
 * Header, with the fields enclosed by brackets [] replaced by your own identifying
 * information: "Portions copyright [year] [name of copyright owner]".
 *
 * Copyright 2016 ForgeRock AS.
 */

package org.forgerock.api.jackson;

import static org.forgerock.api.jackson.JacksonUtils.OBJECT_MAPPER;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;

import javax.validation.ValidationException;

import org.forgerock.api.enums.ReadPolicy;
import org.forgerock.json.JsonValue;

import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.module.jsonSchema.JsonSchema;
import com.fasterxml.jackson.module.jsonSchema.types.ArraySchema;
import org.forgerock.api.enums.WritePolicy;

/**
 * An extension to the Jackson {@code ArraySchema} that includes the custom CREST JSON Schema attributes.
 */
public class CrestArraySchema extends ArraySchema implements CrestReadWritePoliciesSchema, OrderedFieldSchema,
        ValidatableSchema, WithExampleSchema<List<Object>> {
    private static final JavaType EXAMPLE_VALUE_TYPE = OBJECT_MAPPER.getTypeFactory()
            .constructParametrizedType(ArrayList.class, List.class, Object.class);

    private WritePolicy writePolicy;
    private ReadPolicy readPolicy;
    private Boolean errorOnWritePolicyFailure;
    private Boolean returnOnDemand;
    private Integer propertyOrder;
    private List<Object> example;

    @Override
    public WritePolicy getWritePolicy() {
        return writePolicy;
    }

    @Override
    public void setWritePolicy(WritePolicy policy) {
        this.writePolicy = policy;
    }

    @Override
    public ReadPolicy getReadPolicy() {
        return readPolicy;
    }

    @Override
    public void setReadPolicy(ReadPolicy readPolicy) {
        this.readPolicy = readPolicy;
    }

    @Override
    public Boolean getErrorOnWritePolicyFailure() {
        return errorOnWritePolicyFailure;
    }

    @Override
    public void setErrorOnWritePolicyFailure(Boolean errorOnWritePolicyFailure) {
        this.errorOnWritePolicyFailure = errorOnWritePolicyFailure;
    }

    @Override
    public Boolean getReturnOnDemand() {
        return returnOnDemand;
    }

    @Override
    public void setReturnOnDemand(Boolean returnOnDemand) {
        this.returnOnDemand = returnOnDemand;
    }

    @Override
    public Integer getPropertyOrder() {
        return propertyOrder;
    }

    @Override
    public void setPropertyOrder(Integer order) {
        this.propertyOrder = order;
    }

    @Override
    public void validate(JsonValue object) throws ValidationException {
        if (!object.isCollection()) {
            throw new ValidationException("Array expected, but got: " + object.getObject());
        }
        if (maxItems != null && object.size() > maxItems) {
            throw new ValidationException("Array has too many items. Maximum permitted: " + maxItems);
        }
        if (minItems != null && object.size() < minItems) {
            throw new ValidationException("Array has too few items. Minimum permitted: " + minItems);
        }
        if (items.isSingleItems()) {
            if (items.asSingleItems().getSchema() instanceof ValidatableSchema) {
                ValidatableSchema itemSchema = (ValidatableSchema) items.asSingleItems().getSchema();
                for (JsonValue item : object) {
                    itemSchema.validate(item);
                }
            }
        } else {
            Iterator<JsonValue> arrayItems = object.iterator();
            for (JsonSchema itemSchema : items.asArrayItems().getJsonSchemas()) {
                if (!arrayItems.hasNext()) {
                    throw new ValidationException("Not enough items. Expecting " + itemSchema);
                }
                if (itemSchema instanceof ValidatableSchema) {
                    ((ValidatableSchema) itemSchema).validate(arrayItems.next());
                }
            }
        }
    }

    @Override
    public List<Object> getExample() {
        List<Object> example = this.example;
        if (example == null) {
            example = new ArrayList<>();
            boolean foundExample = items.isSingleItems()
                    ? applySingleSchemaExample(example)
                    : applyMultipleSchemasExamples(example);
            if (!foundExample) {
                example = null;
            }
        }
        return example;
    }

    private boolean applySingleSchemaExample(List<Object> example) {
        boolean foundExample = false;
        if (items.asSingleItems().getSchema() instanceof WithExampleSchema) {
            Object itemsExample = ((WithExampleSchema) items.asSingleItems().getSchema()).getExample();
            if (itemsExample != null) {
                int count = minItems != null && minItems > 1 ? minItems : 1;
                foundExample = true;
                for (int i = 0; i < count; i++) {
                    example.add(itemsExample);
                }
            }
        }
        return foundExample;
    }

    private boolean applyMultipleSchemasExamples(List<Object> example) {
        boolean foundExample = false;
        for (JsonSchema schema : items.asArrayItems().getJsonSchemas()) {
            if (schema instanceof WithExampleSchema) {
                Object propertyExample = ((WithExampleSchema) schema).getExample();
                if (propertyExample != null) {
                    foundExample = true;
                    example.add(propertyExample);
                } else {
                    example.add(new HashMap<>());
                }
            }
        }
        return foundExample;
    }

    @Override
    public void setExample(String example) throws IOException {
        this.example = OBJECT_MAPPER.readValue(example, EXAMPLE_VALUE_TYPE);
    }
}