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.filter;
019
020import static org.forgerock.openig.util.Json.*;
021
022import java.io.IOException;
023import java.util.ArrayList;
024import java.util.List;
025import java.util.Map;
026
027import org.forgerock.json.fluent.JsonValue;
028import org.forgerock.openig.el.Expression;
029import org.forgerock.openig.handler.Handler;
030import org.forgerock.openig.handler.HandlerException;
031import org.forgerock.openig.heap.GenericHeaplet;
032import org.forgerock.openig.heap.HeapException;
033import org.forgerock.openig.http.Exchange;
034
035/**
036 * Conditionally diverts the exchange to another handler. Before and after the exchange is
037 * handled, associated conditions are evaluated. If a condition evaluates to {@code true}, then
038 * the exchange flow is diverted to the associated handler. If no condition evaluates to
039 * {@code true}, then the exchange flows normally through the filter.
040 */
041public class SwitchFilter extends GenericFilter {
042
043    /** Associates a condition with a handler to divert to if the condition yields {@code true}. */
044    private static class Case {
045        /** Condition to evaluate if exchange should be diverted to handler. */
046        private final Expression condition;
047
048        /** Handler to divert to if condition yields {@code true}. */
049        private final Handler handler;
050
051        /**
052         * Build a switch case from a condition and the handler to execute if condition yields.
053         * @param condition expression to evaluate
054         * @param handler handler to be executed if the condition yields
055         */
056        public Case(final Expression condition, final Handler handler) {
057            this.condition = condition;
058            this.handler = handler;
059        }
060    }
061
062    /** Switch cases to test before the exchange is handled. */
063    private final List<Case> requestCases = new ArrayList<Case>();
064
065    /** Switch cases to test after the exchange is handled. */
066    private final List<Case> responseCases = new ArrayList<Case>();
067
068    /**
069     * Add a request switch case with a condition and the handler to execute if condition yields.
070     * @param condition expression to evaluate
071     * @param handler handler to be executed if the condition yields
072     * @return this filter for fluent invocation.
073     */
074    public SwitchFilter addRequestCase(final Expression condition, final Handler handler) {
075        requestCases.add(new Case(condition, handler));
076        return this;
077    }
078
079    /**
080     * Add a response switch case with a condition and the handler to execute if condition yields.
081     * @param condition expression to evaluate
082     * @param handler handler to be executed if the condition yields
083     * @return this filter for fluent invocation.
084     */
085    public SwitchFilter addResponseCase(final Expression condition, final Handler handler) {
086        responseCases.add(new Case(condition, handler));
087        return this;
088    }
089
090    @Override
091    public void filter(Exchange exchange, Handler next) throws HandlerException, IOException {
092        if (!doSwitch(exchange, requestCases)) {
093            // not intercepted
094            next.handle(exchange);
095            doSwitch(exchange, responseCases);
096        }
097    }
098
099    private boolean doSwitch(Exchange exchange, List<Case> cases) throws HandlerException, IOException {
100        for (Case c : cases) {
101            Object o = (c.condition != null ? c.condition.eval(exchange) : Boolean.TRUE);
102            if (o instanceof Boolean && ((Boolean) o)) {
103                c.handler.handle(exchange);
104                // switched flow
105                return true;
106            }
107        }
108        // no interception
109        return false;
110    }
111
112    /**
113     * Creates and initializes an expect filter in a heap environment.
114     */
115    public static class Heaplet extends GenericHeaplet {
116        @Override
117        public Object create() throws HeapException {
118            SwitchFilter result = new SwitchFilter();
119            result.requestCases.addAll(asCases("onRequest"));
120            result.responseCases.addAll(asCases("onResponse"));
121            return result;
122        }
123
124        private List<Case> asCases(String name) throws HeapException {
125            ArrayList<Case> result = new ArrayList<Case>();
126            JsonValue cases = config.get(name).expect(List.class);
127            for (JsonValue value : cases) {
128                result.add(asCase(value.required().expect(Map.class)));
129            }
130            return result;
131        }
132
133        private Case asCase(JsonValue value) throws HeapException {
134            return new Case(asExpression(value.get("condition")),
135                            heap.resolve(value.get("handler"), Handler.class));
136        }
137    }
138}