/* * JBoss, Home of Professional Open Source. * Copyright 2012, 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; import static org.wildfly.extension.messaging.activemq.logging.MessagingLogger.ROOT_LOGGER; import java.net.InetSocketAddress; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.function.Consumer; import javax.management.MBeanServer; import javax.sql.DataSource; import org.apache.activemq.artemis.api.config.ActiveMQDefaultConfiguration; import org.apache.activemq.artemis.api.core.BroadcastGroupConfiguration; import org.apache.activemq.artemis.api.core.DiscoveryGroupConfiguration; import org.apache.activemq.artemis.api.core.Interceptor; import org.apache.activemq.artemis.api.core.TransportConfiguration; import org.apache.activemq.artemis.core.config.BridgeConfiguration; import org.apache.activemq.artemis.core.config.Configuration; import org.apache.activemq.artemis.core.config.storage.DatabaseStorageConfiguration; import org.apache.activemq.artemis.core.io.aio.AIOSequentialFileFactory; import org.apache.activemq.artemis.core.remoting.impl.netty.TransportConstants; import org.apache.activemq.artemis.core.server.ActiveMQServer; import org.apache.activemq.artemis.core.server.JournalType; import org.apache.activemq.artemis.core.server.impl.ActiveMQServerImpl; import org.apache.activemq.artemis.spi.core.security.ActiveMQSecurityManager; import org.jboss.as.controller.OperationContext; import org.jboss.as.controller.services.path.AbsolutePathService; import org.jboss.as.controller.services.path.PathManager; import org.jboss.as.network.ClientMapping; import org.jboss.as.network.ManagedBinding; import org.jboss.as.network.NetworkUtils; import org.jboss.as.network.OutboundSocketBinding; import org.jboss.as.network.SocketBinding; import org.jboss.as.security.plugins.SecurityDomainContext; import org.jboss.msc.inject.Injector; import org.jboss.msc.inject.MapInjector; import org.jboss.msc.service.Service; import org.jboss.msc.service.ServiceController; import org.jboss.msc.service.ServiceName; import org.jboss.msc.service.StartContext; import org.jboss.msc.service.StartException; import org.jboss.msc.service.StopContext; import org.jboss.msc.value.InjectedValue; import org.jgroups.JChannel; import org.wildfly.clustering.jgroups.spi.ChannelFactory; import org.wildfly.common.function.ExceptionSupplier; import org.wildfly.extension.messaging.activemq.logging.MessagingLogger; import org.wildfly.security.auth.server.SecurityDomain; import org.wildfly.security.credential.PasswordCredential; import org.wildfly.security.credential.source.CredentialSource; import org.wildfly.security.password.interfaces.ClearPassword; /** * Service configuring and starting the {@code ActiveMQServerService}. * * @author scott.stark@jboss.org * @author Emanuel Muckenhuber */ class ActiveMQServerService implements Service<ActiveMQServer> { /** */ private static final String HOST = "host"; private static final String PORT = "port"; /** * The name of the SocketBinding reference to use for HOST/PORT * configuration */ private static final String SOCKET_REF = RemoteTransportDefinition.SOCKET_BINDING.getName(); private Configuration configuration; private ActiveMQServer server; private Map<String, SocketBinding> socketBindings = new HashMap<String, SocketBinding>(); private Map<String, OutboundSocketBinding> outboundSocketBindings = new HashMap<String, OutboundSocketBinding>(); private Map<String, SocketBinding> groupBindings = new HashMap<String, SocketBinding>(); private final InjectedValue<PathManager> pathManager = new InjectedValue<PathManager>(); private final InjectedValue<MBeanServer> mbeanServer = new InjectedValue<MBeanServer>(); // Injected DataSource for JDBC store use (can be null). private final InjectedValue<DataSource> dataSource = new InjectedValue<>(); private final InjectedValue<SecurityDomainContext> securityDomainContextValue = new InjectedValue<SecurityDomainContext>(); private final InjectedValue<SecurityDomain> elytronSecurityDomain = new InjectedValue<>(); private final PathConfig pathConfig; // mapping between the {broadcast|discovery}-groups and the *names* of the JGroups channel they use private final Map<String, String> jgroupsChannels = new HashMap<String, String>(); // mapping between the {broadcast|discovery}-groups and the JGroups channel factory for the *stack* they use private Map<String, ChannelFactory> jgroupFactories = new HashMap<String, ChannelFactory>(); // broadcast-group and discovery-groups configured with JGroups must share the same channel private final Map<String, JChannel> channels = new HashMap<String, JChannel>(); private final List<Interceptor> incomingInterceptors = new ArrayList<>(); private final List<Interceptor> outgoingInterceptors = new ArrayList<>(); // credential source injectors private Map<String, InjectedValue<ExceptionSupplier<CredentialSource, Exception>>> bridgeCredentialSource = new HashMap<>(); private InjectedValue<ExceptionSupplier<CredentialSource, Exception>> clusterCredentialSource = new InjectedValue<>(); public ActiveMQServerService(Configuration configuration, PathConfig pathConfig) { this.configuration = configuration; this.pathConfig = pathConfig; if (configuration != null) { for (BridgeConfiguration bridgeConfiguration : configuration.getBridgeConfigurations()) { bridgeCredentialSource.put(bridgeConfiguration.getName(), new InjectedValue<>()); } } } Injector<PathManager> getPathManagerInjector(){ return pathManager; } Injector<SocketBinding> getSocketBindingInjector(String name) { return new MapInjector<String, SocketBinding>(socketBindings, name); } Injector<ChannelFactory> getJGroupsInjector(String name) { return new MapInjector<String, ChannelFactory>(jgroupFactories, name); } Injector<OutboundSocketBinding> getOutboundSocketBindingInjector(String name) { return new MapInjector<String, OutboundSocketBinding>(outboundSocketBindings, name); } Injector<SocketBinding> getGroupBindingInjector(String name) { return new MapInjector<String, SocketBinding>(groupBindings, name); } InjectedValue<MBeanServer> getMBeanServer() { return mbeanServer; } InjectedValue<DataSource> getDataSource() { return dataSource; } Map<String, JChannel> getChannels() { return channels; } protected List<Interceptor> getIncomingInterceptors() { return incomingInterceptors; } protected List<Interceptor> getOutgoingInterceptors() { return outgoingInterceptors; } public synchronized void start(final StartContext context) throws StartException { ClassLoader origTCCL = org.wildfly.security.manager.WildFlySecurityManager.getCurrentContextClassLoaderPrivileged(); // Validate whether the AIO native layer can be used JournalType jtype = configuration.getJournalType(); if (jtype == JournalType.ASYNCIO) { boolean supportsAIO = AIOSequentialFileFactory.isSupported(); if (supportsAIO == false) { String osName = System.getProperty("os.name").toLowerCase(Locale.ENGLISH); if (osName.contains("nux")){ ROOT_LOGGER.aioInfoLinux(); } else { ROOT_LOGGER.aioInfo(); } configuration.setJournalType(JournalType.NIO); } } // Setup paths PathManager pathManager = this.pathManager.getValue(); configuration.setBindingsDirectory(pathConfig.resolveBindingsPath(pathManager)); configuration.setLargeMessagesDirectory(pathConfig.resolveLargeMessagePath(pathManager)); configuration.setJournalDirectory(pathConfig.resolveJournalPath(pathManager)); configuration.setPagingDirectory(pathConfig.resolvePagingPath(pathManager)); pathConfig.registerCallbacks(pathManager); try { // Update the acceptor/connector port/host values from the // Map the socket bindings onto the connectors/acceptors Collection<TransportConfiguration> acceptors = configuration.getAcceptorConfigurations(); Collection<TransportConfiguration> connectors = configuration.getConnectorConfigurations().values(); Collection<BroadcastGroupConfiguration> broadcastGroups = configuration.getBroadcastGroupConfigurations(); Map<String, DiscoveryGroupConfiguration> discoveryGroups = configuration.getDiscoveryGroupConfigurations(); if (connectors != null) { for (TransportConfiguration tc : connectors) { // If there is a socket binding set the HOST/PORT values Object socketRef = tc.getParams().remove(SOCKET_REF); if (socketRef != null) { String name = socketRef.toString(); String host; int port; OutboundSocketBinding binding = outboundSocketBindings.get(name); if (binding == null) { final SocketBinding socketBinding = socketBindings.get(name); if (socketBinding == null) { throw MessagingLogger.ROOT_LOGGER.failedToFindConnectorSocketBinding(tc.getName()); } if (socketBinding.getClientMappings() != null && !socketBinding.getClientMappings().isEmpty()) { // At the moment ActiveMQ doesn't allow to select mapping based on client's network. // Instead the first client-mapping element will always be used - see WFLY-8432 ClientMapping clientMapping = socketBinding.getClientMappings().get(0); host = NetworkUtils.canonize(clientMapping.getDestinationAddress()); port = clientMapping.getDestinationPort(); if (socketBinding.getClientMappings().size() > 1) { MessagingLogger.ROOT_LOGGER.multipleClientMappingsFound(tc.getName(), host, port); } } else { InetSocketAddress sa = socketBinding.getSocketAddress(); port = sa.getPort(); // resolve the host name of the address only if a loopback address has been set if (sa.getAddress().isLoopbackAddress()) { host = NetworkUtils.canonize(sa.getAddress().getHostName()); } else { host = NetworkUtils.canonize(sa.getAddress().getHostAddress()); } } } else { port = binding.getDestinationPort(); host = NetworkUtils.canonize(binding.getUnresolvedDestinationAddress()); if (binding.getSourceAddress() != null) { tc.getParams().put(TransportConstants.LOCAL_ADDRESS_PROP_NAME, NetworkUtils.canonize(binding.getSourceAddress().getHostAddress())); } if (binding.getSourcePort() != null) { // Use absolute port to account for source port offset/fixation tc.getParams().put(TransportConstants.LOCAL_PORT_PROP_NAME, binding.getAbsoluteSourcePort()); } } tc.getParams().put(HOST, host); tc.getParams().put(PORT, port); } } } if (acceptors != null) { for (TransportConfiguration tc : acceptors) { // If there is a socket binding set the HOST/PORT values Object socketRef = tc.getParams().remove(SOCKET_REF); if (socketRef != null) { String name = socketRef.toString(); SocketBinding binding = socketBindings.get(name); if (binding == null) { throw MessagingLogger.ROOT_LOGGER.failedToFindConnectorSocketBinding(tc.getName()); } binding.getSocketBindings().getNamedRegistry().registerBinding(ManagedBinding.Factory.createSimpleManagedBinding(binding)); InetSocketAddress socketAddress = binding.getSocketAddress(); tc.getParams().put(HOST, socketAddress.getAddress().getHostAddress()); tc.getParams().put(PORT, socketAddress.getPort()); } } } if(broadcastGroups != null) { final List<BroadcastGroupConfiguration> newConfigs = new ArrayList<BroadcastGroupConfiguration>(); for(final BroadcastGroupConfiguration config : broadcastGroups) { final String name = config.getName(); final String key = "broadcast" + name; if (jgroupFactories.containsKey(key)) { ChannelFactory channelFactory = jgroupFactories.get(key); String channelName = jgroupsChannels.get(key); JChannel channel = (JChannel) channelFactory.createChannel(channelName); channels.put(channelName, channel); newConfigs.add(BroadcastGroupAdd.createBroadcastGroupConfiguration(name, config, channel, channelName)); } else { final SocketBinding binding = groupBindings.get(key); if (binding == null) { throw MessagingLogger.ROOT_LOGGER.failedToFindBroadcastSocketBinding(name); } binding.getSocketBindings().getNamedRegistry().registerBinding(ManagedBinding.Factory.createSimpleManagedBinding(binding)); newConfigs.add(BroadcastGroupAdd.createBroadcastGroupConfiguration(name, config, binding)); } } configuration.getBroadcastGroupConfigurations().clear(); configuration.getBroadcastGroupConfigurations().addAll(newConfigs); } if(discoveryGroups != null) { configuration.setDiscoveryGroupConfigurations(new HashMap<String, DiscoveryGroupConfiguration>()); for(final Map.Entry<String, DiscoveryGroupConfiguration> entry : discoveryGroups.entrySet()) { final String name = entry.getKey(); final String key = "discovery" + name; DiscoveryGroupConfiguration config = null; if (jgroupFactories.containsKey(key)) { ChannelFactory channelFactory = jgroupFactories.get(key); String channelName = jgroupsChannels.get(key); JChannel channel = channels.get(channelName); if (channel == null) { channel = (JChannel) channelFactory.createChannel(channelName); channels.put(channelName, channel); } config = DiscoveryGroupAdd.createDiscoveryGroupConfiguration(name, entry.getValue(), channel, channelName); } else { final SocketBinding binding = groupBindings.get(key); if (binding == null) { throw MessagingLogger.ROOT_LOGGER.failedToFindDiscoverySocketBinding(name); } config = DiscoveryGroupAdd.createDiscoveryGroupConfiguration(name, entry.getValue(), binding); binding.getSocketBindings().getNamedRegistry().registerBinding(ManagedBinding.Factory.createSimpleManagedBinding(binding)); } configuration.getDiscoveryGroupConfigurations().put(name, config); } } // security - if an Elytron domain has been defined we delegate security checks to the Elytron based security manager. ActiveMQSecurityManager securityManager = null; final SecurityDomain elytronDomain = this.elytronSecurityDomain.getOptionalValue(); if (elytronDomain != null) { securityManager = new ElytronSecurityManager(elytronDomain); } else { securityManager = new WildFlySecurityManager(securityDomainContextValue.getValue()); } // insert possible credential source hold passwords setBridgePasswordsFromCredentialSource(); setClusterPasswordFromCredentialSource(); DataSource ds = dataSource.getOptionalValue(); if (ds != null) { DatabaseStorageConfiguration dbConfiguration = (DatabaseStorageConfiguration) configuration.getStoreConfiguration(); dbConfiguration.setDataSource(ds); // inject the datasource into the PropertySQLProviderFactory to be able to determine the // type of database for the datasource metadata PropertySQLProviderFactory sqlProviderFactory = (PropertySQLProviderFactory)dbConfiguration.getSqlProviderFactory(); sqlProviderFactory.investigateDialect(ds); configuration.setStoreConfiguration(dbConfiguration); ROOT_LOGGER.infof("use JDBC store for Artemis server, bindingsTable:%s", dbConfiguration.getBindingsTableName()); } // Now start the server server = new ActiveMQServerImpl(configuration, mbeanServer.getOptionalValue(), securityManager); if (ActiveMQDefaultConfiguration.getDefaultClusterPassword().equals(server.getConfiguration().getClusterPassword())) { server.getConfiguration().setClusterPassword(java.util.UUID.randomUUID().toString()); } for (Interceptor incomingInterceptor : incomingInterceptors) { server.getServiceRegistry().addIncomingInterceptor(incomingInterceptor); } for (Interceptor outgoingInterceptor : outgoingInterceptors) { server.getServiceRegistry().addOutgoingInterceptor(outgoingInterceptor); } // the server is actually started by the JMSService. } catch (Exception e) { throw MessagingLogger.ROOT_LOGGER.failedToStartService(e); } finally { org.wildfly.security.manager.WildFlySecurityManager.setCurrentContextClassLoaderPrivileged(origTCCL); } } public synchronized void stop(final StopContext context) { try { if (server != null) { for (SocketBinding binding : socketBindings.values()) { if (binding != null) { binding.getSocketBindings().getNamedRegistry().unregisterBinding(binding.getName()); } } for (SocketBinding binding : groupBindings.values()) { if (binding != null) { binding.getSocketBindings().getNamedRegistry().unregisterBinding(binding.getName()); } } // the server is actually stopped by the JMS Service } pathConfig.closeCallbacks(pathManager.getValue()); } catch (Exception e) { throw MessagingLogger.ROOT_LOGGER.failedToShutdownServer(e, "Artemis"); } } public synchronized ActiveMQServer getValue() throws IllegalStateException { final ActiveMQServer server = this.server; if (server == null) { throw new IllegalStateException(); } return server; } public Injector<SecurityDomainContext> getSecurityDomainContextInjector() { return securityDomainContextValue; } public Injector<SecurityDomain> getElytronDomainInjector() { return this.elytronSecurityDomain; } public Map<String, String> getJGroupsChannels() { return jgroupsChannels; } /** * Returns true if a {@link ServiceController} for this service has been {@link org.jboss.msc.service.ServiceBuilder#install() installed} * in MSC under the * {@link MessagingServices#getActiveMQServiceName(org.jboss.as.controller.PathAddress) service name appropriate to the given operation}. * * @param context the operation context * @return {@code true} if a {@link ServiceController} is installed */ static boolean isServiceInstalled(final OperationContext context) { if (context.isNormalServer()) { final ServiceName serviceName = MessagingServices.getActiveMQServiceName(context.getCurrentAddress()); final ServiceController<?> controller = context.getServiceRegistry(false).getService(serviceName); return controller != null; } return false; } static class PathConfig { private final String bindingsPath; private final String bindingsRelativeToPath; private final String journalPath; private final String journalRelativeToPath; private final String largeMessagePath; private final String largeMessageRelativeToPath; private final String pagingPath; private final String pagingRelativeToPath; private final List<PathManager.Callback.Handle> callbackHandles = new ArrayList<PathManager.Callback.Handle>(); public PathConfig(String bindingsPath, String bindingsRelativeToPath, String journalPath, String journalRelativeToPath, String largeMessagePath, String largeMessageRelativeToPath, String pagingPath, String pagingRelativeToPath) { this.bindingsPath = bindingsPath; this.bindingsRelativeToPath = bindingsRelativeToPath; this.journalPath = journalPath; this.journalRelativeToPath = journalRelativeToPath; this.largeMessagePath = largeMessagePath; this.largeMessageRelativeToPath = largeMessageRelativeToPath; this.pagingPath = pagingPath; this.pagingRelativeToPath = pagingRelativeToPath; } String resolveBindingsPath(PathManager pathManager) { return resolve(pathManager, bindingsPath, bindingsRelativeToPath); } String resolveJournalPath(PathManager pathManager) { return resolve(pathManager, journalPath, journalRelativeToPath); } String resolveLargeMessagePath(PathManager pathManager) { return resolve(pathManager, largeMessagePath, largeMessageRelativeToPath); } String resolvePagingPath(PathManager pathManager) { return resolve(pathManager, pagingPath, pagingRelativeToPath); } String resolve(PathManager pathManager, String path, String relativeToPath) { // discard the relativeToPath if the path is absolute and must not be resolved according // to the default relativeToPath value String relativeTo = AbsolutePathService.isAbsoluteUnixOrWindowsPath(path) ? null : relativeToPath; return pathManager.resolveRelativePathEntry(path, relativeTo); } synchronized void registerCallbacks(PathManager pathManager) { if (bindingsRelativeToPath != null) { callbackHandles.add(pathManager.registerCallback(bindingsRelativeToPath, PathManager.ReloadServerCallback.create(), PathManager.Event.UPDATED, PathManager.Event.REMOVED)); } if (journalRelativeToPath != null) { callbackHandles.add(pathManager.registerCallback(journalRelativeToPath, PathManager.ReloadServerCallback.create(), PathManager.Event.UPDATED, PathManager.Event.REMOVED)); } if (largeMessageRelativeToPath != null) { callbackHandles.add(pathManager.registerCallback(largeMessageRelativeToPath, PathManager.ReloadServerCallback.create(), PathManager.Event.UPDATED, PathManager.Event.REMOVED)); } if (pagingRelativeToPath != null) { callbackHandles.add(pathManager.registerCallback(pagingRelativeToPath, PathManager.ReloadServerCallback.create(), PathManager.Event.UPDATED, PathManager.Event.REMOVED)); } } synchronized void closeCallbacks(PathManager pathManager) { for (PathManager.Callback.Handle callbackHandle : callbackHandles) { callbackHandle.remove(); } callbackHandles.clear(); } } /** * Get {@link CredentialSource} injector based on name of the bridge. * If name was not used create new injector. * @param name the bridge name * @return injector */ public InjectedValue<ExceptionSupplier<CredentialSource, Exception>> getBridgeCredentialSourceSupplierInjector(String name) { if (bridgeCredentialSource.containsKey(name)) { return bridgeCredentialSource.get(name); } else { InjectedValue<ExceptionSupplier<CredentialSource, Exception>> injector = new InjectedValue<>(); bridgeCredentialSource.put(name, injector); return injector; } } /** * Get {@link CredentialSource} injector based on name of the cluster credential. * @param name the cluster credential name * @return injector */ public InjectedValue<ExceptionSupplier<CredentialSource, Exception>> getClusterCredentialSourceSupplierInjector() { return clusterCredentialSource; } private void setBridgePasswordsFromCredentialSource() { if (configuration != null) { for (BridgeConfiguration bridgeConfiguration : configuration.getBridgeConfigurations()) { setNewPassword(getBridgeCredentialSourceSupplierInjector(bridgeConfiguration.getName()).getOptionalValue(), bridgeConfiguration::setPassword); } } } private void setClusterPasswordFromCredentialSource() { if (configuration != null) setNewPassword(getClusterCredentialSourceSupplierInjector().getOptionalValue(), configuration::setClusterPassword); } private void setNewPassword(ExceptionSupplier<CredentialSource, Exception> credentialSourceSupplier, Consumer<String> passwordConsumer) { if (credentialSourceSupplier != null) { try { CredentialSource credentialSource = credentialSourceSupplier.get(); if (credentialSource != null) { char[] password = credentialSource.getCredential(PasswordCredential.class).getPassword(ClearPassword.class).getPassword(); if (password != null) { passwordConsumer.accept(new String(password)); } } } catch (Exception e) { throw new RuntimeException(e); } } } }