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}