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     */
017    package org.apache.activemq.console;
018    
019    import java.io.File;
020    import java.io.InputStream;
021    import java.io.PrintStream;
022    import java.lang.management.ManagementFactory;
023    import java.lang.reflect.InvocationTargetException;
024    import java.lang.reflect.Method;
025    import java.net.JarURLConnection;
026    import java.net.MalformedURLException;
027    import java.net.URI;
028    import java.net.URL;
029    import java.net.URLClassLoader;
030    import java.util.ArrayList;
031    import java.util.Arrays;
032    import java.util.Comparator;
033    import java.util.Iterator;
034    import java.util.LinkedHashSet;
035    import java.util.LinkedList;
036    import java.util.List;
037    import java.util.Set;
038    import 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     */
044    public 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                app.runTaskClass(tokens);
116                System.exit(0);
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 void 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                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            }
403            return activeMQConfig;
404        }
405    
406        public File getActiveMQDataDir() {
407            File activeMQDataDir = null;
408    
409            if (System.getProperty("activemq.data") != null) {
410                activeMQDataDir = new File(System.getProperty("activemq.data"));
411            } else {
412                activeMQDataDir = new File(getActiveMQBase() + "/data");
413            }
414            return activeMQDataDir;
415        }
416    
417        public String getExtensionDirForLogging() {
418            StringBuilder sb = new StringBuilder("[");
419            for (Iterator<File> it = extensions.iterator(); it.hasNext();) {
420                File file = it.next();
421                sb.append(file.getPath());
422                if (it.hasNext()) {
423                    sb.append(",");
424                }
425            }
426            sb.append("]");
427            return sb.toString();
428        }
429    }