Author: awiner
Date: Wed Apr 1 19:10:52 2009
New Revision: 761018
URL: http://svn.apache.org/viewvc?rev=761018&view=rev
Log:
SHINDIG-1002: HttpRequest format="text" does not function
and
SHINDIG-739: Data Pipelining (in part)
Follow revised spec of os:HttpRequest output format, specifically:
- Move content into a "content" property
- Include headers (a limited set) and http status code
- Support JSON arrays in addition to text
- Support HTTP errors - report them in an "error" block
- If JSON parsing fails, treat as a 406 error
The above makes resolving SHINDIG-1002 tractable, and:
<script xmlns:os="http://ns.opensocial.org/2008/markup"
type="text/os-data">
<os:HttpRequest key="google" href="http://www.google.com"
format="text"/>
</script>
<script type="text/os-template"
xmlns:os="http://ns.opensocial.org/2008/markup">
<os:Html code="${google.content}"/>
</script>
... now sorta works (except that there isn't any content fixup for relative
URLs).
Write lots of tests for the HTTP pipeline format, both unit and end-to-end.
Modified:
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/preload/PipelinedDataPreloader.java
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/preload/PipelinedDataPreloaderTest.java
incubator/shindig/trunk/java/server/src/test/java/org/apache/shindig/server/endtoend/EndToEndTest.java
incubator/shindig/trunk/java/server/src/test/resources/endtoend/templateRewriter.xml
Modified:
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/preload/PipelinedDataPreloader.java
URL:
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/preload/PipelinedDataPreloader.java?rev=761018&r1=761017&r2=761018&view=diff
==============================================================================
---
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/preload/PipelinedDataPreloader.java
(original)
+++
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/preload/PipelinedDataPreloader.java
Wed Apr 1 19:10:52 2009
@@ -42,12 +42,14 @@
import java.util.Collections;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.concurrent.Callable;
import javax.el.ELResolver;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.inject.Inject;
@@ -59,6 +61,9 @@
private final ContainerConfig config;
private static final Charset UTF8 = Charset.forName("UTF-8");
+ private static Set<String> HTTP_RESPONSE_HEADERS =
+ ImmutableSet.of("content-type", "location", "set-cookie");
+
private final Expressions expressions;
@Inject
@@ -227,32 +232,50 @@
public Data(HttpResponse response) {
String format = preload.getAttributes().get("format");
- JSONObject data = new JSONObject();
+ JSONObject wrapper = new JSONObject();
try {
- data.put("id", key);
-
- if (format == null || "json".equals(format)) {
- try {
- // TODO: support arrays and objects
- data.put("data", new JSONObject(response.getResponseAsString()));
- } catch (JSONException je) {
- data.put("code", 500);
- data.put("message", je.getMessage());
- }
+ wrapper.put("id", key);
+ if (response.getHttpStatusCode() >= 400) {
+ wrapper.put("error", createJSONError(response.getHttpStatusCode(),
null, response));
} else {
- if (response.isError()) {
- data.put("code", response.getHttpStatusCode());
- data.put("message", response.getResponseAsString());
+ // Create {data: {status: [CODE], content: {...}|[...]|"...",
headers:{...}}}
+ JSONObject data = new JSONObject();
+ wrapper.put("data", data);
+
+ // Add the status
+ data.put("status", response.getHttpStatusCode());
+ String responseText = response.getResponseAsString();
+
+ // Add allowed headers
+ JSONObject headers = createJSONHeaders(response);
+ if (headers != null) {
+ data.put("headers", headers);
+ }
+
+ // And add the parsed content
+ if (format == null || "json".equals(format)) {
+ try {
+ if (responseText.startsWith("[")) {
+ data.put("content", new JSONArray(responseText));
+ } else {
+ data.put("content", new JSONObject(responseText));
+ }
+ } catch (JSONException je) {
+ // JSON parse failed: create a 406 error, and remove the
"data" section
+ wrapper.remove("data");
+ wrapper.put("error", createJSONError(
+ HttpResponse.SC_NOT_ACCEPTABLE, je.getMessage(),
response));
+ }
} else {
- data.put("data", response.getResponseAsString());
+ data.put("content", responseText);
}
}
} catch (JSONException outerJe) {
throw new RuntimeException(outerJe);
}
- this.data = data;
+ this.data = wrapper;
}
public Collection<Object> toJson() {
@@ -261,6 +284,54 @@
}
}
+ private static JSONObject createJSONHeaders(HttpResponse response)
+ throws JSONException {
+ JSONObject headers = null;
+
+ // Add allowed headers
+ for (String header: HTTP_RESPONSE_HEADERS) {
+ Collection<String> values = response.getHeaders(header);
+ if (values != null && !values.isEmpty()) {
+ JSONArray array = new JSONArray();
+ for (String value : values) {
+ array.put(value);
+ }
+
+ if (headers == null) {
+ headers = new JSONObject();
+ }
+
+ headers.put(header, array);
+ }
+ }
+
+ return headers;
+ }
+
+ /**
+ * Create {error: { code: [CODE], data: {content: "....", headers: {...}}}}
+ */
+ private static JSONObject createJSONError(int code, String message,
HttpResponse response)
+ throws JSONException {
+ JSONObject error = new JSONObject();
+ error.put("code", code);
+ if (message != null) {
+ error.put("message", message);
+ }
+
+ JSONObject data = new JSONObject();
+ error.put("data", data);
+ data.put("content", response.getResponseAsString());
+
+ // Add allowed headers
+ JSONObject headers = createJSONHeaders(response);
+ if (headers != null) {
+ data.put("headers", headers);
+ }
+
+ return error;
+ }
+
private Uri getSocialUri(GadgetContext context) {
String jsonUri = config.getString(context.getContainer(),
"gadgets.osDataUri");
Preconditions.checkNotNull(jsonUri, "No JSON URI available for social
preloads");
Modified:
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/preload/PipelinedDataPreloaderTest.java
URL:
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/preload/PipelinedDataPreloaderTest.java?rev=761018&r1=761017&r2=761018&view=diff
==============================================================================
---
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/preload/PipelinedDataPreloaderTest.java
(original)
+++
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/preload/PipelinedDataPreloaderTest.java
Wed Apr 1 19:10:52 2009
@@ -19,6 +19,7 @@
package org.apache.shindig.gadgets.preload;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import org.apache.shindig.config.ContainerConfig;
@@ -68,6 +69,13 @@
+ " <os:HttpRequest key=\"p\" href=\"" + HTTP_REQUEST_URL + "\" "
+ "refreshInterval=\"60\" method=\"POST\"/>" + "</Content></Module>";
+ private static final String XML_WITH_HTTP_REQUEST_FOR_TEXT = "<Module
xmlns:os=\""
+ + PipelinedData.OPENSOCIAL_NAMESPACE + "\">"
+ + "<ModulePrefs title=\"Title\"/>"
+ + "<Content href=\"http://example.org/proxied.php\" view=\"profile\">"
+ + " <os:HttpRequest key=\"p\" format=\"text\" href=\"" + HTTP_REQUEST_URL
+ "\" "
+ + "refreshInterval=\"60\" method=\"POST\"/>" + "</Content></Module>";
+
private static final String XML_WITH_HTTP_REQUEST_AND_PARAMS = "<Module
xmlns:os=\""
+ PipelinedData.OPENSOCIAL_NAMESPACE + "\">"
+ "<ModulePrefs title=\"Title\"/>"
@@ -141,11 +149,88 @@
}
@Test
- public void testHttpPreload() throws Exception {
- GadgetSpec spec = new GadgetSpec(GADGET_URL, XML_WITH_HTTP_REQUEST);
+ public void testHttpPreloadOfJsonObject() throws Exception {
+ HttpResponse response = new HttpResponseBuilder()
+ .setResponseString("{foo: 'bar'}")
+ .create();
+ String expectedResult = "{data: {status: 200, content: {foo: 'bar'}}, id:
'p'}";
- String httpResult = "{foo: 'bar'}";
- RecordingRequestPipeline pipeline = new
RecordingRequestPipeline(httpResult);
+ verifyHttpPreload(response, expectedResult);
+ }
+
+ @Test
+ public void testHttpPreloadOfJsonArrayWithHeaders() throws Exception {
+ HttpResponse response = new HttpResponseBuilder()
+ .setResponseString("[1, 2]")
+ .addHeader("content-type", "application/json")
+ .addHeader("set-cookie", "cookiecookie")
+ .addHeader("not-ok", "shouldn'tbehere")
+ .create();
+
+ String expectedResult = "{data: {status: 200, headers:" +
+ "{'content-type': ['application/json; charset=UTF-8'],
'set-cookie': ['cookiecookie']}," +
+ "content: [1, 2]}, id: 'p'}";
+
+ verifyHttpPreload(response, expectedResult);
+ }
+
+ @Test
+ public void testHttpPreloadOfJsonWithErrorCode() throws Exception {
+ HttpResponse response = new HttpResponseBuilder()
+ .setResponseString("not found")
+ .addHeader("content-type", "text/html")
+ .setHttpStatusCode(HttpResponse.SC_NOT_FOUND)
+ .create();
+
+ String expectedResult = "{error: {code: 404, data:" +
+ "{headers: {'content-type': ['text/html; charset=UTF-8']}," +
+ "content: 'not found'}}, id: 'p'}";
+
+ verifyHttpPreload(response, expectedResult);
+ }
+
+ @Test
+ public void testHttpPreloadWithBadJson() throws Exception {
+ HttpResponse response = new HttpResponseBuilder()
+ .setResponseString("notjson")
+ .addHeader("content-type", "text/html")
+ .create();
+
+ JSONObject result = new JSONObject(executeHttpPreload(response,
XML_WITH_HTTP_REQUEST));
+ assertFalse(result.has("data"));
+
+ JSONObject error = result.getJSONObject("error");
+ assertEquals(HttpResponse.SC_NOT_ACCEPTABLE, error.getInt("code"));
+ }
+
+ @Test
+ public void testHttpPreloadOfText() throws Exception {
+ HttpResponse response = new HttpResponseBuilder()
+ .setResponseString("{foo: 'bar'}")
+ .addHeader("content-type", "application/json")
+ .create();
+ // Even though the response was actually JSON, @format=text, so the content
+ // will be a block of text
+ String expectedResult = "{data: {status: 200, headers:" +
+ "{'content-type': ['application/json; charset=UTF-8']}," +
+ "content: '{foo: \\'bar\\'}'}, id: 'p'}";
+
+ String resultString = executeHttpPreload(response,
XML_WITH_HTTP_REQUEST_FOR_TEXT);
+ assertEquals(new JSONObject(expectedResult).toString(), resultString);
+ }
+
+ private void verifyHttpPreload(HttpResponse response, String expectedJson)
throws Exception {
+ String resultString = executeHttpPreload(response, XML_WITH_HTTP_REQUEST);
+ assertEquals(new JSONObject(expectedJson).toString(), resultString);
+ }
+
+ /**
+ * Run an HTTP Preload test, returning the String result.
+ */
+ private String executeHttpPreload(HttpResponse response, String xml) throws
Exception {
+ GadgetSpec spec = new GadgetSpec(GADGET_URL, xml);
+
+ RecordingRequestPipeline pipeline = new RecordingRequestPipeline(response);
PipelinedDataPreloader preloader = new PipelinedDataPreloader(pipeline,
containerConfig,
expressions);
view = "profile";
@@ -164,9 +249,6 @@
Collection<Object> result = tasks.iterator().next().call().toJson();
assertEquals(1, result.size());
- String expectedResult = "{data: {foo: 'bar'}, id: 'p'}";
- assertEquals(new JSONObject(expectedResult).toString(),
result.iterator().next().toString());
-
// Should have only fetched one request
assertEquals(1, pipeline.requests.size());
HttpRequest request = pipeline.requests.get(0);
@@ -174,6 +256,8 @@
assertEquals(HTTP_REQUEST_URL, request.getUri().toString());
assertEquals("POST", request.getMethod());
assertEquals(60, request.getCacheTtl());
+
+ return result.iterator().next().toString();
}
@Test
@@ -297,19 +381,21 @@
assertTrue(tasks.isEmpty());
}
- // TODO: test HttpPreloads
-
private static class RecordingRequestPipeline implements RequestPipeline {
public final List<HttpRequest> requests = Lists.newArrayList();
- private final String content;
+ private final HttpResponse response;
public RecordingRequestPipeline(String content) {
- this.content = content;
+ this(new HttpResponseBuilder().setResponseString(content).create());
+ }
+
+ public RecordingRequestPipeline(HttpResponse response) {
+ this.response = response;
}
public HttpResponse execute(HttpRequest request) {
requests.add(request);
- return new HttpResponseBuilder().setResponseString(content).create();
+ return response;
}
public void normalizeProtocol(HttpRequest request) throws GadgetException
{}
Modified:
incubator/shindig/trunk/java/server/src/test/java/org/apache/shindig/server/endtoend/EndToEndTest.java
URL:
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/server/src/test/java/org/apache/shindig/server/endtoend/EndToEndTest.java?rev=761018&r1=761017&r2=761018&view=diff
==============================================================================
---
incubator/shindig/trunk/java/server/src/test/java/org/apache/shindig/server/endtoend/EndToEndTest.java
(original)
+++
incubator/shindig/trunk/java/server/src/test/java/org/apache/shindig/server/endtoend/EndToEndTest.java
Wed Apr 1 19:10:52 2009
@@ -175,7 +175,7 @@
assertEquals("Digg", me.getJSONObject("name").getString("familyName"));
JSONObject json = jsonObjects.get("json").getJSONObject("data");
- JSONObject expected = new JSONObject("{key: 'value'}");
+ JSONObject expected = new JSONObject("{content: {key: 'value'}, status:
200}");
assertEquals(expected.toString(), json.toString());
}
@@ -238,6 +238,12 @@
assertEquals(3, ifList.size());
assertEquals(1, page.getElementsByTagName("b").getLength());
assertEquals(1, getChildrenByTagName(ifList.get(2), "b").size());
+
+ Element jsonPipeline = page.getElementById("json");
+ assertEquals("value", jsonPipeline.getTextContent().trim());
+
+ Element textPipeline = page.getElementById("text");
+ assertEquals("{'key': 'value'}", textPipeline.getTextContent().trim());
}
// HtmlUnits implementation of Element.getElementsByTagName() is just
Modified:
incubator/shindig/trunk/java/server/src/test/resources/endtoend/templateRewriter.xml
URL:
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/server/src/test/resources/endtoend/templateRewriter.xml?rev=761018&r1=761017&r2=761018&view=diff
==============================================================================
---
incubator/shindig/trunk/java/server/src/test/resources/endtoend/templateRewriter.xml
(original)
+++
incubator/shindig/trunk/java/server/src/test/resources/endtoend/templateRewriter.xml
Wed Apr 1 19:10:52 2009
@@ -25,8 +25,10 @@
<Content type="html">
<![CDATA[
<script xmlns:os="http://ns.opensocial.org/2008/markup"
type="text/os-data">
- <os:PeopleRequest key="friends" userId="@viewer"
groupId="@friends"/>
- </script>
+ <os:PeopleRequest key="friends" userId="@viewer" groupId="@friends"/>
+ <os:HttpRequest key="json" href="test.json"/>
+ <os:HttpRequest key="text" href="test.json" format="text"/>
+ </script>
<script type="text/os-template"
xmlns:os="http://ns.opensocial.org/2008/markup">
<ul id="attrs">
@@ -48,7 +50,10 @@
</os:If>
</li>
</ul>
- </script>
+
+ <span id="json">${json.content.key}</span>
+ <span id="text">${text.content}</span>
+ </script>
]]>
</Content>
</Module>