1717package org .apache .catalina .util ;
1818
1919
20+ import java .io .File ;
2021import java .io .InputStream ;
22+ import java .util .ArrayList ;
23+ import java .util .List ;
2124import java .util .Properties ;
25+ import java .util .jar .JarFile ;
26+ import java .util .jar .Manifest ;
2227
2328import org .apache .tomcat .util .ExceptionUtils ;
2429
@@ -121,6 +126,10 @@ public static String getServerNumber() {
121126 }
122127
123128 public static void main (String [] args ) {
129+ // Suppress INFO logging from library initialization
130+ java .util .logging .Logger .getLogger ("org.apache.tomcat.util.net.openssl.panama" ).setLevel (java .util .logging .Level .WARNING );
131+ java .util .logging .Logger .getLogger ("org.apache.catalina.core" ).setLevel (java .util .logging .Level .WARNING );
132+
124133 System .out .println ("Server version: " + getServerInfo ());
125134 System .out .println ("Server built: " + getServerBuilt ());
126135 System .out .println ("Server number: " + getServerNumber ());
@@ -129,6 +138,212 @@ public static void main(String[] args) {
129138 System .out .println ("Architecture: " + System .getProperty ("os.arch" ));
130139 System .out .println ("JVM Version: " + System .getProperty ("java.runtime.version" ));
131140 System .out .println ("JVM Vendor: " + System .getProperty ("java.vm.vendor" ));
141+
142+ // Get CATALINA_HOME for library scanning (already displayed in catalina script output preface)
143+ String catalinaHome = System .getProperty ("catalina.home" );
144+
145+ // Display APR/Tomcat Native information if available
146+ boolean aprLoaded = false ;
147+ try {
148+ // Try to initialize APR by creating an instance and calling isAprAvailable()
149+ // Creating an instance sets the instance flag which allows initialization
150+ Class <?> aprLifecycleListenerClass = Class .forName ("org.apache.catalina.core.AprLifecycleListener" );
151+ aprLifecycleListenerClass .getConstructor ().newInstance ();
152+ Boolean aprAvailable = (Boolean ) aprLifecycleListenerClass .getMethod ("isAprAvailable" ).invoke (null );
153+ if (aprAvailable != null && aprAvailable .booleanValue ()) {
154+ // APR is available, get version information using public methods
155+ String tcnVersion = (String ) aprLifecycleListenerClass .getMethod ("getInstalledTcnVersion" ).invoke (null );
156+ String aprVersion = (String ) aprLifecycleListenerClass .getMethod ("getInstalledAprVersion" ).invoke (null );
157+
158+ System .out .println ("APR loaded: true" );
159+ System .out .println ("APR Version: " + aprVersion );
160+ System .out .println ("Tomcat Native: " + tcnVersion );
161+ aprLoaded = true ;
162+
163+ // Check if installed version is older than recommended
164+ try {
165+ String warning = (String ) aprLifecycleListenerClass .getMethod ("getTcnVersionWarning" ).invoke (null );
166+
167+ if (warning != null ) {
168+ System .out .println (" " + warning );
169+ }
170+ } catch (Exception e ) {
171+ // Failed to check version - ignore
172+ }
173+
174+ // Display OpenSSL version if available
175+ try {
176+ String openSSLVersion = (String ) aprLifecycleListenerClass .getMethod ("getInstalledOpenSslVersion" ).invoke (null );
177+
178+ if (openSSLVersion != null && !openSSLVersion .isEmpty ()) {
179+ System .out .println ("OpenSSL (APR): " + openSSLVersion );
180+ }
181+ } catch (Exception e ) {
182+ // SSL not initialized or not available
183+ }
184+ }
185+ } catch (ClassNotFoundException | NoClassDefFoundError e ) {
186+ // APR/Tomcat Native classes not available on classpath
187+ } catch (Exception e ) {
188+ // Error checking APR status
189+ }
190+
191+ if (!aprLoaded ) {
192+ System .out .println ("APR loaded: false" );
193+ }
194+
195+ // Display FFM OpenSSL information if available
196+ try {
197+ // Try to initialize FFM OpenSSL by creating an instance and calling isAvailable()
198+ // Creating an instance sets the instance flag which allows initialization
199+ Class <?> openSSLLifecycleListenerClass = Class .forName ("org.apache.catalina.core.OpenSSLLifecycleListener" );
200+ openSSLLifecycleListenerClass .getConstructor ().newInstance ();
201+ Boolean ffmAvailable = (Boolean ) openSSLLifecycleListenerClass .getMethod ("isAvailable" ).invoke (null );
202+
203+ if (ffmAvailable != null && ffmAvailable .booleanValue ()) {
204+ // FFM OpenSSL is available, get version information using public method
205+ String versionString = (String ) openSSLLifecycleListenerClass .getMethod ("getInstalledOpenSslVersion" ).invoke (null );
206+
207+ if (versionString != null && !versionString .isEmpty ()) {
208+ System .out .println ("OpenSSL (FFM): " + versionString );
209+ }
210+ }
211+ } catch (ClassNotFoundException | NoClassDefFoundError e ) {
212+ // FFM OpenSSL classes not available on classpath
213+ } catch (Exception e ) {
214+ // Error checking FFM OpenSSL status
215+ }
216+
217+ // Display third-party libraries in CATALINA_HOME/lib
218+ if (catalinaHome != null ) {
219+ File libDir = new File (catalinaHome , "lib" );
220+ if (libDir .exists () && libDir .isDirectory ()) {
221+ File [] allJars = libDir .listFiles ((dir , name ) -> name .endsWith (".jar" ));
222+
223+ if (allJars != null && allJars .length > 0 ) {
224+ // First pass: collect third-party JARs and find longest name
225+ List <File > thirdPartyJars = new ArrayList <>();
226+ int maxNameLength = 0 ;
227+ for (File jar : allJars ) {
228+ if (!isTomcatCoreJar (jar )) {
229+ thirdPartyJars .add (jar );
230+ maxNameLength = Math .max (maxNameLength , jar .getName ().length ());
231+ }
232+ }
233+
234+ // Second pass: print with aligned formatting
235+ if (!thirdPartyJars .isEmpty ()) {
236+ System .out .println ();
237+ System .out .println ("Third-party libraries:" );
238+ for (File jar : thirdPartyJars ) {
239+ String version = getJarVersion (jar );
240+ String jarName = jar .getName ();
241+ // Colon right after name, then pad to align version numbers
242+ String nameWithColon = jarName + ":" ;
243+ String paddedName = String .format ("%-" + (maxNameLength + 1 ) + "s" , nameWithColon );
244+ if (version != null ) {
245+ System .out .println (" " + paddedName + " " + version );
246+ } else {
247+ System .out .println (" " + paddedName + " (unknown)" );
248+ }
249+ }
250+ }
251+ }
252+ }
253+ }
254+ }
255+
256+ private static boolean isTomcatCoreJar (File jarFile ) {
257+ try (JarFile jar = new JarFile (jarFile )) {
258+ Manifest manifest = jar .getManifest ();
259+
260+ if (manifest != null ) {
261+ // Check Bundle-SymbolicName to identify Tomcat core JARs
262+ String bundleName = manifest .getMainAttributes ().getValue ("Bundle-SymbolicName" );
263+ if (bundleName != null ) {
264+ // Tomcat core JARs have Bundle-SymbolicName starting with org.apache.tomcat,
265+ // org.apache.catalina, or jakarta.
266+ if (bundleName .startsWith ("org.apache.tomcat" ) ||
267+ bundleName .startsWith ("org.apache.catalina" ) ||
268+ bundleName .startsWith ("jakarta." )) {
269+ return true ;
270+ }
271+ }
272+
273+ // Fallback: Check Implementation-Vendor and Implementation-Title
274+ String implVendor = manifest .getMainAttributes ().getValue ("Implementation-Vendor" );
275+ String implTitle = manifest .getMainAttributes ().getValue ("Implementation-Title" );
276+
277+ if ("Apache Software Foundation" .equals (implVendor ) && "Apache Tomcat" .equals (implTitle )) {
278+ return true ;
279+ }
280+ }
281+ } catch (Exception e ) {
282+ // Ignore errors reading JAR manifest
283+ }
284+
285+ return false ;
286+ }
287+
288+ private static String getJarVersion (File jarFile ) {
289+ // First try manifest attributes
290+ try (JarFile jar = new JarFile (jarFile )) {
291+ Manifest manifest = jar .getManifest ();
292+
293+ if (manifest != null ) {
294+ // Try different common version attributes
295+ String [] versionAttrs = {"Bundle-Version" , "Implementation-Version" , "Specification-Version" };
296+ for (String attr : versionAttrs ) {
297+ String version = manifest .getMainAttributes ().getValue (attr );
298+ if (version != null ) {
299+ return version ;
300+ }
301+ }
302+ }
303+ } catch (Exception e ) {
304+ // Ignore errors reading JAR manifest
305+ }
306+
307+ // Fallback: try to parse version from filename
308+ return parseVersionFromFilename (jarFile .getName ());
309+ }
310+
311+ /**
312+ * Attempt to extract a version number from a JAR filename.
313+ * Common patterns include:
314+ * - name-version.jar (e.g., commons-logging-1.2.jar)
315+ * - name_version.jar (e.g., library_2.3.4.jar)
316+ * - name-version-SNAPSHOT.jar (e.g., mylib-1.0.0-SNAPSHOT.jar)
317+ *
318+ * @param filename the JAR filename
319+ * @return the extracted version string, or null if no version pattern is found
320+ */
321+ private static String parseVersionFromFilename (String filename ) {
322+ if (filename == null || !filename .endsWith (".jar" )) {
323+ return null ;
324+ }
325+
326+ // Remove .jar extension
327+ String nameWithoutExt = filename .substring (0 , filename .length () - 4 );
328+
329+ // Try to find version pattern by looking for the first separator followed by a digit
330+ // Search from right to left to find the start of the version string
331+ String [] separators = {"-" , "_" };
332+ for (String sep : separators ) {
333+ // Find all occurrences of the separator
334+ int index = nameWithoutExt .indexOf (sep );
335+ while (index >= 0 && index < nameWithoutExt .length () - 1 ) {
336+ String candidate = nameWithoutExt .substring (index + 1 );
337+ // Check if this looks like a version number (starts with digit)
338+ if (!candidate .isEmpty () && Character .isDigit (candidate .charAt (0 ))) {
339+ return candidate ;
340+ }
341+ // Move to next separator
342+ index = nameWithoutExt .indexOf (sep , index + 1 );
343+ }
344+ }
345+
346+ return null ;
132347 }
133348
134349}
0 commit comments