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}