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