3434import java .util .Random ;
3535import java .util .Set ;
3636import java .util .concurrent .ConcurrentHashMap ;
37+ import java .util .logging .Level ;
3738import java .util .logging .Logger ;
3839import java .util .zip .GZIPInputStream ;
3940
@@ -51,6 +52,19 @@ public class LocalResourceManagerHelper {
5152 private static final Random PROJECT_NUMBER_GENERATOR = new Random ();
5253 private static final String VERSION = "v1beta1" ;
5354 private static final String CONTEXT = "/" + VERSION + "/projects" ;
55+ private static final URI BASE_CONTEXT ;
56+ private static final Set <String > SUPPORTED_COMPRESSION_ENCODINGS =
57+ ImmutableSet .of ("gzip" , "x-gzip" );
58+
59+ static {
60+ try {
61+ BASE_CONTEXT = new URI (CONTEXT );
62+ } catch (URISyntaxException e ) {
63+ log .log (Level .WARNING , "URI." , e );
64+ throw new RuntimeException (
65+ "Could not initialize LocalResourceManagerHelper due to URISyntaxException." , e );
66+ }
67+ }
5468
5569 // see https://cloud.google.com/resource-manager/reference/rest/v1beta1/projects
5670 private static final Set <Character > PERMISSIBLE_PROJECT_NAME_PUNCTUATION =
@@ -60,7 +74,7 @@ public class LocalResourceManagerHelper {
6074 private final ConcurrentHashMap <String , Project > projects = new ConcurrentHashMap <>();
6175 private final int port ;
6276
63- static class Response {
77+ private static class Response {
6478 private final int code ;
6579 private final String body ;
6680
@@ -78,7 +92,7 @@ String body() {
7892 }
7993 }
8094
81- enum Error {
95+ private enum Error {
8296 ALREADY_EXISTS (409 , "global" , "alreadyExists" , "ALREADY_EXISTS" ),
8397 PERMISSION_DENIED (403 , "global" , "forbidden" , "PERMISSION_DENIED" ),
8498 // change failed precondition error code to 412 when #440 is fixed
@@ -126,37 +140,27 @@ private class RequestHandler implements HttpHandler {
126140 @ Override
127141 public void handle (HttpExchange exchange ) {
128142 // see https://cloud.google.com/resource-manager/reference/rest/
129- Response response = null ;
130- URI baseContext = null ;
131- try {
132- baseContext = new URI (CONTEXT );
133- } catch (URISyntaxException e ) {
134- writeResponse (
135- exchange ,
136- Error .INTERNAL_ERROR .response (
137- "URI syntax exception when constructing URI from the path '" + CONTEXT + "'" ));
138- return ;
139- }
140- String path = baseContext .relativize (exchange .getRequestURI ()).getPath ();
143+ Response response ;
144+ String path = BASE_CONTEXT .relativize (exchange .getRequestURI ()).getPath ();
141145 String requestMethod = exchange .getRequestMethod ();
142146 try {
143147 switch (requestMethod ) {
144148 case "POST" :
145- if (path .contains (":undelete" )) {
146- response = undelete (projectIdFromURI (path ));
149+ if (path .endsWith (":undelete" )) {
150+ response = undelete (projectIdFromUri (path ));
147151 } else {
148152 String requestBody =
149153 decodeContent (exchange .getRequestHeaders (), exchange .getRequestBody ());
150154 response = create (jsonFactory .fromString (requestBody , Project .class ));
151155 }
152156 break ;
153157 case "DELETE" :
154- response = delete (projectIdFromURI (path ));
158+ response = delete (projectIdFromUri (path ));
155159 break ;
156160 case "GET" :
157161 if (!path .isEmpty ()) {
158162 response =
159- get (projectIdFromURI (path ), parseFields (exchange .getRequestURI ().getQuery ()));
163+ get (projectIdFromUri (path ), parseFields (exchange .getRequestURI ().getQuery ()));
160164 } else {
161165 response = list (parseListOptions (exchange .getRequestURI ().getQuery ()));
162166 }
@@ -165,10 +169,12 @@ public void handle(HttpExchange exchange) {
165169 String requestBody =
166170 decodeContent (exchange .getRequestHeaders (), exchange .getRequestBody ());
167171 response =
168- replace (projectIdFromURI (path ), jsonFactory .fromString (requestBody , Project .class ));
172+ replace (projectIdFromUri (path ), jsonFactory .fromString (requestBody , Project .class ));
169173 break ;
170174 default :
171- response = Error .BAD_REQUEST .response ("The server could not understand the request." );
175+ response = Error .BAD_REQUEST .response (
176+ "The server could not understand the following request URI: " + requestMethod + " "
177+ + path );
172178 }
173179 } catch (IOException e ) {
174180 response = Error .BAD_REQUEST .response (e .getMessage ());
@@ -185,7 +191,7 @@ private static void writeResponse(HttpExchange exchange, Response response) {
185191 outputStream .write (response .body ().getBytes (StandardCharsets .UTF_8 ));
186192 outputStream .close ();
187193 } catch (IOException e ) {
188- log .info ( "IOException encountered when sending response." );
194+ log .log ( Level . WARNING , "IOException encountered when sending response." , e );
189195 }
190196 }
191197
@@ -194,10 +200,12 @@ private static String decodeContent(Headers headers, InputStream inputStream) th
194200 InputStream input = inputStream ;
195201 try {
196202 if (contentEncoding != null && !contentEncoding .isEmpty ()) {
197- if (contentEncoding .get (0 ).equals ("gzip" ) || contentEncoding .get (0 ).equals ("x-gzip" )) {
203+ String encoding = contentEncoding .get (0 );
204+ if (SUPPORTED_COMPRESSION_ENCODINGS .contains (encoding )) {
198205 input = new GZIPInputStream (inputStream );
199- } else if (!contentEncoding .equals ("identity" )) {
200- throw new IOException ("The request has an unsupported HTTP content encoding." );
206+ } else if (!encoding .equals ("identity" )) {
207+ throw new IOException (
208+ "The request has the following unsupported HTTP content encoding: " + encoding );
201209 }
202210 }
203211 return new String (ByteStreams .toByteArray (input ), StandardCharsets .UTF_8 );
@@ -206,7 +214,7 @@ private static String decodeContent(Headers headers, InputStream inputStream) th
206214 }
207215 }
208216
209- private static String projectIdFromURI (String path ) throws IOException {
217+ private static String projectIdFromUri (String path ) throws IOException {
210218 if (path .isEmpty ()) {
211219 throw new IOException ("The URI path '" + path + "' doesn't have a project ID." );
212220 }
@@ -286,7 +294,7 @@ private static final boolean isValidIdOrLabel(String value, int minLength, int m
286294 return false ;
287295 }
288296 }
289- if (value .length () > 0 && (!Character .isLetter (value .charAt (0 )) || value .endsWith ("-" ))) {
297+ if (! value .isEmpty () && (!Character .isLetter (value .charAt (0 )) || value .endsWith ("-" ))) {
290298 return false ;
291299 }
292300 return value .length () >= minLength && value .length () <= maxLength ;
@@ -316,7 +324,9 @@ Response create(Project project) {
316324 Response delete (String projectId ) {
317325 Project project = projects .get (projectId );
318326 if (project == null ) {
319- // when possible, change this to 404 (#440)
327+ // Currently the service returns 403 Permission Denied when trying to delete a project that
328+ // doesn't exist. Here we mimic this behavior, but this line should be changed to throw a
329+ // 404 Not Found error when the service fixes this (#440).
320330 return Error .PERMISSION_DENIED .response (
321331 "Error when deleting " + projectId + " because the project was not found." );
322332 }
@@ -331,7 +341,9 @@ Response delete(String projectId) {
331341
332342 Response get (String projectId , String [] fields ) {
333343 if (!projects .containsKey (projectId )) {
334- // when possible, change this to 404 (#440)
344+ // Currently the service returns 403 Permission Denied when trying to get a project that
345+ // doesn't exist. Here we mimic this behavior, but this line should be changed to throw a
346+ // 404 Not Found error when the service fixes this (#440).
335347 return Error .PERMISSION_DENIED .response ("Project " + projectId + " not found." );
336348 }
337349 Project project = projects .get (projectId );
@@ -385,16 +397,17 @@ private static boolean includeProject(Project project, String[] filters) {
385397 }
386398 for (String filter : filters ) {
387399 String [] filterEntry = filter .toLowerCase ().split (":" );
388- if ("id" .equals (filterEntry [0 ])) {
400+ String filterType = filterEntry [0 ];
401+ if ("id" .equals (filterType )) {
389402 if (!satisfiesFilter (project .getProjectId (), filterEntry [1 ])) {
390403 return false ;
391404 }
392- } else if ("name" .equals (filterEntry [ 0 ] )) {
405+ } else if ("name" .equals (filterType )) {
393406 if (!satisfiesFilter (project .getName (), filterEntry [1 ])) {
394407 return false ;
395408 }
396- } else if (filterEntry [ 0 ] .startsWith ("labels" )) {
397- String labelKey = filterEntry [ 0 ]. split ( " \\ ." )[ 1 ] ;
409+ } else if (filterType .startsWith ("labels. " )) {
410+ String labelKey = filterType . substring ( "labels." . length ()) ;
398411 if (project .getLabels () != null ) {
399412 String labelValue = project .getLabels ().get (labelKey );
400413 if (!satisfiesFilter (labelValue , filterEntry [1 ])) {
@@ -410,7 +423,7 @@ private static boolean satisfiesFilter(String projectValue, String filterValue)
410423 if (projectValue == null ) {
411424 return false ;
412425 }
413- return "*" .equals (filterValue ) ? true : filterValue .equals (projectValue .toLowerCase ());
426+ return "*" .equals (filterValue ) || filterValue .equals (projectValue .toLowerCase ());
414427 }
415428
416429 private static Project extractFields (Project fullProject , String [] fields ) {
@@ -449,7 +462,9 @@ private static Project extractFields(Project fullProject, String[] fields) {
449462 Response replace (String projectId , Project project ) {
450463 Project originalProject = projects .get (projectId );
451464 if (originalProject == null ) {
452- // when possible, change this to 404 (#440)
465+ // Currently the service returns 403 Permission Denied when trying to replace a project that
466+ // doesn't exist. Here we mimic this behavior, but this line should be changed to throw a
467+ // 404 Not Found error when the service fixes this (#440).
453468 return Error .PERMISSION_DENIED .response (
454469 "Error when replacing " + projectId + " because the project was not found." );
455470 } else if (!originalProject .getLifecycleState ().equals ("ACTIVE" )) {
@@ -474,7 +489,9 @@ Response undelete(String projectId) {
474489 Project project = projects .get (projectId );
475490 Response response ;
476491 if (project == null ) {
477- // when possible, change this to 404 (#440)
492+ // Currently the service returns 403 Permission Denied when trying to undelete a project that
493+ // doesn't exist. Here we mimic this behavior, but this line should be changed to throw a
494+ // 404 Not Found error when the service fixes this (#440).
478495 response = Error .PERMISSION_DENIED .response (
479496 "Error when undeleting " + projectId + " because the project was not found." );
480497 } else if (!project .getLifecycleState ().equals ("DELETE_REQUESTED" )) {
@@ -488,9 +505,8 @@ Response undelete(String projectId) {
488505 }
489506
490507 private LocalResourceManagerHelper () {
491- InetSocketAddress addr = new InetSocketAddress (0 );
492508 try {
493- server = HttpServer .create (addr , 0 );
509+ server = HttpServer .create (new InetSocketAddress ( 0 ) , 0 );
494510 port = server .getAddress ().getPort ();
495511 server .createContext (CONTEXT , new RequestHandler ());
496512 } catch (IOException e ) {
@@ -550,9 +566,9 @@ public boolean changeLifecycleState(String projectId, String lifecycleState) {
550566 * <p>This method can be used to fully remove a project (to mimic when the server completely
551567 * deletes a project).
552568 *
553- * @return true if the project was successfully deleted, false otherwise .
569+ * @return true if the project was successfully deleted, false if the project didn't exist .
554570 */
555571 public boolean removeProject (String projectId ) {
556- return projects .remove (checkNotNull (projectId )) != null ? true : false ;
572+ return projects .remove (checkNotNull (projectId )) != null ;
557573 }
558574}
0 commit comments