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.perflog.client; 019 020import static javax.management.JMX.newMBeanProxy; 021import static org.apiguardian.api.API.Status.INTERNAL; 022import static org.apiguardian.api.API.Status.MAINTAINED; 023import static org.tquadrat.foundation.lang.Objects.nonNull; 024import static org.tquadrat.foundation.lang.Objects.requireNonNullArgument; 025import static org.tquadrat.foundation.perflog.PerfLogUtils.getPerfLogMBeanObjectName; 026import static org.tquadrat.foundation.perflog.PerfLogUtils.obtainMBeanServer; 027import static org.tquadrat.foundation.perflog.remote.PerfLogRemote.NOTIFICATION_Type; 028 029import javax.management.InstanceAlreadyExistsException; 030import javax.management.InstanceNotFoundException; 031import javax.management.ListenerNotFoundException; 032import javax.management.MBeanRegistrationException; 033import javax.management.MBeanServer; 034import javax.management.NotCompliantMBeanException; 035import javax.management.Notification; 036import javax.management.NotificationEmitter; 037import javax.management.NotificationFilter; 038import javax.management.NotificationListener; 039import javax.management.ObjectName; 040import java.lang.ref.Cleaner; 041import java.lang.ref.Cleaner.Cleanable; 042import java.util.Queue; 043import java.util.concurrent.BlockingQueue; 044import java.util.concurrent.LinkedBlockingQueue; 045 046import org.apiguardian.api.API; 047import org.tquadrat.foundation.annotation.ClassVersion; 048import org.tquadrat.foundation.exception.ImpossibleExceptionError; 049import org.tquadrat.foundation.exception.UnexpectedExceptionError; 050import org.tquadrat.foundation.perflog.PerfLogMBean; 051import org.tquadrat.foundation.perflog.internal.PerfLogMBeanImpl; 052 053/** 054 * <p>{@summary The abstract base class for a client for the Foundation 055 * Performance Logging and Monitoring.} Basically, this is a recipient for the 056 * {@link Notification} 057 * messages that are sent each time a 058 * {@linkplain org.tquadrat.foundation.perflog.PerformanceSection performance 059 * section} was left.</p> 060 * 061 * @extauthor Thomas Thrien - thomas.thrien@tquadrat.org 062 * @version $Id: PerfLogClientBase.java 1258 2026-06-04 18:33:06Z tquadrat $ 063 * @since 0.25.0 064 * 065 * @UMLGraph.link 066 */ 067@SuppressWarnings( "AbstractClassWithoutAbstractMethods" ) 068@ClassVersion( sourceVersion = "$Id: PerfLogClientBase.java 1258 2026-06-04 18:33:06Z tquadrat $" ) 069@API( status = MAINTAINED, since = "0.25.0" ) 070public abstract class PerfLogClientBase implements AutoCloseable 071{ 072 /*---------------*\ 073 ====** Inner Classes **==================================================== 074 \*---------------*/ 075 /** 076 * <p>{@summary The janitor that takes care of the housekeeping for an 077 * instance of 078 * {@link PerfLogClientBase} 079 * in case that was not properly closed.}</p> 080 * 081 * @extauthor Thomas Thrien - thomas.thrien@tquadrat.org 082 * @version $Id: PerfLogClientBase.java 1258 2026-06-04 18:33:06Z tquadrat $ 083 * @since 0.25.0 084 * 085 * @param mbean The MBean the listener is connected with. 086 * @param listener The notification listener that needs to be removed. 087 * 088 * @UMLGraph.link 089 */ 090 @SuppressWarnings( "NewClassNamingConvention" ) 091 @ClassVersion( sourceVersion = "$Id: PerfLogClientBase.java 1258 2026-06-04 18:33:06Z tquadrat $" ) 092 @API( status = INTERNAL, since = "0.25.0" ) 093 private record Janitor( NotificationEmitter mbean, PerfLogNotificationListener listener ) implements Runnable 094 { 095 /*---------*\ 096 ====** Methods **====================================================== 097 \*---------*/ 098 /** 099 * {@inheritDoc} 100 */ 101 @Override 102 public final void run() 103 { 104 try 105 { 106 mbean.removeNotificationListener( listener, listener, null ); 107 } 108 catch( final ListenerNotFoundException _ ) 109 { 110 /* 111 * Deliberately ignored! 112 * If the listener cannot be found in the MBean any more, it 113 * must have been removed already. As this is what we want to 114 * achieve with this method, we can ignore the exception here. 115 */ 116 } 117 } // run() 118 } 119 // record Janitor 120 121 /** 122 * <p>{@summary The implementation of 123 * {@link NotificationListener NotificationListener} 124 * that receives the 125 * {@linkplain Notification notifications} 126 * from the 127 * {@link PerfLogMBean MBean}.}</p> 128 * 129 * @param messageQueue The reference to the destination for the messages. 130 * 131 * @version $Id: PerfLogClientBase.java 1258 2026-06-04 18:33:06Z tquadrat $ 132 * @extauthor Thomas Thrien - thomas.thrien@tquadrat.org 133 * @UMLGraph.link 134 * @since 0.25.0 135 */ 136 private record PerfLogNotificationListener( Queue<String> messageQueue ) implements NotificationFilter, NotificationListener 137 { 138 /*--------------*\ 139 ====** Constructors **================================================= 140 \*--------------*/ 141 /** 142 * Creates a new instance of {@code PerfLogNotificationListener}. 143 * 144 * @param messageQueue The reference to the destination for the 145 * messages. 146 */ 147 public PerfLogNotificationListener 148 { 149 requireNonNullArgument( messageQueue, "messageQueue" ); 150 } // PerfLogNotificationListener() 151 152 /*---------*\ 153 ====** Methods **====================================================== 154 \*---------*/ 155 /** 156 * {@inheritDoc} 157 */ 158 @Override 159 public final void handleNotification( final Notification notification, final Object handback ) 160 { 161 messageQueue.offer( notification.getMessage() ); 162 } // handleNotification() 163 164 /** 165 * {@inheritDoc} 166 */ 167 @Override 168 public final boolean isNotificationEnabled( final Notification notification ) 169 { 170 final var retValue = nonNull( notification ) 171 && notification.getType().equals( NOTIFICATION_Type ); 172 173 //---* Done *------------------------------------------------------ 174 return retValue; 175 } // isNotificationEnabled() 176 } 177 // record PerfLogNotificationListener 178 179 /*------------*\ 180 ====** Attributes **======================================================= 181 \*------------*/ 182 /** 183 * The 184 * {@link Cleanable} 185 * for this instance. 186 */ 187 private Cleanable m_Cleanable; 188 189 /** 190 * The caretaker for this instance. 191 */ 192 private Janitor m_Janitor; 193 194 /** 195 * The messages. 196 */ 197 private final BlockingQueue<String> m_Messages = new LinkedBlockingQueue<>(); 198 199 /*------------------------*\ 200 ====** Static Initialisations **=========================================== 201 \*------------------------*/ 202 /** 203 * The cleaner that is used to finalise instances of 204 * {@code PerfLogClientBase}. 205 */ 206 private static final Cleaner m_Cleaner = Cleaner.create(); 207 208 /*--------------*\ 209 ====** Constructors **===================================================== 210 \*--------------*/ 211 /** 212 * Creates a new instance of {@code PerfLogClientBase}. 213 */ 214 protected PerfLogClientBase() 215 { 216 // Just exists. 217 } // PerfLogClientBase() 218 219 /*---------*\ 220 ====** Methods **========================================================== 221 \*---------*/ 222 /** 223 * {@inheritDoc} 224 */ 225 @Override 226 public final void close() 227 { 228 if( nonNull( m_Cleanable ) ) 229 { 230 m_Cleanable.clean(); 231 m_Cleanable = null; 232 m_Janitor = null; 233 } 234 } // close() 235 236 /** 237 * <p>{@summary Establishes the connection to the MBean and starts the 238 * listening.}</p> 239 * 240 * @param forceFlag {@true} if the MBean should be registered in 241 * case it is not yet registered, {@false} otherwise. 242 * @throws IllegalStateException The connection is already established. 243 * @throws InstanceNotFoundException {@code forceFlag} is {@false}, 244 * the 245 * {@link PerfLogMBean} 246 * is not registered, and the notification listener cannot connect to 247 * it. 248 */ 249 public final void connect( final boolean forceFlag ) throws IllegalStateException, InstanceNotFoundException 250 { 251 if( nonNull( m_Cleanable ) ) throw new IllegalStateException( "Connection already established" ); 252 253 final var mbeanServer = obtainMBeanServer(); 254 connect( mbeanServer, forceFlag ); 255 } // connect() 256 257 /** 258 * <p>{@summary Establishes the connection to the MBean and starts the 259 * listening.}</p> 260 * 261 * @param mbeanServer The MBean server that holds the MBean. 262 * @param forceFlag {@true} if the MBean should be registered in 263 * case it is not yet registered, {@false} otherwise. 264 * @throws IllegalStateException The connection is already established. 265 * @throws InstanceNotFoundException {@code forceFlag} is {@false}, 266 * the 267 * {@link PerfLogMBean} 268 * is not registered, and the notification listener cannot connect to 269 * it. 270 */ 271 public final void connect( final MBeanServer mbeanServer, final boolean forceFlag ) throws IllegalStateException, InstanceNotFoundException 272 { 273 if( nonNull( m_Cleanable ) ) throw new IllegalStateException( "Connection already established" ); 274 275 final var objectName = getPerfLogMBeanObjectName(); 276 final var mbean = connectToMBean( requireNonNullArgument( mbeanServer, "mbeanServer" ), objectName, forceFlag ); 277 final var notificationListener = new PerfLogNotificationListener( m_Messages ); 278 m_Janitor = new Janitor( mbean, notificationListener ); 279 m_Cleanable = m_Cleaner.register( this, m_Janitor ); 280 mbean.addNotificationListener( notificationListener, notificationListener, null ); 281 } // connect() 282 283 /** 284 * <p>{@summary Establishes the connection with the 285 * {@link PerfLogMBean } 286 * on the given 287 * {@linkplain MBeanServer MBean server} 288 * and returns a proxy for it.}</p> 289 * <p>If the MBean is not registered an exception is thrown.</p> 290 * 291 * @param mbeanServer The MBean server that is used. 292 * @param objectName The name for the MBean. 293 * @param forceFlag {@true} if the MBean should be registered in 294 * case it is not yet registered, {@false} otherwise. 295 * {@link PerfLogMBean}. 296 * @return The MBean that we connected to. 297 * @throws InstanceNotFoundException {@code forceFlag} is {@false} 298 * and the MBean is not registered in the given MBean server. 299 */ 300 private static final PerfLogMBean connectToMBean( final MBeanServer mbeanServer, final ObjectName objectName, final boolean forceFlag ) throws InstanceNotFoundException 301 { 302 final PerfLogMBean retValue; 303 if( !requireNonNullArgument( mbeanServer, "mbeanServer" ).isRegistered( requireNonNullArgument( objectName, "objectName" ) ) ) 304 { 305 if( !forceFlag ) throw new InstanceNotFoundException( "No MBean registered for ObjectName '%s'".formatted( objectName.toString() ) ); 306 final var mbean = new PerfLogMBeanImpl(); 307 try 308 { 309 mbeanServer.registerMBean( mbean, objectName ); 310 } 311 catch( final InstanceAlreadyExistsException _ ) 312 { 313 /* 314 * Someone else was faster to register the MBean. We ignore the 315 * exception and try to create the proxy. 316 */ 317 } 318 catch( final MBeanRegistrationException e ) 319 { 320 throw new UnexpectedExceptionError( e ); 321 } 322 catch( final NotCompliantMBeanException e ) 323 { 324 throw new ImpossibleExceptionError( e ); 325 } 326 } 327 retValue = newMBeanProxy( mbeanServer, objectName, PerfLogMBean.class ); 328 329 //---* Done *---------------------------------------------------------- 330 return retValue; 331 } // connectToMBean 332 333 /** 334 * Returns a reference to the internal message queue. 335 * 336 * @return The message queue. 337 */ 338 @SuppressWarnings( "AssignmentOrReturnOfFieldWithMutableType" ) 339 protected final BlockingQueue<String> getQueue() { return m_Messages; } 340} 341// class PerfLogClientBase 342 343/* 344 * End of File 345 */