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