4040import java .nio .channels .ReadableByteChannel ;
4141import java .nio .file .FileVisitResult ;
4242import java .nio .file .Files ;
43- import java .nio .file .InvalidPathException ;
4443import java .nio .file .Path ;
4544import java .nio .file .Paths ;
4645import java .nio .file .SimpleFileVisitor ;
4746import java .nio .file .attribute .BasicFileAttributes ;
4847import java .security .MessageDigest ;
4948import java .security .NoSuchAlgorithmException ;
50- import java .util .ArrayList ;
51- import java .util .Arrays ;
52- import java .util .List ;
5349import java .util .Locale ;
54- import java .util .regex .Pattern ;
5550import java .util .zip .ZipEntry ;
5651import java .util .zip .ZipInputStream ;
5752
58- import java .util .logging .Level ;
59- import java .util .logging .Logger ;
60-
6153/**
6254 * Utility to start and stop local Google Cloud Datastore process.
6355 */
6456public class LocalGcdHelper {
6557
66- private static final Logger log = Logger .getLogger (LocalGcdHelper .class .getName ());
67-
6858 private final String projectId ;
6959 private Path gcdPath ;
7060 private ProcessStreamReader processReader ;
7161
7262 public static final String DEFAULT_PROJECT_ID = "projectid1" ;
7363 public static final int PORT = 8080 ;
74- private static final String GCD_VERSION = "v1beta2" ;
75- private static final String GCD_BUILD = "rev1-2.1.2b" ;
76- private static final String GCD_BASENAME = "gcd-" + GCD_VERSION + "-" + GCD_BUILD ;
77- private static final String GCD_FILENAME = GCD_BASENAME + ".zip" ;
64+ private static final String GCD = "gcd-v1beta2-rev1-2.1.2b" ;
65+ private static final String GCD_FILENAME = GCD + ".zip" ;
7866 private static final String MD5_CHECKSUM = "d84384cdfa8658e1204f4f8be51300e8" ;
7967 private static final URL GCD_URL ;
80- private static final String GCLOUD = "gcloud" ;
81- private static final Path INSTALLED_GCD_PATH ;
82- private static final String GCD_VERSION_PREFIX = "gcd-emulator " ;
8368
8469 static {
85- INSTALLED_GCD_PATH = installedGcdPath ();
86- if (INSTALLED_GCD_PATH != null ) {
87- GCD_URL = null ;
88- } else {
89- try {
90- GCD_URL = new URL ("http://storage.googleapis.com/gcd/tools/" + GCD_FILENAME );
91- } catch (MalformedURLException e ) {
92- throw new RuntimeException (e );
93- }
94- }
95- }
96-
97- private static Path installedGcdPath () {
98- String gcloudExecutableName ;
99- if (isWindows ()) {
100- gcloudExecutableName = GCLOUD + ".cmd" ;
101- } else {
102- gcloudExecutableName = GCLOUD ;
103- }
104- Path gcloudPath = executablePath (gcloudExecutableName );
105- gcloudPath = (gcloudPath == null ) ? null : gcloudPath .getParent ();
106- if (gcloudPath == null ) {
107- if (log .isLoggable (Level .FINE )) {
108- log .fine ("SDK not found" );
109- }
110- return null ;
111- }
112- if (log .isLoggable (Level .FINE )) {
113- log .fine ("SDK found, looking for datastore emulator" );
114- }
115- Path installedGcdPath = gcloudPath .resolve ("platform" ).resolve ("gcd" );
116- if (Files .exists (installedGcdPath )) {
117- try {
118- String installedVersion = installedGcdVersion ();
119- if (installedVersion != null && installedVersion .startsWith (GCD_VERSION )) {
120- if (log .isLoggable (Level .FINE )) {
121- log .fine ("SDK datastore emulator found" );
122- }
123- return installedGcdPath ;
124- } else {
125- if (log .isLoggable (Level .FINE )) {
126- log .fine ("SDK datastore emulator found but version mismatch" );
127- }
128- }
129- } catch (IOException | InterruptedException ignore ) {
130- // ignore
131- }
132- }
133- return null ;
134- }
135-
136- private static String installedGcdVersion () throws IOException , InterruptedException {
137- Process process =
138- CommandWrapper .create ().command ("gcloud" , "version" ).redirectErrorStream ().start ();
139- process .waitFor ();
140- try (BufferedReader reader =
141- new BufferedReader (new InputStreamReader (process .getInputStream ()))) {
142- for (String line = reader .readLine (); line != null ; line = reader .readLine ()) {
143- if (line .startsWith (GCD_VERSION_PREFIX )) {
144- String [] lineComponents = line .split (" " );
145- if (lineComponents .length > 1 ) {
146- return lineComponents [1 ];
147- }
148- }
149- }
150- return null ;
151- }
152- }
153-
154- private static Path executablePath (String cmd ) {
155- String [] paths = System .getenv ("PATH" ).split (Pattern .quote (File .pathSeparator ));
156- for (String pathString : paths ) {
157- try {
158- Path path = Paths .get (pathString );
159- if (Files .exists (path .resolve (cmd ))) {
160- return path ;
161- }
162- } catch (InvalidPathException ignore ) {
163- // ignore
164- }
70+ try {
71+ GCD_URL = new URL ("http://storage.googleapis.com/gcd/tools/" + GCD_FILENAME );
72+ } catch (MalformedURLException e ) {
73+ throw new RuntimeException (e );
16574 }
166- return null ;
16775 }
16876
16977 private static class ProcessStreamReader extends Thread {
@@ -175,7 +83,7 @@ private static class ProcessStreamReader extends Thread {
17583 super ("Local GCD InputStream reader" );
17684 setDaemon (true );
17785 this .process = process ;
178- reader = new BufferedReader (new InputStreamReader (process .getInputStream ()));
86+ reader = new BufferedReader (new InputStreamReader (process .getInputStream ()));
17987 if (!Strings .isNullOrEmpty (blockUntil )) {
18088 String line ;
18189 do {
@@ -208,81 +116,6 @@ public static ProcessStreamReader start(Process process, String blockUntil) thro
208116 }
209117 }
210118
211- private static class CommandWrapper {
212-
213- private final List <String > prefix ;
214- private List <String > command ;
215- private String nullFilename ;
216- private boolean redirectOutputToNull ;
217- private boolean redirectErrorStream ;
218- private boolean redirectErrorInherit ;
219- private Path directory ;
220-
221- private CommandWrapper () {
222- this .prefix = new ArrayList <>();
223- if (isWindows ()) {
224- this .prefix .add ("cmd" );
225- this .prefix .add ("/C" );
226- this .nullFilename = "NUL:" ;
227- } else {
228- this .prefix .add ("bash" );
229- this .nullFilename = "/dev/null" ;
230- }
231- }
232-
233- public CommandWrapper command (String ... command ) {
234- this .command = new ArrayList <>(command .length + this .prefix .size ());
235- this .command .addAll (prefix );
236- this .command .addAll (Arrays .asList (command ));
237- return this ;
238- }
239-
240- public CommandWrapper redirectOutputToNull () {
241- this .redirectOutputToNull = true ;
242- return this ;
243- }
244-
245- public CommandWrapper redirectErrorStream () {
246- this .redirectErrorStream = true ;
247- return this ;
248- }
249-
250- public CommandWrapper redirectErrorInherit () {
251- this .redirectErrorInherit = true ;
252- return this ;
253- }
254-
255- public CommandWrapper directory (Path directory ) {
256- this .directory = directory ;
257- return this ;
258- }
259-
260- public ProcessBuilder builder () {
261- ProcessBuilder builder = new ProcessBuilder (command );
262- if (redirectOutputToNull ) {
263- builder .redirectOutput (new File (nullFilename ));
264- }
265- if (directory != null ) {
266- builder .directory (directory .toFile ());
267- }
268- if (redirectErrorStream ) {
269- builder .redirectErrorStream (true );
270- }
271- if (redirectErrorInherit ) {
272- builder .redirectError (ProcessBuilder .Redirect .INHERIT );
273- }
274- return builder ;
275- }
276-
277- public Process start () throws IOException {
278- return builder ().start ();
279- }
280-
281- public static CommandWrapper create () {
282- return new CommandWrapper ();
283- }
284- }
285-
286119 public LocalGcdHelper (String projectId ) {
287120 this .projectId = projectId ;
288121 }
@@ -303,41 +136,19 @@ public void start() throws IOException, InterruptedException {
303136 File gcdFolder = gcdPath .toFile ();
304137 gcdFolder .deleteOnExit ();
305138
306- Path gcdExecutablePath ;
307- // If cloud is available we use it, otherwise we download and start gcd
308- if (INSTALLED_GCD_PATH == null ) {
309- downloadGcd ();
310- gcdExecutablePath = gcdPath .resolve (GCD_BASENAME );
311- } else {
312- gcdExecutablePath = INSTALLED_GCD_PATH ;
313- }
314- startGcd (gcdExecutablePath );
315- }
316-
317- private void downloadGcd () throws IOException {
318139 // check if we already have a local copy of the gcd utility and download it if not.
319140 File gcdZipFile = new File (System .getProperty ("java.io.tmpdir" ), GCD_FILENAME );
320141 if (!gcdZipFile .exists () || !MD5_CHECKSUM .equals (md5 (gcdZipFile ))) {
321- if (log .isLoggable (Level .FINE )) {
322- log .fine ("Fetching datastore emulator" );
323- }
324142 ReadableByteChannel rbc = Channels .newChannel (GCD_URL .openStream ());
325143 try (FileOutputStream fos = new FileOutputStream (gcdZipFile )) {
326144 fos .getChannel ().transferFrom (rbc , 0 , Long .MAX_VALUE );
327145 }
328- } else {
329- if (log .isLoggable (Level .FINE )) {
330- log .fine ("Using cached datastore emulator" );
331- }
332146 }
333147 // unzip the gcd
334148 try (ZipInputStream zipIn = new ZipInputStream (new FileInputStream (gcdZipFile ))) {
335- if (log .isLoggable (Level .FINE )) {
336- log .fine ("Unzipping datastore emulator" );
337- }
338149 ZipEntry entry = zipIn .getNextEntry ();
339150 while (entry != null ) {
340- File filePath = new File (gcdPath . toFile () , entry .getName ());
151+ File filePath = new File (gcdFolder , entry .getName ());
341152 if (!entry .isDirectory ()) {
342153 extractFile (zipIn , filePath );
343154 } else {
@@ -347,46 +158,38 @@ private void downloadGcd() throws IOException {
347158 entry = zipIn .getNextEntry ();
348159 }
349160 }
350- }
351-
352- private void startGcd (Path executablePath ) throws IOException , InterruptedException {
353161 // cleanup any possible data for the same project
354- File datasetFolder = new File (gcdPath . toFile (), projectId );
162+ File datasetFolder = new File (gcdFolder , GCD + '/' + projectId );
355163 deleteRecurse (datasetFolder .toPath ());
356164
357- // Get path to cmd executable
358- Path gcdAbsolutePath ;
165+ // create the datastore for the project
166+ ProcessBuilder processBuilder = new ProcessBuilder ()
167+ .redirectError (ProcessBuilder .Redirect .INHERIT )
168+ .directory (new File (gcdFolder , GCD ));
359169 if (isWindows ()) {
360- gcdAbsolutePath = executablePath .toAbsolutePath ().resolve ("gcd.cmd" );
170+ processBuilder .command ("cmd" , "/C" , "gcd.cmd" , "create" , "-p" , projectId , projectId );
171+ processBuilder .redirectOutput (new File ("NULL:" ));
361172 } else {
362- gcdAbsolutePath = executablePath .toAbsolutePath ().resolve ("gcd.sh" );
173+ processBuilder .redirectOutput (new File ("/dev/null" ));
174+ processBuilder .command ("bash" , "gcd.sh" , "create" , "-p" , projectId , projectId );
363175 }
364176
365- // create the datastore for the project
366- if (log .isLoggable (Level .FINE )) {
367- log .log (Level .FINE , "Creating datastore for the project: {0}" , projectId );
368- }
369- Process createProcess =
370- CommandWrapper .create ()
371- .command (gcdAbsolutePath .toString (), "create" , "-p" , projectId , projectId )
372- .redirectErrorInherit ()
373- .directory (gcdPath )
374- .redirectOutputToNull ()
375- .start ();
376- createProcess .waitFor ();
177+ Process temp = processBuilder .start ();
178+ temp .waitFor ();
377179
378180 // start the datastore for the project
379- if (log .isLoggable (Level .FINE )) {
380- log .log (Level .FINE , "Starting datastore emulator for the project: {0}" , projectId );
181+ processBuilder = new ProcessBuilder ()
182+ .directory (new File (gcdFolder , GCD ))
183+ .redirectErrorStream (true );
184+ if (isWindows ()) {
185+ processBuilder .command ("cmd" , "/C" , "gcd.cmd" , "start" , "--testing" ,
186+ "--allow_remote_shutdown" , projectId );
187+ } else {
188+ processBuilder .command ("bash" , "gcd.sh" , "start" , "--testing" , "--allow_remote_shutdown" ,
189+ projectId );
381190 }
382- Process startProcess =
383- CommandWrapper .create ()
384- .command (gcdAbsolutePath .toString (), "start" , "--testing" , "--allow_remote_shutdown" ,
385- projectId )
386- .directory (gcdPath )
387- .redirectErrorStream ()
388- .start ();
389- processReader = ProcessStreamReader .start (startProcess , "Dev App Server is now running" );
191+ temp = processBuilder .start ();
192+ processReader = ProcessStreamReader .start (temp , "Dev App Server is now running" );
390193 }
391194
392195 private static String md5 (File gcdZipFile ) throws IOException {
@@ -399,7 +202,7 @@ private static String md5(File gcdZipFile) throws IOException {
399202 md5 .update (bytes , 0 , len );
400203 }
401204 }
402- return String .format ("%032x" , new BigInteger (1 , md5 .digest ()));
205+ return String .format ("%032x" ,new BigInteger (1 , md5 .digest ()));
403206 } catch (NoSuchAlgorithmException e ) {
404207 throw new IOException (e );
405208 }
@@ -509,7 +312,7 @@ public static boolean isActive(String projectId) {
509312 urlBuilder .append ("/datastore/v1beta2/datasets/" ).append (projectId ).append ("/lookup" );
510313 URL url = new URL (urlBuilder .toString ());
511314 try (BufferedReader reader =
512- new BufferedReader (new InputStreamReader (url .openStream (), UTF_8 ))) {
315+ new BufferedReader (new InputStreamReader (url .openStream (), UTF_8 ))) {
513316 return "Valid RPC" .equals (reader .readLine ());
514317 }
515318 } catch (IOException ignore ) {
0 commit comments