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}