View Javadoc
1   /*
2    * The contents of this file are subject to the terms of the Common Development and
3    * Distribution License (the License). You may not use this file except in compliance with the
4    * License.
5    *
6    * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
7    * specific language governing permission and limitations under the License.
8    *
9    * When distributing Covered Software, include this CDDL Header Notice in each file and include
10   * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
11   * Header, with the fields enclosed by brackets [] replaced by your own identifying
12   * information: "Portions copyright [year] [name of copyright owner]".
13   *
14   * Copyright 2016 ForgeRock AS.
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   * Class that represents the Items type in API descriptor.
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       * Getter of Create.
77       *
78       * @return Create
79       */
80      public Create getCreate() {
81          return create;
82      }
83  
84      /**
85       * Getter of Read.
86       *
87       * @return Read
88       */
89      public Read getRead() {
90          return read;
91      }
92  
93      /**
94       * Getter of Update.
95       *
96       * @return Update
97       */
98      public Update getUpdate() {
99          return update;
100     }
101 
102     /**
103      * Getter of Delete.
104      *
105      * @return Delete
106      */
107     public Delete getDelete() {
108         return delete;
109     }
110 
111     /**
112      * Getter of Patch.
113      *
114      * @return Patch
115      */
116     public Patch getPatch() {
117         return patch;
118     }
119 
120     /**
121      * Getter of actions.
122      *
123      * @return Actions
124      */
125     public Action[] getActions() {
126         return actions.length == 0 ? null : actions;
127     }
128 
129     /**
130      * Getter of sub-resources.
131      *
132      * @return Sub-resources
133      */
134     public SubResources getSubresources() {
135         return subresources;
136     }
137 
138     /**
139      * Get the path parameter.
140      *
141      * @return The path parameter.
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      * Builds a {@link Resource} from this {@code Items} instance.
173      *
174      * @param mvccSupported {@code true} when MVCC is supported and {@code false} otherwise
175      * @param resourceSchema Resource-{@link Schema} or {@code null}
176      * @param title The resource title.
177      * @param description The resource description.
178      * @return New {@link Resource}
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      * Create a new Builder for Resoruce.
201      *
202      * @return Builder
203      */
204     public static Builder items() {
205         return new Builder();
206     }
207 
208     /**
209      * Build an {@code Items} from an annotated request handler.
210      *
211      * @param type The annotated type.
212      * @param descriptor The root descriptor to add definitions to.
213      * @param subResources The sub resources.
214      * @return The built {@code Items} object.
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      * Builder to help construct the {@code Items}.
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          * Private default constructor.
282          */
283         protected Builder() {
284             actions = new TreeSet<>();
285         }
286 
287         /**
288          * Set create.
289          *
290          * @param create The create operation description, if supported
291          * @return Builder
292          */
293         @JsonProperty("create")
294         public Builder create(Create create) {
295             checkState();
296             this.create = create;
297             return this;
298         }
299 
300         /**
301          * Set Read.
302          *
303          * @param read The read operation description, if supported
304          * @return Builder
305          */
306         @JsonProperty("read")
307         public Builder read(Read read) {
308             checkState();
309             this.read = read;
310             return this;
311         }
312 
313         /**
314          * Set Update.
315          *
316          * @param update The update operation description, if supported
317          * @return Builder
318          */
319         @JsonProperty("update")
320         public Builder update(Update update) {
321             checkState();
322             this.update = update;
323             return this;
324         }
325 
326         /**
327          * Set Delete.
328          *
329          * @param delete The delete operation description, if supported
330          * @return Builder
331          */
332         @JsonProperty("delete")
333         public Builder delete(Delete delete) {
334             checkState();
335             this.delete = delete;
336             return this;
337         }
338 
339         /**
340          * Set Patch.
341          *
342          * @param patch The patch operation description, if supported
343          * @return Builder
344          */
345         @JsonProperty("patch")
346         public Builder patch(Patch patch) {
347             checkState();
348             this.patch = patch;
349             return this;
350         }
351 
352         /**
353          * Set Actions.
354          *
355          * @param actions The list of action operation descriptions, if supported
356          * @return Builder
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          * Adds one Action to the list of Actions.
367          *
368          * @param action Action operation description to be added to the list
369          * @return Builder
370          */
371         public Builder action(Action action) {
372             checkState();
373             this.actions.add(action);
374             return this;
375         }
376 
377         /**
378          * Sets the path parameter for this resource.
379          *
380          * @param pathParameter The path parameter definition.
381          * @return Builder
382          */
383         @JsonProperty("pathParameter")
384         public Builder pathParameter(Parameter pathParameter) {
385             checkState();
386             this.pathParameter = pathParameter;
387             return this;
388         }
389 
390         /**
391          * Sets the sub-resources for this resource.
392          *
393          * @param subresources The sub-reosurces definition.
394          * @return Builder
395          */
396         @JsonProperty("subresources")
397         public Builder subresources(SubResources subresources) {
398             checkState();
399             this.subresources = subresources;
400             return this;
401         }
402 
403         /**
404          * Construct a new instance of Resource.
405          *
406          * @return Resource instance
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 }