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.impl.remoting.RemoteServer;
040    import org.crsh.util.CloseableList;
041    import org.crsh.util.InterruptHandler;
042    import org.crsh.util.Safe;
043    import org.slf4j.Logger;
044    import org.slf4j.LoggerFactory;
045    
046    import java.io.Closeable;
047    import java.io.File;
048    import java.io.FileDescriptor;
049    import java.io.FileInputStream;
050    import java.io.IOException;
051    import java.io.PrintWriter;
052    import java.net.*;
053    import java.util.List;
054    import java.util.Properties;
055    
056    /**
057     * @author <a href="mailto:julien.viet@exoplatform.com">Julien Viet</a>
058     * @version $Revision$
059     */
060    public class CRaSH {
061    
062      /** . */
063      private static Logger log = LoggerFactory.getLogger(CRaSH.class);
064    
065      /** . */
066      private final ClassDescriptor<CRaSH> descriptor;
067    
068      public CRaSH() throws IntrospectionException {
069        this.descriptor = CommandFactory.create(CRaSH.class);
070      }
071    
072      @Command
073      public void main(
074        @Option(names = {"h","help"})
075        @Usage("display standalone mode help")
076        Boolean help,
077        @Option(names={"j","jar"})
078        @Usage("specify a file system path of a jar added to the class path")
079        List<String> jars,
080        @Option(names={"c","cmd"})
081        @Usage("specify a file system path of a dir added to the command path")
082        List<String> cmds,
083        @Option(names={"conf"})
084        @Usage("specify a file system path of a dir added to the configuration path")
085        List<String> confs,
086        @Option(names={"p","property"})
087        @Usage("specify a configuration property of the form a=b")
088        List<String> properties,
089        @Argument(name = "pid")
090        @Usage("the optional JVM process id to attach to")
091        Integer pid) throws Exception {
092    
093        //
094        if (Boolean.TRUE.equals(help)) {
095          descriptor.printUsage(System.out);
096        } else {
097    
098          CloseableList closeable = new CloseableList();
099          Shell shell;
100          if (pid != null) {
101    
102            // Standalone
103            URL url = CRaSH.class.getProtectionDomain().getCodeSource().getLocation();
104            java.io.File f = new java.io.File(url.toURI());
105            log.info("Attaching to remote process " + pid);
106            final VirtualMachine vm = VirtualMachine.attach("" + pid);
107    
108            //
109            RemoteServer server = new RemoteServer(0);
110            int port = server.bind();
111            log.info("Callback server set on port " + port);
112    
113            // Build the options
114            StringBuilder sb = new StringBuilder();
115    
116            // Rewrite canonical path
117            if (cmds != null) {
118              for (String cmd : cmds) {
119                File cmdPath = new File(cmd);
120                if (cmdPath.exists()) {
121                  sb.append("--cmd ");
122                  Delimiter.EMPTY.escape(cmdPath.getCanonicalPath(), sb);
123                  sb.append(' ');
124                }
125              }
126            }
127    
128            // Rewrite canonical path
129            if (confs != null) {
130              for (String conf : confs) {
131                File confPath = new File(conf);
132                if (confPath.exists()) {
133                  sb.append("--conf ");
134                  Delimiter.EMPTY.escape(confPath.getCanonicalPath(), sb);
135                  sb.append(' ');
136                }
137              }
138            }
139    
140            // Rewrite canonical path
141            if (jars != null) {
142              for (String jar : jars) {
143                File jarPath = new File(jar);
144                if (jarPath.exists()) {
145                  sb.append("--jar ");
146                  Delimiter.EMPTY.escape(jarPath.getCanonicalPath(), sb);
147                  sb.append(' ');
148                }
149              }
150            }
151    
152            // Propagate canonical config
153            if (properties != null) {
154              for (String property : properties) {
155                sb.append("--property ");
156                Delimiter.EMPTY.escape(property, sb);
157                sb.append(' ');
158              }
159            }
160    
161            // Append callback port
162            sb.append(port);
163    
164            //
165            String options = sb.toString();
166            log.info("Loading agent with command " + options);
167            vm.loadAgent(f.getCanonicalPath(), options);
168    
169            //
170            server.accept();
171    
172            //
173            shell = server.getShell();
174            closeable.add(new Closeable() {
175              public void close() throws IOException {
176                vm.detach();
177              }
178            });
179          } else {
180            final Bootstrap bootstrap = new Bootstrap(Thread.currentThread().getContextClassLoader());
181    
182            //
183            if (cmds != null) {
184              for (String cmd : cmds) {
185                File cmdPath = new File(cmd);
186                bootstrap.addCmdPath(cmdPath);
187              }
188            }
189    
190            //
191            if (confs != null) {
192              for (String conf : confs) {
193                File confPath = new File(conf);
194                bootstrap.addConfPath(confPath);
195              }
196            }
197    
198            //
199            if (jars != null) {
200              for (String jar : jars) {
201                File jarPath = new File(jar);
202                bootstrap.addJarPath(jarPath);
203              }
204            }
205    
206            //
207            if (properties != null) {
208              Properties config = new Properties();
209              for (String property : properties) {
210                int index = property.indexOf('=');
211                if (index == -1) {
212                  config.setProperty(property, "");
213                } else {
214                  config.setProperty(property.substring(0, index), property.substring(index + 1));
215                }
216              }
217              bootstrap.setConfig(config);
218            }
219    
220            // Register shutdown hook
221            Runtime.getRuntime().addShutdownHook(new Thread() {
222              @Override
223              public void run() {
224                // Should trigger some kind of run interruption
225              }
226            });
227    
228            // Do bootstrap
229            bootstrap.bootstrap();
230            Runtime.getRuntime().addShutdownHook(new Thread(){
231              @Override
232              public void run() {
233                bootstrap.shutdown();
234              }
235            });
236    
237            //
238            org.crsh.shell.impl.command.CRaSH crash = new org.crsh.shell.impl.command.CRaSH(bootstrap.getContext());
239            shell = crash.createSession(null);
240            closeable = null;
241          }
242    
243          // Start crash for this command line
244          final Terminal term = TerminalFactory.create();
245          term.init();
246          ConsoleReader reader = new ConsoleReader(null, new FileInputStream(FileDescriptor.in), System.out, term);
247          Runtime.getRuntime().addShutdownHook(new Thread(){
248            @Override
249            public void run() {
250              try {
251                term.restore();
252              }
253              catch (Exception ignore) {
254              }
255            }
256          });
257    
258          //
259          final PrintWriter out = new PrintWriter(System.out);
260          final JLineProcessor processor = new JLineProcessor(
261            shell,
262            reader,
263            out
264          );
265          reader.addCompleter(processor);
266    
267          // Install signal handler
268          InterruptHandler ih = new InterruptHandler(new Runnable() {
269            public void run() {
270              processor.cancel();
271            }
272          });
273          ih.install();
274    
275          //
276          try {
277            processor.run();
278          }
279          finally {
280    
281            //
282            if (closeable != null) {
283              Safe.close(closeable);
284            }
285    
286            // Force exit
287            System.exit(0);
288          }
289        }
290      }
291    
292      public static void main(String[] args) throws Exception {
293    
294        StringBuilder line = new StringBuilder();
295        for (int i = 0;i < args.length;i++) {
296          if (i  > 0) {
297            line.append(' ');
298          }
299          Delimiter.EMPTY.escape(args[i], line);
300        }
301    
302        //
303        CRaSH main = new CRaSH();
304        Matcher<CRaSH> matcher = Matcher.createMatcher("main", main.descriptor);
305        CommandMatch<CRaSH, ?, ?> match = matcher.match(line.toString());
306        match.invoke(new InvocationContext(), new CRaSH());
307      }
308    }