/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.activemq.broker;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.activemq.ActiveMQConnectionMetaData;
import org.apache.activemq.Service;
import org.apache.activemq.artemis.core.remoting.impl.netty.TransportConstants;
import org.apache.activemq.broker.artemiswrapper.ArtemisBrokerWrapper;
import org.apache.activemq.broker.jmx.BrokerView;
import org.apache.activemq.broker.jmx.ManagementContext;
import org.apache.activemq.broker.region.Destination;
import org.apache.activemq.broker.region.DestinationInterceptor;
import org.apache.activemq.broker.region.policy.PolicyMap;
import org.apache.activemq.broker.scheduler.JobSchedulerStore;
import org.apache.activemq.command.ActiveMQDestination;
import org.apache.activemq.command.BrokerId;
import org.apache.activemq.network.ConnectionFilter;
import org.apache.activemq.network.DiscoveryNetworkConnector;
import org.apache.activemq.network.NetworkConnector;
import org.apache.activemq.network.jms.JmsConnector;
import org.apache.activemq.proxy.ProxyConnector;
import org.apache.activemq.security.MessageAuthorizationPolicy;
import org.apache.activemq.spring.SpringSslContext;
import org.apache.activemq.store.PListStore;
import org.apache.activemq.store.PersistenceAdapter;
import org.apache.activemq.store.PersistenceAdapterFactory;
import org.apache.activemq.thread.TaskRunnerFactory;
import org.apache.activemq.transport.TransportServer;
import org.apache.activemq.usage.SystemUsage;
import org.apache.activemq.util.IOExceptionHandler;
import org.apache.activemq.util.IOHelper;
import org.apache.activemq.util.ServiceStopper;
import org.junit.rules.TemporaryFolder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Manages the life-cycle of an ActiveMQ Broker. A BrokerService consists of a
* number of transport connectors, network connectors and a bunch of properties
* which can be used to configure the broker as its lazily created.
*/
public class BrokerService implements Service {
public static final String DEFAULT_PORT = "61616";
public static final AtomicInteger RANDOM_PORT_BASE = new AtomicInteger(51616);
public static final String DEFAULT_BROKER_NAME = "localhost";
public static final String BROKER_VERSION;
public static final int DEFAULT_MAX_FILE_LENGTH = 1024 * 1024 * 32;
public static final long DEFAULT_START_TIMEOUT = 600000L;
public static boolean disableWrapper = false;
private SslContext sslContext;
private static final Logger LOG = LoggerFactory.getLogger(BrokerService.class);
@SuppressWarnings("unused")
private static final long serialVersionUID = 7353129142305630237L;
private String brokerName = DEFAULT_BROKER_NAME;
private Broker broker;
private BrokerId brokerId;
private Throwable startException = null;
private boolean startAsync = false;
public Set<ConnectorInfo> extraConnectors = new HashSet<>();
private List<TransportConnector> transportConnectors = new ArrayList<>();
private File dataDirectoryFile;
private PolicyMap destinationPolicy;
private SystemUsage systemUsage;
private final List<NetworkConnector> networkConnectors = new CopyOnWriteArrayList<>();
private TemporaryFolder tmpfolder;
public static WeakHashMap<Broker, Exception> map = new WeakHashMap<>();
static {
InputStream in;
String version = null;
if ((in = BrokerService.class.getResourceAsStream("/org/apache/activemq/version.txt")) != null) {
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
try {
version = reader.readLine();
} catch (Exception e) {
}
}
BROKER_VERSION = version;
}
@Override
public String toString() {
return "BrokerService[" + getBrokerName() + "]" + super.toString();
}
private String getBrokerVersion() {
String version = ActiveMQConnectionMetaData.PROVIDER_VERSION;
if (version == null) {
version = BROKER_VERSION;
}
return version;
}
@Override
public void start() throws Exception {
File targetTmp = new File("./target/tmp");
targetTmp.mkdirs();
tmpfolder = new TemporaryFolder(targetTmp);
tmpfolder.create();
Exception e = new Exception();
startBroker(startAsync);
map.put(broker, e);
}
private void startBroker(boolean async) throws Exception {
if (async) {
new Thread("Broker Starting Thread") {
@Override
public void run() {
try {
doStartBroker();
} catch (Throwable t) {
startException = t;
}
}
}.start();
} else {
doStartBroker();
}
}
private void doStartBroker() throws Exception {
if (startException != null) {
return;
}
broker = getBroker();
brokerId = broker.getBrokerId();
LOG.info("Apache ActiveMQ Artemis Wrapper {} ({}, {}) is starting", new Object[]{getBrokerVersion(), getBrokerName(), brokerId});
try {
broker.start();
} catch (Exception e) {
throw e;
} catch (Throwable t) {
throw new Exception(t);
}
LOG.info("Apache ActiveMQ Artemis Wrapper {} ({}, {}) started", new Object[]{getBrokerVersion(), getBrokerName(), brokerId});
LOG.info("For help or more information please see: http://activemq.apache.org");
}
@Override
public void stop() throws Exception {
System.out.println("broker is: " + broker);
LOG.info("Apache ActiveMQ Artemis{} ({}, {}) is shutting down", new Object[]{getBrokerVersion(), getBrokerName(), brokerId});
if (broker != null) {
broker.stop();
broker = null;
}
tmpfolder.delete();
LOG.info("Apache ActiveMQ Artemis {} ({}, {}) is shutdown", new Object[]{getBrokerVersion(), getBrokerName(), brokerId});
}
// Properties
// -------------------------------------------------------------------------
public Broker getBroker() throws Exception {
if (broker == null) {
broker = createBroker(tmpfolder.getRoot());
}
return broker;
}
public String getBrokerName() {
return brokerName;
}
public void setBrokerName(String brokerName) {
if (brokerName == null) {
throw new NullPointerException("The broker name cannot be null");
}
String str = brokerName.replaceAll("[^a-zA-Z0-9\\.\\_\\-\\:]", "_");
if (!str.equals(brokerName)) {
LOG.error("Broker Name: {} contained illegal characters - replaced with {}", brokerName, str);
}
this.brokerName = str.trim();
}
protected Broker createBroker(File temporaryFile) throws Exception {
broker = createBrokerWrapper(temporaryFile);
return broker;
}
private Broker createBrokerWrapper(File temporaryFile) {
return new ArtemisBrokerWrapper(this, temporaryFile);
}
public void makeSureDestinationExists(ActiveMQDestination activemqDestination) throws Exception {
ArtemisBrokerWrapper hqBroker = (ArtemisBrokerWrapper) this.broker;
//it can be null
if (activemqDestination == null) {
return;
}
if (activemqDestination.isQueue()) {
String qname = activemqDestination.getPhysicalName();
System.out.println("physical name: " + qname);
hqBroker.makeSureQueueExists(qname);
}
}
//below are methods called directly by tests
//we don't actually implement any of these for now,
//just to make test compile pass.
//we may get class cast exception as in TestSupport it
//casts the broker to RegionBroker, which we didn't
//implement (wrap) yet. Consider solving it later.
public Broker getRegionBroker() {
return broker;
}
public void setPersistenceAdapter(PersistenceAdapter persistenceAdapter) throws IOException {
}
public File getDataDirectoryFile() {
if (dataDirectoryFile == null) {
dataDirectoryFile = new File(IOHelper.getDefaultDataDirectory());
}
return dataDirectoryFile;
}
public File getBrokerDataDirectory() {
String brokerDir = getBrokerName();
return new File(getDataDirectoryFile(), brokerDir);
}
public PersistenceAdapter getPersistenceAdapter() throws IOException {
return null;
}
public void waitUntilStopped() {
}
public boolean waitUntilStarted() {
return true;
}
public void setDestinationPolicy(PolicyMap policyMap) {
this.destinationPolicy = policyMap;
}
public void setDeleteAllMessagesOnStartup(boolean deletePersistentMessagesOnStartup) {
}
public void setUseJmx(boolean useJmx) {
}
public ManagementContext getManagementContext() {
return null;
}
public BrokerView getAdminView() throws Exception {
return null;
}
public List<TransportConnector> getTransportConnectors() {
return transportConnectors;
}
public TransportConnector addConnector(String bindAddress) throws Exception {
return addConnector(new URI(bindAddress));
}
public void setIoExceptionHandler(IOExceptionHandler ioExceptionHandler) {
}
public void setPersistent(boolean persistent) {
}
public boolean isSlave() {
return false;
}
public Destination getDestination(ActiveMQDestination destination) throws Exception {
return null;
}
public void setAllowTempAutoCreationOnSend(boolean allowTempAutoCreationOnSend) {
}
public void setDedicatedTaskRunner(boolean dedicatedTaskRunner) {
}
public void setAdvisorySupport(boolean advisorySupport) {
}
public void setUseShutdownHook(boolean useShutdownHook) {
}
public void deleteAllMessages() throws IOException {
}
public Service[] getServices() {
return null;
}
public void setPopulateUserNameInMBeans(boolean value) {
}
public void setDestinations(ActiveMQDestination[] destinations) {
}
public URI getVmConnectorURI() {
try {
URI substituteUri = new URI("tcp://localhost:61616");
return substituteUri;
} catch (URISyntaxException e) {
e.printStackTrace();
}
return null;
}
public SystemUsage getSystemUsage() {
if (systemUsage == null) {
systemUsage = new SystemUsage();
}
return systemUsage;
}
public synchronized PListStore getTempDataStore() {
return null;
}
public void setJmsBridgeConnectors(JmsConnector[] jmsConnectors) {
}
public void setDestinationInterceptors(DestinationInterceptor[] destinationInterceptors) {
}
public SslContext getSslContext() {
return this.sslContext;
}
public void setDataDirectory(String dataDirectory) {
}
public void setPlugins(BrokerPlugin[] plugins) {
}
public void setKeepDurableSubsActive(boolean keepDurableSubsActive) {
}
public TransportConnector getConnectorByName(String connectorName) {
return null;
}
public TransportConnector addConnector(TransportConnector connector) throws Exception {
return connector;
}
public void setEnableStatistics(boolean enableStatistics) {
}
public void setSystemUsage(SystemUsage memoryManager) {
this.systemUsage = memoryManager;
}
public void setManagementContext(ManagementContext managementContext) {
}
public void setSchedulerDirectoryFile(File schedulerDirectory) {
}
public NetworkConnector addNetworkConnector(String discoveryAddress) throws Exception {
return addNetworkConnector(new URI(discoveryAddress));
}
public NetworkConnector addNetworkConnector(URI discoveryAddress) throws Exception {
NetworkConnector connector = new DiscoveryNetworkConnector(discoveryAddress);
return addNetworkConnector(connector);
}
public List<NetworkConnector> getNetworkConnectors() {
return this.networkConnectors;
}
public void setSchedulerSupport(boolean schedulerSupport) {
}
public void setPopulateJMSXUserID(boolean populateJMSXUserID) {
}
public boolean isUseJmx() {
return false;
}
public boolean isPersistent() {
return false;
}
public TransportConnector getTransportConnectorByScheme(String scheme) {
return null;
}
public TaskRunnerFactory getTaskRunnerFactory() {
return null;
}
public boolean isStarted() {
if (broker == null)
return false;
return !broker.isStopped();
}
public ProxyConnector addProxyConnector(ProxyConnector connector) throws Exception {
return connector;
}
public void setDataDirectoryFile(File dataDirectoryFile) {
this.dataDirectoryFile = dataDirectoryFile;
}
public PolicyMap getDestinationPolicy() {
return this.destinationPolicy;
}
public void setTransportConnectorURIs(String[] transportConnectorURIs) {
}
public boolean isPopulateJMSXUserID() {
return false;
}
public NetworkConnector getNetworkConnectorByName(String connectorName) {
return null;
}
public boolean removeNetworkConnector(NetworkConnector connector) {
return true;
}
public void setTransportConnectors(List<TransportConnector> transportConnectors) throws Exception {
this.transportConnectors = transportConnectors;
for (TransportConnector connector : transportConnectors) {
if (sslContext instanceof SpringSslContext) {
this.extraConnectors.add(new ConnectorInfo(connector.getUri(), (SpringSslContext) sslContext));
} else {
this.extraConnectors.add(new ConnectorInfo(connector.getUri()));
}
}
}
public NetworkConnector addNetworkConnector(NetworkConnector connector) throws Exception {
connector.setBrokerService(this);
System.out.println("------------------------ this broker uri: " + this.getConnectURI());
connector.setLocalUri(this.getConnectURI());
// Set a connection filter so that the connector does not establish loop
// back connections.
connector.setConnectionFilter(new ConnectionFilter() {
@Override
public boolean connectTo(URI location) {
List<TransportConnector> transportConnectors = getTransportConnectors();
for (Iterator<TransportConnector> iter = transportConnectors.iterator(); iter.hasNext(); ) {
try {
TransportConnector tc = iter.next();
if (location.equals(tc.getConnectUri())) {
return false;
}
} catch (Throwable e) {
}
}
return true;
}
});
networkConnectors.add(connector);
return connector;
}
public void setTempDataStore(PListStore tempDataStore) {
}
public void setJobSchedulerStore(JobSchedulerStore jobSchedulerStore) {
}
public ObjectName getBrokerObjectName() throws MalformedObjectNameException {
return null;
}
public TransportConnector addConnector(URI bindAddress) throws Exception {
Integer port = bindAddress.getPort();
String host = bindAddress.getHost();
FakeTransportConnector connector = null;
host = (host == null || host.length() == 0) ? "localhost" : host;
if ("0.0.0.0".equals(host)) {
host = "localhost";
}
if (port == 0) {
//In actual impl in amq5, after connector has been added the socket
//is bound already. This means in case of 0 port uri, the random
//port is available after this call. With artemis wrapper however
//the real binding happens during broker start. To work around this
//we use manually calculated port for that.
port = getPseudoRandomPort();
}
bindAddress = new URI(bindAddress.getScheme(), bindAddress.getUserInfo(), host, port, bindAddress.getPath(), bindAddress.getQuery(), bindAddress.getFragment());
connector = new FakeTransportConnector(bindAddress);
this.transportConnectors.add(connector);
if (sslContext instanceof SpringSslContext) {
this.extraConnectors.add(new ConnectorInfo(bindAddress, (SpringSslContext) sslContext));
} else {
this.extraConnectors.add(new ConnectorInfo(bindAddress));
}
return connector;
}
private static int getPseudoRandomPort() {
int currentRandomPort = RANDOM_PORT_BASE.getAndIncrement();
int maxTry = 20;
while (!checkPort(currentRandomPort)) {
currentRandomPort = RANDOM_PORT_BASE.getAndIncrement();
System.out.println("for port: " + currentRandomPort);
maxTry--;
if (maxTry == 0) {
LOG.error("Too many port used");
break;
}
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
}
}
return currentRandomPort;
}
public static boolean checkPort(final int port) {
ServerSocket ssocket = null;
try {
ssocket = new ServerSocket(port);
} catch (Exception e) {
LOG.info("port " + port + " is being used.");
return false;
} finally {
if (ssocket != null) {
try {
ssocket.close();
} catch (IOException e) {
}
}
}
return true;
}
public void setCacheTempDestinations(boolean cacheTempDestinations) {
}
public void setOfflineDurableSubscriberTimeout(long offlineDurableSubscriberTimeout) {
}
public void setOfflineDurableSubscriberTaskSchedule(long offlineDurableSubscriberTaskSchedule) {
}
public boolean isStopped() {
return broker != null ? broker.isStopped() : true;
}
public void setBrokerId(String brokerId) {
}
public BrokerPlugin[] getPlugins() {
return null;
}
public void stopAllConnectors(ServiceStopper stopper) {
}
public void setMessageAuthorizationPolicy(MessageAuthorizationPolicy messageAuthorizationPolicy) {
}
public void setNetworkConnectorStartAsync(boolean networkConnectorStartAsync) {
}
public boolean isRestartAllowed() {
return true;
}
public void setTaskRunnerFactory(TaskRunnerFactory taskRunnerFactory) {
}
public void start(boolean force) throws Exception {
this.start();
}
public void setMonitorConnectionSplits(boolean monitorConnectionSplits) {
}
public void setUseMirroredQueues(boolean useMirroredQueues) {
}
public File getTmpDataDirectory() {
return null;
}
public boolean isUseShutdownHook() {
return true;
}
public boolean isDeleteAllMessagesOnStartup() {
return false;
}
public void setUseVirtualTopics(boolean useVirtualTopics) {
}
public boolean isUseLoggingForShutdownErrors() {
return true;
}
public TransportConnector addConnector(TransportServer transport) throws Exception {
return null;
}
public synchronized JobSchedulerStore getJobSchedulerStore() {
return null;
}
public boolean removeConnector(TransportConnector connector) throws Exception {
return true;
}
public ConnectionContext getAdminConnectionContext() throws Exception {
return null;
}
public void setUseAuthenticatedPrincipalForJMSXUserID(boolean useAuthenticatedPrincipalForJMSXUserID) {
}
public void setSchedulePeriodForDestinationPurge(int schedulePeriodForDestinationPurge) {
}
public void setMbeanInvocationTimeout(long mbeanInvocationTimeout) {
}
public void setNetworkConnectors(List<?> networkConnectors) throws Exception {
}
public void removeDestination(ActiveMQDestination destination) throws Exception {
}
public void setMaxPurgedDestinationsPerSweep(int maxPurgedDestinationsPerSweep) {
}
public void setBrokerObjectName(ObjectName brokerObjectName) {
}
public Map<String, String> getTransportConnectorURIsAsMap() {
return null;
}
public void setSslContext(SslContext sslContext) {
this.sslContext = sslContext;
}
public void setPersistenceFactory(PersistenceAdapterFactory persistenceFactory) {
}
protected TransportConnector createTransportConnector(URI brokerURI) throws Exception {
return null;
}
public String getDefaultUri() {
return "tcp://localhost:61616";
}
public static boolean checkStopped() {
boolean runningBrokers = false;
for (Map.Entry<Broker, Exception> brokerExceptionEntry : map.entrySet()) {
Broker b = brokerExceptionEntry.getKey();
if (!b.isStopped()) {
try {
b.stop();
} catch (Exception e) {
e.printStackTrace();
}
brokerExceptionEntry.getValue().printStackTrace();
runningBrokers = true;
}
}
map.clear();
return runningBrokers;
}
public URI getConnectURI() {
URI uri = null;
try {
if (this.extraConnectors.size() > 0) {
ConnectorInfo info = extraConnectors.iterator().next();
Integer port = info.uri.getPort();
String schema = info.ssl ? "ssl" : "tcp";
uri = new URI(schema + "://localhost:" + port);
} else {
uri = new URI(this.getDefaultUri());
}
} catch (URISyntaxException e) {
//ignore
}
return uri;
}
public static class ConnectorInfo {
public static final String defaultKeyStore = "server.keystore";
public static final String defaultKeyStorePassword = "password";
public static final String defaultKeyStoreType = "jks";
public static final String defaultTrustStore = "client.keystore";
public static final String defaultTrustStorePassword = "password";
public static final String defaultTrustStoreType = "jks";
public URI uri;
public boolean ssl;
public boolean clientAuth;
public ConnectorInfo(URI bindAddress) throws URISyntaxException {
this(bindAddress, null);
}
//bindAddress must be Artemis compliant, except
//scheme
public ConnectorInfo(URI bindAddress, SpringSslContext context) throws URISyntaxException {
Integer port = bindAddress.getPort();
String host = bindAddress.getHost();
this.ssl = "ssl".equals(bindAddress.getScheme());
host = (host == null || host.length() == 0) ? "localhost" : host;
if ("0.0.0.0".equals(host)) {
host = "localhost";
}
if (port == 0) {
port = getPseudoRandomPort();
}
String query = bindAddress.getQuery();
if (!ssl || query != null && query.contains(TransportConstants.SSL_ENABLED_PROP_NAME)) {
//it means the uri is already configured ssl
uri = new URI("tcp", bindAddress.getUserInfo(), host, port, bindAddress.getPath(), bindAddress.getQuery(), bindAddress.getFragment());
} else {
String baseUri = "tcp://" + host + ":" + port + "?" + TransportConstants.SSL_ENABLED_PROP_NAME + "=true&" + TransportConstants.KEYSTORE_PATH_PROP_NAME + "=" + (context == null ? defaultKeyStore : context.getKeyStore()) + "&" + TransportConstants.KEYSTORE_PASSWORD_PROP_NAME + "=" + (context == null ? defaultKeyStorePassword : context.getKeyStorePassword()) + "&" + TransportConstants.KEYSTORE_PROVIDER_PROP_NAME + "=" + (context == null ? defaultKeyStoreType : context.getKeyStoreType());
if (clientAuth) {
baseUri = baseUri + "&" + TransportConstants.NEED_CLIENT_AUTH_PROP_NAME + "=true" + "&" + TransportConstants.TRUSTSTORE_PATH_PROP_NAME + "=" + (context == null ? defaultTrustStore : context.getTrustStore()) + "&" + TransportConstants.TRUSTSTORE_PASSWORD_PROP_NAME + "=" + (context == null ? defaultTrustStorePassword : context.getTrustStorePassword()) + "&" + TransportConstants.TRUSTSTORE_PROVIDER_PROP_NAME + "=" + (context == null ? defaultTrustStoreType : context.getTrustStoreType());
}
uri = new URI(baseUri);
}
System.out.println("server uri:::::::::::: " + uri.toString());
}
@Override
public int hashCode() {
return uri.getPort();
}
@Override
public boolean equals(Object obj) {
if (obj instanceof ConnectorInfo) {
return uri.getPort() == ((ConnectorInfo) obj).uri.getPort();
}
return false;
}
}
}