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