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}