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