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 static org.forgerock.audit.events.EventTopicsMetaDataBuilder.coreTopicSchemas;
020
021import org.forgerock.audit.events.EventTopicsMetaData;
022import org.forgerock.audit.events.handlers.AuditEventHandler;
023import org.forgerock.audit.events.handlers.AuditEventHandlerFactory;
024import org.forgerock.audit.events.handlers.DependencyProviderAuditEventHandlerFactory;
025import org.forgerock.audit.events.handlers.EventHandlerConfiguration;
026import org.forgerock.json.resource.ServiceUnavailableException;
027import org.forgerock.util.Reject;
028import org.forgerock.util.annotations.VisibleForTesting;
029import org.slf4j.Logger;
030import org.slf4j.LoggerFactory;
031
032import java.util.LinkedHashMap;
033import java.util.LinkedHashSet;
034import java.util.Map;
035import java.util.Set;
036
037/**
038 * Builder for AuditService.
039 */
040public final class AuditServiceBuilder {
041
042    private static final Logger logger = LoggerFactory.getLogger(AuditServiceBuilder.class);
043
044    private final AuditServiceFactory auditServiceFactory;
045    private AuditServiceConfiguration auditServiceConfiguration = new AuditServiceConfiguration();
046    private AuditEventHandlerFactory auditEventHandlerFactory =
047            new DependencyProviderAuditEventHandlerFactory(new DependencyProviderBase());
048    private Map<String, HandlerRegistration> handlerRegistrations = new LinkedHashMap<>();
049    private Set<AuditEventHandler> prebuiltHandlers = new LinkedHashSet<>();
050    private EventTopicsMetaData eventTopicsMetaData = coreTopicSchemas().build();
051
052    @VisibleForTesting
053    AuditServiceBuilder(AuditServiceFactory auditServiceFactory) {
054        this.auditServiceFactory = auditServiceFactory;
055    }
056
057    /**
058     * Factory method for new instances of this builder.
059     *
060     * @return A new instance of the AuditServiceBuilder.
061     */
062    public static AuditServiceBuilder newAuditService() {
063        return new AuditServiceBuilder(new AuditServiceFactory());
064    }
065
066    /**
067     * Sets the AuditServiceConfiguration that is to be passed to the AuditService.
068     * <p/>
069     * AuditServiceConfiguration embodies the configuration state that can be set by system administrators.
070     *
071     * @param auditServiceConfiguration
072     *          user-facing configuration that is to be applied to the AuditService.
073     * @return this builder for method-chaining.
074     */
075    public AuditServiceBuilder withConfiguration(AuditServiceConfiguration auditServiceConfiguration) {
076        Reject.ifNull(auditServiceConfiguration, "Audit service configuration cannot be null");
077        this.auditServiceConfiguration = auditServiceConfiguration;
078        return this;
079    }
080
081    public AuditServiceBuilder withEventTopicsMetaData(EventTopicsMetaData eventTopicsMetaData) {
082        Reject.ifNull(eventTopicsMetaData, "Audit service event topic meta-data cannot be null");
083        this.eventTopicsMetaData = eventTopicsMetaData;
084        return this;
085    }
086
087    /**
088     * Register the DependencyProvider, after which, an AuditEventHandler can be registered and
089     * receive this provider.  The dependency provider allows the handler to obtain resources or
090     * objects from the product which integrates the Audit Service.
091     *
092     * @param dependencyProvider
093     *            the DependencyProvider to register.
094     * @return this builder for method-chaining.
095     */
096    public AuditServiceBuilder withDependencyProvider(DependencyProvider dependencyProvider) {
097        Reject.ifNull(dependencyProvider, "Audit event handler DependencyProvider cannot be null");
098        this.auditEventHandlerFactory = new DependencyProviderAuditEventHandlerFactory(dependencyProvider);
099        return this;
100    }
101
102    /**
103     * Register factory for creating instances of {@link AuditEventHandler}.
104     *
105     * @param auditEventHandlerFactory
106     *            the AuditEventHandlerFactory to register.
107     * @return this builder for method-chaining.
108     */
109    public AuditServiceBuilder withAuditEventHandlerFactory(AuditEventHandlerFactory auditEventHandlerFactory) {
110        Reject.ifNull(auditEventHandlerFactory, "AuditEventHandlerFactory cannot be null");
111        this.auditEventHandlerFactory = auditEventHandlerFactory;
112        return this;
113    }
114
115    /**
116     * Register an AuditEventHandler. After that registration, that AuditEventHandler can be referred with the given
117     * name. This AuditEventHandler will only be notified about the events specified in the parameter events.
118     *
119     * @param clazz
120     *            the AuditEventHandler type to register.
121     * @param configuration
122     *            the handler configuration.
123     * @throws AuditException
124     *             if already asked to register a handler with the same name.
125     * @return this builder for method-chaining.
126     */
127    public AuditServiceBuilder withAuditEventHandler(
128            Class<? extends AuditEventHandler> clazz, EventHandlerConfiguration configuration) throws AuditException {
129
130        Reject.ifNull(clazz, "Audit event handler clazz cannot be null");
131        Reject.ifNull(configuration, "Audit event handler configuration cannot be null");
132        Reject.ifNull(configuration.getName(), "Audit event handler name cannot be null");
133
134        rejectIfHandlerNameAlreadyTaken(configuration.getName());
135        handlerRegistrations.put(configuration.getName(), new HandlerRegistration<>(clazz, configuration));
136        return this;
137    }
138
139    /**
140     * Register an AuditEventHandler.
141     *
142     * @param auditEventHandler
143     *            the AuditEventHandler to register.
144     * @throws AuditException
145     *             if already asked to register a handler with the same name.
146     * @return this builder for method-chaining.
147     */
148    public AuditServiceBuilder withAuditEventHandler(AuditEventHandler auditEventHandler) throws AuditException {
149        Reject.ifNull(auditEventHandler, "Audit event handler cannot be null");
150        rejectIfHandlerNameAlreadyTaken(auditEventHandler.getName());
151        prebuiltHandlers.add(auditEventHandler);
152        return this;
153    }
154
155    private void rejectIfHandlerNameAlreadyTaken(String name) throws AuditException {
156        if (handlerRegistrations.containsKey(name)) {
157            throw new AuditException("There is already a handler registered for " + name);
158        }
159        for (AuditEventHandler handler : prebuiltHandlers) {
160            if (handler.getName() != null && handler.getName().equals(name)) {
161                throw new AuditException("There is already a handler registered for " + name);
162            }
163        }
164    }
165
166    /**
167     * Creates a new AuditService instance.
168     * <p/>
169     * Instances receive their configuration when constructed and cannot be reconfigured. Where "hot-swappable"
170     * reconfiguration is required, an instance of {@link AuditServiceProxy} should be used as a proxy. The old
171     * AuditService should fully shutdown before the new instance is started. Care must be taken to ensure that
172     * no other threads can interact with this object while {@link AuditService#startup()} and
173     * {@link AuditService#shutdown()} methods are running.
174     * <p/>
175     * After construction, the AuditService will be in the 'STARTING' state until {@link AuditService#startup()}
176     * is called. When in the 'STARTING' state, a call to any method other than {@link AuditService#startup()}
177     * will lead to {@link ServiceUnavailableException}.
178     * <p/>
179     * After {@link AuditService#startup()} is called, assuming startup succeeds, the AuditService will then be in
180     * the 'RUNNING' state and further calls to {@link AuditService#startup()} will be ignored.
181     * <p/>
182     * Calling {@link AuditService#shutdown()} will put the AuditService into the 'SHUTDOWN' state; once shutdown, the
183     * AuditService will remain in this state and cannot be restarted. Further calls to {@link AuditService#shutdown()}
184     * will be ignored. When in the 'SHUTDOWN' state, a call to any method other than {@link AuditService#shutdown()}
185     * will lead to {@link ServiceUnavailableException}.
186     * <p/>
187     * When instances are no longer needed, {@link AuditService#shutdown()} should be called to ensure that any
188     * buffered audit events are flushed and that all open file handles or connections are closed.
189     *
190     * @return a new AuditService instance.
191     */
192    public AuditService build() {
193        Set<AuditEventHandler> handlers = buildAuditEventHandlers(eventTopicsMetaData);
194        return auditServiceFactory.newAuditService(auditServiceConfiguration, eventTopicsMetaData, handlers);
195    }
196
197
198    private Set<AuditEventHandler> buildAuditEventHandlers(final EventTopicsMetaData eventTopicsMetaData) {
199        Set<AuditEventHandler> handlers = new LinkedHashSet<>(prebuiltHandlers);
200        for (HandlerRegistration handlerRegistration : handlerRegistrations.values()) {
201            logger.debug("Registering handler '{}' for {} topics",
202                    handlerRegistration.configuration.getName(),
203                    handlerRegistration.configuration.getTopics().toString());
204            try {
205                handlers.add(auditEventHandlerFactory.create(
206                        handlerRegistration.configuration.getName(),
207                        handlerRegistration.clazz,
208                        handlerRegistration.configuration,
209                        eventTopicsMetaData));
210            } catch (AuditException e) {
211                logger.error(e.getMessage(), e);
212            }
213        }
214        logger.debug("Registered {}", handlers.toString());
215        return handlers;
216    }
217
218    /**
219     * Captures details of a handler registration request.
220     * <p/>
221     * Calls to {@link AuditServiceBuilder#withAuditEventHandler} are lazily-processed when
222     * {@link AuditServiceBuilder#build()} is called so that all event topic schema meta-data
223     * is available for validation of the mapping from topics to handlers without constraining
224     * the order in which the builder's methods should be called.
225     */
226    private static class HandlerRegistration<C extends EventHandlerConfiguration> {
227
228        final Class<? extends AuditEventHandler> clazz;
229        final C configuration;
230
231        private HandlerRegistration(Class<? extends AuditEventHandler> clazz, C configuration) {
232            this.clazz = clazz;
233            this.configuration = configuration;
234        }
235    }
236
237    /**
238     * This class exists solely to provide a 'seam' that can be mocked during unit testing.
239     */
240    @VisibleForTesting
241    static class AuditServiceFactory {
242
243        AuditService newAuditService(
244                final AuditServiceConfiguration configuration,
245                final EventTopicsMetaData eventTopicsMetaData,
246                final Set<AuditEventHandler> auditEventHandlers) {
247            return new AuditServiceImpl(configuration, eventTopicsMetaData, auditEventHandlers);
248        }
249    }
250}