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