001/* 002 * JGrapes Event Driven Framework 003 * Copyright (C) 2016-2018 Michael N. Lipp 004 * 005 * This program is free software; you can redistribute it and/or modify it 006 * under the terms of the GNU Affero General Public License as published by 007 * the Free Software Foundation; either version 3 of the License, or 008 * (at your option) any later version. 009 * 010 * This program is distributed in the hope that it will be useful, but 011 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 012 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License 013 * for more details. 014 * 015 * You should have received a copy of the GNU Affero General Public License along 016 * with this program; if not, see <http://www.gnu.org/licenses/>. 017 */ 018 019package org.jgrapes.net; 020 021import java.io.IOException; 022import java.lang.management.ManagementFactory; 023import java.lang.ref.WeakReference; 024import java.net.InetSocketAddress; 025import java.nio.channels.SelectionKey; 026import java.nio.channels.ServerSocketChannel; 027import java.nio.channels.SocketChannel; 028import java.util.ArrayList; 029import java.util.Collections; 030import java.util.Comparator; 031import java.util.HashSet; 032import java.util.IntSummaryStatistics; 033import java.util.List; 034import java.util.Optional; 035import java.util.Set; 036import java.util.SortedMap; 037import java.util.TreeMap; 038import java.util.stream.Collectors; 039import javax.management.InstanceAlreadyExistsException; 040import javax.management.MBeanRegistrationException; 041import javax.management.MBeanServer; 042import javax.management.MalformedObjectNameException; 043import javax.management.NotCompliantMBeanException; 044import javax.management.ObjectName; 045import org.jgrapes.core.Channel; 046import org.jgrapes.core.Components; 047import org.jgrapes.core.Event; 048import org.jgrapes.core.Manager; 049import org.jgrapes.core.Self; 050import org.jgrapes.core.Subchannel; 051import org.jgrapes.core.annotation.Handler; 052import org.jgrapes.core.events.Error; 053import org.jgrapes.core.events.Start; 054import org.jgrapes.core.events.Stop; 055import org.jgrapes.io.NioHandler; 056import org.jgrapes.io.events.Close; 057import org.jgrapes.io.events.Closed; 058import org.jgrapes.io.events.IOError; 059import org.jgrapes.io.events.Input; 060import org.jgrapes.io.events.NioRegistration; 061import org.jgrapes.io.events.NioRegistration.Registration; 062import org.jgrapes.io.events.Opening; 063import org.jgrapes.io.events.Output; 064import org.jgrapes.io.events.Purge; 065import org.jgrapes.io.util.AvailabilityListener; 066import org.jgrapes.io.util.LinkedIOSubchannel; 067import org.jgrapes.io.util.PermitsPool; 068import org.jgrapes.net.events.Accepted; 069import org.jgrapes.net.events.Ready; 070import org.jgrapes.util.events.ConfigurationUpdate; 071 072/** 073 * Provides a TCP server. The server binds to the given address. If the 074 * address is {@code null}, address and port are automatically assigned. 075 * The port may be overwritten by a configuration event 076 * (see {@link #onConfigurationUpdate(ConfigurationUpdate)}). 077 * 078 * For each established connection, the server creates a new 079 * {@link LinkedIOSubchannel}. The servers basic operation is to 080 * fire {@link Input} (and {@link Closed}) events on the 081 * appropriate subchannel in response to data received from the 082 * network and to handle {@link Output} (and {@link Close}) events 083 * on the subchannel and forward the information to the network 084 * connection. 085 * 086 * The server supports limiting the number of concurrent connections 087 * with a {@link PermitsPool}. If such a pool is set as connection 088 * limiter (see {@link #setConnectionLimiter(PermitsPool)}), a 089 * permit is acquired for each new connection attempt. If no more 090 * permits are available, the server sends a {@link Purge} event on 091 * each channel that is purgeable for at least the time span 092 * set with {@link #setMinimalPurgeableTime(long)}. Purgeability 093 * is derived from the end of record flag of {@link Output} events 094 * (see {@link #onOutput(Output, TcpChannelImpl)}. When using this feature, 095 * make sure that connections are either short lived or the application 096 * level components support the {@link Purge} event. Else, it may become 097 * impossible to establish new connections. 098 */ 099@SuppressWarnings({ "PMD.ExcessiveImports", "PMD.ExcessivePublicCount", 100 "PMD.NcssCount", "PMD.EmptyCatchBlock", "PMD.AvoidDuplicateLiterals", 101 "PMD.ExcessiveClassLength" }) 102public class TcpServer extends TcpConnectionManager implements NioHandler { 103 104 private InetSocketAddress serverAddress; 105 private ServerSocketChannel serverSocketChannel; 106 private boolean closing; 107 private int backlog; 108 private PermitsPool connLimiter; 109 private Registration registration; 110 private Purger purger; 111 private long minimumPurgeableTime; 112 113 /** 114 * The purger thread. 115 */ 116 private class Purger extends Thread implements AvailabilityListener { 117 118 private boolean permitsAvailable = true; 119 120 /** 121 * Instantiates a new purger. 122 */ 123 public Purger() { 124 setName(Components.simpleObjectName(this)); 125 setDaemon(true); 126 } 127 128 @Override 129 public void availabilityChanged(PermitsPool pool, boolean available) { 130 if (registration == null) { 131 return; 132 } 133 synchronized (this) { 134 permitsAvailable = available; 135 registration.updateInterested( 136 permitsAvailable ? SelectionKey.OP_ACCEPT : 0); 137 if (!permitsAvailable) { 138 this.notifyAll(); 139 } 140 } 141 } 142 143 @Override 144 @SuppressWarnings({ "PMD.AvoidInstantiatingObjectsInLoops", 145 "PMD.DataflowAnomalyAnalysis", "PMD.CognitiveComplexity" }) 146 public void run() { 147 if (connLimiter == null) { 148 return; 149 } 150 try { 151 connLimiter.addListener(this); 152 while (serverSocketChannel.isOpen()) { 153 synchronized (this) { 154 while (permitsAvailable) { 155 wait(); 156 } 157 } 158 // Copy to avoid ConcurrentModificationException 159 List<TcpChannelImpl> candidates; 160 synchronized (channels) { 161 candidates = new ArrayList<>(channels); 162 } 163 long purgeableSince 164 = System.currentTimeMillis() - minimumPurgeableTime; 165 candidates = candidates.stream() 166 .filter(channel -> channel.isPurgeable() 167 && channel.purgeableSince() < purgeableSince) 168 .sorted(new Comparator<TcpChannelImpl>() { 169 @Override 170 @SuppressWarnings("PMD.ShortVariable") 171 public int compare(TcpChannelImpl c1, 172 TcpChannelImpl c2) { 173 if (c1.purgeableSince() < c2 174 .purgeableSince()) { 175 return 1; 176 } 177 if (c1.purgeableSince() > c2 178 .purgeableSince()) { 179 return -1; 180 } 181 return 0; 182 } 183 }) 184 .collect(Collectors.toList()); 185 for (TcpChannelImpl channel : candidates) { 186 // Sorting may have taken time... 187 if (!channel.isPurgeable()) { 188 continue; 189 } 190 channel.downPipeline().fire(new Purge(), channel); 191 // Continue only as long as necessary 192 if (permitsAvailable) { 193 break; 194 } 195 } 196 sleep(1000); 197 } 198 } catch (InterruptedException e) { 199 // Fall through 200 } finally { 201 connLimiter.removeListener(this); 202 } 203 } 204 205 } 206 207 /** 208 * Creates a new server, using itself as component channel. 209 */ 210 public TcpServer() { 211 this(Channel.SELF); 212 } 213 214 /** 215 * Creates a new server using the given channel. 216 * 217 * @param componentChannel the component's channel 218 */ 219 public TcpServer(Channel componentChannel) { 220 super(componentChannel); 221 } 222 223 /** 224 * Sets the address to bind to. If none is set, the address and port 225 * are assigned automatically. 226 * 227 * @param serverAddress the address to bind to 228 * @return the TCP server for easy chaining 229 */ 230 public TcpServer setServerAddress(InetSocketAddress serverAddress) { 231 this.serverAddress = serverAddress; 232 return this; 233 } 234 235 @Override 236 public TcpServer setBufferSize(int size) { 237 super.setBufferSize(size); 238 return this; 239 } 240 241 /** 242 * The component can be configured with events that include 243 * a path (see @link {@link ConfigurationUpdate#paths()}) 244 * that matches this components path (see {@link Manager#componentPath()}). 245 * 246 * The following properties are recognized: 247 * 248 * `hostname` 249 * : If given, is used as first parameter for 250 * {@link InetSocketAddress#InetSocketAddress(String, int)}. 251 * 252 * `port` 253 * : If given, is used as parameter for 254 * {@link InetSocketAddress#InetSocketAddress(String, int)} 255 * or {@link InetSocketAddress#InetSocketAddress(int)}, 256 * depending on whether a host name is specified. Defaults to "0". 257 * 258 * `backlog` 259 * : See {@link #setBacklog(int)}. 260 * 261 * `bufferSize` 262 * : See {@link #setBufferSize(int)}. 263 * 264 * `maxConnections` 265 * : Calls {@link #setConnectionLimiter} with a 266 * {@link PermitsPool} of the specified size. 267 * 268 * `minimalPurgeableTime` 269 * : See {@link #setMinimalPurgeableTime(long)}. 270 * 271 * @param event the event 272 */ 273 @Handler 274 @SuppressWarnings("PMD.ConfusingTernary") 275 public void onConfigurationUpdate(ConfigurationUpdate event) { 276 event.values(componentPath()).ifPresent(values -> { 277 String hostname = values.get("hostname"); 278 if (hostname != null) { 279 setServerAddress(new InetSocketAddress(hostname, 280 Integer.parseInt(values.getOrDefault("port", "0")))); 281 } else if (values.containsKey("port")) { 282 setServerAddress(new InetSocketAddress( 283 Integer.parseInt(values.get("port")))); 284 } 285 Optional.ofNullable(values.get("backlog")).ifPresent( 286 value -> setBacklog(Integer.parseInt(value))); 287 Optional.ofNullable(values.get("bufferSize")).ifPresent( 288 value -> setBufferSize(Integer.parseInt(value))); 289 Optional.ofNullable(values.get("maxConnections")) 290 .map(Integer::parseInt).map(PermitsPool::new) 291 .ifPresent(this::setConnectionLimiter); 292 Optional.ofNullable(values.get("minimalPurgeableTime")) 293 .map(Long::parseLong).ifPresent(this::setMinimalPurgeableTime); 294 }); 295 } 296 297 /** 298 * Returns the server address. Before starting, the address is the 299 * address set with {@link #setServerAddress(InetSocketAddress)}. After 300 * starting the address is obtained from the created socket. 301 * 302 * @return the serverAddress 303 */ 304 public InetSocketAddress serverAddress() { 305 try { 306 return serverSocketChannel == null ? serverAddress 307 : (InetSocketAddress) serverSocketChannel.getLocalAddress(); 308 } catch (IOException e) { 309 return serverAddress; 310 } 311 } 312 313 /** 314 * Sets the backlog size. 315 * 316 * @param backlog the backlog to set 317 * @return the TCP server for easy chaining 318 */ 319 public TcpServer setBacklog(int backlog) { 320 this.backlog = backlog; 321 return this; 322 } 323 324 /** 325 * Return the configured backlog size. 326 * 327 * @return the backlog 328 */ 329 public int backlog() { 330 return backlog; 331 } 332 333 /** 334 * Sets a permit "pool". A new connection is created only if a permit 335 * can be obtained from the pool. 336 * 337 * A connection limiter must be set before starting the component. 338 * 339 * @param connectionLimiter the connection pool to set 340 * @return the TCP server for easy chaining 341 */ 342 public TcpServer setConnectionLimiter(PermitsPool connectionLimiter) { 343 this.connLimiter = connectionLimiter; 344 return this; 345 } 346 347 /** 348 * Returns the connection limiter. 349 * 350 * @return the connection Limiter 351 */ 352 public PermitsPool getConnectionLimiter() { 353 return connLimiter; 354 } 355 356 /** 357 * Sets a minimal time that a connection must be purgeable (idle) 358 * before it may be purged. 359 * 360 * @param millis the millis 361 * @return the tcp server 362 */ 363 public TcpServer setMinimalPurgeableTime(long millis) { 364 this.minimumPurgeableTime = millis; 365 return this; 366 } 367 368 /** 369 * Gets the minimal purgeable time. 370 * 371 * @return the minimal purgeable time 372 */ 373 public long getMinimalPurgeableTime() { 374 return minimumPurgeableTime; 375 } 376 377 /** 378 * Starts the server. 379 * 380 * @param event the start event 381 * @throws IOException if an I/O exception occurred 382 */ 383 @Handler 384 public void onStart(Start event) throws IOException { 385 closing = false; 386 serverSocketChannel = ServerSocketChannel.open(); 387 serverSocketChannel.bind(serverAddress, backlog); 388 MBeanView.addServer(this); 389 fire(new NioRegistration(this, serverSocketChannel, 390 SelectionKey.OP_ACCEPT, this), Channel.BROADCAST); 391 } 392 393 /** 394 * Handles the successful channel registration. 395 * 396 * @param event the event 397 * @throws InterruptedException the interrupted exception 398 * @throws IOException Signals that an I/O exception has occurred. 399 */ 400 @Handler(channels = Self.class) 401 public void onRegistered(NioRegistration.Completed event) 402 throws InterruptedException, IOException { 403 NioHandler handler = event.event().handler(); 404 if (handler == this) { 405 if (event.event().get() == null) { 406 fire(new Error(event, 407 "Registration failed, no NioDispatcher?")); 408 return; 409 } 410 registration = event.event().get(); 411 purger = new Purger(); 412 purger.start(); 413 fire(new Ready(serverSocketChannel.getLocalAddress())); 414 return; 415 } 416 if (handler instanceof TcpChannelImpl) { 417 TcpChannelImpl channel = (TcpChannelImpl) handler; 418 var accepted = new Accepted(channel.nioChannel().getLocalAddress(), 419 channel.nioChannel().getRemoteAddress(), false, 420 Collections.emptyList()); 421 var registration = event.event().get(); 422 // (1) Opening, (2) Accepted, (3) process input 423 channel.downPipeline().fire(Event.onCompletion(new Opening<Void>(), 424 e -> { 425 channel.downPipeline().fire(accepted, channel); 426 channel.registrationComplete(registration); 427 }), channel); 428 } 429 } 430 431 /* 432 * (non-Javadoc) 433 * 434 * @see org.jgrapes.io.NioSelectable#handleOps(int) 435 */ 436 @Override 437 public void handleOps(int ops) { 438 if ((ops & SelectionKey.OP_ACCEPT) == 0 || closing) { 439 return; 440 } 441 synchronized (channels) { 442 if (connLimiter != null && !connLimiter.tryAcquire()) { 443 return; 444 } 445 try { 446 @SuppressWarnings("PMD.CloseResource") 447 SocketChannel socketChannel = serverSocketChannel.accept(); 448 if (socketChannel == null) { 449 // "False alarm" 450 if (connLimiter != null) { 451 connLimiter.release(); 452 } 453 return; 454 } 455 channels.add(new TcpChannelImpl(null, socketChannel)); 456 } catch (IOException e) { 457 fire(new IOError(null, e)); 458 } 459 } 460 } 461 462 @Override 463 protected boolean removeChannel(TcpChannelImpl channel) { 464 synchronized (channels) { 465 if (!channels.remove(channel)) { 466 // Closed already 467 return false; 468 } 469 // In case the server is shutting down 470 channels.notifyAll(); 471 } 472 if (connLimiter != null) { 473 connLimiter.release(); 474 } 475 return true; 476 } 477 478 /** 479 * Shuts down the server or one of the connections to the server. 480 * 481 * @param event the event 482 * @throws IOException if an I/O exception occurred 483 * @throws InterruptedException if the execution was interrupted 484 */ 485 @Handler 486 @SuppressWarnings("PMD.DataflowAnomalyAnalysis") 487 public void onClose(Close event) throws IOException, InterruptedException { 488 boolean closeServer = false; 489 for (Channel channel : event.channels()) { 490 if (channels.contains(channel)) { 491 ((TcpChannelImpl) channel).close(); 492 continue; 493 } 494 if (channel instanceof Subchannel) { 495 // Some subchannel that we're not interested in. 496 continue; 497 } 498 // Close event on "main" channel 499 closeServer = true; 500 } 501 if (!closeServer) { 502 // Only connection(s) were to be closed. 503 return; 504 } 505 if (!serverSocketChannel.isOpen()) { 506 // Closed already 507 fire(new Closed<Void>()); 508 return; 509 } 510 synchronized (channels) { 511 closing = true; 512 // Copy to avoid concurrent modification exception 513 Set<TcpChannelImpl> conns = new HashSet<>(channels); 514 for (TcpChannelImpl conn : conns) { 515 conn.close(); 516 } 517 while (!channels.isEmpty()) { 518 channels.wait(); 519 } 520 } 521 serverSocketChannel.close(); 522 purger.interrupt(); 523 closing = false; 524 fire(new Closed<Void>()); 525 } 526 527 /** 528 * Shuts down the server by firing a {@link Close} using the 529 * server as channel. Note that this automatically results 530 * in closing all open connections by the runtime system 531 * and thus in {@link Closed} events on all subchannels. 532 * 533 * @param event the event 534 * @throws InterruptedException 535 */ 536 @Handler(priority = -1000) 537 public void onStop(Stop event) throws InterruptedException { 538 if (closing || !serverSocketChannel.isOpen()) { 539 return; 540 } 541 newEventPipeline().fire(new Close(), this).get(); 542 } 543 544 /** 545 * The Interface of the TcpServer MXBean. 546 */ 547 public interface TcpServerMXBean { 548 549 /** 550 * The Class ChannelInfo. 551 */ 552 class ChannelInfo { 553 554 private final TcpChannelImpl channel; 555 556 /** 557 * Instantiates a new channel info. 558 * 559 * @param channel the channel 560 */ 561 public ChannelInfo(TcpChannelImpl channel) { 562 this.channel = channel; 563 } 564 565 /** 566 * Checks if is purgeable. 567 * 568 * @return true, if is purgeable 569 */ 570 public boolean isPurgeable() { 571 return channel.isPurgeable(); 572 } 573 574 /** 575 * Gets the downstream pool. 576 * 577 * @return the downstream pool 578 */ 579 public String getDownstreamPool() { 580 return channel.readBuffers().name(); 581 } 582 583 /** 584 * Gets the upstream pool. 585 * 586 * @return the upstream pool 587 */ 588 public String getUpstreamPool() { 589 return channel.byteBufferPool().name(); 590 } 591 } 592 593 /** 594 * Gets the component path. 595 * 596 * @return the component path 597 */ 598 String getComponentPath(); 599 600 /** 601 * Gets the port. 602 * 603 * @return the port 604 */ 605 int getPort(); 606 607 /** 608 * Gets the channel count. 609 * 610 * @return the channel count 611 */ 612 int getChannelCount(); 613 614 /** 615 * Gets the channels. 616 * 617 * @return the channels 618 */ 619 SortedMap<String, ChannelInfo> getChannels(); 620 621 } 622 623 /** 624 * The Class TcpServerInfo. 625 */ 626 public static class TcpServerInfo implements TcpServerMXBean { 627 628 private static MBeanServer mbs 629 = ManagementFactory.getPlatformMBeanServer(); 630 631 private ObjectName mbeanName; 632 private final WeakReference<TcpServer> serverRef; 633 634 /** 635 * Instantiates a new tcp server info. 636 * 637 * @param server the server 638 */ 639 @SuppressWarnings({ "PMD.EmptyCatchBlock", 640 "PMD.AvoidCatchingGenericException" }) 641 public TcpServerInfo(TcpServer server) { 642 serverRef = new WeakReference<>(server); 643 try { 644 int port = server.serverAddress().getPort(); 645 mbeanName = new ObjectName("org.jgrapes.io:type=" 646 + TcpServer.class.getSimpleName() + ",name=" 647 + ObjectName.quote(Components.objectName(server) 648 + " (:" + port + ")")); 649 } catch (MalformedObjectNameException e) { 650 // Should not happen 651 } 652 try { 653 mbs.unregisterMBean(mbeanName); 654 } catch (Exception e) { 655 // Just in case, should not work 656 } 657 try { 658 mbs.registerMBean(this, mbeanName); 659 } catch (InstanceAlreadyExistsException | MBeanRegistrationException 660 | NotCompliantMBeanException e) { 661 // Have to live with that 662 } 663 } 664 665 /** 666 * Server. 667 * 668 * @return the optional 669 */ 670 @SuppressWarnings({ "PMD.AvoidCatchingGenericException", 671 "PMD.EmptyCatchBlock" }) 672 public Optional<TcpServer> server() { 673 TcpServer server = serverRef.get(); 674 if (server == null) { 675 try { 676 mbs.unregisterMBean(mbeanName); 677 } catch (Exception e) { 678 // Should work. 679 } 680 } 681 return Optional.ofNullable(server); 682 } 683 684 /* 685 * (non-Javadoc) 686 * 687 * @see org.jgrapes.net.TcpServer.TcpServerMXBean#getComponentPath() 688 */ 689 @Override 690 public String getComponentPath() { 691 return server().map(mgr -> mgr.componentPath()).orElse("<removed>"); 692 } 693 694 /* 695 * (non-Javadoc) 696 * 697 * @see org.jgrapes.net.TcpServer.TcpServerMXBean#getPort() 698 */ 699 @Override 700 public int getPort() { 701 return server().map(server -> server 702 .serverAddress().getPort()).orElse(0); 703 } 704 705 /* 706 * (non-Javadoc) 707 * 708 * @see org.jgrapes.net.TcpServer.TcpServerMXBean#getChannelCount() 709 */ 710 @Override 711 public int getChannelCount() { 712 return server().map(server -> server.channels.size()).orElse(0); 713 } 714 715 /* 716 * (non-Javadoc) 717 * 718 * @see org.jgrapes.net.TcpServer.TcpServerMXBean#getChannels() 719 */ 720 @Override 721 @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops") 722 public SortedMap<String, ChannelInfo> getChannels() { 723 return server().map(server -> { 724 SortedMap<String, ChannelInfo> result = new TreeMap<>(); 725 for (TcpChannelImpl channel : server.channels) { 726 result.put(channel.nioChannel().socket() 727 .getRemoteSocketAddress().toString(), 728 new ChannelInfo(channel)); 729 } 730 return result; 731 }).orElse(Collections.emptySortedMap()); 732 } 733 } 734 735 /** 736 * An MBean interface for getting information about the TCP servers 737 * and established connections. 738 */ 739 public interface TcpServerSummaryMXBean { 740 741 /** 742 * Gets the connections per server statistics. 743 * 744 * @return the connections per server statistics 745 */ 746 IntSummaryStatistics getConnectionsPerServerStatistics(); 747 748 /** 749 * Gets the servers. 750 * 751 * @return the servers 752 */ 753 Set<TcpServerMXBean> getServers(); 754 } 755 756 /** 757 * The MBeanView. 758 */ 759 private static class MBeanView implements TcpServerSummaryMXBean { 760 private static Set<TcpServerInfo> serverInfos = new HashSet<>(); 761 762 /** 763 * Adds the server to the reported servers. 764 * 765 * @param server the server 766 */ 767 public static void addServer(TcpServer server) { 768 synchronized (serverInfos) { 769 serverInfos.add(new TcpServerInfo(server)); 770 } 771 } 772 773 /** 774 * Returns the infos. 775 * 776 * @return the sets the 777 */ 778 private Set<TcpServerInfo> infos() { 779 Set<TcpServerInfo> expired = new HashSet<>(); 780 synchronized (serverInfos) { 781 for (TcpServerInfo serverInfo : serverInfos) { 782 if (!serverInfo.server().isPresent()) { 783 expired.add(serverInfo); 784 } 785 } 786 serverInfos.removeAll(expired); 787 } 788 return serverInfos; 789 } 790 791 @SuppressWarnings("unchecked") 792 @Override 793 public Set<TcpServerMXBean> getServers() { 794 return (Set<TcpServerMXBean>) (Object) infos(); 795 } 796 797 @Override 798 public IntSummaryStatistics getConnectionsPerServerStatistics() { 799 return infos().stream().map(info -> info.server().get()) 800 .filter(ref -> ref != null).collect( 801 Collectors.summarizingInt(srv -> srv.channels.size())); 802 } 803 } 804 805 static { 806 try { 807 MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); 808 ObjectName mxbeanName = new ObjectName("org.jgrapes.io:type=" 809 + TcpServer.class.getSimpleName() + "s"); 810 mbs.registerMBean(new MBeanView(), mxbeanName); 811 } catch (MalformedObjectNameException | InstanceAlreadyExistsException 812 | MBeanRegistrationException | NotCompliantMBeanException e) { 813 // Does not happen 814 } 815 } 816 817}