/*
* 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.messaging.activemq.deployment;
import static org.wildfly.extension.messaging.activemq.CommonAttributes.CONNECTORS;
import static org.wildfly.extension.messaging.activemq.CommonAttributes.DEFAULT;
import static org.wildfly.extension.messaging.activemq.CommonAttributes.JGROUPS_CHANNEL;
import static org.wildfly.extension.messaging.activemq.CommonAttributes.NO_TX;
import static org.wildfly.extension.messaging.activemq.CommonAttributes.POOLED_CONNECTION_FACTORY;
import static org.wildfly.extension.messaging.activemq.CommonAttributes.SERVER;
import static org.wildfly.extension.messaging.activemq.CommonAttributes.XA_TX;
import static org.wildfly.extension.messaging.activemq.jms.ConnectionFactoryAttributes.Common.DISCOVERY_GROUP;
import static org.wildfly.extension.messaging.activemq.jms.ConnectionFactoryAttributes.Pooled.ENLISTMENT_TRACE;
import static org.wildfly.extension.messaging.activemq.jms.ConnectionFactoryAttributes.Pooled.MANAGED_CONNECTION_POOL;
import static org.wildfly.extension.messaging.activemq.jms.ConnectionFactoryAttributes.Pooled.MAX_POOL_SIZE;
import static org.wildfly.extension.messaging.activemq.jms.ConnectionFactoryAttributes.Pooled.MIN_POOL_SIZE;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.resource.spi.TransactionSupport;
import org.jboss.as.connector.deployers.ra.ConnectionFactoryDefinitionInjectionSource;
import org.jboss.as.connector.services.resourceadapters.ConnectionFactoryReferenceFactoryService;
import org.jboss.as.controller.OperationFailedException;
import org.jboss.as.controller.PathAddress;
import org.jboss.as.controller.PathElement;
import org.jboss.as.ee.component.InjectionSource;
import org.jboss.as.ee.resource.definition.ResourceDefinitionInjectionSource;
import org.jboss.as.naming.ManagedReferenceFactory;
import org.jboss.as.naming.deployment.ContextNames;
import org.jboss.as.server.deployment.Attachments;
import org.jboss.as.server.deployment.DeploymentPhaseContext;
import org.jboss.as.server.deployment.DeploymentResourceSupport;
import org.jboss.as.server.deployment.DeploymentUnit;
import org.jboss.as.server.deployment.DeploymentUnitProcessingException;
import org.jboss.dmr.ModelNode;
import org.jboss.dmr.Property;
import org.jboss.msc.inject.Injector;
import org.jboss.msc.service.ServiceBuilder;
import org.jboss.msc.service.ServiceName;
import org.jboss.msc.service.ServiceRegistry;
import org.jboss.msc.service.ServiceTarget;
import org.wildfly.extension.messaging.activemq.CommonAttributes;
import org.wildfly.extension.messaging.activemq.MessagingExtension;
import org.wildfly.extension.messaging.activemq.MessagingServices;
import org.wildfly.extension.messaging.activemq.jms.ConnectionFactoryAttribute;
import org.wildfly.extension.messaging.activemq.jms.ConnectionFactoryAttributes;
import org.wildfly.extension.messaging.activemq.jms.JMSServices;
import org.wildfly.extension.messaging.activemq.jms.PooledConnectionFactoryConfigProperties;
import org.wildfly.extension.messaging.activemq.jms.PooledConnectionFactoryConfigurationRuntimeHandler;
import org.wildfly.extension.messaging.activemq.jms.PooledConnectionFactoryDefinition;
import org.wildfly.extension.messaging.activemq.jms.PooledConnectionFactoryService;
import org.wildfly.extension.messaging.activemq.logging.MessagingLogger;
/**
* @author <a href="http://jmesnil.net/">Jeff Mesnil</a> (c) 2013 Red Hat inc.
* @author Eduardo Martins
*/
public class JMSConnectionFactoryDefinitionInjectionSource extends ResourceDefinitionInjectionSource {
/*
String description() default "";
String name();
String interfaceName() default "javax.jms.ConnectionFactory";
String className() default "";
String resourceAdapter() default "";
String user() default "";
String password() default "";
String clientId() default "";
String[] properties() default {};
boolean transactional() default true;
int maxPoolSize() default -1;
int minPoolSize() default -1;
*/
// not used: ActiveMQ CF implements all JMS CF interfaces
private String interfaceName;
// not used
private String className;
private String resourceAdapter;
private String user;
private String password;
private String clientId;
private boolean transactional;
private int maxPoolSize;
private int minPoolSize;
public JMSConnectionFactoryDefinitionInjectionSource(String jndiName) {
super(jndiName);
}
void setInterfaceName(String interfaceName) {
this.interfaceName = interfaceName;
}
void setClassName(String className) {
this.className = className;
}
void setResourceAdapter(String resourceAdapter) {
this.resourceAdapter = resourceAdapter;
}
void setUser(String user) {
this.user = user;
}
void setPassword(String password) {
this.password = password;
}
void setClientId(String clientId) {
this.clientId = clientId;
}
void setTransactional(boolean transactional) {
this.transactional = transactional;
}
void setMaxPoolSize(int maxPoolSize) {
this.maxPoolSize = maxPoolSize;
}
void setMinPoolSize(int minPoolSize) {
this.minPoolSize = minPoolSize;
}
@Override
public void getResourceValue(ResolutionContext context, ServiceBuilder<?> serviceBuilder, DeploymentPhaseContext phaseContext, Injector<ManagedReferenceFactory> injector) throws DeploymentUnitProcessingException {
final DeploymentUnit deploymentUnit = phaseContext.getDeploymentUnit();
if (targetsPooledConnectionFactory(getActiveMQServerName(properties), resourceAdapter, phaseContext.getServiceRegistry())) {
try {
startedPooledConnectionFactory(context, jndiName, serviceBuilder, phaseContext.getServiceTarget(), deploymentUnit, injector);
} catch (OperationFailedException e) {
throw new DeploymentUnitProcessingException(e);
}
} else {
// delegate to the resource-adapter subsystem to create a generic JCA connection factory.
ConnectionFactoryDefinitionInjectionSource cfdis = new ConnectionFactoryDefinitionInjectionSource(jndiName, interfaceName, resourceAdapter);
cfdis.setMaxPoolSize(maxPoolSize);
cfdis.setMinPoolSize(minPoolSize);
cfdis.setTransactionSupportLevel(transactional ? TransactionSupport.TransactionSupportLevel.XATransaction : TransactionSupport.TransactionSupportLevel.NoTransaction);
// transfer all the generic properties + the additional properties specific to the JMSConnectionFactoryDefinition
for (Map.Entry<String, String> property : properties.entrySet()) {
cfdis.addProperty(property.getKey(), property.getValue());
}
if (!user.isEmpty()) {
cfdis.addProperty("user", user);
}
if (!password.isEmpty()) {
cfdis.addProperty("password", password);
}
if (!clientId.isEmpty()) {
cfdis.addProperty("clientId", clientId);
}
cfdis.getResourceValue(context, serviceBuilder, phaseContext, injector);
}
}
private void startedPooledConnectionFactory(ResolutionContext context, String name, ServiceBuilder<?> serviceBuilder, ServiceTarget serviceTarget, DeploymentUnit deploymentUnit, Injector<ManagedReferenceFactory> injector) throws DeploymentUnitProcessingException, OperationFailedException {
Map<String, String> props = new HashMap<>(properties);
List<String> connectors = getConnectors(props);
clearUnknownProperties(properties);
ModelNode model = new ModelNode();
for (String connector : connectors) {
model.get(CONNECTORS).add(connector);
}
for (Map.Entry<String, String> entry : properties.entrySet()) {
model.get(entry.getKey()).set(entry.getValue());
}
model.get(MIN_POOL_SIZE.getName()).set(minPoolSize);
model.get(MAX_POOL_SIZE.getName()).set(maxPoolSize);
if (user != null && !user.isEmpty()) {
model.get(ConnectionFactoryAttributes.Pooled.USER.getName()).set(user);
}
if (password != null && !password.isEmpty()) {
model.get(ConnectionFactoryAttributes.Pooled.PASSWORD.getName()).set(password);
}
if (clientId != null && !clientId.isEmpty()) {
model.get(CommonAttributes.CLIENT_ID.getName()).set(clientId);
}
final String discoveryGroupName = properties.containsKey(DISCOVERY_GROUP.getName()) ? properties.get(DISCOVERY_GROUP.getName()) : null;
if (discoveryGroupName != null) {
model.get(DISCOVERY_GROUP.getName()).set(discoveryGroupName);
}
final String jgroupsChannelName = properties.containsKey(JGROUPS_CHANNEL.getName()) ? properties.get(JGROUPS_CHANNEL.getName()) : null;
if (jgroupsChannelName != null) {
model.get(JGROUPS_CHANNEL.getName()).set(jgroupsChannelName);
}
final String managedConnectionPoolClassName = properties.containsKey(MANAGED_CONNECTION_POOL.getName()) ? properties.get(MANAGED_CONNECTION_POOL.getName()) : null;
if (managedConnectionPoolClassName != null) {
model.get(MANAGED_CONNECTION_POOL.getName()).set(managedConnectionPoolClassName);
}
final Boolean enlistmentTrace = properties.containsKey(ENLISTMENT_TRACE.getName()) ? Boolean.valueOf(properties.get(ENLISTMENT_TRACE.getName())) : null;
List<PooledConnectionFactoryConfigProperties> adapterParams = getAdapterParams(model);
String txSupport = transactional ? XA_TX : NO_TX;
final String serverName = getActiveMQServerName(properties);
final String pcfName = uniqueName(context, name);
final ContextNames.BindInfo bindInfo = ContextNames.bindInfoForEnvEntry(context.getApplicationName(), context.getModuleName(), context.getComponentName(), !context.isCompUsesModule(), name);
PooledConnectionFactoryService.installService(serviceTarget, pcfName, serverName, connectors,
discoveryGroupName, jgroupsChannelName, adapterParams,
bindInfo,
txSupport, minPoolSize, maxPoolSize, managedConnectionPoolClassName, enlistmentTrace, true);
final ServiceName referenceFactoryServiceName = ConnectionFactoryReferenceFactoryService.SERVICE_NAME_BASE
.append(bindInfo.getBinderServiceName());
serviceBuilder.addDependency(referenceFactoryServiceName, ManagedReferenceFactory.class, injector);
//create the management registration
String managementName = managementName(context, name);
final PathElement serverElement = PathElement.pathElement(SERVER, serverName);
final DeploymentResourceSupport deploymentResourceSupport = deploymentUnit.getAttachment(Attachments.DEPLOYMENT_RESOURCE_SUPPORT);
deploymentResourceSupport.getDeploymentSubModel(MessagingExtension.SUBSYSTEM_NAME, serverElement);
final PathElement pcfPath = PathElement.pathElement(POOLED_CONNECTION_FACTORY, managementName);
PathAddress registration = PathAddress.pathAddress(serverElement, pcfPath);
MessagingXmlInstallDeploymentUnitProcessor.createDeploymentSubModel(registration, deploymentUnit);
PooledConnectionFactoryConfigurationRuntimeHandler.INSTANCE.registerResource(serverName, managementName, model);
}
private List<String> getConnectors(Map<String, String> props) {
List<String> connectors = new ArrayList<>();
if (props.containsKey(CONNECTORS)) {
String connectorsStr = properties.remove(CONNECTORS);
for (String s : connectorsStr.split(",")) {
String connector = s.trim();
if (!connector.isEmpty()) {
connectors.add(connector);
}
}
}
return connectors;
}
void clearUnknownProperties(final Map<String, String> props) {
Set<String> attributeNames = PooledConnectionFactoryDefinition.getAttributesMap().keySet();
final Iterator<Map.Entry<String, String>> it = props.entrySet().iterator();
while (it.hasNext()) {
final Map.Entry<String, String> entry = it.next();
String value = entry.getKey();
if (value == null || "".equals(value)) {
it.remove();
} else if (!attributeNames.contains(entry.getKey())) {
MessagingLogger.ROOT_LOGGER.unknownPooledConnectionFactoryAttribute(entry.getKey());
it.remove();
}
}
}
private static String uniqueName(InjectionSource.ResolutionContext context, final String jndiName) {
StringBuilder uniqueName = new StringBuilder();
return uniqueName.append(context.getApplicationName() + "_")
.append(managementName(context, jndiName))
.toString();
}
private static String managementName(InjectionSource.ResolutionContext context, final String jndiName) {
StringBuilder uniqueName = new StringBuilder();
uniqueName.append(context.getModuleName() + "_");
if (context.getComponentName() != null) {
uniqueName.append(context.getComponentName() + "_");
}
return uniqueName
.append(jndiName.replace(':', '_'))
.toString();
}
private List<PooledConnectionFactoryConfigProperties> getAdapterParams(ModelNode model) {
Map<String, ConnectionFactoryAttribute> attributes = PooledConnectionFactoryDefinition.getAttributesMap();
List<PooledConnectionFactoryConfigProperties> props = new ArrayList<>();
for (Property property : model.asPropertyList()) {
ConnectionFactoryAttribute attribute = attributes.get(property.getName());
if (attribute.getPropertyName() == null) {
// not a RA property
continue;
}
props.add(new PooledConnectionFactoryConfigProperties(attribute.getPropertyName(), property.getValue().asString(), attribute.getClassType()));
}
return props;
}
/**
* Return whether the definition targets an existing pooled connection factory or use a JCA-based ConnectionFactory.
*
* Checks the service registry for a PooledConnectionFactoryService with the ServiceName
* created by the {@code server} property (or {@code "default") and the {@code resourceAdapter} property.
*/
static boolean targetsPooledConnectionFactory(String server, String resourceAdapter, ServiceRegistry serviceRegistry) {
// if the resourceAdapter is not defined, the default behaviour is to create a pooled-connection-factory.
if (resourceAdapter == null || resourceAdapter.isEmpty()) {
return true;
}
ServiceName activeMQServiceName = MessagingServices.getActiveMQServiceName(server);
ServiceName pcfName = JMSServices.getPooledConnectionFactoryBaseServiceName(activeMQServiceName).append(resourceAdapter);
return serviceRegistry.getServiceNames().contains(pcfName);
}
/**
* The JMS connection factory can specify another server to deploy its destinations
* by passing a property server=<name of the server>. Otherwise, "default" is used by default.
*/
static String getActiveMQServerName(Map<String, String> properties) {
return properties.containsKey(SERVER) ? properties.get(SERVER) : DEFAULT;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (!super.equals(o)) return false;
JMSConnectionFactoryDefinitionInjectionSource that = (JMSConnectionFactoryDefinitionInjectionSource) o;
if (maxPoolSize != that.maxPoolSize) return false;
if (minPoolSize != that.minPoolSize) return false;
if (transactional != that.transactional) return false;
if (className != null ? !className.equals(that.className) : that.className != null) return false;
if (clientId != null ? !clientId.equals(that.clientId) : that.clientId != null) return false;
if (interfaceName != null ? !interfaceName.equals(that.interfaceName) : that.interfaceName != null)
return false;
if (password != null ? !password.equals(that.password) : that.password != null) return false;
if (resourceAdapter != null ? !resourceAdapter.equals(that.resourceAdapter) : that.resourceAdapter != null)
return false;
if (user != null ? !user.equals(that.user) : that.user != null) return false;
return true;
}
@Override
public int hashCode() {
int result = super.hashCode();
result = 31 * result + (interfaceName != null ? interfaceName.hashCode() : 0);
result = 31 * result + (className != null ? className.hashCode() : 0);
result = 31 * result + (resourceAdapter != null ? resourceAdapter.hashCode() : 0);
result = 31 * result + (user != null ? user.hashCode() : 0);
result = 31 * result + (password != null ? password.hashCode() : 0);
result = 31 * result + (clientId != null ? clientId.hashCode() : 0);
result = 31 * result + (transactional ? 1 : 0);
result = 31 * result + maxPoolSize;
result = 31 * result + minPoolSize;
return result;
}
}