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 */ 016package org.forgerock.api.jackson; 017 018import static org.forgerock.api.jackson.JacksonUtils.OBJECT_MAPPER; 019 020import com.fasterxml.jackson.databind.JavaType; 021import com.fasterxml.jackson.module.jsonSchema.jakarta.JsonSchema; 022import com.fasterxml.jackson.module.jsonSchema.jakarta.types.ArraySchema; 023import jakarta.validation.ValidationException; 024import java.io.IOException; 025import java.util.ArrayList; 026import java.util.HashMap; 027import java.util.Iterator; 028import java.util.List; 029import org.forgerock.api.enums.ReadPolicy; 030import org.forgerock.api.enums.WritePolicy; 031import org.forgerock.json.JsonValue; 032 033/** 034 * An extension to the Jackson {@code ArraySchema} that includes the custom CREST JSON Schema attributes. 035 */ 036public class CrestArraySchema extends ArraySchema implements CrestReadWritePoliciesSchema, OrderedFieldSchema, 037 ValidatableSchema, WithExampleSchema<List<Object>> { 038 private static final JavaType EXAMPLE_VALUE_TYPE = OBJECT_MAPPER.getTypeFactory() 039 .constructParametrizedType(ArrayList.class, List.class, Object.class); 040 041 private WritePolicy writePolicy; 042 private ReadPolicy readPolicy; 043 private Boolean errorOnWritePolicyFailure; 044 private Boolean returnOnDemand; 045 private Integer propertyOrder; 046 private List<Object> example; 047 048 @Override 049 public WritePolicy getWritePolicy() { 050 return writePolicy; 051 } 052 053 @Override 054 public void setWritePolicy(WritePolicy policy) { 055 this.writePolicy = policy; 056 } 057 058 @Override 059 public ReadPolicy getReadPolicy() { 060 return readPolicy; 061 } 062 063 @Override 064 public void setReadPolicy(ReadPolicy readPolicy) { 065 this.readPolicy = readPolicy; 066 } 067 068 @Override 069 public Boolean getErrorOnWritePolicyFailure() { 070 return errorOnWritePolicyFailure; 071 } 072 073 @Override 074 public void setErrorOnWritePolicyFailure(Boolean errorOnWritePolicyFailure) { 075 this.errorOnWritePolicyFailure = errorOnWritePolicyFailure; 076 } 077 078 @Override 079 public Boolean getReturnOnDemand() { 080 return returnOnDemand; 081 } 082 083 @Override 084 public void setReturnOnDemand(Boolean returnOnDemand) { 085 this.returnOnDemand = returnOnDemand; 086 } 087 088 @Override 089 public Integer getPropertyOrder() { 090 return propertyOrder; 091 } 092 093 @Override 094 public void setPropertyOrder(Integer order) { 095 this.propertyOrder = order; 096 } 097 098 @Override 099 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}