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}