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; 025import java.util.Map; 026 027import org.forgerock.http.Filter; 028import org.forgerock.http.Handler; 029import org.forgerock.http.protocol.Request; 030import org.forgerock.http.protocol.Response; 031import org.forgerock.json.JsonValue; 032import org.forgerock.openig.el.Bindings; 033import org.forgerock.openig.el.Expression; 034import org.forgerock.openig.heap.GenericHeapObject; 035import org.forgerock.openig.heap.GenericHeaplet; 036import org.forgerock.openig.heap.HeapException; 037import org.forgerock.services.context.Context; 038import org.forgerock.util.AsyncFunction; 039import org.forgerock.util.promise.NeverThrowsException; 040import org.forgerock.util.promise.Promise; 041import org.forgerock.util.promise.Promises; 042 043/** 044 * Conditionally diverts the request to another handler. Before and after the request is 045 * handled, associated conditions are evaluated. If a condition evaluates to {@code true}, then 046 * the processing flow is diverted to the associated handler. If no condition evaluates to 047 * {@code true}, then the request flows normally through the filter. 048 */ 049public class SwitchFilter extends GenericHeapObject implements Filter { 050 051 /** Associates a condition with a handler to divert to if the condition yields {@code true}. */ 052 private static class Case { 053 /** Condition to evaluate if request should be diverted to handler. */ 054 private final Expression<Boolean> condition; 055 056 /** Handler to divert to if condition yields {@code true}. */ 057 private final Handler handler; 058 059 /** 060 * Build a switch case from a condition and the handler to execute if condition yields. 061 * @param condition expression to evaluate 062 * @param handler handler to be executed if the condition yields 063 */ 064 public Case(final Expression<Boolean> condition, final Handler handler) { 065 this.condition = condition; 066 this.handler = handler; 067 } 068 } 069 070 /** Switch cases to test before the request is handled. */ 071 private final List<Case> requestCases = new ArrayList<>(); 072 073 /** Switch cases to test after the request is handled. */ 074 private final List<Case> responseCases = new ArrayList<>(); 075 076 /** 077 * Add a request switch case with a condition and the handler to execute if condition yields. 078 * @param condition expression to evaluate 079 * @param handler handler to be executed if the condition yields 080 * @return this filter for fluent invocation. 081 */ 082 public SwitchFilter addRequestCase(final Expression<Boolean> condition, final Handler handler) { 083 requestCases.add(new Case(condition, handler)); 084 return this; 085 } 086 087 /** 088 * Add a response switch case with a condition and the handler to execute if condition yields. 089 * @param condition expression to evaluate 090 * @param handler handler to be executed if the condition yields 091 * @return this filter for fluent invocation. 092 */ 093 public SwitchFilter addResponseCase(final Expression<Boolean> condition, final Handler handler) { 094 responseCases.add(new Case(condition, handler)); 095 return this; 096 } 097 098 @Override 099 public Promise<Response, NeverThrowsException> filter(final Context context, 100 final Request request, 101 final Handler next) { 102 // Switch on the request flow 103 Promise<Response, NeverThrowsException> promise = doSwitch(bindings(context, request), 104 context, 105 request, 106 requestCases); 107 if (promise != null) { 108 return promise; 109 } 110 // not intercepted on request 111 // Invoke next filter in chain and try switching on the response flow 112 return next.handle(context, request) 113 .thenAsync(new AsyncFunction<Response, Response, NeverThrowsException>() { 114 @Override 115 public Promise<Response, NeverThrowsException> apply(final Response value) { 116 Promise<Response, NeverThrowsException> promise = doSwitch(bindings(context, 117 request, 118 value), 119 context, 120 request, 121 responseCases); 122 // not intercepted on response, just return the original response 123 if (promise == null) { 124 promise = Promises.newResultPromise(value); 125 } 126 return promise; 127 } 128 }); 129 } 130 131 private Promise<Response, NeverThrowsException> doSwitch(Bindings bindings, 132 Context context, 133 Request request, 134 List<Case> cases) { 135 for (Case c : cases) { 136 if (c.condition == null || Boolean.TRUE.equals(c.condition.eval(bindings))) { 137 // switched flow 138 return c.handler.handle(context, request); 139 } 140 } 141 // no interception 142 return null; 143 } 144 145 /** 146 * Creates and initializes an expect filter in a heap environment. 147 */ 148 public static class Heaplet extends GenericHeaplet { 149 @Override 150 public Object create() throws HeapException { 151 SwitchFilter result = new SwitchFilter(); 152 result.requestCases.addAll(asCases("onRequest")); 153 result.responseCases.addAll(asCases("onResponse")); 154 return result; 155 } 156 157 private List<Case> asCases(String name) throws HeapException { 158 ArrayList<Case> result = new ArrayList<>(); 159 JsonValue cases = config.get(name).expect(List.class); 160 for (JsonValue value : cases) { 161 result.add(asCase(value.required().expect(Map.class))); 162 } 163 return result; 164 } 165 166 private Case asCase(JsonValue value) throws HeapException { 167 return new Case(asExpression(value.get("condition"), Boolean.class), 168 heap.resolve(value.get("handler"), Handler.class)); 169 } 170 } 171}