001/** 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 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, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.activemq.console; 018 019import java.io.File; 020import java.io.InputStream; 021import java.io.PrintStream; 022import java.lang.management.ManagementFactory; 023import java.lang.reflect.InvocationTargetException; 024import java.lang.reflect.Method; 025import java.net.JarURLConnection; 026import java.net.MalformedURLException; 027import java.net.URI; 028import java.net.URL; 029import java.net.URLClassLoader; 030import java.util.ArrayList; 031import java.util.Arrays; 032import java.util.Comparator; 033import java.util.Iterator; 034import java.util.LinkedHashSet; 035import java.util.LinkedList; 036import java.util.List; 037import java.util.Set; 038import java.util.StringTokenizer; 039 040/** 041 * Main class that can bootstrap an ActiveMQ broker console. Handles command 042 * line argument parsing to set up and run broker tasks. 043 */ 044public class Main { 045 046 public static final String TASK_DEFAULT_CLASS = "org.apache.activemq.console.command.ShellCommand"; 047 private static boolean useDefExt = true; 048 049 private File activeMQHome; 050 private File activeMQBase; 051 private ClassLoader classLoader; 052 private final Set<File> extensions = new LinkedHashSet<File>(); 053 private final Set<File> activeMQClassPath = new LinkedHashSet<File>(); 054 055 public static void main(String[] args) { 056 057 // Create the tmpdir if it does not exist yet.. 058 File tmpdir = new File(System.getProperty("java.io.tmpdir")); 059 if(!tmpdir.exists()) { 060 tmpdir.mkdirs(); 061 } 062 063 Main app = new Main(); 064 065 // Convert arguments to collection for easier management 066 List<String> tokens = new LinkedList<String>(Arrays.asList(args)); 067 // Parse for extension directory option 068 app.parseExtensions(tokens); 069 070 // lets add the conf directory first, to find the log4j.properties just in case its not 071 // in the activemq.classpath system property or some jar incorrectly includes one 072 File confDir = app.getActiveMQConfig(); 073 app.addClassPath(confDir); 074 075 // Add the following to the classpath: 076 // 077 // ${activemq.base}/conf 078 // ${activemq.base}/lib/* (only if activemq.base != activemq.home) 079 // ${activemq.home}/lib/* 080 // ${activemq.base}/lib/optional/* (only if activemq.base != 081 // activemq.home) 082 // ${activemq.home}/lib/optional/* 083 // ${activemq.base}/lib/web/* (only if activemq.base != activemq.home) 084 // ${activemq.home}/lib/web/* 085 // 086 if (useDefExt && app.canUseExtdir()) { 087 088 boolean baseIsHome = app.getActiveMQBase().equals(app.getActiveMQHome()); 089 090 File baseLibDir = new File(app.getActiveMQBase(), "lib"); 091 File homeLibDir = new File(app.getActiveMQHome(), "lib"); 092 093 if (!baseIsHome) { 094 app.addExtensionDirectory(baseLibDir); 095 } 096 app.addExtensionDirectory(homeLibDir); 097 098 if (!baseIsHome) { 099 app.addExtensionDirectory(new File(baseLibDir, "camel")); 100 app.addExtensionDirectory(new File(baseLibDir, "optional")); 101 app.addExtensionDirectory(new File(baseLibDir, "web")); 102 app.addExtensionDirectory(new File(baseLibDir, "extra")); 103 } 104 app.addExtensionDirectory(new File(homeLibDir, "camel")); 105 app.addExtensionDirectory(new File(homeLibDir, "optional")); 106 app.addExtensionDirectory(new File(homeLibDir, "web")); 107 app.addExtensionDirectory(new File(homeLibDir, "extra")); 108 } 109 110 // Add any custom classpath specified from the system property 111 // activemq.classpath 112 app.addClassPathList(System.getProperty("activemq.classpath")); 113 114 try { 115 int ret = app.runTaskClass(tokens); 116 System.exit(ret); 117 } catch (ClassNotFoundException e) { 118 System.out.println("Could not load class: " + e.getMessage()); 119 try { 120 ClassLoader cl = app.getClassLoader(); 121 if (cl != null) { 122 System.out.println("Class loader setup: "); 123 printClassLoaderTree(cl); 124 } 125 } catch (MalformedURLException e1) { 126 } 127 System.exit(1); 128 } catch (Throwable e) { 129 System.out.println("Failed to execute main task. Reason: " + e); 130 System.exit(1); 131 } 132 } 133 134 /** 135 * Print out what's in the classloader tree being used. 136 * 137 * @param cl 138 * @return depth 139 */ 140 private static int printClassLoaderTree(ClassLoader cl) { 141 int depth = 0; 142 if (cl.getParent() != null) { 143 depth = printClassLoaderTree(cl.getParent()) + 1; 144 } 145 146 StringBuffer indent = new StringBuffer(); 147 for (int i = 0; i < depth; i++) { 148 indent.append(" "); 149 } 150 151 if (cl instanceof URLClassLoader) { 152 URLClassLoader ucl = (URLClassLoader)cl; 153 System.out.println(indent + cl.getClass().getName() + " {"); 154 URL[] urls = ucl.getURLs(); 155 for (int i = 0; i < urls.length; i++) { 156 System.out.println(indent + " " + urls[i]); 157 } 158 System.out.println(indent + "}"); 159 } else { 160 System.out.println(indent + cl.getClass().getName()); 161 } 162 return depth; 163 } 164 165 public void parseExtensions(List<String> tokens) { 166 if (tokens.isEmpty()) { 167 return; 168 } 169 170 int count = tokens.size(); 171 int i = 0; 172 173 // Parse for all --extdir and --noDefExt options 174 while (i < count) { 175 String token = tokens.get(i); 176 // If token is an extension dir option 177 if (token.equals("--extdir")) { 178 // Process token 179 count--; 180 tokens.remove(i); 181 182 // If no extension directory is specified, or next token is 183 // another option 184 if (i >= count || tokens.get(i).startsWith("-")) { 185 System.out.println("Extension directory not specified."); 186 System.out.println("Ignoring extension directory option."); 187 continue; 188 } 189 190 // Process extension dir token 191 count--; 192 File extDir = new File(tokens.remove(i)); 193 194 if (!canUseExtdir()) { 195 System.out.println("Extension directory feature not available due to the system classpath being able to load: " + TASK_DEFAULT_CLASS); 196 System.out.println("Ignoring extension directory option."); 197 continue; 198 } 199 200 if (!extDir.isDirectory()) { 201 System.out.println("Extension directory specified is not valid directory: " + extDir); 202 System.out.println("Ignoring extension directory option."); 203 continue; 204 } 205 206 addExtensionDirectory(extDir); 207 } else if (token.equals("--noDefExt")) { // If token is 208 // --noDefExt option 209 count--; 210 tokens.remove(i); 211 useDefExt = false; 212 } else { 213 i++; 214 } 215 } 216 217 } 218 219 public int runTaskClass(List<String> tokens) throws Throwable { 220 221 StringBuilder buffer = new StringBuilder(); 222 buffer.append(System.getProperty("java.vendor")); 223 buffer.append(" "); 224 buffer.append(System.getProperty("java.version")); 225 buffer.append(" "); 226 buffer.append(System.getProperty("java.home")); 227 System.out.println("Java Runtime: " + buffer.toString()); 228 229 buffer = new StringBuilder(); 230 buffer.append("current="); 231 buffer.append(Runtime.getRuntime().totalMemory()/1024L); 232 buffer.append("k free="); 233 buffer.append(Runtime.getRuntime().freeMemory()/1024L); 234 buffer.append("k max="); 235 buffer.append(Runtime.getRuntime().maxMemory()/1024L); 236 buffer.append("k"); 237 System.out.println(" Heap sizes: " + buffer.toString()); 238 239 List<?> jvmArgs = ManagementFactory.getRuntimeMXBean().getInputArguments(); 240 buffer = new StringBuilder(); 241 for (Object arg : jvmArgs) { 242 buffer.append(" ").append(arg); 243 } 244 System.out.println(" JVM args:" + buffer.toString()); 245 System.out.println("Extensions classpath:\n " + getExtensionDirForLogging()); 246 247 System.out.println("ACTIVEMQ_HOME: " + getActiveMQHome()); 248 System.out.println("ACTIVEMQ_BASE: " + getActiveMQBase()); 249 System.out.println("ACTIVEMQ_CONF: " + getActiveMQConfig()); 250 System.out.println("ACTIVEMQ_DATA: " + getActiveMQDataDir()); 251 252 ClassLoader cl = getClassLoader(); 253 Thread.currentThread().setContextClassLoader(cl); 254 255 // Use reflection to run the task. 256 try { 257 String[] args = tokens.toArray(new String[tokens.size()]); 258 Class<?> task = cl.loadClass(TASK_DEFAULT_CLASS); 259 Method runTask = task.getMethod("main", new Class[] { 260 String[].class, InputStream.class, PrintStream.class 261 }); 262 return (int)runTask.invoke(task.newInstance(), args, System.in, System.out); 263 } catch (InvocationTargetException e) { 264 throw e.getCause(); 265 } 266 } 267 268 public void addExtensionDirectory(File directory) { 269 extensions.add(directory); 270 } 271 272 public void addClassPathList(String fileList) { 273 if (fileList != null && fileList.length() > 0) { 274 StringTokenizer tokenizer = new StringTokenizer(fileList, ";"); 275 while (tokenizer.hasMoreTokens()) { 276 addClassPath(new File(tokenizer.nextToken())); 277 } 278 } 279 } 280 281 public void addClassPath(File classpath) { 282 activeMQClassPath.add(classpath); 283 } 284 285 /** 286 * The extension directory feature will not work if the broker factory is 287 * already in the classpath since we have to load him from a child 288 * ClassLoader we build for it to work correctly. 289 * 290 * @return true, if extension dir can be used. false otherwise. 291 */ 292 public boolean canUseExtdir() { 293 try { 294 Main.class.getClassLoader().loadClass(TASK_DEFAULT_CLASS); 295 return false; 296 } catch (ClassNotFoundException e) { 297 return true; 298 } 299 } 300 301 public ClassLoader getClassLoader() throws MalformedURLException { 302 if (classLoader == null) { 303 // Setup the ClassLoader 304 classLoader = Main.class.getClassLoader(); 305 if (!extensions.isEmpty() || !activeMQClassPath.isEmpty()) { 306 307 ArrayList<URL> urls = new ArrayList<URL>(); 308 309 for (Iterator<File> iter = activeMQClassPath.iterator(); iter.hasNext();) { 310 File dir = iter.next(); 311 urls.add(dir.toURI().toURL()); 312 } 313 314 for (Iterator<File> iter = extensions.iterator(); iter.hasNext();) { 315 File dir = iter.next(); 316 if (dir.isDirectory()) { 317 File[] files = dir.listFiles(); 318 if (files != null) { 319 320 // Sort the jars so that classpath built is consistently in the same 321 // order. Also allows us to use jar names to control classpath order. 322 Arrays.sort(files, new Comparator<File>() { 323 public int compare(File f1, File f2) { 324 return f1.getName().compareTo(f2.getName()); 325 } 326 }); 327 328 for (int j = 0; j < files.length; j++) { 329 if (files[j].getName().endsWith(".zip") || files[j].getName().endsWith(".jar")) { 330 urls.add(files[j].toURI().toURL()); 331 } 332 } 333 } 334 } 335 } 336 337 URL u[] = new URL[urls.size()]; 338 urls.toArray(u); 339 classLoader = new URLClassLoader(u, classLoader); 340 } 341 Thread.currentThread().setContextClassLoader(classLoader); 342 } 343 return classLoader; 344 } 345 346 public void setActiveMQHome(File activeMQHome) { 347 this.activeMQHome = activeMQHome; 348 } 349 350 public File getActiveMQHome() { 351 if (activeMQHome == null) { 352 if (System.getProperty("activemq.home") != null) { 353 activeMQHome = new File(System.getProperty("activemq.home")); 354 } 355 356 if (activeMQHome == null) { 357 // guess from the location of the jar 358 URL url = Main.class.getClassLoader().getResource("org/apache/activemq/console/Main.class"); 359 if (url != null) { 360 try { 361 JarURLConnection jarConnection = (JarURLConnection)url.openConnection(); 362 url = jarConnection.getJarFileURL(); 363 URI baseURI = new URI(url.toString()).resolve(".."); 364 activeMQHome = new File(baseURI).getCanonicalFile(); 365 System.setProperty("activemq.home", activeMQHome.getAbsolutePath()); 366 } catch (Exception ignored) { 367 } 368 } 369 } 370 371 if (activeMQHome == null) { 372 activeMQHome = new File("../."); 373 System.setProperty("activemq.home", activeMQHome.getAbsolutePath()); 374 } 375 } 376 377 return activeMQHome; 378 } 379 380 public File getActiveMQBase() { 381 if (activeMQBase == null) { 382 if (System.getProperty("activemq.base") != null) { 383 activeMQBase = new File(System.getProperty("activemq.base")); 384 } 385 386 if (activeMQBase == null) { 387 activeMQBase = getActiveMQHome(); 388 System.setProperty("activemq.base", activeMQBase.getAbsolutePath()); 389 } 390 } 391 392 return activeMQBase; 393 } 394 395 public File getActiveMQConfig() { 396 File activeMQConfig = null; 397 398 if (System.getProperty("activemq.conf") != null) { 399 activeMQConfig = new File(System.getProperty("activemq.conf")); 400 } else { 401 activeMQConfig = new File(getActiveMQBase() + "/conf"); 402 System.setProperty("activemq.conf", activeMQConfig.getAbsolutePath()); 403 } 404 return activeMQConfig; 405 } 406 407 public File getActiveMQDataDir() { 408 File activeMQDataDir = null; 409 410 if (System.getProperty("activemq.data") != null) { 411 activeMQDataDir = new File(System.getProperty("activemq.data")); 412 } else { 413 activeMQDataDir = new File(getActiveMQBase() + "/data"); 414 System.setProperty("activemq.data", activeMQDataDir.getAbsolutePath()); 415 } 416 return activeMQDataDir; 417 } 418 419 public String getExtensionDirForLogging() { 420 StringBuilder sb = new StringBuilder("["); 421 for (Iterator<File> it = extensions.iterator(); it.hasNext();) { 422 File file = it.next(); 423 sb.append(file.getPath()); 424 if (it.hasNext()) { 425 sb.append(","); 426 } 427 } 428 sb.append("]"); 429 return sb.toString(); 430 } 431}