/*
* JBoss, Home of Professional Open Source.
* Copyright 2011, 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.jboss.as.connector.subsystems.datasources;
import static org.jboss.as.connector.logging.ConnectorLogger.SUBSYSTEM_DATASOURCES_LOGGER;
import static org.jboss.as.connector.subsystems.datasources.Constants.AUTHENTICATION_CONTEXT;
import static org.jboss.as.connector.subsystems.datasources.Constants.DATASOURCE_DRIVER;
import static org.jboss.as.connector.subsystems.datasources.Constants.ELYTRON_ENABLED;
import static org.jboss.as.connector.subsystems.datasources.Constants.ENABLED;
import static org.jboss.as.connector.subsystems.datasources.Constants.JNDI_NAME;
import static org.jboss.as.connector.subsystems.datasources.Constants.JTA;
import static org.jboss.as.connector.subsystems.datasources.Constants.RECOVERY_AUTHENTICATION_CONTEXT;
import static org.jboss.as.connector.subsystems.datasources.Constants.RECOVERY_ELYTRON_ENABLED;
import static org.jboss.as.connector.subsystems.datasources.Constants.RECOVERY_SECURITY_DOMAIN;
import static org.jboss.as.connector.subsystems.datasources.Constants.SECURITY_DOMAIN;
import static org.jboss.as.connector.subsystems.datasources.Constants.STATISTICS_ENABLED;
import static org.jboss.as.connector.subsystems.datasources.DataSourceModelNodeUtil.from;
import static org.jboss.as.connector.subsystems.datasources.DataSourceModelNodeUtil.xaFrom;
import static org.jboss.as.connector.subsystems.jca.Constants.DEFAULT_NAME;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OP_ADDR;
import javax.sql.DataSource;
import java.sql.Driver;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import org.jboss.as.connector._private.Capabilities;
import org.jboss.as.connector.logging.ConnectorLogger;
import org.jboss.as.connector.services.datasources.statistics.DataSourceStatisticsService;
import org.jboss.as.connector.services.driver.registry.DriverRegistry;
import org.jboss.as.connector.util.ConnectorServices;
import org.jboss.as.controller.AbstractAddStepHandler;
import org.jboss.as.controller.AttributeDefinition;
import org.jboss.as.controller.OperationContext;
import org.jboss.as.controller.OperationFailedException;
import org.jboss.as.controller.OperationStepHandler;
import org.jboss.as.controller.PathAddress;
import org.jboss.as.controller.registry.ManagementResourceRegistration;
import org.jboss.as.controller.registry.Resource;
import org.jboss.as.controller.security.CredentialReference;
import org.jboss.as.core.security.ServerSecurityManager;
import org.jboss.as.naming.ManagedReferenceFactory;
import org.jboss.as.naming.ServiceBasedNamingStore;
import org.jboss.as.naming.deployment.ContextNames;
import org.jboss.as.naming.service.BinderService;
import org.jboss.as.naming.service.NamingService;
import org.jboss.as.security.service.SecurityDomainService;
import org.jboss.as.security.service.SimpleSecurityManagerService;
import org.jboss.as.security.service.SubjectFactoryService;
import org.jboss.as.server.Services;
import org.jboss.dmr.ModelNode;
import org.jboss.jca.common.api.metadata.common.Credential;
import org.jboss.jca.common.api.metadata.ds.DsSecurity;
import org.jboss.jca.common.api.validator.ValidateException;
import org.jboss.jca.core.api.connectionmanager.ccm.CachedConnectionManager;
import org.jboss.jca.core.api.management.ManagementRepository;
import org.jboss.jca.core.spi.mdr.MetadataRepository;
import org.jboss.jca.core.spi.rar.ResourceAdapterRepository;
import org.jboss.jca.core.spi.transaction.TransactionIntegration;
import org.jboss.jca.deployers.common.CommonDeployment;
import org.jboss.msc.service.AbstractServiceListener;
import org.jboss.msc.service.ServiceBuilder;
import org.jboss.msc.service.ServiceController;
import org.jboss.msc.service.ServiceName;
import org.jboss.msc.service.ServiceRegistry;
import org.jboss.msc.service.ServiceTarget;
import org.jboss.msc.service.ValueInjectionService;
import org.jboss.security.SubjectFactory;
import org.wildfly.common.function.ExceptionSupplier;
import org.wildfly.security.auth.client.AuthenticationContext;
import org.wildfly.security.credential.source.CredentialSource;
/**
* Abstract operation handler responsible for adding a DataSource.
*
* @author John Bailey
*/
public abstract class AbstractDataSourceAdd extends AbstractAddStepHandler {
AbstractDataSourceAdd(Collection<AttributeDefinition> attributes) {
super(Capabilities.DATA_SOURCE_CAPABILITY, attributes);
}
@Override
protected void populateModel(final OperationContext context, final ModelNode operation, final Resource resource) throws OperationFailedException {
if (context.getProcessType().isServer()) {
DataSourceStatisticsService.registerStatisticsResources(resource);
}
super.populateModel(context, operation, resource);
}
@Override
protected void performRuntime(final OperationContext context, final ModelNode operation, final ModelNode model) throws OperationFailedException {
// add extra security validation: authentication contexts should only be defined when Elytron Enabled is true
// domains should only be defined when Elytron enabled is undefined or false (default value)
if (model.hasDefined(AUTHENTICATION_CONTEXT.getName()) && !ELYTRON_ENABLED.resolveModelAttribute(context, model).asBoolean()) {
throw SUBSYSTEM_DATASOURCES_LOGGER.attributeRequiresTrueAttribute(AUTHENTICATION_CONTEXT.getName(), ELYTRON_ENABLED.getName());
}
else if (ELYTRON_ENABLED.resolveModelAttribute(context, model).asBoolean() && model.hasDefined(SECURITY_DOMAIN.getName())){
throw SUBSYSTEM_DATASOURCES_LOGGER.attributeRequiresFalseOrUndefinedAttribute(SECURITY_DOMAIN.getName(), ELYTRON_ENABLED.getName());
}
final boolean enabled = ENABLED.resolveModelAttribute(context, model).asBoolean();
if (enabled) {
firstRuntimeStep(context, operation, model);
context.addStep(new OperationStepHandler() {
@Override
public void execute(OperationContext operationContext, ModelNode modelNode) throws OperationFailedException {
secondRuntimeStep(context, operation, context.getResourceRegistrationForUpdate(), model, isXa());
}
}, OperationContext.Stage.RUNTIME);
}
}
void firstRuntimeStep(OperationContext context, ModelNode operation, ModelNode model) throws OperationFailedException {
final String jndiName = JNDI_NAME.resolveModelAttribute(context, model).asString();
final ContextNames.BindInfo bindInfo = ContextNames.bindInfoFor(jndiName);
final boolean jta = JTA.resolveModelAttribute(context, operation).asBoolean();
final String dsName = context.getCurrentAddressValue();// The STATISTICS_ENABLED.resolveModelAttribute(context, model) call should remain as it serves to validate that any
// expression in the model can be resolved to a correct value.
@SuppressWarnings("unused")
final boolean statsEnabled = STATISTICS_ENABLED.resolveModelAttribute(context, model).asBoolean();
final ServiceTarget serviceTarget = context.getServiceTarget();
ModelNode node = DATASOURCE_DRIVER.resolveModelAttribute(context, model);
final String driverName = node.asString();
final ServiceName driverServiceName = ServiceName.JBOSS.append("jdbc-driver", driverName.replaceAll("\\.", "_"));
ValueInjectionService<Driver> driverDemanderService = new ValueInjectionService<Driver>();
final ServiceName driverDemanderServiceName = ServiceName.JBOSS.append("driver-demander").append(jndiName);
final ServiceBuilder<?> driverDemanderBuilder = serviceTarget
.addService(driverDemanderServiceName, driverDemanderService)
.addDependency(driverServiceName, Driver.class, driverDemanderService.getInjector());
driverDemanderBuilder.setInitialMode(ServiceController.Mode.ACTIVE);
AbstractDataSourceService dataSourceService = createDataSourceService(dsName, jndiName);
final ManagementResourceRegistration registration = context.getResourceRegistrationForUpdate();
final ServiceName dataSourceServiceNameAlias = AbstractDataSourceService.getServiceName(bindInfo);
final ServiceName dataSourceServiceName = context.getCapabilityServiceName(Capabilities.DATA_SOURCE_CAPABILITY_NAME, dsName, DataSource.class);
final ServiceBuilder<?> dataSourceServiceBuilder =
Services.addServerExecutorDependency(
serviceTarget.addService(dataSourceServiceName, dataSourceService),
dataSourceService.getExecutorServiceInjector())
.addAliases(dataSourceServiceNameAlias)
.addDependency(ConnectorServices.MANAGEMENT_REPOSITORY_SERVICE, ManagementRepository.class,
dataSourceService.getManagementRepositoryInjector())
.addDependency(ConnectorServices.JDBC_DRIVER_REGISTRY_SERVICE, DriverRegistry.class,
dataSourceService.getDriverRegistryInjector())
.addDependency(ConnectorServices.IDLE_REMOVER_SERVICE)
.addDependency(ConnectorServices.CONNECTION_VALIDATOR_SERVICE)
.addDependency(ConnectorServices.IRONJACAMAR_MDR, MetadataRepository.class, dataSourceService.getMdrInjector())
.addDependency(NamingService.SERVICE_NAME);
if (jta) {
dataSourceServiceBuilder.addDependency(ConnectorServices.TRANSACTION_INTEGRATION_SERVICE, TransactionIntegration.class, dataSourceService.getTransactionIntegrationInjector())
.addDependency(ConnectorServices.CCM_SERVICE, CachedConnectionManager.class, dataSourceService.getCcmInjector())
.addDependency(ConnectorServices.BOOTSTRAP_CONTEXT_SERVICE.append(DEFAULT_NAME))
.addDependency(ConnectorServices.RA_REPOSITORY_SERVICE, ResourceAdapterRepository.class, dataSourceService.getRaRepositoryInjector());
} else {
dataSourceServiceBuilder.addDependency(ConnectorServices.NON_JTA_DS_RA_REPOSITORY_SERVICE, ResourceAdapterRepository.class, dataSourceService.getRaRepositoryInjector())
.addDependency(ConnectorServices.NON_TX_CCM_SERVICE, CachedConnectionManager.class, dataSourceService.getCcmInjector());
}
//Register an empty override model regardless of we're enabled or not - the statistics listener will add the relevant childresources
if (registration.isAllowsOverride()) {
registration.registerOverrideModel(dsName, DataSourcesSubsystemProviders.OVERRIDE_DS_DESC);
}
startConfigAndAddDependency(dataSourceServiceBuilder, dataSourceService, dsName, serviceTarget, operation);
dataSourceServiceBuilder.addDependency(driverServiceName, Driver.class,
dataSourceService.getDriverInjector());
// If the authentication context is defined, add the capability
boolean requireLegacySecurity = false;
if (ELYTRON_ENABLED.resolveModelAttribute(context, model).asBoolean()) {
if (model.hasDefined(AUTHENTICATION_CONTEXT.getName())) {
dataSourceServiceBuilder.addDependency(
context.getCapabilityServiceName(
Capabilities.AUTHENTICATION_CONTEXT_CAPABILITY,
AUTHENTICATION_CONTEXT.resolveModelAttribute(context, model).asString(),
AuthenticationContext.class),
AuthenticationContext.class,
dataSourceService.getAuthenticationContext()
);
}
} else {
String secDomain = SECURITY_DOMAIN.resolveModelAttribute(context, model).asStringOrNull();
requireLegacySecurity = (secDomain != null && secDomain.length() > 0) ;
}
if (isXa()) {
if (RECOVERY_ELYTRON_ENABLED.resolveModelAttribute(context, model).asBoolean()) {
if (model.hasDefined(RECOVERY_AUTHENTICATION_CONTEXT.getName())) {
dataSourceServiceBuilder.addDependency(
context.getCapabilityServiceName(
Capabilities.AUTHENTICATION_CONTEXT_CAPABILITY,
RECOVERY_AUTHENTICATION_CONTEXT.resolveModelAttribute(context, model).asString(),
AuthenticationContext.class),
AuthenticationContext.class,
dataSourceService.getRecoveryAuthenticationContext()
);
}
} else if (!requireLegacySecurity) {
String secDomain = RECOVERY_SECURITY_DOMAIN.resolveModelAttribute(context, model).asStringOrNull();
requireLegacySecurity = (secDomain != null && secDomain.length() > 0);
}
}
if (requireLegacySecurity) {
dataSourceServiceBuilder.addDependency(SubjectFactoryService.SERVICE_NAME, SubjectFactory.class, dataSourceService.getSubjectFactoryInjector())
.addDependency(SimpleSecurityManagerService.SERVICE_NAME, ServerSecurityManager.class, dataSourceService.getServerSecurityManager());
}
ModelNode credentialReference = Constants.CREDENTIAL_REFERENCE.resolveModelAttribute(context, model);
if (credentialReference.isDefined()) {
dataSourceService.getCredentialSourceSupplierInjector()
.inject(
CredentialReference.getCredentialSourceSupplier(context, Constants.CREDENTIAL_REFERENCE, model, dataSourceServiceBuilder));
}
ModelNode recoveryCredentialReference = Constants.RECOVERY_CREDENTIAL_REFERENCE.resolveModelAttribute(context, model);
if (recoveryCredentialReference.isDefined()) {
dataSourceService.getRecoveryCredentialSourceSupplierInjector()
.inject(
CredentialReference.getCredentialSourceSupplier(context, Constants.RECOVERY_CREDENTIAL_REFERENCE, model, dataSourceServiceBuilder));
}
dataSourceServiceBuilder.setInitialMode(ServiceController.Mode.NEVER);
dataSourceServiceBuilder.install();
driverDemanderBuilder.install();
}
static void secondRuntimeStep(OperationContext context, ModelNode operation, ManagementResourceRegistration datasourceRegistration, ModelNode model, boolean isXa) throws OperationFailedException {
final ServiceTarget serviceTarget = context.getServiceTarget();
final ModelNode address = operation.require(OP_ADDR);
final String dsName = PathAddress.pathAddress(address).getLastElement().getValue();
final String jndiName = JNDI_NAME.resolveModelAttribute(context, model).asString();
final ServiceRegistry registry = context.getServiceRegistry(true);
final List<ServiceName> serviceNames = registry.getServiceNames();
final boolean elytronEnabled = ELYTRON_ENABLED.resolveModelAttribute(context, model).asBoolean();
final ServiceName dataSourceServiceName = context.getCapabilityServiceName(Capabilities.DATA_SOURCE_CAPABILITY_NAME, dsName, DataSource.class);
final ServiceController<?> dataSourceController = registry.getService(dataSourceServiceName);
final ExceptionSupplier<CredentialSource, Exception> credentialSourceExceptionExceptionSupplier =
dataSourceController.getService() instanceof AbstractDataSourceService ?
((AbstractDataSourceService)dataSourceController.getService()).getCredentialSourceSupplierInjector().getOptionalValue()
:
null;
final ExceptionSupplier<CredentialSource, Exception> recoveryCredentialSourceExceptionExceptionSupplier =
dataSourceController.getService() instanceof AbstractDataSourceService ?
((AbstractDataSourceService)dataSourceController.getService()).getRecoveryCredentialSourceSupplierInjector().getOptionalValue()
:
null;
final boolean jta;
if (isXa) {
jta = true;
final ModifiableXaDataSource dataSourceConfig;
try {
dataSourceConfig = xaFrom(context, model, dsName, credentialSourceExceptionExceptionSupplier, recoveryCredentialSourceExceptionExceptionSupplier);
} catch (ValidateException e) {
throw new OperationFailedException(ConnectorLogger.ROOT_LOGGER.failedToCreate("XaDataSource", operation, e.getLocalizedMessage()));
}
final ServiceName xaDataSourceConfigServiceName = XADataSourceConfigService.SERVICE_NAME_BASE.append(dsName);
final XADataSourceConfigService xaDataSourceConfigService = new XADataSourceConfigService(dataSourceConfig);
final ServiceBuilder<?> builder = serviceTarget.addService(xaDataSourceConfigServiceName, xaDataSourceConfigService);
// add dependency on security domain service if applicable
final DsSecurity dsSecurityConfig = dataSourceConfig.getSecurity();
if (dsSecurityConfig != null) {
final String securityDomainName = dsSecurityConfig.getSecurityDomain();
if (!elytronEnabled && securityDomainName != null) {
builder.addDependency(SecurityDomainService.SERVICE_NAME.append(securityDomainName));
}
}
// add dependency on security domain service if applicable for recovery config
if (dataSourceConfig.getRecovery() != null) {
final Credential credential = dataSourceConfig.getRecovery().getCredential();
if (credential != null) {
final String securityDomainName = credential.getSecurityDomain();
if (!RECOVERY_ELYTRON_ENABLED.resolveModelAttribute(context, model).asBoolean() && securityDomainName != null) {
builder.addDependency(SecurityDomainService.SERVICE_NAME.append(securityDomainName));
}
}
}
int propertiesCount = 0;
for (ServiceName name : serviceNames) {
if (xaDataSourceConfigServiceName.append("xa-datasource-properties").isParentOf(name)) {
final ServiceController<?> xaConfigPropertyController = registry.getService(name);
XaDataSourcePropertiesService xaPropService = (XaDataSourcePropertiesService) xaConfigPropertyController.getService();
if (!ServiceController.State.UP.equals(xaConfigPropertyController.getState())) {
propertiesCount++;
xaConfigPropertyController.setMode(ServiceController.Mode.ACTIVE);
builder.addDependency(name, String.class, xaDataSourceConfigService.getXaDataSourcePropertyInjector(xaPropService.getName()));
} else {
throw new OperationFailedException(ConnectorLogger.ROOT_LOGGER.serviceAlreadyStarted("Data-source.xa-config-property", name));
}
}
}
if (propertiesCount == 0) {
throw ConnectorLogger.ROOT_LOGGER.xaDataSourcePropertiesNotPresent();
}
builder.install();
} else {
final ModifiableDataSource dataSourceConfig;
try {
dataSourceConfig = from(context, model, dsName, credentialSourceExceptionExceptionSupplier);
} catch (ValidateException e) {
throw new OperationFailedException(ConnectorLogger.ROOT_LOGGER.failedToCreate("DataSource", operation, e.getLocalizedMessage()));
}
jta = dataSourceConfig.isJTA();
final ServiceName dataSourceCongServiceName = DataSourceConfigService.SERVICE_NAME_BASE.append(dsName);
final DataSourceConfigService configService = new DataSourceConfigService(dataSourceConfig);
final ServiceBuilder<?> builder = serviceTarget.addService(dataSourceCongServiceName, configService);
// add dependency on security domain service if applicable
final DsSecurity dsSecurityConfig = dataSourceConfig.getSecurity();
if (dsSecurityConfig != null) {
final String securityDomainName = dsSecurityConfig.getSecurityDomain();
if (!elytronEnabled && securityDomainName != null) {
builder.addDependency(SecurityDomainService.SERVICE_NAME.append(securityDomainName));
}
}
for (ServiceName name : serviceNames) {
if (dataSourceCongServiceName.append("connection-properties").isParentOf(name)) {
final ServiceController<?> connPropServiceController = registry.getService(name);
ConnectionPropertiesService connPropService = (ConnectionPropertiesService) connPropServiceController.getService();
if (!ServiceController.State.UP.equals(connPropServiceController.getState())) {
connPropServiceController.setMode(ServiceController.Mode.ACTIVE);
builder.addDependency(name, String.class, configService.getConnectionPropertyInjector(connPropService.getName()));
} else {
throw new OperationFailedException(ConnectorLogger.ROOT_LOGGER.serviceAlreadyStarted("Data-source.connectionProperty", name));
}
}
}
builder.install();
}
final ServiceName dataSourceServiceNameAlias = AbstractDataSourceService.SERVICE_NAME_BASE.append(jndiName).append(Constants.STATISTICS);
if (dataSourceController != null) {
if (!ServiceController.State.UP.equals(dataSourceController.getState())) {
final boolean statsEnabled = STATISTICS_ENABLED.resolveModelAttribute(context, model).asBoolean();
DataSourceStatisticsService statsService = new DataSourceStatisticsService(datasourceRegistration, statsEnabled);
serviceTarget.addService(dataSourceServiceName.append(Constants.STATISTICS), statsService)
.addAliases(dataSourceServiceNameAlias)
.addDependency(dataSourceServiceName)
.addDependency(CommonDeploymentService.getServiceName( ContextNames.bindInfoFor(jndiName)), CommonDeployment.class, statsService.getCommonDeploymentInjector())
.setInitialMode(ServiceController.Mode.PASSIVE)
.install();
dataSourceController.setMode(ServiceController.Mode.ACTIVE);
} else {
throw new OperationFailedException(ConnectorLogger.ROOT_LOGGER.serviceAlreadyStarted("Data-source", dsName));
}
} else {
throw new OperationFailedException(ConnectorLogger.ROOT_LOGGER.serviceNotAvailable("Data-source", dsName));
}
final DataSourceReferenceFactoryService referenceFactoryService = new DataSourceReferenceFactoryService();
final ServiceName referenceFactoryServiceName = DataSourceReferenceFactoryService.SERVICE_NAME_BASE
.append(dsName);
final ServiceBuilder<?> referenceBuilder = serviceTarget.addService(referenceFactoryServiceName,
referenceFactoryService).addDependency(dataSourceServiceName, DataSource.class,
referenceFactoryService.getDataSourceInjector());
referenceBuilder.install();
final ContextNames.BindInfo bindInfo = ContextNames.bindInfoFor(jndiName);
final BinderService binderService = new BinderService(bindInfo.getBindName());
final ServiceBuilder<?> binderBuilder = serviceTarget
.addService(bindInfo.getBinderServiceName(), binderService)
.addDependency(referenceFactoryServiceName, ManagedReferenceFactory.class, binderService.getManagedObjectInjector())
.addDependency(bindInfo.getParentContextServiceName(), ServiceBasedNamingStore.class, binderService.getNamingStoreInjector()).addListener(new AbstractServiceListener<Object>() {
public void transition(final ServiceController<? extends Object> controller, final ServiceController.Transition transition) {
switch (transition) {
case STARTING_to_UP: {
if (jta) {
SUBSYSTEM_DATASOURCES_LOGGER.boundDataSource(jndiName);
} else {
SUBSYSTEM_DATASOURCES_LOGGER.boundNonJTADataSource(jndiName);
}
break;
}
case STOPPING_to_DOWN: {
if (jta) {
SUBSYSTEM_DATASOURCES_LOGGER.unboundDataSource(jndiName);
} else {
SUBSYSTEM_DATASOURCES_LOGGER.unBoundNonJTADataSource(jndiName);
}
break;
}
case REMOVING_to_REMOVED: {
SUBSYSTEM_DATASOURCES_LOGGER.debugf("Removed JDBC Data-source [%s]", jndiName);
break;
}
}
}
});
binderBuilder.setInitialMode(ServiceController.Mode.ACTIVE);
binderBuilder.install();
}
protected abstract void startConfigAndAddDependency(ServiceBuilder<?> dataSourceServiceBuilder,
AbstractDataSourceService dataSourceService, String jndiName, ServiceTarget serviceTarget, final ModelNode operation)
throws OperationFailedException;
protected abstract AbstractDataSourceService createDataSourceService(final String dsName, final String jndiName) throws OperationFailedException;
protected abstract boolean isXa();
static Collection<AttributeDefinition> join(final AttributeDefinition[] a, final AttributeDefinition[] b) {
final List<AttributeDefinition> result = new ArrayList<>();
result.addAll(Arrays.asList(a));
result.addAll(Arrays.asList(b));
return result;
}
}