This is an automated email from the ASF dual-hosted git repository.

heneveld pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/brooklyn-server.git


The following commit(s) were added to refs/heads/master by this push:
     new bfe0677f24 Change config/sensor to return 200, 204, or 404. Tidy other 
API documentation bits.
bfe0677f24 is described below

commit bfe0677f247d15f47f20bb86fa23e1b2b9db87f1
Author: Alex Heneveld <[email protected]>
AuthorDate: Mon Jun 17 13:22:39 2024 +0100

    Change config/sensor to return 200, 204, or 404. Tidy other API 
documentation bits.
    
                @ApiResponse(code = 200, message = "OK"),
                @ApiResponse(code = 204, message = "No Content. The config is 
known, but unset."),
                @ApiResponse(code = 404, message = "Could not find application, 
entity or config key"),
    
    The behavior is now as above.  404 is returned if the sensor (or config) is 
not defined, and 204 if it is defined but unset.
    200 with null or empty string is only returned if the sensor/config is 
explictly set as such.
    
    Additionally, some discrepancies where Accepted was used instead of Created 
or sometimes OK has been fixed,
    and OK is returned in some places where ACCEPTED or CREATED had been where 
it makes more sense to do so.
---
 .../org/apache/brooklyn/rest/api/ActivityApi.java  |  3 ++-
 .../apache/brooklyn/rest/api/ApplicationApi.java   |  2 +-
 .../org/apache/brooklyn/rest/api/BundleApi.java    |  9 ++++---
 .../org/apache/brooklyn/rest/api/CatalogApi.java   |  4 +--
 .../org/apache/brooklyn/rest/api/EffectorApi.java  |  5 ++--
 .../org/apache/brooklyn/rest/api/EntityApi.java    | 16 ++++++------
 .../apache/brooklyn/rest/api/EntityConfigApi.java  | 10 +++++---
 .../org/apache/brooklyn/rest/api/LogoutApi.java    |  7 +++---
 .../org/apache/brooklyn/rest/api/ScriptApi.java    |  2 +-
 .../org/apache/brooklyn/rest/api/SensorApi.java    |  9 ++++---
 .../org/apache/brooklyn/rest/api/ServerApi.java    | 14 +++++------
 .../org/apache/brooklyn/rest/domain/ApiError.java  | 15 +++++++++++
 .../brooklyn/rest/resources/EffectorResource.java  |  7 +++++-
 .../rest/resources/EntityConfigResource.java       | 20 +++++++++++++--
 .../brooklyn/rest/resources/SensorResource.java    | 29 +++++++++++++++++++---
 .../brooklyn/rest/util/EntityAttributesUtils.java  | 21 ++++++++++++++++
 .../brooklyn/rest/util/WebResourceUtils.java       | 14 +++++++----
 .../rest/resources/ApplicationResourceTest.java    |  4 +--
 .../rest/resources/EffectorResourceTest.java       |  6 ++---
 .../rest/resources/SensorResourceTest.java         |  7 ++++--
 .../entity/brooklynnode/DeployBlueprintTest.java   |  5 ++--
 21 files changed, 152 insertions(+), 57 deletions(-)

diff --git 
a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/ActivityApi.java 
b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/ActivityApi.java
index aabc2d2bd5..8679528957 100644
--- a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/ActivityApi.java
+++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/ActivityApi.java
@@ -44,7 +44,8 @@ public interface ActivityApi {
     @Path("/{task}")
     @ApiOperation(value = "Fetch task details", response = 
org.apache.brooklyn.rest.domain.TaskSummary.class)
     @ApiResponses(value = {
-            @ApiResponse(code = 200, message = "OK"),
+            @ApiResponse(code = 200, message = "OK. The task is completed and 
details are returned."),
+            @ApiResponse(code = 202, message = "Accepted. The task is still 
running but it is not permitted to wait any longer so current details are 
returned."),
             @ApiResponse(code = 400, message = "Bad Request"),
             @ApiResponse(code = 401, message = "Unauthorized"),
             @ApiResponse(code = 404, message = "Could not find task"),
diff --git 
a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/ApplicationApi.java 
b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/ApplicationApi.java
index bd9ed5b473..e3f9f2e6fa 100644
--- 
a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/ApplicationApi.java
+++ 
b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/ApplicationApi.java
@@ -384,7 +384,7 @@ public interface ApplicationApi {
             response = org.apache.brooklyn.rest.domain.TaskSummary.class
     )
     @ApiResponses(value = {
-            @ApiResponse(code = 200, message = "OK"),
+            @ApiResponse(code = 202, message = "Accepted. The application is 
submitted for deletion."),
             @ApiResponse(code = 400, message = "Bad Request"),
             @ApiResponse(code = 401, message = "Unauthorized"),
             @ApiResponse(code = 404, message = "Application not found"),
diff --git 
a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/BundleApi.java 
b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/BundleApi.java
index 033533b5ac..b9c8103942 100644
--- a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/BundleApi.java
+++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/BundleApi.java
@@ -247,7 +247,8 @@ public interface BundleApi {
     @Consumes("application/deprecated-yaml")
     @ApiOperation(value = "(deprecated; use same endpoint accepting optional 
format)", hidden = true, response = BundleInstallationRestResult.class)
     @ApiResponses(value = {
-            @ApiResponse(code = 201, message = "Accepted"),
+            @ApiResponse(code = 200, message = "OK. The bundle is already 
installed."),
+            @ApiResponse(code = 201, message = "Created. The bundle has been 
installed."),
             @ApiResponse(code = 400, message = "Bad Request"),
             @ApiResponse(code = 401, message = "Unauthorized"),
             @ApiResponse(code = 404, message = "Application, entity, or sensor 
not found"),
@@ -266,7 +267,8 @@ public interface BundleApi {
     @Consumes({"application/deprecated-zip"})
     @ApiOperation(value = "(deprecated; use same endpoint accepting optional 
format)", hidden = true, response = BundleInstallationRestResult.class)
     @ApiResponses(value = {
-            @ApiResponse(code = 201, message = "Accepted"),
+            @ApiResponse(code = 200, message = "OK. The bundle is already 
installed."),
+            @ApiResponse(code = 201, message = "Created. The bundle has been 
installed."),
             @ApiResponse(code = 400, message = "Bad Request"),
             @ApiResponse(code = 401, message = "Unauthorized"),
             @ApiResponse(code = 404, message = "Application, entity, or sensor 
not found"),
@@ -291,7 +293,8 @@ public interface BundleApi {
                     + "a ZIP/JAR containing a catalog.bom and optional other 
items",
             response = BundleInstallationRestResult.class)
     @ApiResponses(value = {
-            @ApiResponse(code = 201, message = "Catalog items added 
successfully"),
+            @ApiResponse(code = 200, message = "OK. The bundle is already 
installed."),
+            @ApiResponse(code = 201, message = "Created. The bundle has been 
installed."),
             @ApiResponse(code = 400, message = "Error processing the given 
archive, or the catalog.bom is invalid"),
             @ApiResponse(code = 401, message = "Unauthorized"),
             @ApiResponse(code = 500, message = "Internal Server Error")
diff --git 
a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/CatalogApi.java 
b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/CatalogApi.java
index 89993f8d11..3fb25bb17b 100644
--- a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/CatalogApi.java
+++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/CatalogApi.java
@@ -155,8 +155,8 @@ public interface CatalogApi {
                     + "with a message, bundle, and code.",
             response = String.class)
     @ApiResponses(value = {
-            @ApiResponse(code = 200, message = "OK"),
-            @ApiResponse(code = 201, message = "Catalog items added 
successfully"),
+            @ApiResponse(code = 200, message = "OK. The bundle is already 
installed."),
+            @ApiResponse(code = 201, message = "Created. The bundle has been 
installed."),
             @ApiResponse(code = 400, message = "Error processing the given 
archive, or the catalog.bom is invalid"),
             @ApiResponse(code = 401, message = "Unauthorized"),
             @ApiResponse(code = 404, message = "Application or entity 
missing"),
diff --git 
a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/EffectorApi.java 
b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/EffectorApi.java
index ca76932aa6..56fb0bd704 100644
--- a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/EffectorApi.java
+++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/EffectorApi.java
@@ -68,11 +68,12 @@ public interface EffectorApi {
     @ApiOperation(value = "Trigger an effector",
             notes="Returns the return value (status 200) if it completes, or 
an activity task ID (status 202) if it times out", response = String.class)
     @ApiResponses(value = {
-            @ApiResponse(code = 202, message = "Accepted"),
+            @ApiResponse(code = 200, message = "OK. The effector run. The 
result is returned."),
+            @ApiResponse(code = 202, message = "Accepted. The effector is 
running. The task object is returned."),
             @ApiResponse(code = 400, message = "Bad Request"),
             @ApiResponse(code = 401, message = "Unauthorized"),
             @ApiResponse(code = 404, message = "Could not find application, 
entity or effector"),
-            @ApiResponse(code = 500, message = "Internal Server Error")
+            @ApiResponse(code = 500, message = "Internal Server Error. The 
effector may have returned an error.")
     })
     @Consumes({MediaType.APPLICATION_JSON, 
MediaType.APPLICATION_FORM_URLENCODED})
     public Response invoke(
diff --git 
a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/EntityApi.java 
b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/EntityApi.java
index b1871c7af4..c952e7815b 100644
--- a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/EntityApi.java
+++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/EntityApi.java
@@ -114,7 +114,7 @@ public interface EntityApi {
             "text/yaml", "text/x-yaml", "application/yaml", 
MediaType.APPLICATION_JSON})
     @Path("/{entity}/children")
     @ApiResponses(value = {
-            @ApiResponse(code = 201, message = "Accepted"),
+            @ApiResponse(code = 201, message = "Created"),
             @ApiResponse(code = 400, message = "Bad Request"),
             @ApiResponse(code = 401, message = "Unauthorized"),
             @ApiResponse(code = 404, message = "Application or entity 
missing"),
@@ -166,7 +166,7 @@ public interface EntityApi {
     @Path("/{entity}/activities/{task}")
     @ApiOperation(value = "Fetch task details", response = 
org.apache.brooklyn.rest.domain.TaskSummary.class)
     @ApiResponses(value = {
-            @ApiResponse(code = 201, message = "Accepted"),
+            @ApiResponse(code = 200, message = "OK"),
             @ApiResponse(code = 400, message = "Bad Request"),
             @ApiResponse(code = 401, message = "Unauthorized"),
             @ApiResponse(code = 404, message = "Could not find application, 
entity or task"),
@@ -224,7 +224,7 @@ public interface EntityApi {
     @Path("/{entity}/tag/add")
     @ApiOperation(value = "Add a tag on this entity")
     @ApiResponses(value = {
-            @ApiResponse(code = 201, message = "Accepted"),
+            @ApiResponse(code = 204, message = "No Content"),
             @ApiResponse(code = 400, message = "Bad Request"),
             @ApiResponse(code = 401, message = "Unauthorized"),
             @ApiResponse(code = 404, message = "Application or entity 
missing"),
@@ -239,7 +239,7 @@ public interface EntityApi {
     @Path("/{entity}/tag/delete")
     @ApiOperation(value = "Delete a tag on this entity, returning whether the 
tag was found (and deleted)")
     @ApiResponses(value = {
-            @ApiResponse(code = 201, message = "Accepted"),
+            @ApiResponse(code = 200, message = "OK"),
             @ApiResponse(code = 400, message = "Bad Request"),
             @ApiResponse(code = 401, message = "Unauthorized"),
             @ApiResponse(code = 404, message = "Application or entity 
missing"),
@@ -254,7 +254,7 @@ public interface EntityApi {
     @Path("/{entity}/tag/upsert/{tagKey}")
     @ApiOperation(value = "Inserts a tag which is a single-key map with the 
given key (path parameter) and value (post body), removing any existing tag 
matching the key")
     @ApiResponses(value = {
-            @ApiResponse(code = 201, message = "Accepted"),
+            @ApiResponse(code = 204, message = "No Content"),
             @ApiResponse(code = 400, message = "Bad Request"),
             @ApiResponse(code = 401, message = "Unauthorized"),
             @ApiResponse(code = 404, message = "Application or entity 
missing"),
@@ -286,7 +286,7 @@ public interface EntityApi {
             value = "Rename an entity"
     )
     @ApiResponses(value = {
-            @ApiResponse(code = 201, message = "Accepted"),
+            @ApiResponse(code = 200, message = "OK"),
             @ApiResponse(code = 400, message = "Bad Request"),
             @ApiResponse(code = 401, message = "Unauthorized"),
             @ApiResponse(code = 404, message = "Undefined application or 
entity"),
@@ -304,7 +304,7 @@ public interface EntityApi {
             response = org.apache.brooklyn.rest.domain.TaskSummary.class
     )
     @ApiResponses(value = {
-            @ApiResponse(code = 201, message = "Accepted"),
+            @ApiResponse(code = 202, message = "Accepted. The entity is 
submitted for expunging."),
             @ApiResponse(code = 400, message = "Bad Request"),
             @ApiResponse(code = 401, message = "Unauthorized"),
             @ApiResponse(code = 404, message = "Application or entity 
missing"),
@@ -476,7 +476,7 @@ public interface EntityApi {
             "text/yaml", "text/x-yaml", "application/yaml", 
MediaType.APPLICATION_JSON})
     @Path("/{entity}/workflows")
     @ApiResponses(value = {
-            @ApiResponse(code = 201, message = "Accepted"),
+            @ApiResponse(code = 200, message = "OK"),
             @ApiResponse(code = 400, message = "Bad Request"),
             @ApiResponse(code = 401, message = "Unauthorized"),
             @ApiResponse(code = 404, message = "Application or entity 
missing"),
diff --git 
a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/EntityConfigApi.java 
b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/EntityConfigApi.java
index 4168c0766f..f3f2c9c35b 100644
--- 
a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/EntityConfigApi.java
+++ 
b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/EntityConfigApi.java
@@ -96,10 +96,11 @@ public interface EntityConfigApi {
     @Path("/{config}")
     @ApiOperation(value = "Fetch config value (json)", response = Object.class)
     @ApiResponses(value = {
-            @ApiResponse(code = 200, message = "OK"),
+            @ApiResponse(code = 200, message = "OK. The sensor value is 
returned."),
+            @ApiResponse(code = 204, message = "No Content. The sensor is 
known, but unset."),
             @ApiResponse(code = 400, message = "Bad Request"),
             @ApiResponse(code = 401, message = "Unauthorized"),
-            @ApiResponse(code = 404, message = "Could not find application, 
entity or config key"),
+            @ApiResponse(code = 404, message = "Application, entity, or config 
not found"),
             @ApiResponse(code = 500, message = "Internal Server Error")
     })
     @Produces(MediaType.APPLICATION_JSON)
@@ -128,6 +129,7 @@ public interface EntityConfigApi {
     @ApiOperation(value = "Fetch config value (text/plain)", response = 
String.class)
     @ApiResponses(value = {
             @ApiResponse(code = 200, message = "OK"),
+            @ApiResponse(code = 204, message = "No Content. The config is 
known, but unset."),
             @ApiResponse(code = 400, message = "Bad Request"),
             @ApiResponse(code = 401, message = "Unauthorized"),
             @ApiResponse(code = 404, message = "Could not find application, 
entity or config key"),
@@ -156,7 +158,7 @@ public interface EntityConfigApi {
     @POST
     @ApiOperation(value = "Manually set multiple config values")
     @ApiResponses(value = {
-            @ApiResponse(code = 201, message = "Accepted"),
+            @ApiResponse(code = 204, message = "No Content"),
             @ApiResponse(code = 400, message = "Bad Request"),
             @ApiResponse(code = 401, message = "Unauthorized"),
             @ApiResponse(code = 404, message = "Could not find application or 
entity"),
@@ -177,7 +179,7 @@ public interface EntityConfigApi {
     @Path("/{config}")
     @ApiOperation(value = "Manually set a config value")
     @ApiResponses(value = {
-            @ApiResponse(code = 201, message = "Accepted"),
+            @ApiResponse(code = 204, message = "No Content"),
             @ApiResponse(code = 400, message = "Bad Request"),
             @ApiResponse(code = 401, message = "Unauthorized"),
             @ApiResponse(code = 404, message = "Could not find application, 
entity or config key"),
diff --git 
a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/LogoutApi.java 
b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/LogoutApi.java
index e3cc4e9e7d..6d7937df3e 100644
--- a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/LogoutApi.java
+++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/LogoutApi.java
@@ -33,7 +33,7 @@ public interface LogoutApi {
     @POST
     @ApiOperation(value = "Logout and clean session")
     @ApiResponses(value = {
-            @ApiResponse(code = 201, message = "Accepted"),
+            @ApiResponse(code = 200, message = "OK"),
             @ApiResponse(code = 400, message = "Bad Request"),
             @ApiResponse(code = 401, message = "Unauthorized"),
             @ApiResponse(code = 500, message = "Internal Server Error")
@@ -56,9 +56,8 @@ public interface LogoutApi {
     @Path("/unauthorize")
     @ApiOperation(value = "Return UNAUTHORIZED 401 response, but without 
disabling the session [deprecated in favour of /logout query parameter]")
     @ApiResponses(value = {
-            @ApiResponse(code = 201, message = "Accepted"),
             @ApiResponse(code = 400, message = "Bad Request"),
-            @ApiResponse(code = 401, message = "Unauthorized"),
+            @ApiResponse(code = 401, message = "Unauthorized. Normal 
response."),
             @ApiResponse(code = 500, message = "Internal Server Error")
     })
     Response unAuthorize();
@@ -69,7 +68,7 @@ public interface LogoutApi {
     @Path("/{user}")
     @ApiOperation(value = "Logout and clean session if matching user logged in 
(deprecated; username should now be omitted)")
     @ApiResponses(value = {
-            @ApiResponse(code = 201, message = "Accepted"),
+            @ApiResponse(code = 200, message = "OK"),
             @ApiResponse(code = 400, message = "Bad Request"),
             @ApiResponse(code = 401, message = "Unauthorized"),
             @ApiResponse(code = 500, message = "Internal Server Error")
diff --git 
a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/ScriptApi.java 
b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/ScriptApi.java
index f0b11b0764..91bc18cd94 100644
--- a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/ScriptApi.java
+++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/ScriptApi.java
@@ -48,7 +48,7 @@ public interface ScriptApi {
     @ApiOperation(value = "Execute a groovy script",
             response = org.apache.brooklyn.rest.domain.SensorSummary.class)
     @ApiResponses(value = {
-            @ApiResponse(code = 201, message = "Accepted"),
+            @ApiResponse(code = 200, message = "OK"),
             @ApiResponse(code = 400, message = "Bad Request"),
             @ApiResponse(code = 401, message = "Unauthorized"),
             @ApiResponse(code = 500, message = "Internal Server Error")
diff --git 
a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/SensorApi.java 
b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/SensorApi.java
index 5f1970908a..03abc82502 100644
--- a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/SensorApi.java
+++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/SensorApi.java
@@ -91,7 +91,8 @@ public interface SensorApi {
     @ApiOperation(value = "Fetch sensor value (json)", response = Object.class)
     @Produces({MediaType.APPLICATION_JSON})
     @ApiResponses(value = {
-            @ApiResponse(code = 200, message = "OK"),
+            @ApiResponse(code = 200, message = "OK. The sensor value is 
returned."),
+            @ApiResponse(code = 204, message = "No Content. The sensor is 
known, but unset."),
             @ApiResponse(code = 400, message = "Bad Request"),
             @ApiResponse(code = 401, message = "Unauthorized"),
             @ApiResponse(code = 404, message = "Application, entity, or sensor 
not found"),
@@ -142,7 +143,7 @@ public interface SensorApi {
     @POST
     @ApiOperation(value = "Manually set multiple sensor values")
     @ApiResponses(value = {
-            @ApiResponse(code = 201, message = "Accepted"),
+            @ApiResponse(code = 204, message = "No Content"),
             @ApiResponse(code = 400, message = "Bad Request"),
             @ApiResponse(code = 401, message = "Unauthorized"),
             @ApiResponse(code = 404, message = "Application, entity, or sensor 
not found"),
@@ -161,7 +162,7 @@ public interface SensorApi {
     @Path("/{sensor}")
     @ApiOperation(value = "Manually set a sensor value")
     @ApiResponses(value = {
-            @ApiResponse(code = 201, message = "Accepted"),
+            @ApiResponse(code = 200, message = "OK"),
             @ApiResponse(code = 400, message = "Bad Request"),
             @ApiResponse(code = 401, message = "Unauthorized"),
             @ApiResponse(code = 404, message = "Application, entity, or sensor 
not found"),
@@ -181,7 +182,7 @@ public interface SensorApi {
     @Path("/{sensor}")
     @ApiOperation(value = "Manually clear a sensor value")
     @ApiResponses(value = {
-            @ApiResponse(code = 200, message = "Accepted"),
+            @ApiResponse(code = 200, message = "OK"),
             @ApiResponse(code = 400, message = "Bad Request"),
             @ApiResponse(code = 401, message = "Unauthorized"),
             @ApiResponse(code = 404, message = "Application, entity, or sensor 
not found"),
diff --git 
a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/ServerApi.java 
b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/ServerApi.java
index a805bca22a..d90e32d2c0 100644
--- a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/ServerApi.java
+++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/ServerApi.java
@@ -59,7 +59,7 @@ public interface ServerApi {
     @Path("/properties/reload")
     @ApiOperation(value = "Reload brooklyn.properties")
     @ApiResponses(value = {
-            @ApiResponse(code = 201, message = "Accepted"),
+            @ApiResponse(code = 204, message = "No Content"),
             @ApiResponse(code = 400, message = "Bad Request"),
             @ApiResponse(code = 401, message = "Unauthorized"),
             @ApiResponse(code = 500, message = "Internal Server Error")
@@ -71,7 +71,7 @@ public interface ServerApi {
     @ApiOperation(value = "Terminate this Brooklyn server instance")
     @Consumes({MediaType.APPLICATION_FORM_URLENCODED})
     @ApiResponses(value = {
-            @ApiResponse(code = 201, message = "Accepted"),
+            @ApiResponse(code = 204, message = "No Content"),
             @ApiResponse(code = 400, message = "Bad Request"),
             @ApiResponse(code = 401, message = "Unauthorized"),
             @ApiResponse(code = 500, message = "Internal Server Error")
@@ -204,7 +204,7 @@ public interface ServerApi {
     @ApiOperation(value = "Changes the HA state of this management node")
     @Consumes({MediaType.APPLICATION_FORM_URLENCODED})
     @ApiResponses(value = {
-            @ApiResponse(code = 201, message = "Accepted"),
+            @ApiResponse(code = 200, message = "OK"),
             @ApiResponse(code = 400, message = "Bad Request"),
             @ApiResponse(code = 401, message = "Unauthorized"),
             @ApiResponse(code = 500, message = "Internal Server Error")
@@ -229,7 +229,7 @@ public interface ServerApi {
     @Path("/ha/states/clear")
     @ApiOperation(value = "Clears HA node information for non-master nodes; 
active nodes will repopulate and other records will be erased")
     @ApiResponses(value = {
-            @ApiResponse(code = 201, message = "Accepted"),
+            @ApiResponse(code = 200, message = "OK"),
             @ApiResponse(code = 400, message = "Bad Request"),
             @ApiResponse(code = 401, message = "Unauthorized"),
             @ApiResponse(code = 500, message = "Internal Server Error")
@@ -242,7 +242,7 @@ public interface ServerApi {
     @ApiOperation(value = "Clears HA node information for a particular 
non-master node; other nodes will repopulate and selected node will be erased",
             consumes = MediaType.APPLICATION_FORM_URLENCODED)
     @ApiResponses(value = {
-            @ApiResponse(code = 201, message = "Accepted"),
+            @ApiResponse(code = 200, message = "OK"),
             @ApiResponse(code = 400, message = "Bad Request"),
             @ApiResponse(code = 401, message = "Unauthorized"),
             @ApiResponse(code = 500, message = "Internal Server Error")
@@ -267,7 +267,7 @@ public interface ServerApi {
     @ApiOperation(value = "Sets the HA node priority for MASTER failover")
     @Consumes({MediaType.APPLICATION_FORM_URLENCODED})
     @ApiResponses(value = {
-            @ApiResponse(code = 201, message = "Accepted"),
+            @ApiResponse(code = 200, message = "OK"),
             @ApiResponse(code = 400, message = "Bad Request"),
             @ApiResponse(code = 401, message = "Unauthorized"),
             @ApiResponse(code = 500, message = "Internal Server Error")
@@ -296,7 +296,7 @@ public interface ServerApi {
     @Consumes
     @ApiOperation(value = "Imports a persistence export to a file-based store, 
moving catalog items, locations and managed applications (merged with the 
current persistence).")
     @ApiResponses(value = {
-            @ApiResponse(code = 201, message = "Accepted"),
+            @ApiResponse(code = 200, message = "OK"),
             @ApiResponse(code = 400, message = "Bad Request"),
             @ApiResponse(code = 401, message = "Unauthorized"),
             @ApiResponse(code = 500, message = "Internal Server Error")
diff --git 
a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/ApiError.java 
b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/ApiError.java
index be72f07bd4..cc9b70ed5e 100644
--- a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/ApiError.java
+++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/ApiError.java
@@ -23,11 +23,14 @@ import static 
com.google.common.base.Preconditions.checkNotNull;
 import java.io.Serializable;
 import java.util.Objects;
 
+import javax.annotation.Nullable;
+import javax.ws.rs.WebApplicationException;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.Response.Status;
 
 import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.apache.brooklyn.util.exceptions.PropagatedRuntimeException;
 import org.apache.brooklyn.util.text.Strings;
 
 import com.fasterxml.jackson.annotation.JsonInclude;
@@ -210,4 +213,16 @@ public class ApiError implements Serializable {
                 ", error=" + error +
                 '}';
     }
+
+    public WebApplicationException throwWebApplicationException() {
+        throw throwWebApplicationException(null);
+    }
+
+    public WebApplicationException throwWebApplicationException(@Nullable 
Throwable exception) {
+        throw new WebApplicationException(
+                exception==null ? new Throwable(this.toString()) :
+                        message==null ? exception :
+                                new PropagatedRuntimeException(message, 
exception),
+                this.asJsonResponse());
+    }
 }
diff --git 
a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/EffectorResource.java
 
b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/EffectorResource.java
index ba6eda643c..8431c7951c 100644
--- 
a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/EffectorResource.java
+++ 
b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/EffectorResource.java
@@ -21,6 +21,7 @@ package org.apache.brooklyn.rest.resources;
 import com.google.common.base.Function;
 import com.google.common.base.Predicate;
 import com.google.common.collect.FluentIterable;
+import javax.ws.rs.core.Response.Status;
 import org.apache.brooklyn.api.effector.Effector;
 import org.apache.brooklyn.api.entity.Entity;
 import org.apache.brooklyn.api.mgmt.Task;
@@ -96,18 +97,22 @@ public class EffectorResource extends 
AbstractBrooklynRestResource implements Ef
 
         try {
             Object result;
+            boolean stillRunning;
             if (timeout == null || timeout.isEmpty() || 
"never".equalsIgnoreCase(timeout)) {
                 result = t.get();
+                stillRunning = false;
             } else {
                 long timeoutMillis = "always".equalsIgnoreCase(timeout) ? 0 : 
Time.parseElapsedTime(timeout);
                 try {
                     if (timeoutMillis == 0) throw new TimeoutException();
                     result = t.get(timeoutMillis, TimeUnit.MILLISECONDS);
+                    stillRunning = false;
                 } catch (TimeoutException e) {
                     result = TaskTransformer.taskSummary(t, 
ui.getBaseUriBuilder(), resolving(null), null);
+                    stillRunning = true;
                 }
             }
-            return 
Response.status(Response.Status.ACCEPTED).entity(result).build();
+            return Response.status(stillRunning ? Response.Status.ACCEPTED : 
Status.OK).entity(result).build();
         } catch (Exception e) {
             throw Exceptions.propagate(e);
         }
diff --git 
a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/EntityConfigResource.java
 
b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/EntityConfigResource.java
index dc5a3714c4..438b0f66ce 100644
--- 
a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/EntityConfigResource.java
+++ 
b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/EntityConfigResource.java
@@ -21,12 +21,14 @@ package org.apache.brooklyn.rest.resources;
 import io.swagger.annotations.ApiParam;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.Callable;
 
 import javax.ws.rs.DefaultValue;
 import javax.ws.rs.QueryParam;
 import org.apache.brooklyn.api.entity.Entity;
 import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.api.sensor.Sensor;
 import org.apache.brooklyn.config.ConfigKey;
 import org.apache.brooklyn.core.config.BasicConfigKey;
 import org.apache.brooklyn.core.config.Sanitizer;
@@ -44,6 +46,7 @@ import org.apache.brooklyn.rest.transform.ConfigTransformer;
 import org.apache.brooklyn.rest.util.WebResourceUtils;
 import org.apache.brooklyn.util.core.flags.TypeCoercions;
 import org.apache.brooklyn.util.core.task.Tasks;
+import org.apache.brooklyn.util.guava.Maybe;
 import org.apache.brooklyn.util.text.Strings;
 import org.apache.brooklyn.util.time.Duration;
 import org.slf4j.Logger;
@@ -191,8 +194,21 @@ public class EntityConfigResource extends 
AbstractBrooklynRestResource implement
                     Entitlements.getEntitlementContext().user(), entity, 
ck.getName());
         }
         
-        Object value = ((EntityInternal)entity).config().getRaw(ck).orNull();
-        return 
resolving(value).preferJson(preferJson).asJerseyOutermostReturnValue(true)
+        Maybe<Object> valueM = ((EntityInternal)entity).config().getRaw(ck);
+        if (valueM.isAbsent()) {
+            Set<ConfigKey<?>> configDefinition = ((EntityInternal) 
entity).config().findKeysDeclared(x -> x.getName().equals(configKeyName));
+
+            // could do this...
+//            // if sensor is defined, but value unavailable, return 424
+//            if (sensorInMap) return WebResourceUtils.dependencyFailed("Value 
specified but not resolvable.");
+            // cf sensors
+
+            if (!configDefinition.isEmpty()) return 
WebResourceUtils.noContent("Value specified but not resolvable.");
+            // if sensor is not defined, return 404
+            throw WebResourceUtils.notFound("Config '%s' not known", 
configKeyName);
+
+        }
+        return 
resolving(valueM.get()).preferJson(preferJson).asJerseyOutermostReturnValue(true)
                 .useDisplayHints(useDisplayHints)
                 .skipResolution(skipResolution)
                 .suppressIfSecret(ck.getName(), suppressSecrets)
diff --git 
a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/SensorResource.java
 
b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/SensorResource.java
index e79d037301..25bdacf6dd 100644
--- 
a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/SensorResource.java
+++ 
b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/SensorResource.java
@@ -38,6 +38,7 @@ import org.apache.brooklyn.rest.util.EntityAttributesUtils;
 import org.apache.brooklyn.rest.util.WebResourceUtils;
 import org.apache.brooklyn.util.collections.MutableMap;
 import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.apache.brooklyn.util.guava.Maybe;
 import org.apache.brooklyn.util.text.Strings;
 import org.apache.brooklyn.util.time.Duration;
 import org.slf4j.Logger;
@@ -120,9 +121,30 @@ public class SensorResource extends 
AbstractBrooklynRestResource implements Sens
             throw WebResourceUtils.forbidden("User '%s' is not authorized to 
see entity '%s' sensor '%s'",
                     Entitlements.getEntitlementContext().user(), entity, 
sensor.getName());
         }
-        
-        Object value = EntityAttributesUtils.tryGetAttribute(entity, sensor);
-        return 
resolving(value).preferJson(preferJson).asJerseyOutermostReturnValue(true).useDisplayHints(useDisplayHints).raw(raw).context(entity).immediately(true).renderAs(sensor)
+
+        Maybe<?> vm = EntityAttributesUtils.getAttributeMaybe(entity, sensor);
+        if (vm.isAbsent()) {
+             Sensor<?> sensorDefinition = ((EntityInternal) 
entity).getMutableEntityType().getSensor(sensorName);
+
+//            boolean sensorInMap = ((EntityInternal) 
entity).sensors().getAll().keySet().stream().anyMatch(k -> 
k.getName().equals(sensorName));
+            // if sensor is defined, but value unavailable, return 424
+
+            // if sensor defined, but not set, retun 204
+            if (vm!=EntityAttributesUtils.SENSOR_NOT_SET) {
+//                // could support 424, but that would probably be done below; 
this is raw, so any error is low level
+//                return WebResourceUtils.dependencyFailed("Value specified 
but not resolvable.");
+                vm.get();
+            }
+
+            if (sensorDefinition!=null && 
vm==EntityAttributesUtils.SENSOR_NOT_SET) {
+                 return WebResourceUtils.noContent("No value for sensor");
+            }
+
+            // if sensor is not defined, return 404
+            throw WebResourceUtils.notFound("Sensor '%s' not known", 
sensorName);
+
+        }
+        return 
resolving(vm.get()).preferJson(preferJson).asJerseyOutermostReturnValue(true).useDisplayHints(useDisplayHints).raw(raw).context(entity).immediately(true).renderAs(sensor)
                 .suppressIfSecret(sensorName, suppressSecrets).resolve();
     }
 
@@ -190,6 +212,7 @@ public class SensorResource extends 
AbstractBrooklynRestResource implements Sens
         if (log.isDebugEnabled())
             log.debug("REST user "+Entitlements.getEntitlementContext()+" 
deleting sensor "+sensorName);
         ((EntityInternal)entity).sensors().remove(sensor);
+        ((EntityInternal)entity).getMutableEntityType().removeSensor(sensor);
     }
     
 }
diff --git 
a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/EntityAttributesUtils.java
 
b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/EntityAttributesUtils.java
index 68a266e4f3..6649ba4105 100644
--- 
a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/EntityAttributesUtils.java
+++ 
b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/EntityAttributesUtils.java
@@ -21,6 +21,7 @@ package org.apache.brooklyn.rest.util;
 import org.apache.brooklyn.api.entity.Entity;
 import org.apache.brooklyn.api.sensor.AttributeSensor;
 import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.apache.brooklyn.util.guava.Maybe;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -46,4 +47,24 @@ public class EntityAttributesUtils {
         }
         return attribute;
     }
+
+    public static final Maybe SENSOR_NOT_SET = Maybe.absent("Sensor is unset");
+
+    /** returns {@link #SENSOR_NOT_SET} if not defined; equals check can be 
done to compare that with a different error */
+    public static <T> Maybe<T> getAttributeMaybe(Entity entity, 
AttributeSensor<T> sensor) {
+        T attribute = null;
+        try {
+            attribute = entity.getAttribute(sensor);
+            if (attribute==null) {
+                if (!entity.sensors().getAll().keySet().stream().anyMatch(sn 
-> sn.getName().equals(sensor.getName()))) {
+                    return SENSOR_NOT_SET;
+                }
+            }
+        } catch (Exception exception) {
+            Exceptions.propagateIfFatal(exception);
+            LOG.warn("Error retrieving sensor " + sensor + " for " + entity + 
" (ignoring): " + exception);
+            return Maybe.absent(exception);
+        }
+        return Maybe.ofAllowingNull(attribute);
+    }
 }
diff --git 
a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/WebResourceUtils.java
 
b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/WebResourceUtils.java
index cef552b4e3..090ebd9dea 100644
--- 
a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/WebResourceUtils.java
+++ 
b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/WebResourceUtils.java
@@ -86,11 +86,7 @@ public class WebResourceUtils {
                 : ApiError.builder().message(fullMsg==null ? "" : fullMsg))
             .errorCode(status).build();
         // including a Throwable is the only way to include a message with the 
WebApplicationException - ugly!
-        throw new WebApplicationException(
-            exception==null ? new Throwable(apiError.toString()) :
-                suppliedMsg==null ? exception :
-                new PropagatedRuntimeException(suppliedMsg, exception), 
-            apiError.asJsonResponse());
+        throw apiError.throwWebApplicationException(exception);
     }
 
     /** @throws WebApplicationException With code 500 internal server error */
@@ -133,6 +129,14 @@ public class WebResourceUtils {
         return 
throwWebApplicationException(Response.Status.PRECONDITION_FAILED, format, args);
     }
 
+    public static WebApplicationException dependencyFailed(String message) {
+        return 
ApiError.builder().message(message).errorCode(424).build().throwWebApplicationException();
+    }
+
+    public static WebApplicationException noContent(String message) {
+        return 
ApiError.builder().message(message).errorCode(204).build().throwWebApplicationException();
+    }
+
     public final static Map<String,com.google.common.net.MediaType> 
IMAGE_FORMAT_MIME_TYPES = ImmutableMap.<String, 
com.google.common.net.MediaType>builder()
             .put("jpg", com.google.common.net.MediaType.JPEG)
             .put("jpeg", com.google.common.net.MediaType.JPEG)
diff --git 
a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/ApplicationResourceTest.java
 
b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/ApplicationResourceTest.java
index 6c2ac33d91..0ffb66eeae 100644
--- 
a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/ApplicationResourceTest.java
+++ 
b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/ApplicationResourceTest.java
@@ -609,7 +609,7 @@ public class ApplicationResourceTest extends 
BrooklynRestResourceTest {
                 .type(MediaType.APPLICATION_JSON_TYPE)
                 .post(ImmutableMap.of("param1", "foo", "param2", 4));
 
-        assertEquals(response.getStatus(), 
Response.Status.ACCEPTED.getStatusCode());
+        assertEquals(response.getStatus(), Response.Status.OK.getStatusCode());
 
         String result = response.readEntity(String.class);
         assertEquals(result, "foo4");
@@ -625,7 +625,7 @@ public class ApplicationResourceTest extends 
BrooklynRestResourceTest {
                 .type(MediaType.APPLICATION_FORM_URLENCODED_TYPE)
                 .post(data);
 
-        assertEquals(response.getStatus(), 
Response.Status.ACCEPTED.getStatusCode());
+        assertEquals(response.getStatus(), Response.Status.OK.getStatusCode());
 
         String result = response.readEntity(String.class);
         assertEquals(result, "foo4");
diff --git 
a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/EffectorResourceTest.java
 
b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/EffectorResourceTest.java
index a8d3cdf680..dba54257cb 100644
--- 
a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/EffectorResourceTest.java
+++ 
b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/EffectorResourceTest.java
@@ -76,7 +76,7 @@ public class EffectorResourceTest extends 
BrooklynRestResourceTest {
         Response response = client().path(path)
                 .accept(MediaType.APPLICATION_JSON)
                 .post(null);
-        assertEquals(response.getStatus(), 202);
+        assertEquals(response.getStatus(), 200);
         Asserts.succeedsEventually(() -> 
assertTrue(entity.getCallHistory().contains("myEffector")));
     }
     
@@ -100,7 +100,7 @@ public class EffectorResourceTest extends 
BrooklynRestResourceTest {
                 .accept(MediaType.APPLICATION_JSON)
                 .header("Content-Type", MediaType.APPLICATION_JSON)
                 .post("{\"arg\": \"myval\"}");
-        assertEquals(response.getStatus(), 202);
+        assertEquals(response.getStatus(), 200);
         
         String responseBody = response.readEntity(String.class);
         assertTrue(entity.getCallHistory().contains("identityEffector"));
@@ -118,7 +118,7 @@ public class EffectorResourceTest extends 
BrooklynRestResourceTest {
                 .header("Content-Type", MediaType.APPLICATION_JSON)
                 .post("{\"duration\": \"50ms\"}");
         Duration runDuration = Duration.of(stopwatch);
-        assertEquals(response.getStatus(), 202);
+        assertEquals(response.getStatus(), 200);
         
         assertTrue(entity.getCallHistory().contains("sleepEffector"));
         assertTrue(runDuration.isLongerThan(Duration.millis(40)), 
"runDuration="+runDuration);
diff --git 
a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/SensorResourceTest.java
 
b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/SensorResourceTest.java
index 23dbdcb79b..ac4d87e28f 100644
--- 
a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/SensorResourceTest.java
+++ 
b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/SensorResourceTest.java
@@ -313,8 +313,11 @@ public class SensorResourceTest extends 
BrooklynRestResourceTest {
                 .delete();
             assertEquals(response.getStatus(), 
Response.Status.NO_CONTENT.getStatusCode());
 
-            String value = client().path(SENSORS_ENDPOINT + "/" + 
SENSOR_NAME).accept(MediaType.TEXT_PLAIN_TYPE).get(String.class);
-            assertEquals(value, "");
+            Response r = client().path(SENSORS_ENDPOINT + "/" + 
SENSOR_NAME).accept(MediaType.TEXT_PLAIN_TYPE).get();
+            assertEquals(r.getStatus(), 404);  // not defined
+
+            r = client().path(SENSORS_ENDPOINT + "/" + 
RestMockSimpleEntity.SAMPLE_SENSOR.getName()).accept(MediaType.TEXT_PLAIN_TYPE).get();
+            assertEquals(r.getStatus(), 204);  // defined but no value
 
         } finally { addAmphibianSensor(entity); }
     }
diff --git 
a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/test/entity/brooklynnode/DeployBlueprintTest.java
 
b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/test/entity/brooklynnode/DeployBlueprintTest.java
index 4d1d3b6821..aeab1dd0fc 100644
--- 
a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/test/entity/brooklynnode/DeployBlueprintTest.java
+++ 
b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/test/entity/brooklynnode/DeployBlueprintTest.java
@@ -81,8 +81,9 @@ public class DeployBlueprintTest extends 
BrooklynRestResourceTest {
         List<String> appType = parseJsonList(apps, ImmutableList.of("spec", 
"type"), String.class);
         assertEquals(appType, 
ImmutableList.of(BasicApplication.class.getName()));
 
-        String status = 
HttpTool.getContentUnsafe(getEndpointAddress()+"/applications/"+id+"/entities/"+id+"/sensors/service.status");
-        log.info("STATUS: "+status);
+        // status was empty, at least for a short while; now throws 404
+//        String status = 
HttpTool.getContentUnsafe(getEndpointAddress()+"/applications/"+id+"/entities/"+id+"/sensors/service.status");
+//        log.info("STATUS: "+status);
     }
 
     private <T> List<T> parseJsonList(String json, List<String> elements, 
Class<T> clazz) {


Reply via email to