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.events.handlers; 018 019import org.forgerock.audit.AuditException; 020import org.forgerock.audit.DependencyProvider; 021import org.forgerock.audit.events.EventTopicsMetaData; 022 023import java.lang.annotation.Annotation; 024import java.lang.reflect.Constructor; 025import java.lang.reflect.InvocationTargetException; 026import javax.inject.Inject; 027 028/** 029 * AuditEventFactory capable of performing construction injection by resolving dependencies using a DependencyProvider. 030 */ 031public class DependencyProviderAuditEventHandlerFactory implements AuditEventHandlerFactory { 032 033 private final DependencyProvider dependencyProvider; 034 035 /** 036 * Construct a new instance. 037 * 038 * @param dependencyProvider 039 * Dependency lookup abstraction for obtaining resources or objects from the product which 040 * integrates this AuditEventHandler. 041 */ 042 public DependencyProviderAuditEventHandlerFactory(DependencyProvider dependencyProvider) { 043 this.dependencyProvider = dependencyProvider; 044 } 045 046 @Override 047 public <T extends AuditEventHandler> T create( 048 String name, 049 Class<T> clazz, 050 EventHandlerConfiguration configuration, 051 EventTopicsMetaData eventTopicsMetaData) throws AuditException { 052 053 Constructor<T> constructor = getConstructorForInjection(clazz); 054 Object[] parameters = getConstructorParameters(constructor, name, configuration, eventTopicsMetaData); 055 056 try { 057 return constructor.newInstance(parameters); 058 } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { 059 String errorMessage = "Unable to create " + clazz.getSimpleName() + " '" + name + "': " + e.getMessage(); 060 throw new AuditException(errorMessage, e); 061 } 062 } 063 064 @SuppressWarnings("unchecked") 065 private <T extends AuditEventHandler>Constructor<T> getConstructorForInjection(Class<T> clazz) { 066 Constructor<?>[] constructors = clazz.getConstructors(); 067 if (constructors.length == 1) { 068 return (Constructor<T>) constructors[0]; 069 } 070 for (Constructor<?> candidateConstructor : constructors) { 071 if (hasInjectAnnotation(candidateConstructor)) { 072 // TODO: Ensure that only one constructor is marked with the @Inject annotation 073 return (Constructor<T>) candidateConstructor; 074 } 075 } 076 throw new IllegalStateException(clazz.getSimpleName() 077 + " should have a single public constructor. If multiple public constructors " 078 + "are required, annotate one with @Inject."); 079 } 080 081 private boolean hasInjectAnnotation(Constructor<?> constructor) { 082 for (Annotation annotation : constructor.getDeclaredAnnotations()) { 083 if (annotation.annotationType().equals(Inject.class)) { 084 return true; 085 } 086 } 087 return false; 088 } 089 090 private <T extends AuditEventHandler> Object[] getConstructorParameters( 091 Constructor<T> constructor, 092 String name, 093 EventHandlerConfiguration configuration, 094 EventTopicsMetaData eventTopicsMetaData) throws AuditException { 095 096 final Class<?>[] parameterTypes = constructor.getParameterTypes(); 097 final Object[] parameters = new Object[parameterTypes.length]; 098 099 for (int i = 0; i < parameterTypes.length; i++) { 100 if (parameterTypes[i].equals(String.class)) { 101 parameters[i] = name; 102 } else if (parameterTypes[i].isAssignableFrom(configuration.getClass())) { 103 parameters[i] = configuration; 104 } else if (parameterTypes[i].equals(EventTopicsMetaData.class)) { 105 parameters[i] = eventTopicsMetaData; 106 } else { 107 try { 108 parameters[i] = dependencyProvider.getDependency(parameterTypes[i]); 109 } catch (ClassNotFoundException e) { 110 parameters[i] = null; 111 } 112 } 113 } 114 115 return parameters; 116 } 117 118}