1  package com.palantir.blog.processspawner;
2  /*
3   * All source code and information in this file is made
4   * available under the following licensing terms:
5   *
6   * Copyright (c) 2009, Palantir Technologies, Inc.
7   * All rights reserved.
8   *
9   * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions are
11  * met:
12  *
13  *     * Redistributions of source code must retain the above copyright
14  *       notice, this list of conditions and the following disclaimer.
15  *
16  *     * Redistributions in binary form must reproduce the above
17  *       copyright notice, this list of conditions and the following
18  *       disclaimer in the documentation and/or other materials provided
19  *       with the distribution.
20  *
21  *     * Neither the name of Palantir Technologies, Inc. nor the names of its
22  *       contributors may be used to endorse or promote products derived
23  *       from this software without specific prior written permission.
24  *
25  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
26  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
27  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
28  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
29  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
30  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
31  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
32  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
33  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
34  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
35  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
36  *
37  *
38  */ 
39 import java.io.File;
40 import java.util.ArrayList;
41 import java.util.HashMap;
42 import java.util.List;
43 import java.util.Map;
44 import java.util.Map.Entry;
45 
46 import org.apache.log4j.LogManager;
47 import org.apache.log4j.Logger;
48 
49 /**
50  * Specialized delegate for the invocation of Java processes.
51  * 
52  * @see ProcessBuilder
53  * @author regs
54  *
55  */
56 public class JavaInvoke extends ProcessSpawner {
57 
58    static final Logger javaInvokeLog = LogManager.getLogger(JavaInvoke.class);
59    
60    /**
61     * Constructs a new JavaInvoker object. Any of the passed arguments, aside from classToInvoke, may be null.  
62     * @param classToInvoke Fully-qualified classname of the class to invoke as main in the spawned VM.
63     * Does not need to be loaded in this VM (but must be on the classpath of the spawned VM).
64     * @param workingDirectory Directory this should run in.
65     * @param javaProperties Map of Java options as defined by {@link System#getProperties()}.
66     * @param args Command line arguments to be passed to the invoked main.  These are not arguments to the VM
67     * but arguments to the Java class.  These will be the contents of the args[] array in the main method on 
68     * the invoked class.
69     * @param additionalClassPath additional classpath entries to be added to the beginning of the classpath
70     * (to allow for overriding).
71     * @param environmentToMerge Java environment entries to be merged (overwriting) with the current environment
72     * to be used as the environment for spawned processes.
73     */
74    public JavaInvoke(String classToInvoke,
75                      File workingDirectory, 
76                      Map<String,String> javaProperties, 
77                      String[] args, 
78                      List<String> additionalClasspath,
79                      Map<String,String> environmentToMerge) {
80       super(workingDirectory, 
81             buildCommandLine(classToInvoke, javaProperties, new String[] {}, args), 
82             buildEnvironmentToMerge(additionalClasspath, environmentToMerge));
83    }
84    
85    public static final String[] buildCommandLine(String classToInvoke,
86                                                  Map<String,String> javaProperties,
87                                                  String[] vmargs,
88                                                  String[] processArgs) {
89       String[] javaSysProps = new String[0]; // reasonable default
90       // construct the system properties 
91       if(javaProperties != null && javaProperties.size() > 0) {
92          ArrayList<String> propList = new ArrayList<String>(javaProperties.size());
93          for(Entry<String, String> javaProp : javaProperties.entrySet()) {
94             propList.add("-D" + javaProp.getKey() + "=" + javaProp.getValue());
95          }
96          javaSysProps = propList.toArray(javaSysProps);
97       }
98       
99       if(vmargs == null) {
100         vmargs = new String[]{};
101      }
102      if(processArgs == null) {
103         processArgs = new String[]{};
104      }
105      
106      // construct the command line
107      final String javaPath = System.getProperty("java.home") + 
108                        File.separator + "bin" + 
109                        File.separator + "java" + 
110                        (File.separator.equals("\\") ? ".exe" : "");
111      final int cmdLineLength = vmargs.length + 
112                          processArgs.length + 
113                          javaSysProps.length + 2;
114      final String[] cmdarray = new String[cmdLineLength];
115
116      
117      // write out the command line
118      final int javaPosition = 0;
119      final int vmargsPosition = javaPosition + 1;
120      final int javaSysPropsPosition = vmargsPosition + vmargs.length;
121      final int classPosition = javaSysPropsPosition + javaSysProps.length;
122      final int processArgsPosition = classPosition + 1;
123      cmdarray[javaPosition] = javaPath;
124      System.arraycopy(vmargs,0, cmdarray, vmargsPosition, vmargs.length);
125      System.arraycopy(javaSysProps, 0, cmdarray, javaSysPropsPosition, javaSysProps.length);
126      cmdarray[classPosition] = classToInvoke;
127      System.arraycopy(processArgs, 0, cmdarray, processArgsPosition, processArgs.length);
128      
129      return cmdarray;
130   }
131   
132   /**
133    * Merges with existing classpath (that this VM was spawned with), and places it into the 
134    * CLASSPATH environment variable (to avoid command line escaping issues).
135    * 
136    * @param additionalClasspath - classpath entries to place at the front of the classpath.
137    * @param environment - entries in the 
138    * @return
139    */
140   public static final Map<String,String> buildEnvironmentToMerge(List<String> additionalClasspath,
141                                                                  Map<String,String> environment) {
142      
143      
144      // deal with additional classpath elements (prepend for overrides)
145      String cp = System.getProperty("java.class.path");
146      StringBuilder cpath = new StringBuilder(cp == null ? "" : cp);
147      if(additionalClasspath != null && additionalClasspath.size() > 0) {
148         for(String cpathEntry : additionalClasspath) {
149            cpath.insert(0,File.pathSeparatorChar).insert(0,cpathEntry);
150         }
151      }
152      if(environment == null) {
153         environment = new HashMap<String,String>();
154      }
155      environment.put("CLASSPATH",cpath.toString());
156      
157      if(javaInvokeLog.isInfoEnabled()) {
158         javaInvokeLog.info("CLASSPATH=" + cpath.toString());
159      }
160      return environment;
161   }
162}
163