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;
025
026import org.forgerock.json.fluent.JsonValue;
027import org.forgerock.openig.el.Expression;
028import org.forgerock.openig.handler.Handler;
029import org.forgerock.openig.handler.HandlerException;
030import org.forgerock.openig.heap.GenericHeaplet;
031import org.forgerock.openig.heap.HeapException;
032import org.forgerock.openig.http.Exchange;
033
034/**
035 * Conditionally assigns values to expressions before and after the exchange is handled.
036 */
037public class AssignmentFilter extends GenericFilter {
038
039    /** Defines assignment condition, target and value expressions. */
040    private static final class Binding {
041        /** Condition to evaluate to determine if assignment should occur, or {@code null} if assignment is
042         * unconditional. */
043        private Expression condition;
044        /** Expression that yields the target object whose value is to be set. */
045        private Expression target;
046        /** Expression that yields the value to be set in the target. */
047        private Expression value;
048
049        private Binding(final Expression condition, final Expression target, final Expression value) {
050            this.condition = condition;
051            this.target = target;
052            this.value = value;
053        }
054    }
055
056    /** Assignment bindings to apply before the request is handled. */
057    private final List<Binding> onRequest = new ArrayList<Binding>();
058
059    /** Assignment bindings to apply after the request is handled. */
060    private final List<Binding> onResponse = new ArrayList<Binding>();
061
062    /**
063     * Registers an unconditional (always executed) binding on the request flow. The value stored in the target will be
064     * {@literal null}.
065     *
066     * @param target
067     *         Expression that yields the target object whose value is to be set
068     * @return this object for fluent usage
069     */
070    public AssignmentFilter addRequestBinding(final Expression target) {
071        return this.addRequestBinding(target, null);
072    }
073
074    /**
075     * Registers an unconditional (always executed) binding on the request flow. The value stored in the target will be
076     * the result of the value {@link Expression}.
077     *
078     * @param target
079     *         Expression that yields the target object whose value is to be set
080     * @param value
081     *         Expression that yields the value to be set in the target (may be {@literal null})
082     * @return this object for fluent usage
083     */
084    public AssignmentFilter addRequestBinding(final Expression target, final Expression value) {
085        return this.addRequestBinding(null, target, value);
086    }
087
088    /**
089     * Registers a conditional binding on the request flow. If the condition is fulfilled, the value stored in the
090     * target will be the result of the value {@link Expression}.
091     *
092     * @param condition
093     *         Condition to evaluate to determine if assignment should occur (may be {@literal null}, aka
094     *         unconditional)
095     * @param target
096     *         Expression that yields the target object whose value is to be set
097     * @param value
098     *         Expression that yields the value to be set in the target (may be {@literal null})
099     * @return this object for fluent usage
100     */
101    public AssignmentFilter addRequestBinding(final Expression condition,
102                                              final Expression target,
103                                              final Expression value) {
104        this.onRequest.add(new Binding(condition, target, value));
105        return this;
106    }
107
108    /**
109     * Registers an unconditional (always executed) binding on the response flow. The value stored in the target will be
110     * {@literal null}.
111     *
112     * @param target
113     *         Expression that yields the target object whose value is to be set
114     * @return this object for fluent usage
115     */
116    public AssignmentFilter addResponseBinding(final Expression target) {
117        return this.addResponseBinding(target, null);
118    }
119
120    /**
121     * Registers an unconditional (always executed) binding on the response flow. The value stored in the target will be
122     * the result of the value {@link Expression}.
123     *
124     * @param target
125     *         Expression that yields the target object whose value is to be set
126     * @param value
127     *         Expression that yields the value to be set in the target (may be {@literal null})
128     * @return this object for fluent usage
129     */
130    public AssignmentFilter addResponseBinding(final Expression target, final Expression value) {
131        return this.addResponseBinding(null, target, value);
132    }
133
134    /**
135     * Registers a conditional binding on the response flow. If the condition is fulfilled, the value stored in the
136     * target will be the result of the value {@link Expression}.
137     *
138     * @param condition
139     *         Condition to evaluate to determine if assignment should occur (may be {@literal null}, aka
140     *         unconditional)
141     * @param target
142     *         Expression that yields the target object whose value is to be set
143     * @param value
144     *         Expression that yields the value to be set in the target (may be {@literal null})
145     * @return this object for fluent usage
146     */
147    public AssignmentFilter addResponseBinding(final Expression condition,
148                                               final Expression target,
149                                               final Expression value) {
150        this.onResponse.add(new Binding(condition, target, value));
151        return this;
152    }
153
154    @Override
155    public void filter(Exchange exchange, Handler next) throws HandlerException, IOException {
156        for (Binding binding : onRequest) {
157            eval(binding, exchange);
158        }
159        next.handle(exchange);
160        for (Binding binding : onResponse) {
161            eval(binding, exchange);
162        }
163    }
164
165    private void eval(Binding binding, Exchange exchange) {
166        if (binding.condition == null || Boolean.TRUE.equals(binding.condition.eval(exchange))) {
167            binding.target.set(exchange, binding.value != null ? binding.value.eval(exchange) : null);
168        }
169    }
170
171    /** Creates and initializes an assignment filter in a heap environment. */
172    public static class Heaplet extends GenericHeaplet {
173        @Override
174        public Object create() throws HeapException {
175            AssignmentFilter result = new AssignmentFilter();
176            addRequestBindings(result);
177            addResponseBindings(result);
178            return result;
179        }
180
181        private void addRequestBindings(final AssignmentFilter filter) {
182            // optional
183            JsonValue bindings = config.get("onRequest").expect(List.class);
184            for (JsonValue binding : bindings) {
185                Expression condition = asExpression(binding.get("condition"));
186                Expression target = asExpression(binding.get("target").required());
187                Expression value = asExpression(binding.get("value"));
188
189                filter.addRequestBinding(condition, target, value);
190            }
191        }
192
193        private void addResponseBindings(final AssignmentFilter filter) {
194            // optional
195            JsonValue bindings = config.get("onResponse").expect(List.class);
196            for (JsonValue binding : bindings) {
197                Expression condition = asExpression(binding.get("condition"));
198                Expression target = asExpression(binding.get("target").required());
199                Expression value = asExpression(binding.get("value"));
200
201                filter.addResponseBinding(condition, target, value);
202            }
203        }
204    }
205}