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