001/*
002 * The contents of this file are subject to the terms of the Common Development and
003 * Distribution License (the License). You may not use this file except in compliance with the
004 * License.
005 *
006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
007 * specific language governing permission and limitations under the License.
008 *
009 * When distributing Covered Software, include this CDDL Header Notice in each file and include
010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
011 * Header, with the fields enclosed by brackets [] replaced by your own identifying
012 * information: "Portions copyright [year] [name of copyright owner]".
013 *
014 * Copyright 2016 ForgeRock AS.
015 */
016package org.forgerock.api.models;
017
018import static org.forgerock.api.models.Definitions.definitions;
019import static org.forgerock.api.models.Errors.errors;
020import static org.forgerock.api.models.Paths.paths;
021import static org.forgerock.api.models.Services.services;
022import static org.forgerock.api.util.ValidationUtil.isEmpty;
023
024import java.util.Objects;
025
026import com.fasterxml.jackson.annotation.JsonInclude;
027import com.fasterxml.jackson.annotation.JsonProperty;
028import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
029import org.forgerock.api.ApiValidationException;
030import org.forgerock.util.i18n.LocalizableString;
031
032/** Class that represents the ApiDescription type in API descriptor. */
033@JsonDeserialize(builder = ApiDescription.Builder.class)
034@JsonInclude(JsonInclude.Include.NON_NULL)
035public final class ApiDescription {
036
037    private final String id;
038    private final String version;
039    private final LocalizableString description;
040    private final Definitions definitions;
041    private final Services services;
042    private final Errors errors;
043    private final Paths paths;
044
045    private ApiDescription(Builder builder) {
046        this.id = builder.id;
047        this.version = builder.version;
048        this.description = builder.description;
049        this.definitions = builder.definitions == null ? definitions().build() : builder.definitions;
050        this.services = builder.services == null ? services().build() : builder.services;
051        this.errors = builder.errors == null ? errors().build() : builder.errors;
052        this.paths = builder.paths == null ? paths().build() : builder.paths;
053
054        if (isEmpty(id) || isEmpty(version)) {
055            throw new ApiValidationException("id and version required");
056        }
057    }
058
059    /**
060     * Getter of id.
061     *
062     * @return id
063     */
064    public String getId() {
065        return id;
066    }
067
068    /**
069     * Getter of version.
070     *
071     * @return The version.
072     */
073    public String getVersion() {
074        return version;
075    }
076
077    /**
078     * Gets description of API Descriptor.
079     *
080     * @return Description of API Descriptor
081     */
082    public LocalizableString getDescription() {
083        return description;
084    }
085
086    /**
087     * Getter of definitions.
088     *
089     * @return Definitions map
090     */
091    public Definitions getDefinitions() {
092        return definitions.getDefinitions().isEmpty() ? null : definitions;
093    }
094
095    /**
096     * This allows the models package to mutate the schema defined here. This is used when processing
097     * annotations on resources that may reference schemas using an id, so those schemas need to be defined here
098     * rather than in-line in the resource descriptions.
099     *
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