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.remote.internal; 019 020import static org.apiguardian.api.API.Status.INTERNAL; 021import static org.tquadrat.foundation.lang.Objects.requireNonNullArgument; 022import static org.tquadrat.foundation.lang.Objects.requireNotBlankArgument; 023 024import javax.management.AttributeNotFoundException; 025import javax.management.InstanceNotFoundException; 026import javax.management.IntrospectionException; 027import javax.management.ListenerNotFoundException; 028import javax.management.MBeanException; 029import javax.management.MBeanInfo; 030import javax.management.MBeanServerConnection; 031import javax.management.MalformedObjectNameException; 032import javax.management.NotificationListener; 033import javax.management.ObjectName; 034import javax.management.ReflectionException; 035import javax.management.remote.JMXConnector; 036import javax.management.remote.JMXConnectorFactory; 037import javax.management.remote.JMXServiceURL; 038import java.io.IOException; 039import java.lang.ref.Cleaner; 040import java.lang.ref.Cleaner.Cleanable; 041import java.util.Hashtable; 042 043import org.apiguardian.api.API; 044import org.tquadrat.foundation.annotation.ClassVersion; 045import org.tquadrat.foundation.perflog.remote.PerfLogRemote; 046 047/** 048 * <p>{@summary The implementation of 049 * {@link PerfLogRemote }.}</p> 050 * 051 * @extauthor Thomas Thrien - thomas.thrien@tquadrat.org 052 * @version $Id: PerfLogRemoteImpl.java 1229 2026-05-04 19:11:41Z tquadrat $ 053 * @since 0.25.0 054 * 055 * @UMLGraph.link 056 */ 057@ClassVersion( sourceVersion = "$Id: PerfLogRemoteImpl.java 1229 2026-05-04 19:11:41Z tquadrat $" ) 058@API( status = INTERNAL, since = "0.25.0" ) 059public final class PerfLogRemoteImpl implements PerfLogRemote 060{ 061 /*---------------*\ 062 ====** Inner Classes **==================================================== 063 \*---------------*/ 064 /** 065 * <p>{@summary The janitor that takes care of the housekeeping for an 066 * instance of 067 * {@link PerfLogRemote} 068 * in case that was not properly closed.}</p> 069 * 070 * @extauthor Thomas Thrien - thomas.thrien@tquadrat.org 071 * @version $Id: PerfLogRemoteImpl.java 1229 2026-05-04 19:11:41Z tquadrat $ 072 * @since 0.25.0 073 * 074 * @param objectName The object name that is used to connect to the 075 * Performance Logging MBean. 076 * @param connector The JMX connector. 077 * @param connection The connection to the performance logging MBean. 078 * @param url The service URL for the connection to the MBean server. 079 * @param listener The notification listener that receives the 080 * performance messages. 081 * 082 * @UMLGraph.link 083 */ 084 @ClassVersion( sourceVersion = "$Id: PerfLogRemoteImpl.java 1229 2026-05-04 19:11:41Z tquadrat $" ) 085 @API( status = INTERNAL, since = "0.25.0" ) 086 private record Janitor( ObjectName objectName, JMXServiceURL url, JMXConnector connector, MBeanServerConnection connection, NotificationListener listener ) implements Runnable 087 { 088 /*---------*\ 089 ====** Methods **====================================================== 090 \*---------*/ 091 /** 092 * {@inheritDoc} 093 */ 094 @Override 095 public final void run() 096 { 097 try 098 { 099 connection.removeNotificationListener( objectName, listener ); 100 connector.close(); 101 } 102 catch( final InstanceNotFoundException | ListenerNotFoundException | IOException _ ) 103 { 104 /* 105 * Deliberately ignored! 106 * The instance will be garbage collected anyway. 107 */ 108 } 109 } // run() 110 } 111 // record Janitor 112 113 /*-----------*\ 114 ====** Constants **======================================================== 115 \*-----------*/ 116 117 /*------------*\ 118 ====** Attributes **======================================================= 119 \*------------*/ 120 /** 121 * The 122 * {@link Cleanable} 123 * for this instance. 124 */ 125 private final Cleanable m_Cleanable; 126 127 /** 128 * The connection to the MBean. 129 */ 130 private final MBeanServerConnection m_Connection; 131 132 /** 133 * The flag the indicates whether this remote is (still) active. 134 */ 135 private boolean m_IsActive; 136 137 /** 138 * The caretaker for this instance. 139 */ 140 @SuppressWarnings( "FieldCanBeLocal" ) 141 private final Janitor m_Janitor; 142 143 /*------------------------*\ 144 ====** Static Initialisations **=========================================== 145 \*------------------------*/ 146 /** 147 * The cleaner that is used to finalise instances of 148 * {@code PerfLogRemoteImpl}. 149 */ 150 private static final Cleaner m_Cleaner = Cleaner.create(); 151 152 /** 153 * The object name for the Performance Logging MBean. 154 */ 155 private static final ObjectName m_ObjectName; 156 157 static 158 { 159 try 160 { 161 final var attributes = new Hashtable<String,String>(); 162 attributes.put( "type", MBEAN_TYPE ); 163 m_ObjectName = ObjectName.getInstance( DOMAIN_NAME, attributes ); 164 } 165 catch( final MalformedObjectNameException e ) 166 { 167 throw new ExceptionInInitializerError( e ); 168 } 169 } 170 171 /*--------------*\ 172 ====** Constructors **===================================================== 173 \*--------------*/ 174 /** 175 * Creates a new instance of {@code PerfLogRemoteImpl}. 176 * 177 * @param url The service URL for the connection to the MBean server. 178 * @param listener The notification listener that receives the 179 * performance messages. 180 * @throws IOException Unable to connect to the MBean server. 181 * @throws InstanceNotFoundException There is no performance logging 182 * MBean registered on the MBean server. 183 */ 184 public PerfLogRemoteImpl( final JMXServiceURL url, final NotificationListener listener ) throws InstanceNotFoundException, IOException 185 { 186 requireNonNullArgument( listener, "listener" ); 187 188 final var connector = JMXConnectorFactory.connect( requireNonNullArgument( url, "url" ) ); 189 m_Connection = connector.getMBeanServerConnection(); 190 191 final var objectName = getPerfLogMBeanObjectName(); 192 m_Connection.addNotificationListener( objectName, listener, null, null ); 193 194 //---* Register the housekeeping *------------------------------------- 195 m_Janitor = new Janitor( objectName, url, connector, m_Connection, listener ); 196 //noinspection ThisEscapedInObjectConstruction 197 m_Cleanable = m_Cleaner.register( this, m_Janitor ); 198 199 m_IsActive = true; 200 } // PerfLogRemoteImpl() 201 202 /*---------*\ 203 ====** Methods **========================================================== 204 \*---------*/ 205 /** 206 * <p>{@summary Checks whether this performance logging remote is still 207 * active.} Throws an 208 * {@link IllegalStateException} 209 * if not. 210 * 211 * @return {@code true} if the instance is still active. 212 * @throws IllegalStateException 213 * {@link #close()} 214 * was already called on this instance. 215 */ 216 @SuppressWarnings( "UnusedReturnValue" ) 217 private final boolean checkActive() throws IllegalStateException 218 { 219 if( !m_IsActive ) throw new IllegalStateException( "PerfLogRemote was already terminated" ); 220 221 //---* Done *---------------------------------------------------------- 222 //noinspection ConstantValue 223 return m_IsActive; 224 } // checkActive() 225 226 /** 227 * {@inheritDoc} 228 */ 229 @Override 230 public final void close() 231 { 232 if( m_IsActive ) 233 { 234 m_Cleanable.clean(); 235 m_IsActive = false; 236 } 237 } // close() 238 239 /** 240 * <p>{@summary Connects to the Performance Logging MBean specified 241 * through the given 242 * {@link JMXServiceURL} 243 * and the 244 * {@link ObjectName} 245 * returned by 246 * {@link #getPerfLogMBeanObjectName()}.}</p> 247 * 248 * @param url The JMX service URL. 249 * @param listener The notification listener. 250 * @return A new instance of {@code PerFlogRemote}. 251 * @throws IOException Unable to connect to the MBean server. 252 * @throws InstanceNotFoundException There is no performance logging 253 * MBean registered on the MBean server. 254 */ 255 public static final PerfLogRemoteImpl connect( final JMXServiceURL url, final NotificationListener listener ) throws InstanceNotFoundException, IOException 256 { 257 final var retValue = new PerfLogRemoteImpl( url, listener ); 258 259 //---* Done *---------------------------------------------------------- 260 return retValue; 261 } // connect() 262 263 /** 264 * {@inheritDoc} 265 */ 266 @Override 267 public final MBeanInfo getMBeanInfo() throws IllegalStateException, ReflectionException, InstanceNotFoundException, IntrospectionException, IOException 268 { 269 checkActive(); 270 271 final var retValue = m_Connection.getMBeanInfo( m_ObjectName ); 272 273 //---* Done *---------------------------------------------------------- 274 return retValue; 275 } // getMBeanInfo() 276 277 /** 278 * Returns the 279 * {@link ObjectName} 280 * for the Performance Logging MBean. 281 * 282 * @return The object name for the MBean. 283 */ 284 public static final ObjectName getPerfLogMBeanObjectName() { return m_ObjectName; } 285 286 /** 287 * {@inheritDoc} 288 */ 289 @Override 290 public String getPerformanceSection( final String name ) throws IllegalStateException, ReflectionException, InstanceNotFoundException, MBeanException, IOException 291 { 292 checkActive(); 293 294 final var retValue = (String) m_Connection.invoke( m_ObjectName, "showPerformanceSection", new Object[] {requireNotBlankArgument( name, "name" )}, new String[] { String.class.getName() } ); 295 296 //---* Done *---------------------------------------------------------- 297 return retValue; 298 } // getPerformanceSection() 299 300 /** 301 * {@inheritDoc} 302 */ 303 @SuppressWarnings( "MethodWithTooExceptionsDeclared" ) 304 @Override 305 public final String[] getPerformanceSections() throws IllegalStateException, ReflectionException, AttributeNotFoundException, InstanceNotFoundException, MBeanException, IOException 306 { 307 checkActive(); 308 309 final var retValue = (String[]) m_Connection.getAttribute( m_ObjectName, "PerformanceSections" ); 310 311 //---* Done *---------------------------------------------------------- 312 return retValue; 313 } 314} 315// class PerfLogRemoteImpl 316 317/* 318 * End of File 319 */