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 */