001    /**
002     *
003     * Copyright 2004 Protique Ltd
004     *
005     * Licensed under the Apache License, Version 2.0 (the "License");
006     * you may not use this file except in compliance with the License.
007     * 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     *
017     **/
018    
019    package org.activemq.broker.impl;
020    
021    import java.util.ArrayList;
022    import java.util.HashMap;
023    import java.util.Iterator;
024    import java.util.List;
025    import java.util.Map;
026    import javax.jms.InvalidClientIDException;
027    import javax.jms.InvalidDestinationException;
028    import javax.jms.JMSException;
029    import javax.jms.JMSSecurityException;
030    import javax.transaction.xa.XAException;
031    import org.apache.commons.logging.Log;
032    import org.apache.commons.logging.LogFactory;
033    import org.activemq.ActiveMQConnectionMetaData;
034    import org.activemq.broker.Broker;
035    import org.activemq.broker.BrokerClient;
036    import org.activemq.broker.BrokerConnector;
037    import org.activemq.broker.BrokerContainer;
038    import org.activemq.broker.BrokerContext;
039    import org.activemq.capacity.CapacityMonitorEvent;
040    import org.activemq.capacity.CapacityMonitorEventListener;
041    import org.activemq.io.WireFormat;
042    import org.activemq.io.impl.DefaultWireFormat;
043    import org.activemq.io.util.MemoryBoundedObjectManager;
044    import org.activemq.message.ActiveMQDestination;
045    import org.activemq.message.ActiveMQMessage;
046    import org.activemq.message.ActiveMQXid;
047    import org.activemq.message.ConnectionInfo;
048    import org.activemq.message.ConsumerInfo;
049    import org.activemq.message.DurableUnsubscribe;
050    import org.activemq.message.MessageAck;
051    import org.activemq.message.ProducerInfo;
052    import org.activemq.message.SessionInfo;
053    import org.activemq.security.SecurityAdapter;
054    import org.activemq.service.RedeliveryPolicy;
055    import org.activemq.service.Service;
056    import org.activemq.store.PersistenceAdapter;
057    import org.activemq.transport.DiscoveryAgent;
058    import org.activemq.transport.NetworkConnector;
059    import org.activemq.transport.TransportServerChannel;
060    import org.activemq.util.IdGenerator;
061    import EDU.oswego.cs.dl.util.concurrent.ConcurrentHashMap;
062    import EDU.oswego.cs.dl.util.concurrent.CopyOnWriteArrayList;
063    import EDU.oswego.cs.dl.util.concurrent.CopyOnWriteArraySet;
064    import java.util.Set;
065    
066    /**
067     * Represents the ActiveMQ JMS Broker which typically has one or many connectors
068     *
069     * @version $Revision: 1.1.1.1 $
070     */
071    public class BrokerContainerImpl implements BrokerContainer, CapacityMonitorEventListener {
072        public static final String DISABLE_CLEAN_SHUTDOWN_PROPERTY = "activemq.broker.disable-clean-shutdown";
073        private static final Log log = LogFactory.getLog(BrokerContainerImpl.class);
074        private static final boolean useLoggingForShutdownErrors = false;
075    
076        private BrokerContext context;
077        private Broker broker;
078        private Map clientIds;
079        private Map consumerInfos;
080        private Map producerInfos;
081        private List transportConnectors;
082        private Thread shutdownHook;
083        private boolean stopped;
084        private List networkConnectors;
085        private DiscoveryAgent discoveryAgent;
086        private Map localDiscoveryDetails;
087        private Set remoteClientIds;
088    
089    
090        public BrokerContainerImpl() {
091            this(new IdGenerator().generateId());
092        }
093    
094        public BrokerContainerImpl(String brokerName) {
095            this(brokerName, BrokerContext.getInstance());
096        }
097    
098        public BrokerContainerImpl(String brokerName, MemoryBoundedObjectManager memoryManager) {
099            this(brokerName, BrokerContext.getInstance(), memoryManager);
100        }
101        
102        public BrokerContainerImpl(String brokerName,String clusterName) {
103            this(brokerName, clusterName,BrokerContext.getInstance());
104            
105        }
106    
107        public BrokerContainerImpl(String brokerName, PersistenceAdapter persistenceAdapter) {
108            this(brokerName, persistenceAdapter, BrokerContext.getInstance());
109        }
110    
111        public BrokerContainerImpl(String brokerName, BrokerContext context) {
112            this(new DefaultBroker(brokerName), context);
113        }
114        
115        public BrokerContainerImpl(String brokerName, BrokerContext context, MemoryBoundedObjectManager memoryManager) {
116            this(new DefaultBroker(brokerName,memoryManager), context);
117        }
118        
119        public BrokerContainerImpl(String brokerName, String clusterName, BrokerContext context) {
120            this(new DefaultBroker(brokerName,clusterName), context);
121        }
122        
123        public BrokerContainerImpl(String brokerName, PersistenceAdapter persistenceAdapter, BrokerContext context) {
124            this(new DefaultBroker(brokerName, persistenceAdapter), context);
125        }
126    
127        public BrokerContainerImpl(String brokerName,String clusterName, PersistenceAdapter persistenceAdapter, BrokerContext context) {
128            this(new DefaultBroker(brokerName,clusterName, persistenceAdapter), context);
129        }
130    
131        /**
132         * @param broker
133         */
134        public BrokerContainerImpl(Broker broker, BrokerContext context) {
135            this.broker = broker;
136            this.context = context;
137            this.clientIds = new ConcurrentHashMap();
138            this.consumerInfos = new ConcurrentHashMap();
139            this.producerInfos = new ConcurrentHashMap();
140            this.transportConnectors = new CopyOnWriteArrayList();
141            this.networkConnectors = new CopyOnWriteArrayList();
142            this.remoteClientIds = new CopyOnWriteArraySet();
143            this.broker.addCapacityEventListener(this);
144    
145            // lets register ourselves with the context
146            context.registerContainer(broker.getBrokerName(), this);
147            //register ourselves for vm:// transports
148            context.registerContainer("vm://" + broker.getBrokerName(), this);
149        }
150    
151        /**
152         * start the Container
153         *
154         * @throws JMSException
155         */
156        public void start() throws JMSException {
157            log.info("ActiveMQ "+ActiveMQConnectionMetaData.PROVIDER_VERSION+" JMS Message Broker (" + broker.getBrokerName() + ") is starting");
158            log.info("For help or more information please see: http://www.logicblaze.com");
159            broker.start();
160            addShutdownHook();
161    
162            // TODO we might not need to copy the collections, as maybe the List might not
163            // throw concurrent modification exception? Couldn't tell from the docs
164            // but I don't think it does
165    
166            for (Iterator iter = new ArrayList(networkConnectors).iterator(); iter.hasNext();) {
167                Service connector = (Service) iter.next();
168                connector.start();
169            }
170    
171            for (Iterator iter = new ArrayList(transportConnectors).iterator(); iter.hasNext();) {
172                Service connector = (Service) iter.next();
173                connector.start();
174            }
175    
176            if (discoveryAgent != null) {
177                discoveryAgent.start();
178    
179                localDiscoveryDetails = createLocalDiscoveryDetails();
180                discoveryAgent.registerService(getLocalBrokerName(), localDiscoveryDetails);
181            }
182    
183            log.info("ActiveMQ JMS Message Broker (" + broker.getBrokerName() + ") has started");
184        }
185    
186        /**
187         * Stop the Container
188         *
189         * @throws JMSException
190         */
191        public synchronized void stop() throws JMSException {
192            if (!stopped) {
193                stopped = true;
194    
195                log.info("ActiveMQ Message Broker (" + broker.getBrokerName() + ") is shutting down");
196    
197                context.deregisterContainer(broker.getBrokerName(), this);
198    
199                try {
200                    Runtime.getRuntime().removeShutdownHook(shutdownHook);
201                }
202                catch (Exception e) {
203                    log.debug("Caught exception, must be shutting down: " + e);
204                }
205    
206                JMSException firstException = null;
207                if (discoveryAgent != null) {
208                    try {
209                        discoveryAgent.stop();
210                    } catch (JMSException e) {
211                        firstException = e;
212                        log.warn("Could not close discovery agent: " + discoveryAgent + " due to: " + e, e);
213                    }
214                }
215    
216                for (Iterator iter = new ArrayList(transportConnectors).iterator(); iter.hasNext();) {
217                    Service connector = (Service) iter.next();
218                    try {
219                        connector.stop();
220                    }
221                    catch (JMSException e) {
222                        if (firstException == null) {
223                            firstException = e;
224                        }
225                        log.warn("Could not close transport connector: " + connector + " due to: " + e, e);
226                    }
227                }
228                transportConnectors.clear();
229    
230                for (Iterator iter = new ArrayList(networkConnectors).iterator(); iter.hasNext();) {
231                    Service connector = (Service) iter.next();
232                    try {
233                        connector.stop();
234                    }
235                    catch (JMSException e) {
236                        if (firstException == null) {
237                            firstException = e;
238                        }
239                        log.warn("Could not close network connector: " + connector + " due to: " + e, e);
240                    }
241                }
242                networkConnectors.clear();
243    
244                
245    
246                // lets close all the channels
247                // note that this Map implementation does not throw concurrent modification exception
248                for (Iterator iter = clientIds.values().iterator();iter.hasNext();) {
249                    // should remove clients from parent container?
250                    BrokerClient client = (BrokerClient) iter.next();
251                    if (client != null) {
252                        try {
253                            client.stop();
254                        }
255                        catch (JMSException e) {
256                            if (firstException == null) {
257                                firstException = e;
258                            }
259                            log.warn("Could not close client: " + client + " due to: " + e, e);
260                        }
261                    }
262                }
263                clientIds.clear();
264    
265                broker.removeCapacityEventListener(this);
266                broker.stop();
267    
268                log.info("ActiveMQ JMS Message Broker (" + broker.getBrokerName() + ") stopped");
269    
270                if (firstException != null) {
271                    throw firstException;
272                }
273            }
274        }
275    
276        /**
277         * registers a new Connection
278         *
279         * @param client
280         * @param info   infomation about the client-side Connection
281         * @throws InvalidClientIDException if the ClientID of the Connection is a duplicate
282         */
283            synchronized public void registerConnection(BrokerClient client, ConnectionInfo info) throws JMSException {
284                    String clientId = info.getClientId();
285                    if (clientIds.containsKey(clientId)) {
286                            int timeout = 5000;
287                            log.info("Got duplicate client with id: " + clientId + ". Giving the existing client " + timeout + " millis to prove it's alive.");
288    
289                            // Assert that the existing client is alive
290                            BrokerClient existingClient = (BrokerClient) clientIds.get(clientId);
291                            JMSException ex = null;
292                            boolean isValid = true;
293                            try {
294                                    existingClient.validateConnection(timeout);
295                            } catch (JMSException e) {
296                                    isValid = false;
297                                    ex = e;
298                            }
299                            if (isValid) {
300                                    // The existing client is valid, so kick the new client
301                                    log.info("Client: " + clientId + " on transport: " + existingClient.getChannel()
302                                                    + "' is alive, rejecting new client on transport: " + client.getChannel());
303                                    throw new InvalidClientIDException("Duplicate clientId: " + info);
304                            } else {
305                                    // A transport error occured or the existing client did not
306                                    // respond in time, so kick it and let the new client connect.
307                                    log.info("Replacing client: " + clientId + " on transport: " + existingClient.getChannel() + " ("
308                                                    + ex.getMessage() + ") with client on transport: " + client.getChannel());
309                                    
310                                    // @TODO: Not sure this is the proper way to close the existing client
311                                    existingClient.cleanUp(); 
312                                    existingClient.stop();
313                            }
314    
315                    }
316                    getBroker().addClient(client, info);
317                    log.info("Adding new client: " + clientId + " on transport: " + client.getChannel());
318                    clientIds.put(clientId, client);
319            }
320    
321        /**
322         * un-registers a Connection
323         *
324         * @param client
325         * @param info   infomation about the client-side Connection
326         * @throws JMSException
327         */
328        public void deregisterConnection(BrokerClient client, ConnectionInfo info) throws JMSException {
329            String clientId = client.getClientID();
330            if (clientId != null) {
331                Object answer = clientIds.remove(clientId);
332                if (answer != null) {
333                    log.info("Removing client: " + clientId + " on transport: " + client.getChannel());
334                    getBroker().removeClient(client, info);
335                }
336                else {
337                    log.warn("Got duplicate deregisterConnection for client: " + clientId);
338                }
339            }
340            else {
341                log.warn("No clientID available for client: " + client);
342            }
343        }
344    
345        /**
346         * Registers a MessageConsumer
347         *
348         * @param client
349         * @param info
350         * @throws JMSException
351         * @throws JMSSecurityException if client authentication fails for the Destination the Consumer applies for
352         */
353        public void registerMessageConsumer(BrokerClient client, ConsumerInfo info) throws JMSException {
354            consumerInfos.put(info, client);
355            getBroker().addMessageConsumer(client, info);
356        }
357    
358        /**
359         * De-register a MessageConsumer from the Broker
360         *
361         * @param client
362         * @param info
363         * @throws JMSException
364         */
365        public void deregisterMessageConsumer(BrokerClient client, ConsumerInfo info) throws JMSException {
366            consumerInfos.remove(info);
367            getBroker().removeMessageConsumer(client, info);
368        }
369    
370        /**
371         * Registers a MessageProducer
372         *
373         * @param client
374         * @param info
375         * @throws JMSException
376         * @throws JMSSecurityException if client authentication fails for the Destination the Consumer applies for
377         */
378        public void registerMessageProducer(BrokerClient client, ProducerInfo info) throws JMSException {
379            ActiveMQDestination dest = info.getDestination();
380            checkTempDestinationExistance(dest);
381            getBroker().addMessageProducer(client, info);
382    
383            producerInfos.put(info, client);
384        }
385    
386        /**
387         * De-register a MessageProducer from the Broker
388         *
389         * @param client
390         * @param info
391         * @throws JMSException
392         */
393        public void deregisterMessageProducer(BrokerClient client, ProducerInfo info) throws JMSException {
394            getBroker().removeMessageProducer(client, info);
395    
396            producerInfos.remove(info);
397        }
398    
399        /**
400         * Register a client-side Session (used for Monitoring)
401         *
402         * @param client
403         * @param info
404         * @throws JMSException
405         */
406        public void registerSession(BrokerClient client, SessionInfo info) throws JMSException {
407        }
408    
409        /**
410         * De-register a client-side Session from the Broker (used for monitoring)
411         *
412         * @param client
413         * @param info
414         * @throws JMSException
415         */
416        public void deregisterSession(BrokerClient client, SessionInfo info) throws JMSException {
417        }
418    
419        /**
420         * Start a transaction from the Client session
421         *
422         * @param client
423         * @param transactionId
424         * @throws JMSException
425         */
426        public void startTransaction(BrokerClient client, String transactionId) throws JMSException {
427            getBroker().startTransaction(client, transactionId);
428        }
429    
430        /**
431         * Rollback a transacton
432         *
433         * @param client
434         * @param transactionId
435         * @throws JMSException
436         */
437        public void rollbackTransaction(BrokerClient client, String transactionId) throws JMSException {
438            getBroker().rollbackTransaction(client, transactionId);
439        }
440    
441        /**
442         * Commit a transaction
443         *
444         * @param client
445         * @param transactionId
446         * @throws JMSException
447         */
448        public void commitTransaction(BrokerClient client, String transactionId) throws JMSException {
449            getBroker().commitTransaction(client, transactionId);
450        }
451    
452        /**
453         * Send a non-transacted message to the Broker
454         *
455         * @param client
456         * @param message
457         * @throws JMSException
458         */
459        public void sendMessage(BrokerClient client, ActiveMQMessage message) throws JMSException {
460            ActiveMQDestination dest = message.getJMSActiveMQDestination();        
461            checkTempDestinationExistance(dest);
462            broker.sendMessage(client, message);
463        }
464        
465        /**
466         * register a remote clientID
467         * @param remoteClientID
468         */
469        
470        public void registerRemoteClientID(String remoteClientID){
471            remoteClientIds.add(remoteClientID);
472        }
473        
474        /**
475         * deregister a remote clientID
476         * @param remoteClientID
477         */
478        public void deregisterRemoteClientID(String remoteClientID){
479            remoteClientIds.remove(remoteClientID);
480        }
481    
482        /**
483         * @param dest
484         * @throws InvalidDestinationException
485         */
486        private void checkTempDestinationExistance(ActiveMQDestination dest) throws InvalidDestinationException {
487            if (dest != null && dest.isTemporary()) {
488                //check to see if the client that is the target for the temporary destination still exists
489                String clientId = ActiveMQDestination.getClientId(dest);
490                if (clientId == null) {
491                    throw new InvalidDestinationException("Destination " + dest.getPhysicalName()
492                            + " is a temporary destination with null clientId");
493                }
494                if (!clientIds.containsKey(clientId) && !remoteClientIds.contains(clientId)) {
495                    throw new InvalidDestinationException("Destination " + dest.getPhysicalName()
496                            + " is no longer valid because the client " + clientId + " no longer exists");
497                }
498            }
499        }
500    
501        /**
502         * Acknowledge reciept of a message
503         *
504         * @param client
505         * @param ack
506         * @throws JMSException
507         */
508        public void acknowledgeMessage(BrokerClient client, MessageAck ack) throws JMSException {
509            getBroker().acknowledgeMessage(client, ack);
510        }
511    
512        /**
513         * Command to delete a durable topic subscription
514         *
515         * @param client
516         * @param ds
517         * @throws JMSException
518         */
519        public void durableUnsubscribe(BrokerClient client, DurableUnsubscribe ds) throws JMSException {
520            getBroker().deleteSubscription(ds.getClientId(), ds.getSubscriberName());
521        }
522    
523        /**
524         * Start an XA transaction.
525         *
526         * @param client
527         * @param xid
528         */
529        public void startTransaction(BrokerClient client, ActiveMQXid xid) throws XAException {
530            getBroker().startTransaction(client, xid);
531        }
532    
533        /**
534         * Gets the prepared XA transactions.
535         *
536         * @param client
537         * @return
538         */
539        public ActiveMQXid[] getPreparedTransactions(BrokerClient client) throws XAException {
540            return getBroker().getPreparedTransactions(client);
541        }
542    
543        /**
544         * Prepare an XA transaction.
545         *
546         * @param client
547         * @param xid
548         */
549        public int prepareTransaction(BrokerClient client, ActiveMQXid xid) throws XAException {
550            return getBroker().prepareTransaction(client, xid);
551        }
552    
553        /**
554         * Rollback an XA transaction.
555         *
556         * @param client
557         * @param xid
558         */
559        public void rollbackTransaction(BrokerClient client, ActiveMQXid xid) throws XAException {
560            getBroker().rollbackTransaction(client, xid);
561        }
562    
563        /**
564         * Commit an XA transaction.
565         *
566         * @param client
567         * @param xid
568         * @param onePhase
569         */
570        public void commitTransaction(BrokerClient client, ActiveMQXid xid, boolean onePhase) throws XAException {
571            getBroker().commitTransaction(client, xid, onePhase);
572        }
573    
574    
575        /**
576         * Update any message producers about our capacity to handle messages
577         *
578         * @param event
579         */
580        public void capacityChanged(CapacityMonitorEvent event) {
581            //only send to producers
582            for (Iterator i = producerInfos.values().iterator(); i.hasNext();) {
583                BrokerClient client = (BrokerClient) i.next();
584                client.updateBrokerCapacity(event.getCapacity());
585            }
586        }
587    
588    
589        // Properties
590        //-------------------------------------------------------------------------
591    
592        public List getTransportConnectors() {
593            return transportConnectors;
594        }
595    
596        public void setTransportConnectors(List transportConnectors) {
597            this.transportConnectors = new CopyOnWriteArrayList(transportConnectors);
598        }
599    
600        public void addConnector(BrokerConnector connector) {
601            if( !transportConnectors.contains(connector) ) {
602                transportConnectors.add(connector);
603                context.registerConnector(connector.getServerChannel().getUrl(), connector);
604            }
605        }
606    
607        public void removeConnector(BrokerConnector connector) {
608            transportConnectors.remove(connector);
609            context.deregisterConnector(connector.getServerChannel().getUrl());
610        }
611    
612    
613        public void addConnector(String bindAddress) throws JMSException {
614            addConnector(bindAddress, new DefaultWireFormat());
615        }
616    
617        public void addConnector(String bindAddress, WireFormat wireFormat) throws JMSException {
618            addConnector(new BrokerConnectorImpl(this, bindAddress, wireFormat));
619        }
620    
621        public void addConnector(TransportServerChannel transportConnector) {
622            addConnector(new BrokerConnectorImpl(this, transportConnector));
623        }
624    
625        public List getNetworkConnectors() {
626            return networkConnectors;
627        }
628    
629        public void setNetworkConnectors(List networkConnectors) {
630            this.networkConnectors = new CopyOnWriteArrayList(networkConnectors);
631        }
632    
633        public NetworkConnector addNetworkConnector(String uri) throws JMSException {
634            NetworkConnector connector = addNetworkConnector();
635            connector.addNetworkChannel(uri);
636            return connector;
637        }
638    
639        public NetworkConnector addNetworkConnector() {
640            NetworkConnector connector = new NetworkConnector(this);
641            addNetworkConnector(connector);
642            return connector;
643        }
644    
645        public void addNetworkConnector(NetworkConnector connector) {
646            networkConnectors.add(connector);
647        }
648    
649        public void removeNetworkConnector(NetworkConnector connector) {
650            networkConnectors.remove(connector);
651        }
652    
653    
654        public Broker getBroker() {
655            return broker;
656        }
657    
658        public PersistenceAdapter getPersistenceAdapter() {
659            return broker != null ? broker.getPersistenceAdapter() : null;
660        }
661    
662        public void setPersistenceAdapter(PersistenceAdapter persistenceAdapter) {
663            checkBrokerSet();
664            broker.setPersistenceAdapter(persistenceAdapter);
665        }
666    
667        public DiscoveryAgent getDiscoveryAgent() {
668            return discoveryAgent;
669        }
670    
671        public void setDiscoveryAgent(DiscoveryAgent discoveryAgent) {
672            this.discoveryAgent = discoveryAgent;
673        }
674    
675        public SecurityAdapter getSecurityAdapter() {
676            return broker != null ? broker.getSecurityAdapter() : null;
677        }
678    
679        public void setSecurityAdapter(SecurityAdapter securityAdapter) {
680            checkBrokerSet();
681            broker.setSecurityAdapter(securityAdapter);
682        }
683    
684        public RedeliveryPolicy getRedeliveryPolicy() {
685            return broker != null ? broker.getRedeliveryPolicy() : null;
686        }
687    
688        public void setRedeliveryPolicy(RedeliveryPolicy redeliveryPolicy) {
689            checkBrokerSet();
690            broker.setRedeliveryPolicy(redeliveryPolicy);
691        }
692    
693        // Implementation methods
694        //-------------------------------------------------------------------------
695    
696        protected void checkBrokerSet() {
697            if (broker == null) {
698                throw new IllegalStateException("Cannot set this property as we don't have a broker yet");
699            }
700        }
701    
702        protected Map createLocalDiscoveryDetails() {
703            Map map = new HashMap();
704            map.put("brokerName", getLocalBrokerName());
705            map.put("connectURL", getLocalConnectionURL());
706            return map;
707        }
708    
709        protected String getLocalBrokerName() {
710            return getBroker().getBrokerName();
711        }
712    
713        protected String getLocalConnectionURL() {
714            StringBuffer buffer = new StringBuffer("reliable:");
715            List list = getTransportConnectors();
716            boolean first = true;
717            for (Iterator iter = list.iterator(); iter.hasNext();) {
718                BrokerConnector brokerConnector = (BrokerConnector) iter.next();
719                TransportServerChannel connector = brokerConnector.getServerChannel();
720                String url = connector.getUrl();
721                if (first) {
722                    first = false;
723                }
724                else {
725                    buffer.append(",");
726                }
727                buffer.append(url);
728            }
729            return buffer.toString();
730        }
731    
732        protected void addShutdownHook() {
733            if(System.getProperty(DISABLE_CLEAN_SHUTDOWN_PROPERTY,"false").equals("true"))
734                return;
735            
736            shutdownHook = new Thread("ActiveMQ ShutdownHook") {
737                public void run() {
738                    containerShutdown();
739                }
740            };
741            Runtime.getRuntime().addShutdownHook(shutdownHook);
742        }
743    
744        /**
745         * Causes a clean shutdown of the container when the VM is being shut down
746         */
747        protected void containerShutdown() {
748            try {
749                stop();
750            }
751            catch (JMSException e) {
752                Exception linkedException = e.getLinkedException();
753                if (linkedException != null) {
754                    if (useLoggingForShutdownErrors) {
755                        log.error("Failed to shut down: " + e + ". Reason: " + linkedException, linkedException);
756                    }
757                    else {
758                        System.err.println("Failed to shut down: " + e + ". Reason: " + linkedException);
759                    }
760                }
761                else {
762                    if (useLoggingForShutdownErrors) {
763                        log.error("Failed to shut down: " + e);
764                    }
765                    else {
766                        System.err.println("Failed to shut down: " + e);
767                    }
768                }
769                if (!useLoggingForShutdownErrors) {
770                    e.printStackTrace(System.err);
771                }
772            }
773            catch (Exception e) {
774                if (useLoggingForShutdownErrors) {
775                    log.error("Failed to shut down: " + e, e);
776                }
777                else {
778                    System.err.println("Failed to shut down: " + e);
779                    e.printStackTrace(System.err);
780                }
781            }
782        }
783    }