View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.syncope.core.rest.cxf;
20  
21  import io.swagger.v3.oas.integration.api.OpenAPIConfiguration;
22  import io.swagger.v3.oas.models.ExternalDocumentation;
23  import io.swagger.v3.oas.models.Operation;
24  import io.swagger.v3.oas.models.headers.Header;
25  import io.swagger.v3.oas.models.media.ArraySchema;
26  import io.swagger.v3.oas.models.media.Content;
27  import io.swagger.v3.oas.models.media.IntegerSchema;
28  import io.swagger.v3.oas.models.media.MediaType;
29  import io.swagger.v3.oas.models.media.Schema;
30  import io.swagger.v3.oas.models.media.StringSchema;
31  import io.swagger.v3.oas.models.parameters.HeaderParameter;
32  import io.swagger.v3.oas.models.parameters.Parameter;
33  import io.swagger.v3.oas.models.responses.ApiResponse;
34  import io.swagger.v3.oas.models.responses.ApiResponses;
35  import io.swagger.v3.oas.models.servers.Server;
36  import java.util.LinkedHashMap;
37  import java.util.List;
38  import java.util.Map;
39  import java.util.Optional;
40  import java.util.stream.Collectors;
41  import java.util.stream.Stream;
42  import javax.ws.rs.core.Response;
43  import org.apache.commons.lang3.StringUtils;
44  import org.apache.cxf.jaxrs.ext.MessageContext;
45  import org.apache.cxf.jaxrs.model.OperationResourceInfo;
46  import org.apache.cxf.jaxrs.openapi.OpenApiCustomizer;
47  import org.apache.cxf.jaxrs.utils.JAXRSUtils;
48  import org.apache.syncope.common.lib.SyncopeConstants;
49  import org.apache.syncope.common.lib.to.ErrorTO;
50  import org.apache.syncope.common.lib.types.ClientExceptionType;
51  import org.apache.syncope.common.rest.api.RESTHeaders;
52  import org.apache.syncope.core.persistence.api.DomainHolder;
53  
54  public class SyncopeOpenApiCustomizer extends OpenApiCustomizer {
55  
56      private final DomainHolder domainHolder;
57  
58      public SyncopeOpenApiCustomizer(final DomainHolder domainHolder) {
59          this.domainHolder = domainHolder;
60      }
61  
62      @Override
63      public OpenAPIConfiguration customize(final OpenAPIConfiguration configuration) {
64          super.customize(configuration);
65  
66          MessageContext ctx = JAXRSUtils.createContextValue(JAXRSUtils.getCurrentMessage(), null, MessageContext.class);
67  
68          String url = StringUtils.substringBeforeLast(ctx.getUriInfo().getRequestUri().getRawPath(), "/");
69          configuration.getOpenAPI().setServers(List.of(new Server().url(url)));
70  
71          return configuration;
72      }
73  
74      @Override
75      protected void addParameters(final List<Parameter> parameters) {
76          Optional<Parameter> domainHeaderParameter = parameters.stream().
77                  filter(p -> p instanceof HeaderParameter && RESTHeaders.DOMAIN.equals(p.getName())).findFirst();
78          if (domainHeaderParameter.isEmpty()) {
79              HeaderParameter parameter = new HeaderParameter();
80              parameter.setName(RESTHeaders.DOMAIN);
81              parameter.setRequired(true);
82  
83              ExternalDocumentation extDoc = new ExternalDocumentation();
84              extDoc.setDescription("Apache Syncope Reference Guide");
85              extDoc.setUrl("https://syncope.apache.org/docs/3.0/reference-guide.html#domains");
86  
87              Schema<String> schema = new Schema<>();
88              schema.setDescription("Domains are built to facilitate multitenancy.");
89              schema.setExternalDocs(extDoc);
90              schema.setEnum(domainHolder.getDomains().keySet().stream().sorted().collect(Collectors.toList()));
91              schema.setDefault(SyncopeConstants.MASTER_DOMAIN);
92              parameter.setSchema(schema);
93  
94              parameters.add(parameter);
95          }
96  
97          Optional<Parameter> delegatedByHeaderParameter = parameters.stream().
98                  filter(p -> p instanceof HeaderParameter && RESTHeaders.DELEGATED_BY.equals(p.getName())).findFirst();
99          if (delegatedByHeaderParameter.isEmpty()) {
100             HeaderParameter parameter = new HeaderParameter();
101             parameter.setName(RESTHeaders.DELEGATED_BY);
102             parameter.setRequired(false);
103 
104             ExternalDocumentation extDoc = new ExternalDocumentation();
105             extDoc.setDescription("Apache Syncope Reference Guide");
106             extDoc.setUrl("https://syncope.apache.org/docs/3.0/reference-guide.html#delegation");
107 
108             Schema<String> schema = new Schema<>();
109             schema.setDescription("Acton behalf of someone else");
110             schema.setExternalDocs(extDoc);
111             parameter.setSchema(schema);
112 
113             parameters.add(parameter);
114         }
115     }
116 
117     @Override
118     protected void customizeResponses(final Operation operation, final OperationResourceInfo ori) {
119         super.customizeResponses(operation, ori);
120 
121         ApiResponses responses = operation.getResponses();
122         if (responses == null) {
123             responses = new ApiResponses();
124             operation.setResponses(responses);
125         }
126 
127         ApiResponse defaultResponse = responses.getDefault();
128         if (defaultResponse != null) {
129             responses.remove(ApiResponses.DEFAULT);
130             responses.addApiResponse("200", defaultResponse);
131         }
132 
133         Map<String, Header> headers = new LinkedHashMap<>();
134         headers.put(
135                 RESTHeaders.ERROR_CODE,
136                 new Header().schema(new Schema<>().type("string")).description("Error code"));
137         headers.put(
138                 RESTHeaders.ERROR_INFO,
139                 new Header().schema(new Schema<>().type("string")).description("Error message(s)"));
140 
141         ErrorTO sampleError = new ErrorTO();
142         sampleError.setStatus(Response.Status.BAD_REQUEST.getStatusCode());
143         sampleError.setType(ClientExceptionType.InvalidEntity);
144         sampleError.getElements().add("error message");
145 
146         Schema<ErrorTO> errorSchema = new Schema<>();
147         errorSchema.example(sampleError).
148                 addProperty("status", new IntegerSchema().description("HTTP status code")).
149                 addProperty("type", new StringSchema().
150                         _enum(Stream.of(ClientExceptionType.values()).map(Enum::name).collect(Collectors.toList())).
151                         description("Error code")).
152                 addProperty("elements", new ArraySchema().type("string").description("Error message(s)"));
153 
154         Content content = new Content();
155         content.addMediaType(
156                 javax.ws.rs.core.MediaType.APPLICATION_JSON,
157                 new MediaType().schema(errorSchema));
158         content.addMediaType(
159                 RESTHeaders.APPLICATION_YAML,
160                 new MediaType().schema(errorSchema));
161         content.addMediaType(
162                 javax.ws.rs.core.MediaType.APPLICATION_XML,
163                 new MediaType().schema(errorSchema));
164 
165         responses.addApiResponse("400", new ApiResponse().
166                 description("An error occurred; HTTP status code can vary depending on the actual error: "
167                         + "400, 403, 404, 409, 412").
168                 headers(headers).
169                 content(content));
170     }
171 }