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}