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-2017 ForgeRock AS.
015 */
016
017package org.forgerock.api.models;
018
019import static org.forgerock.api.models.ApiError.apiError;
020import static org.forgerock.api.models.Reference.reference;
021import static org.forgerock.api.util.ValidationUtil.isEmpty;
022
023import java.util.ArrayList;
024import java.util.Arrays;
025import java.util.List;
026import java.util.Objects;
027
028import com.fasterxml.jackson.annotation.JsonInclude;
029import com.fasterxml.jackson.annotation.JsonProperty;
030import org.forgerock.api.enums.Stability;
031import org.forgerock.util.i18n.LocalizableString;
032import org.slf4j.Logger;
033import org.slf4j.LoggerFactory;
034
035/**
036 * Class that represents the Operation type in API descriptor.
037 */
038@JsonInclude(JsonInclude.Include.NON_NULL)
039public abstract class Operation {
040
041    private static final Logger logger = LoggerFactory.getLogger(Operation.class);
042
043    private final LocalizableString description;
044    private final String[] supportedLocales;
045    @JsonProperty("errors")
046    private final ApiError[] apiErrors;
047    private final Parameter[] parameters;
048    private final Stability stability;
049
050    /**
051     * Protected constructor of the Operation.
052     *
053     * @param builder Operation Builder
054     */
055    protected Operation(Builder builder) {
056        this.description = builder.description;
057        this.supportedLocales = builder.supportedLocales;
058        this.stability = builder.stability;
059
060        final List<ApiError> apiErrors = builder.apiErrors;
061        this.apiErrors = apiErrors.toArray(new ApiError[apiErrors.size()]);
062
063        final List<Parameter> parameters = builder.parameters;
064        this.parameters = parameters.toArray(new Parameter[parameters.size()]);
065    }
066
067    /**
068     * Getter of the description.
069     *
070     * @return Description
071     */
072    public LocalizableString getDescription() {
073        return description;
074    }
075
076    /**
077     * Getter of the supported locales array.
078     *
079     * @return Supported locales
080     */
081    public String[] getSupportedLocales() {
082        return supportedLocales;
083    }
084
085    /**
086     * Getter of the error array.
087     *
088     * @return ApiError array
089     */
090    public ApiError[] getApiErrors() {
091        return apiErrors.length == 0 ? null : apiErrors;
092    }
093
094    /**
095     * Getter of the parameters array.
096     *
097     * @return Parameters
098     */
099    public Parameter[] getParameters() {
100        return parameters.length == 0 ? null : parameters;
101    }
102
103    /**
104     * Getter of Operation stability.
105     *
106     * @return Stability or {@code null} which suggests {@link Stability#STABLE} (default).
107     */
108    public Stability getStability() {
109        return stability;
110    }
111
112    @Override
113    public boolean equals(Object o) {
114        if (this == o) {
115            return true;
116        }
117        if (o == null || getClass() != o.getClass()) {
118            return false;
119        }
120        Operation operation = (Operation) o;
121        return Objects.equals(description, operation.description)
122                && Arrays.equals(supportedLocales, operation.supportedLocales)
123                && Arrays.equals(apiErrors, operation.apiErrors)
124                && Arrays.equals(parameters, operation.parameters)
125                && stability == operation.stability;
126    }
127
128    @Override
129    public int hashCode() {
130        return Objects.hash(description, supportedLocales, apiErrors, parameters, stability);
131    }
132
133    /**
134     * Allocates the operation by operation type to the given Resource Builder
135     * by calling the corresonding method by type.
136     *
137     * @param resourceBuilder - Resource Builder to add the operation
138     */
139    protected abstract void allocateToResource(Resource.Builder resourceBuilder);
140
141    /**
142     * Builder to help construct the Operation.
143     */
144    public abstract static class Builder<T extends Builder<T>> {
145
146        private LocalizableString description;
147        private String[] supportedLocales;
148        private final List<ApiError> apiErrors;
149        private final List<Parameter> parameters;
150        private Stability stability;
151
152        /**
153         * Creates a new Builder.
154         */
155        protected Builder() {
156            apiErrors = new ArrayList<>();
157            parameters = new ArrayList<>();
158        }
159
160        /**
161         * Abstract method that returns the instantiated Builder itself.
162         *
163         * @return Builder
164         */
165        protected abstract T self();
166
167        /**
168         * Set the description.
169         *
170         * @param description A description of the endpoint
171         * @return Builder
172         */
173        public T description(LocalizableString description) {
174            this.description = description;
175            return self();
176        }
177
178        /**
179         * Set the description.
180         *
181         * @param description A description of the endpoint
182         * @return Builder
183         */
184        @JsonProperty("description")
185        public T description(String description) {
186            this.description = new LocalizableString(description);
187            return self();
188        }
189
190        /**
191         * Set the supported locale.
192         *
193         * @param supportedlocales Locales codes supported by the operation
194         * @return Builder
195         */
196        @JsonProperty("supportedLocales")
197        public T supportedLocales(String... supportedlocales) {
198            this.supportedLocales = supportedlocales;
199            return self();
200        }
201
202        /**
203         * Set multiple supported errors.
204         *
205         * @param apiErrors What errors may be returned by this operation
206         * @return Builder
207         */
208        @JsonProperty("errors")
209        public T errors(List<ApiError> apiErrors) {
210            this.apiErrors.addAll(apiErrors);
211            return self();
212        }
213
214        /**
215         * Sets a single supported error.
216         *
217         * @param apiError An error that may be returned by this operation
218         * @return Builder
219         */
220        public T error(ApiError apiError) {
221            this.apiErrors.add(apiError);
222            return self();
223        }
224
225        /**
226         * Set multiple supported parameters.
227         *
228         * @param parameters Extra parameters supported by the operation
229         * @return Builder
230         */
231        @JsonProperty("parameters")
232        public T parameters(List<Parameter> parameters) {
233            this.parameters.addAll(parameters);
234            return self();
235        }
236
237        /**
238         * Sets a single supported parameters.
239         *
240         * @param parameter Extra parameter supported by the operation
241         * @return Builder
242         */
243        public T parameter(Parameter parameter) {
244            this.parameters.add(parameter);
245            return self();
246        }
247
248        /**
249         * Sets stability of Operation.
250         *
251         * @param stability Stability
252         * @return Builder
253         */
254        @JsonProperty("stability")
255        public T stability(Stability stability) {
256            this.stability = stability;
257            return self();
258        }
259
260        /**
261         * Set all properties in the Builder using the data in the annotation.
262         * @param operation The annotation that holds the data
263         * @param descriptor The root descriptor
264         * @param relativeType The type relative to which schema resources should be resolved.
265         * @return Builder
266         */
267        public T detailsFromAnnotation(org.forgerock.api.annotations.Operation operation,
268                ApiDescription descriptor, Class<?> relativeType) {
269            for (String ref : operation.errorRefs()) {
270                if (isEmpty(ref)) {
271                    logger.debug("Empty errorRefs array-element ignored on: " + relativeType.getCanonicalName());
272                } else {
273                    error(apiError().reference(reference().value(ref).build()).build());
274                }
275            }
276            for (org.forgerock.api.annotations.ApiError apiApiError : operation.errors()) {
277                error(ApiError.fromAnnotation(apiApiError, descriptor, relativeType));
278            }
279            for (org.forgerock.api.annotations.Parameter parameter : operation.parameters()) {
280                parameter(Parameter.fromAnnotation(relativeType, parameter));
281            }
282            return description(new LocalizableString(operation.description(), relativeType))
283                    .supportedLocales(operation.locales())
284                    .stability(operation.stability());
285        }
286    }
287
288}