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.io.IOException; 024import java.io.Reader; 025import java.nio.charset.Charset; 026import java.util.HashMap; 027import java.util.List; 028import java.util.Map; 029 030import org.forgerock.http.Filter; 031import org.forgerock.http.Handler; 032import org.forgerock.http.protocol.Message; 033import org.forgerock.http.protocol.Request; 034import org.forgerock.http.protocol.Response; 035import org.forgerock.json.JsonValue; 036import org.forgerock.json.JsonValueException; 037import org.forgerock.openig.el.Bindings; 038import org.forgerock.openig.el.Expression; 039import org.forgerock.openig.heap.GenericHeapObject; 040import org.forgerock.openig.heap.GenericHeaplet; 041import org.forgerock.openig.heap.HeapException; 042import org.forgerock.openig.regex.PatternTemplate; 043import org.forgerock.openig.regex.StreamPatternExtractor; 044import org.forgerock.openig.util.MessageType; 045import org.forgerock.services.context.Context; 046import org.forgerock.util.promise.NeverThrowsException; 047import org.forgerock.util.promise.Promise; 048import org.forgerock.util.promise.ResultHandler; 049 050/** 051 * Extracts regular expression patterns from a message entity. Extraction occurs either 052 * before the request is handled if {@code messageType} is {@link MessageType#REQUEST}, or 053 * after the request is handled if it is {@link MessageType#RESPONSE}. Each pattern can have 054 * an associated template, which is applied to its match result. 055 * <p> 056 * The extraction results are contained in a {@link Map} object, whose location is specified 057 * by the {@code target} expression. For a given matched pattern, the value stored in the map 058 * is either the result of applying its associated pattern template (if specified) or the 059 * match result itself otherwise. 060 * 061 * @see StreamPatternExtractor 062 * @see PatternTemplate 063 */ 064public class EntityExtractFilter extends GenericHeapObject implements Filter { 065 066 /** Extracts regular expression patterns from entities. */ 067 private final StreamPatternExtractor extractor = new StreamPatternExtractor(); 068 069 /** The message type to extract patterns from. */ 070 private final MessageType messageType; 071 072 /** Overrides the character set encoding specified in message. If {@code null}, the message encoding is used. */ 073 private final Charset charset; 074 075 /** Expression that yields the target object that will contain the mapped extraction results. */ 076 private final Expression<?> target; 077 078 /** 079 * Builds an EntityExtractFilter that will act either on {@link MessageType#REQUEST} or {@link MessageType#RESPONSE} 080 * flow, extracting patterns into the given {@code target} {@link Expression}. The {@link Charset} used is the one 081 * of the message. 082 * 083 * @param type 084 * Specifies the execution flow to be executed in 085 * @param target 086 * Expression that yields the target object that will contain the mapped extraction results 087 */ 088 public EntityExtractFilter(final MessageType type, final Expression<?> target) { 089 this(type, target, null); 090 } 091 092 /** 093 * Builds an EntityExtractFilter that will act either on {@link MessageType#REQUEST} or {@link MessageType#RESPONSE} 094 * flow, extracting patterns into the given {@code target} {@link Expression}. The {@link Charset} used is the one 095 * specified. 096 * 097 * @param type 098 * Specifies the execution flow to be executed in 099 * @param target 100 * Expression that yields the target object that will contain the mapped extraction results 101 * @param charset 102 * Overrides the character set encoding specified in message. If {@code null}, the message encoding is used 103 */ 104 public EntityExtractFilter(final MessageType type, final Expression<?> target, final Charset charset) { 105 this.messageType = type; 106 this.target = target; 107 this.charset = charset; 108 } 109 110 /** 111 * Returns the regular expression patterns extractor. 112 * @return the regular expression patterns extractor. 113 */ 114 public StreamPatternExtractor getExtractor() { 115 return extractor; 116 } 117 118 private void process(Bindings bindings, Message message) { 119 HashMap<String, String> map = new HashMap<>(); 120 if (message != null) { 121 try { 122 try (Reader reader = message.getEntity().newDecodedContentReader(charset)) { 123 // get 'em all now 124 for (Map.Entry<String, String> match : extractor.extract(reader)) { 125 map.put(match.getKey(), match.getValue()); 126 } 127 } 128 } catch (IOException ioe) { 129 // may yield partial or unresolved attributes 130 } 131 } 132 target.set(bindings, map); 133 } 134 135 @Override 136 public Promise<Response, NeverThrowsException> filter(final Context context, 137 final Request request, 138 final Handler next) { 139 140 if (messageType == MessageType.REQUEST) { 141 process(bindings(context, request), request); 142 } 143 Promise<Response, NeverThrowsException> promise = next.handle(context, request); 144 if (messageType == MessageType.RESPONSE) { 145 return promise.thenOnResult(new ResultHandler<Response>() { 146 @Override 147 public void handleResult(final Response response) { 148 process(bindings(context, request, response), response); 149 } 150 }); 151 } 152 return promise; 153 } 154 155 /** Creates and initializes an entity extract handler in a heap environment. */ 156 public static class Heaplet extends GenericHeaplet { 157 @Override 158 public Object create() throws HeapException { 159 EntityExtractFilter filter = new EntityExtractFilter( 160 config.get("messageType").required().asEnum(MessageType.class), 161 asExpression(config.get("target").required(), Object.class), 162 config.get("charset").asCharset()); 163 164 for (JsonValue jv : config.get("bindings").required().expect(List.class)) { 165 jv.required().expect(Map.class); 166 String key = jv.get("key").required().asString(); 167 if (filter.extractor.getPatterns().containsKey(key)) { 168 throw new JsonValueException(jv.get("key"), "Key already defined"); 169 } 170 filter.extractor.getPatterns().put(key, jv.get("pattern").required().asPattern()); 171 String template = jv.get("template").asString(); 172 if (template != null) { 173 filter.extractor.getTemplates().put(key, new PatternTemplate(template)); 174 } 175 } 176 return filter; 177 } 178 } 179}