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