001/*
002 * ============================================================================
003 * Copyright © 2002-2023 by Thomas Thrien.
004 * All Rights Reserved.
005 * ============================================================================
006 * Licensed to the public under the agreements of the GNU Lesser General Public
007 * License, version 3.0 (the "License"). You may obtain a copy of the License at
008 *
009 *      http://www.gnu.org/licenses/lgpl.html
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, WITHOUT
013 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
014 * License for the specific language governing permissions and limitations
015 * under the License.
016 */
017
018package org.tquadrat.foundation.mgmt.internal;
019
020import static java.lang.String.format;
021import static java.lang.System.currentTimeMillis;
022import static java.lang.management.ManagementFactory.getPlatformMBeanServer;
023import static java.util.concurrent.Executors.newSingleThreadExecutor;
024import static org.apiguardian.api.API.Status.INTERNAL;
025import static org.tquadrat.foundation.lang.Objects.isNull;
026import static org.tquadrat.foundation.lang.Objects.nonNull;
027import static org.tquadrat.foundation.lang.Objects.requireNonNullArgument;
028import static org.tquadrat.foundation.lang.Objects.requireNotEmptyArgument;
029
030import javax.management.Attribute;
031import javax.management.AttributeChangeNotification;
032import javax.management.AttributeList;
033import javax.management.AttributeNotFoundException;
034import javax.management.DynamicMBean;
035import javax.management.InstanceAlreadyExistsException;
036import javax.management.InstanceNotFoundException;
037import javax.management.IntrospectionException;
038import javax.management.InvalidAttributeValueException;
039import javax.management.ListenerNotFoundException;
040import javax.management.MBeanAttributeInfo;
041import javax.management.MBeanException;
042import javax.management.MBeanInfo;
043import javax.management.MBeanNotificationInfo;
044import javax.management.MBeanOperationInfo;
045import javax.management.MBeanParameterInfo;
046import javax.management.MBeanRegistrationException;
047import javax.management.MalformedObjectNameException;
048import javax.management.NotCompliantMBeanException;
049import javax.management.Notification;
050import javax.management.NotificationBroadcasterSupport;
051import javax.management.NotificationFilter;
052import javax.management.NotificationListener;
053import javax.management.ObjectName;
054import javax.management.ReflectionException;
055import java.lang.reflect.InvocationTargetException;
056import java.lang.reflect.Method;
057import java.util.Collection;
058import java.util.HashMap;
059import java.util.HashSet;
060import java.util.LinkedList;
061import java.util.Map;
062import java.util.Optional;
063import java.util.concurrent.Executor;
064import java.util.concurrent.ThreadFactory;
065import java.util.concurrent.atomic.AtomicLong;
066
067import org.apiguardian.api.API;
068import org.tquadrat.foundation.annotation.ClassVersion;
069import org.tquadrat.foundation.exception.ImpossibleExceptionError;
070import org.tquadrat.foundation.lang.Lazy;
071import org.tquadrat.foundation.lang.Objects;
072import org.tquadrat.foundation.mgmt.JMXSupport;
073import org.tquadrat.foundation.mgmt.MBeanAction;
074import org.tquadrat.foundation.mgmt.MBeanGetter;
075import org.tquadrat.foundation.mgmt.MBeanNotification;
076import org.tquadrat.foundation.mgmt.MBeanNotifications;
077import org.tquadrat.foundation.mgmt.MBeanParameter;
078import org.tquadrat.foundation.mgmt.MBeanSetter;
079import org.tquadrat.foundation.mgmt.ManagedObject;
080import org.tquadrat.foundation.stream.MapStream;
081
082/**
083 *  This class implements the interface
084 *  {@link JMXSupport}
085 *  that in turn extends the definition for a dynamic MBean
086 *  ({@link javax.management.DynamicMBean})
087 *  and can instrument any object whose class is annotated properly.
088 *
089 *  @param  <T> The type of the managed object.
090 *
091 *  @see ManagedObject
092 *  @see MBeanAction
093 *  @see MBeanGetter
094 *  @see MBeanNotification
095 *  @see MBeanNotifications
096 *  @see MBeanSetter
097 *
098 *  @extauthor Thomas Thrien - thomas.thrien@tquadrat.org
099 *  @version $Id: JMXSupportImpl.java 1070 2023-09-29 17:09:34Z tquadrat $
100 *  @since 0.0.1
101 *
102 *  @UMLGraph.link
103 */
104@SuppressWarnings( "OverlyComplexClass" )
105@ClassVersion( sourceVersion = "$Id: JMXSupportImpl.java 1070 2023-09-29 17:09:34Z tquadrat $" )
106@API( status = INTERNAL, since = "0.0.1" )
107public final class JMXSupportImpl<T> implements JMXSupport<T>
108{
109        /*-----------*\
110    ====** Constants **========================================================
111        \*-----------*/
112    /**
113     *  Message: {@value}.
114     */
115    public static final String MSG_ActionNotFound = "There is no MBean action with the name '%1$s'";
116
117    /**
118     *  Message: {@value}.
119     */
120    public static final String MSG_AttributeNotFound = "The MBean attribute '%1$s' could not be found";
121
122    /**
123     *  Message: {@value}.
124     */
125    public static final String MSG_InvalidAttributeValue = "The value '%2$s' is invalid for the MBean attribute with the name '%1$s'";
126
127    /**
128     *  Message: {@value}.
129     */
130    public static final String MSG_InvocationDenied = "Cannot invoke the MBean action '%1$s'";
131
132    /**
133     *  Message: {@value}.
134     */
135    public static final String MSG_InvocationException = "The invocation of the MBean action '%1$s' caused an Exception";
136
137    /**
138     *  Message: {@value}.
139     */
140    public static final String MSG_InvocationProblems = "There was a problem when invoking the MBean action '%1$s'";
141
142    /**
143     *  Message: {@value}.
144     */
145    public static final String MSG_NoManagedObject = "The given Object is not annotated as 'ManagedObject'";
146
147    /**
148     *  Message: {@value}.
149     */
150    public static final String MSG_NoProperties = "No properties had been provided";
151
152    /**
153     *  Message: {@value}.
154     */
155    public static final String MSG_SetterDenied = "The invocation of the setter for the MBean attribute '%1$s' was denied";
156
157    /**
158     *  Message: {@value}.
159     */
160    public static final String MSG_SetterException = "The invocation of the setter for the MBean attribute '%1$s' caused an Exception";
161
162    /**
163     *  Message: {@value}.
164     */
165    public static final String MSG_SetterProblems = "There was a problem when invoking the setter for the MBean attribute '%1$s'";
166
167        /*------------*\
168    ====** Attributes **=======================================================
169        \*------------*/
170    /**
171     *  {@summary This object supports the broadcasting of notifications.} It
172     *  is
173     *  {@linkplain Optional#empty()}
174     *  if the object, that is instrumented by this instance of
175     *  {@code JMXSupport}, indicates in its
176     *  {@link ManagedObject}
177     *  annotation that it will not use notifications.
178     */
179    @SuppressWarnings( "OptionalUsedAsFieldOrParameterType" )
180    private Optional<NotificationBroadcasterSupport> m_BroadcasterSupport = Optional.empty();
181
182    /**
183     *  The actions (operations) for the JMX API; the action name is the key
184     *  for this map.
185     */
186    private final Map<String,Method> m_MBeanActions = new HashMap<>();
187
188    /**
189     *  The getters for the JMX API; the attribute name is the key for this
190     *  map.
191     */
192    private final Map<String,Method> m_MBeanGetters = new HashMap<>();
193
194    /**
195     *  The predefined
196     *  {@link MBeanInfo}
197     *  structure.
198     */
199    private final Lazy<MBeanInfo> m_MBeanInfo;
200
201    /**
202     *  The setters for the JMX API; the attribute name is the key for this
203     *  map.
204     */
205    private final Map<String,Method> m_MBeanSetters = new HashMap<>();
206
207    /**
208     *  The object that is instrumented by this generic MBean.
209     */
210    private final T m_Object;
211
212    /**
213     *  The description for the object that is instrumented by this generic
214     *  MBean.
215     */
216    private final String m_ObjectDescription;
217
218    /**
219     *  The object name for this generic MBean.
220     */
221    private final ObjectName m_ObjectName;
222
223    /**
224     *  The notification sequence number.
225     */
226    private final AtomicLong m_Sequence = new AtomicLong( 0 );
227
228        /*--------------*\
229    ====** Constructors **=====================================================
230        \*--------------*/
231    /**
232     *  Initialises the abstract server part of a concrete server
233     *  implementation.
234     *
235     *  @param  object  The object to register with the JMX agent.
236     *  @param  name  The object name for the MBean.
237     *  @param  description The description for the MBean.
238     *  @param  useNotifications    {@code true} if the instrumented object
239     *      will send notifications, {@code false} (the default) otherwise.
240     *  @param  threadFactory   The thread factory that is used when
241     *      notifications will be emitted asynchronously; can be {@code null}
242     *      and will be ignored if {@code useNotifications} is {@code false}.
243     */
244    @SuppressWarnings( "OverlyComplexMethod" )
245    private JMXSupportImpl( final T object, final ObjectName name, final String description, final boolean useNotifications, final ThreadFactory threadFactory )
246    {
247        m_MBeanInfo = Lazy.use( this::createMBeanInfo );
248
249        //---* Save the parameters *-------------------------------------------
250        m_Object = requireNonNullArgument( object, "object" );
251        m_ObjectName = requireNonNullArgument( name, "objectName" );
252        m_ObjectDescription = requireNotEmptyArgument( description, "description" );
253
254        //---* Collect the methods *-------------------------------------------
255        for( final var method: object.getClass().getMethods() )
256        {
257            //---* It is a getter *--------------------------------------------
258            if( method.isAnnotationPresent( MBeanGetter.class ) )
259            {
260                /*
261                 *  A getter may not have any parameters and must return a
262                 *  value, but in this context it is not required that its name
263                 *  starts with "get".
264                 */
265                if( (method.getParameterTypes().length == 0) && !method.getReturnType().equals( void.class ) )
266                {
267                    final var annotation = method.getAnnotation( MBeanGetter.class );
268                    m_MBeanGetters.put( annotation.attribute(), method );
269                }
270            }
271
272            //---* It is a setter *--------------------------------------------
273            if( method.isAnnotationPresent( MBeanSetter.class ) )
274            {
275                /*
276                 *  A setter must have exactly one parameter and may not return
277                 *  anything but void, but in this context it is not required
278                 *  that its name starts with "set".
279                 */
280                if( (method.getParameterTypes().length == 1) && method.getReturnType().equals( void.class ) )
281                {
282                    final var annotation = method.getAnnotation( MBeanSetter.class );
283                    m_MBeanSetters.put( annotation.attribute(), method );
284                }
285            }
286
287            //---* It is an action *-------------------------------------------
288            if( method.isAnnotationPresent( MBeanAction.class ) )
289            {
290                final var annotation = method.getAnnotation( MBeanAction.class );
291                m_MBeanActions.put( annotation.action(), method );
292            }
293        }
294
295        //---* Add the notification support *----------------------------------
296        if( useNotifications )
297        {
298            //---* Create the executor if necessary *--------------------------
299            final Executor executor = nonNull( threadFactory ) ? newSingleThreadExecutor( threadFactory ) : null;
300
301            //---* Retrieve the notification info *----------------------------
302            final var notificationInfo = createNotificationInfo( object );
303
304            //---* Create the broadcast support *------------------------------
305            m_BroadcasterSupport = Optional.of( new NotificationBroadcasterSupport( executor, notificationInfo ) );
306        }
307    }   //  JMXSupportImpl()
308
309        /*---------*\
310    ====** Methods **==========================================================
311        \*---------*/
312    /**
313     *  {@inheritDoc}
314     */
315    @Override
316    public final void addNotificationListener( final NotificationListener listener, final NotificationFilter filter, final Object handback )
317    {
318        m_BroadcasterSupport.ifPresent( broadcasterSupport ->broadcasterSupport.addNotificationListener( listener, filter, handback ) );
319    }   //  addNotificationListener()
320
321    /**
322     *  Creates the structure that holds the action information.
323     *
324     *  @return The action (or operation) information.
325     */
326    private final MBeanOperationInfo [] createActionInfo()
327    {
328        @SuppressWarnings( "OverlyLongLambda" )
329        final var retValue = MapStream.of( m_MBeanActions )
330            .map( e ->
331            {
332                final var name = e.getKey();
333                final var method = e.getValue();
334                final var annotation = method.getAnnotation( MBeanAction.class );
335                final var description = annotation.description();
336                final var signature = determineSignature( method );
337                final var returnType = method.getReturnType().getName();
338                final var impact = annotation.impact();
339
340                //---* Done *--------------------------------------------------
341                return new MBeanOperationInfo( name, description, signature, returnType, impact );
342            })
343            .toArray( MBeanOperationInfo []::new );
344
345        //---* Done *----------------------------------------------------------
346        return retValue;
347    }   //  createActionInfo()
348
349    /**
350     *  Creates the structure that holds the attribute information.
351     *
352     *  @return The attribute information.
353     *  @throws IntrospectionException  There is a consistency problem in the
354     *      definition of an attribute.
355     */
356    private final MBeanAttributeInfo [] createAttributeInfo() throws IntrospectionException
357    {
358        final Collection<MBeanAttributeInfo> attributeInfos = new LinkedList<>();
359        final Collection<String> processedSetters = new HashSet<>();
360
361        //---* Process the getters first *-------------------------------------
362        //noinspection OverlyLongLambda
363        m_MBeanGetters.forEach( (name,getter) ->
364        {
365            //---* Collect the values *----------------------------------------
366            final var type = getter.getReturnType().getName();
367            final var description = getter.getAnnotation( MBeanGetter.class ).description();
368            final var isWriteable = m_MBeanSetters.containsKey( name );
369            if( isWriteable )
370            {
371                //---* Setter is paired with the getter *----------------------
372                processedSetters.add( name );
373            }
374
375            //---* Create the info object *------------------------------------
376            attributeInfos.add( new MBeanAttributeInfo( name, type, description, true, isWriteable, false ) );
377        });
378
379        //---* Process the setters *-------------------------------------------
380        m_MBeanSetters.forEach( (name,setter) ->
381        {
382            //---* Check if already processed *--------------------------------
383            if( !processedSetters.contains( name ) )
384            {
385                //---* Collect the values *------------------------------------
386                final var type = setter.getParameterTypes() [0].getName();
387                final var description = setter.getAnnotation( MBeanSetter.class ).description();
388
389                //---* Create the info object *--------------------------------
390                /*
391                 * Setters that have a correspondent getter are already
392                 * processed when the getter was handled; therefore all the
393                 * attributes created here will be read-only (the isReadable
394                 * argument is set to false).
395                 */
396                attributeInfos.add( new MBeanAttributeInfo( name, type, description, false, true, false ) );
397            }
398        });
399
400        //---* Cleanup *-------------------------------------------------------
401        processedSetters.clear();
402
403        //---* Compose the return values *-------------------------------------
404        final var retValue = attributeInfos.toArray( MBeanAttributeInfo []::new );
405
406        //---* Done *----------------------------------------------------------
407        return retValue;
408    }   //  createAttributeInfo()
409
410    /**
411     *  Creates the MBeanInfo data structure.<br>
412     *  <br>This implementation will swallow all exceptions, an overriding
413     *  implementation may want to log it.
414     *
415     *  @return An instance of
416     *      {@link MBeanInfo}
417     *      allowing all attributes and actions exposed by this generic MBean
418     *      to be retrieved.
419     */
420    private MBeanInfo createMBeanInfo()
421    {
422        MBeanInfo retValue = null;
423        try
424        {
425            retValue = new MBeanInfo
426            (
427                getObject().getClass().getName(), // The name of the instrumented class.
428                getObjectDescription(),           // The description of the instrumented object.
429                createAttributeInfo(),            // The attributes.
430                null,                             // The constructors.
431                createActionInfo(),               // The operations.
432                getNotificationInfo()             // The notifications.
433           );
434        }
435        catch( @SuppressWarnings( "unused" ) final IntrospectionException ignored ) { /* Ignored */ }
436
437        //---* Done *----------------------------------------------------------
438        return retValue;
439    }   //  createMBeanInfo()
440
441    /**
442     *  Creates a single notification info element.
443     *
444     *  @param  notification    The annotation data.
445     *  @return The notification info element.
446     */
447    private static MBeanNotificationInfo createNotificationInfo( final MBeanNotification notification )
448    {
449        final var retValue = new MBeanNotificationInfo( notification.notifierTypes(), notification.notificationClass().getName(), notification.description() );
450
451        //---* Done *----------------------------------------------------------
452        return retValue;
453    }   //  createNotificationInfo()
454
455    /**
456     *  Creates the structure that holds the notification info.
457     *
458     *  @param  object  The object to examine.
459     *  @return The notification info structure.
460     */
461    private MBeanNotificationInfo [] createNotificationInfo( final Object object )
462    {
463        final Collection<MBeanNotificationInfo> notificationInfos = new LinkedList<>();
464
465        //---* Get class level notification declarations *---------------------
466        var notifications = m_Object.getClass().getAnnotation( MBeanNotifications.class );
467        if( nonNull( notifications ) )
468        {
469            for( final var entry : notifications.value() )
470            {
471                notificationInfos.add( createNotificationInfo( entry ) );
472            }
473        }
474
475        var notification = m_Object.getClass().getAnnotation( MBeanNotification.class );
476        if( nonNull( notification ) )
477        {
478            notificationInfos.add( createNotificationInfo( notification ) );
479        }
480
481        //---* Get the method level notifications *----------------------------
482        for( final var method: object.getClass().getMethods() )
483        {
484            notifications = method.getAnnotation( MBeanNotifications.class );
485            if( nonNull( notifications ) )
486            {
487                for( final var entry : notifications.value() )
488                {
489                    notificationInfos.add( createNotificationInfo( entry ) );
490                }
491            }
492
493            notification = method.getAnnotation( MBeanNotification.class );
494            if( nonNull( notification ) )
495            {
496                notificationInfos.add( createNotificationInfo( notification ) );
497            }
498        }
499
500        //---* Compose the return value *--------------------------------------
501        final var retValue = notificationInfos.isEmpty()
502            ? null
503            : notificationInfos.toArray( MBeanNotificationInfo[]::new );
504
505        //---* Done *----------------------------------------------------------
506        return retValue;
507    }   //  createNotificationInfo()
508
509    /**
510     *  Determines the parameter information for the given method.
511     *
512     *  @param  method  The method to analyse.
513     *  @return The parameter info structure.
514     */
515    private static MBeanParameterInfo [] determineSignature( final Method method )
516    {
517        MBeanParameterInfo [] retValue = null;
518
519        //---* Get the parameter types *---------------------------------------
520        final var parameterTypes = method.getParameterTypes();
521        if( parameterTypes.length > 0 )
522        {
523            retValue = new MBeanParameterInfo [parameterTypes.length];
524
525            //---* Get the annotations *---------------------------------------
526            final var allAnnotations = method.getParameterAnnotations();
527
528            //---* Lets compose the signature *--------------------------------
529            String name;
530            String type;
531            String description;
532            if( allAnnotations.length > 0 )
533            {
534                for( var i = 0; i < parameterTypes.length; ++i )
535                {
536                    //---* Collect the values *--------------------------------
537                    type = parameterTypes [i].getName();
538                    name = format( "arg%d", i );
539                    description = "?";
540                    for( final var annotation : allAnnotations [i] )
541                    {
542                        //---* Is it the right annotation type? *--------------
543                        if( annotation instanceof final MBeanParameter parameterAnnotation )
544                        {
545                            name = parameterAnnotation.name();
546                            description = parameterAnnotation.description();
547                            break;
548                        }
549                    }
550
551                    //---* Create the information object *---------------------
552                    retValue [i] = new MBeanParameterInfo( name, type, description );
553                }
554            }
555            else
556            {
557                description = "?";
558                for( var i = 0; i < parameterTypes.length; ++i )
559                {
560                    //---* Collect the values *--------------------------------
561                    type = parameterTypes [i].getName();
562                    name = format( "arg%1$d", i );
563
564                    //---* Create the information object *---------------------
565                    retValue [i] = new MBeanParameterInfo( name, type, description );
566                }
567            }
568        }
569
570        //---* Done *----------------------------------------------------------
571        return retValue;
572    }   //  determineSignature()
573
574    /**
575     *  {@inheritDoc}
576     */
577    @Override
578    public final Object getAttribute( final String attributeName ) throws AttributeNotFoundException, MBeanException, ReflectionException
579    {
580        //---* Get the getter *------------------------------------------------
581        final var method = m_MBeanGetters.get( requireNonNullArgument( attributeName, "attributeName" ) );
582        if( isNull( method ) )
583        {
584            throw new AttributeNotFoundException( format( MSG_AttributeNotFound, attributeName ) );
585        }
586
587        //---* Retrieve the attributes value *---------------------------------
588        Object retValue = null;
589        try
590        {
591            retValue = method.invoke( m_Object, (Object []) null );
592        }
593        catch( final InvocationTargetException | IllegalArgumentException | IllegalAccessException e )
594        {
595            throw new ReflectionException( e );
596        }
597
598        //---* Done *----------------------------------------------------------
599        return retValue;
600    }   //  getAttribute()
601
602    /**
603     *  Get the values of several attributes of the Dynamic MBean. This
604     *  implementation will swallow all exceptions that might be thrown.
605     *
606     *  @param  attributes  A list of the attributes to be retrieved.
607     *  @return The list of attributes retrieved.
608     */
609    @Override
610    public AttributeList getAttributes( final String [] attributes )
611    {
612        //---* Create the return value *---------------------------------------
613        final var retValue = new AttributeList();
614        Attribute attribute;
615        for( final var attributeName: requireNonNullArgument( attributes, "attributes" ) )
616        {
617            try
618            {
619                attribute = new Attribute( attributeName, getAttribute( attributeName ) );
620                retValue.add( attribute );
621            }
622            catch( @SuppressWarnings( "unused" ) final AttributeNotFoundException | MBeanException | ReflectionException ignored ) { /* Ignored! */ }
623        }
624
625        //---* Done *----------------------------------------------------------
626        return retValue;
627    }   //  getAttributes()
628
629    /**
630     *  {@inheritDoc}
631     */
632    @Override
633    public final MBeanInfo getMBeanInfo() { return m_MBeanInfo.get(); }
634
635    /**
636     *  {@inheritDoc}
637     */
638    @Override
639    public final long getNextNotificationSequenceNumber() { return m_Sequence.get(); }
640
641    /**
642     *  {@inheritDoc}
643     */
644    @Override
645    public final MBeanNotificationInfo [] getNotificationInfo()
646    {
647        final var retValue = m_BroadcasterSupport.map( NotificationBroadcasterSupport::getNotificationInfo ).orElse( null );
648
649        //---* Done *----------------------------------------------------------
650        return retValue;
651    }   //  getNotificationInfo()
652
653    /**
654     *  {@inheritDoc}
655     */
656    @Override
657    public final T getObject() { return m_Object; }
658
659    /**
660     *  {@inheritDoc}
661     */
662    @Override
663    public final String getObjectDescription() { return m_ObjectDescription; }
664
665    /**
666     *  {@inheritDoc}
667     */
668    @Override
669    public final ObjectName getObjectName()
670    {
671        ObjectName retValue = null;
672
673        //---* Create the return value *---------------------------------------
674        try
675        {
676            retValue =  new ObjectName( m_ObjectName.getDomain(), m_ObjectName.getKeyPropertyList() );
677        }
678        catch( final MalformedObjectNameException e )
679        {
680            throw new ImpossibleExceptionError( e );
681        }
682
683        //---* Done *----------------------------------------------------------
684        return retValue;
685    }   //  getObjectName()
686
687    /**
688     *  {@inheritDoc}
689     */
690    @Override
691    public final Object invoke( final String actionName, final Object [] params, final String [] signature ) throws MBeanException, ReflectionException
692    {
693        Object retValue = null;
694
695        //---* Get the method for the action *---------------------------------
696        final var method = m_MBeanActions.get( actionName );
697        if( nonNull( method ) )
698        {
699            //noinspection OverlyBroadCatchBlock
700            try
701            {
702                retValue = method.invoke( m_Object, params );
703            }
704            catch( final InvocationTargetException e )
705            {
706                final var throwable = e.getCause();
707                final var cause = throwable instanceof Exception ? (Exception) throwable : e;
708                throw new ReflectionException( cause, format( MSG_InvocationProblems, actionName ) );
709            }
710            catch( final IllegalAccessException e )
711            {
712                throw new ReflectionException( e, format( MSG_InvocationDenied, actionName ) );
713            }
714            catch( final Exception e )
715            {
716                throw new MBeanException( e, format( MSG_InvocationException, actionName ) );
717            }
718        }
719        else
720        {
721            throw new MBeanException( new NoSuchMethodException( format( MSG_ActionNotFound, actionName ) ) );
722        }
723
724        //---* Done *----------------------------------------------------------
725        return retValue;
726    }   //  invoke()
727
728    /**
729     *  {@inheritDoc}
730     */
731    @Override
732    public final void removeNotificationListener( final NotificationListener listener ) throws ListenerNotFoundException
733    {
734        if( m_BroadcasterSupport.isPresent() )
735        {
736            m_BroadcasterSupport.get().removeNotificationListener( listener );
737        }
738    }   //  removeNotificationListener()
739
740    /**
741     *  {@inheritDoc}
742     */
743    @Override
744    public final void removeNotificationListener( final NotificationListener listener, final NotificationFilter filter, final Object handback ) throws ListenerNotFoundException
745    {
746        if( m_BroadcasterSupport.isPresent() )
747        {
748            m_BroadcasterSupport.get().removeNotificationListener( listener, filter, handback );
749        }
750    }   //  removeNotificationListener()
751
752    /**
753     *  Registers the server with the JMX agent.
754     *
755     *  @param  <O> The type of the object.
756     *  @param  object  The object to register with the JMX agent.
757     *  @param  objectName  The object name that is used for the object to
758     *      register.
759     *  @param  threadFactory   The thread factory that is used when
760     *      notifications should be sent asynchronously; can be {@code null}.
761     *  @return The MBean object that was generated as the instrumentation for
762     *      the object to manage.
763     *  @throws IllegalArgumentException    The object is not annotated with
764     *      {@link ManagedObject &#64;ManagedObject}.
765     *  @throws InstanceAlreadyExistsException  There is already a MBean with
766     *      the given object name registered.
767     *  @throws MBeanRegistrationException  Problems with the registration of
768     *      the MBean.
769     */
770    public static <O> JMXSupport<O> register( final O object, final ObjectName objectName, final ThreadFactory threadFactory ) throws IllegalArgumentException, InstanceAlreadyExistsException, MBeanRegistrationException
771    {
772        final var instrumentation = requireNonNullArgument( object, "object" )
773            .getClass()
774            .getAnnotation( ManagedObject.class );
775        if( isNull( instrumentation ) ) throw new IllegalArgumentException( MSG_NoManagedObject );
776        final var description = instrumentation.description();
777        final var useNotifications = instrumentation.useNotifications();
778
779        //---* Create the MBean *----------------------------------------------
780        final var retValue = new JMXSupportImpl<>( object, requireNonNullArgument( objectName, "objectName" ), description, useNotifications, threadFactory );
781
782        //---* Register the MBean with the platform MBeanServer *--------------
783        registerMBean( retValue, objectName );
784
785        //---* Done *----------------------------------------------------------
786        return retValue;
787    }   //  register()
788
789    /**
790     *  Register the MBean with the platform MBeanServer.
791     *
792     *  @param  mbean   The MBean to register.
793     *  @param  objectName  The object name for the registration.
794     *  @throws InstanceAlreadyExistsException  There is already an MBean with
795     *      the given object name.
796     *  @throws MBeanRegistrationException  Problems with the registration of
797     *      the MBean.
798     */
799    private static void registerMBean( final DynamicMBean mbean, final ObjectName objectName ) throws InstanceAlreadyExistsException, MBeanRegistrationException
800    {
801        final var mbeanServer = getPlatformMBeanServer();
802        try
803        {
804            mbeanServer.registerMBean( mbean, objectName );
805        }
806        catch( final NotCompliantMBeanException e )
807        {
808            throw new ImpossibleExceptionError( e );
809        }
810    }   //  registerMBean()
811
812    /**
813     *  Returns the notification sequence number for the current notification
814     *  and increments the number.
815     *
816     *  @return The next sequence number.
817     */
818    private long retrieveNextNotificationSequenceNumber() { return m_Sequence.getAndIncrement(); }
819
820    /**
821     *  Sends an attribute change notification. The type is always
822     *  {@code jmx.attribute.change}.
823     *
824     *  @param  <A> The type for the attribute.
825     *  @param  <V> The type for the values.
826     *  @param  message The message for this notification.
827     *  @param  description The description for the attribute.
828     *  @param  attributeType   The type of the attribute.
829     *  @param  oldValue    The old value.
830     *  @param  newValue    The new value.
831     */
832    @Override
833    public final <A,V extends A> void sendAttributeChangeNotification( final String message, final String description, final Class<A> attributeType, final V oldValue, final V newValue )
834    {
835        final Notification notification = new AttributeChangeNotification
836        (
837            this,
838            retrieveNextNotificationSequenceNumber(),
839            currentTimeMillis(),
840            message,
841            description,
842            attributeType.getName(),
843            oldValue,
844            newValue
845        );
846        sendNotification( notification );
847    }   //  sendAttributeChangeNotification()
848
849    /**
850     *  <p>{@summary Sends a notification.} It is not recommended using this
851     *  method directly as this class will provide some very helpful
852     *  convenience methods.</p>
853     *  <p>If it is really necessary to create your own implementation for a
854     *  notification, it is important that the source element is set to a
855     *  reference to this instance, and not to the object that is instrumented
856     *  using this object.
857     *
858     *  @param  notification    The notification to send.
859     *
860     *  @see javax.management.NotificationBroadcasterSupport#sendNotification(javax.management.Notification)
861     *  @see javax.management.Notification#setSource(Object)
862     *  @see javax.management.Notification#getSource()
863     */
864    private final void sendNotification( final Notification notification )
865    {
866        if( nonNull( notification ) )
867        {
868            m_BroadcasterSupport.ifPresent( broadcasterSupport -> broadcasterSupport.sendNotification( notification ) );
869        }
870    }   //  sendNotification()
871
872    /**
873     *  Sends a simple notification with a message text.
874     *
875     *  @param  type    The type of the notification.
876     *  @param  message The message for this notification.
877     */
878    @Override
879    public final void sendNotification( final String type, final String message )
880    {
881        final var notification = new Notification( type, this, retrieveNextNotificationSequenceNumber(), currentTimeMillis(), message );
882        sendNotification( notification );
883    }   //  sendNotification()
884
885    /**
886     *  Sets the value of a specific attribute of the generic MBean.
887     *
888     *  @param  attribute   The identification of the attribute to be set and
889     *      the value it is to be set to.
890     *  @throws AttributeNotFoundException  There is no attribute with the
891     *      given name.
892     *  @throws InvalidAttributeValueException  The value of the attribute does
893     *      not fit to the attribute's declaration.
894     *  @throws MBeanException  Wraps a
895     *      {@link java.lang.Exception Exception}
896     *      thrown by the MBean's setter.
897     *  @throws ReflectionException  Wraps a
898     *      {@link java.lang.Exception Exception}
899     *      thrown while trying to invoke MBean's setter.
900     *
901     *  @see #getAttribute(String) getAttribute()
902     *  @see javax.management.DynamicMBean#setAttribute(javax.management.Attribute)
903     */
904    @SuppressWarnings( {"MethodWithTooExceptionsDeclared", "OverlyComplexMethod", "ExtractMethodRecommender"} )
905    @Override
906    public final void setAttribute( final Attribute attribute ) throws AttributeNotFoundException, InvalidAttributeValueException, MBeanException, ReflectionException
907    {
908        //---* Get the setter *------------------------------------------------
909        final var method = m_MBeanSetters.get( attribute.getName() );
910        if( method != null )
911        {
912            final var signature = method.getParameterTypes();
913
914            //---* Test if the value can be set to the attribute *-------------
915            var isAssignable = signature [0].isInstance( attribute.getValue() );
916            if( !isAssignable )
917            {
918                isAssignable = signature [0] == Boolean.TYPE && attribute.getValue() instanceof Boolean;
919            }
920            if( !isAssignable )
921            {
922                isAssignable = signature [0] == Byte.TYPE && attribute.getValue() instanceof Byte;
923            }
924            if( !isAssignable )
925            {
926                isAssignable = signature [0] == Character.TYPE && attribute.getValue() instanceof Character;
927            }
928            if( !isAssignable )
929            {
930                isAssignable = signature [0] == Double.TYPE && attribute.getValue() instanceof Double;
931            }
932            if( !isAssignable )
933            {
934                isAssignable = signature [0] == Float.TYPE && attribute.getValue() instanceof Float;
935            }
936            if( !isAssignable )
937            {
938                isAssignable = signature [0] == Integer.TYPE && attribute.getValue() instanceof Integer;
939            }
940            if( !isAssignable )
941            {
942                isAssignable = signature [0] == Long.TYPE && attribute.getValue() instanceof Long;
943            }
944            if( !isAssignable )
945            {
946                isAssignable = signature [0] == Short.TYPE && attribute.getValue() instanceof Short;
947            }
948
949            //---* Set the attribute *-----------------------------------------
950            if( isAssignable )
951            {
952                //noinspection OverlyBroadCatchBlock
953                try
954                {
955                    method.invoke( m_Object, attribute.getValue() );
956                }
957                catch( final InvocationTargetException e )
958                {
959                    throw new ReflectionException( e, format( MSG_SetterProblems, attribute.getName() ) );
960                }
961                catch( final IllegalAccessException e )
962                {
963                    throw new ReflectionException( e, format( MSG_SetterDenied, attribute.getName() ) );
964                }
965                catch( final Exception e )
966                {
967                    throw new MBeanException( e, format( MSG_SetterException, attribute.getName() ) );
968                }
969            }
970            else
971            {
972                throw new InvalidAttributeValueException( format( MSG_InvalidAttributeValue, attribute.getName(), Objects.toString( attribute.getValue() ) ) );
973            }
974        }
975        else
976        {
977            throw new AttributeNotFoundException( format( MSG_AttributeNotFound, attribute.getName() ) );
978        }
979    }   //  setAttribute()
980
981    /**
982     *  Sets the values of several attributes of the generic MBean. This
983     *  implementation will silently swallow any exception that might occur; an
984     *  implementation in a subclass might want to log them.<br>
985     *  <br>An Attribute that causes an exception when
986     *  {@link #setAttribute(Attribute) setAttribute()}
987     *  is called with it will not be part of the returned list of attributes.
988     *
989     *  @param  attributes  A list of attributes: The identification of the
990     *      attributes to be set and  the values they are to be set to.
991     *  @return The list of attributes that were set, with their new values.
992     *
993     *  @see #getAttributes(String[]) getAttributes()
994     *  @see javax.management.DynamicMBean#setAttributes(javax.management.AttributeList)
995     */
996    @Override
997    public AttributeList setAttributes( final AttributeList attributes )
998    {
999        final var retValue = new AttributeList();
1000
1001        //---* Set the values *------------------------------------------------
1002        for( final var o : attributes )
1003        {
1004            final var attribute = (Attribute) o;
1005            try
1006            {
1007                setAttribute( attribute );
1008                retValue.add( attribute );
1009            }
1010            catch( @SuppressWarnings( "unused" ) final AttributeNotFoundException | InvalidAttributeValueException | MBeanException | ReflectionException ignored )
1011            {
1012                continue;
1013            }
1014        }
1015
1016        //---* Done *----------------------------------------------------------
1017        return retValue;
1018    }   //  setAttributes()
1019
1020    /**
1021     *  Unregisters the MBean from the MBeanServer.
1022     *
1023     *  @throws InstanceNotFoundException   The MBean is not registered.
1024     *  @throws MBeanRegistrationException  Problems with the registration of
1025     *      the MBean.
1026     */
1027    @Override
1028    public final void unregister() throws InstanceNotFoundException, MBeanRegistrationException
1029    {
1030        //---* Unregister the MBean *------------------------------------------
1031        final var mbeanServer = getPlatformMBeanServer();
1032        mbeanServer.unregisterMBean( m_ObjectName );
1033
1034        //---* Cleanup *-------------------------------------------------------
1035        m_MBeanActions.clear();
1036        m_MBeanGetters.clear();
1037        m_MBeanSetters.clear();
1038
1039        /*
1040         * Even if the object would expose a method to destroy it, we would not
1041         * be allowed to call it here – obviously.
1042         */
1043    }   //  unregister()
1044}
1045//  class JMXSupportImpl
1046
1047/*
1048 *  End of File
1049 */