4747import java .nio .file .attribute .BasicFileAttributes ;
4848import java .security .MessageDigest ;
4949import java .security .NoSuchAlgorithmException ;
50+ import java .util .ArrayList ;
51+ import java .util .Arrays ;
52+ import java .util .List ;
5053import java .util .Locale ;
5154import java .util .regex .Pattern ;
5255import java .util .zip .ZipEntry ;
5659 * Utility to start and stop local Google Cloud Datastore process.
5760 */
5861public class LocalGcdHelper {
59-
62+
6063 private final String projectId ;
6164 private Path gcdPath ;
6265 private ProcessStreamReader processReader ;
6366
6467 public static final String DEFAULT_PROJECT_ID = "projectid1" ;
6568 public static final int PORT = 8080 ;
66- private static final String GCD = "gcd-v1beta2-rev1-2.1.2b" ;
6769 private static final String GCD_VERSION = "v1beta2" ;
68- private static final String GCD_FILENAME = GCD + ".zip" ;
70+ private static final String GCD_BUILD = "rev1-2.1.2b" ;
71+ private static final String GCD_BASENAME = "gcd-" + GCD_VERSION + "-" + GCD_BUILD ;
72+ private static final String GCD_FILENAME = GCD_BASENAME + ".zip" ;
6973 private static final String MD5_CHECKSUM = "d84384cdfa8658e1204f4f8be51300e8" ;
7074 private static final URL GCD_URL ;
7175 private static final String GCLOUD = "gcloud" ;
7276 private static final Path INSTALLED_GCD_PATH ;
77+ private static final String GCD_VERSION_PREFIX = "gcd-emulator " ;
7378
7479 static {
7580 INSTALLED_GCD_PATH = installedGcdPath ();
@@ -85,7 +90,13 @@ public class LocalGcdHelper {
8590 }
8691
8792 private static Path installedGcdPath () {
88- Path gcloudPath = executablePath (GCLOUD );
93+ String gcloudExecutableName ;
94+ if (isWindows ()) {
95+ gcloudExecutableName = GCLOUD + ".cmd" ;
96+ } else {
97+ gcloudExecutableName = GCLOUD ;
98+ }
99+ Path gcloudPath = executablePath (gcloudExecutableName );
89100 gcloudPath = (gcloudPath == null ) ? null : gcloudPath .getParent ();
90101 if (gcloudPath == null ) {
91102 return null ;
@@ -105,19 +116,18 @@ private static Path installedGcdPath() {
105116 }
106117
107118 private static String installedGcdVersion () throws IOException , InterruptedException {
108- ProcessBuilder processBuilder = new ProcessBuilder ()
109- .redirectErrorStream (true );
119+ CommandWrapper gcloudCommand ;
110120 if (isWindows ()) {
111- processBuilder . command ( "cmd" , "/C" , "gcloud" , "version" );
121+ gcloudCommand = CommandWrapper . cmd ( );
112122 } else {
113- processBuilder . command ( "bash" , "gcloud" , "version" );
123+ gcloudCommand = CommandWrapper . bash ( );
114124 }
115- Process process = processBuilder .start ();
125+ Process process = gcloudCommand . command ( "gcloud" , "version" ). redirectErrorStream () .start ();
116126 process .waitFor ();
117127 try (BufferedReader reader =
118128 new BufferedReader (new InputStreamReader (process .getInputStream ()))) {
119129 for (String line = reader .readLine (); line != null ; line = reader .readLine ()) {
120- if (line .startsWith ("gcd-emulator" )) {
130+ if (line .startsWith (GCD_VERSION_PREFIX )) {
121131 String [] lineComponents = line .split (" " );
122132 if (lineComponents .length > 1 ) {
123133 return lineComponents [1 ];
@@ -126,7 +136,6 @@ private static String installedGcdVersion() throws IOException, InterruptedExcep
126136 }
127137 return null ;
128138 }
129-
130139 }
131140
132141 private static Path executablePath (String cmd ) {
@@ -185,6 +194,84 @@ public static ProcessStreamReader start(Process process, String blockUntil) thro
185194 return thread ;
186195 }
187196 }
197+
198+ private static class CommandWrapper {
199+
200+ private final List <String > prefix ;
201+ private List <String > command ;
202+ private String nullFilename ;
203+ private boolean redirectOutputToNull ;
204+ private boolean redirectErrorStream ;
205+ private boolean redirectErrorInherit ;
206+ private Path directory ;
207+
208+ private CommandWrapper () {
209+ this .prefix = new ArrayList <>();
210+ }
211+
212+ public CommandWrapper command (String ... command ) {
213+ this .command = new ArrayList <>(command .length + this .prefix .size ());
214+ this .command .addAll (prefix );
215+ this .command .addAll (Arrays .asList (command ));
216+ return this ;
217+ }
218+
219+ public CommandWrapper redirectOutputToNull () {
220+ this .redirectOutputToNull = true ;
221+ return this ;
222+ }
223+
224+ public CommandWrapper redirectErrorStream () {
225+ this .redirectErrorStream = true ;
226+ return this ;
227+ }
228+
229+ public CommandWrapper redirectErrorInherit () {
230+ this .redirectErrorInherit = true ;
231+ return this ;
232+ }
233+
234+ public CommandWrapper directory (Path directory ) {
235+ this .directory = directory ;
236+ return this ;
237+ }
238+
239+ public ProcessBuilder builder () {
240+ ProcessBuilder builder = new ProcessBuilder (command );
241+ if (redirectOutputToNull ) {
242+ builder .redirectOutput (new File (nullFilename ));
243+ }
244+ if (directory != null ) {
245+ builder .directory (directory .toFile ());
246+ }
247+ if (redirectErrorStream ) {
248+ builder .redirectErrorStream (true );
249+ }
250+ if (redirectErrorInherit ) {
251+ builder .redirectError (ProcessBuilder .Redirect .INHERIT );
252+ }
253+ return builder ;
254+ }
255+
256+ public Process start () throws IOException {
257+ return builder ().start ();
258+ }
259+
260+ public static CommandWrapper cmd () {
261+ CommandWrapper wrapper = new CommandWrapper ();
262+ wrapper .prefix .add ("cmd" );
263+ wrapper .prefix .add ("/C" );
264+ wrapper .nullFilename = "NUL:" ;
265+ return wrapper ;
266+ }
267+
268+ public static CommandWrapper bash () {
269+ CommandWrapper wrapper = new CommandWrapper ();
270+ wrapper .prefix .add ("bash" );
271+ wrapper .nullFilename = "/dev/null" ;
272+ return wrapper ;
273+ }
274+ }
188275
189276 public LocalGcdHelper (String projectId ) {
190277 this .projectId = projectId ;
@@ -210,7 +297,7 @@ public void start() throws IOException, InterruptedException {
210297 // If cloud is available we use it, otherwise we download and start gcd
211298 if (INSTALLED_GCD_PATH == null ) {
212299 downloadGcd ();
213- gcdExecutablePath = gcdPath .resolve (GCD );
300+ gcdExecutablePath = gcdPath .resolve (GCD_BASENAME );
214301 } else {
215302 gcdExecutablePath = INSTALLED_GCD_PATH ;
216303 }
@@ -226,6 +313,7 @@ private void downloadGcd() throws IOException {
226313 fos .getChannel ().transferFrom (rbc , 0 , Long .MAX_VALUE );
227314 }
228315 }
316+
229317 // unzip the gcd
230318 try (ZipInputStream zipIn = new ZipInputStream (new FileInputStream (gcdZipFile ))) {
231319 ZipEntry entry = zipIn .getNextEntry ();
@@ -246,38 +334,36 @@ private void startGcd(Path executablePath) throws IOException, InterruptedExcept
246334 // cleanup any possible data for the same project
247335 File datasetFolder = new File (gcdPath .toFile (), projectId );
248336 deleteRecurse (datasetFolder .toPath ());
249-
250- // create the datastore for the project
251- ProcessBuilder processBuilder = new ProcessBuilder ()
252- . redirectError ( ProcessBuilder . Redirect . INHERIT )
253- . directory ( gcdPath . toFile ()) ;
337+
338+ // create command wrappers
339+ CommandWrapper gcdCreateCommand ;
340+ CommandWrapper gcdStartCommand ;
341+ Path gcdAbsolutePath ;
254342 if (isWindows ()) {
255- Path gcdAbsolutePath = executablePath . toAbsolutePath (). resolve ( "gcd. cmd" );
256- processBuilder . command ( "cmd" , "/C" , gcdAbsolutePath . toString (), "create" ,
257- "-p" , projectId , projectId );
343+ gcdCreateCommand = CommandWrapper . cmd ( );
344+ gcdStartCommand = CommandWrapper . cmd ();
345+ gcdAbsolutePath = executablePath . toAbsolutePath (). resolve ( "gcd.cmd" );
258346 } else {
259- Path gcdAbsolutePath = executablePath . toAbsolutePath (). resolve ( "gcd.sh" );
260- processBuilder . command ( "bash" , gcdAbsolutePath . toString (), "create" ,
261- "-p" , projectId , projectId );
347+ gcdCreateCommand = CommandWrapper . bash ( );
348+ gcdStartCommand = CommandWrapper . bash ();
349+ gcdAbsolutePath = executablePath . toAbsolutePath (). resolve ( "gcd.sh" );
262350 }
263351
264- Process temp = processBuilder .start ();
352+ // create the datastore for the project
353+ Process temp = gcdCreateCommand .command (gcdAbsolutePath .toString (), "create" , "-p" , projectId ,
354+ projectId )
355+ .redirectErrorInherit ()
356+ .directory (gcdPath )
357+ .redirectOutputToNull ()
358+ .start ();
265359 temp .waitFor ();
266360
267361 // start the datastore for the project
268- processBuilder = new ProcessBuilder ()
269- .directory (gcdPath .toFile ())
270- .redirectErrorStream (true );
271- if (isWindows ()) {
272- Path gcdAbsolutePath = executablePath .toAbsolutePath ().resolve ("gcd.cmd" );
273- processBuilder .command ("cmd" , "/C" , gcdAbsolutePath .toString (), "start" , "--testing" ,
274- "--allow_remote_shutdown" , projectId );
275- } else {
276- Path gcdAbsolutePath = executablePath .toAbsolutePath ().resolve ("gcd.sh" );
277- processBuilder .command ("bash" , gcdAbsolutePath .toString (), "start" , "--testing" ,
278- "--allow_remote_shutdown" , projectId );
279- }
280- temp = processBuilder .start ();
362+ temp = gcdStartCommand .command (gcdAbsolutePath .toString (), "start" , "--testing" ,
363+ "--allow_remote_shutdown" , projectId )
364+ .directory (gcdPath )
365+ .redirectErrorStream ()
366+ .start ();
281367 processReader = ProcessStreamReader .start (temp , "Dev App Server is now running" );
282368 }
283369
0 commit comments