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 2015 ForgeRock AS. 015 */ 016 017package org.forgerock.audit; 018 019import org.forgerock.audit.events.handlers.AuditEventHandler; 020import org.forgerock.json.resource.ActionRequest; 021import org.forgerock.json.resource.ActionResponse; 022import org.forgerock.json.resource.CreateRequest; 023import org.forgerock.json.resource.DeleteRequest; 024import org.forgerock.json.resource.PatchRequest; 025import org.forgerock.json.resource.QueryRequest; 026import org.forgerock.json.resource.QueryResourceHandler; 027import org.forgerock.json.resource.QueryResponse; 028import org.forgerock.json.resource.ReadRequest; 029import org.forgerock.json.resource.ResourceException; 030import org.forgerock.json.resource.ResourceResponse; 031import org.forgerock.json.resource.ServiceUnavailableException; 032import org.forgerock.json.resource.UpdateRequest; 033import org.forgerock.services.context.Context; 034import org.forgerock.util.Reject; 035import org.forgerock.util.annotations.VisibleForTesting; 036import org.forgerock.util.promise.Promise; 037 038import java.util.Set; 039import java.util.concurrent.locks.ReentrantReadWriteLock; 040 041/** 042 * AuditService proxy that allows products to implement threadsafe hot-swappable configuration updates. 043 * <p/> 044 * The proxied AuditService can be swapped by calling {@link #setDelegate(AuditService)}. 045 * <p/> 046 * Thread-safety is achieved by blocking proxied calls until the old AuditService has flushed all buffers 047 * and closed any open file or network connections. 048 */ 049public class AuditServiceProxy implements AuditService { 050 051 /** Parameter that may be used when using an action, to provide the name of the handler to use as a target. */ 052 public static final String ACTION_PARAM_TARGET_HANDLER = "handler"; 053 054 private final ReentrantReadWriteLock delegateLock; 055 private AuditService delegate; 056 057 /** 058 * Create a new {@code AuditServiceProxy}. 059 * 060 * @param delegate 061 * The {@code AuditService} that this object should proxy. 062 */ 063 public AuditServiceProxy(AuditService delegate) { 064 this(delegate, new ReentrantReadWriteLock()); 065 } 066 067 @VisibleForTesting 068 AuditServiceProxy(AuditService delegate, ReentrantReadWriteLock delegateLock) { 069 Reject.ifNull(delegate); 070 this.delegate = delegate; 071 this.delegateLock = delegateLock; 072 } 073 074 /** 075 * Sets the AuditService this object proxies. 076 * <p/> 077 * Thread-safety is achieved by blocking proxied calls until the old AuditService has flushed all buffers 078 * and closed any open file or network connections. 079 * 080 * @param newDelegate 081 * A new AuditService instance with updated configuration. 082 */ 083 public void setDelegate(AuditService newDelegate) throws ServiceUnavailableException { 084 Reject.ifNull(newDelegate); 085 obtainWriteLock(); 086 try { 087 final AuditService oldDelegate = this.delegate; 088 if (oldDelegate == newDelegate) { 089 return; 090 } 091 oldDelegate.shutdown(); 092 newDelegate.startup(); 093 this.delegate = newDelegate; 094 } finally { 095 releaseWriteLock(); 096 } 097 } 098 099 @Override 100 public Promise<ResourceResponse, ResourceException> handleRead(Context context, ReadRequest request) { 101 obtainReadLock(); 102 try { 103 return delegate.handleRead(context, request); 104 } finally { 105 releaseReadLock(); 106 } 107 } 108 109 @Override 110 public Promise<ResourceResponse, ResourceException> handleCreate(Context context, CreateRequest request) { 111 obtainReadLock(); 112 try { 113 return delegate.handleCreate(context, request); 114 } finally { 115 releaseReadLock(); 116 } 117 } 118 119 @Override 120 public Promise<ResourceResponse, ResourceException> handleUpdate(Context context, UpdateRequest request) { 121 obtainReadLock(); 122 try { 123 return delegate.handleUpdate(context, request); 124 } finally { 125 releaseReadLock(); 126 } 127 } 128 129 @Override 130 public Promise<ResourceResponse, ResourceException> handleDelete(Context context, DeleteRequest request) { 131 obtainReadLock(); 132 try { 133 return delegate.handleDelete(context, request); 134 } finally { 135 releaseReadLock(); 136 } 137 } 138 139 @Override 140 public Promise<ResourceResponse, ResourceException> handlePatch(Context context, PatchRequest request) { 141 obtainReadLock(); 142 try { 143 return delegate.handlePatch(context, request); 144 } finally { 145 releaseReadLock(); 146 } 147 } 148 149 @Override 150 public Promise<QueryResponse, ResourceException> handleQuery( 151 Context context, QueryRequest request, QueryResourceHandler handler) { 152 obtainReadLock(); 153 try { 154 return delegate.handleQuery(context, request, handler); 155 } finally { 156 releaseReadLock(); 157 } 158 } 159 160 @Override 161 public Promise<ActionResponse, ResourceException> handleAction(Context context, ActionRequest request) { 162 obtainReadLock(); 163 try { 164 return delegate.handleAction(context, request); 165 } finally { 166 releaseReadLock(); 167 } 168 } 169 170 @Override 171 public AuditServiceConfiguration getConfig() throws ServiceUnavailableException { 172 obtainReadLock(); 173 try { 174 return delegate.getConfig(); 175 } finally { 176 releaseReadLock(); 177 } 178 } 179 180 @Override 181 public AuditEventHandler getRegisteredHandler(String handlerName) throws ServiceUnavailableException { 182 obtainReadLock(); 183 try { 184 return delegate.getRegisteredHandler(handlerName); 185 } finally { 186 releaseReadLock(); 187 } 188 } 189 190 @Override 191 public boolean isAuditing(String topic) throws ServiceUnavailableException { 192 obtainReadLock(); 193 try { 194 return delegate.isAuditing(topic); 195 } finally { 196 releaseReadLock(); 197 } 198 } 199 200 @Override 201 public Set<String> getKnownTopics() throws ServiceUnavailableException { 202 obtainReadLock(); 203 try { 204 return delegate.getKnownTopics(); 205 } finally { 206 releaseReadLock(); 207 } 208 } 209 210 @Override 211 public void shutdown() { 212 obtainWriteLock(); 213 try { 214 delegate.shutdown(); 215 } finally { 216 releaseWriteLock(); 217 } 218 } 219 220 @Override 221 public void startup() throws ServiceUnavailableException { 222 obtainWriteLock(); 223 try { 224 delegate.startup(); 225 } finally { 226 releaseWriteLock(); 227 } 228 } 229 230 @Override 231 public boolean isRunning() { 232 obtainReadLock(); 233 try { 234 return delegate.isRunning(); 235 } finally { 236 releaseReadLock(); 237 } 238 } 239 240 /** 241 * Obtain the read lock or block until it becomes available. 242 * 243 * @throws IllegalStateException If the current thread already holds the write lock. 244 */ 245 protected final void obtainReadLock() { 246 delegateLock.readLock().lock(); 247 if (delegateLock.isWriteLockedByCurrentThread()) { 248 throw new IllegalStateException( 249 "AuditServiceProxy should not be called from delegate shutdown or startup operations"); 250 } 251 } 252 253 /** 254 * Release the read lock. 255 */ 256 protected final void releaseReadLock() { 257 delegateLock.readLock().unlock(); 258 } 259 260 /** 261 * Obtain the write lock or block until it becomes available. 262 */ 263 protected final void obtainWriteLock() { 264 delegateLock.writeLock().lock(); 265 } 266 267 /** 268 * Release the write lock. 269 */ 270 protected final void releaseWriteLock() { 271 delegateLock.writeLock().unlock(); 272 } 273}