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}