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