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}