/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2013, Red Hat, Inc., and individual contributors
 * as indicated by the @author tags. See the copyright.txt file in the
 * distribution for a full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */

package org.wildfly.extension.picketlink.idm.model;

import org.jboss.as.controller.AbstractAddStepHandler;
import org.jboss.as.controller.OperationContext;
import org.jboss.as.controller.OperationFailedException;
import org.jboss.as.controller.PathAddress;
import org.jboss.as.controller.SimpleAttributeDefinition;
import org.jboss.as.controller.registry.Resource;
import org.jboss.as.controller.services.path.PathManager;
import org.jboss.as.controller.services.path.PathManagerService;
import org.jboss.as.naming.ValueManagedReferenceFactory;
import org.jboss.as.naming.deployment.ContextNames;
import org.jboss.dmr.ModelNode;
import org.jboss.dmr.Property;
import org.jboss.modules.Module;
import org.jboss.modules.ModuleIdentifier;
import org.jboss.modules.ModuleLoadException;
import org.jboss.modules.ModuleLoader;
import org.jboss.msc.service.ServiceBuilder;
import org.jboss.msc.service.ServiceController;
import org.jboss.msc.service.ServiceController.Mode;
import org.jboss.msc.service.ServiceName;
import org.jboss.msc.value.InjectedValue;
import org.picketlink.idm.PartitionManager;
import org.picketlink.idm.config.FileStoreConfigurationBuilder;
import org.picketlink.idm.config.IdentityConfigurationBuilder;
import org.picketlink.idm.config.IdentityStoreConfigurationBuilder;
import org.picketlink.idm.config.LDAPMappingConfigurationBuilder;
import org.picketlink.idm.config.LDAPStoreConfigurationBuilder;
import org.picketlink.idm.config.NamedIdentityConfigurationBuilder;
import org.picketlink.idm.credential.handler.CredentialHandler;
import org.picketlink.idm.model.AttributedType;
import org.picketlink.idm.model.Relationship;
import org.wildfly.extension.picketlink.common.model.ModelElement;
import org.wildfly.extension.picketlink.idm.config.JPAStoreSubsystemConfiguration;
import org.wildfly.extension.picketlink.idm.config.JPAStoreSubsystemConfigurationBuilder;
import org.wildfly.extension.picketlink.idm.service.FileIdentityStoreService;
import org.wildfly.extension.picketlink.idm.service.JPAIdentityStoreService;
import org.wildfly.extension.picketlink.idm.service.PartitionManagerService;

import javax.transaction.TransactionSynchronizationRegistry;
import java.util.List;

import static org.jboss.as.controller.PathAddress.EMPTY_ADDRESS;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OP_ADDR;
import static org.wildfly.extension.picketlink.common.model.ModelElement.FILE_STORE;
import static org.wildfly.extension.picketlink.common.model.ModelElement.IDENTITY_CONFIGURATION;
import static org.wildfly.extension.picketlink.common.model.ModelElement.IDENTITY_STORE_CREDENTIAL_HANDLER;
import static org.wildfly.extension.picketlink.common.model.ModelElement.JPA_STORE;
import static org.wildfly.extension.picketlink.common.model.ModelElement.LDAP_STORE;
import static org.wildfly.extension.picketlink.common.model.ModelElement.LDAP_STORE_ATTRIBUTE;
import static org.wildfly.extension.picketlink.common.model.ModelElement.LDAP_STORE_MAPPING;
import static org.wildfly.extension.picketlink.common.model.ModelElement.SUPPORTED_TYPE;
import static org.wildfly.extension.picketlink.common.model.ModelElement.SUPPORTED_TYPES;
import static org.wildfly.extension.picketlink.logging.PicketLinkLogger.ROOT_LOGGER;

/**
 * @author <a href="mailto:psilva@redhat.com">Pedro Silva</a>
 */
public class PartitionManagerAddHandler extends AbstractAddStepHandler {

    static final PartitionManagerAddHandler INSTANCE = new PartitionManagerAddHandler();

    @Override
    protected void populateModel(ModelNode operation, ModelNode model) throws OperationFailedException {
        for (SimpleAttributeDefinition attribute : PartitionManagerResourceDefinition.INSTANCE.getAttributes()) {
            attribute.validateAndSet(operation, model);
        }
    }

    @Override
    protected void performRuntime(OperationContext context, ModelNode operation, ModelNode model) throws OperationFailedException {
        PathAddress address = PathAddress.pathAddress(operation.get(OP_ADDR));
        final String federationName = address.getLastElement().getValue();
        ModelNode partitionManager = Resource.Tools.readModel(context.readResource(EMPTY_ADDRESS));
        createPartitionManagerService(context, federationName, partitionManager, false);
    }

    public void validateModel(final OperationContext context, String partitionManagerName, final ModelNode partitionManager) throws OperationFailedException {
        createPartitionManagerService(context, partitionManagerName, partitionManager, true);
    }

    public void createPartitionManagerService(final OperationContext context, String partitionManagerName, final ModelNode partitionManager, boolean onlyValidate) throws OperationFailedException {
        String jndiName = PartitionManagerResourceDefinition.IDENTITY_MANAGEMENT_JNDI_URL
            .resolveModelAttribute(context, partitionManager).asString();
        IdentityConfigurationBuilder builder = new IdentityConfigurationBuilder();
        PartitionManagerService partitionManagerService = new PartitionManagerService(partitionManagerName, jndiName, builder);
        ServiceBuilder<PartitionManager> serviceBuilder = null;

        if (!onlyValidate) {
            serviceBuilder = context.getServiceTarget()
                .addService(PartitionManagerService.createServiceName(partitionManagerName), partitionManagerService);
        }

        ModelNode identityConfigurationNode = partitionManager.get(IDENTITY_CONFIGURATION.getName());

        if (!identityConfigurationNode.isDefined()) {
            throw ROOT_LOGGER.idmNoIdentityConfigurationProvided();
        }

        for (Property identityConfiguration : identityConfigurationNode.asPropertyList()) {
            String configurationName = identityConfiguration.getName();
            NamedIdentityConfigurationBuilder namedIdentityConfigurationBuilder = builder.named(configurationName);

            if (!identityConfiguration.getValue().isDefined()) {
                throw ROOT_LOGGER.idmNoIdentityStoreProvided(configurationName);
            }

            List<ModelNode> identityStores = identityConfiguration.getValue().asList();

            for (ModelNode store : identityStores) {
                configureIdentityStore(context, serviceBuilder, partitionManagerService, configurationName, namedIdentityConfigurationBuilder, store);
            }
        }

        if (!onlyValidate) {
            ServiceController<PartitionManager> controller = serviceBuilder
                .setInitialMode(Mode.PASSIVE)
                .install();
        }
    }

    private void configureIdentityStore(OperationContext context, ServiceBuilder<PartitionManager> serviceBuilder, PartitionManagerService partitionManagerService, String configurationName, NamedIdentityConfigurationBuilder namedIdentityConfigurationBuilder, ModelNode modelNode) throws OperationFailedException {
        Property prop = modelNode.asProperty();
        String storeType = prop.getName();
        ModelNode identityStore = prop.getValue().asProperty().getValue();
        IdentityStoreConfigurationBuilder<?, ?> storeConfig = null;

        if (storeType.equals(JPA_STORE.getName())) {
            storeConfig = configureJPAIdentityStore(context, serviceBuilder, partitionManagerService, identityStore, configurationName, namedIdentityConfigurationBuilder);
        } else if (storeType.equals(FILE_STORE.getName())) {
            storeConfig = configureFileIdentityStore(context, serviceBuilder, partitionManagerService, identityStore, configurationName, namedIdentityConfigurationBuilder);
        } else if (storeType.equals(LDAP_STORE.getName())) {
            storeConfig = configureLDAPIdentityStore(context, identityStore, namedIdentityConfigurationBuilder);
        }

        ModelNode supportAttributeNode = JPAStoreResourceDefinition.SUPPORT_ATTRIBUTE.resolveModelAttribute(context, identityStore);

        storeConfig.supportAttributes(supportAttributeNode.asBoolean());

        ModelNode supportCredentialNode = JPAStoreResourceDefinition.SUPPORT_CREDENTIAL
            .resolveModelAttribute(context, identityStore);

        storeConfig.supportCredentials(supportCredentialNode.asBoolean());

        configureSupportedTypes(context, identityStore, storeConfig);
        configureCredentialHandlers(context, identityStore, storeConfig);
    }

    private LDAPStoreConfigurationBuilder configureLDAPIdentityStore(OperationContext context, ModelNode ldapIdentityStore, NamedIdentityConfigurationBuilder builder) throws OperationFailedException {
        LDAPStoreConfigurationBuilder storeConfig = builder.stores().ldap();
        ModelNode url = LDAPStoreResourceDefinition.URL.resolveModelAttribute(context, ldapIdentityStore);
        ModelNode bindDn = LDAPStoreResourceDefinition.BIND_DN.resolveModelAttribute(context, ldapIdentityStore);
        ModelNode bindCredential = LDAPStoreResourceDefinition.BIND_CREDENTIAL.resolveModelAttribute(context, ldapIdentityStore);
        ModelNode baseDn = LDAPStoreResourceDefinition.BASE_DN_SUFFIX.resolveModelAttribute(context, ldapIdentityStore);
        ModelNode uniqueIdAttributeName = LDAPStoreResourceDefinition.UNIQUE_ID_ATTRIBUTE_NAME.resolveModelAttribute(context, ldapIdentityStore);
        ModelNode activeDirectory = LDAPStoreResourceDefinition.ACTIVE_DIRECTORY.resolveModelAttribute(context, ldapIdentityStore);

        if (url.isDefined()) {
            storeConfig.url(url.asString());
        }

        if (bindDn.isDefined()) {
            storeConfig.bindDN(bindDn.asString());
        }

        if (bindCredential.isDefined()) {
            storeConfig.bindCredential(bindCredential.asString());
        }

        if (baseDn.isDefined()) {
            storeConfig.baseDN(baseDn.asString());
        }

        if (uniqueIdAttributeName.isDefined()) {
            storeConfig.uniqueIdentifierAttributeName(uniqueIdAttributeName.asString());
        }

        storeConfig.activeDirectory(activeDirectory.asBoolean());

        if (ldapIdentityStore.hasDefined(LDAP_STORE_MAPPING.getName())) {
            for (Property mappingNode : ldapIdentityStore.get(LDAP_STORE_MAPPING.getName()).asPropertyList()) {
                ModelNode ldapMapping = mappingNode.getValue();
                ModelNode classNameNode = LDAPStoreMappingResourceDefinition.CLASS_NAME.resolveModelAttribute(context, ldapMapping);
                ModelNode codeNode = LDAPStoreMappingResourceDefinition.CODE.resolveModelAttribute(context, ldapMapping);
                ModelNode moduleNode = LDAPStoreMappingResourceDefinition.MODULE.resolveModelAttribute(context, ldapMapping);

                String typeName;

                if (classNameNode.isDefined()) {
                    typeName = classNameNode.asString();
                } else if (codeNode.isDefined()) {
                    typeName = AttributedTypeEnum.forType(codeNode.asString());
                } else {
                    throw ROOT_LOGGER.typeNotProvided(LDAP_STORE_MAPPING.getName());
                }

                LDAPMappingConfigurationBuilder storeMapping = storeConfig
                    .mapping(this.<AttributedType>loadClass(moduleNode, typeName));
                ModelNode relatesToNode = LDAPStoreMappingResourceDefinition.RELATES_TO.resolveModelAttribute(context, ldapMapping);

                if (relatesToNode.isDefined()) {
                    String relatesTo = AttributedTypeEnum.forType(relatesToNode.asString());

                    if (relatesTo == null) {
                        relatesTo = relatesToNode.asString();
                    }

                    storeMapping.forMapping(this.<AttributedType>loadClass(moduleNode, relatesTo));
                } else {
                    String baseDN = LDAPStoreMappingResourceDefinition.BASE_DN.resolveModelAttribute(context, ldapMapping)
                        .asString();

                    storeMapping.baseDN(baseDN);

                    String objectClasses = LDAPStoreMappingResourceDefinition.OBJECT_CLASSES
                        .resolveModelAttribute(context, ldapMapping).asString();

                    for (String objClass : objectClasses.split(",")) {
                        if (!objClass.trim().isEmpty()) {
                            storeMapping.objectClasses(objClass);
                        }
                    }

                    ModelNode parentAttributeName = LDAPStoreMappingResourceDefinition.PARENT_ATTRIBUTE
                        .resolveModelAttribute(context, ldapMapping);

                    if (parentAttributeName.isDefined()) {
                        storeMapping.parentMembershipAttributeName(parentAttributeName.asString());
                    }
                }

                if (ldapMapping.hasDefined(LDAP_STORE_ATTRIBUTE.getName())) {
                    for (Property attributeNode : ldapMapping.get(LDAP_STORE_ATTRIBUTE.getName()).asPropertyList()) {
                        ModelNode attribute = attributeNode.getValue();
                        String name = LDAPStoreAttributeResourceDefinition.NAME.resolveModelAttribute(context, attribute)
                            .asString();
                        String ldapName = LDAPStoreAttributeResourceDefinition.LDAP_NAME.resolveModelAttribute(context, attribute)
                            .asString();
                        boolean readOnly = LDAPStoreAttributeResourceDefinition.READ_ONLY.resolveModelAttribute(context, attribute)
                            .asBoolean();

                        if (readOnly) {
                            storeMapping.readOnlyAttribute(name, ldapName);
                        } else {
                            boolean isIdentifier = LDAPStoreAttributeResourceDefinition.IS_IDENTIFIER
                                .resolveModelAttribute(context, attribute).asBoolean();
                            storeMapping.attribute(name, ldapName, isIdentifier);
                        }
                    }
                }
            }
        } else {
            throw ROOT_LOGGER.idmLdapNoMappingDefined();
        }

        return storeConfig;
    }

    private IdentityStoreConfigurationBuilder<?, ?> configureFileIdentityStore(OperationContext context, ServiceBuilder<PartitionManager> serviceBuilder, PartitionManagerService partitionManagerService, ModelNode resource, String configurationName, final NamedIdentityConfigurationBuilder builder) throws OperationFailedException {
        FileStoreConfigurationBuilder fileStoreBuilder = builder.stores().file();
        String workingDir = FileStoreResourceDefinition.WORKING_DIR.resolveModelAttribute(context, resource).asString();
        String relativeTo = FileStoreResourceDefinition.RELATIVE_TO.resolveModelAttribute(context, resource).asString();
        ModelNode alwaysCreateFiles = FileStoreResourceDefinition.ALWAYS_CREATE_FILE.resolveModelAttribute(context, resource);
        ModelNode asyncWrite = FileStoreResourceDefinition.ASYNC_WRITE.resolveModelAttribute(context, resource);
        ModelNode asyncWriteThreadPool = FileStoreResourceDefinition.ASYNC_WRITE_THREAD_POOL
            .resolveModelAttribute(context, resource);

        fileStoreBuilder.preserveState(!alwaysCreateFiles.asBoolean());
        fileStoreBuilder.asyncWrite(asyncWrite.asBoolean());
        fileStoreBuilder.asyncWriteThreadPool(asyncWriteThreadPool.asInt());

        if (serviceBuilder != null) {
            FileIdentityStoreService storeService = new FileIdentityStoreService(fileStoreBuilder, workingDir, relativeTo);
            ServiceName storeServiceName = PartitionManagerService
                .createIdentityStoreServiceName(partitionManagerService.getName(), configurationName, ModelElement.FILE_STORE
                    .getName());
            ServiceBuilder<FileIdentityStoreService> storeServiceBuilder = context.getServiceTarget()
                .addService(storeServiceName, storeService);

            storeServiceBuilder.addDependency(PathManagerService.SERVICE_NAME, PathManager.class, storeService.getPathManager());

            serviceBuilder.requires(storeServiceName);

            ServiceController<FileIdentityStoreService> controller = storeServiceBuilder
                .setInitialMode(Mode.PASSIVE)
                .install();
        }

        return fileStoreBuilder;
    }

    private JPAStoreSubsystemConfigurationBuilder configureJPAIdentityStore(OperationContext context, ServiceBuilder<PartitionManager> serviceBuilder, PartitionManagerService partitionManagerService, final ModelNode identityStore, String configurationName, final NamedIdentityConfigurationBuilder builder) throws OperationFailedException {
        JPAStoreSubsystemConfigurationBuilder storeConfig = builder.stores()
            .add(JPAStoreSubsystemConfiguration.class, JPAStoreSubsystemConfigurationBuilder.class);

        ModelNode jpaDataSourceNode = JPAStoreResourceDefinition.DATA_SOURCE.resolveModelAttribute(context, identityStore);
        ModelNode jpaEntityModule = JPAStoreResourceDefinition.ENTITY_MODULE.resolveModelAttribute(context, identityStore);
        ModelNode jpaEntityModuleUnitName = JPAStoreResourceDefinition.ENTITY_MODULE_UNIT_NAME
            .resolveModelAttribute(context, identityStore);
        ModelNode jpaEntityManagerFactoryNode = JPAStoreResourceDefinition.ENTITY_MANAGER_FACTORY
            .resolveModelAttribute(context, identityStore);

        if (jpaEntityModule.isDefined()) {
            storeConfig.entityModule(jpaEntityModule.asString());
        }

        storeConfig.entityModuleUnitName(jpaEntityModuleUnitName.asString());

        if (serviceBuilder != null) {
            JPAIdentityStoreService storeService = new JPAIdentityStoreService(storeConfig);
            ServiceName storeServiceName = PartitionManagerService
                .createIdentityStoreServiceName(partitionManagerService.getName(), configurationName, ModelElement.JPA_STORE
                    .getName());
            ServiceBuilder<JPAIdentityStoreService> storeServiceBuilder = context.getServiceTarget()
                .addService(storeServiceName, storeService);

            /* org.wildfly.transactions.global-default-local-provider capability ensures a local provider of
               transactions is present. Once its service is started, calls to the getInstance() methods of
               ContextTransactionManager and LocalUserTransaction
               can be made knowing that the global default TM, TSR and UT will be from that provider. */
            storeServiceBuilder.requires(context.getCapabilityServiceName("org.wildfly.transactions.global-default-local-provider", null));

            storeServiceBuilder
                .addDependency(context.getCapabilityServiceName("org.wildfly.transactions.transaction-synchronization-registry", null), TransactionSynchronizationRegistry.class, storeService
                    .getTransactionSynchronizationRegistry());

            if (jpaDataSourceNode.isDefined()) {
                storeConfig.dataSourceJndiUrl(toJndiName(jpaDataSourceNode.asString()));
                storeServiceBuilder
                    .requires(ContextNames.JAVA_CONTEXT_SERVICE_NAME
                        .append(toJndiName(jpaDataSourceNode.asString()).split("/")));
            }

            if (jpaEntityManagerFactoryNode.isDefined()) {
                storeConfig.entityManagerFactoryJndiName(jpaEntityManagerFactoryNode.asString());
                storeServiceBuilder
                    .addDependency(ContextNames.JAVA_CONTEXT_SERVICE_NAME.append(jpaEntityManagerFactoryNode.asString().split("/")),
                        ValueManagedReferenceFactory.class, new InjectedValue<ValueManagedReferenceFactory>());
            }

            serviceBuilder.requires(storeServiceName);

            ServiceController<JPAIdentityStoreService> controller = storeServiceBuilder
                .setInitialMode(Mode.PASSIVE)
                .install();
        }

        return storeConfig;
    }

    private void configureSupportedTypes(OperationContext context, ModelNode identityStore, IdentityStoreConfigurationBuilder storeConfig) throws OperationFailedException {
        boolean hasSupportedType = identityStore.hasDefined(SUPPORTED_TYPES.getName());

        if (hasSupportedType) {
            ModelNode featuresSetNode = identityStore.get(SUPPORTED_TYPES.getName()).asProperty().getValue();
            ModelNode supportsAllNode = SupportedTypesResourceDefinition.SUPPORTS_ALL
                .resolveModelAttribute(context, featuresSetNode);

            if (supportsAllNode.asBoolean()) {
                storeConfig.supportAllFeatures();
            }

            hasSupportedType = supportsAllNode.asBoolean();

            if (featuresSetNode.hasDefined(SUPPORTED_TYPE.getName())) {
                for (Property supportedTypeNode : featuresSetNode.get(SUPPORTED_TYPE.getName()).asPropertyList()) {
                    ModelNode supportedType = supportedTypeNode.getValue();
                    ModelNode classNameNode = SupportedTypeResourceDefinition.CLASS_NAME
                        .resolveModelAttribute(context, supportedType);
                    ModelNode codeNode = SupportedTypeResourceDefinition.CODE.resolveModelAttribute(context, supportedType);
                    String typeName;

                    if (classNameNode.isDefined()) {
                        typeName = classNameNode.asString();
                    } else if (codeNode.isDefined()) {
                        typeName = AttributedTypeEnum.forType(codeNode.asString());
                    } else {
                        throw ROOT_LOGGER.typeNotProvided(SUPPORTED_TYPE.getName());
                    }

                    ModelNode moduleNode = SupportedTypeResourceDefinition.MODULE.resolveModelAttribute(context, supportedType);
                    Class<? extends AttributedType> attributedTypeClass = loadClass(moduleNode, typeName);

                    if (Relationship.class.isAssignableFrom(attributedTypeClass)) {
                        storeConfig.supportGlobalRelationship((Class<? extends Relationship>) attributedTypeClass);
                    } else {
                        storeConfig.supportType(attributedTypeClass);
                    }

                    hasSupportedType = true;
                }
            }
        }

        if (!hasSupportedType) {
            throw ROOT_LOGGER.idmNoSupportedTypesDefined();
        }
    }

    @SuppressWarnings({ "unchecked", "rawtypes" })
    private void configureCredentialHandlers(OperationContext context, ModelNode identityStore, IdentityStoreConfigurationBuilder<?, ?> storeConfig) throws OperationFailedException {
        if (identityStore.hasDefined(IDENTITY_STORE_CREDENTIAL_HANDLER.getName())) {
            for (Property credentialHandler : identityStore.get(IDENTITY_STORE_CREDENTIAL_HANDLER.getName()).asPropertyList()) {
                ModelNode classNameNode = CredentialHandlerResourceDefinition.CLASS_NAME
                    .resolveModelAttribute(context, credentialHandler.getValue());
                ModelNode codeNode = CredentialHandlerResourceDefinition.CODE
                    .resolveModelAttribute(context, credentialHandler.getValue());
                ModelNode moduleNode = CredentialHandlerResourceDefinition.MODULE
                    .resolveModelAttribute(context, credentialHandler.getValue());
                String typeName;

                if (classNameNode.isDefined()) {
                    typeName = classNameNode.asString();
                } else if (codeNode.isDefined()) {
                    typeName = CredentialTypeEnum.forType(codeNode.asString());
                } else {
                    throw ROOT_LOGGER.typeNotProvided(IDENTITY_STORE_CREDENTIAL_HANDLER.getName());
                }

                storeConfig.addCredentialHandler(this.<CredentialHandler>loadClass(moduleNode, typeName));
            }
        }
    }

    private String toJndiName(String jndiName) {
        if (jndiName != null) {
            if (jndiName.startsWith("java:")) {
                return jndiName.substring(jndiName.indexOf(":") + 1);
            }
        }

        return jndiName;
    }

    private Module getModule(ModelNode moduleNode) {
        Module module;

        if (moduleNode.isDefined()) {
            ModuleLoader moduleLoader = Module.getBootModuleLoader();
            try {
                module = moduleLoader.loadModule(ModuleIdentifier.fromString(moduleNode.asString()));
            } catch (ModuleLoadException e) {
                throw ROOT_LOGGER.moduleCouldNotLoad(moduleNode.asString(), e);
            }
        } else {
            // fallback to caller module.
            module = Module.getCallerModule();
        }

        return module;
    }

    @SuppressWarnings("unchecked")
    private <T> Class<T> loadClass(ModelNode moduleNode, String typeName) {
        try {
            Module module = getModule(moduleNode);

            if (module != null) {
                return (Class<T>) module.getClassLoader().loadClass(typeName);
            } else {
                return (Class<T>) getClass().getClassLoader().loadClass(typeName);
            }
        } catch (ClassNotFoundException cnfe) {
            throw ROOT_LOGGER.couldNotLoadClass(typeName, cnfe);
        }
    }
}
