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  package org.forgerock.api.models;
17  
18  import static org.forgerock.api.models.Definitions.definitions;
19  import static org.forgerock.api.models.Errors.errors;
20  import static org.forgerock.api.models.Paths.paths;
21  import static org.forgerock.api.models.Services.services;
22  import static org.forgerock.api.util.ValidationUtil.isEmpty;
23  
24  import java.util.Objects;
25  
26  import com.fasterxml.jackson.annotation.JsonInclude;
27  import com.fasterxml.jackson.annotation.JsonProperty;
28  import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
29  import org.forgerock.api.ApiValidationException;
30  import org.forgerock.util.i18n.LocalizableString;
31  
32  /** Class that represents the ApiDescription type in API descriptor. */
33  @JsonDeserialize(builder = ApiDescription.Builder.class)
34  @JsonInclude(JsonInclude.Include.NON_NULL)
35  public final class ApiDescription {
36  
37      private final String id;
38      private final String version;
39      private final LocalizableString description;
40      private final Definitions definitions;
41      private final Services services;
42      private final Errors errors;
43      private final Paths paths;
44  
45      private ApiDescription(Builder builder) {
46          this.id = builder.id;
47          this.version = builder.version;
48          this.description = builder.description;
49          this.definitions = builder.definitions == null ? definitions().build() : builder.definitions;
50          this.services = builder.services == null ? services().build() : builder.services;
51          this.errors = builder.errors == null ? errors().build() : builder.errors;
52          this.paths = builder.paths == null ? paths().build() : builder.paths;
53  
54          if (isEmpty(id) || isEmpty(version)) {
55              throw new ApiValidationException("id and version required");
56          }
57      }
58  
59      /**
60       * Getter of id.
61       *
62       * @return id
63       */
64      public String getId() {
65          return id;
66      }
67  
68      /**
69       * Getter of version.
70       *
71       * @return The version.
72       */
73      public String getVersion() {
74          return version;
75      }
76  
77      /**
78       * Gets description of API Descriptor.
79       *
80       * @return Description of API Descriptor
81       */
82      public LocalizableString getDescription() {
83          return description;
84      }
85  
86      /**
87       * Getter of definitions.
88       *
89       * @return Definitions map
90       */
91      public Definitions getDefinitions() {
92          return definitions.getDefinitions().isEmpty() ? null : definitions;
93      }
94  
95      /**
96       * This allows the models package to mutate the schema defined here. This is used when processing
97       * annotations on resources that may reference schemas using an id, so those schemas need to be defined here
98       * rather than in-line in the resource descriptions.
99       *
100      * @param id The definition id.
101      * @param schema The definition.
102      */
103     void addDefinition(String id, Schema schema) {
104         if (schema.getReference() != null) {
105             throw new IllegalArgumentException("Cannot define a schema using a reference");
106         }
107         Schema defined = definitions.get(id);
108         if (defined != null && !defined.equals(schema)) {
109             throw new IllegalArgumentException("Trying to redefine already defined schema, " + id);
110         }
111         definitions.getDefinitions().put(id, schema);
112     }
113 
114     /**
115      * Getter of services.
116      *
117      * @return Services map
118      */
119     public Services getServices() {
120         return services.getServices().isEmpty() ? null : services;
121     }
122 
123     /**
124      * This allows the models package to mutate the resources defined here. This is used when processing
125      * annotations on resources that may reference resources using an id, so those services need to be defined here
126      * rather than in-line in the resource descriptions.
127      *
128      * @param id The resource id.
129      * @param resource The resource definition.
130      * @see Resource#fromAnnotatedType(Class, Resource.AnnotatedTypeVariant, ApiDescription)
131      */
132     void addService(String id, Resource resource) {
133         if (resource.getReference() != null) {
134             throw new IllegalArgumentException("Cannot define a resource using a reference");
135         }
136         Resource defined = services.get(id);
137         if (defined != null && !defined.equals(resource)) {
138             throw new IllegalArgumentException("Trying to redefine already defined resource, " + id);
139         }
140         services.getServices().put(id, resource);
141     }
142 
143     /**
144      * Getter of errors.
145      *
146      * @return Errors map
147      */
148     public Errors getErrors() {
149         return errors.getErrors().isEmpty() ? null : errors;
150     }
151 
152     /**
153      * This allows the models package to mutate the errors defined here. This is used when processing annotations on
154      * resources that may reference errors using an id, so those errors need to be defined here rather than in-line in
155      * the resource descriptions.
156      *
157      * @param id The error id.
158      * @param apiError The error definition.
159      * @see ApiError#fromAnnotation(org.forgerock.api.annotations.ApiError, ApiDescription, Class)
160      */
161     void addError(String id, ApiError apiError) {
162         if (apiError.getReference() != null) {
163             throw new IllegalArgumentException("Cannot define an apiError using a reference");
164         }
165         ApiError defined = errors.get(id);
166         if (defined != null && !defined.equals(apiError)) {
167             throw new IllegalArgumentException("Trying to redefine already defined apiError, " + id);
168         }
169         errors.getErrors().put(id, apiError);
170     }
171 
172     /**
173      * Getter of paths.
174      *
175      * @return Paths
176      */
177     // Jackson queries PathsModule.PathsSerializer.isEmpty() to know whether a Paths object is empty
178     @JsonInclude(JsonInclude.Include.NON_EMPTY)
179     public Paths getPaths() {
180         return paths;
181     }
182 
183     /**
184      * Create a new Builder for ApiDescription.
185      *
186      * @return Builder
187      */
188     public static Builder apiDescription() {
189         return new Builder();
190     }
191 
192     @Override
193     public boolean equals(Object o) {
194         if (this == o) {
195             return true;
196         }
197         if (o == null || getClass() != o.getClass()) {
198             return false;
199         }
200         ApiDescription that = (ApiDescription) o;
201         return Objects.equals(id, that.id)
202                 && Objects.equals(version, that.version)
203                 && Objects.equals(description, that.description)
204                 && Objects.equals(definitions, that.definitions)
205                 && Objects.equals(services, that.services)
206                 && Objects.equals(errors, that.errors)
207                 && Objects.equals(paths, that.paths);
208     }
209 
210     @Override
211     public int hashCode() {
212         return Objects.hash(id, version, description, definitions, services, errors, paths);
213     }
214 
215     /**
216      * Builder for the ApiDescription.
217      */
218     public static final class Builder {
219 
220         private String id;
221         private LocalizableString description;
222         private Definitions definitions;
223         private Errors errors;
224         private Services services;
225         private Paths paths;
226         private String version;
227 
228         /**
229          * Private default constructor with the mandatory fields.
230          */
231         private Builder() {
232         }
233 
234         /**
235          * Set the id.
236          *
237          * @param id ApiDescription id
238          * @return Builder
239          */
240         @JsonProperty("id")
241         public Builder id(String id) {
242             this.id = id;
243             return this;
244         }
245 
246         /**
247          * Sets the description.
248          *
249          * @param description Description of API Description
250          * @return Builder
251          */
252         @JsonProperty("description")
253         public Builder description(String description) {
254             this.description = new LocalizableString(description);
255             return this;
256         }
257 
258         /**
259          * Sets the description.
260          *
261          * @param description Description of API Description
262          * @return Builder
263          */
264         public Builder description(LocalizableString description) {
265             this.description = description;
266             return this;
267         }
268 
269         /**
270          * Set the definitions.
271          *
272          * @param definitions Definitions for this API Description
273          * @return Builder
274          */
275         @JsonProperty("definitions")
276         public Builder definitions(Definitions definitions) {
277             this.definitions = definitions;
278             return this;
279         }
280 
281 
282         /**
283          * Set the services.
284          *
285          * @param services Services for this API Description
286          * @return Builder
287          */
288         @JsonProperty("services")
289         public Builder services(Services services) {
290             this.services = services;
291             return this;
292         }
293 
294         /**
295          * Set the errors.
296          *
297          * @param errors Errors for this API Description
298          * @return Builder
299          */
300         @JsonProperty("errors")
301         public Builder errors(Errors errors) {
302             this.errors = errors;
303             return this;
304         }
305 
306         /**
307          * Set the paths.
308          *
309          * @param paths Paths
310          * @return Builder
311          */
312         @JsonProperty("paths")
313         public Builder paths(Paths paths) {
314             this.paths = paths;
315             return this;
316         }
317 
318         /**
319          * Set the version of the API.
320          *
321          * @param version The version.
322          * @return This builder.
323          */
324         @JsonProperty("version")
325         public Builder version(String version) {
326             this.version = version;
327             return this;
328         }
329 
330         /**
331          * Builds the ApiDescription instance.
332          *
333          * @return ApiDescription instance
334          */
335         public ApiDescription build() {
336             return new ApiDescription(this);
337         }
338     }
339 }
340