1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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 }