Operation.java

/*
 * The contents of this file are subject to the terms of the Common Development and
 * Distribution License (the License). You may not use this file except in compliance with the
 * License.
 *
 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
 * specific language governing permission and limitations under the License.
 *
 * When distributing Covered Software, include this CDDL Header Notice in each file and include
 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
 * Header, with the fields enclosed by brackets [] replaced by your own identifying
 * information: "Portions copyright [year] [name of copyright owner]".
 *
 * Copyright 2016-2017 ForgeRock AS.
 */

package org.forgerock.api.models;

import static org.forgerock.api.models.ApiError.apiError;
import static org.forgerock.api.models.Reference.reference;
import static org.forgerock.api.util.ValidationUtil.isEmpty;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.forgerock.api.enums.Stability;
import org.forgerock.util.i18n.LocalizableString;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Class that represents the Operation type in API descriptor.
 */
@JsonInclude(JsonInclude.Include.NON_NULL)
public abstract class Operation {

    private static final Logger logger = LoggerFactory.getLogger(Operation.class);

    private final LocalizableString description;
    private final String[] supportedLocales;
    @JsonProperty("errors")
    private final ApiError[] apiErrors;
    private final Parameter[] parameters;
    private final Stability stability;

    /**
     * Protected constructor of the Operation.
     *
     * @param builder Operation Builder
     */
    protected Operation(Builder builder) {
        this.description = builder.description;
        this.supportedLocales = builder.supportedLocales;
        this.stability = builder.stability;

        final List<ApiError> apiErrors = builder.apiErrors;
        this.apiErrors = apiErrors.toArray(new ApiError[apiErrors.size()]);

        final List<Parameter> parameters = builder.parameters;
        this.parameters = parameters.toArray(new Parameter[parameters.size()]);
    }

    /**
     * Getter of the description.
     *
     * @return Description
     */
    public LocalizableString getDescription() {
        return description;
    }

    /**
     * Getter of the supported locales array.
     *
     * @return Supported locales
     */
    public String[] getSupportedLocales() {
        return supportedLocales;
    }

    /**
     * Getter of the error array.
     *
     * @return ApiError array
     */
    public ApiError[] getApiErrors() {
        return apiErrors.length == 0 ? null : apiErrors;
    }

    /**
     * Getter of the parameters array.
     *
     * @return Parameters
     */
    public Parameter[] getParameters() {
        return parameters.length == 0 ? null : parameters;
    }

    /**
     * Getter of Operation stability.
     *
     * @return Stability or {@code null} which suggests {@link Stability#STABLE} (default).
     */
    public Stability getStability() {
        return stability;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        Operation operation = (Operation) o;
        return Objects.equals(description, operation.description)
                && Arrays.equals(supportedLocales, operation.supportedLocales)
                && Arrays.equals(apiErrors, operation.apiErrors)
                && Arrays.equals(parameters, operation.parameters)
                && stability == operation.stability;
    }

    @Override
    public int hashCode() {
        return Objects.hash(description, supportedLocales, apiErrors, parameters, stability);
    }

    /**
     * Allocates the operation by operation type to the given Resource Builder
     * by calling the corresonding method by type.
     *
     * @param resourceBuilder - Resource Builder to add the operation
     */
    protected abstract void allocateToResource(Resource.Builder resourceBuilder);

    /**
     * Builder to help construct the Operation.
     */
    public abstract static class Builder<T extends Builder<T>> {

        private LocalizableString description;
        private String[] supportedLocales;
        private final List<ApiError> apiErrors;
        private final List<Parameter> parameters;
        private Stability stability;

        /**
         * Creates a new Builder.
         */
        protected Builder() {
            apiErrors = new ArrayList<>();
            parameters = new ArrayList<>();
        }

        /**
         * Abstract method that returns the instantiated Builder itself.
         *
         * @return Builder
         */
        protected abstract T self();

        /**
         * Set the description.
         *
         * @param description A description of the endpoint
         * @return Builder
         */
        public T description(LocalizableString description) {
            this.description = description;
            return self();
        }

        /**
         * Set the description.
         *
         * @param description A description of the endpoint
         * @return Builder
         */
        @JsonProperty("description")
        public T description(String description) {
            this.description = new LocalizableString(description);
            return self();
        }

        /**
         * Set the supported locale.
         *
         * @param supportedlocales Locales codes supported by the operation
         * @return Builder
         */
        @JsonProperty("supportedLocales")
        public T supportedLocales(String... supportedlocales) {
            this.supportedLocales = supportedlocales;
            return self();
        }

        /**
         * Set multiple supported errors.
         *
         * @param apiErrors What errors may be returned by this operation
         * @return Builder
         */
        @JsonProperty("errors")
        public T errors(List<ApiError> apiErrors) {
            this.apiErrors.addAll(apiErrors);
            return self();
        }

        /**
         * Sets a single supported error.
         *
         * @param apiError An error that may be returned by this operation
         * @return Builder
         */
        public T error(ApiError apiError) {
            this.apiErrors.add(apiError);
            return self();
        }

        /**
         * Set multiple supported parameters.
         *
         * @param parameters Extra parameters supported by the operation
         * @return Builder
         */
        @JsonProperty("parameters")
        public T parameters(List<Parameter> parameters) {
            this.parameters.addAll(parameters);
            return self();
        }

        /**
         * Sets a single supported parameters.
         *
         * @param parameter Extra parameter supported by the operation
         * @return Builder
         */
        public T parameter(Parameter parameter) {
            this.parameters.add(parameter);
            return self();
        }

        /**
         * Sets stability of Operation.
         *
         * @param stability Stability
         * @return Builder
         */
        @JsonProperty("stability")
        public T stability(Stability stability) {
            this.stability = stability;
            return self();
        }

        /**
         * Set all properties in the Builder using the data in the annotation.
         * @param operation The annotation that holds the data
         * @param descriptor The root descriptor
         * @param relativeType The type relative to which schema resources should be resolved.
         * @return Builder
         */
        public T detailsFromAnnotation(org.forgerock.api.annotations.Operation operation,
                ApiDescription descriptor, Class<?> relativeType) {
            for (String ref : operation.errorRefs()) {
                if (isEmpty(ref)) {
                    logger.debug("Empty errorRefs array-element ignored on: " + relativeType.getCanonicalName());
                } else {
                    error(apiError().reference(reference().value(ref).build()).build());
                }
            }
            for (org.forgerock.api.annotations.ApiError apiApiError : operation.errors()) {
                error(ApiError.fromAnnotation(apiApiError, descriptor, relativeType));
            }
            for (org.forgerock.api.annotations.Parameter parameter : operation.parameters()) {
                parameter(Parameter.fromAnnotation(relativeType, parameter));
            }
            return description(new LocalizableString(operation.description(), relativeType))
                    .supportedLocales(operation.locales())
                    .stability(operation.stability());
        }
    }

}