Added support in REST API for POSTing or PUTting multiple preferences and shared data in one request. See WOOKIE-414

git-svn-id: https://svn.apache.org/repos/asf/wookie/trunk@1575276 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/wookie-connector/java/src/main/java/org/apache/wookie/connector/framework/AbstractWookieConnectorService.java b/wookie-connector/java/src/main/java/org/apache/wookie/connector/framework/AbstractWookieConnectorService.java
index 692c2a3..ceab972 100644
--- a/wookie-connector/java/src/main/java/org/apache/wookie/connector/framework/AbstractWookieConnectorService.java
+++ b/wookie-connector/java/src/main/java/org/apache/wookie/connector/framework/AbstractWookieConnectorService.java
@@ -23,7 +23,6 @@
 import java.io.OutputStream;

 import java.net.MalformedURLException;

 import java.net.URL;

-import java.net.URLEncoder;

 import java.util.ArrayList;

 import java.util.HashMap;

 import java.util.List;

@@ -95,16 +94,18 @@
 		

 		SignedApiRequest request = SignedApiRequest.POST(conn.getURL()+"/properties", conn.getApiKey(), conn.getSecret());

 		createInstanceParams(request, instance.id);

+		

+		String json = "{";

 

 		if ( is_public ) {

-			request.addParameter("is_public", "true");

+			json += "\"shareddata\":[{\"name\":\""+fName+"\", \"value\":\""+fValue+"\"}]";

 		}

 		else {

-			request.addParameter("is_public", "false");

+			json += "\"preferences\":[{\"name\":\""+fName+"\", \"value\":\""+fValue+"\"}]";

 		}

-		request.addParameter("propertyname", fName);

-		request.addParameter("propertyvalue", fValue);

-

+		json += "}";

+		

+		request.setRequestEntity(json);

 		request.execute();

 

 		if (request.getStatusCode() > 201) {

@@ -132,20 +133,22 @@
 	}

 

 

-	public void updatePropertyForInstance (WidgetInstance instance, boolean is_public, String propertyName, String data ) throws WookieConnectorException, IOException {

+	public void updatePropertyForInstance (WidgetInstance instance, boolean is_public, String name, String value ) throws WookieConnectorException, IOException {

 		

 		SignedApiRequest request = SignedApiRequest.PUT(conn.getURL()+"/properties", conn.getApiKey(), conn.getSecret());

 		createInstanceParams(request, instance.id);

 		

+		String json = "{";

+

 		if ( is_public ) {

-			request.addParameter("is_public", "true");

+			json += "\"shareddata\":[{\"name\":\""+name+"\", \"value\":\""+value+"\"}]";

 		}

 		else {

-			request.addParameter("is_public", "false");

+			json += "\"preferences\":[{\"name\":\""+name+"\", \"value\":\""+value+"\"}]";

 		}

+		json += "}";

 		

-		request.addParameter("propertyname", propertyName);

-		request.addParameter("propertyvalue", data);

+		request.setRequestEntity(json);

 

 		request.execute();

 

@@ -702,7 +705,7 @@
 	 * @throws IOException

 	 */

 	public void deletePolicy ( Policy policy ) throws WookieConnectorException, IOException {

-		SignedApiRequest request = SignedApiRequest.DELETE(conn.getURL()+"/policies"+URLEncoder.encode(policy.toString(), "UTF-8"), conn.getApiKey(), conn.getSecret());

+		SignedApiRequest request = SignedApiRequest.DELETE(conn.getURL()+"/policies/"+policy.toString(), conn.getApiKey(), conn.getSecret());

 		request.execute();

 		if (request.getStatusCode() != 200){

 			throw new WookieConnectorException("Problem DELETEing from /policies", new IOException("Error:"+request.getStatusCode()));

diff --git a/wookie-server/src/main/java/org/apache/wookie/controller/PropertiesController.java b/wookie-server/src/main/java/org/apache/wookie/controller/PropertiesController.java
index 780e6e9..9c4da1e 100644
--- a/wookie-server/src/main/java/org/apache/wookie/controller/PropertiesController.java
+++ b/wookie-server/src/main/java/org/apache/wookie/controller/PropertiesController.java
@@ -21,6 +21,7 @@
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
+import org.apache.commons.io.IOUtils;
 import org.apache.log4j.Logger;
 import org.apache.wookie.auth.AuthToken;
 import org.apache.wookie.beans.ISharedData;
@@ -31,6 +32,8 @@
 import org.apache.wookie.exceptions.UnauthorizedAccessException;
 import org.apache.wookie.helpers.Notifier;
 import org.apache.wookie.services.PreferencesService;
+import org.json.JSONArray;
+import org.json.JSONObject;
 
 /**
  * REST implementation for widgetInstance
@@ -45,35 +48,29 @@
 	private static final long serialVersionUID = 308590474406800659L;		
 	static Logger _logger = Logger.getLogger(PropertiesController.class.getName());	
 
-	/**
-	 * We only override doGet to allow tunneling requests through GET 
-	 * for legacy clients
+	
+	
+	/* (non-Javadoc)
+	 * @see org.apache.wookie.controller.Controller#doGet(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
 	 */
 	@Override
 	protected void doGet(HttpServletRequest request,
 			HttpServletResponse response) throws ServletException, IOException {
-		String requestId = request.getParameter("requestid");//$NON-NLS-1$
-		// If the request id is not null, show otherwise index
-		if (requestId != null && requestId.length() > 0) {
-			try {
-				createOrUpdate(request);
-			} catch (InvalidParametersException e) {
-				response.sendError(HttpServletResponse.SC_BAD_REQUEST);
-			} catch (UnauthorizedAccessException e){
-				response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
-			}
-		} else {
-			try {
-				show(null, request, response);
-				response.setStatus(HttpServletResponse.SC_OK);
-			} catch (ResourceNotFoundException e) {
-				response.sendError(HttpServletResponse.SC_NOT_FOUND);
-			} catch (UnauthorizedAccessException e){
-				response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
-			}
+		//
+		// For now we just execute the show() method. In future we could return a JSON array of all properties
+		//
+		String name = request.getParameter("propertyname"); //$NON-NLS-1$
+		try {
+			show(name, request, response);
+		} catch (ResourceNotFoundException e) {
+			response.sendError(HttpServletResponse.SC_NOT_FOUND);
+		} catch(UnauthorizedAccessException e1){
+			response.sendError(HttpServletResponse.SC_FORBIDDEN);
 		}
 	}
 
+
+
 	@Override
 	protected void show(String resourceId, HttpServletRequest request,
 			HttpServletResponse response) throws ResourceNotFoundException,
@@ -81,14 +78,13 @@
 		
 		AuthToken authToken = getAuthTokenFromRequest(request);		
 		if (authToken == null) throw new ResourceNotFoundException();
-		String name = request.getParameter("propertyname"); //$NON-NLS-1$
-		if (name == null || name.trim().equals("")) throw new ResourceNotFoundException();
+		if (resourceId == null || resourceId.trim().equals("")) throw new ResourceNotFoundException();
 		String value = null;
 		// Note that preferences and shared data keys may be the same!
 		// We let the shared data values override.
 		
-		value = PreferencesService.Factory.getInstance().getPreference(authToken.getApiKey(), authToken.getWidgetId(), authToken.getContextId(), authToken.getViewerId(), name);
-		ISharedData data = new SharedContext(authToken).getSharedData(name);
+		value = PreferencesService.Factory.getInstance().getPreference(authToken.getApiKey(), authToken.getWidgetId(), authToken.getContextId(), authToken.getViewerId(), resourceId);
+		ISharedData data = new SharedContext(authToken).getSharedData(resourceId);
 		if (data != null) value = data.getDvalue();
 		if (value == null) throw new ResourceNotFoundException();
 		PrintWriter out = response.getWriter();
@@ -111,7 +107,7 @@
 			found = new SharedContext(authToken).removeSharedData(name);
 			Notifier.notifyWidgets(request.getSession(), authToken, Notifier.STATE_UPDATED);
 		} else {
-			found = updatePreference(authToken, name, null);
+			found = updatePreference(authToken, name, null, false);
 		}
 		if (!found) throw new ResourceNotFoundException();
 		return true;
@@ -138,21 +134,68 @@
 	 * @throws InvalidParametersException
 	 * @throws UnauthorizedAccessException
 	 */
-	public static void createOrUpdate(HttpServletRequest request)
+	private static void createOrUpdate(HttpServletRequest request)
 	throws InvalidParametersException,UnauthorizedAccessException {
-		String name = request.getParameter("propertyname"); //$NON-NLS-1$
-		String value = request.getParameter("propertyvalue"); //$NON-NLS-1$
-		
+
 		AuthToken authToken = getAuthTokenFromRequest(request);
 		if (authToken == null) throw new InvalidParametersException();
+
+		//
+		// The data we need is in JSON in the request body, structured as:
+		//
+		// {
+		//   {"preferences" {[
+		//      {"name":"pref1", "value":"pass", "readOnly":0}
+		//   ]},
+		//   {"shareddata" {[
+		//      {"name":"sd1", "value":"pass"}
+		//   ]}
+		// }
+		//
+		//
 		
-		if (name == null || name.trim().equals("")) throw new InvalidParametersException();
-		
-		if (isPublic(request)){ 
-		    new SharedContext(authToken).updateSharedData(name, value, false);
-			Notifier.notifyWidgets(request.getSession(), authToken, Notifier.STATE_UPDATED);
-		} else {
-			updatePreference(authToken, name, value);
+		try {
+			String body = IOUtils.toString(request.getInputStream());
+			JSONObject json = new JSONObject(body);
+			if (json != null){
+				if (json.has("preferences")){
+					JSONArray array = json.getJSONArray("preferences");
+					if (array != null){
+						for (int i=0;i<array.length();i++){
+							JSONObject property = array.getJSONObject(i);
+							if (!property.has("name")) throw new InvalidParametersException();
+							String name = property.getString("name");
+							if (name != null && !name.trim().equals("")){
+								String value = property.getString("value");
+								boolean readOnly = property.getBoolean("readOnly");
+								updatePreference(authToken, name, value, readOnly);
+							} else {
+								throw new InvalidParametersException();
+							}
+						}
+					}
+				}
+				if (json.has("shareddata")){
+					JSONArray array = json.getJSONArray("shareddata");
+					if (array != null){
+						for (int i=0;i<array.length();i++){
+							JSONObject property = array.getJSONObject(i);
+							if (!property.has("name")) throw new InvalidParametersException();
+							String name = property.getString("name");
+							if (name != null && !name.trim().equals("")){
+								String value = property.getString("value");
+								new SharedContext(authToken).updateSharedData(name, value, false);
+								Notifier.notifyWidgets(request.getSession(), authToken, Notifier.STATE_UPDATED);
+							} else {
+								throw new InvalidParametersException();
+							}
+						}
+					}
+				}
+			}
+		} catch (Exception e) {
+			e.printStackTrace();
+			throw new InvalidParametersException();
 		}
 	}
 
@@ -162,11 +205,11 @@
 	 * @param name
 	 * @param value
 	 */
-	public static boolean updatePreference(AuthToken authToken, String name, String value){
+	private static boolean updatePreference(AuthToken authToken, String name, String value, boolean readOnly){
         boolean found=false;
         String preference = PreferencesService.Factory.getInstance().getPreference(authToken.getApiKey(), authToken.getWidgetId(), authToken.getContextId(), authToken.getViewerId(), name);        
         if (preference != null) found=true;
-   	    PreferencesService.Factory.getInstance().setPreference(authToken.getApiKey(), authToken.getWidgetId(), authToken.getContextId(), authToken.getViewerId(), name, value);
+   	    PreferencesService.Factory.getInstance().setPreference(authToken.getApiKey(), authToken.getWidgetId(), authToken.getContextId(), authToken.getViewerId(), name, value, readOnly);
         return found;
 	}
 
diff --git a/wookie-server/src/test/java/org/apache/wookie/tests/functional/PropertiesControllerTest.java b/wookie-server/src/test/java/org/apache/wookie/tests/functional/PropertiesControllerTest.java
index cc2c760..f746703 100644
--- a/wookie-server/src/test/java/org/apache/wookie/tests/functional/PropertiesControllerTest.java
+++ b/wookie-server/src/test/java/org/apache/wookie/tests/functional/PropertiesControllerTest.java
@@ -18,7 +18,11 @@
 import java.io.IOException;
 
 import org.apache.commons.httpclient.HttpException;
+import org.apache.commons.httpclient.methods.StringRequestEntity;
 import org.apache.wookie.tests.helpers.Request;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
 import org.junit.AfterClass;
 import org.junit.BeforeClass;
 import org.junit.Test;
@@ -51,12 +55,12 @@
 	}
 
 	/**
-	 * Test that we can set preferences using post parameters
+	 * Test that we can set preferences using POST
 	 * 
 	 * @throws Exception
 	 */
 	@Test
-	public void setPreferenceUsingPostParameters() throws Exception {
+	public void setPreferenceUsingPost() throws Exception {
 		//
 		// Set a property ("testpost=pass") using POST
 		//
@@ -64,10 +68,18 @@
 		post.addParameter("api_key", API_KEY_VALID);
 		post.addParameter("widgetid", WIDGET_ID_VALID);
 		post.addParameter("userid", "test");
-		post.addParameter("is_public", "false");
 		post.addParameter("shareddatakey", "propstest");
-		post.addParameter("propertyname", "testpost");
-		post.addParameter("propertyvalue", "pass");
+
+		JSONObject pref = new JSONObject();
+		pref.put("name", "testpost");
+		pref.put("value", "pass");
+		pref.put("readOnly", false);
+		JSONObject json = new JSONObject();
+		JSONArray prefs = new JSONArray();
+		prefs.put(pref);
+		json.put("preferences", prefs);		
+		StringRequestEntity entity = new StringRequestEntity(json.toString(), "application/json", "UTF-8");
+		post.setRequestEntity(entity);
 		post.execute(true, false);
 		int code = post.getStatusCode();
 		assertEquals(201, code);
@@ -88,14 +100,82 @@
 		assertEquals("pass", resp);
 	}
 
+
+	/**
+	 * Test that we can set multiple preferences at the same time by POSTing JSON
+	 * 
+	 * @throws Exception
+	 */
+	@Test
+	public void setMultiplePreferencesUsingPost() throws Exception {
+		//
+		// Set a property ("testpost=pass") using POST
+		//
+		Request post = new Request("POST", TEST_PROPERTIES_SERVICE_URL_VALID);
+		post.addParameter("api_key", API_KEY_VALID);
+		post.addParameter("widgetid", WIDGET_ID_VALID);
+		post.addParameter("userid", "test");
+		post.addParameter("shareddatakey", "propstest");
+
+		JSONObject pref1 = new JSONObject();
+		pref1.put("name", "testpost1");
+		pref1.put("value", "pass1");
+		pref1.put("readOnly", false);
+		
+		JSONObject pref2 = new JSONObject();
+		pref2.put("name", "testpost2");
+		pref2.put("value", "pass2");
+		pref2.put("readOnly", false);
+		
+		JSONObject json = new JSONObject();
+		JSONArray prefs = new JSONArray();
+		prefs.put(pref1);
+		prefs.put(pref2);
+		json.put("preferences", prefs);		
+		StringRequestEntity entity = new StringRequestEntity(json.toString(), "application/json", "UTF-8");
+		post.setRequestEntity(entity);
+		post.execute(true, false);
+		int code = post.getStatusCode();
+		assertEquals(201, code);
+
+		//
+		// Read back each property using GET
+		//
+		Request get = new Request("GET", TEST_PROPERTIES_SERVICE_URL_VALID);
+		get.addParameter("api_key", API_KEY_VALID);
+		get.addParameter("widgetid", WIDGET_ID_VALID);
+		get.addParameter("userid", "test");
+		get.addParameter("shareddatakey", "propstest");
+		get.addParameter("propertyname", "testpost1");
+		get.execute(true, false);
+		code = get.getStatusCode();
+		assertEquals(200, code);
+		String resp = get.getResponseBodyAsString();
+		assertEquals("pass1", resp);
+		
+		get = new Request("GET", TEST_PROPERTIES_SERVICE_URL_VALID);
+		get.addParameter("api_key", API_KEY_VALID);
+		get.addParameter("widgetid", WIDGET_ID_VALID);
+		get.addParameter("userid", "test");
+		get.addParameter("shareddatakey", "propstest");
+		get.addParameter("propertyname", "testpost2");
+		get.execute(true, false);
+		code = get.getStatusCode();
+		assertEquals(200, code);
+		resp = get.getResponseBodyAsString();
+		assertEquals("pass2", resp);
+	}
+
+	
 	/**
 	 * Test we can set shared data values using querystring parameters
 	 * 
 	 * @throws IOException
 	 * @throws HttpException
+	 * @throws JSONException 
 	 */
 	@Test
-	public void setSharedData() throws HttpException, IOException {
+	public void setSharedData() throws HttpException, IOException, JSONException {
 
 		//
 		// Set some shared data with POST
@@ -104,10 +184,17 @@
 		post.addParameter("api_key", API_KEY_VALID);
 		post.addParameter("widgetid", WIDGET_ID_VALID);
 		post.addParameter("userid", "test");
-		post.addParameter("is_public", "false");
 		post.addParameter("shareddatakey", "propstest");
-		post.addParameter("propertyname", "cat");
-		post.addParameter("propertyvalue", "garfield");
+
+		JSONObject data = new JSONObject();
+		data.put("name", "cat");
+		data.put("value", "garfield");
+		JSONObject json = new JSONObject();
+		JSONArray set = new JSONArray();
+		set.put(data);
+		json.put("shareddata", set);		
+		StringRequestEntity entity = new StringRequestEntity(json.toString(), "application/json", "UTF-8");
+		post.setRequestEntity(entity);
 		post.execute(true, false);
 		int code = post.getStatusCode();
 		assertEquals(201, code);
@@ -134,9 +221,10 @@
 	 * 
 	 * @throws IOException
 	 * @throws HttpException
+	 * @throws JSONException 
 	 */
 	@Test
-	public void updateProperty() throws HttpException, IOException {
+	public void updateProperty() throws HttpException, IOException, JSONException {
 
 		//
 		// Set cat=felix using POST
@@ -145,10 +233,18 @@
 		put.addParameter("api_key", API_KEY_VALID);
 		put.addParameter("widgetid", WIDGET_ID_VALID);
 		put.addParameter("userid", "test");
-		put.addParameter("is_public", "false");
 		put.addParameter("shareddatakey", "propstest");
-		put.addParameter("propertyname", "cat");
-		put.addParameter("propertyvalue", "felix");
+		
+		JSONObject data = new JSONObject();
+		data.put("name", "cat");
+		data.put("value", "felix");
+		JSONObject json = new JSONObject();
+		JSONArray set = new JSONArray();
+		set.put(data);
+		json.put("shareddata", set);		
+		StringRequestEntity entity = new StringRequestEntity(json.toString(), "application/json", "UTF-8");
+		put.setRequestEntity(entity);
+		
 		put.execute(true, false);
 		int code = put.getStatusCode();
 		assertEquals(200, code);
@@ -186,6 +282,7 @@
 		delete.addParameter("api_key", API_KEY_VALID);
 		delete.addParameter("widgetid", WIDGET_ID_VALID);
 		delete.addParameter("userid", "test");
+		delete.addParameter("is_public", "true");
 		delete.addParameter("shareddatakey", "propstest");
 		delete.addParameter("propertyname", "cat");
 		delete.execute(true, false);
@@ -217,16 +314,25 @@
 	 * 
 	 * @throws IOException
 	 * @throws HttpException
+	 * @throws JSONException 
 	 */
 	@Test
-	public void setPropertyNoName() throws HttpException, IOException {
+	public void setPropertyNoName() throws HttpException, IOException, JSONException {
 		Request post = new Request("POST", TEST_PROPERTIES_SERVICE_URL_VALID);
 		post.addParameter("api_key", API_KEY_VALID);
 		post.addParameter("widgetid", WIDGET_ID_VALID);
 		post.addParameter("userid", "test");
-		post.addParameter("is_public", "false");
 		post.addParameter("shareddatakey", "propstest");
-		post.addParameter("propertyvalue", "garfield");
+		
+		JSONObject data = new JSONObject();
+		data.put("value", "garfield");
+		JSONObject json = new JSONObject();
+		JSONArray set = new JSONArray();
+		set.put(data);
+		json.put("shareddata", set);		
+		StringRequestEntity entity = new StringRequestEntity(json.toString(), "application/json", "UTF-8");
+		post.setRequestEntity(entity);
+		
 		post.execute(true, false);
 		int code = post.getStatusCode();
 		assertEquals(400, code);
@@ -238,17 +344,26 @@
 	 * 
 	 * @throws IOException
 	 * @throws HttpException
+	 * @throws JSONException 
 	 */
 	@Test
-	public void setPropertyEmptyName() throws HttpException, IOException {
+	public void setPropertyEmptyName() throws HttpException, IOException, JSONException {
 		Request post = new Request("POST", TEST_PROPERTIES_SERVICE_URL_VALID);
 		post.addParameter("api_key", API_KEY_VALID);
 		post.addParameter("widgetid", WIDGET_ID_VALID);
 		post.addParameter("userid", "test");
-		post.addParameter("is_public", "false");
 		post.addParameter("shareddatakey", "propstest");
-		post.addParameter("propertyname", "");
-		post.addParameter("propertyvalue", "garfield");
+		
+		JSONObject data = new JSONObject();
+		data.put("name", "");
+		data.put("value", "garfield");
+		JSONObject json = new JSONObject();
+		JSONArray set = new JSONArray();
+		set.put(data);
+		json.put("shareddata", set);		
+		StringRequestEntity entity = new StringRequestEntity(json.toString(), "application/json", "UTF-8");
+		post.setRequestEntity(entity);
+		
 		post.execute(true, false);
 		int code = post.getStatusCode();
 		assertEquals(400, code);
@@ -259,17 +374,26 @@
 	 * 
 	 * @throws IOException
 	 * @throws HttpException
+	 * @throws JSONException 
 	 */
 	@Test
-	public void setPropertyWhitespaceName() throws HttpException, IOException {
+	public void setPropertyWhitespaceName() throws HttpException, IOException, JSONException {
 		Request post = new Request("POST", TEST_PROPERTIES_SERVICE_URL_VALID);
 		post.addParameter("api_key", API_KEY_VALID);
 		post.addParameter("widgetid", WIDGET_ID_VALID);
 		post.addParameter("userid", "test");
-		post.addParameter("is_public", "false");
 		post.addParameter("shareddatakey", "propstest");
-		post.addParameter("propertyname", "  ");
-		post.addParameter("propertyvalue", "garfield");
+		
+		JSONObject data = new JSONObject();
+		data.put("name", " ");
+		data.put("value", "garfield");
+		JSONObject json = new JSONObject();
+		JSONArray set = new JSONArray();
+		set.put(data);
+		json.put("shareddata", set);		
+		StringRequestEntity entity = new StringRequestEntity(json.toString(), "application/json", "UTF-8");
+		post.setRequestEntity(entity);
+		
 		post.execute(true, false);
 		int code = post.getStatusCode();
 		assertEquals(400, code);