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