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.HashSet;
034import java.util.Iterator;
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 Set<File> extensions = new HashSet<File>(5);
053    private Set<File> activeMQClassPath = new HashSet<File>(5);
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, "optional"));
100                app.addExtensionDirectory(new File(baseLibDir, "web"));
101            }
102            app.addExtensionDirectory(new File(homeLibDir, "optional"));
103            app.addExtensionDirectory(new File(homeLibDir, "web"));
104        }
105
106        // Add any custom classpath specified from the system property
107        // activemq.classpath
108        app.addClassPathList(System.getProperty("activemq.classpath"));
109
110        try {
111            app.runTaskClass(tokens);
112            System.exit(0);
113        } catch (ClassNotFoundException e) {
114            System.out.println("Could not load class: " + e.getMessage());
115            try {
116                ClassLoader cl = app.getClassLoader();
117                if (cl != null) {
118                    System.out.println("Class loader setup: ");
119                    printClassLoaderTree(cl);
120                }
121            } catch (MalformedURLException e1) {
122            }
123            System.exit(1);
124        } catch (Throwable e) {
125            System.out.println("Failed to execute main task. Reason: " + e);
126            System.exit(1);
127        }
128    }
129
130    /**
131     * Print out what's in the classloader tree being used.
132     *
133     * @param cl
134     * @return depth
135     */
136    private static int printClassLoaderTree(ClassLoader cl) {
137        int depth = 0;
138        if (cl.getParent() != null) {
139            depth = printClassLoaderTree(cl.getParent()) + 1;
140        }
141
142        StringBuffer indent = new StringBuffer();
143        for (int i = 0; i < depth; i++) {
144            indent.append("  ");
145        }
146
147        if (cl instanceof URLClassLoader) {
148            URLClassLoader ucl = (URLClassLoader)cl;
149            System.out.println(indent + cl.getClass().getName() + " {");
150            URL[] urls = ucl.getURLs();
151            for (int i = 0; i < urls.length; i++) {
152                System.out.println(indent + "  " + urls[i]);
153            }
154            System.out.println(indent + "}");
155        } else {
156            System.out.println(indent + cl.getClass().getName());
157        }
158        return depth;
159    }
160
161    public void parseExtensions(List<String> tokens) {
162        if (tokens.isEmpty()) {
163            return;
164        }
165
166        int count = tokens.size();
167        int i = 0;
168
169        // Parse for all --extdir and --noDefExt options
170        while (i < count) {
171            String token = tokens.get(i);
172            // If token is an extension dir option
173            if (token.equals("--extdir")) {
174                // Process token
175                count--;
176                tokens.remove(i);
177
178                // If no extension directory is specified, or next token is
179                // another option
180                if (i >= count || tokens.get(i).startsWith("-")) {
181                    System.out.println("Extension directory not specified.");
182                    System.out.println("Ignoring extension directory option.");
183                    continue;
184                }
185
186                // Process extension dir token
187                count--;
188                File extDir = new File(tokens.remove(i));
189
190                if (!canUseExtdir()) {
191                    System.out.println("Extension directory feature not available due to the system classpath being able to load: " + TASK_DEFAULT_CLASS);
192                    System.out.println("Ignoring extension directory option.");
193                    continue;
194                }
195
196                if (!extDir.isDirectory()) {
197                    System.out.println("Extension directory specified is not valid directory: " + extDir);
198                    System.out.println("Ignoring extension directory option.");
199                    continue;
200                }
201
202                addExtensionDirectory(extDir);
203            } else if (token.equals("--noDefExt")) { // If token is
204                // --noDefExt option
205                count--;
206                tokens.remove(i);
207                useDefExt = false;
208            } else {
209                i++;
210            }
211        }
212
213    }
214
215    public void runTaskClass(List<String> tokens) throws Throwable {
216
217        StringBuilder buffer = new StringBuilder();
218        buffer.append(System.getProperty("java.vendor"));
219        buffer.append(" ");
220        buffer.append(System.getProperty("java.version"));
221        buffer.append(" ");
222        buffer.append(System.getProperty("java.home"));
223        System.out.println("Java Runtime: " + buffer.toString());
224
225        buffer = new StringBuilder();
226        buffer.append("current=");
227        buffer.append(Runtime.getRuntime().totalMemory()/1024L);
228        buffer.append("k  free=");
229        buffer.append(Runtime.getRuntime().freeMemory()/1024L);
230        buffer.append("k  max=");
231        buffer.append(Runtime.getRuntime().maxMemory()/1024L);
232        buffer.append("k");
233        System.out.println("  Heap sizes: " + buffer.toString());
234
235        List<?> jvmArgs = ManagementFactory.getRuntimeMXBean().getInputArguments();
236        buffer = new StringBuilder();
237        for (Object arg : jvmArgs) {
238            buffer.append(" ").append(arg);
239        }
240        System.out.println("    JVM args:" + buffer.toString());
241
242        System.out.println("ACTIVEMQ_HOME: " + getActiveMQHome());
243        System.out.println("ACTIVEMQ_BASE: " + getActiveMQBase());
244        System.out.println("ACTIVEMQ_CONF: " + getActiveMQConfig());
245        System.out.println("ACTIVEMQ_DATA: " + getActiveMQDataDir());
246
247        ClassLoader cl = getClassLoader();
248        Thread.currentThread().setContextClassLoader(cl);
249
250        // Use reflection to run the task.
251        try {
252            String[] args = tokens.toArray(new String[tokens.size()]);
253            Class<?> task = cl.loadClass(TASK_DEFAULT_CLASS);
254            Method runTask = task.getMethod("main", new Class[] {
255                String[].class, InputStream.class, PrintStream.class
256            });
257            runTask.invoke(task.newInstance(), new Object[] {
258                args, System.in, System.out
259            });
260        } catch (InvocationTargetException e) {
261            throw e.getCause();
262        }
263    }
264
265    public void addExtensionDirectory(File directory) {
266        extensions.add(directory);
267    }
268
269    public void addClassPathList(String fileList) {
270        if (fileList != null && fileList.length() > 0) {
271            StringTokenizer tokenizer = new StringTokenizer(fileList, ";");
272            while (tokenizer.hasMoreTokens()) {
273                addClassPath(new File(tokenizer.nextToken()));
274            }
275        }
276    }
277
278    public void addClassPath(File classpath) {
279        activeMQClassPath.add(classpath);
280    }
281
282    /**
283     * The extension directory feature will not work if the broker factory is
284     * already in the classpath since we have to load him from a child
285     * ClassLoader we build for it to work correctly.
286     *
287     * @return true, if extension dir can be used. false otherwise.
288     */
289    public boolean canUseExtdir() {
290        try {
291            Main.class.getClassLoader().loadClass(TASK_DEFAULT_CLASS);
292            return false;
293        } catch (ClassNotFoundException e) {
294            return true;
295        }
296    }
297
298    public ClassLoader getClassLoader() throws MalformedURLException {
299        if (classLoader == null) {
300            // Setup the ClassLoader
301            classLoader = Main.class.getClassLoader();
302            if (!extensions.isEmpty() || !activeMQClassPath.isEmpty()) {
303
304                ArrayList<URL> urls = new ArrayList<URL>();
305
306                for (Iterator<File> iter = activeMQClassPath.iterator(); iter.hasNext();) {
307                    File dir = iter.next();
308                    urls.add(dir.toURI().toURL());
309                }
310
311                for (Iterator<File> iter = extensions.iterator(); iter.hasNext();) {
312                    File dir = iter.next();
313                    if (dir.isDirectory()) {
314                        File[] files = dir.listFiles();
315                        if (files != null) {
316
317                            // Sort the jars so that classpath built is consistently in the same
318                            // order. Also allows us to use jar names to control classpath order.
319                            Arrays.sort(files, new Comparator<File>() {
320                                public int compare(File f1, File f2) {
321                                    return f1.getName().compareTo(f2.getName());
322                                }
323                            });
324
325                            for (int j = 0; j < files.length; j++) {
326                                if (files[j].getName().endsWith(".zip") || files[j].getName().endsWith(".jar")) {
327                                    urls.add(files[j].toURI().toURL());
328                                }
329                            }
330                        }
331                    }
332                }
333
334                URL u[] = new URL[urls.size()];
335                urls.toArray(u);
336                classLoader = new URLClassLoader(u, classLoader);
337            }
338            Thread.currentThread().setContextClassLoader(classLoader);
339        }
340        return classLoader;
341    }
342
343    public void setActiveMQHome(File activeMQHome) {
344        this.activeMQHome = activeMQHome;
345    }
346
347    public File getActiveMQHome() {
348        if (activeMQHome == null) {
349            if (System.getProperty("activemq.home") != null) {
350                activeMQHome = new File(System.getProperty("activemq.home"));
351            }
352
353            if (activeMQHome == null) {
354                // guess from the location of the jar
355                URL url = Main.class.getClassLoader().getResource("org/apache/activemq/console/Main.class");
356                if (url != null) {
357                    try {
358                        JarURLConnection jarConnection = (JarURLConnection)url.openConnection();
359                        url = jarConnection.getJarFileURL();
360                        URI baseURI = new URI(url.toString()).resolve("..");
361                        activeMQHome = new File(baseURI).getCanonicalFile();
362                        System.setProperty("activemq.home", activeMQHome.getAbsolutePath());
363                    } catch (Exception ignored) {
364                    }
365                }
366            }
367
368            if (activeMQHome == null) {
369                activeMQHome = new File("../.");
370                System.setProperty("activemq.home", activeMQHome.getAbsolutePath());
371            }
372        }
373
374        return activeMQHome;
375    }
376
377    public File getActiveMQBase() {
378        if (activeMQBase == null) {
379            if (System.getProperty("activemq.base") != null) {
380                activeMQBase = new File(System.getProperty("activemq.base"));
381            }
382
383            if (activeMQBase == null) {
384                activeMQBase = getActiveMQHome();
385                System.setProperty("activemq.base", activeMQBase.getAbsolutePath());
386            }
387        }
388
389        return activeMQBase;
390    }
391
392    public File getActiveMQConfig() {
393        File activeMQConfig = null;
394
395        if (System.getProperty("activemq.conf") != null) {
396            activeMQConfig = new File(System.getProperty("activemq.conf"));
397        } else {
398            activeMQConfig = new File(getActiveMQBase() + "/conf");
399        }
400        return activeMQConfig;
401    }
402
403    public File getActiveMQDataDir() {
404        File activeMQDataDir = null;
405
406        if (System.getProperty("activemq.data") != null) {
407            activeMQDataDir = new File(System.getProperty("activemq.data"));
408        } else {
409            activeMQDataDir = new File(getActiveMQBase() + "/data");
410        }
411        return activeMQDataDir;
412    }
413}