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 2010-2011 ApexIdentity Inc.
015 * Portions Copyright 2011-2015 ForgeRock AS.
016 */
017
018package org.forgerock.openig.handler;
019
020import static org.forgerock.openig.util.JsonValues.asExpression;
021
022import java.net.URI;
023import java.util.ArrayList;
024import java.util.List;
025import java.util.Map;
026
027import org.forgerock.http.Handler;
028import org.forgerock.http.protocol.Request;
029import org.forgerock.http.protocol.Response;
030import org.forgerock.json.JsonValue;
031import org.forgerock.openig.el.Bindings;
032import org.forgerock.openig.el.Expression;
033import org.forgerock.openig.heap.GenericHeapObject;
034import org.forgerock.openig.heap.GenericHeaplet;
035import org.forgerock.openig.heap.HeapException;
036import org.forgerock.openig.http.Responses;
037import org.forgerock.services.context.Context;
038import org.forgerock.util.promise.NeverThrowsException;
039import org.forgerock.util.promise.Promise;
040import org.forgerock.util.promise.Promises;
041
042/**
043 * Dispatches to one of a list of handlers. When a request is handled, each handler's
044 * condition is evaluated. If a condition expression yields {@code true}, then the request
045 * is dispatched to the associated handler with no further processing.
046 * <p>
047 * If no condition yields {@code true} then the handler will return a {@literal 404} not found response.
048 * Therefore, it's advisable to have a single "default" handler at the end of the list
049 * with no condition (unconditional) to handle otherwise un-dispatched requests.
050 */
051public class DispatchHandler extends GenericHeapObject implements Handler {
052
053    /** Expressions to evaluate against request and context, bound to handlers to dispatch to. */
054    private final List<Binding> bindings = new ArrayList<>();
055
056    /**
057     * Binds an expression to the current handler to dispatch to.
058     *
059     * @param condition
060     *            Condition to evaluate to determine if associated handler should be dispatched to. If omitted, then
061     *            dispatch is unconditional.
062     * @param handler
063     *            The name of the handler heap object to dispatch to if the associated condition yields true.
064     * @param baseURI
065     *            Overrides the existing request URI, making requests relative to a new base URI. Only scheme, host and
066     *            port are used in the supplied URI. Default: leave URI untouched.
067     * @return The current dispatch handler.
068     */
069    public DispatchHandler addBinding(Expression<Boolean> condition, Handler handler, URI baseURI) {
070        bindings.add(new Binding(condition, handler, baseURI));
071        return this;
072    }
073
074    /**
075     * Adds an unconditional bindings to the handler.
076     *
077     * @param handler
078     *            The name of the handler heap object to dispatch to if the associated condition yields true.
079     * @param baseURI
080     *            Overrides the existing request URI, making requests relative to a new base URI. Only scheme, host and
081     *            port are used in the supplied URI. Default: leave URI untouched.
082     * @return The current dispatch handler.
083     */
084    public DispatchHandler addUnconditionalBinding(Handler handler, URI baseURI) {
085        bindings.add(new Binding(null, handler, baseURI));
086        return this;
087    }
088
089    @Override
090    public Promise<Response, NeverThrowsException> handle(final Context context, final Request request) {
091        for (Binding binding : bindings) {
092            if (binding.condition == null
093                    || Boolean.TRUE.equals(binding.condition.eval(Bindings.bindings(context, request)))) {
094                if (binding.baseURI != null) {
095                    request.getUri().rebase(binding.baseURI);
096                }
097                return binding.handler.handle(context, request);
098            }
099        }
100        return Promises.newResultPromise(Responses.newNotFound("no handler to dispatch to"));
101    }
102
103    /** Binds an expression with a handler to dispatch to. */
104    private static class Binding {
105
106        /** Condition to dispatch to handler or {@code null} if unconditional. */
107        private Expression<Boolean> condition;
108
109        /** Handler to dispatch to. */
110        private Handler handler;
111
112        /** Overrides scheme/host/port of the request with a base URI. */
113        private URI baseURI;
114
115        /**
116         * Constructor.
117         *
118         * @param condition
119         *            Condition to evaluate to determine if associated handler should be dispatched to. If omitted, then
120         *            dispatch is unconditional.
121         * @param handler
122         *            The name of the handler heap object to dispatch to if the associated condition yields true.
123         * @param baseURI
124         *            Overrides the existing request URI, making requests relative to a new base URI. Only scheme, host
125         *            and port are used in the supplied URI. Default: leave URI untouched.
126         */
127        public Binding(Expression<Boolean> condition, Handler handler, URI baseURI) {
128            super();
129            this.condition = condition;
130            this.handler = handler;
131            this.baseURI = baseURI;
132        }
133    }
134
135    /**
136     * Creates and initializes a dispatch handler in a heap environment.
137     */
138    public static class Heaplet extends GenericHeaplet {
139        @Override
140        public Object create() throws HeapException {
141            DispatchHandler dispatchHandler = new DispatchHandler();
142            for (JsonValue jv : config.get("bindings").expect(List.class)) {
143                jv.required().expect(Map.class);
144                final Expression<Boolean> expression = asExpression(jv.get("condition"), Boolean.class);
145                final Handler handler = heap.resolve(jv.get("handler"), Handler.class);
146                final URI uri = jv.get("baseURI").asURI();
147                dispatchHandler.addBinding(expression, handler, uri);
148            }
149            return dispatchHandler;
150        }
151    }
152}