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 */
016
017package org.forgerock.api;
018
019import static java.util.Collections.*;
020import static org.forgerock.api.models.ApiDescription.*;
021import static org.forgerock.api.models.Definitions.*;
022import static org.forgerock.api.models.Errors.*;
023import static org.forgerock.api.models.Paths.*;
024import static org.forgerock.api.models.Services.services;
025import static org.forgerock.api.models.VersionedPath.*;
026
027import java.util.List;
028import java.util.Set;
029
030import org.forgerock.api.models.ApiDescription;
031import org.forgerock.api.models.Definitions;
032import org.forgerock.api.models.Errors;
033import org.forgerock.util.i18n.LocalizableString;
034import org.forgerock.api.models.Paths;
035import org.forgerock.api.models.Services;
036import org.forgerock.api.models.VersionedPath;
037import org.forgerock.http.routing.Version;
038import org.forgerock.http.ApiProducer;
039import org.slf4j.Logger;
040import org.slf4j.LoggerFactory;
041
042/**
043 * An {@link ApiProducer} implementation for CREST resources, that provides {@code ApiDescription} descriptors.
044 */
045public class CrestApiProducer implements ApiProducer<ApiDescription> {
046
047    private final String version;
048    private final String id;
049    private final LocalizableString description;
050    private static final Logger LOGGER = LoggerFactory.getLogger(CrestApiProducer.class);
051
052    /**
053     * Construct a new producer.
054     * @param id The API ID fragment for this producer.
055     * @param apiVersion The version of the API being described.
056     * @param description The API description.
057     */
058    public CrestApiProducer(String id, String apiVersion, LocalizableString description) {
059        this.id = id;
060        this.version = apiVersion;
061        this.description = description;
062    }
063
064    /**
065     * Construct a new producer.
066     * @param id The API ID fragment for this producer.
067     * @param apiVersion The version of the API being described.
068     */
069    public CrestApiProducer(String id, String apiVersion) {
070        this(id, apiVersion, null);
071    }
072
073    @Override
074    public ApiDescription withPath(ApiDescription api, String parentPath) {
075        Paths.Builder paths = paths();
076        Set<String> names = api.getPaths().getNames();
077        for (String subpath : names) {
078            paths.put(subpath.equals("") ? parentPath : parentPath + "/" + subpath,
079                    api.getPaths().get(subpath));
080        }
081        return createApi(api.getDefinitions(), api.getErrors(), api.getServices(), paths.build());
082    }
083
084    @Override
085    public ApiDescription withVersion(ApiDescription api, Version version) {
086        Paths.Builder paths = paths();
087        Set<String> names = api.getPaths().getNames();
088        for (String path : names) {
089            VersionedPath versionedPath = api.getPaths().get(path);
090            if (singleton(UNVERSIONED).equals(versionedPath.getVersions())) {
091                paths.put(path, versionedPath().put(version, versionedPath.get(UNVERSIONED)).build());
092            } else {
093                throw new IllegalStateException("Trying to version something already versioned: " + versionedPath);
094            }
095        }
096        return createApi(api.getDefinitions(), api.getErrors(), api.getServices(), paths.build());
097    }
098
099    @Override
100    public ApiDescription merge(List<ApiDescription> descriptions) {
101        Paths.Builder paths = paths();
102        Definitions.Builder definitions = definitions();
103        Errors.Builder errors = errors();
104        Services.Builder services = services();
105        for (ApiDescription description : descriptions) {
106            if (description != null) {
107                try {
108                    if (description.getDefinitions() != null) {
109                        for (String definition : description.getDefinitions().getNames()) {
110                            definitions.put(definition, description.getDefinitions().get(definition));
111                        }
112                    }
113                    if (description.getErrors() != null) {
114                        for (String error : description.getErrors().getNames()) {
115                            errors.put(error, description.getErrors().get(error));
116                        }
117                    }
118                    if (description.getServices() != null) {
119                        for (String service : description.getServices().getNames()) {
120                            services.put(service, description.getServices().get(service));
121                        }
122                    }
123                    if (description.getPaths() != null) {
124                        for (String path : description.getPaths().getNames()) {
125                            paths.merge(path, description.getPaths().get(path));
126                        }
127                    }
128                } catch (RuntimeException re) {
129                    LOGGER.error(re.getMessage(), re.fillInStackTrace());
130                    throw re;
131                }
132            }
133        }
134        return createApi(definitions.build(), errors.build(), services.build(), paths.build());
135    }
136
137    @Override
138    public ApiDescription addApiInfo(ApiDescription api) {
139        return createApi(api.getDefinitions(), api.getErrors(), api.getServices(), api.getPaths());
140    }
141
142    private ApiDescription createApi(Definitions definitions, Errors errors, Services services, Paths paths) {
143        return apiDescription()
144                .definitions(definitions)
145                .errors(errors)
146                .services(services)
147                .paths(paths)
148                .id(this.id)
149                .version(this.version)
150                .description(this.description)
151                .build();
152    }
153
154    @Override
155    public ApiProducer<ApiDescription> newChildProducer(String idFragment) {
156        return new CrestApiProducer(id + idFragment, version);
157    }
158}