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 2014-2015 ForgeRock AS.
015 */
016
017package org.forgerock.openig.decoration.capture;
018
019import static java.lang.String.*;
020import static java.util.Arrays.*;
021import static org.forgerock.openig.decoration.helper.LazyReference.*;
022import static org.forgerock.openig.heap.Keys.LOGSINK_HEAP_KEY;
023import static org.forgerock.openig.util.JsonValues.*;
024
025import java.util.Set;
026import java.util.TreeSet;
027
028import org.forgerock.http.Filter;
029import org.forgerock.http.Handler;
030import org.forgerock.json.JsonValue;
031import org.forgerock.openig.decoration.Context;
032import org.forgerock.openig.decoration.Decorator;
033import org.forgerock.openig.decoration.helper.AbstractHandlerAndFilterDecorator;
034import org.forgerock.openig.decoration.helper.DecoratorHeaplet;
035import org.forgerock.openig.decoration.helper.LazyReference;
036import org.forgerock.openig.heap.Heap;
037import org.forgerock.openig.heap.HeapException;
038import org.forgerock.openig.heap.Name;
039import org.forgerock.openig.log.LogSink;
040import org.forgerock.openig.log.Logger;
041
042/**
043 * The capture decorator can decorates both {@link Filter} and {@link Handler} instances. It enables
044 * the user to see the messages coming in and out of the decorated object.
045 * <p>
046 * Multiple input/output can be intercepted:
047 * <ul>
048 *     <li>{@link CapturePoint#ALL}: Prints all of the messages</li>
049 *     <li>{@link CapturePoint#FILTERED_REQUEST}: Prints the outgoing request (Filter only)</li>
050 *     <li>{@link CapturePoint#FILTERED_RESPONSE}: Prints the outgoing response</li>
051 *     <li>{@link CapturePoint#REQUEST}: Prints incoming + outgoing request</li>
052 *     <li>{@link CapturePoint#RESPONSE}: Prints incoming + outgoing response</li>
053 * </ul>
054 * Notice that camel case is not important: {@literal all} equals {@literal ALL} and even {@literal AlL}.
055 *
056 * It has to be declared inside of the heap objects section:
057 * <pre>
058 *     {@code
059 *     {
060 *       "name": "capture",
061 *       "type": "CaptureDecorator",
062 *       "config": {
063 *           "captureEntity": false,
064 *           "captureContext": false
065 *       }
066 *     }
067 *     }
068 * </pre>
069 * The capture decorator can be configured to globally enable entity capture using the {@literal captureEntity}
070 * boolean attribute (default to {@code false}).
071 * To capture the context at the capture point as well, use the {@literal captureContext} boolean attribute
072 * (default to {@code false}), Note that {@literal captureExchange} is deprecated.
073 * The common {@literal logSink} attribute can be used to force message capture in a given sink. By default, messages
074 * are sent to the heap object defined LogSink.
075 * <p>
076 * To decorate a component, just add the decorator declaration next to the {@code config} element:
077 * <pre>
078 *     {@code
079 *     {
080 *       "type": "...",
081 *       "capture": [ "FILTERED_REQUEST", "RESPONSE" ],
082 *       "config": { ... }
083 *     }
084 *     }
085 * </pre>
086 *
087 * Notice that the attribute name in the decorated object <b>has to be</b> the same as the decorator
088 * heap object name ({@code capture} in our example).
089 *
090 * A default {@literal capture} decorator is automatically created when OpenIG starts. It can be overridden
091 * in the configuration files if default values are not satisfying.
092 */
093public class CaptureDecorator extends AbstractHandlerAndFilterDecorator {
094
095    private final LazyReference<LogSink> reference;
096    private final boolean captureEntity;
097    private final boolean captureContext;
098
099    /**
100     * Builds a new {@code capture} decorator with the given sink reference (possibly {@code null})
101     * printing (or not) the entity content.
102     * If the {@code sink} is specified (not {@code null}), every message intercepted by this decorator will be
103     * send to the provided sink.
104     *
105     *  @param reference
106     *         Log Sink reference for message capture (may be {@code null})
107     * @param captureEntity
108     *         {@code true} if the decorator needs to capture the entity, {@code false} otherwise
109     * @param captureContext
110     *         {@code true} if the decorator needs to capture the context,
111     *         {@code false} otherwise
112     */
113    public CaptureDecorator(final LazyReference<LogSink> reference,
114                            final boolean captureEntity,
115                            final boolean captureContext) {
116        this.reference = reference;
117        this.captureEntity = captureEntity;
118        this.captureContext = captureContext;
119    }
120
121    @Override
122    protected Filter decorateFilter(final Filter delegate, final JsonValue decoratorConfig, final Context context)
123            throws HeapException {
124        Set<CapturePoint> points = getCapturePoints(decoratorConfig);
125        if (!points.isEmpty()) {
126            // Only intercept if needed
127            return new CaptureFilter(delegate, buildMessageCapture(context), points);
128        }
129        return delegate;
130    }
131
132    @Override
133    protected Handler decorateHandler(final Handler delegate, final JsonValue decoratorConfig, final Context context)
134            throws HeapException {
135        Set<CapturePoint> points = getCapturePoints(decoratorConfig);
136        if (!points.isEmpty()) {
137            // Only intercept if needed
138            return new CaptureHandler(delegate, buildMessageCapture(context), points);
139        }
140        return delegate;
141    }
142
143    private Set<CapturePoint> getCapturePoints(final JsonValue decoratorConfig) throws HeapException {
144        Set<CapturePoint> modes = new TreeSet<>();
145        if (decoratorConfig.isNull()) {
146            throw new HeapException("Capture's decorator cannot be null");
147        }
148        if (decoratorConfig.isString()) {
149            // Single value
150            modes.add(decoratorConfig.asEnum(CapturePoint.class));
151        } else if (decoratorConfig.isList()) {
152            // Array values
153            if (!decoratorConfig.asList(ofEnum(CapturePoint.class)).contains(null)) {
154                modes.addAll(decoratorConfig.asList(ofEnum(CapturePoint.class)));
155            } else {
156                throw new HeapException("Capture's decorator cannot contain null value");
157            }
158        } else {
159            throw new HeapException(format("Invalid JSON configuration in '%s'. It should either be a simple String "
160                                           + " or an array of String", decoratorConfig.toString()));
161        }
162
163        // Sanity check
164        if (modes.contains(CapturePoint.ALL)) {
165            // Replace ALL by its implicitly referenced values
166            modes.addAll(asList(CapturePoint.values()));
167            modes.remove(CapturePoint.ALL);
168        }
169        return modes;
170    }
171
172    /**
173     * Builds a new MessageCapture dedicated for the heap object context.
174     *
175     * @param context
176     *         Context of the heap object
177     * @return a new MessageCapture dedicated for the heap object context.
178     * @throws HeapException
179     *         when no logSink can be resolved (very unlikely to happen).
180     */
181    private MessageCapture buildMessageCapture(final Context context) throws HeapException {
182        LogSink sink = (reference == null) ? null : reference.get();
183        if (sink == null) {
184            // Use the sink of the decorated component
185            Heap heap = context.getHeap();
186            sink = heap.resolve(context.getConfig().get("logSink").defaultTo(LOGSINK_HEAP_KEY), LogSink.class);
187        }
188        Name name = context.getName();
189        return new MessageCapture(new Logger(sink, name.decorated("Capture")), captureEntity, captureContext);
190    }
191
192    /**
193     * Creates and initializes a CaptureDecorator in a heap environment.
194     */
195    public static class Heaplet extends DecoratorHeaplet {
196        @Override
197        public Decorator create() throws HeapException {
198            LazyReference<LogSink> reference = newReference(heap,
199                                                            config.get("logSink"),
200                                                            LogSink.class,
201                                                            true);
202            boolean captureEntity = config.get("captureEntity").defaultTo(false).asBoolean();
203
204            // captureExchange is deprecated
205            boolean captureContext = false;
206            if (config.isDefined("captureExchange")) {
207                captureContext = config.get("captureExchange").asBoolean();
208            }
209            if (config.isDefined("captureContext")) {
210                captureContext = config.get("captureContext").asBoolean();
211            }
212            return new CaptureDecorator(reference, captureEntity, captureContext);
213        }
214    }
215}