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