CrestApiProducer.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 ForgeRock AS.
 */

package org.forgerock.api;

import static java.util.Collections.*;
import static org.forgerock.api.models.ApiDescription.*;
import static org.forgerock.api.models.Definitions.*;
import static org.forgerock.api.models.Errors.*;
import static org.forgerock.api.models.Paths.*;
import static org.forgerock.api.models.Services.services;
import static org.forgerock.api.models.VersionedPath.*;

import java.util.List;
import java.util.Set;

import org.forgerock.api.models.ApiDescription;
import org.forgerock.api.models.Definitions;
import org.forgerock.api.models.Errors;
import org.forgerock.util.i18n.LocalizableString;
import org.forgerock.api.models.Paths;
import org.forgerock.api.models.Services;
import org.forgerock.api.models.VersionedPath;
import org.forgerock.http.routing.Version;
import org.forgerock.http.ApiProducer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * An {@link ApiProducer} implementation for CREST resources, that provides {@code ApiDescription} descriptors.
 */
public class CrestApiProducer implements ApiProducer<ApiDescription> {

    private final String version;
    private final String id;
    private final LocalizableString description;
    private static final Logger LOGGER = LoggerFactory.getLogger(CrestApiProducer.class);

    /**
     * Construct a new producer.
     * @param id The API ID fragment for this producer.
     * @param apiVersion The version of the API being described.
     * @param description The API description.
     */
    public CrestApiProducer(String id, String apiVersion, LocalizableString description) {
        this.id = id;
        this.version = apiVersion;
        this.description = description;
    }

    /**
     * Construct a new producer.
     * @param id The API ID fragment for this producer.
     * @param apiVersion The version of the API being described.
     */
    public CrestApiProducer(String id, String apiVersion) {
        this(id, apiVersion, null);
    }

    @Override
    public ApiDescription withPath(ApiDescription api, String parentPath) {
        Paths.Builder paths = paths();
        Set<String> names = api.getPaths().getNames();
        for (String subpath : names) {
            paths.put(subpath.equals("") ? parentPath : parentPath + "/" + subpath,
                    api.getPaths().get(subpath));
        }
        return createApi(api.getDefinitions(), api.getErrors(), api.getServices(), paths.build());
    }

    @Override
    public ApiDescription withVersion(ApiDescription api, Version version) {
        Paths.Builder paths = paths();
        Set<String> names = api.getPaths().getNames();
        for (String path : names) {
            VersionedPath versionedPath = api.getPaths().get(path);
            if (singleton(UNVERSIONED).equals(versionedPath.getVersions())) {
                paths.put(path, versionedPath().put(version, versionedPath.get(UNVERSIONED)).build());
            } else {
                throw new IllegalStateException("Trying to version something already versioned: " + versionedPath);
            }
        }
        return createApi(api.getDefinitions(), api.getErrors(), api.getServices(), paths.build());
    }

    @Override
    public ApiDescription merge(List<ApiDescription> descriptions) {
        Paths.Builder paths = paths();
        Definitions.Builder definitions = definitions();
        Errors.Builder errors = errors();
        Services.Builder services = services();
        for (ApiDescription description : descriptions) {
            if (description != null) {
                try {
                    if (description.getDefinitions() != null) {
                        for (String definition : description.getDefinitions().getNames()) {
                            definitions.put(definition, description.getDefinitions().get(definition));
                        }
                    }
                    if (description.getErrors() != null) {
                        for (String error : description.getErrors().getNames()) {
                            errors.put(error, description.getErrors().get(error));
                        }
                    }
                    if (description.getServices() != null) {
                        for (String service : description.getServices().getNames()) {
                            services.put(service, description.getServices().get(service));
                        }
                    }
                    if (description.getPaths() != null) {
                        for (String path : description.getPaths().getNames()) {
                            paths.merge(path, description.getPaths().get(path));
                        }
                    }
                } catch (RuntimeException re) {
                    LOGGER.error(re.getMessage(), re.fillInStackTrace());
                    throw re;
                }
            }
        }
        return createApi(definitions.build(), errors.build(), services.build(), paths.build());
    }

    @Override
    public ApiDescription addApiInfo(ApiDescription api) {
        return createApi(api.getDefinitions(), api.getErrors(), api.getServices(), api.getPaths());
    }

    private ApiDescription createApi(Definitions definitions, Errors errors, Services services, Paths paths) {
        return apiDescription()
                .definitions(definitions)
                .errors(errors)
                .services(services)
                .paths(paths)
                .id(this.id)
                .version(this.version)
                .description(this.description)
                .build();
    }

    @Override
    public ApiProducer<ApiDescription> newChildProducer(String idFragment) {
        return new CrestApiProducer(id + idFragment, version);
    }
}