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.enums.ParameterSource.*;
20 import static org.forgerock.api.util.ValidationUtil.*;
21 import static org.forgerock.util.Reject.*;
22
23 import java.lang.reflect.Method;
24 import java.util.Arrays;
25 import java.util.Collections;
26 import java.util.List;
27 import java.util.Objects;
28 import java.util.Set;
29 import java.util.TreeSet;
30
31 import com.fasterxml.jackson.annotation.JsonIgnore;
32 import com.fasterxml.jackson.annotation.JsonInclude;
33 import com.fasterxml.jackson.annotation.JsonProperty;
34 import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
35 import org.forgerock.api.ApiValidationException;
36 import org.forgerock.api.annotations.Actions;
37 import org.forgerock.api.annotations.CollectionProvider;
38 import org.forgerock.util.i18n.LocalizableString;
39 import org.slf4j.Logger;
40 import org.slf4j.LoggerFactory;
41
42
43
44
45 @JsonDeserialize(builder = Items.Builder.class)
46 @JsonInclude(JsonInclude.Include.NON_NULL)
47 public final class Items {
48
49 private static final Logger LOGGER = LoggerFactory.getLogger(Items.class);
50
51 private final Create create;
52 private final Read read;
53 private final Update update;
54 private final Delete delete;
55 private final Patch patch;
56 private final Action[] actions;
57 private final SubResources subresources;
58 private final Parameter pathParameter;
59
60 private Items(Builder builder) {
61 this.create = builder.create;
62 this.read = builder.read;
63 this.update = builder.update;
64 this.delete = builder.delete;
65 this.patch = builder.patch;
66 this.subresources = builder.subresources;
67 this.pathParameter = builder.pathParameter;
68 this.actions = builder.actions.toArray(new Action[builder.actions.size()]);
69
70 if (create == null && read == null && update == null && delete == null && patch == null && isEmpty(actions)) {
71 throw new ApiValidationException("At least one operation required");
72 }
73 }
74
75
76
77
78
79
80 public Create getCreate() {
81 return create;
82 }
83
84
85
86
87
88
89 public Read getRead() {
90 return read;
91 }
92
93
94
95
96
97
98 public Update getUpdate() {
99 return update;
100 }
101
102
103
104
105
106
107 public Delete getDelete() {
108 return delete;
109 }
110
111
112
113
114
115
116 public Patch getPatch() {
117 return patch;
118 }
119
120
121
122
123
124
125 public Action[] getActions() {
126 return actions.length == 0 ? null : actions;
127 }
128
129
130
131
132
133
134 public SubResources getSubresources() {
135 return subresources;
136 }
137
138
139
140
141
142
143 public Parameter getPathParameter() {
144 return pathParameter;
145 }
146
147 @Override
148 public boolean equals(Object o) {
149 if (this == o) {
150 return true;
151 }
152 if (o == null || getClass() != o.getClass()) {
153 return false;
154 }
155 Items items = (Items) o;
156 return Objects.equals(create, items.create)
157 && Objects.equals(read, items.read)
158 && Objects.equals(update, items.update)
159 && Objects.equals(delete, items.delete)
160 && Objects.equals(patch, items.patch)
161 && Arrays.equals(actions, items.actions)
162 && Objects.equals(subresources, items.subresources)
163 && Objects.equals(pathParameter, items.pathParameter);
164 }
165
166 @Override
167 public int hashCode() {
168 return Objects.hash(create, read, update, delete, patch, actions, pathParameter, subresources);
169 }
170
171
172
173
174
175
176
177
178
179
180 @JsonIgnore
181 public Resource asResource(boolean mvccSupported, Schema resourceSchema, LocalizableString title,
182 LocalizableString description) {
183 final List<Action> actions =
184 getActions() == null ? Collections.<Action>emptyList() : Arrays.asList(getActions());
185 return Resource.resource()
186 .mvccSupported(mvccSupported)
187 .resourceSchema(resourceSchema)
188 .title(title)
189 .description(description)
190 .create(getCreate())
191 .read(getRead())
192 .update(getUpdate())
193 .delete(getDelete())
194 .patch(getPatch())
195 .actions(actions)
196 .build();
197 }
198
199
200
201
202
203
204 public static Builder items() {
205 return new Builder();
206 }
207
208
209
210
211
212
213
214
215
216 public static Items fromAnnotatedType(Class<?> type, ApiDescription descriptor, SubResources subResources) {
217 final Builder builder = items();
218 final CollectionProvider provider = type.getAnnotation(CollectionProvider.class);
219 if (provider == null) {
220 LOGGER.info("Asked for Items for annotated type, but type does not have required RequestHandler"
221 + " annotation. No api descriptor will be available for " + type);
222 return null;
223 }
224 builder.pathParameter(Parameter.fromAnnotation(type, provider.pathParam()));
225
226 for (final Method m : type.getMethods()) {
227 boolean instanceMethod = Arrays.asList(m.getParameterTypes()).indexOf(String.class) > -1;
228 org.forgerock.api.annotations.Action action = m.getAnnotation(org.forgerock.api.annotations.Action.class);
229 if (action != null && instanceMethod) {
230 builder.actions.add(Action.fromAnnotation(action, m, descriptor, type));
231 }
232 Actions actions = m.getAnnotation(Actions.class);
233 if (actions != null && instanceMethod) {
234 for (org.forgerock.api.annotations.Action a : actions.value()) {
235 builder.actions.add(Action.fromAnnotation(a, null, descriptor, type));
236 }
237 }
238 org.forgerock.api.annotations.Create create = m.getAnnotation(org.forgerock.api.annotations.Create.class);
239 if (create != null) {
240 builder.create = Create.fromAnnotation(create, true, descriptor, type);
241 }
242 org.forgerock.api.annotations.Read read = m.getAnnotation(org.forgerock.api.annotations.Read.class);
243 if (read != null) {
244 builder.read = Read.fromAnnotation(read, descriptor, type);
245 }
246 org.forgerock.api.annotations.Update update =
247 m.getAnnotation(org.forgerock.api.annotations.Update.class);
248 if (update != null) {
249 builder.update = Update.fromAnnotation(update, descriptor, type);
250 }
251 org.forgerock.api.annotations.Delete delete =
252 m.getAnnotation(org.forgerock.api.annotations.Delete.class);
253 if (delete != null) {
254 builder.delete = Delete.fromAnnotation(delete, descriptor, type);
255 }
256 org.forgerock.api.annotations.Patch patch = m.getAnnotation(org.forgerock.api.annotations.Patch.class);
257 if (patch != null) {
258 builder.patch = Patch.fromAnnotation(patch, descriptor, type);
259 }
260 }
261
262 return builder.subresources(subResources).build();
263 }
264
265
266
267
268 public final static class Builder {
269 private Create create;
270 private Read read;
271 private Update update;
272 private Delete delete;
273 private Patch patch;
274 private SubResources subresources;
275 private Parameter pathParameter = Parameter.parameter().name("id").type("string").source(PATH).required(true)
276 .build();
277 private final Set<Action> actions;
278 private boolean built = false;
279
280
281
282
283 protected Builder() {
284 actions = new TreeSet<>();
285 }
286
287
288
289
290
291
292
293 @JsonProperty("create")
294 public Builder create(Create create) {
295 checkState();
296 this.create = create;
297 return this;
298 }
299
300
301
302
303
304
305
306 @JsonProperty("read")
307 public Builder read(Read read) {
308 checkState();
309 this.read = read;
310 return this;
311 }
312
313
314
315
316
317
318
319 @JsonProperty("update")
320 public Builder update(Update update) {
321 checkState();
322 this.update = update;
323 return this;
324 }
325
326
327
328
329
330
331
332 @JsonProperty("delete")
333 public Builder delete(Delete delete) {
334 checkState();
335 this.delete = delete;
336 return this;
337 }
338
339
340
341
342
343
344
345 @JsonProperty("patch")
346 public Builder patch(Patch patch) {
347 checkState();
348 this.patch = patch;
349 return this;
350 }
351
352
353
354
355
356
357
358 @JsonProperty("actions")
359 public Builder actions(List<Action> actions) {
360 checkState();
361 this.actions.addAll(actions);
362 return this;
363 }
364
365
366
367
368
369
370
371 public Builder action(Action action) {
372 checkState();
373 this.actions.add(action);
374 return this;
375 }
376
377
378
379
380
381
382
383 @JsonProperty("pathParameter")
384 public Builder pathParameter(Parameter pathParameter) {
385 checkState();
386 this.pathParameter = pathParameter;
387 return this;
388 }
389
390
391
392
393
394
395
396 @JsonProperty("subresources")
397 public Builder subresources(SubResources subresources) {
398 checkState();
399 this.subresources = subresources;
400 return this;
401 }
402
403
404
405
406
407
408 public Items build() {
409 checkState();
410 this.built = true;
411 if (create == null && read == null && update == null && delete == null && patch == null
412 && actions.isEmpty() && subresources == null) {
413 return null;
414 }
415
416 return new Items(this);
417 }
418
419 private void checkState() {
420 rejectStateIfTrue(built, "Already built Items");
421 }
422
423 }
424 }