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 }