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