You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@shindig.apache.org by aw...@apache.org on 2009/02/11 20:31:29 UTC
svn commit: r743460 - in /incubator/shindig/trunk/java:
common/src/main/java/org/apache/shindig/expressions/
common/src/test/java/org/apache/shindig/expressions/
gadgets/src/main/java/org/apache/shindig/gadgets/parse/nekohtml/
gadgets/src/main/java/org...
Author: awiner
Date: Wed Feb 11 19:31:29 2009
New Revision: 743460
URL: http://svn.apache.org/viewvc?rev=743460&view=rev
Log:
SHINDIG-905: Implement content rewriter for data pipelining
- Added rewriter and html parser for handling <script type="text/os-data"> sections on the server. This is turned off by default - to enable, bindg GadgetHtmlParser to SocialMarkupHtmlParser and register the PipelineDataContentRewriter.
- Rewrote pipeline data evaluation code to support multiple interdependent batches
TODOs:
- Turn this on by default
- Support errors (needs spec decisions)
- If all os-data sections are fully evaluated, disable the full opensocial-data feature, and only supply DataContext
Added:
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/parse/nekohtml/SocialMarkupHtmlParser.java
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/PipelineDataContentRewriter.java
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/PipelineDataContentRewriterTest.java
Modified:
incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/expressions/RootELResolver.java
incubator/shindig/trunk/java/common/src/test/java/org/apache/shindig/expressions/ExpressionsTest.java
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/parse/nekohtml/NekoSimplifiedHtmlParser.java
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/preload/ConcurrentPreloaderService.java
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/preload/PipelinedDataPreloader.java
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/preload/PreloaderService.java
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/spec/PipelinedData.java
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/render/HtmlRendererTest.java
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/spec/PipelinedDataTest.java
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/spec/ViewTest.java
Modified: incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/expressions/RootELResolver.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/expressions/RootELResolver.java?rev=743460&r1=743459&r2=743460&view=diff
==============================================================================
--- incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/expressions/RootELResolver.java (original)
+++ incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/expressions/RootELResolver.java Wed Feb 11 19:31:29 2009
@@ -26,7 +26,6 @@
import javax.el.ELResolver;
import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Maps;
/**
* ELResolver implementation that adds a map of top-level variables.
@@ -38,15 +37,14 @@
* @see Expressions#newELContext(ELResolver...)
*/
public class RootELResolver extends ELResolver {
- private final Map<String, Object> map;
+ private final Map<String, ? extends Object> map;
public RootELResolver() {
this(ImmutableMap.<String, Object>of());
}
public RootELResolver(Map<String, ? extends Object> base) {
- // TODO: if read-only is OK, then a copy is unnecessary
- map = Maps.newHashMap(base);
+ this.map = base;
}
@Override
@@ -66,7 +64,7 @@
@Override
public Class<?> getType(ELContext context, Object base, Object property) {
- if (base == null) {
+ if (base == null && map.containsKey(property)) {
context.setPropertyResolved(true);
Object value = map.get(property);
return value == null ? null : value.getClass();
@@ -77,7 +75,7 @@
@Override
public Object getValue(ELContext context, Object base, Object property) {
- if (base == null) {
+ if (base == null && map.containsKey(property)) {
context.setPropertyResolved(true);
return map.get(property);
}
@@ -87,8 +85,9 @@
@Override
public boolean isReadOnly(ELContext context, Object base, Object property) {
- if (base == null) {
+ if (base == null && map.containsKey(property)) {
context.setPropertyResolved(true);
+ return true;
}
return false;
@@ -96,9 +95,5 @@
@Override
public void setValue(ELContext context, Object base, Object property, Object value) {
- if (base == null) {
- context.setPropertyResolved(true);
- map.put(property.toString(), value);
- }
}
}
Modified: incubator/shindig/trunk/java/common/src/test/java/org/apache/shindig/expressions/ExpressionsTest.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/common/src/test/java/org/apache/shindig/expressions/ExpressionsTest.java?rev=743460&r1=743459&r2=743460&view=diff
==============================================================================
--- incubator/shindig/trunk/java/common/src/test/java/org/apache/shindig/expressions/ExpressionsTest.java (original)
+++ incubator/shindig/trunk/java/common/src/test/java/org/apache/shindig/expressions/ExpressionsTest.java Wed Feb 11 19:31:29 2009
@@ -19,6 +19,9 @@
package org.apache.shindig.expressions;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import java.util.Map;
import org.json.JSONArray;
import org.json.JSONObject;
@@ -26,19 +29,23 @@
import org.junit.Test;
import javax.el.ELContext;
+import javax.el.PropertyNotFoundException;
import javax.el.ValueExpression;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Maps;
public class ExpressionsTest {
private Expressions expressions;
private ELContext context;
+ private Map<String, Object> variables;
@Before
public void setUp() {
expressions = new Expressions();
- context = expressions.newELContext(new RootELResolver());
+ variables = Maps.newHashMap();
+ context = expressions.newELContext(new RootELResolver(variables));
}
@Test
@@ -91,12 +98,32 @@
assertEquals(expected.toString(), result.toString());
}
+ @Test
+ public void missingJsonSubproperty() throws Exception {
+ addVariable("object", new JSONObject("{foo: 125}"));
+ assertNull(evaluate("${object.bar.baz}", Object.class));
+ }
+
+ @Test
+ public void missingMapSubproperty() throws Exception {
+ addVariable("map", ImmutableMap.of("key", "value"));
+ assertNull(evaluate("${map.bar.baz}", Object.class));
+ }
+
+ @Test(expected = PropertyNotFoundException.class)
+ public void missingTopLevelVariable() throws Exception {
+ // Top-level properties must throw a PropertyNotFoundException when
+ // failing; other properties must not. Pipeline data batching
+ // relies on this
+ assertNull(evaluate("${map.bar.baz}", Object.class));
+ }
+
private <T> T evaluate(String expression, Class<T> type) {
ValueExpression expr = expressions.parse(expression, type);
return type.cast(expr.getValue(context));
}
private void addVariable(String key, Object value) {
- context.getELResolver().setValue(context, null, key, value);
+ variables.put(key, value);
}
}
Modified: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/parse/nekohtml/NekoSimplifiedHtmlParser.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/parse/nekohtml/NekoSimplifiedHtmlParser.java?rev=743460&r1=743459&r2=743460&view=diff
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/parse/nekohtml/NekoSimplifiedHtmlParser.java (original)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/parse/nekohtml/NekoSimplifiedHtmlParser.java Wed Feb 11 19:31:29 2009
@@ -39,6 +39,7 @@
import org.cyberneko.html.HTMLEntities;
import org.cyberneko.html.HTMLScanner;
import org.cyberneko.html.HTMLTagBalancer;
+import org.cyberneko.html.filters.NamespaceBinder;
import org.w3c.dom.DOMImplementation;
import org.w3c.dom.Document;
import org.w3c.dom.DocumentFragment;
@@ -67,26 +68,32 @@
public NekoSimplifiedHtmlParser(DOMImplementation documentFactory) {
this.documentFactory = documentFactory;
}
-
+
@Override
protected Document parseDomImpl(String source) {
+
+ HTMLConfiguration config = newConfiguration();
+
HTMLScanner htmlScanner = new HTMLScanner();
HTMLTagBalancer tagBalancer = new HTMLTagBalancer();
- DocumentHandler handler = new DocumentHandler(source);
- tagBalancer.setDocumentHandler(handler);
+ DocumentHandler handler = newDocumentHandler(source, htmlScanner);
+
+ if (config.getFeature("http://xml.org/sax/features/namespaces")) {
+ NamespaceBinder namespaceBinder = new NamespaceBinder();
+ namespaceBinder.setDocumentHandler(handler);
+ namespaceBinder.setDocumentSource(tagBalancer);
+ namespaceBinder.reset(config);
+ tagBalancer.setDocumentHandler(namespaceBinder);
+ } else {
+ tagBalancer.setDocumentHandler(handler);
+ }
+
+ tagBalancer.setDocumentSource(htmlScanner);
htmlScanner.setDocumentHandler(tagBalancer);
- HTMLConfiguration config = new HTMLConfiguration();
- // Maintain original case for elements and attributes
- config.setProperty("http://cyberneko.org/html/properties/names/elems", "match");
- config.setProperty("http://cyberneko.org/html/properties/names/attrs", "no-change");
- // Parse as fragment.
- config.setFeature("http://cyberneko.org/html/features/balance-tags/document-fragment", true);
- // Get notified of entity and character references
- config.setFeature("http://apache.org/xml/features/scanner/notify-char-refs", true);
- config.setFeature("http://cyberneko.org/html/features/scanner/notify-builtin-refs", true);
tagBalancer.reset(config);
htmlScanner.reset(config);
+
XMLInputSource inputSource = new XMLInputSource(null, null, null);
inputSource.setEncoding("UTF-8");
inputSource.setCharacterStream(new StringReader(source));
@@ -103,10 +110,34 @@
}
}
+ protected HTMLConfiguration newConfiguration() {
+ HTMLConfiguration config = new HTMLConfiguration();
+ // Maintain original case for elements and attributes
+ config.setProperty("http://cyberneko.org/html/properties/names/elems", "match");
+ config.setProperty("http://cyberneko.org/html/properties/names/attrs", "no-change");
+ // Parse as fragment.
+ config.setFeature("http://cyberneko.org/html/features/balance-tags/document-fragment", true);
+ // Get notified of entity and character references
+ config.setFeature("http://apache.org/xml/features/scanner/notify-char-refs", true);
+ config.setFeature("http://cyberneko.org/html/features/scanner/notify-builtin-refs", true);
+ return config;
+ }
+
+ protected DocumentHandler newDocumentHandler(String source, HTMLScanner scanner) {
+ return new DocumentHandler(source);
+ }
+
+ /**
+ * Is the given element important enough to preserve in the DOM?
+ */
+ protected boolean isElementImportant(QName qName) {
+ return elements.contains(qName.rawname.toLowerCase());
+ }
+
/**
* Handler for XNI events from Neko
*/
- private class DocumentHandler implements XMLDocumentHandler {
+ protected class DocumentHandler implements XMLDocumentHandler {
private final Stack<Node> elementStack = new Stack<Node>();
private final StringBuilder builder;
private boolean inEntity = false;
@@ -172,49 +203,60 @@
public void startElement(QName qName, XMLAttributes xmlAttributes, Augmentations augs)
throws XNIException {
- if (elements.contains(qName.rawname.toLowerCase())) {
- if (builder.length() > 0) {
- elementStack.peek().appendChild(document.createTextNode(builder.toString()));
- builder.setLength(0);
- }
- Element element = document.createElement(qName.rawname);
- for (int i = 0; i < xmlAttributes.getLength(); i++) {
- element.setAttribute(xmlAttributes.getLocalName(i) , xmlAttributes.getValue(i));
- }
- elementStack.peek().appendChild(element);
+ if (isElementImportant(qName)) {
+ Element element = startImportantElement(qName, xmlAttributes);
+ // Not an empty element, so push on the stack
elementStack.push(element);
} else {
- builder.append('<').append(qName.rawname);
- for (int i = 0; i < xmlAttributes.getLength(); i++) {
- builder.append(' ').append(xmlAttributes.getLocalName(i)).append("=\"");
- appendAttributeValue(xmlAttributes.getValue(i));
- builder.append('\"');
- }
- builder.append('>');
+ startUnimportantElement(qName, xmlAttributes);
}
}
public void emptyElement(QName qName, XMLAttributes xmlAttributes, Augmentations augs)
throws XNIException {
- if (elements.contains(qName.rawname.toLowerCase())) {
- if (builder.length() > 0) {
- elementStack.peek().appendChild(document.createTextNode(builder.toString()));
- builder.setLength(0);
- }
- Element element = document.createElement(qName.rawname);
- for (int i = 0; i < xmlAttributes.getLength(); i++) {
- element.setAttribute(xmlAttributes.getLocalName(i) , xmlAttributes.getValue(i));
- }
- elementStack.peek().appendChild(element);
+ if (isElementImportant(qName)) {
+ startImportantElement(qName, xmlAttributes);
} else {
- builder.append('<').append(qName.rawname);
- for (int i = 0; i < xmlAttributes.getLength(); i++) {
- builder.append(' ').append(xmlAttributes.getLocalName(i)).append("=\"");
- appendAttributeValue(xmlAttributes.getValue(i));
- builder.append('\"');
+ startUnimportantElement(qName, xmlAttributes);
+ }
+ }
+
+ /** Write an unimportant element into content as raw text */
+ private void startUnimportantElement(QName qName, XMLAttributes xmlAttributes) {
+ builder.append('<').append(qName.rawname);
+ for (int i = 0; i < xmlAttributes.getLength(); i++) {
+ builder.append(' ').append(xmlAttributes.getLocalName(i)).append("=\"");
+ appendAttributeValue(xmlAttributes.getValue(i));
+ builder.append('\"');
+ }
+ builder.append('>');
+ }
+
+ /** Create an Element in the DOM for an important element */
+ private Element startImportantElement(QName qName, XMLAttributes xmlAttributes) {
+ if (builder.length() > 0) {
+ elementStack.peek().appendChild(document.createTextNode(builder.toString()));
+ builder.setLength(0);
+ }
+
+ Element element;
+ // Preserve XML namespace if present
+ if (qName.uri != null) {
+ element = document.createElementNS(qName.uri, qName.rawname);
+ } else {
+ element = document.createElement(qName.rawname);
+ }
+
+ for (int i = 0; i < xmlAttributes.getLength(); i++) {
+ if (xmlAttributes.getURI(i) != null) {
+ element.setAttributeNS(xmlAttributes.getURI(i), xmlAttributes.getQName(i),
+ xmlAttributes.getValue(i));
+ } else {
+ element.setAttribute(xmlAttributes.getLocalName(i) , xmlAttributes.getValue(i));
}
- builder.append('>');
}
+ elementStack.peek().appendChild(element);
+ return element;
}
private void appendAttributeValue(String text) {
@@ -275,7 +317,7 @@
}
public void endElement(QName qName, Augmentations augs) throws XNIException {
- if (elements.contains(qName.rawname.toLowerCase())) {
+ if (isElementImportant(qName)) {
if (builder.length() > 0) {
elementStack.peek().appendChild(document.createTextNode(builder.toString()));
builder.setLength(0);
Added: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/parse/nekohtml/SocialMarkupHtmlParser.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/parse/nekohtml/SocialMarkupHtmlParser.java?rev=743460&view=auto
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/parse/nekohtml/SocialMarkupHtmlParser.java (added)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/parse/nekohtml/SocialMarkupHtmlParser.java Wed Feb 11 19:31:29 2009
@@ -0,0 +1,115 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shindig.gadgets.parse.nekohtml;
+
+import java.io.StringReader;
+
+import org.apache.xerces.xni.Augmentations;
+import org.apache.xerces.xni.QName;
+import org.apache.xerces.xni.XMLAttributes;
+import org.apache.xerces.xni.XMLString;
+import org.apache.xerces.xni.XNIException;
+import org.apache.xerces.xni.parser.XMLInputSource;
+import org.cyberneko.html.HTMLConfiguration;
+import org.cyberneko.html.HTMLScanner;
+import org.w3c.dom.DOMImplementation;
+
+import com.google.inject.Inject;
+
+public class SocialMarkupHtmlParser extends NekoSimplifiedHtmlParser {
+ @Inject
+ public SocialMarkupHtmlParser(DOMImplementation documentProvider) {
+ super(documentProvider);
+ }
+
+ @Override
+ protected boolean isElementImportant(QName name) {
+ // For now, just include everything
+ return true;
+ }
+
+ @Override
+ protected HTMLConfiguration newConfiguration() {
+ HTMLConfiguration config = super.newConfiguration();
+ config.setFeature("http://xml.org/sax/features/namespaces", true);
+ return config;
+ }
+
+ @Override
+ protected DocumentHandler newDocumentHandler(String source, HTMLScanner scanner) {
+ return new SocialMarkupDocumentHandler(source, scanner);
+ }
+
+ private class SocialMarkupDocumentHandler extends DocumentHandler {
+
+ private StringBuilder scriptContent;
+ private boolean inScript = false;
+ private final HTMLScanner scanner;
+
+ public SocialMarkupDocumentHandler(String content, HTMLScanner scanner) {
+ super(content);
+ this.scanner = scanner;
+ }
+
+ @Override
+ public void characters(XMLString text, Augmentations augs) throws XNIException {
+ if (scriptContent != null) {
+ scriptContent.append(text.ch, text.offset, text.length);
+ } else {
+ super.characters(text, augs);
+ }
+ }
+
+ @Override
+ public void endElement(QName name, Augmentations augs) throws XNIException {
+ if (scriptContent != null) {
+ XMLInputSource scriptSource = new XMLInputSource(null, null, null);
+ scriptSource.setCharacterStream(new StringReader(scriptContent.toString()));
+ scriptContent.setLength(0);
+ inScript = true;
+
+ // Evaluate the content of the script block immediately
+ scanner.evaluateInputSource(scriptSource);
+ }
+
+ super.endElement(name, augs);
+ }
+
+ @Override
+ public void startElement(QName name, XMLAttributes xmlAttributes, Augmentations augs)
+ throws XNIException {
+ // Start gathering the content of any os-data or os-template elements
+ if (name.rawname.toLowerCase().equals("script")) {
+ String type = xmlAttributes.getValue("type");
+ if ("text/os-data".equals(type) ||
+ "text/os-template".equals(type)) {
+ if (inScript) {
+ throw new XNIException("Nested OpenSocial script elements");
+ }
+ inScript = true;
+ if (scriptContent == null) {
+ scriptContent = new StringBuilder();
+ }
+ }
+ }
+
+ super.startElement(name, xmlAttributes, augs);
+ }
+ }
+}
Modified: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/preload/ConcurrentPreloaderService.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/preload/ConcurrentPreloaderService.java?rev=743460&r1=743459&r2=743460&view=diff
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/preload/ConcurrentPreloaderService.java (original)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/preload/ConcurrentPreloaderService.java Wed Feb 11 19:31:29 2009
@@ -57,6 +57,10 @@
tasks.addAll(taskCollection);
}
+ return preload(tasks);
+ }
+
+ public Preloads preload(Collection<Callable<PreloadedData>> tasks) {
ConcurrentPreloads preloads = new ConcurrentPreloads();
int processed = tasks.size();
for (Callable<PreloadedData> task : tasks) {
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=743460&r1=743459&r2=743460&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 Feb 11 19:31:29 2009
@@ -23,10 +23,12 @@
import org.apache.shindig.config.ContainerConfig;
import org.apache.shindig.gadgets.AuthType;
import org.apache.shindig.gadgets.GadgetContext;
+import org.apache.shindig.gadgets.GadgetELResolver;
import org.apache.shindig.gadgets.http.HttpRequest;
import org.apache.shindig.gadgets.http.HttpResponse;
import org.apache.shindig.gadgets.http.RequestPipeline;
import org.apache.shindig.gadgets.spec.GadgetSpec;
+import org.apache.shindig.gadgets.spec.PipelinedData;
import org.apache.shindig.gadgets.spec.RequestAuthenticationInfo;
import org.apache.shindig.gadgets.spec.View;
@@ -47,6 +49,8 @@
import java.util.Map;
import java.util.concurrent.Callable;
+import javax.el.ELResolver;
+
/**
* Preloader for loading Data Pipelining Preload data.
*/
@@ -62,6 +66,7 @@
this.config = config;
}
+ /** Create preloads from a gadget view */
public Collection<Callable<PreloadedData>> createPreloadTasks(GadgetContext context,
GadgetSpec gadget, PreloaderService.PreloadPhase phase) {
View view = gadget.getView(context.getView());
@@ -69,36 +74,43 @@
&& view.getPipelinedData() != null
&& phase == PreloaderService.PreloadPhase.PROXY_FETCH) {
- List<Callable<PreloadedData>> preloadList = Lists.newArrayList();
- Map<String, Object> socialPreloads = view.getPipelinedData().getSocialPreloads(context);
+ ELResolver resolver = new GadgetELResolver(context);
+ PipelinedData.Batch batch = view.getPipelinedData().getBatch(resolver);
+ if (batch != null) {
+ return createPreloadTasks(context, batch);
+ }
+ }
- // Load any social preloads into a JSONArray for delivery to
- // JsonRpcServlet
- if (!socialPreloads.isEmpty()) {
- JSONArray array = new JSONArray();
- for (Object socialRequest : socialPreloads.values()) {
- array.put(socialRequest);
- }
+ return Collections.emptyList();
+ }
- Callable<PreloadedData> preloader = new SocialPreloadTask(context, array);
- preloadList.add(preloader);
+ /** Create preload tasks from an explicit list of social and http preloads */
+ public Collection<Callable<PreloadedData>> createPreloadTasks(GadgetContext context,
+ PipelinedData.Batch batch) {
+ List<Callable<PreloadedData>> preloadList = Lists.newArrayList();
+
+ // Load any social preloads into a JSONArray for delivery to
+ // JsonRpcServlet
+ if (!batch.getSocialPreloads().isEmpty()) {
+ JSONArray array = new JSONArray();
+ for (Object socialRequest : batch.getSocialPreloads().values()) {
+ array.put(socialRequest);
}
- Map<String, RequestAuthenticationInfo> httpPreloads =
- view.getPipelinedData().getHttpPreloads(context);
- if (!httpPreloads.isEmpty()) {
- for (Map.Entry<String, RequestAuthenticationInfo> httpPreloadEntry
- : httpPreloads.entrySet()) {
- preloadList.add(new HttpPreloadTask(context, httpPreloadEntry.getValue(),
- httpPreloadEntry.getKey()));
- }
+ Callable<PreloadedData> preloader = new SocialPreloadTask(context, array);
+ preloadList.add(preloader);
+ }
+ if (!batch.getHttpPreloads().isEmpty()) {
+ for (Map.Entry<String, RequestAuthenticationInfo> httpPreloadEntry
+ : batch.getHttpPreloads().entrySet()) {
+ preloadList.add(new HttpPreloadTask(context, httpPreloadEntry.getValue(),
+ httpPreloadEntry.getKey()));
}
- return preloadList;
}
- return Collections.emptyList();
+ return preloadList;
}
/**
@@ -130,6 +142,8 @@
// Unpack the response into a map of PreloadedData responses
final Map<String, Object> data = Maps.newHashMap();
+ // TODO: if the entire request fails, the result is an object,
+ // not an array
JSONArray array = new JSONArray(response.getResponseAsString());
for (int i = 0; i < array.length(); i++) {
JSONObject arrayElement = array.getJSONObject(i);
Modified: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/preload/PreloaderService.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/preload/PreloaderService.java?rev=743460&r1=743459&r2=743460&view=diff
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/preload/PreloaderService.java (original)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/preload/PreloaderService.java Wed Feb 11 19:31:29 2009
@@ -21,6 +21,9 @@
import org.apache.shindig.gadgets.GadgetContext;
import org.apache.shindig.gadgets.spec.GadgetSpec;
+import java.util.Collection;
+import java.util.concurrent.Callable;
+
/**
* Handles preloading operations, such as HTTP fetches, social data retrieval, or anything else that
* would benefit from preloading on the server instead of incurring a network request for users.
@@ -51,4 +54,9 @@
* @return The preloads for the gadget.
*/
Preloads preload(GadgetContext context, GadgetSpec gadget, PreloadPhase phase);
+
+ /**
+ * Execute preloads with a specific set of preload tasks.
+ */
+ Preloads preload(Collection<Callable<PreloadedData>> tasks);
}
Added: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/PipelineDataContentRewriter.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/PipelineDataContentRewriter.java?rev=743460&view=auto
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/PipelineDataContentRewriter.java (added)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/PipelineDataContentRewriter.java Wed Feb 11 19:31:29 2009
@@ -0,0 +1,217 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shindig.gadgets.rewrite;
+
+import org.apache.shindig.common.JsonSerializer;
+import org.apache.shindig.common.xml.DomUtil;
+import org.apache.shindig.expressions.RootELResolver;
+import org.apache.shindig.gadgets.Gadget;
+import org.apache.shindig.gadgets.GadgetELResolver;
+import org.apache.shindig.gadgets.http.HttpRequest;
+import org.apache.shindig.gadgets.http.HttpResponse;
+import org.apache.shindig.gadgets.preload.PipelinedDataPreloader;
+import org.apache.shindig.gadgets.preload.PreloadException;
+import org.apache.shindig.gadgets.preload.PreloadedData;
+import org.apache.shindig.gadgets.preload.PreloaderService;
+import org.apache.shindig.gadgets.preload.Preloads;
+import org.apache.shindig.gadgets.spec.PipelinedData;
+import org.apache.shindig.gadgets.spec.SpecParserException;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.traversal.DocumentTraversal;
+import org.w3c.dom.traversal.NodeFilter;
+import org.w3c.dom.traversal.NodeIterator;
+
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Callable;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.el.CompositeELResolver;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.inject.Inject;
+
+/**
+ * ContentRewriter that resolves opensocial-data elements on the server.
+ *
+ * This rewriter cannot be used currently without the SocialMarkupHtmlParser.
+ */
+public class PipelineDataContentRewriter implements ContentRewriter {
+
+ private static final Logger logger = Logger.getLogger(
+ PipelineDataContentRewriter.class.getName());
+ // TODO: support configuration
+ private static final int MAX_BATCH_COUNT = 3;
+
+ private final PipelinedDataPreloader preloader;
+ private final PreloaderService preloaderService;
+
+ @Inject
+ public PipelineDataContentRewriter(PipelinedDataPreloader preloader,
+ PreloaderService preloaderService) {
+ this.preloader = preloader;
+ this.preloaderService = preloaderService;
+ }
+
+ public RewriterResults rewrite(HttpRequest request, HttpResponse original, MutableContent content) {
+ return null;
+ }
+
+ public RewriterResults rewrite(Gadget gadget, MutableContent content) {
+ // Only bother for gadgets using the opensocial-data feature
+ if (!gadget.getSpec().getModulePrefs().getFeatures().containsKey("opensocial-data")) {
+ return null;
+ }
+
+ Document doc = content.getDocument();
+ NodeIterator nodeIterator = ((DocumentTraversal) doc)
+ .createNodeIterator(doc, NodeFilter.SHOW_ELEMENT,
+ new NodeFilter() {
+ public short acceptNode(Node n) {
+ if ("script".equalsIgnoreCase(n.getNodeName()) &&
+ "text/os-data".equals(((Element) n).getAttribute("type"))) {
+ return NodeFilter.FILTER_ACCEPT;
+ }
+ return NodeFilter.FILTER_REJECT;
+ }
+ }, false);
+
+
+ Map<String, JSONObject> results = Maps.newHashMap();
+
+ // Use the default objects in the GadgetContext, and any objects that
+ // have been resolved
+ List<PipelineState> pipelines = Lists.newArrayList();
+ CompositeELResolver rootObjects = new CompositeELResolver();
+ rootObjects.add(new GadgetELResolver(gadget.getContext()));
+ rootObjects.add(new RootELResolver(results));
+
+ for (Node n = nodeIterator.nextNode(); n != null ; n = nodeIterator.nextNode()) {
+ try {
+ PipelinedData pipelineData = new PipelinedData((Element) n, gadget.getSpec().getUrl());
+ PipelinedData.Batch batch = pipelineData.getBatch(rootObjects);
+ if (batch == null) {
+ // An empty pipeline element - just remove it
+ n.getParentNode().removeChild(n);
+ } else {
+ // Not empty, ready it
+ PipelineState state = new PipelineState();
+ state.batch = batch;
+ state.node = n;
+ pipelines.add(state);
+ }
+ } catch (SpecParserException e) {
+ // Leave the element to the client
+ logger.log(Level.INFO, "Failed to parse preload in " + gadget.getSpec().getUrl(), e);
+ }
+ }
+
+ // No pipline elements found, return early
+ if (pipelines.isEmpty()) {
+ return null;
+ }
+
+ // Run batches until we run out
+ int batchCount = 0;
+ while (true) {
+ // Gather all tasks from the first batch
+ List<Callable<PreloadedData>> tasks = Lists.newArrayList();
+ for (PipelineState pipeline : pipelines) {
+ if (pipeline.batch != null) {
+ tasks.addAll(preloader.createPreloadTasks(gadget.getContext(), pipeline.batch));
+ }
+ }
+
+ // No further progress - quit
+ if (tasks.isEmpty()) {
+ break;
+ }
+
+ // And run the pipeline
+ Preloads preloads = preloaderService.preload(tasks);
+ for (PreloadedData preloaded : preloads.getData()) {
+ try {
+ for (Map.Entry<String, Object> entry : preloaded.toJson().entrySet()) {
+ JSONObject obj = (JSONObject) entry.getValue();
+ if (obj.has("data")) {
+ results.put(entry.getKey(), obj.getJSONObject("data"));
+ }
+ // TODO: handle errors?
+ }
+ } catch (PreloadException pe) {
+ // This will be thrown in the event of some unexpected exception. We can move on.
+ logger.log(Level.WARNING, "Unexpected error when preloading", pe);
+ } catch (JSONException je) {
+ throw new RuntimeException(je);
+ }
+ }
+
+ // Advance to the next batch
+ for (PipelineState pipeline : pipelines) {
+ if (pipeline.batch != null) {
+ pipeline.batch = pipeline.batch.getNextBatch(rootObjects);
+ // Once there are no more batches, delete the associated script node.
+ if (pipeline.batch == null) {
+ pipeline.node.getParentNode().removeChild(pipeline.node);
+ }
+ }
+ }
+
+ // TODO: necessary?
+ if (batchCount++ >= MAX_BATCH_COUNT) {
+ break;
+ }
+ }
+
+ Element head = (Element) DomUtil.getFirstNamedChildNode(doc.getDocumentElement(), "head");
+ Element pipelineScript = doc.createElement("script");
+ pipelineScript.setAttribute("type", "text/javascript");
+
+ StringBuilder script = new StringBuilder();
+ for (Map.Entry<String, JSONObject> entry : results.entrySet()) {
+ String key = entry.getKey();
+
+ // TODO: escape key
+ // TODO: push to MutableContent
+ script.append("osd.DataContext.putDataSet(\"")
+ .append(key)
+ .append("\",")
+ .append(JsonSerializer.serialize(entry.getValue()))
+ .append(");");
+ }
+
+ pipelineScript.appendChild(doc.createTextNode(script.toString()));
+ head.appendChild(pipelineScript);
+ MutableContent.notifyEdit(doc);
+
+ return RewriterResults.notCacheable();
+ }
+
+ static class PipelineState {
+ public Node node;
+ public PipelinedData.Batch batch;
+
+ }
+}
Modified: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/spec/PipelinedData.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/spec/PipelinedData.java?rev=743460&r1=743459&r2=743460&view=diff
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/spec/PipelinedData.java (original)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/spec/PipelinedData.java Wed Feb 11 19:31:29 2009
@@ -17,6 +17,13 @@
*/
package org.apache.shindig.gadgets.spec;
+import org.apache.shindig.common.uri.Uri;
+import org.apache.shindig.common.xml.XmlException;
+import org.apache.shindig.expressions.Expressions;
+import org.apache.shindig.gadgets.AuthType;
+import org.apache.shindig.gadgets.GadgetELResolver;
+import org.apache.shindig.gadgets.variables.Substitutions;
+
import java.util.Collections;
import java.util.List;
import java.util.Map;
@@ -25,15 +32,10 @@
import javax.el.ELContext;
import javax.el.ELException;
+import javax.el.ELResolver;
+import javax.el.PropertyNotFoundException;
import javax.el.ValueExpression;
-import org.apache.shindig.common.uri.Uri;
-import org.apache.shindig.common.xml.XmlException;
-import org.apache.shindig.expressions.Expressions;
-import org.apache.shindig.gadgets.AuthType;
-import org.apache.shindig.gadgets.GadgetContext;
-import org.apache.shindig.gadgets.GadgetELResolver;
-import org.apache.shindig.gadgets.variables.Substitutions;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
@@ -131,39 +133,122 @@
public PipelinedData substitute(Substitutions substituter) {
return new PipelinedData(this, substituter);
}
-
- public Map<String, Object> getSocialPreloads(GadgetContext context) {
- Map<String, Object> evaluatedPreloads = Maps.newHashMapWithExpectedSize(
- socialPreloads.size());
- Expressions expressions = Expressions.sharedInstance();
- ELContext elContext = expressions.newELContext(new GadgetELResolver(context));
- for (Map.Entry<String, SocialData> preload : socialPreloads.entrySet()) {
- try {
- evaluatedPreloads.put(preload.getKey(), preload.getValue().toJson(elContext));
- } catch (ELException e) {
- // TODO: Handle!?!
- throw new RuntimeException(e);
- }
- }
-
- return evaluatedPreloads;
+
+ public interface Batch {
+ Map<String, Object> getSocialPreloads();
+ Map<String, RequestAuthenticationInfo> getHttpPreloads();
+ Batch getNextBatch(ELResolver rootObjects);
}
- public Map<String, RequestAuthenticationInfo> getHttpPreloads(GadgetContext context) {
- Map<String, RequestAuthenticationInfo> evaluatedPreloads = Maps.newHashMapWithExpectedSize(
- httpPreloads.size());
+ /**
+ * Gets the first batch of preload requests. Preloads that require root
+ * objects not yet available will not be executed in this batch, but may
+ * become available in subsequent batches.
+ *
+ * @param rootObjects an ELResolver that can evaluate currently available
+ * root objects.
+ * @see GadgetELResolver
+ * @return a batch, or null if no batch could be created
+ */
+ public Batch getBatch(ELResolver rootObjects) {
+ return getBatch(rootObjects, socialPreloads, httpPreloads);
+ }
+
+ /**
+ * Create a Batch of preload requests
+ * @param rootObjects an ELResolver that can evaluate currently available
+ * root objects.
+ * @param currentSocialPreloads the remaining social preloads
+ * @param currentHttpPreloads the remaining http preloads
+ */
+ private Batch getBatch(ELResolver rootObjects, Map<String, SocialData> currentSocialPreloads,
+ Map<String, HttpData> currentHttpPreloads) {
Expressions expressions = Expressions.sharedInstance();
- ELContext elContext = expressions.newELContext(new GadgetELResolver(context));
- for (Map.Entry<String, HttpData> preload : httpPreloads.entrySet()) {
- try {
- evaluatedPreloads.put(preload.getKey(), preload.getValue().evaluate(elContext));
- } catch (ELException e) {
- // TODO: Handle!?!
- throw new RuntimeException(e);
+ ELContext elContext = expressions.newELContext(rootObjects);
+
+ // Evaluate all existing social preloads
+ Map<String, Object> evaluatedSocialPreloads = Maps.newHashMap();
+ Map<String, SocialData> pendingSocialPreloads = null;
+
+ if (currentSocialPreloads != null) {
+ for (Map.Entry<String, SocialData> preload : currentSocialPreloads.entrySet()) {
+ try {
+ Object value = preload.getValue().toJson(elContext);
+ evaluatedSocialPreloads.put(preload.getKey(), value);
+ } catch (PropertyNotFoundException pnfe) {
+ // Missing top-level property: put it in the pending set
+ if (pendingSocialPreloads == null) {
+ pendingSocialPreloads = Maps.newHashMap();
+ }
+ pendingSocialPreloads.put(preload.getKey(), preload.getValue());
+ } catch (ELException e) {
+ // TODO: Handle!?!
+ throw new RuntimeException(e);
+ }
+ }
+ }
+ // And evaluate all existing HTTP preloads
+ Map<String, RequestAuthenticationInfo> evaluatedHttpPreloads = Maps.newHashMap();
+ Map<String, HttpData> pendingHttpPreloads = null;
+
+ if (currentHttpPreloads != null) {
+ for (Map.Entry<String, HttpData> preload : currentHttpPreloads.entrySet()) {
+ try {
+ RequestAuthenticationInfo value = preload.getValue().evaluate(elContext);
+ evaluatedHttpPreloads.put(preload.getKey(), value);
+ } catch (PropertyNotFoundException pnfe) {
+ if (pendingHttpPreloads == null) {
+ pendingHttpPreloads = Maps.newHashMap();
+ }
+ pendingHttpPreloads.put(preload.getKey(), preload.getValue());
+ } catch (ELException e) {
+ // TODO: Handle!?!
+ throw new RuntimeException(e);
+ }
}
}
+
+ // Nothing evaluated or pending; return null for the batch. Note that
+ // there may be multiple PipelinedData objects (e.g., from multiple
+ // <script type="text/os-data"> elements), so even if all evaluations
+ // fail here, evaluations might succeed elsewhere and free up pending preloads
+ if (evaluatedSocialPreloads.isEmpty() && evaluatedHttpPreloads.isEmpty() &&
+ pendingHttpPreloads == null && pendingSocialPreloads == null) {
+ return null;
+ }
+
+ return new BatchImpl(evaluatedSocialPreloads, evaluatedHttpPreloads,
+ pendingSocialPreloads, pendingHttpPreloads);
+ }
+
+ /** Batch implementation */
+ class BatchImpl implements Batch {
+
+ private final Map<String, Object> evaluatedSocialPreloads;
+ private final Map<String, RequestAuthenticationInfo> evaluatedHttpPreloads;
+ private final Map<String, SocialData> pendingSocialPreloads;
+ private final Map<String, HttpData> pendingHttpPreloads;
+
+ public BatchImpl(Map<String, Object> evaluatedSocialPreloads,
+ Map<String, RequestAuthenticationInfo> evaluatedHttpPreloads,
+ Map<String, SocialData> pendingSocialPreloads, Map<String, HttpData> pendingHttpPreloads) {
+ this.evaluatedSocialPreloads = evaluatedSocialPreloads;
+ this.evaluatedHttpPreloads = evaluatedHttpPreloads;
+ this.pendingSocialPreloads = pendingSocialPreloads;
+ this.pendingHttpPreloads = pendingHttpPreloads;
+ }
+
+ public Map<String, Object> getSocialPreloads() {
+ return evaluatedSocialPreloads;
+ }
+
+ public Map<String, RequestAuthenticationInfo> getHttpPreloads() {
+ return evaluatedHttpPreloads;
+ }
- return evaluatedPreloads;
+ public Batch getNextBatch(ELResolver rootObjects) {
+ return getBatch(rootObjects, pendingSocialPreloads, pendingHttpPreloads);
+ }
}
public boolean needsViewer() {
Modified: incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/render/HtmlRendererTest.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/render/HtmlRendererTest.java?rev=743460&r1=743459&r2=743460&view=diff
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/render/HtmlRendererTest.java (original)
+++ incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/render/HtmlRendererTest.java Wed Feb 11 19:31:29 2009
@@ -54,6 +54,7 @@
import java.util.Collections;
import java.util.Locale;
import java.util.Map;
+import java.util.concurrent.Callable;
/**
* Tests for HtmlRenderer
@@ -360,6 +361,11 @@
wasPreloaded = true;
return preloads;
}
+
+ public Preloads preload(Collection<Callable<PreloadedData>> tasks) {
+ wasPreloaded = true;
+ return preloads;
+ }
}
private static class FakePreloads implements Preloads {
Added: incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/PipelineDataContentRewriterTest.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/PipelineDataContentRewriterTest.java?rev=743460&view=auto
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/PipelineDataContentRewriterTest.java (added)
+++ incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/PipelineDataContentRewriterTest.java Wed Feb 11 19:31:29 2009
@@ -0,0 +1,315 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shindig.gadgets.rewrite;
+
+import static org.easymock.EasyMock.and;
+import static org.easymock.EasyMock.capture;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.reportMatcher;
+import static org.easymock.EasyMock.same;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import org.apache.shindig.common.uri.Uri;
+import org.apache.shindig.gadgets.Gadget;
+import org.apache.shindig.gadgets.GadgetContext;
+import org.apache.shindig.gadgets.parse.ParseModule;
+import org.apache.shindig.gadgets.parse.nekohtml.SocialMarkupHtmlParser;
+import org.apache.shindig.gadgets.preload.ConcurrentPreloaderService;
+import org.apache.shindig.gadgets.preload.PipelinedDataPreloader;
+import org.apache.shindig.gadgets.preload.PreloadException;
+import org.apache.shindig.gadgets.preload.PreloadedData;
+import org.apache.shindig.gadgets.preload.PreloaderService;
+import org.apache.shindig.gadgets.spec.GadgetSpec;
+import org.apache.shindig.gadgets.spec.PipelinedData;
+import org.apache.shindig.gadgets.spec.RequestAuthenticationInfo;
+import org.apache.shindig.gadgets.spec.SpecParserException;
+import org.easymock.Capture;
+import org.easymock.IArgumentMatcher;
+import org.easymock.classextension.EasyMock;
+import org.easymock.classextension.IMocksControl;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Map;
+import java.util.concurrent.Callable;
+import java.util.concurrent.Executors;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+/**
+ * Test of PipelineDataContentRewriter.
+ */
+public class PipelineDataContentRewriterTest {
+
+ private IMocksControl control;
+ private PipelinedDataPreloader preloader;
+ private PreloaderService preloaderService;
+ private PipelineDataContentRewriter rewriter;
+ private GadgetSpec gadgetSpec;
+ private Gadget gadget;
+ private MutableContent content;
+ private static final Uri GADGET_URI = Uri.parse("http://example.org/gadget.php");
+
+ private static final String CONTENT =
+ "<script xmlns:os='http://ns.opensocial.org/2008/markup' type='text/os-data'>"
+ + " <os:PeopleRequest key='me' userId='canonical'/>"
+ + " <os:MakeRequest key='json' href='test.json'/>"
+ + "</script>";
+
+ // Two requests, one depends on the other
+ private static final String TWO_BATCH_CONTENT =
+ "<script xmlns:os='http://ns.opensocial.org/2008/markup' type='text/os-data'>"
+ + " <os:PeopleRequest key='me' userId='${json.user}'/>"
+ + " <os:MakeRequest key='json' href='${ViewParams.file}'/>"
+ + "</script>";
+
+ // One request, but it requires data that isn't present
+ private static final String BLOCKED_FIRST_BATCH_CONTENT =
+ "<script xmlns:os='http://ns.opensocial.org/2008/markup' type='text/os-data'>"
+ + " <os:PeopleRequest key='me' userId='${json.user}'/>"
+ + "</script>";
+
+ private static final String XML_WITHOUT_FEATURE = "<Module>" + "<ModulePrefs title='Title'>"
+ + "</ModulePrefs>" + "<Content>" + " <![CDATA[" + CONTENT + "]]></Content></Module>";
+
+ private static final String XML_WITHOUT_PIPELINE = "<Module>" + "<ModulePrefs title='Title'>"
+ + "<Require feature='opensocial-data'/>" + "</ModulePrefs>" + "<Content/></Module>";
+
+ @Before
+ public void setUp() throws Exception {
+ control = EasyMock.createStrictControl();
+ preloader = control.createMock(PipelinedDataPreloader.class);
+// preloaderService = control.createMock(PreloaderService.class);
+ preloaderService = new ConcurrentPreloaderService(Executors.newSingleThreadExecutor(), null);
+ rewriter = new PipelineDataContentRewriter(preloader, preloaderService);
+ }
+
+ private void setupGadget(String gadgetXml) throws SpecParserException {
+ gadgetSpec = new GadgetSpec(GADGET_URI, gadgetXml);
+ gadget = new Gadget();
+ gadget.setSpec(gadgetSpec);
+ gadget.setContext(new GadgetContext() {});
+ gadget.setCurrentView(gadgetSpec.getView("default"));
+
+ content = new MutableContent(new SocialMarkupHtmlParser(
+ new ParseModule.DOMImplementationProvider().get()), gadget.getCurrentView().getContent());
+ }
+
+ @Test
+ public void rewrite() throws Exception {
+ setupGadget(getGadgetXml(CONTENT));
+
+ Capture<PipelinedData.Batch> batchCapture =
+ new Capture<PipelinedData.Batch>();
+
+ // Dummy return results (the "real" return would have two values)
+ Callable<PreloadedData> callable = createPreloadTask(
+ "key", "{data: {foo: 'bar'}}");
+
+ // One batch with 1 each HTTP and Social preload
+ expect(preloader.createPreloadTasks(same(gadget.getContext()),
+ and(eqBatch(1, 1), capture(batchCapture))))
+ .andReturn(ImmutableList.of(callable));
+
+ control.replay();
+
+ rewriter.rewrite(gadget, content);
+
+ // Verify the data set is injected, and the os-data was deleted
+ assertTrue("Script not inserted", content.getContent().indexOf(
+ "DataContext.putDataSet(\"key\",{\"foo\":\"bar\"})") >= 0);
+ assertFalse("os-data wasn't deleted",
+ content.getContent().indexOf("type=\"text/os-data\"") >= 0);
+
+ assertTrue(batchCapture.getValue().getSocialPreloads().containsKey("me"));
+ assertTrue(batchCapture.getValue().getHttpPreloads().containsKey("json"));
+
+ control.verify();
+ }
+
+ @Test
+ public void rewriteWithTwoBatches() throws Exception {
+ setupGadget(getGadgetXml(TWO_BATCH_CONTENT));
+
+ gadget.setContext(new GadgetContext() {
+ @Override
+ public String getParameter(String property) {
+ // Provide the filename to be requested in the first batch
+ if ("view-params".equals(property)) {
+ return "{'file': 'test.json'}";
+ }
+ return null;
+ }
+ });
+
+ // First batch, the HTTP fetch
+ Capture<PipelinedData.Batch> firstBatch =
+ new Capture<PipelinedData.Batch>();
+ Callable<PreloadedData> firstTask = createPreloadTask("json",
+ "{data: {user: 'canonical'}}");
+
+ // Second batch, the user fetch
+ Capture<PipelinedData.Batch> secondBatch =
+ new Capture<PipelinedData.Batch>();
+ Callable<PreloadedData> secondTask = createPreloadTask("me",
+ "{data: {'id':'canonical'}}");
+
+ // First, a batch with an HTTP request
+ expect(
+ preloader.createPreloadTasks(same(gadget.getContext()),
+ and(eqBatch(0, 1), capture(firstBatch))))
+ .andReturn(ImmutableList.of(firstTask));
+ // Second, a batch with a social request
+ expect(
+ preloader.createPreloadTasks(same(gadget.getContext()),
+ and(eqBatch(1, 0), capture(secondBatch))))
+ .andReturn(ImmutableList.of(secondTask));
+
+ control.replay();
+
+ rewriter.rewrite(gadget, content);
+
+ control.verify();
+
+ // Verify the data set is injected, and the os-data was deleted
+ assertTrue("First batch not inserted", content.getContent().indexOf(
+ "DataContext.putDataSet(\"json\",{\"user\":\"canonical\"})") >= 0);
+ assertTrue("Second batch not inserted", content.getContent().indexOf(
+ "DataContext.putDataSet(\"me\",{\"id\":\"canonical\"})") >= 0);
+ assertFalse("os-data wasn't deleted",
+ content.getContent().indexOf("type=\"text/os-data\"") >= 0);
+
+ // Check the evaluated HTTP request
+ RequestAuthenticationInfo request = firstBatch.getValue().getHttpPreloads().get("json");
+ assertEquals("http://example.org/test.json", request.getHref().toString());
+
+ // Check the evaluated person request
+ JSONObject personRequest = (JSONObject) secondBatch.getValue().getSocialPreloads().get("me");
+ assertEquals("canonical", personRequest.getJSONObject("params").getJSONArray("userId").get(0));
+ }
+
+ @Test
+ public void rewriteWithBlockedBatch() throws Exception {
+ setupGadget(getGadgetXml(BLOCKED_FIRST_BATCH_CONTENT));
+
+ // Expect a batch with no content
+ expect(
+ preloader.createPreloadTasks(same(gadget.getContext()), eqBatch(0, 0)))
+ .andReturn(ImmutableList.<Callable<PreloadedData>>of());
+
+ control.replay();
+
+ rewriter.rewrite(gadget, content);
+
+ control.verify();
+
+ // Check there is no DataContext inserted
+ assertFalse("DataContext write shouldn't be present", content.getContent().indexOf(
+ "DataContext.putDataSet(") > 0);
+ // And the os-data elements should be present
+ assertTrue("os-data was deleted",
+ content.getContent().indexOf("type=\"text/os-data\"") > 0);
+ }
+
+ /** Match a batch with the specified count of social and HTTP data items */
+ private PipelinedData.Batch eqBatch(int socialCount, int httpCount) {
+ reportMatcher(new BatchMatcher(socialCount, httpCount));
+ return null;
+ }
+
+ private static class BatchMatcher implements IArgumentMatcher {
+ private final int socialCount;
+ private final int httpCount;
+
+ public BatchMatcher(int socialCount, int httpCount) {
+ this.socialCount = socialCount;
+ this.httpCount = httpCount;
+ }
+
+ public void appendTo(StringBuffer buffer) {
+ buffer.append("eqBuffer[social=" + socialCount + ",http=" + httpCount + "]");
+ }
+
+ public boolean matches(Object obj) {
+ if (!(obj instanceof PipelinedData.Batch)) {
+ return false;
+ }
+
+ PipelinedData.Batch batch = (PipelinedData.Batch) obj;
+ return (socialCount == batch.getSocialPreloads().size()
+ && httpCount == batch.getHttpPreloads().size());
+ }
+
+ }
+
+ @Test
+ public void rewriteWithoutPipeline() throws Exception {
+ setupGadget(XML_WITHOUT_PIPELINE);
+ control.replay();
+
+ // If there are no pipeline elements, the rewrite is a no-op
+ assertNull(rewriter.rewrite(gadget, content));
+
+ control.verify();
+ }
+
+ @Test
+ public void rewriteWithoutFeature() throws Exception {
+ // If the opensocial-data feature is present, the rewrite is a no-op
+ setupGadget(XML_WITHOUT_FEATURE);
+
+ control.replay();
+
+ assertNull(rewriter.rewrite(gadget, content));
+
+ control.verify();
+ }
+
+ /** Create a mock Callable for a single preload task */
+ private Callable<PreloadedData> createPreloadTask(final String key, String jsonResult)
+ throws JSONException {
+ final Object value = new JSONObject(jsonResult);
+ final PreloadedData preloadResult = new PreloadedData() {
+ public Map<String, Object> toJson() throws PreloadException {
+ return ImmutableMap.of(key, value);
+ }
+ };
+
+ Callable<PreloadedData> callable = new Callable<PreloadedData>() {
+ public PreloadedData call() throws Exception {
+ return preloadResult;
+ }
+ };
+ return callable;
+ }
+
+ private static String getGadgetXml(String content) {
+ return "<Module>" + "<ModulePrefs title='Title'>"
+ + "<Require feature='opensocial-data'/>" + "</ModulePrefs>"
+ + "<Content>"
+ + " <![CDATA[" + content + "]]>"
+ + "</Content></Module>";
+ }
+}
Modified: incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/spec/PipelinedDataTest.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/spec/PipelinedDataTest.java?rev=743460&r1=743459&r2=743460&view=diff
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/spec/PipelinedDataTest.java (original)
+++ incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/spec/PipelinedDataTest.java Wed Feb 11 19:31:29 2009
@@ -21,27 +21,35 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import org.apache.shindig.common.uri.Uri;
import org.apache.shindig.common.xml.XmlUtil;
+import org.apache.shindig.expressions.RootELResolver;
import org.apache.shindig.gadgets.AuthType;
-import org.apache.shindig.gadgets.GadgetContext;
import java.util.Map;
+import javax.el.ELResolver;
+
import org.json.JSONObject;
import org.junit.Before;
import org.junit.Test;
+import com.google.common.collect.Maps;
+
public class PipelinedDataTest {
- //TODO: test os:MakeRequest
- private GadgetContext context;
+ private static final Uri GADGET_URI = Uri.parse("http://example.org/");
+ private ELResolver elResolver;
+ private Map<String, Object> elValues;
@Before
public void setUp() {
- context = new GadgetContext();
+ elValues = Maps.newHashMap();
+ elResolver = new RootELResolver(elValues);
+
}
@Test
@@ -67,8 +75,11 @@
+ "fields: ['name','id']"
+ "}}");
- assertEquals(1, socialData.getSocialPreloads(context).size());
- assertEquals(expected.toString(), socialData.getSocialPreloads(context).get("key").toString());
+ PipelinedData.Batch batch = socialData.getBatch(elResolver);
+ assertTrue(batch.getHttpPreloads().isEmpty());
+ assertEquals(1, batch.getSocialPreloads().size());
+ assertEquals(expected.toString(), batch.getSocialPreloads().get("key").toString());
+ assertNull(batch.getNextBatch(elResolver));
}
@Test
@@ -78,10 +89,13 @@
+ " groupId=\"group\""
+ " userId=\"first,second\""
+ " startIndex=\"20\""
- + " count=\"10\""
- + " fields=\"name,id\""
+ + " count=\"${count}\""
+ + " fields=\"${fields}\""
+ "/></Content>";
+ elValues.put("count", 10);
+ // TODO: try List, JSONArray
+ elValues.put("fields", "name,id");
PipelinedData socialData = new PipelinedData(XmlUtil.parse(xml), null);
assertFalse(socialData.needsOwner());
assertFalse(socialData.needsViewer());
@@ -94,8 +108,10 @@
+ "fields: ['name','id']"
+ "}}");
- assertEquals(1, socialData.getSocialPreloads(context).size());
- assertEquals(expected.toString(), socialData.getSocialPreloads(context).get("key").toString());
+ PipelinedData.Batch batch = socialData.getBatch(elResolver);
+ assertTrue(batch.getHttpPreloads().isEmpty());
+ assertEquals(1, batch.getSocialPreloads().size());
+ assertEquals(expected.toString(), batch.getSocialPreloads().get("key").toString());
}
@Test
@@ -114,8 +130,10 @@
+ "fields: ['name','id']"
+ "}}");
- assertEquals(1, socialData.getSocialPreloads(context).size());
- assertEquals(expected.toString(), socialData.getSocialPreloads(context).get("key").toString());
+ PipelinedData.Batch batch = socialData.getBatch(elResolver);
+ assertTrue(batch.getHttpPreloads().isEmpty());
+ assertEquals(1, batch.getSocialPreloads().size());
+ assertEquals(expected.toString(), batch.getSocialPreloads().get("key").toString());
}
@Test
@@ -134,8 +152,10 @@
+ "fields: ['name','id']"
+ "}}");
- assertEquals(1, socialData.getSocialPreloads(context).size());
- assertEquals(expected.toString(), socialData.getSocialPreloads(context).get("key").toString());
+ PipelinedData.Batch batch = socialData.getBatch(elResolver);
+ assertTrue(batch.getHttpPreloads().isEmpty());
+ assertEquals(1, batch.getSocialPreloads().size());
+ assertEquals(expected.toString(), batch.getSocialPreloads().get("key").toString());
}
@Test
@@ -155,8 +175,10 @@
+ "fields: ['foo','bar']"
+ "}}");
- assertEquals(1, socialData.getSocialPreloads(context).size());
- assertEquals(expected.toString(), socialData.getSocialPreloads(context).get("key").toString());
+ PipelinedData.Batch batch = socialData.getBatch(elResolver);
+ assertTrue(batch.getHttpPreloads().isEmpty());
+ assertEquals(1, batch.getSocialPreloads().size());
+ assertEquals(expected.toString(), batch.getSocialPreloads().get("key").toString());
}
@Test
@@ -176,8 +198,10 @@
+ "fields: ['foo','bar']"
+ "}}");
- assertEquals(1, socialData.getSocialPreloads(context).size());
- assertEquals(expected.toString(), socialData.getSocialPreloads(context).get("key").toString());
+ PipelinedData.Batch batch = socialData.getBatch(elResolver);
+ assertTrue(batch.getHttpPreloads().isEmpty());
+ assertEquals(1, batch.getSocialPreloads().size());
+ assertEquals(expected.toString(), batch.getSocialPreloads().get("key").toString());
}
@Test
@@ -191,7 +215,8 @@
PipelinedData socialData = new PipelinedData(XmlUtil.parse(xml), null);
assertFalse(socialData.needsOwner());
- assertTrue(socialData.getSocialPreloads(context).isEmpty());
+ PipelinedData.Batch batch = socialData.getBatch(elResolver);
+ assertNull(batch);
}
@Test(expected = SpecParserException.class)
@@ -203,21 +228,47 @@
}
@Test
+ public void testBatching() throws Exception {
+ String xml = "<Content xmlns=\"" + PipelinedData.OPENSOCIAL_NAMESPACE + "\">"
+ + "<PeopleRequest key=\"key\" userId=\"${userId}\"/>"
+ + "<MakeRequest key=\"key2\" href=\"${key}\"/>"
+ + "</Content>";
+
+ PipelinedData socialData = new PipelinedData(XmlUtil.parse(xml), GADGET_URI);
+
+ PipelinedData.Batch batch = socialData.getBatch(elResolver);
+ assertTrue(batch.getSocialPreloads().isEmpty());
+ assertTrue(batch.getHttpPreloads().isEmpty());
+
+ // Now have "userId", the next batch should resolve the people request
+ elValues.put("userId", "foo");
+ batch = batch.getNextBatch(elResolver);
+ assertEquals(1, batch.getSocialPreloads().size());
+ assertTrue(batch.getHttpPreloads().isEmpty());
+
+ elValues.put("key", "somedata");
+ batch = batch.getNextBatch(elResolver);
+ assertTrue(batch.getSocialPreloads().isEmpty());
+ assertEquals(1, batch.getHttpPreloads().size());
+ assertNull(batch.getNextBatch(elResolver));
+ }
+
+
+ @Test
public void makeRequestDefaults() throws Exception {
String xml = "<Content><MakeRequest xmlns=\"" + PipelinedData.OPENSOCIAL_NAMESPACE + "\" "
+ " key=\"key\""
+ " href=\"/example.html\""
+ "/></Content>";
- PipelinedData pipelinedData = new PipelinedData(
- XmlUtil.parse(xml), Uri.parse("http://example.org/"));
- Map<String, RequestAuthenticationInfo> httpPreloads =
- pipelinedData.getHttpPreloads(context);
+ PipelinedData pipelinedData = new PipelinedData(XmlUtil.parse(xml), GADGET_URI);
+ PipelinedData.Batch batch = pipelinedData.getBatch(elResolver);
- assertEquals(1, httpPreloads.size());
- RequestAuthenticationInfo preload = httpPreloads.get("key");
+ assertEquals(1, batch.getHttpPreloads().size());
+ RequestAuthenticationInfo preload = batch.getHttpPreloads().get("key");
assertEquals(AuthType.NONE, preload.getAuthType());
assertEquals(Uri.parse("http://example.org/example.html"), preload.getHref());
+ assertTrue(batch.getSocialPreloads().isEmpty());
}
@Test
@@ -229,15 +280,14 @@
+ " sign_owner=\"false\""
+ "/></Content>";
- PipelinedData pipelinedData = new PipelinedData(
- XmlUtil.parse(xml), Uri.parse("http://example.org/"));
- Map<String, RequestAuthenticationInfo> httpPreloads =
- pipelinedData.getHttpPreloads(context);
+ PipelinedData pipelinedData = new PipelinedData(XmlUtil.parse(xml), GADGET_URI);
+ PipelinedData.Batch batch = pipelinedData.getBatch(elResolver);
- assertEquals(1, httpPreloads.size());
- RequestAuthenticationInfo preload = httpPreloads.get("key");
+ assertEquals(1, batch.getHttpPreloads().size());
+ RequestAuthenticationInfo preload = batch.getHttpPreloads().get("key");
assertEquals(AuthType.SIGNED, preload.getAuthType());
assertTrue(preload.isSignViewer());
assertFalse(preload.isSignOwner());
+ assertTrue(batch.getSocialPreloads().isEmpty());
}
}
Modified: incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/spec/ViewTest.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/spec/ViewTest.java?rev=743460&r1=743459&r2=743460&view=diff
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/spec/ViewTest.java (original)
+++ incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/spec/ViewTest.java Wed Feb 11 19:31:29 2009
@@ -23,15 +23,16 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
-import java.util.Arrays;
-
import org.apache.shindig.common.uri.Uri;
import org.apache.shindig.common.xml.XmlUtil;
-import org.apache.shindig.gadgets.GadgetContext;
+import org.apache.shindig.expressions.RootELResolver;
import org.apache.shindig.gadgets.variables.Substitutions;
import org.apache.shindig.gadgets.variables.Substitutions.Type;
-import org.junit.Test;
+
+import java.util.Arrays;
+
import org.junit.Assert;
+import org.junit.Test;
public class ViewTest {
private static final Uri SPEC_URL = Uri.parse("http://example.org/g.xml");
@@ -209,10 +210,11 @@
+ " key=\"key\""
+ " fields=\"name,id\""
+ "/></Content>";
- GadgetContext context = new GadgetContext();
View view = new View("test", Arrays.asList(XmlUtil.parse(xml)), SPEC_URL);
- assertEquals(1, view.getPipelinedData().getSocialPreloads(context).size());
- assertTrue(view.getPipelinedData().getSocialPreloads(context).containsKey("key"));
+ PipelinedData.Batch batch = view.getPipelinedData().getBatch(new RootELResolver());
+
+ assertEquals(1, batch.getSocialPreloads().size());
+ assertTrue(batch.getSocialPreloads().containsKey("key"));
}
@Test(expected = SpecParserException.class)