1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.forgerock.api.models;
18
19 import static org.forgerock.api.jackson.JacksonUtils.OBJECT_MAPPER;
20 import static org.forgerock.api.jackson.JacksonUtils.schemaFor;
21 import static org.forgerock.json.JsonValue.json;
22
23 import com.fasterxml.jackson.annotation.JsonAnySetter;
24 import com.fasterxml.jackson.annotation.JsonIgnore;
25 import com.fasterxml.jackson.annotation.JsonProperty;
26 import com.fasterxml.jackson.databind.JsonMappingException;
27 import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
28 import com.fasterxml.jackson.module.jsonSchema.jakarta.JsonSchema;
29 import java.io.IOException;
30 import java.io.InputStream;
31 import java.util.HashMap;
32 import java.util.Map;
33 import java.util.Objects;
34 import org.forgerock.api.jackson.JacksonUtils;
35 import org.forgerock.json.JsonValue;
36 import org.forgerock.util.Reject;
37 import org.wrensecurity.guava.common.base.Strings;
38
39
40
41
42 @JsonDeserialize(builder = Schema.Builder.class)
43 public abstract class Schema {
44
45 private Schema() {
46
47 }
48
49
50
51
52
53 public abstract Reference getReference();
54
55
56
57
58
59 public abstract JsonValue getSchema();
60
61 @Override
62 public boolean equals(Object o) {
63 if (this == o) {
64 return true;
65 }
66 if (o == null || getClass() != o.getClass()) {
67 return false;
68 }
69
70 Schema schema1 = (Schema) o;
71 return Objects.equals(getReference(), schema1.getReference())
72 && isSchemaPropertyMatches(schema1);
73
74 }
75
76 private boolean isSchemaPropertyMatches(Schema schema1) {
77 return getSchema() != null && schema1.getSchema() != null
78 ? Objects.equals(getSchema().getObject(), schema1.getSchema().getObject())
79 : schema1.getSchema() == getSchema();
80 }
81
82 @Override
83 public int hashCode() {
84 JsonValue schema = getSchema();
85 return Objects.hash(getReference(), schema == null ? null : schema.getObject());
86 }
87
88
89
90
91
92 public static Builder newBuilder() {
93 return new Builder();
94 }
95
96
97
98
99
100 public static Builder schema() {
101 return newBuilder();
102 }
103
104
105
106
107
108
109
110
111
112
113
114 public static Schema fromAnnotation(org.forgerock.api.annotations.Schema schema, ApiDescription descriptor,
115 final Class<?> relativeType) {
116 Class<?> type = schema.fromType();
117 if (type.equals(Void.class) && Strings.isNullOrEmpty(schema.schemaResource())) {
118 return null;
119 }
120 Builder builder = schema();
121 String id = schema.id();
122 if (!type.equals(Void.class)) {
123
124 builder.type(type);
125 if (Strings.isNullOrEmpty(id)) {
126
127
128 org.forgerock.api.annotations.Schema typeSchema =
129 type.getAnnotation(org.forgerock.api.annotations.Schema.class);
130 if (typeSchema != null && !Strings.isNullOrEmpty(typeSchema.id())) {
131 id = typeSchema.id();
132 }
133 }
134 } else {
135
136 InputStream resource = relativeType.getResourceAsStream(schema.schemaResource());
137 try {
138 JsonValue json = json(JacksonUtils.OBJECT_MAPPER.readValue(resource, Object.class))
139 .as(new TranslateJsonSchema(relativeType.getClassLoader()));
140 builder.schema(json);
141 } catch (IOException e) {
142 throw new IllegalArgumentException("Could not read declared resource " + schema.schemaResource(), e);
143 }
144 }
145 if (!Strings.isNullOrEmpty(id)) {
146
147 descriptor.addDefinition(id, builder.build());
148 return schema().reference(Reference.reference().value("#/definitions/" + id).build()).build();
149 } else {
150 return builder.build();
151 }
152 }
153
154
155
156
157 public static final class Builder {
158
159 private JsonValue schema;
160 private Reference reference;
161 private Map<String, Object> jsonValueObject = new HashMap<>();
162
163
164
165
166 private Builder() { }
167
168
169
170
171
172
173 @JsonProperty("$ref")
174 public Builder reference(Reference reference) {
175 Reject.ifNull(reference);
176 this.reference = reference;
177 return this;
178 }
179
180
181
182
183
184
185 public Builder schema(JsonValue schema) {
186 Reject.ifNull(schema);
187 this.schema = schema;
188 return this;
189 }
190
191
192
193
194
195
196
197 @JsonAnySetter
198 public Builder schema(String key, Object value) {
199 jsonValueObject.put(key, value);
200 return this;
201 }
202
203
204
205
206
207
208 public Builder type(Class<?> type) {
209 Reject.ifNull(type);
210 try {
211 JsonSchema jsonSchema = schemaFor(type);
212 String schemaString = OBJECT_MAPPER.writer().writeValueAsString(jsonSchema);
213 this.schema = json(OBJECT_MAPPER.readValue(schemaString, Object.class))
214 .as(new TranslateJsonSchema(type.getClassLoader()));
215 } catch (JsonMappingException e) {
216 throw new IllegalArgumentException(e);
217 } catch (IOException e) {
218 throw new IllegalStateException("Jackson cannot read its own JSON", e);
219 }
220 return this;
221 }
222
223
224
225
226
227
228 public Schema build() {
229 if (!jsonValueObject.isEmpty()) {
230 schema(json(jsonValueObject));
231 }
232 return reference == null ? new SchemaSchema(schema) : new ReferenceSchema(reference);
233 }
234 }
235
236 private static final class ReferenceSchema extends Schema {
237
238 private final Reference reference;
239
240 private ReferenceSchema(Reference reference) {
241 this.reference = reference;
242 }
243
244 @Override
245 @JsonProperty("$ref")
246 public Reference getReference() {
247 return reference;
248 }
249
250 @Override
251 @JsonIgnore
252 public JsonValue getSchema() {
253 return null;
254 }
255 }
256
257 private static final class SchemaSchema extends Schema {
258
259 private final JsonValue schema;
260
261 private SchemaSchema(JsonValue schema) {
262 this.schema = schema;
263 }
264
265 @Override
266 @JsonIgnore
267 public Reference getReference() {
268 return null;
269 }
270
271 @Override
272 @com.fasterxml.jackson.annotation.JsonValue
273 public JsonValue getSchema() {
274 return schema;
275 }
276 }
277 }