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}