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 2015 ForgeRock AS.
015 */
016
017package org.forgerock.openig.http;
018
019import static org.forgerock.http.routing.RouteMatchers.requestUriMatcher;
020import static org.forgerock.http.routing.RoutingMode.STARTS_WITH;
021
022import org.forgerock.http.Handler;
023import org.forgerock.http.protocol.Request;
024import org.forgerock.http.routing.Router;
025import org.forgerock.openig.heap.GenericHeaplet;
026import org.forgerock.services.routing.RouteMatcher;
027
028/**
029 * Registry for OpenIG REST API endpoints.
030 *
031 * <p>Components can use that class to register additional endpoints into the {@literal /openig/api} namespace:
032 *
033 * <ul>
034 *     <li>{@literal /openig/api/system/objects/[heap-object-name]} for components defined in {@code config.json}</li>
035 *     <li>{@literal /openig/api/system/objects/.../[router-name]/routes/[route-name]/objects/[heap-object-name]} for
036 *     components defined inside routes</li>
037 * </ul>
038 *
039 * @see GenericHeaplet#endpointRegistry()
040 */
041public final class EndpointRegistry {
042    private final Router router;
043    private final String path;
044
045    /**
046     * Creates a registry around the given Router instance.
047     * Registered endpoints will be sub-elements of the given {@code router}.
048     *
049     * @param router base Router
050     * @param path path for this registry
051     */
052    public EndpointRegistry(final Router router, final String path) {
053        this.router = router;
054        this.path = path;
055    }
056
057    /**
058     * Registers a new endpoint under the given {@code name}.
059     *
060     * <p>Equivalent to calling this {@code Router} code:
061     * <pre>
062     *     {@code
063     *     router.addRoute(requestUriMatcher(STARTS_WITH, name), handler);
064     *     }
065     * </pre>
066     *
067     * @param name
068     *         registered endpoint name
069     * @param handler
070     *         endpoint implementation
071     * @return a handle for later endpoint un-registration
072     */
073    public Registration register(final String name, final Handler handler) {
074        RouteMatcher<Request> matcher = requestUriMatcher(STARTS_WITH, name);
075        router.addRoute(matcher, handler);
076        return new Registration(router, matcher, pathInfo(name));
077    }
078
079    /**
080     * Returns the path info computed by appending the given {@code name} to this registry's path.
081     *
082     * @param name
083     *         path element to append
084     * @return composed path
085     */
086    public String pathInfo(String name) {
087        StringBuilder sb = new StringBuilder(path);
088        if (!name.startsWith("/")) {
089            sb.append("/");
090        }
091        sb.append(name);
092        if (name.endsWith("/")) {
093            sb.deleteCharAt(sb.length() - 1);
094        }
095        return sb.toString();
096    }
097
098    /**
099     * Returns this registry's base path.
100     * @return this registry's base path.
101     */
102    public String getPath() {
103        return path;
104    }
105
106    /**
107     * Handle for un-registering an endpoint.
108     */
109    public static class Registration {
110        private final Router router;
111        private final RouteMatcher<Request> matcher;
112        private final String path;
113
114        Registration(final Router router, final RouteMatcher<Request> matcher, String path) {
115            this.router = router;
116            this.matcher = matcher;
117            this.path = path;
118        }
119
120        /**
121         * Un-register the endpoint.
122         */
123        public void unregister() {
124            router.removeRoute(matcher);
125        }
126
127        /**
128         * Returns this endpoint's path.
129         * @return this endpoint's path.
130         */
131        public String getPath() {
132            return path;
133        }
134    }
135
136}