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}