View Javadoc
1   /*
2    * The contents of this file are subject to the terms of the Common Development and
3    * Distribution License (the License). You may not use this file except in compliance with the
4    * License.
5    *
6    * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
7    * specific language governing permission and limitations under the License.
8    *
9    * When distributing Covered Software, include this CDDL Header Notice in each file and include
10   * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
11   * Header, with the fields enclosed by brackets [] replaced by your own identifying
12   * information: "Portions copyright [year] [name of copyright owner]".
13   *
14   * Copyright 2015-2016 ForgeRock AS.
15   * Portions Copyright 2026 Wren Security
16   */
17  package org.forgerock.json.resource.http.examples;
18  
19  import static org.forgerock.http.handler.Handlers.chainOf;
20  import static org.forgerock.http.routing.RoutingMode.STARTS_WITH;
21  import static org.forgerock.json.resource.Applications.simpleCrestApplication;
22  import static org.forgerock.json.resource.Requests.newApiRequest;
23  import static org.forgerock.json.resource.Resources.newHandler;
24  import static org.forgerock.json.resource.Resources.newInternalConnectionFactory;
25  import static org.forgerock.json.resource.RouteMatchers.requestUriMatcher;
26  import static org.forgerock.json.resource.http.CrestHttp.newHttpHandler;
27  
28  import io.swagger.v3.oas.models.OpenAPI;
29  import io.swagger.v3.oas.models.info.Info;
30  import org.asciidoctor.Asciidoctor;
31  import org.asciidoctor.Attributes;
32  import org.asciidoctor.Options;
33  import org.asciidoctor.Placement;
34  import org.asciidoctor.SafeMode;
35  import org.forgerock.api.markup.ApiDocGenerator;
36  import org.forgerock.api.models.ApiDescription;
37  import org.forgerock.http.ApiProducer;
38  import org.forgerock.http.DescribedHttpApplication;
39  import org.forgerock.http.Handler;
40  import org.forgerock.http.HttpApplicationException;
41  import org.forgerock.http.example.DescribedOauth2Endpoint;
42  import org.forgerock.http.header.ContentTypeHeader;
43  import org.forgerock.http.io.Buffer;
44  import org.forgerock.http.protocol.Request;
45  import org.forgerock.http.protocol.Response;
46  import org.forgerock.http.protocol.Status;
47  import org.forgerock.http.routing.RouteMatchers;
48  import org.forgerock.http.routing.RoutingMode;
49  import org.forgerock.http.swagger.OpenApiRequestFilter;
50  import org.forgerock.http.swagger.SwaggerApiProducer;
51  import org.forgerock.http.util.Uris;
52  import org.forgerock.json.resource.MemoryBackend;
53  import org.forgerock.json.resource.ResourcePath;
54  import org.forgerock.json.resource.Router;
55  import org.forgerock.json.resource.descriptor.examples.handler.UserCollectionHandler;
56  import org.forgerock.services.context.Context;
57  import org.forgerock.services.routing.DelegatingRouteMatcher;
58  import org.forgerock.services.routing.RouteMatch;
59  import org.forgerock.util.Factory;
60  import org.forgerock.util.promise.NeverThrowsException;
61  import org.forgerock.util.promise.Promise;
62  
63  /**
64   * Http Application implementation to demonstrate integration with the Commons HTTP Framework.
65   */
66  public class CrestHttpApplication implements DescribedHttpApplication {
67  
68      private static final String SWAGGER_JSON_ROUTE = "../..?_api";
69  
70      private static final ContentTypeHeader HTML_CONTENT_TYPE_HEADER =
71              ContentTypeHeader.valueOf("text/html; charset=UTF-8");
72  
73      @Override
74      public Handler start() throws HttpApplicationException {
75          final Asciidoctor asciidoctor = Asciidoctor.Factory.create();
76  
77          final Router crestRouter = new Router();
78          crestRouter.addRoute(requestUriMatcher(STARTS_WITH, "/users"), newHandler(new MemoryBackend()));
79          crestRouter.addRoute(requestUriMatcher(STARTS_WITH, "/groups"), newHandler(new MemoryBackend()));
80          crestRouter.addRoute(requestUriMatcher(STARTS_WITH, "/api/users"), UserCollectionHandler.getUsersRouter());
81          crestRouter.addRoute(requestUriMatcher(STARTS_WITH, "/api/admins"), UserCollectionHandler.getAdminsRouter());
82  
83          Handler crestHandler = newHttpHandler(simpleCrestApplication(newInternalConnectionFactory(crestRouter),
84                  "frapi:example", "1.0"));
85  
86          final org.forgerock.http.routing.Router router = new org.forgerock.http.routing.Router();
87          router.setDefaultRoute(crestHandler);
88          router.addRoute(RouteMatchers.requestUriMatcher(RoutingMode.STARTS_WITH, "/chf/oauth2"),
89                  new DescribedOauth2Endpoint());
90  
91          // convert ApiDescription to HTML documentation
92          router.addRoute(RouteMatchers.requestUriMatcher(RoutingMode.STARTS_WITH, "/docs/html"),
93                  new Handler() {
94                      @Override
95                      public Promise<Response, NeverThrowsException> handle(Context context, Request request) {
96                          ApiDescription apiDescription = crestRouter.handleApiRequest(context,
97                                  newApiRequest(ResourcePath.empty()));
98                          final String asciiDocMarkup = ApiDocGenerator.execute("Users and Devices API", apiDescription,
99                                  null);
100 
101                         final String html;
102                         synchronized (asciidoctor) {
103                             html = asciidoctor.convert(asciiDocMarkup,
104                                     Options.builder()
105                                             .attributes(Attributes.builder()
106                                                     .tableOfContents(Placement.LEFT)
107                                                     .sectNumLevels(5)
108                                                     .attribute("toclevels", 5)
109                                                     .build())
110                                             .safe(SafeMode.SAFE)
111                                             .standalone(true)
112                                             .build());
113                         }
114 
115                         final Response response = new Response(Status.OK);
116                         response.getHeaders().add(HTML_CONTENT_TYPE_HEADER);
117                         response.setEntity(html);
118                         return Response.newResponsePromise(response);
119                     }
120                 });
121 
122         // redirect to Swagger UI page, given a URL parameter to point to the Swagger JSON endpoint
123         router.addRoute(RouteMatchers.requestUriMatcher(RoutingMode.EQUALS, "/docs/api"),
124                 new Handler() {
125                     @Override
126                     public Promise<Response, NeverThrowsException> handle(Context context, Request request) {
127                         final String uri = request.getUri().toString();
128                         final String baseUrl = uri.substring(0, uri.indexOf("/docs/api"));
129                         final String url = baseUrl + "/openapi/index.html?url="
130                                 + Uris.urlEncodeQueryParameterNameOrValue(SWAGGER_JSON_ROUTE)
131                                 + "&title=" + Uris.urlEncodeQueryParameterNameOrValue("Users and Devices API");
132 
133                         final Response response = new Response(Status.FOUND);
134                         response.getHeaders().add("Location", url);
135                         return Response.newResponsePromise(response);
136                     }
137                 });
138 
139         // simple page providing links to HTML docs and Swagger UI
140         router.addRoute(new DelegatingRouteMatcher<Request>(RouteMatchers.requestUriMatcher(RoutingMode.EQUALS, "/")) {
141             @Override
142             public RouteMatch evaluate(Context context, Request request) {
143                 if (request.getForm().containsKey("_crestapi")) {
144                     return null;
145                 }
146                 return super.evaluate(context, request);
147             }
148         }, new Handler() {
149             @Override
150             public Promise<Response, NeverThrowsException> handle(Context context, Request request) {
151                 final String html = "<!DOCTYPE html><html><head><title>CREST Examples</title></head><body>"
152                         + "<p><a href=\"?_api\">Users and Devices API OpenAPI JSON</a></p>"
153                         + "<p><a href=\"?_crestapi\">Users and Devices API CREST Descriptor JSON</a></p>"
154                         + "<p><a href=\"./docs/api\">Users and Devices API explorer</a></p>"
155                         + "<p><a href=\"./docs/html\">Users and Devices API documentation</a></p>"
156                         + "</body></html>";
157 
158                 final Response response = new Response(Status.OK);
159                 response.getHeaders().add(HTML_CONTENT_TYPE_HEADER);
160                 response.setEntity(html);
161                 return Response.newResponsePromise(response);
162             }
163         });
164         return chainOf(router, new OpenApiRequestFilter());
165     }
166 
167     @Override
168     public Factory<Buffer> getBufferFactory() {
169         return null;
170     }
171 
172     @Override
173     public void stop() {
174         // empty
175     }
176 
177     @Override
178     public ApiProducer<OpenAPI> getApiProducer() {
179         return new SwaggerApiProducer(new Info().title("CREST Examples"), null, null);
180     }
181 }