001    /*
002     * Copyright (C) 2010 eXo Platform SAS.
003     *
004     * This is free software; you can redistribute it and/or modify it
005     * under the terms of the GNU Lesser General Public License as
006     * published by the Free Software Foundation; either version 2.1 of
007     * the License, or (at your option) any later version.
008     *
009     * This software is distributed in the hope that it will be useful,
010     * but WITHOUT ANY WARRANTY; without even the implied warranty of
011     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012     * Lesser General Public License for more details.
013     *
014     * You should have received a copy of the GNU Lesser General Public
015     * License along with this software; if not, write to the Free
016     * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
017     * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
018     */
019    
020    package org.crsh.standalone;
021    
022    import com.sun.tools.attach.VirtualMachine;
023    import jline.Terminal;
024    import jline.TerminalFactory;
025    import jline.console.ConsoleReader;
026    import org.crsh.cmdline.ClassDescriptor;
027    import org.crsh.cmdline.CommandFactory;
028    import org.crsh.cmdline.Delimiter;
029    import org.crsh.cmdline.IntrospectionException;
030    import org.crsh.cmdline.annotations.Argument;
031    import org.crsh.cmdline.annotations.Command;
032    import org.crsh.cmdline.annotations.Option;
033    import org.crsh.cmdline.annotations.Usage;
034    import org.crsh.cmdline.matcher.CommandMatch;
035    import org.crsh.cmdline.matcher.InvocationContext;
036    import org.crsh.cmdline.matcher.Matcher;
037    import org.crsh.processor.jline.JLineProcessor;
038    import org.crsh.shell.Shell;
039    import org.crsh.shell.ShellFactory;
040    import org.crsh.shell.impl.remoting.RemoteServer;
041    import org.crsh.util.CloseableList;
042    import org.crsh.util.InterruptHandler;
043    import org.crsh.util.Safe;
044    import org.slf4j.Logger;
045    import org.slf4j.LoggerFactory;
046    
047    import java.io.Closeable;
048    import java.io.File;
049    import java.io.FileDescriptor;
050    import java.io.FileInputStream;
051    import java.io.IOException;
052    import java.io.PrintWriter;
053    import java.net.*;
054    import java.util.List;
055    import java.util.Properties;
056    
057    /**
058     * @author <a href="mailto:julien.viet@exoplatform.com">Julien Viet</a>
059     * @version $Revision$
060     */
061    public class CRaSH {
062    
063      /** . */
064      private static Logger log = LoggerFactory.getLogger(CRaSH.class);
065    
066      /** . */
067      private final ClassDescriptor<CRaSH> descriptor;
068    
069      public CRaSH() throws IntrospectionException {
070        this.descriptor = CommandFactory.create(CRaSH.class);
071      }
072    
073      @Command
074      public void main(
075        @Option(names = {"h","help"})
076        @Usage("display standalone mode help")
077        Boolean help,
078        @Option(names={"j","jar"})
079        @Usage("specify a file system path of a jar added to the class path")
080        List<String> jars,
081        @Option(names={"c","cmd"})
082        @Usage("specify a file system path of a dir added to the command path")
083        List<String> cmds,
084        @Option(names={"conf"})
085        @Usage("specify a file system path of a dir added to the configuration path")
086        List<String> confs,
087        @Option(names={"p","property"})
088        @Usage("specify a configuration property of the form a=b")
089        List<String> properties,
090        @Argument(name = "pid")
091        @Usage("the optional JVM process id to attach to")
092        Integer pid) throws Exception {
093    
094        //
095        if (Boolean.TRUE.equals(help)) {
096          descriptor.printUsage(System.out);
097        } else {
098    
099          CloseableList closeable = new CloseableList();
100          Shell shell;
101          if (pid != null) {
102    
103            // Standalone
104            URL url = CRaSH.class.getProtectionDomain().getCodeSource().getLocation();
105            java.io.File f = new java.io.File(url.toURI());
106            log.info("Attaching to remote process " + pid);
107            final VirtualMachine vm = VirtualMachine.attach("" + pid);
108    
109            //
110            RemoteServer server = new RemoteServer(0);
111            int port = server.bind();
112            log.info("Callback server set on port " + port);
113    
114            // Build the options
115            StringBuilder sb = new StringBuilder();
116    
117            // Rewrite canonical path
118            if (cmds != null) {
119              for (String cmd : cmds) {
120                File cmdPath = new File(cmd);
121                if (cmdPath.exists()) {
122                  sb.append("--cmd ");
123                  Delimiter.EMPTY.escape(cmdPath.getCanonicalPath(), sb);
124                  sb.append(' ');
125                }
126              }
127            }
128    
129            // Rewrite canonical path
130            if (confs != null) {
131              for (String conf : confs) {
132                File confPath = new File(conf);
133                if (confPath.exists()) {
134                  sb.append("--conf ");
135                  Delimiter.EMPTY.escape(confPath.getCanonicalPath(), sb);
136                  sb.append(' ');
137                }
138              }
139            }
140    
141            // Rewrite canonical path
142            if (jars != null) {
143              for (String jar : jars) {
144                File jarPath = new File(jar);
145                if (jarPath.exists()) {
146                  sb.append("--jar ");
147                  Delimiter.EMPTY.escape(jarPath.getCanonicalPath(), sb);
148                  sb.append(' ');
149                }
150              }
151            }
152    
153            // Propagate canonical config
154            if (properties != null) {
155              for (String property : properties) {
156                sb.append("--property ");
157                Delimiter.EMPTY.escape(property, sb);
158                sb.append(' ');
159              }
160            }
161    
162            // Append callback port
163            sb.append(port);
164    
165            //
166            String options = sb.toString();
167            log.info("Loading agent with command " + options);
168            vm.loadAgent(f.getCanonicalPath(), options);
169    
170            //
171            server.accept();
172    
173            //
174            shell = server.getShell();
175            closeable.add(new Closeable() {
176              public void close() throws IOException {
177                vm.detach();
178              }
179            });
180          } else {
181            final Bootstrap bootstrap = new Bootstrap(Thread.currentThread().getContextClassLoader());
182    
183            //
184            if (cmds != null) {
185              for (String cmd : cmds) {
186                File cmdPath = new File(cmd);
187                bootstrap.addCmdPath(cmdPath);
188              }
189            }
190    
191            //
192            if (confs != null) {
193              for (String conf : confs) {
194                File confPath = new File(conf);
195                bootstrap.addConfPath(confPath);
196              }
197            }
198    
199            //
200            if (jars != null) {
201              for (String jar : jars) {
202                File jarPath = new File(jar);
203                bootstrap.addJarPath(jarPath);
204              }
205            }
206    
207            //
208            if (properties != null) {
209              Properties config = new Properties();
210              for (String property : properties) {
211                int index = property.indexOf('=');
212                if (index == -1) {
213                  config.setProperty(property, "");
214                } else {
215                  config.setProperty(property.substring(0, index), property.substring(index + 1));
216                }
217              }
218              bootstrap.setConfig(config);
219            }
220    
221            // Register shutdown hook
222            Runtime.getRuntime().addShutdownHook(new Thread() {
223              @Override
224              public void run() {
225                // Should trigger some kind of run interruption
226              }
227            });
228    
229            // Do bootstrap
230            bootstrap.bootstrap();
231            Runtime.getRuntime().addShutdownHook(new Thread(){
232              @Override
233              public void run() {
234                bootstrap.shutdown();
235              }
236            });
237    
238            //
239            ShellFactory factory = bootstrap.getContext().getPlugin(ShellFactory.class);
240            shell = factory.create(null);
241            closeable = null;
242          }
243    
244          // Start crash for this command line
245          final Terminal term = TerminalFactory.create();
246          term.init();
247          ConsoleReader reader = new ConsoleReader(null, new FileInputStream(FileDescriptor.in), System.out, term);
248          Runtime.getRuntime().addShutdownHook(new Thread(){
249            @Override
250            public void run() {
251              try {
252                term.restore();
253              }
254              catch (Exception ignore) {
255              }
256            }
257          });
258    
259          //
260          final PrintWriter out = new PrintWriter(System.out);
261          final JLineProcessor processor = new JLineProcessor(
262            shell,
263            reader,
264            out
265          );
266          reader.addCompleter(processor);
267    
268          // Install signal handler
269          InterruptHandler ih = new InterruptHandler(new Runnable() {
270            public void run() {
271              processor.cancel();
272            }
273          });
274          ih.install();
275    
276          //
277          try {
278            processor.run();
279          }
280          finally {
281    
282            //
283            if (closeable != null) {
284              Safe.close(closeable);
285            }
286    
287            // Force exit
288            System.exit(0);
289          }
290        }
291      }
292    
293      public static void main(String[] args) throws Exception {
294    
295        StringBuilder line = new StringBuilder();
296        for (int i = 0;i < args.length;i++) {
297          if (i  > 0) {
298            line.append(' ');
299          }
300          Delimiter.EMPTY.escape(args[i], line);
301        }
302    
303        //
304        CRaSH main = new CRaSH();
305        Matcher<CRaSH> matcher = Matcher.createMatcher("main", main.descriptor);
306        CommandMatch<CRaSH, ?, ?> match = matcher.match(line.toString());
307        match.invoke(new InvocationContext(), new CRaSH());
308      }
309    }