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}