11/*
2+ * Elemental
3+ * Copyright (C) 2024, Evolved Binary Ltd
4+ *
5+ 6+ * https://www.evolvedbinary.com | https://www.elemental.xyz
7+ *
8+ * This library is free software; you can redistribute it and/or
9+ * modify it under the terms of the GNU Lesser General Public
10+ * License as published by the Free Software Foundation; version 2.1.
11+ *
12+ * This library is distributed in the hope that it will be useful,
13+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
14+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15+ * Lesser General Public License for more details.
16+ *
17+ * You should have received a copy of the GNU Lesser General Public
18+ * License along with this library; if not, write to the Free Software
19+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20+ *
21+ * NOTE: Parts of this file contain code from 'The eXist-db Authors'.
22+ * The original license header is included below.
23+ *
24+ * =====================================================================
25+ *
226 * eXist-db Open Source Native XML Database
327 * Copyright (C) 2001 The eXist-db Authors
428 *
2347
2448import java .io .IOException ;
2549import java .io .StringWriter ;
26- import java .lang .management .ManagementFactory ;
2750
51+ import static com .evolvedbinary .j8fu .tuple .Tuple .Tuple ;
2852import static java .lang .management .ManagementFactory .CLASS_LOADING_MXBEAN_NAME ;
2953import static java .lang .management .ManagementFactory .MEMORY_MXBEAN_NAME ;
3054import static java .lang .management .ManagementFactory .OPERATING_SYSTEM_MXBEAN_NAME ;
3458import java .net .MalformedURLException ;
3559import java .util .*;
3660import java .util .concurrent .*;
61+ import javax .annotation .Nullable ;
3762import javax .management .*;
3863import javax .management .openmbean .CompositeData ;
3964import javax .management .openmbean .CompositeType ;
4570import javax .xml .transform .OutputKeys ;
4671import javax .xml .transform .TransformerException ;
4772
73+ import com .evolvedbinary .j8fu .tuple .Tuple2 ;
4874import org .apache .logging .log4j .LogManager ;
4975import org .apache .logging .log4j .Logger ;
5076import org .exist .dom .QName ;
@@ -137,17 +163,16 @@ private static void putCategory(final String categoryName, final String... objec
137163
138164 public static final int VERSION = 1 ;
139165
140- private final MBeanServerConnection platformConnection = ManagementFactory .getPlatformMBeanServer ();
141- private MBeanServerConnection connection ;
166+ private @ Nullable List <MBeanServerConnection > connections ;
142167 private JMXServiceURL url ;
143168
144169 /**
145170 * Connect to the local JMX instance.
146171 */
147172 public void connect () {
148173 final List <MBeanServer > servers = MBeanServerFactory .findMBeanServer (null );
149- if (servers .size () > 0 ) {
150- this .connection = servers . get ( 0 );
174+ if (! servers .isEmpty () ) {
175+ this .connections = new ArrayList <>( servers );
151176 }
152177 }
153178
@@ -166,7 +191,7 @@ public void connect(final String address, final int port) throws MalformedURLExc
166191 env .put (JMXConnector .CREDENTIALS , creds );
167192
168193 final JMXConnector jmxc = JMXConnectorFactory .connect (url , env );
169- this .connection = jmxc .getMBeanServerConnection ();
194+ this .connections = Collections . singletonList ( jmxc .getMBeanServerConnection () );
170195
171196 if (LOG .isDebugEnabled ()) {
172197 LOG .debug ("Connected to JMX server at {}" , url .toString ());
@@ -204,7 +229,24 @@ public long ping(final String instance, final long timeout) {
204229 final long start = System .currentTimeMillis ();
205230 final ThreadFactory jmxPingFactory = new NamedThreadFactory (instance , "jmx.ping" );
206231 final ExecutorService executorService = Executors .newSingleThreadExecutor (jmxPingFactory );
207- final Future <Long > futurePing = executorService .submit (new Ping (instance , connection ));
232+
233+ // find the connection and MBean name for the ping
234+ final ObjectName name ;
235+ final MBeanServerConnection connection ;
236+ try {
237+ name = SanityReport .getName (instance );
238+ final List <Tuple2 <MBeanServerConnection , Set <ObjectName >>> matches = queryNames (name );
239+ if (matches .isEmpty ()) {
240+ LOG .warn ("Unable to locate MBean connection for ping destination" );
241+ return SanityReport .PING_ERROR ;
242+ }
243+ connection = matches .get (0 )._1 ;
244+ } catch (final MalformedObjectNameException | IOException e ) {
245+ LOG .warn ("Unable to locate MBean connection for ping destination: " + e .getMessage (), e );
246+ return SanityReport .PING_ERROR ;
247+ }
248+
249+ final Future <Long > futurePing = executorService .submit (new Ping (connection , name ));
208250
209251 while (true ) {
210252 try {
@@ -224,18 +266,17 @@ public long ping(final String instance, final long timeout) {
224266 }
225267
226268 private static class Ping implements Callable <Long > {
227- private final String instance ;
228269 private final MBeanServerConnection connection ;
270+ private final ObjectName name ;
229271
230- public Ping (final String instance , final MBeanServerConnection connection ) {
231- this .instance = instance ;
272+ public Ping (final MBeanServerConnection connection , final ObjectName name ) {
232273 this .connection = connection ;
274+ this .name = name ;
233275 }
234276
235277 @ Override
236278 public Long call () {
237279 try {
238- final ObjectName name = SanityReport .getName (instance );
239280 return (Long ) connection .invoke (name , "ping" , new Object []{Boolean .TRUE }, new String []{boolean .class .getName ()});
240281 } catch (final Exception e ) {
241282 LOG .warn (e .getMessage (), e );
@@ -290,23 +331,27 @@ public Element generateXMLReport(final String errcode, final String categories[]
290331
291332 public String getDataDir () {
292333 try {
293- final Object dir = connection .getAttribute (new ObjectName ("org.exist.management.exist:type=DiskUsage" ), "DataDirectory" );
334+ final List <Tuple2 <MBeanServerConnection , Object >> attributeValues = getAttribute (new ObjectName ("org.exist.management.exist:type=DiskUsage" ), "DataDirectory" );
335+ if (attributeValues .isEmpty ()) {
336+ return null ;
337+ }
338+ final Object dir = attributeValues .get (0 )._2 ;
294339 return dir == null ? null : dir .toString ();
295- } catch (final MBeanException | AttributeNotFoundException | InstanceNotFoundException | ReflectionException | IOException | MalformedObjectNameException e ) {
340+ } catch (final MalformedObjectNameException | IOException e ) {
296341 return null ;
297342 }
298343 }
299344
300- public Element invoke (final String objectName , final String operation , String [] args ) throws InstanceNotFoundException , MalformedObjectNameException , MBeanException , IOException , ReflectionException , IntrospectionException {
301- final ObjectName name = new ObjectName (objectName );
302- MBeanServerConnection conn = connection ;
303- MBeanInfo info ;
304- try {
305- info = conn .getMBeanInfo (name );
306- } catch (InstanceNotFoundException e ) {
307- conn = platformConnection ;
308- info = conn .getMBeanInfo (name );
345+ public Element invoke (final String name , final String operation , final String [] args ) throws InstanceNotFoundException , MalformedObjectNameException , MBeanException , IOException , ReflectionException , IntrospectionException {
346+ final ObjectName objectName = new ObjectName (name );
347+ final List <Tuple2 <MBeanServerConnection , MBeanInfo >> mbeanInfos = getMBeanInfo (objectName );
348+ if (mbeanInfos .isEmpty ()) {
349+ return null ;
309350 }
351+
352+ final MBeanServerConnection conn = mbeanInfos .get (0 )._1 ;
353+ final MBeanInfo info = mbeanInfos .get (0 )._2 ;
354+
310355 final MBeanOperationInfo [] operations = info .getOperations ();
311356 for (final MBeanOperationInfo op : operations ) {
312357 if (operation .equals (op .getName ())) {
@@ -318,7 +363,7 @@ public Element invoke(final String objectName, final String operation, String[]
318363 types [i ] = type ;
319364 params [i ] = mapParameter (type , args [i ]);
320365 }
321- final Object result = conn .invoke (name , operation , params , types );
366+ final Object result = conn .invoke (objectName , operation , params , types );
322367
323368 final MemTreeBuilder builder = new MemTreeBuilder ((Expression ) null );
324369
@@ -350,46 +395,83 @@ public Element invoke(final String objectName, final String operation, String[]
350395 return null ;
351396 }
352397
353- private void queryMBeans (final MemTreeBuilder builder , final ObjectName query )
354- throws IOException , InstanceNotFoundException , IntrospectionException , ReflectionException , NullPointerException {
398+ private List <Tuple2 <MBeanServerConnection , Set <ObjectName >>> queryNames (final ObjectName nameQuery ) throws IOException {
399+ final List <Tuple2 <MBeanServerConnection , Set <ObjectName >>> matchedNames = new ArrayList <>();
400+ if (connections != null ) {
401+ for (final MBeanServerConnection connection : connections ) {
402+ final Set <ObjectName > matches = connection .queryNames (nameQuery , null );
403+ if (!matches .isEmpty ()) {
404+ matchedNames .add (Tuple (connection , matches ));
405+ }
406+ }
407+ }
408+ return matchedNames ;
409+ }
355410
356- MBeanServerConnection conn = connection ;
357- Set <ObjectName > beans = conn .queryNames (query , null );
358-
359- //if the query is not found in the eXist specific MBeans server, then attempt to query the platform for it
360- if (beans .isEmpty ()) {
361- beans = platformConnection .queryNames (query , null );
362- conn = platformConnection ;
363- } //TODO examine JUnit source code as alternative method
364-
365- for (final ObjectName name : beans ) {
366- final MBeanInfo info = conn .getMBeanInfo (name );
367- String className = info .getClassName ().replace ('$' , '.' );
368- final int p = className .lastIndexOf ('.' );
369- if (p > -1 && p + 1 < className .length ()) {
370- className = className .substring (p + 1 );
411+ private List <Tuple2 <MBeanServerConnection , Object >> getAttribute (final ObjectName objectName , final String attributeName ) throws IOException {
412+ final List <Tuple2 <MBeanServerConnection , Object >> attributeValues = new ArrayList <>();
413+ if (connections != null ) {
414+ for (final MBeanServerConnection connection : connections ) {
415+ try {
416+ attributeValues .add (Tuple (connection , connection .getAttribute (objectName , attributeName )));
417+ } catch (final AttributeNotFoundException | MBeanException | InstanceNotFoundException | ReflectionException e ) {
418+ // no-op
419+ }
371420 }
421+ }
422+ return attributeValues ;
423+ }
372424
373- final QName qname = new QName (className , JMX_NAMESPACE , JMX_PREFIX );
374- builder .startElement (qname , null );
375- builder .addAttribute (new QName ("name" , XMLConstants .NULL_NS_URI ), name .toString ());
376-
377- final MBeanAttributeInfo [] beanAttribs = info .getAttributes ();
378- for (MBeanAttributeInfo beanAttrib : beanAttribs ) {
379- if (beanAttrib .isReadable ()) {
380- try {
381- final QName attrQName = new QName (beanAttrib .getName (), JMX_NAMESPACE , JMX_PREFIX );
382- final Object attrib = conn .getAttribute (name , beanAttrib .getName ());
383-
384- builder .startElement (attrQName , null );
385- serializeObject (builder , attrib );
386- builder .endElement ();
387- } catch (final Exception e ) {
388- LOG .debug ("exception caught: {}" , e .getMessage (), e );
425+ private List <Tuple2 <MBeanServerConnection , MBeanInfo >> getMBeanInfo (final ObjectName objectName ) throws IOException {
426+ final List <Tuple2 <MBeanServerConnection , MBeanInfo >> mbeanInfos = new ArrayList <>();
427+ if (connections != null ) {
428+ for (final MBeanServerConnection connection : connections ) {
429+ try {
430+ mbeanInfos .add (Tuple (connection , connection .getMBeanInfo (objectName )));
431+ } catch (final IntrospectionException | InstanceNotFoundException | ReflectionException e ) {
432+ // no-op
433+ }
434+ }
435+ }
436+ return mbeanInfos ;
437+ }
438+
439+ private void queryMBeans (final MemTreeBuilder builder , final ObjectName nameQuery )
440+ throws IOException , InstanceNotFoundException , IntrospectionException , ReflectionException , NullPointerException {
441+
442+ final List <Tuple2 <MBeanServerConnection , Set <ObjectName >>> matchedNames = queryNames (nameQuery );
443+
444+ for (final Tuple2 <MBeanServerConnection , Set <ObjectName >> matchedName : matchedNames ) {
445+ final MBeanServerConnection connection = matchedName ._1 ;
446+ for (final ObjectName name : matchedName ._2 ) {
447+ final MBeanInfo info = connection .getMBeanInfo (name );
448+ String className = info .getClassName ().replace ('$' , '.' );
449+ final int p = className .lastIndexOf ('.' );
450+ if (p > -1 && p + 1 < className .length ()) {
451+ className = className .substring (p + 1 );
452+ }
453+
454+ final QName qname = new QName (className , JMX_NAMESPACE , JMX_PREFIX );
455+ builder .startElement (qname , null );
456+ builder .addAttribute (new QName ("name" , XMLConstants .NULL_NS_URI ), name .toString ());
457+
458+ final MBeanAttributeInfo [] beanAttribs = info .getAttributes ();
459+ for (MBeanAttributeInfo beanAttrib : beanAttribs ) {
460+ if (beanAttrib .isReadable ()) {
461+ try {
462+ final QName attrQName = new QName (beanAttrib .getName (), JMX_NAMESPACE , JMX_PREFIX );
463+ final Object attrib = connection .getAttribute (name , beanAttrib .getName ());
464+
465+ builder .startElement (attrQName , null );
466+ serializeObject (builder , attrib );
467+ builder .endElement ();
468+ } catch (final Exception e ) {
469+ LOG .debug ("exception caught: {}" , e .getMessage (), e );
470+ }
389471 }
390472 }
473+ builder .endElement ();
391474 }
392- builder .endElement ();
393475 }
394476 }
395477
0 commit comments