001/** 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.activemq.transport.vm; 018 019import java.io.IOException; 020import java.net.URI; 021import java.net.URISyntaxException; 022import java.util.HashMap; 023import java.util.Map; 024import java.util.concurrent.ConcurrentHashMap; 025 026import org.apache.activemq.broker.BrokerFactory; 027import org.apache.activemq.broker.BrokerFactoryHandler; 028import org.apache.activemq.broker.BrokerRegistry; 029import org.apache.activemq.broker.BrokerService; 030import org.apache.activemq.broker.TransportConnector; 031import org.apache.activemq.transport.MarshallingTransportFilter; 032import org.apache.activemq.transport.Transport; 033import org.apache.activemq.transport.TransportFactory; 034import org.apache.activemq.transport.TransportServer; 035import org.apache.activemq.util.IOExceptionSupport; 036import org.apache.activemq.util.IntrospectionSupport; 037import org.apache.activemq.util.ServiceSupport; 038import org.apache.activemq.util.URISupport; 039import org.apache.activemq.util.URISupport.CompositeData; 040import org.slf4j.Logger; 041import org.slf4j.LoggerFactory; 042import org.slf4j.MDC; 043 044public class VMTransportFactory extends TransportFactory { 045 046 public static final ConcurrentHashMap<String, BrokerService> BROKERS = new ConcurrentHashMap<String, BrokerService>(); 047 public static final ConcurrentHashMap<String, TransportConnector> CONNECTORS = new ConcurrentHashMap<String, TransportConnector>(); 048 public static final ConcurrentHashMap<String, VMTransportServer> SERVERS = new ConcurrentHashMap<String, VMTransportServer>(); 049 private static final Logger LOG = LoggerFactory.getLogger(VMTransportFactory.class); 050 051 BrokerFactoryHandler brokerFactoryHandler; 052 053 @Override 054 public Transport doConnect(URI location) throws Exception { 055 return VMTransportServer.configure(doCompositeConnect(location)); 056 } 057 058 @Override 059 public Transport doCompositeConnect(URI location) throws Exception { 060 URI brokerURI; 061 String host; 062 Map<String, String> options; 063 boolean create = true; 064 int waitForStart = -1; 065 CompositeData data = URISupport.parseComposite(location); 066 if (data.getComponents().length == 1 && "broker".equals(data.getComponents()[0].getScheme())) { 067 brokerURI = data.getComponents()[0]; 068 CompositeData brokerData = URISupport.parseComposite(brokerURI); 069 host = brokerData.getParameters().get("brokerName"); 070 if (host == null) { 071 host = "localhost"; 072 } 073 if (brokerData.getPath() != null) { 074 host = brokerData.getPath(); 075 } 076 options = data.getParameters(); 077 location = new URI("vm://" + host); 078 } else { 079 // If using the less complex vm://localhost?broker.persistent=true 080 // form 081 try { 082 host = extractHost(location); 083 options = URISupport.parseParameters(location); 084 String config = options.remove("brokerConfig"); 085 if (config != null) { 086 brokerURI = new URI(config); 087 } else { 088 Map<String, Object> brokerOptions = IntrospectionSupport.extractProperties(options, "broker."); 089 brokerURI = new URI("broker://()/" + host + "?" 090 + URISupport.createQueryString(brokerOptions)); 091 } 092 if ("false".equals(options.remove("create"))) { 093 create = false; 094 } 095 String waitForStartString = options.remove("waitForStart"); 096 if (waitForStartString != null) { 097 waitForStart = Integer.parseInt(waitForStartString); 098 } 099 } catch (URISyntaxException e1) { 100 throw IOExceptionSupport.create(e1); 101 } 102 location = new URI("vm://" + host); 103 } 104 if (host == null) { 105 host = "localhost"; 106 } 107 VMTransportServer server = SERVERS.get(host); 108 // validate the broker is still active 109 if (!validateBroker(host) || server == null) { 110 BrokerService broker = null; 111 // Synchronize on the registry so that multiple concurrent threads 112 // doing this do not think that the broker has not been created and 113 // cause multiple brokers to be started. 114 synchronized (BrokerRegistry.getInstance().getRegistryMutext()) { 115 broker = lookupBroker(BrokerRegistry.getInstance(), host, waitForStart); 116 if (broker == null) { 117 if (!create) { 118 throw new IOException("Broker named '" + host + "' does not exist."); 119 } 120 try { 121 if (brokerFactoryHandler != null) { 122 broker = brokerFactoryHandler.createBroker(brokerURI); 123 } else { 124 broker = BrokerFactory.createBroker(brokerURI); 125 } 126 broker.start(); 127 MDC.put("activemq.broker", broker.getBrokerName()); 128 } catch (URISyntaxException e) { 129 throw IOExceptionSupport.create(e); 130 } 131 BROKERS.put(host, broker); 132 BrokerRegistry.getInstance().getRegistryMutext().notifyAll(); 133 } 134 135 server = SERVERS.get(host); 136 if (server == null) { 137 server = (VMTransportServer)bind(location, true); 138 TransportConnector connector = new TransportConnector(server); 139 connector.setBrokerService(broker); 140 connector.setUri(location); 141 connector.setTaskRunnerFactory(broker.getTaskRunnerFactory()); 142 connector.start(); 143 CONNECTORS.put(host, connector); 144 } 145 146 } 147 } 148 149 VMTransport vmtransport = server.connect(); 150 IntrospectionSupport.setProperties(vmtransport.peer, new HashMap<String,String>(options)); 151 IntrospectionSupport.setProperties(vmtransport, options); 152 Transport transport = vmtransport; 153 if (vmtransport.isMarshal()) { 154 Map<String, String> optionsCopy = new HashMap<String, String>(options); 155 transport = new MarshallingTransportFilter(transport, createWireFormat(options), 156 createWireFormat(optionsCopy)); 157 } 158 if (!options.isEmpty()) { 159 throw new IllegalArgumentException("Invalid connect parameters: " + options); 160 } 161 return transport; 162 } 163 164 private static String extractHost(URI location) { 165 String host = location.getHost(); 166 if (host == null || host.length() == 0) { 167 host = location.getAuthority(); 168 if (host == null || host.length() == 0) { 169 host = "localhost"; 170 } 171 } 172 return host; 173 } 174 175 /** 176 * Attempt to find a Broker instance. 177 * 178 * @param registry 179 * the registry in which to search for the BrokerService instance. 180 * @param brokerName 181 * the name of the Broker that should be located. 182 * @param waitForStart 183 * time in milliseconds to wait for a broker to appear and be started. 184 * 185 * @return a BrokerService instance if one is found, or null. 186 */ 187 private BrokerService lookupBroker(final BrokerRegistry registry, final String brokerName, int waitForStart) { 188 BrokerService broker = null; 189 synchronized(registry.getRegistryMutext()) { 190 broker = registry.lookup(brokerName); 191 if (broker == null || waitForStart > 0) { 192 final long expiry = System.currentTimeMillis() + waitForStart; 193 while ((broker == null || !broker.isStarted()) && expiry > System.currentTimeMillis()) { 194 long timeout = Math.max(0, expiry - System.currentTimeMillis()); 195 if (broker == null) { 196 try { 197 LOG.debug("waiting for broker named: " + brokerName + " to enter registry"); 198 registry.getRegistryMutext().wait(timeout); 199 broker = registry.lookup(brokerName); 200 } catch (InterruptedException ignored) { 201 } 202 } 203 if (broker != null && !broker.isStarted()) { 204 LOG.debug("waiting for broker named: " + brokerName + " to start"); 205 timeout = Math.max(0, expiry - System.currentTimeMillis()); 206 // Wait for however long we have left for broker to be started, if 207 // it doesn't get started we need to clear broker so it doesn't get 208 // returned. A null return should throw an exception. 209 if (!broker.waitUntilStarted(timeout)) { 210 broker = null; 211 break; 212 } 213 } 214 } 215 } 216 } 217 return broker; 218 } 219 220 @Override 221 public TransportServer doBind(URI location) throws IOException { 222 return bind(location, false); 223 } 224 225 /** 226 * @param location 227 * @return the TransportServer 228 * @throws IOException 229 */ 230 private TransportServer bind(URI location, boolean dispose) throws IOException { 231 String host = extractHost(location); 232 LOG.debug("binding to broker: " + host); 233 VMTransportServer server = new VMTransportServer(location, dispose); 234 Object currentBoundValue = SERVERS.get(host); 235 if (currentBoundValue != null) { 236 throw new IOException("VMTransportServer already bound at: " + location); 237 } 238 SERVERS.put(host, server); 239 return server; 240 } 241 242 public static void stopped(VMTransportServer server) { 243 String host = extractHost(server.getBindURI()); 244 stopped(host); 245 } 246 247 public static void stopped(String host) { 248 SERVERS.remove(host); 249 TransportConnector connector = CONNECTORS.remove(host); 250 if (connector != null) { 251 LOG.debug("Shutting down VM connectors for broker: " + host); 252 ServiceSupport.dispose(connector); 253 BrokerService broker = BROKERS.remove(host); 254 if (broker != null) { 255 ServiceSupport.dispose(broker); 256 } 257 MDC.remove("activemq.broker"); 258 } 259 } 260 261 public BrokerFactoryHandler getBrokerFactoryHandler() { 262 return brokerFactoryHandler; 263 } 264 265 public void setBrokerFactoryHandler(BrokerFactoryHandler brokerFactoryHandler) { 266 this.brokerFactoryHandler = brokerFactoryHandler; 267 } 268 269 private boolean validateBroker(String host) { 270 boolean result = true; 271 if (BROKERS.containsKey(host) || SERVERS.containsKey(host) || CONNECTORS.containsKey(host)) { 272 // check the broker is still in the BrokerRegistry 273 TransportConnector connector = CONNECTORS.get(host); 274 if (BrokerRegistry.getInstance().lookup(host) == null 275 || (connector != null && connector.getBroker().isStopped())) { 276 result = false; 277 // clean-up 278 BROKERS.remove(host); 279 SERVERS.remove(host); 280 if (connector != null) { 281 CONNECTORS.remove(host); 282 if (connector != null) { 283 ServiceSupport.dispose(connector); 284 } 285 } 286 } 287 } 288 return result; 289 } 290}