001/* 002 * ============================================================================ 003 * Copyright © 2002-2026 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 * <p>{@summary This class implements the interface 084 * {@link JMXSupport} 085 * that in turn extends the definition for a dynamic MBean 086 * ({@link DynamicMBean}) 087 * and can instrument any object whose class is annotated properly.}</p> 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 1258 2026-06-04 18:33:06Z tquadrat $ 100 * @since 0.0.1 101 * 102 * @UMLGraph.link 103 */ 104@SuppressWarnings( "OverlyComplexClass" ) 105@ClassVersion( sourceVersion = "$Id: JMXSupportImpl.java 1258 2026-06-04 18:33:06Z 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 {@true} if the instrumented object 239 * will send notifications, {@false} (the default) otherwise. 240 * @param threadFactory The thread factory that is used when 241 * notifications will be emitted asynchronously; can be {@null} 242 * and will be ignored if {@code useNotifications} is {@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; 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 @SuppressWarnings( "NewMethodNamingConvention" ) 639 @Override 640 public final long getNextNotificationSequenceNumber() { return m_Sequence.get(); } 641 642 /** 643 * {@inheritDoc} 644 */ 645 @Override 646 public final MBeanNotificationInfo [] getNotificationInfo() 647 { 648 final var retValue = m_BroadcasterSupport.map( NotificationBroadcasterSupport::getNotificationInfo ).orElse( null ); 649 650 //---* Done *---------------------------------------------------------- 651 return retValue; 652 } // getNotificationInfo() 653 654 /** 655 * {@inheritDoc} 656 */ 657 @Override 658 public final T getObject() { return m_Object; } 659 660 /** 661 * {@inheritDoc} 662 */ 663 @Override 664 public final String getObjectDescription() { return m_ObjectDescription; } 665 666 /** 667 * {@inheritDoc} 668 */ 669 @Override 670 public final ObjectName getObjectName() 671 { 672 ObjectName retValue; 673 674 //---* Create the return value *--------------------------------------- 675 try 676 { 677 retValue = new ObjectName( m_ObjectName.getDomain(), m_ObjectName.getKeyPropertyList() ); 678 } 679 catch( final MalformedObjectNameException e ) 680 { 681 throw new ImpossibleExceptionError( e ); 682 } 683 684 //---* Done *---------------------------------------------------------- 685 return retValue; 686 } // getObjectName() 687 688 /** 689 * {@inheritDoc} 690 */ 691 @Override 692 public final Object invoke( final String actionName, final Object [] params, final String [] signature ) throws MBeanException, ReflectionException 693 { 694 Object retValue; 695 696 //---* Get the method for the action *--------------------------------- 697 final var method = m_MBeanActions.get( actionName ); 698 if( nonNull( method ) ) 699 { 700 //noinspection OverlyBroadCatchBlock 701 try 702 { 703 retValue = method.invoke( m_Object, params ); 704 } 705 catch( final InvocationTargetException e ) 706 { 707 final var exception = e.getCause() instanceof Exception cause ? cause : e; 708 throw new ReflectionException( exception, 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 /* 717 * No further specified exception was thrown. At this location, 718 * we are unable to perform further analysis of that exception. 719 */ 720 throw new MBeanException( e, format( MSG_InvocationException, actionName ) ); 721 } 722 } 723 else 724 { 725 throw new MBeanException( new NoSuchMethodException( format( MSG_ActionNotFound, actionName ) ) ); 726 } 727 728 //---* Done *---------------------------------------------------------- 729 return retValue; 730 } // invoke() 731 732 /** 733 * {@inheritDoc} 734 */ 735 @Override 736 public final void removeNotificationListener( final NotificationListener listener ) throws ListenerNotFoundException 737 { 738 if( m_BroadcasterSupport.isPresent() ) 739 { 740 m_BroadcasterSupport.get().removeNotificationListener( listener ); 741 } 742 } // removeNotificationListener() 743 744 /** 745 * {@inheritDoc} 746 */ 747 @Override 748 public final void removeNotificationListener( final NotificationListener listener, final NotificationFilter filter, final Object handback ) throws ListenerNotFoundException 749 { 750 if( m_BroadcasterSupport.isPresent() ) 751 { 752 m_BroadcasterSupport.get().removeNotificationListener( listener, filter, handback ); 753 } 754 } // removeNotificationListener() 755 756 /** 757 * Registers the server with the JMX agent. 758 * 759 * @param <O> The type of the object. 760 * @param object The object to register with the JMX agent. 761 * @param objectName The object name that is used for the object to 762 * register. 763 * @param threadFactory The thread factory that is used when 764 * notifications should be sent asynchronously; can be {@null}. 765 * @return The MBean object that was generated as the instrumentation for 766 * the object to manage. 767 * @throws IllegalArgumentException The object is not annotated with 768 * {@link ManagedObject @ManagedObject}. 769 * @throws InstanceAlreadyExistsException There is already a MBean with 770 * the given object name registered. 771 * @throws MBeanRegistrationException Problems with the registration of 772 * the MBean. 773 */ 774 public static <O> JMXSupport<O> register( final O object, final ObjectName objectName, final ThreadFactory threadFactory ) throws IllegalArgumentException, InstanceAlreadyExistsException, MBeanRegistrationException 775 { 776 final var instrumentation = requireNonNullArgument( object, "object" ) 777 .getClass() 778 .getAnnotation( ManagedObject.class ); 779 if( isNull( instrumentation ) ) throw new IllegalArgumentException( MSG_NoManagedObject ); 780 final var description = instrumentation.description(); 781 final var useNotifications = instrumentation.useNotifications(); 782 783 //---* Create the MBean *---------------------------------------------- 784 final var retValue = new JMXSupportImpl<>( object, requireNonNullArgument( objectName, "objectName" ), description, useNotifications, threadFactory ); 785 786 //---* Register the MBean with the platform MBeanServer *-------------- 787 registerMBean( retValue, objectName ); 788 789 //---* Done *---------------------------------------------------------- 790 return retValue; 791 } // register() 792 793 /** 794 * Register the MBean with the platform MBeanServer. 795 * 796 * @param mbean The MBean to register. 797 * @param objectName The object name for the registration. 798 * @throws InstanceAlreadyExistsException There is already an MBean with 799 * the given object name. 800 * @throws MBeanRegistrationException Problems with the registration of 801 * the MBean. 802 */ 803 private static void registerMBean( final DynamicMBean mbean, final ObjectName objectName ) throws InstanceAlreadyExistsException, MBeanRegistrationException 804 { 805 final var mbeanServer = getPlatformMBeanServer(); 806 try 807 { 808 mbeanServer.registerMBean( mbean, objectName ); 809 } 810 catch( final NotCompliantMBeanException e ) 811 { 812 throw new ImpossibleExceptionError( e ); 813 } 814 } // registerMBean() 815 816 /** 817 * Returns the notification sequence number for the current notification 818 * and increments the number. 819 * 820 * @return The next sequence number. 821 */ 822 @SuppressWarnings( "NewMethodNamingConvention" ) 823 private long retrieveNextNotificationSequenceNumber() { return m_Sequence.getAndIncrement(); } 824 825 /** 826 * Sends an attribute change notification. The type is always 827 * {@code jmx.attribute.change}. 828 * 829 * @param <A> The type for the attribute. 830 * @param <V> The type for the values. 831 * @param message The message for this notification. 832 * @param description The description for the attribute. 833 * @param attributeType The type of the attribute. 834 * @param oldValue The old value. 835 * @param newValue The new value. 836 */ 837 @Override 838 public final <A,V extends A> void sendAttributeChangeNotification( final String message, final String description, final Class<A> attributeType, final V oldValue, final V newValue ) 839 { 840 final Notification notification = new AttributeChangeNotification 841 ( 842 this, 843 retrieveNextNotificationSequenceNumber(), 844 currentTimeMillis(), 845 message, 846 description, 847 attributeType.getName(), 848 oldValue, 849 newValue 850 ); 851 sendNotification( notification ); 852 } // sendAttributeChangeNotification() 853 854 /** 855 * <p>{@summary Sends a notification.} It is not recommended using this 856 * method directly as this class will provide some very helpful 857 * convenience methods.</p> 858 * <p>If it is really necessary to create your own implementation for a 859 * notification, it is important that the source element is set to a 860 * reference to this instance, and not to the object that is instrumented 861 * using this object. 862 * 863 * @param notification The notification to send. 864 * 865 * @see javax.management.NotificationBroadcasterSupport#sendNotification(javax.management.Notification) 866 * @see javax.management.Notification#setSource(Object) 867 * @see javax.management.Notification#getSource() 868 */ 869 private final void sendNotification( final Notification notification ) 870 { 871 if( nonNull( notification ) ) 872 { 873 m_BroadcasterSupport.ifPresent( broadcasterSupport -> broadcasterSupport.sendNotification( notification ) ); 874 } 875 } // sendNotification() 876 877 /** 878 * Sends a simple notification with a message text. 879 * 880 * @param type The type of the notification. 881 * @param message The message for this notification. 882 */ 883 @Override 884 public final void sendNotification( final String type, final String message ) 885 { 886 final var notification = new Notification( type, this, retrieveNextNotificationSequenceNumber(), currentTimeMillis(), message ); 887 sendNotification( notification ); 888 } // sendNotification() 889 890 /** 891 * Sets the value of a specific attribute of the generic MBean. 892 * 893 * @param attribute The identification of the attribute to be set and 894 * the value it is to be set to. 895 * @throws AttributeNotFoundException There is no attribute with the 896 * given name. 897 * @throws InvalidAttributeValueException The value of the attribute does 898 * not fit to the attribute's declaration. 899 * @throws MBeanException Wraps a 900 * {@link java.lang.Exception Exception} 901 * thrown by the MBean's setter. 902 * @throws ReflectionException Wraps a 903 * {@link java.lang.Exception Exception} 904 * thrown while trying to invoke MBean's setter. 905 * 906 * @see #getAttribute(String) getAttribute() 907 * @see javax.management.DynamicMBean#setAttribute(javax.management.Attribute) 908 */ 909 @SuppressWarnings( {"MethodWithTooExceptionsDeclared", "OverlyComplexMethod", "ExtractMethodRecommender"} ) 910 @Override 911 public final void setAttribute( final Attribute attribute ) throws AttributeNotFoundException, InvalidAttributeValueException, MBeanException, ReflectionException 912 { 913 //---* Get the setter *------------------------------------------------ 914 final var method = m_MBeanSetters.get( attribute.getName() ); 915 if( method != null ) 916 { 917 final var signature = method.getParameterTypes(); 918 919 //---* Test if the value can be set to the attribute *------------- 920 var isAssignable = signature [0].isInstance( attribute.getValue() ); 921 if( !isAssignable ) 922 { 923 isAssignable = signature [0] == Boolean.TYPE && attribute.getValue() instanceof Boolean; 924 } 925 if( !isAssignable ) 926 { 927 isAssignable = signature [0] == Byte.TYPE && attribute.getValue() instanceof Byte; 928 } 929 if( !isAssignable ) 930 { 931 isAssignable = signature [0] == Character.TYPE && attribute.getValue() instanceof Character; 932 } 933 if( !isAssignable ) 934 { 935 isAssignable = signature [0] == Double.TYPE && attribute.getValue() instanceof Double; 936 } 937 if( !isAssignable ) 938 { 939 isAssignable = signature [0] == Float.TYPE && attribute.getValue() instanceof Float; 940 } 941 if( !isAssignable ) 942 { 943 isAssignable = signature [0] == Integer.TYPE && attribute.getValue() instanceof Integer; 944 } 945 if( !isAssignable ) 946 { 947 isAssignable = signature [0] == Long.TYPE && attribute.getValue() instanceof Long; 948 } 949 if( !isAssignable ) 950 { 951 isAssignable = signature [0] == Short.TYPE && attribute.getValue() instanceof Short; 952 } 953 954 //---* Set the attribute *----------------------------------------- 955 if( isAssignable ) 956 { 957 //noinspection OverlyBroadCatchBlock 958 try 959 { 960 method.invoke( m_Object, attribute.getValue() ); 961 } 962 catch( final InvocationTargetException e ) 963 { 964 throw new ReflectionException( e, format( MSG_SetterProblems, attribute.getName() ) ); 965 } 966 catch( final IllegalAccessException e ) 967 { 968 throw new ReflectionException( e, format( MSG_SetterDenied, attribute.getName() ) ); 969 } 970 catch( final Exception e ) 971 { 972 throw new MBeanException( e, format( MSG_SetterException, attribute.getName() ) ); 973 } 974 } 975 else 976 { 977 throw new InvalidAttributeValueException( format( MSG_InvalidAttributeValue, attribute.getName(), Objects.toString( attribute.getValue() ) ) ); 978 } 979 } 980 else 981 { 982 throw new AttributeNotFoundException( format( MSG_AttributeNotFound, attribute.getName() ) ); 983 } 984 } // setAttribute() 985 986 /** 987 * Sets the values of several attributes of the generic MBean. This 988 * implementation will silently swallow any exception that might occur; an 989 * implementation in a subclass might want to log them.<br> 990 * <br>An Attribute that causes an exception when 991 * {@link #setAttribute(Attribute) setAttribute()} 992 * is called with it will not be part of the returned list of attributes. 993 * 994 * @param attributes A list of attributes: The identification of the 995 * attributes to be set and the values they are to be set to. 996 * @return The list of attributes that were set, with their new values. 997 * 998 * @see #getAttributes(String[]) getAttributes() 999 * @see javax.management.DynamicMBean#setAttributes(javax.management.AttributeList) 1000 */ 1001 @Override 1002 public AttributeList setAttributes( final AttributeList attributes ) 1003 { 1004 final var retValue = new AttributeList(); 1005 1006 //---* Set the values *------------------------------------------------ 1007 for( final var o : attributes ) 1008 { 1009 final var attribute = (Attribute) o; 1010 try 1011 { 1012 setAttribute( attribute ); 1013 retValue.add( attribute ); 1014 } 1015 catch( @SuppressWarnings( "unused" ) final AttributeNotFoundException | InvalidAttributeValueException | MBeanException | ReflectionException ignored ) 1016 { 1017 continue; 1018 } 1019 } 1020 1021 //---* Done *---------------------------------------------------------- 1022 return retValue; 1023 } // setAttributes() 1024 1025 /** 1026 * Unregisters the MBean from the MBeanServer. 1027 * 1028 * @throws InstanceNotFoundException The MBean is not registered. 1029 * @throws MBeanRegistrationException Problems with the registration of 1030 * the MBean. 1031 */ 1032 @Override 1033 public final void unregister() throws InstanceNotFoundException, MBeanRegistrationException 1034 { 1035 //---* Unregister the MBean *------------------------------------------ 1036 final var mbeanServer = getPlatformMBeanServer(); 1037 mbeanServer.unregisterMBean( m_ObjectName ); 1038 1039 //---* Cleanup *------------------------------------------------------- 1040 m_MBeanActions.clear(); 1041 m_MBeanGetters.clear(); 1042 m_MBeanSetters.clear(); 1043 1044 /* 1045 * Even if the object would expose a method to destroy it, we would not 1046 * be allowed to call it here – obviously. 1047 */ 1048 } // unregister() 1049} 1050// class JMXSupportImpl 1051 1052/* 1053 * End of File 1054 */