View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
4   //  ------------------------------------------------------------------------
5   //  All rights reserved. This program and the accompanying materials
6   //  are made available under the terms of the Eclipse Public License v1.0
7   //  and Apache License v2.0 which accompanies this distribution.
8   //
9   //      The Eclipse Public License is available at
10  //      http://www.eclipse.org/legal/epl-v10.html
11  //
12  //      The Apache License v2.0 is available at
13  //      http://www.opensource.org/licenses/apache2.0.php
14  //
15  //  You may elect to redistribute this code under either of these licenses.
16  //  ========================================================================
17  //
18  
19  package org.eclipse.jetty.maven.plugin;
20  
21  import java.io.BufferedOutputStream;
22  import java.io.BufferedReader;
23  import java.io.File;
24  import java.io.FileOutputStream;
25  import java.io.IOException;
26  import java.io.InputStream;
27  import java.io.InputStreamReader;
28  import java.io.LineNumberReader;
29  import java.io.OutputStream;
30  import java.net.MalformedURLException;
31  import java.util.ArrayList;
32  import java.util.Arrays;
33  import java.util.HashSet;
34  import java.util.Iterator;
35  import java.util.List;
36  import java.util.Locale;
37  import java.util.Properties;
38  import java.util.Random;
39  import java.util.Set;
40  
41  import org.apache.maven.artifact.Artifact;
42  import org.apache.maven.plugin.AbstractMojo;
43  import org.apache.maven.plugin.MojoExecutionException;
44  import org.apache.maven.plugin.MojoFailureException;
45  import org.apache.maven.plugin.descriptor.PluginDescriptor;
46  import org.apache.maven.project.MavenProject;
47  import org.eclipse.jetty.util.IO;
48  import org.eclipse.jetty.util.resource.Resource;
49  import org.eclipse.jetty.util.resource.ResourceCollection;
50  
51  
52  /**
53   * <p>
54   *  This goal is used to assemble your webapp into a war and automatically deploy it to Jetty in a forked JVM.
55   *  </p>
56   *  <p>
57   *  You need to define a jetty.xml file to configure connectors etc and a context xml file that sets up anything special
58   *  about your webapp. This plugin will fill in the:
59   *  <ul>
60   *  <li>context path
61   *  <li>classes
62   *  <li>web.xml
63   *  <li>root of the webapp
64   *  </ul>
65   *  Based on a combination of information that you supply and the location of files in your unassembled webapp.
66   *  </p>
67   *  <p>
68   *  There is a <a href="run-war-mojo.html">reference guide</a> to the configuration parameters for this plugin, and more detailed information
69   *  with examples in the <a href="http://docs.codehaus.org/display/JETTY/Maven+Jetty+Plugin/">Configuration Guide</a>.
70   *  </p>
71   * 
72   * @goal run-forked
73   * @requiresDependencyResolution test
74   * @execute phase="test-compile"
75   * @description Runs Jetty in forked JVM on an unassembled webapp
76   *
77   */
78  public class JettyRunForkedMojo extends AbstractMojo
79  {    
80      public static final String DEFAULT_WEBAPP_SRC = "src"+File.separator+"main"+File.separator+"webapp";
81      public static final String FAKE_WEBAPP = "webapp-tmp";
82      
83      
84      public String PORT_SYSPROPERTY = "jetty.port";
85      
86      /**
87       * Whether or not to include dependencies on the plugin's classpath with &lt;scope&gt;provided&lt;/scope&gt;
88       * Use WITH CAUTION as you may wind up with duplicate jars/classes.
89       * @parameter  default-value="false"
90       */
91      protected boolean useProvidedScope;
92      
93      
94      /**
95       * The maven project.
96       *
97       * @parameter expression="${project}"
98       * @required
99       * @readonly
100      */
101     private MavenProject project;
102 
103     
104     /**
105      * If true, the &lt;testOutputDirectory&gt;
106      * and the dependencies of &lt;scope&gt;test&lt;scope&gt;
107      * will be put first on the runtime classpath.
108      * @parameter alias="useTestClasspath" default-value="false"
109      */
110     private boolean useTestScope;
111     
112     
113     /**
114      * The default location of the web.xml file. Will be used
115      * if &lt;webAppConfig&gt;&lt;descriptor&gt; is not set.
116      * 
117      * @parameter expression="${basedir}/src/main/webapp/WEB-INF/web.xml"
118      * @readonly
119      */
120     private String webXml;
121     
122     
123     /**
124      * The target directory
125      * 
126      * @parameter expression="${project.build.directory}"
127      * @required
128      * @readonly
129      */
130     protected File target;
131     
132     
133     /**
134      * The temporary directory to use for the webapp.
135      * Defaults to target/tmp
136      *
137      * @parameter alias="tmpDirectory" expression="${project.build.directory}/tmp"
138      * @required
139      * @readonly
140      */
141     protected File tempDirectory;
142     
143     
144     
145     /**
146      * Whether temporary directory contents should survive webapp restarts.
147      * 
148      * @parameter default-value="false"
149      */
150     private boolean persistTempDirectory;
151 
152     
153     /**
154      * The directory containing generated classes.
155      *
156      * @parameter expression="${project.build.outputDirectory}"
157      * @required
158      * 
159      */
160     private File classesDirectory;    
161     
162     
163     /**
164      * The directory containing generated test classes.
165      * 
166      * @parameter expression="${project.build.testOutputDirectory}"
167      * @required
168      */
169     private File testClassesDirectory;
170    
171     
172     /**
173      * Root directory for all html/jsp etc files
174      *
175      * @parameter expression="${basedir}/src/main/webapp"
176      *
177      */
178     private File webAppSourceDirectory;
179 
180     /**
181      * Resource Bases
182      *
183      * @parameter
184      *
185      */
186      private String[] resourceBases;
187 
188     /**
189      * If true, the webAppSourceDirectory will be first on the list of 
190      * resources that form the resource base for the webapp. If false, 
191      * it will be last.
192      * 
193      * @parameter  default-value="true"
194      */
195     private boolean baseAppFirst;
196     
197 
198     /**
199      * Location of jetty xml configuration files whose contents 
200      * will be applied before any plugin configuration. Optional.
201      * @parameter
202      */
203     private String jettyXml;
204     
205     /**
206      * The context path for the webapp. Defaults to / for jetty-9
207      *
208      * @parameter expression="/"
209      */
210     private String contextPath;
211 
212 
213     /**
214      * Location of a context xml configuration file whose contents
215      * will be applied to the webapp AFTER anything in &lt;webAppConfig&gt;.Optional.
216      * @parameter
217      */
218     private String contextXml;
219 
220     
221     /**  
222      * @parameter expression="${jetty.skip}" default-value="false"
223      */
224     private boolean skip;
225 
226     
227     /**
228      * Port to listen to stop jetty on executing -DSTOP.PORT=&lt;stopPort&gt; 
229      * -DSTOP.KEY=&lt;stopKey&gt; -jar start.jar --stop
230      * @parameter
231      * @required
232      */
233     protected int stopPort;
234  
235     
236     /**
237      * Key to provide when stopping jetty on executing java -DSTOP.KEY=&lt;stopKey&gt; 
238      * -DSTOP.PORT=&lt;stopPort&gt; -jar start.jar --stop
239      * @parameter
240      * @required
241      */
242     protected String stopKey;
243 
244     
245     /**
246      * Arbitrary jvm args to pass to the forked process
247      * @parameter
248      */
249     private String jvmArgs;
250     
251     
252     /**
253      * @parameter expression="${plugin.artifacts}"
254      * @readonly
255      */
256     private List pluginArtifacts;
257     
258     
259     /**
260      * @parameter expression="${plugin}"
261      * @readonly
262      */
263     private PluginDescriptor plugin;
264     
265     
266     /**
267      * @parameter expression="true" default-value="true"
268      */
269     private boolean waitForChild;
270 
271     /**
272      * @parameter default-value="50"
273      */
274     private int maxStartupLines;
275 
276     /**
277      * The forked jetty instance
278      */
279     private Process forkedProcess;
280     
281     
282     /**
283      * Random number generator
284      */
285     private Random random;    
286     
287     
288     
289     
290     
291     
292     /**
293      * ShutdownThread
294      *
295      *
296      */
297     public class ShutdownThread extends Thread
298     {
299         public ShutdownThread()
300         {
301             super("RunForkedShutdown");
302         }
303         
304         public void run ()
305         {
306             if (forkedProcess != null && waitForChild)
307             {
308                 forkedProcess.destroy();
309             }
310         }
311     }
312     
313 
314     
315     
316     /**
317      * ConsoleStreamer
318      * 
319      * Simple streamer for the console output from a Process
320      */
321     private static class ConsoleStreamer implements Runnable
322     {
323         private String mode;
324         private BufferedReader reader;
325 
326         public ConsoleStreamer(String mode, InputStream is)
327         {
328             this.mode = mode;
329             this.reader = new BufferedReader(new InputStreamReader(is));
330         }
331 
332 
333         public void run()
334         {
335             String line;
336             try
337             {
338                 while ((line = reader.readLine()) != (null))
339                 {
340                     System.out.println("[" + mode + "] " + line);
341                 }
342             }
343             catch (IOException ignore)
344             {
345                 /* ignore */
346             }
347             finally
348             {
349                 IO.close(reader);
350             }
351         }
352     }
353     
354     
355     
356     
357     
358     /**
359      * @see org.apache.maven.plugin.Mojo#execute()
360      */
361     public void execute() throws MojoExecutionException, MojoFailureException
362     {
363         getLog().info("Configuring Jetty for project: " + project.getName());
364         if (skip)
365         {
366             getLog().info("Skipping Jetty start: jetty.skip==true");
367             return;
368         }
369         PluginLog.setLog(getLog());
370         Runtime.getRuntime().addShutdownHook(new ShutdownThread());
371         random = new Random();
372         startJettyRunner();
373     }
374     
375     
376     
377     
378     /**
379      * @return
380      * @throws MojoExecutionException
381      */
382     public List<String> getProvidedJars() throws MojoExecutionException
383     {  
384         //if we are configured to include the provided dependencies on the plugin's classpath
385         //(which mimics being on jetty's classpath vs being on the webapp's classpath), we first
386         //try and filter out ones that will clash with jars that are plugin dependencies, then
387         //create a new classloader that we setup in the parent chain.
388         if (useProvidedScope)
389         {
390             
391                 List<String> provided = new ArrayList<String>();        
392                 for ( Iterator<Artifact> iter = project.getArtifacts().iterator(); iter.hasNext(); )
393                 {                   
394                     Artifact artifact = iter.next();
395                     if (Artifact.SCOPE_PROVIDED.equals(artifact.getScope()) && !isPluginArtifact(artifact))
396                     {
397                         provided.add(artifact.getFile().getAbsolutePath());
398                         if (getLog().isDebugEnabled()) { getLog().debug("Adding provided artifact: "+artifact);}
399                     }
400                 }
401                 return provided;
402 
403         }
404         else
405             return null;
406     }
407     
408    
409     
410     
411     /**
412      * @return
413      * @throws MojoExecutionException
414      */
415     public File prepareConfiguration() throws MojoExecutionException
416     {
417         try
418         {   
419             //work out the configuration based on what is configured in the pom
420             File propsFile = new File (target, "fork.props");
421             if (propsFile.exists())
422                 propsFile.delete();   
423 
424             propsFile.createNewFile();
425             //propsFile.deleteOnExit();
426 
427             Properties props = new Properties();
428 
429 
430             //web.xml
431             if (webXml != null)
432                 props.put("web.xml", webXml);
433 
434             //sort out the context path
435             if (contextPath != null)
436                 props.put("context.path", contextPath);
437 
438             //sort out the tmp directory (make it if it doesn't exist)
439             if (tempDirectory != null)
440             {
441                 if (!tempDirectory.exists())
442                     tempDirectory.mkdirs();
443                 props.put("tmp.dir", tempDirectory.getAbsolutePath());
444             }
445             
446             props.put("tmp.dir.persist", Boolean.toString(persistTempDirectory));
447 
448             if (resourceBases == null)
449             {
450                 //sort out base dir of webapp
451                 if (webAppSourceDirectory == null || !webAppSourceDirectory.exists())
452                 {
453                     webAppSourceDirectory = new File (project.getBasedir(), DEFAULT_WEBAPP_SRC);
454                     if (!webAppSourceDirectory.exists())
455                     {
456                         //try last resort of making a fake empty dir
457                         File target = new File(project.getBuild().getDirectory());
458                         webAppSourceDirectory = new File(target, FAKE_WEBAPP);
459                         if (!webAppSourceDirectory.exists())
460                             webAppSourceDirectory.mkdirs();
461                     }
462                 }
463                 resourceBases = new String[] { webAppSourceDirectory.getAbsolutePath() };
464             }
465             StringBuffer rb = new StringBuffer(resourceBases[0]);
466             for (int i=1; i<resourceBases.length; i++) 
467             {
468                 rb.append(File.pathSeparator);
469                 rb.append(resourceBases[i]);
470             }
471             props.put("base.dirs", rb.toString());
472 
473             //sort out the resource base directories of the webapp
474             StringBuilder builder = new StringBuilder();
475             props.put("base.first", Boolean.toString(baseAppFirst));
476 
477             //web-inf classes
478             List<File> classDirs = getClassesDirs();
479             StringBuffer strbuff = new StringBuffer();
480             for (int i=0; i<classDirs.size(); i++)
481             {
482                 File f = classDirs.get(i);
483                 strbuff.append(f.getAbsolutePath());
484                 if (i < classDirs.size()-1)
485                     strbuff.append(",");
486             }
487 
488             if (classesDirectory != null)
489             {
490                 props.put("classes.dir", classesDirectory.getAbsolutePath());
491             }
492             
493             if (useTestScope && testClassesDirectory != null)
494             {
495                 props.put("testClasses.dir", testClassesDirectory.getAbsolutePath());
496             }
497 
498             //web-inf lib
499             List<File> deps = getDependencyFiles();
500             strbuff.setLength(0);
501             for (int i=0; i<deps.size(); i++)
502             {
503                 File d = deps.get(i);
504                 strbuff.append(d.getAbsolutePath());
505                 if (i < deps.size()-1)
506                     strbuff.append(",");
507             }
508             props.put("lib.jars", strbuff.toString());
509 
510             //any war files
511             List<Artifact> warArtifacts = getWarArtifacts(); 
512             for (int i=0; i<warArtifacts.size(); i++)
513             {
514                 strbuff.setLength(0);           
515                 Artifact a  = warArtifacts.get(i);
516                 strbuff.append(a.getGroupId()+",");
517                 strbuff.append(a.getArtifactId()+",");
518                 strbuff.append(a.getFile().getAbsolutePath());
519                 props.put("maven.war.artifact."+i, strbuff.toString());
520             }
521           
522             
523             //any overlay configuration
524             WarPluginInfo warPlugin = new WarPluginInfo(project);
525             
526             //add in the war plugins default includes and excludes
527             props.put("maven.war.includes", toCSV(warPlugin.getDependentMavenWarIncludes()));
528             props.put("maven.war.excludes", toCSV(warPlugin.getDependentMavenWarExcludes()));
529             
530             
531             List<OverlayConfig> configs = warPlugin.getMavenWarOverlayConfigs();
532             int i=0;
533             for (OverlayConfig c:configs)
534             {
535                 props.put("maven.war.overlay."+(i++), c.toString());
536             }
537             
538             try (OutputStream out = new BufferedOutputStream(new FileOutputStream(propsFile)))
539             {
540                 props.store(out, "properties for forked webapp");
541             }
542             return propsFile;
543         }
544         catch (Exception e)
545         {
546             throw new MojoExecutionException("Prepare webapp configuration", e);
547         }
548     }
549     
550 
551     
552     
553     /**
554      * @return
555      */
556     private List<File> getClassesDirs ()
557     {
558         List<File> classesDirs = new ArrayList<File>();
559         
560         //if using the test classes, make sure they are first
561         //on the list
562         if (useTestScope && (testClassesDirectory != null))
563             classesDirs.add(testClassesDirectory);
564         
565         if (classesDirectory != null)
566             classesDirs.add(classesDirectory);
567         
568         return classesDirs;
569     }
570   
571     
572   
573     
574     /**
575      * @return
576      * @throws MalformedURLException
577      * @throws IOException
578      */
579     private List<Artifact> getWarArtifacts()
580     throws MalformedURLException, IOException
581     {
582         List<Artifact> warArtifacts = new ArrayList<Artifact>();
583         for ( Iterator<Artifact> iter = project.getArtifacts().iterator(); iter.hasNext(); )
584         {
585             Artifact artifact = (Artifact) iter.next();  
586             
587             if (artifact.getType().equals("war"))
588                 warArtifacts.add(artifact);
589         }
590 
591         return warArtifacts;
592     }
593     
594     
595     
596     
597     /**
598      * @return
599      */
600     private List<File> getDependencyFiles ()
601     {
602         List<File> dependencyFiles = new ArrayList<File>();
603 
604         for ( Iterator<Artifact> iter = project.getArtifacts().iterator(); iter.hasNext(); )
605         {
606             Artifact artifact = (Artifact) iter.next();
607             // Test never appears here !
608             if (((!Artifact.SCOPE_PROVIDED.equals(artifact.getScope())) && (!Artifact.SCOPE_TEST.equals( artifact.getScope())))
609                     ||
610                 (useTestScope && Artifact.SCOPE_TEST.equals( artifact.getScope())))
611             {
612                 dependencyFiles.add(artifact.getFile());
613                 getLog().debug( "Adding artifact " + artifact.getFile().getName() + " for WEB-INF/lib " );
614             }
615         }
616         
617         return dependencyFiles; 
618     }
619     
620     
621     
622     
623     /**
624      * @param artifact
625      * @return
626      */
627     public boolean isPluginArtifact(Artifact artifact)
628     {
629         if (pluginArtifacts == null || pluginArtifacts.isEmpty())
630             return false;
631         
632         boolean isPluginArtifact = false;
633         for (Iterator<Artifact> iter = pluginArtifacts.iterator(); iter.hasNext() && !isPluginArtifact; )
634         {
635             Artifact pluginArtifact = iter.next();
636             if (getLog().isDebugEnabled()) { getLog().debug("Checking "+pluginArtifact);}
637             if (pluginArtifact.getGroupId().equals(artifact.getGroupId()) && pluginArtifact.getArtifactId().equals(artifact.getArtifactId()))
638                 isPluginArtifact = true;
639         }
640         
641         return isPluginArtifact;
642     }
643     
644     
645     
646     
647     /**
648      * @return
649      * @throws Exception
650      */
651     private Set<Artifact> getExtraJars()
652     throws Exception
653     {
654         Set<Artifact> extraJars = new HashSet<Artifact>();
655   
656         
657         List l = pluginArtifacts;
658         Artifact pluginArtifact = null;
659 
660         if (l != null)
661         {
662             Iterator itor = l.iterator();
663             while (itor.hasNext() && pluginArtifact == null)
664             {              
665                 Artifact a = (Artifact)itor.next();
666                 if (a.getArtifactId().equals(plugin.getArtifactId())) //get the jetty-maven-plugin jar
667                 {
668                     extraJars.add(a);
669                 }
670             }
671         }
672 
673         return extraJars;
674     }
675 
676     
677 
678     
679     /**
680      * @throws MojoExecutionException
681      */
682     public void startJettyRunner() throws MojoExecutionException
683     {      
684         try
685         {
686         
687             File props = prepareConfiguration();
688             
689             List<String> cmd = new ArrayList<String>();
690             cmd.add(getJavaBin());
691             
692             if (jvmArgs != null)
693             {
694                 String[] args = jvmArgs.split(" ");
695                 for (int i=0;args != null && i<args.length;i++)
696                 {
697                     if (args[i] !=null && !"".equals(args[i]))
698                         cmd.add(args[i].trim());
699                 }
700             }
701             
702             String classPath = getClassPath();
703             if (classPath != null && classPath.length() > 0)
704             {
705                 cmd.add("-cp");
706                 cmd.add(classPath);
707             }
708             cmd.add(Starter.class.getCanonicalName());
709             
710             if (stopPort > 0 && stopKey != null)
711             {
712                 cmd.add("--stop-port");
713                 cmd.add(Integer.toString(stopPort));
714                 cmd.add("--stop-key");
715                 cmd.add(stopKey);
716             }
717             if (jettyXml != null)
718             {
719                 cmd.add("--jetty-xml");
720                 cmd.add(jettyXml);
721             }
722         
723             if (contextXml != null)
724             {
725                 cmd.add("--context-xml");
726                 cmd.add(contextXml);
727             }
728             
729             cmd.add("--props");
730             cmd.add(props.getAbsolutePath());
731             
732             String token = createToken();
733             cmd.add("--token");
734             cmd.add(token);
735             
736             ProcessBuilder builder = new ProcessBuilder(cmd);
737             builder.directory(project.getBasedir());
738             
739             if (PluginLog.getLog().isDebugEnabled())
740                 PluginLog.getLog().debug(Arrays.toString(cmd.toArray()));
741             
742             PluginLog.getLog().info("Forked process starting");
743 
744             if (waitForChild)
745             {
746                 forkedProcess = builder.start();
747                 startPump("STDOUT",forkedProcess.getInputStream());
748                 startPump("STDERR",forkedProcess.getErrorStream());
749                 int exitcode = forkedProcess.waitFor();            
750                 PluginLog.getLog().info("Forked execution exit: "+exitcode);
751             }
752             else
753             {   //merge stderr and stdout from child
754                 builder.redirectErrorStream(true);
755                 forkedProcess = builder.start();
756 
757                 //wait for the child to be ready before terminating.
758                 //child indicates it has finished starting by printing on stdout the token passed to it
759                 try
760                 {
761                     String line = "";
762                     try (InputStream is = forkedProcess.getInputStream();
763                             LineNumberReader reader = new LineNumberReader(new InputStreamReader(is)))
764                     {
765                         int attempts = maxStartupLines; //max lines we'll read trying to get token
766                         while (attempts>0 && line != null)
767                         {
768                             --attempts;
769                             line = reader.readLine();
770                             if (line != null && line.startsWith(token))
771                                 break;
772                         }
773 
774                     }
775 
776                     if (line != null && line.trim().equals(token))
777                         PluginLog.getLog().info("Forked process started.");
778                     else
779                     {
780                         String err = (line == null?"":(line.startsWith(token)?line.substring(token.length()):line));
781                         PluginLog.getLog().info("Forked process startup errors"+(!"".equals(err)?", received: "+err:""));
782                     }
783                 }
784                 catch (Exception e)
785                 {
786                     throw new MojoExecutionException ("Problem determining if forked process is ready: "+e.getMessage());
787                 }
788 
789             }
790         }
791         catch (InterruptedException ex)
792         {
793             if (forkedProcess != null && waitForChild)
794                 forkedProcess.destroy();
795             
796             throw new MojoExecutionException("Failed to start Jetty within time limit");
797         }
798         catch (Exception ex)
799         {
800             if (forkedProcess != null && waitForChild)
801                 forkedProcess.destroy();
802             
803             throw new MojoExecutionException("Failed to create Jetty process", ex);
804         }
805     }
806     
807  
808 
809     
810     /**
811      * @return
812      * @throws Exception
813      */
814     public String getClassPath() throws Exception
815     {
816         StringBuilder classPath = new StringBuilder();
817         for (Object obj : pluginArtifacts)
818         {
819             Artifact artifact = (Artifact) obj;
820             if ("jar".equals(artifact.getType()))
821             {
822                 if (classPath.length() > 0)
823                 {
824                     classPath.append(File.pathSeparator);
825                 }
826                 classPath.append(artifact.getFile().getAbsolutePath());
827 
828             }
829         }
830         
831         //Any jars that we need from the plugin environment (like the ones containing Starter class)
832         Set<Artifact> extraJars = getExtraJars();
833         for (Artifact a:extraJars)
834         { 
835             classPath.append(File.pathSeparator);
836             classPath.append(a.getFile().getAbsolutePath());
837         }
838         
839         
840         //Any jars that we need from the project's dependencies because we're useProvided
841         List<String> providedJars = getProvidedJars();
842         if (providedJars != null && !providedJars.isEmpty())
843         {
844             for (String jar:providedJars)
845             {
846                 classPath.append(File.pathSeparator);
847                 classPath.append(jar);
848                 if (getLog().isDebugEnabled()) getLog().debug("Adding provided jar: "+jar);
849             }
850         }
851 
852         return classPath.toString();
853     }
854 
855     
856 
857     
858     /**
859      * @return
860      */
861     private String getJavaBin()
862     {
863         String javaexes[] = new String[]
864         { "java", "java.exe" };
865 
866         File javaHomeDir = new File(System.getProperty("java.home"));
867         for (String javaexe : javaexes)
868         {
869             File javabin = new File(javaHomeDir,fileSeparators("bin/" + javaexe));
870             if (javabin.exists() && javabin.isFile())
871             {
872                 return javabin.getAbsolutePath();
873             }
874         }
875 
876         return "java";
877     }
878     
879 
880     
881     
882     /**
883      * @param path
884      * @return
885      */
886     public static String fileSeparators(String path)
887     {
888         StringBuilder ret = new StringBuilder();
889         for (char c : path.toCharArray())
890         {
891             if ((c == '/') || (c == '\\'))
892             {
893                 ret.append(File.separatorChar);
894             }
895             else
896             {
897                 ret.append(c);
898             }
899         }
900         return ret.toString();
901     }
902 
903 
904     
905     
906     /**
907      * @param path
908      * @return
909      */
910     public static String pathSeparators(String path)
911     {
912         StringBuilder ret = new StringBuilder();
913         for (char c : path.toCharArray())
914         {
915             if ((c == ',') || (c == ':'))
916             {
917                 ret.append(File.pathSeparatorChar);
918             }
919             else
920             {
921                 ret.append(c);
922             }
923         }
924         return ret.toString();
925     }
926 
927 
928     
929     
930     /**
931      * @return
932      */
933     private String createToken ()
934     {
935         return Long.toString(random.nextLong()^System.currentTimeMillis(), 36).toUpperCase(Locale.ENGLISH);
936     }
937     
938 
939     
940     
941     /**
942      * @param mode
943      * @param inputStream
944      */
945     private void startPump(String mode, InputStream inputStream)
946     {
947         ConsoleStreamer pump = new ConsoleStreamer(mode,inputStream);
948         Thread thread = new Thread(pump,"ConsoleStreamer/" + mode);
949         thread.setDaemon(true);
950         thread.start();
951     }
952 
953 
954     
955     
956     /**
957      * @param strings
958      * @return
959      */
960     private String toCSV (List<String> strings)
961     {
962         if (strings == null)
963             return "";
964         StringBuffer strbuff = new StringBuffer();
965         Iterator<String> itor = strings.iterator();
966         while (itor.hasNext())
967         {
968             strbuff.append(itor.next());
969             if (itor.hasNext())
970                 strbuff.append(",");
971         }
972         return strbuff.toString();
973     }
974 }