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.net.URISyntaxException; 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; 034import org.forgerock.openig.http.Form; 035import org.forgerock.openig.http.Request; 036import org.forgerock.openig.util.CaseInsensitiveMap; 037import org.forgerock.openig.util.MultiValueMap; 038 039/** 040 * Creates a new request with in the exchange object. It will replace any request that may 041 * already be present in the exchange. The request can include a form, specified in the 042 * {@code form} field, which is included in an entity encoded in 043 * {@code application/x-www-form-urlencoded} format if request method is {@code POST}, or 044 * otherwise as (additional) query parameters in the URI. 045 */ 046public class StaticRequestFilter extends GenericFilter { 047 048 /** 049 * By default, do not restore the original {@link Request} back into {@code exchange.request}. 050 */ 051 public static final boolean DEFAULT_RESTORE = false; 052 053 /** The HTTP method to be performed on the resource. */ 054 private final String method; 055 056 /** URI as an expression to allow dynamic URI construction. */ 057 private Expression uri; 058 059 /** Protocol version (e.g. {@code "HTTP/1.1"}). */ 060 private String version; 061 062 /** Restore the original Request after execution (defaults to {@literal false}). */ 063 private boolean restore = DEFAULT_RESTORE; 064 065 /** Message header fields whose values are expressions that are evaluated. */ 066 private final MultiValueMap<String, Expression> headers = 067 new MultiValueMap<String, Expression>(new CaseInsensitiveMap<List<Expression>>()); 068 069 /** A form to include in the request, whose values are exchange-scoped expressions that are evaluated. */ 070 private final MultiValueMap<String, Expression> form = 071 new MultiValueMap<String, Expression>(new CaseInsensitiveMap<List<Expression>>()); 072 073 /** 074 * Builds a new {@link StaticRequestFilter} that will uses the given HTTP method on the resource. 075 * 076 * @param method 077 * The HTTP method to be performed on the resource 078 */ 079 public StaticRequestFilter(final String method) { 080 this.method = method; 081 } 082 083 /** 084 * Sets the target URI as an expression to allow dynamic URI construction. 085 * 086 * @param uri 087 * target URI expression 088 */ 089 public void setUri(final Expression uri) { 090 this.uri = uri; 091 } 092 093 /** 094 * Sets the new request message's version. 095 * 096 * @param version 097 * Protocol version (e.g. {@code "HTTP/1.1"}). 098 */ 099 public void setVersion(final String version) { 100 this.version = version; 101 } 102 103 /** 104 * Sets to {@literal false} if this filter should not restore the original Request after execution. 105 * 106 * @param restore 107 * {@literal true} if restore is required, {@literal false} otherwise 108 */ 109 public void setRestore(final boolean restore) { 110 this.restore = restore; 111 } 112 113 /** 114 * Adds a new header value using the given {@code key} with the given {@link Expression}. As headers are 115 * multi-valued objects, it's perfectly legal to call this method multiple times with the same key. 116 * 117 * @param key 118 * Header name 119 * @param value 120 * {@link Expression} that represents the value of the new header 121 * @return this object for fluent usage 122 */ 123 public StaticRequestFilter addHeaderValue(final String key, final Expression value) { 124 headers.add(key, value); 125 return this; 126 } 127 128 /** 129 * Adds a new form parameter using the given {@code key} with the given {@link Expression}. As form parameters are 130 * multi-valued objects, it's perfectly legal to call this method multiple times with the same key. 131 * 132 * @param name 133 * Form parameter name 134 * @param value 135 * {@link Expression} that represents the value of the parameter 136 * @return this object for fluent usage 137 */ 138 public StaticRequestFilter addFormParameter(final String name, final Expression value) { 139 form.add(name, value); 140 return this; 141 } 142 143 @Override 144 public void filter(Exchange exchange, Handler next) throws HandlerException, IOException { 145 Request request = new Request(); 146 request.setMethod(this.method); 147 String value = this.uri.eval(exchange, String.class); 148 if (value != null) { 149 try { 150 request.setUri(value); 151 } catch (URISyntaxException e) { 152 throw logger.debug(new HandlerException("The URI " + value + " was not valid, " + e.getMessage(), e)); 153 } 154 } else { 155 throw logger.debug(new HandlerException("The URI expression evaluated to null")); 156 } 157 if (this.version != null) { 158 // default in Message class 159 request.setVersion(version); 160 } 161 for (String key : this.headers.keySet()) { 162 for (Expression expression : this.headers.get(key)) { 163 String eval = expression.eval(exchange, String.class); 164 if (eval != null) { 165 request.getHeaders().add(key, eval); 166 } 167 } 168 } 169 if (this.form != null && !this.form.isEmpty()) { 170 Form f = new Form(); 171 for (String key : this.form.keySet()) { 172 for (Expression expression : this.form.get(key)) { 173 String eval = expression.eval(exchange, String.class); 174 if (eval != null) { 175 f.add(key, eval); 176 } 177 } 178 } 179 if (request.getMethod().equals("POST")) { 180 f.toRequestEntity(request); 181 } else { 182 f.appendRequestQuery(request); 183 } 184 } 185 Request saved = exchange.request; 186 exchange.request = request; 187 next.handle(exchange); 188 if (restore) { 189 exchange.request = saved; 190 } 191 } 192 193 /** Creates and initializes a request filter in a heap environment. */ 194 public static class Heaplet extends GenericHeaplet { 195 @Override 196 public Object create() throws HeapException { 197 StaticRequestFilter filter = new StaticRequestFilter(config.get("method").required().asString()); 198 filter.setUri(asExpression(config.get("uri").required())); 199 filter.setVersion(config.get("version").asString()); 200 filter.setRestore(config.get("restore").defaultTo(DEFAULT_RESTORE).asBoolean()); 201 202 JsonValue headers = config.get("headers").expect(Map.class); 203 if (headers != null) { 204 for (String key : headers.keys()) { 205 for (JsonValue value : headers.get(key).required().expect(List.class)) { 206 filter.addHeaderValue(key, asExpression(value.required())); 207 } 208 } 209 } 210 JsonValue form = config.get("form").expect(Map.class); 211 if (form != null) { 212 for (String key : form.keys()) { 213 for (JsonValue value : form.get(key).required().expect(List.class)) { 214 filter.addFormParameter(key, asExpression(value.required())); 215 } 216 } 217 } 218 return filter; 219 } 220 } 221}