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, File.pathSeparator);
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                                @Override
324                                public int compare(File f1, File f2) {
325                                    return f1.getName().compareTo(f2.getName());
326                                }
327                            });
328
329                            for (int j = 0; j < files.length; j++) {
330                                if (files[j].getName().endsWith(".zip") || files[j].getName().endsWith(".jar")) {
331                                    urls.add(files[j].toURI().toURL());
332                                }
333                            }
334                        }
335                    }
336                }
337
338                URL u[] = new URL[urls.size()];
339                urls.toArray(u);
340                classLoader = new URLClassLoader(u, classLoader);
341            }
342            Thread.currentThread().setContextClassLoader(classLoader);
343        }
344        return classLoader;
345    }
346
347    public void setActiveMQHome(File activeMQHome) {
348        this.activeMQHome = activeMQHome;
349    }
350
351    public File getActiveMQHome() {
352        if (activeMQHome == null) {
353            if (System.getProperty("activemq.home") != null) {
354                activeMQHome = new File(System.getProperty("activemq.home"));
355            }
356
357            if (activeMQHome == null) {
358                // guess from the location of the jar
359                URL url = Main.class.getClassLoader().getResource("org/apache/activemq/console/Main.class");
360                if (url != null) {
361                    try {
362                        JarURLConnection jarConnection = (JarURLConnection)url.openConnection();
363                        url = jarConnection.getJarFileURL();
364                        URI baseURI = new URI(url.toString()).resolve("..");
365                        activeMQHome = new File(baseURI).getCanonicalFile();
366                        System.setProperty("activemq.home", activeMQHome.getAbsolutePath());
367                    } catch (Exception ignored) {
368                    }
369                }
370            }
371
372            if (activeMQHome == null) {
373                activeMQHome = new File("../.");
374                System.setProperty("activemq.home", activeMQHome.getAbsolutePath());
375            }
376        }
377
378        return activeMQHome;
379    }
380
381    public File getActiveMQBase() {
382        if (activeMQBase == null) {
383            if (System.getProperty("activemq.base") != null) {
384                activeMQBase = new File(System.getProperty("activemq.base"));
385            }
386
387            if (activeMQBase == null) {
388                activeMQBase = getActiveMQHome();
389                System.setProperty("activemq.base", activeMQBase.getAbsolutePath());
390            }
391        }
392
393        return activeMQBase;
394    }
395
396    public File getActiveMQConfig() {
397        File activeMQConfig = null;
398
399        if (System.getProperty("activemq.conf") != null) {
400            activeMQConfig = new File(System.getProperty("activemq.conf"));
401        } else {
402            activeMQConfig = new File(getActiveMQBase() + "/conf");
403            System.setProperty("activemq.conf", activeMQConfig.getAbsolutePath());
404        }
405        return activeMQConfig;
406    }
407
408    public File getActiveMQDataDir() {
409        File activeMQDataDir = null;
410
411        if (System.getProperty("activemq.data") != null) {
412            activeMQDataDir = new File(System.getProperty("activemq.data"));
413        } else {
414            activeMQDataDir = new File(getActiveMQBase() + "/data");
415            System.setProperty("activemq.data", activeMQDataDir.getAbsolutePath());
416        }
417        return activeMQDataDir;
418    }
419
420    public String getExtensionDirForLogging() {
421        StringBuilder sb = new StringBuilder("[");
422        for (Iterator<File> it = extensions.iterator(); it.hasNext();) {
423            File file = it.next();
424            sb.append(file.getPath());
425            if (it.hasNext()) {
426                sb.append(",");
427            }
428        }
429        sb.append("]");
430        return sb.toString();
431    }
432}