View Javadoc

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