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