Author: awiner
Date: Wed Mar 18 23:05:16 2009
New Revision: 755772

URL: http://svn.apache.org/viewvc?rev=755772&view=rev
Log:
SHINDIG-748: OpenSocial templates
Patch from Lev Epshteyn, and cleanup and further work from me, implementing:
- @tag attribute on script blocks
- os:Render element
- ${My} support
- Fix for stack overflow exception in ELResolvers if ${Cur} is null
- @repeat or os:Repeat over a scalary type now iterates once


Added:
    
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/ElementELResolver.java
   (with props)
    
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/RenderTagHandler.java
   (with props)
    
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/TemplateBasedTagHandler.java
   (with props)
    
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/templates/RenderTagHandlerTest.java
   (with props)
    
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/templates/TemplateBasedTagHandlerTest.java
   (with props)
Modified:
    
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/TemplateRewriter.java
    
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/AbstractTagHandler.java
    
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/DefaultTemplateProcessor.java
    
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/TagRegistry.java
    
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/TemplateContext.java
    
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/TemplateELResolver.java
    
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/TemplateModule.java
    
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/TemplateProcessor.java
    
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/TemplateRewriterTest.java
    
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/templates/DefaultTemplateProcessorTest.java
    
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/templates/FakeTemplateProcessor.java

Modified: 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/TemplateRewriter.java
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/TemplateRewriter.java?rev=755772&r1=755771&r2=755772&view=diff
==============================================================================
--- 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/TemplateRewriter.java
 (original)
+++ 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/TemplateRewriter.java
 Wed Mar 18 23:05:16 2009
@@ -27,6 +27,9 @@
 import org.apache.shindig.gadgets.spec.Feature;
 import org.apache.shindig.gadgets.spec.MessageBundle;
 import org.apache.shindig.gadgets.templates.MessageELResolver;
+import org.apache.shindig.gadgets.templates.TagHandler;
+import org.apache.shindig.gadgets.templates.TagRegistry;
+import org.apache.shindig.gadgets.templates.TemplateBasedTagHandler;
 import org.apache.shindig.gadgets.templates.TemplateContext;
 import org.apache.shindig.gadgets.templates.TemplateProcessor;
 import org.json.JSONObject;
@@ -66,13 +69,16 @@
   private final Provider<TemplateProcessor> processor;
   private final MessageBundleFactory messageBundleFactory;
   private final Expressions expressions;
+  private final TagRegistry baseTagRegistry;
 
   @Inject
   public TemplateRewriter(Provider<TemplateProcessor> processor,
-      MessageBundleFactory messageBundleFactory, Expressions expressions) {
+      MessageBundleFactory messageBundleFactory, Expressions expressions, 
+      TagRegistry baseTagRegistry) {
     this.processor = processor;
     this.messageBundleFactory = messageBundleFactory;
     this.expressions = expressions;
+    this.baseTagRegistry = baseTagRegistry;
   }
 
   public RewriterResults rewrite(HttpRequest request, HttpResponse original,
@@ -107,22 +113,65 @@
 
   private RewriterResults rewriteImpl(Gadget gadget, MutableContent content)
       throws GadgetException {
-    List<Element> tagList =
-      DomUtil.getElementsByTagNameCaseInsensitive(content.getDocument(), TAGS);
+    List<Element> templates = ImmutableList.copyOf(
+      Iterables.filter(
+          DomUtil.getElementsByTagNameCaseInsensitive(content.getDocument(), 
TAGS),
+          new Predicate<Element>() {
+            public boolean apply(Element element) {
+              return "text/os-template".equals(element.getAttribute("type"));
+            }
+          }));
+    
+    TagRegistry registry = registerCustomTags(templates);
+    
+    return processInlineTemplates(gadget, content, templates, registry);
+  }
+  
+  /**
+   * Register templates with a "tag" attribute.
+   */
+  private TagRegistry registerCustomTags(List<Element> allTemplates) {
+    ImmutableSet.Builder<TagHandler> handlers = ImmutableSet.builder();
+    for (Element template : allTemplates) {
+      // Only process templates with a tag attribute
+      if (template.getAttribute("tag").length() == 0) {
+        continue;
+      }
+      
+      // TODO: split() is a regex compile, and should be avoided
+      String [] nameParts = template.getAttribute("tag").split(":");
+      // At this time, we only support 
+      if (nameParts.length != 2) {
+        continue;
+      }
+      String namespaceUri = template.lookupPrefix(nameParts[0]);      
+      if (namespaceUri == null) {
+        // If the namespace prefix is defined on the template tag, get it from
+        // the attribute.
+        namespaceUri = template.getAttribute("xmlns:" + nameParts[0]);
+      }
+      if (namespaceUri.length() > 0) {
+        handlers.add(new TemplateBasedTagHandler(template, namespaceUri, 
nameParts[1]));
+      }
+    }
+    
+    return baseTagRegistry.addHandlers(handlers.build());
+  }
+  
+  private RewriterResults processInlineTemplates(Gadget gadget, MutableContent 
content,
+      List<Element> allTemplates, TagRegistry registry) throws GadgetException 
{
     final Map<String, JSONObject> pipelinedData = content.getPipelinedData();
 
     List<Element> templates = ImmutableList.copyOf(
-        Iterables.filter(tagList, new Predicate<Element>() {
+        Iterables.filter(allTemplates, new Predicate<Element>() {
       public boolean apply(Element element) {
-        String type = element.getAttribute("type");
         String name = element.getAttribute("name");
         String tag = element.getAttribute("tag");
         String require = element.getAttribute("require");
         // Templates with "tag" or "name" can't be processed;  templates
         // that require data that isn't available on the server can't
         // be processed either
-        return "text/os-template".equals(type)
-            && "".equals(name)
+        return "".equals(name)
             && "".equals(tag)
             && checkRequiredData(require, pipelinedData.keySet());
       }
@@ -141,7 +190,7 @@
 
     for (Element template : templates) {
       DocumentFragment result = processor.get().processTemplate(
-          template, templateContext, messageELResolver);
+          template, templateContext, messageELResolver, registry);
       // Note: replaceNode errors when replacing Element with DocumentFragment
       template.getParentNode().insertBefore(result, template);
       // TODO: clients that need to update data that is initially available,

Modified: 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/AbstractTagHandler.java
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/AbstractTagHandler.java?rev=755772&r1=755771&r2=755772&view=diff
==============================================================================
--- 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/AbstractTagHandler.java
 (original)
+++ 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/AbstractTagHandler.java
 Wed Mar 18 23:05:16 2009
@@ -19,6 +19,7 @@
 package org.apache.shindig.gadgets.templates;
 
 import org.apache.shindig.gadgets.parse.nekohtml.NekoSerializer;
+import org.w3c.dom.DocumentFragment;
 import org.w3c.dom.Element;
 import org.w3c.dom.Node;
 
@@ -50,12 +51,19 @@
   public String getNamespaceUri() {
     return namespaceUri;
   }
-  
+
   protected final <T> T getValueFromTag(Element tag, String name, 
       TemplateProcessor processor, Class<T> type) {
     return processor.evaluate(tag.getAttribute(name), type, null);
   }
   
+  protected final DocumentFragment processChildren(Element tag, 
+      TemplateProcessor processor) {
+    DocumentFragment fragment = 
tag.getOwnerDocument().createDocumentFragment();
+    processor.processChildNodes(fragment, tag);
+    return fragment;
+  }
+  
   /** 
    * Create a text node with proper escaping.
    */

Modified: 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/DefaultTemplateProcessor.java
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/DefaultTemplateProcessor.java?rev=755772&r1=755771&r2=755772&view=diff
==============================================================================
--- 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/DefaultTemplateProcessor.java
 (original)
+++ 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/DefaultTemplateProcessor.java
 Wed Mar 18 23:05:16 2009
@@ -70,17 +70,16 @@
   public static final String ATTRIBUTE_VAR = "var";
   
   private final Expressions expressions;
-  private final TagRegistry registry;
   // Reused buffer for creating template output
   private final StringBuilder outputBuffer;
 
+  private TagRegistry registry;
   private TemplateContext templateContext;
   private ELContext elContext;
   
   @Inject
-  public DefaultTemplateProcessor(Expressions expressions, TagRegistry 
registry) {  
+  public DefaultTemplateProcessor(Expressions expressions) {  
     this.expressions = expressions;
-    this.registry = registry;
     outputBuffer = new StringBuilder();
   }
 
@@ -95,12 +94,14 @@
    * @return a document fragment with the resolved content
    */
   public DocumentFragment processTemplate(Element template,
-      TemplateContext templateContext, ELResolver globals) {
+      TemplateContext templateContext, ELResolver globals, TagRegistry 
registry) {
 
+    this.registry = registry;
     this.templateContext = templateContext;
     this.elContext = expressions.newELContext(globals,
         new GadgetELResolver(templateContext.getGadgetContext()),
-        new TemplateELResolver(templateContext));
+        new TemplateELResolver(templateContext),
+        new ElementELResolver());
 
     DocumentFragment result = 
template.getOwnerDocument().createDocumentFragment();
     processChildNodes(result, template);
@@ -234,6 +235,10 @@
    */
   public void processRepeat(Node result, Element element, Iterable<?> dataList,
       Runnable onEachLoop) {
+    if (dataList == null) {
+      return;
+    }
+    
     // Compute list size
     int size = Iterables.size(dataList);
     
@@ -306,18 +311,15 @@
     }
 
     if (handler != null) {
-      // TODO: We are passing in an element with all special attributes intact.
-      // This may be problematic. Perhaps doing a deep clone and stripping them
-      // would work better.
       handler.process(result, element, this);
     } else {
       Element resultNode = (Element) element.cloneNode(false);
       clearSpecialAttributes(resultNode);
       processAttributes(resultNode);
-      result.appendChild(resultNode);
-      
       processChildNodes(resultNode, element);
+      result.appendChild(resultNode);
     }
+    
   }
 
   private void clearSpecialAttributes(Element element) {
@@ -328,13 +330,15 @@
   }
   
   /**
-   * Process expressions on attributes
+   * Process expressions on attributes. For custom tags, in addition to 
+   * processing attributes, 
+   * @param element The Element to process attributes on
    */
   private void processAttributes(Element element) {
     NamedNodeMap attributes = element.getAttributes();
     for (int i = 0; i < attributes.getLength(); i++) {
       Attr attribute = (Attr) attributes.item(i);
-      attribute.setNodeValue(evaluate(attribute.getValue(), String.class, 
null));
+      attribute.setNodeValue(evaluate(attribute.getValue(), String.class, 
null));      
     }
   }
   
@@ -424,6 +428,6 @@
       return ImmutableList.of(json);
     }
     
-    return null;
+    return ImmutableList.of(value);
   }
 }
\ No newline at end of file

Added: 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/ElementELResolver.java
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/ElementELResolver.java?rev=755772&view=auto
==============================================================================
--- 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/ElementELResolver.java
 (added)
+++ 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/ElementELResolver.java
 Wed Mar 18 23:05:16 2009
@@ -0,0 +1,138 @@
+/*
+ * 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.templates;
+
+import org.w3c.dom.Attr;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+import java.beans.FeatureDescriptor;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.el.ELContext;
+import javax.el.ELResolver;
+
+import com.google.common.collect.Lists;
+
+/**
+ * ELResolver that processes DOM elements.
+ */
+class ElementELResolver extends ELResolver {
+  /**
+   * A wrapper for a DOM Element that overrides toString().
+   * TODO: remove with JUEL 2.1.1.
+   */
+  public static class ElementWrapper {
+    public final Element element;
+    
+    public ElementWrapper(Element element) {
+      this.element = element;
+    }
+    
+    @Override
+    public String toString() {
+      return element.getTextContent();
+    }
+  }
+
+  @Override
+  public Class<?> getCommonPropertyType(ELContext context, Object base) {
+    if (base instanceof ElementWrapper) {
+      return String.class;
+    }
+    return null;
+  }
+
+  @Override
+  public Iterator<FeatureDescriptor> getFeatureDescriptors(ELContext context,
+      Object base) {
+    return null;
+  }
+
+  @Override
+  public Class<?> getType(ELContext context, Object base, Object property) {
+    Object value = getValue(context, base, property);
+    return value == null ? null : value.getClass();
+  }
+
+  @Override
+  public Object getValue(ELContext context, Object base, Object property) {
+    if (!(base instanceof ElementWrapper)) {
+      return null;
+    }
+    
+    context.setPropertyResolved(true);
+    Element element = ((ElementWrapper) base).element;
+    String propertyString = property.toString();
+    
+    // See if there is an Object property. 
+    Object data = element.getUserData(propertyString);
+    if (data != null) {
+      return data;
+    }
+    
+    // Next, check for an attribute.
+    Attr attribute = element.getAttributeNode(propertyString);
+    if (attribute != null) {
+      return attribute.getValue();
+    }
+    
+    // Finally, look for child nodes with matching local names.
+    List<ElementWrapper> childElements = null;
+    for (Node child = element.getFirstChild(); child != null; child = 
child.getNextSibling()) {
+      if (!(child instanceof Element)) {
+        continue;
+      }
+      
+      Element childElement = (Element) child;
+      if (!propertyString.equals(childElement.getLocalName())) {
+        continue;
+      }
+      
+      if (childElements == null) {
+        childElements = Lists.newArrayListWithCapacity(2);
+      }
+      
+      childElements.add(new ElementWrapper(childElement));
+    }
+    
+    if (childElements == null) {
+      return null;
+    } else if (childElements.size() == 1) {
+      return childElements.get(0);
+    } else {
+      return childElements;
+    }
+  }
+
+  @Override
+  public boolean isReadOnly(ELContext context, Object base, Object property) {
+    if (base instanceof ElementWrapper) {
+      context.setPropertyResolved(true);
+    }
+    return true;
+  }
+
+  @Override
+  public void setValue(ELContext context, Object base, Object property,
+      Object value) {
+  }
+
+}

Propchange: 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/ElementELResolver.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/RenderTagHandler.java
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/RenderTagHandler.java?rev=755772&view=auto
==============================================================================
--- 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/RenderTagHandler.java
 (added)
+++ 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/RenderTagHandler.java
 Wed Mar 18 23:05:16 2009
@@ -0,0 +1,81 @@
+/*
+ * 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.templates;
+
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+import java.util.List;
+import java.util.Map;
+
+import com.google.inject.Inject;
+import org.apache.shindig.gadgets.templates.ElementELResolver.ElementWrapper;
+
+/**
+ * Tag Handler for <os:Render/> tag.
+ */
+public class RenderTagHandler extends AbstractTagHandler {
+
+  public static final String DEFAULT_NAME = "Render";
+  
+  public static final String ATTR_CONTENT = "content";
+  
+  @Inject
+  public RenderTagHandler() {
+    super(TagHandler.OPENSOCIAL_NAMESPACE, DEFAULT_NAME);
+  }
+  
+  public void process(Node result, Element tag, TemplateProcessor processor) {
+    Map<String, Object> myMap = processor.getTemplateContext().getMy();
+    if (myMap == null) {
+      return;
+    }
+    
+    String content = tag.getAttribute(ATTR_CONTENT);
+    // No @content specified - move it all.
+    if ("".equals(content)) {
+      Node root = processor.getTemplateContext().getTemplateRoot();
+      if (root != null) {
+        for (Node child = root.getFirstChild(); child != null; child = 
child.getNextSibling()) {
+          result.appendChild(child.cloneNode(true));
+        }
+      }
+    } else {
+      Object value = myMap.get(content);
+      // TODO: for non-Elements, output errors
+      if (value instanceof ElementWrapper) {
+        copyChildren((ElementWrapper) value, result);
+      } else if (value instanceof List) {
+        List<?> children = (List<?>) value;
+        for (Object probablyAnElement : children) {
+          if (probablyAnElement instanceof ElementWrapper) {
+            copyChildren((ElementWrapper) probablyAnElement, result);
+          }
+        }
+      } 
+    }
+  }
+
+  private void copyChildren(ElementWrapper fromWrapper, Node to) {
+    Element from = fromWrapper.element;
+    for (Node child = from.getFirstChild(); child != null; child = 
child.getNextSibling()) {
+      to.appendChild(child.cloneNode(true));
+    }
+  }
+}

Propchange: 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/RenderTagHandler.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/TagRegistry.java
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/TagRegistry.java?rev=755772&r1=755771&r2=755772&view=diff
==============================================================================
--- 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/TagRegistry.java
 (original)
+++ 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/TagRegistry.java
 Wed Mar 18 23:05:16 2009
@@ -20,34 +20,68 @@
 
 import org.w3c.dom.Element;
 
-import com.google.common.collect.Maps;
-import com.google.inject.Inject;
-
 import java.util.Map;
 import java.util.Set;
 
+import com.google.common.collect.Maps;
+import com.google.inject.Inject;
+
 /**
  * A registry of custom tag handlers, keyed by a combination of namespace URL
  * and tag name.
  */
 public class TagRegistry {
 
+  private final TagRegistry parentRegistry;
   private final Map<NSName, TagHandler> handlers = Maps.newHashMap();
 
   @Inject
   public TagRegistry(Set<TagHandler> handlers) {
-    for (TagHandler handler : handlers) {
-      this.handlers.put(new NSName(handler.getNamespaceUri(), 
-          handler.getTagName()), handler);
+    this(handlers, null);
+  }
+  
+  /**
+   * Create a new TagRegistry containing all the existing handlers, with
+   * new tags added.  The handlers from the existing registry will take
+   * precedence over any newly registered tags.
+   */
+  public TagRegistry addHandlers(Set<? extends TagHandler> handlers) {
+    if (handlers.isEmpty()) {
+      return this;
     }
+
+    return new TagRegistry(handlers, this);
   }
   
+  private TagRegistry(Set<? extends TagHandler> handlers, TagRegistry 
parentRegistry) {
+    this.parentRegistry = parentRegistry;
+    for (TagHandler handler : handlers) {
+      this.handlers.put(new NSName(handler.getNamespaceUri(), 
handler.getTagName()), handler);
+    }    
+  }
+  
+  /**
+   * Look up a tag handler for an element.
+   */
   public TagHandler getHandlerFor(Element element) {
     if (element.getNamespaceURI() == null) {
       return null;
     }
-    TagHandler handler = handlers.get(new NSName(element.getNamespaceURI(), 
-        element.getLocalName()));
+    
+    return getHandlerFor(new NSName(element.getNamespaceURI(),  
element.getLocalName()));
+  }
+  
+  private TagHandler getHandlerFor(NSName name) {
+    // Check the parent registry first;  built-in tags cannot be overridden
+    TagHandler handler = null;
+    if (parentRegistry != null) {
+      handler = parentRegistry.getHandlerFor(name);
+    }
+    
+    if (handler == null) {
+      handler =  handlers.get(name);
+    }
+
     return handler;
   }
   
@@ -81,8 +115,6 @@
     @Override
     public int hashCode() {    
       return hash;
-    }
-    
+    }    
   }
-  
 }

Added: 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/TemplateBasedTagHandler.java
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/TemplateBasedTagHandler.java?rev=755772&view=auto
==============================================================================
--- 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/TemplateBasedTagHandler.java
 (added)
+++ 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/TemplateBasedTagHandler.java
 Wed Mar 18 23:05:16 2009
@@ -0,0 +1,114 @@
+/*
+ * 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.templates;
+
+import org.w3c.dom.DocumentFragment;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import java.util.List;
+import java.util.Map;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import org.apache.shindig.gadgets.templates.ElementELResolver.ElementWrapper;
+
+/**
+ * TagHandler implemented by an declarative XML definition.
+ */
+public class TemplateBasedTagHandler extends AbstractTagHandler {
+
+  private final Element templateDefinition;
+  
+  public TemplateBasedTagHandler(Element templateDefinition, String 
namespaceUri, String tagName) {
+    super(namespaceUri, tagName);
+    this.templateDefinition = templateDefinition;
+  }
+  
+  public void process(Node result, Element tagInstance, TemplateProcessor 
processor) {
+    // Process the children of the tag
+    DocumentFragment processedContent = processChildren(tagInstance, 
processor);
+    
+    // Save the old values of "My", "Cur", and the template root element,
+    // and update each
+    Map<String, Object> oldMy = processor.getTemplateContext().setMy(
+        computeMy(tagInstance, processedContent, processor));
+    Object oldCur = processor.getTemplateContext().setCur(null);
+    Node oldTemplateRoot = 
processor.getTemplateContext().setTemplateRoot(processedContent);
+    
+    // Process the template content given the new EL state
+    processor.processChildNodes(result, templateDefinition);
+    
+    // And restore the template context
+    processor.getTemplateContext().setMy(oldMy);
+    processor.getTemplateContext().setCur(oldCur);
+    processor.getTemplateContext().setTemplateRoot(oldTemplateRoot);
+  }
+  
+  /**
+   * Compute the value of ${My} for this tag execution.
+   */
+  private Map<String, Object> computeMy(Element tagInstance, Node 
processedContent,
+      TemplateProcessor processor) {
+    Map<String, Object> myMap = Maps.newHashMap();
+    
+    NodeList children = processedContent.getChildNodes();
+    
+    for (int i = 0;  i < children.getLength(); i++) {
+      Node child = children.item(i);
+      if (child instanceof Element) {
+        Element el = (Element) child;
+        String name = el.getLocalName();
+        // TODO: why???  There should always be a local name.
+        if (name == null) {
+          name = el.getNodeName();
+        }
+
+        ElementWrapper wrapper = new ElementWrapper(el);
+        Object previous = myMap.get(name);
+        if (previous == null) {
+          myMap.put(name, wrapper);
+        } else if (previous instanceof ElementWrapper) {
+          List<ElementWrapper> bucket = 
Lists.newArrayListWithCapacity(children.getLength());
+          bucket.add((ElementWrapper) previous);
+          bucket.add(wrapper);
+          myMap.put(name, bucket);
+         } else {
+           // Must be a List<ElementWrapper>
+           @SuppressWarnings("unchecked")
+           List<ElementWrapper> bucket = (List<ElementWrapper>) previous;
+           bucket.add(wrapper);
+        }
+      }
+    }
+
+    NamedNodeMap atts = tagInstance.getAttributes();
+    for (int i = 0; i < atts.getLength(); i++) {
+      String name = atts.item(i).getNodeName();
+      // Overwrite any pre-existing values, as attributes take
+      // precedence over elements.  This is wasteful if there are attributes
+      // and elements with the same name, but that should be very rare 
+      myMap.put(name, getValueFromTag(tagInstance, name, processor, 
Object.class));
+    }
+    
+    return myMap;
+  }
+}

Propchange: 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/TemplateBasedTagHandler.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/TemplateContext.java
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/TemplateContext.java?rev=755772&r1=755771&r2=755772&view=diff
==============================================================================
--- 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/TemplateContext.java
 (original)
+++ 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/TemplateContext.java
 Wed Mar 18 23:05:16 2009
@@ -20,6 +20,7 @@
 
 import org.apache.shindig.gadgets.GadgetContext;
 import org.json.JSONObject;
+import org.w3c.dom.Node;
 
 import java.util.Map;
 
@@ -35,7 +36,9 @@
   private Object cur = null;
   // TODO: support unique Id
   private Map<String, ? extends Object> context = ImmutableMap.of();
-
+  private Map<String, Object> myMap = null;
+  private Node templateRoot;
+  
   public TemplateContext(GadgetContext gadgetContext, Map<String, JSONObject> 
top) {
     this.gadgetContext = gadgetContext;
     this.top = top;
@@ -65,7 +68,27 @@
     return oldContext;
   }
   
+  public Map<String, Object> setMy(Map<String, Object> myMap) {
+    Map<String, Object> oldMy = this.myMap;
+    this.myMap = myMap;
+    return oldMy;
+  }
+  
+  public Map<String, Object> getMy() {
+    return myMap;
+  }
+  
   public GadgetContext getGadgetContext() {
     return gadgetContext;
   }
+
+  public Node setTemplateRoot(Node root) {
+    Node oldRoot = this.templateRoot;
+    this.templateRoot = root;
+    return oldRoot;
+  }
+  
+  public Node getTemplateRoot() {
+    return this.templateRoot;
+  }
 }

Modified: 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/TemplateELResolver.java
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/TemplateELResolver.java?rev=755772&r1=755771&r2=755772&view=diff
==============================================================================
--- 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/TemplateELResolver.java
 (original)
+++ 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/TemplateELResolver.java
 Wed Mar 18 23:05:16 2009
@@ -41,9 +41,10 @@
   public static final String PROPERTY_TOP = "Top";
   public static final String PROPERTY_CONTEXT = "Context";
   public static final String PROPERTY_CUR = "Cur";
+  public static final String PROPERTY_MY = "My";
   
   private static final Set<String> TOP_LEVEL_PROPERTIES =
-    ImmutableSet.of(PROPERTY_TOP, PROPERTY_CONTEXT, PROPERTY_CUR);
+    ImmutableSet.of(PROPERTY_TOP, PROPERTY_CONTEXT, PROPERTY_CUR, PROPERTY_MY);
   
   private final TemplateContext templateContext;
 
@@ -81,6 +82,8 @@
           return templateContext.getTop();
         } else if (PROPERTY_CONTEXT.equals(property)) {
           return templateContext.getContext();
+        } else if (PROPERTY_MY.equals(property)) {
+          return templateContext.getMy();
         } else {
           return templateContext.getCur();
         }
@@ -99,25 +102,27 @@
       Object cur = templateContext.getCur();
       // Resolve through "cur" as if it were a value - if 
"isPropertyResolved()"
       // is true, it was handled
-      Object value = context.getELResolver().getValue(context, cur, property);
-      if (context.isPropertyResolved()) {
-        if (value != null) {
-          return value;
-        } else {
-          context.setPropertyResolved(false);
+      if (cur != null) {
+        Object value = context.getELResolver().getValue(context, cur, 
property);
+        if (context.isPropertyResolved()) {
+          if (value != null) {
+            return value;
+          } else {
+            context.setPropertyResolved(false);
+          }
         }
       }
       
       // Check current scope variables next.
-      Map<String, ? extends Object> scope = templateContext.getContext();
-      if (scope.containsKey(property)) {
+      Map<String, ? extends Object> scope = templateContext.getMy();
+      if (scope != null && scope.containsKey(property)) {
         context.setPropertyResolved(true);
         return scope.get(property);
       }
       
       // Look at Top context last.
       scope = templateContext.getTop();
-      if (scope.containsKey(property)) {
+      if (scope != null && scope.containsKey(property)) {
         context.setPropertyResolved(true);
         return scope.get(property);
       }

Modified: 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/TemplateModule.java
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/TemplateModule.java?rev=755772&r1=755771&r2=755772&view=diff
==============================================================================
--- 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/TemplateModule.java
 (original)
+++ 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/TemplateModule.java
 Wed Mar 18 23:05:16 2009
@@ -43,9 +43,10 @@
     
     @Inject
     public TagHandlersProvider(HtmlTagHandler htmlHandler, NameTagHandler 
nameHandler,
-        IfTagHandler ifHandler, RepeatTagHandler repeatHandler) {
+        IfTagHandler ifHandler, RepeatTagHandler repeatHandler, 
+        RenderTagHandler renderHandler) {
       handlers = ImmutableSet.of((TagHandler) htmlHandler, nameHandler, 
ifHandler,
-          repeatHandler);
+          repeatHandler, renderHandler);
     }
     
     public Set<TagHandler> get() {

Modified: 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/TemplateProcessor.java
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/TemplateProcessor.java?rev=755772&r1=755771&r2=755772&view=diff
==============================================================================
--- 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/TemplateProcessor.java
 (original)
+++ 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/TemplateProcessor.java
 Wed Mar 18 23:05:16 2009
@@ -43,7 +43,7 @@
    * @return a document fragment with the resolved content
    */
   DocumentFragment processTemplate(Element template,
-      TemplateContext templateContext, ELResolver globals);    
+      TemplateContext templateContext, ELResolver globals, TagRegistry 
registry);    
   
   
   /**

Modified: 
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/TemplateRewriterTest.java
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/TemplateRewriterTest.java?rev=755772&r1=755771&r2=755772&view=diff
==============================================================================
--- 
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/TemplateRewriterTest.java
 (original)
+++ 
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/TemplateRewriterTest.java
 Wed Mar 18 23:05:16 2009
@@ -38,11 +38,11 @@
 import org.junit.Before;
 import org.junit.Test;
 
+import java.util.Set;
+
 import com.google.common.collect.ImmutableSet;
 import com.google.inject.Provider;
 
-import java.util.Set;
-
 /** 
  * Tests for TemplateRewriter
  */
@@ -71,21 +71,21 @@
     "<script type='text/os-template' name='myTemplate'>Hello, 
${user.name}</script>";  
   
   private static final String CONTENT_WITH_TAG =
-    "<script type='text/os-template' tag='foo:Bar'>Hello, 
${user.name}</script>";  
+    "<script type='text/os-template' xmlns:foo='#foo' tag='foo:Bar'>Hello, 
${user.name}</script>";  
 
   
   @Before
   public void setUp() {
+    Set<TagHandler> handlers = ImmutableSet.of();
     rewriter = new TemplateRewriter(
         new Provider<TemplateProcessor>() {
           public TemplateProcessor get() {
-            Set<TagHandler> handlers = ImmutableSet.of();
-            return new DefaultTemplateProcessor(new Expressions(), 
-                new TagRegistry(handlers));
+            return new DefaultTemplateProcessor(new Expressions());
           }
         },
         new FakeMessageBundleFactory(),
-        new Expressions());
+        new Expressions(),
+        new TagRegistry(handlers));
   }
   
   @Test

Modified: 
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/templates/DefaultTemplateProcessorTest.java
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/templates/DefaultTemplateProcessorTest.java?rev=755772&r1=755771&r2=755772&view=diff
==============================================================================
--- 
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/templates/DefaultTemplateProcessorTest.java
 (original)
+++ 
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/templates/DefaultTemplateProcessorTest.java
 Wed Mar 18 23:05:16 2009
@@ -19,6 +19,7 @@
 package org.apache.shindig.gadgets.templates;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.assertFalse;
 
 import org.apache.shindig.expressions.Expressions;
@@ -73,7 +74,7 @@
     Set<TagHandler> handlers = ImmutableSet.of((TagHandler) new 
TestTagHandler());
     registry = new TagRegistry(handlers);
 
-    processor = new DefaultTemplateProcessor(expressions, registry);
+    processor = new DefaultTemplateProcessor(expressions);
     resolver = new RootELResolver();
     parser = new SocialMarkupHtmlParser(new 
ParseModule.DOMImplementationProvider().get());    
     context = new TemplateContext(new GadgetContext(), variables);
@@ -155,7 +156,7 @@
   
   @Test
   public void testCustomTag() throws Exception {
-    String output = executeTemplate("<test:Foo text='${foo.title}'/>", 
+    String output = executeTemplate("<test:Foo text='${foo.title}' 
data='${user}'/>", 
         "xmlns:test='" + TEST_NS + "'");
     assertEquals("<b>BAR</b>", output);
   }
@@ -166,7 +167,7 @@
   
   private String executeTemplate(String markup, String extra) throws Exception 
{
     Element template = prepareTemplate(markup, extra);
-    DocumentFragment result = processor.processTemplate(template, context, 
resolver);
+    DocumentFragment result = processor.processTemplate(template, context, 
resolver, registry);
     return serialize(result);
   }
   
@@ -191,9 +192,10 @@
   }
   
   /**
-   * A dummy custom tag that looks for the @text attribute, uppercases it and
-   * encloses it in a &lt;b&gt; tag.  <code>&lt;test:Foo text="bar"/&gt;</code>
-   * turns into <code>&lt;b&gt;BAR&lt;/b&gt;</code> 
+   * A dummy custom tag.
+   * Expects a @text attribute equal to "bar", and a @data attribute that
+   * evaluates to a JSONObject with an id property equal to "101".
+   * If these conditions are met, returns <code>&lt;b&gt;BAR&lt;/b&gt;</code> 
    */
   private static class TestTagHandler extends AbstractTagHandler {
     
@@ -201,9 +203,13 @@
       super(TEST_NS, "Foo");
     }
     
-    public void process(Node result, Element tag, TemplateProcessor processor) 
{
+    public void process(Node result, Element tag, TemplateProcessor processor) 
{     
+      Object data = getValueFromTag(tag, "data", processor, Object.class);
+      assertTrue(data instanceof JSONObject);
+      assertEquals("101", ((JSONObject) data).optString("id"));
+            
       String text = getValueFromTag(tag, "text", processor, String.class);
-      text = (text == null) ? "" : text.toUpperCase();
+      text = text.toUpperCase();
       Document doc = result.getOwnerDocument();
       Element b = doc.createElement("b");
       b.appendChild(doc.createTextNode(text));

Modified: 
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/templates/FakeTemplateProcessor.java
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/templates/FakeTemplateProcessor.java?rev=755772&r1=755771&r2=755772&view=diff
==============================================================================
--- 
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/templates/FakeTemplateProcessor.java
 (original)
+++ 
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/templates/FakeTemplateProcessor.java
 Wed Mar 18 23:05:16 2009
@@ -53,7 +53,7 @@
   }
 
   public DocumentFragment processTemplate(Element template,
-      TemplateContext templateContext, ELResolver globals) {
+      TemplateContext templateContext, ELResolver globals, TagRegistry 
registry) {
     throw new UnsupportedOperationException();
   }
 

Added: 
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/templates/RenderTagHandlerTest.java
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/templates/RenderTagHandlerTest.java?rev=755772&view=auto
==============================================================================
--- 
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/templates/RenderTagHandlerTest.java
 (added)
+++ 
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/templates/RenderTagHandlerTest.java
 Wed Mar 18 23:05:16 2009
@@ -0,0 +1,127 @@
+/*
+ * 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.templates;
+
+import static org.junit.Assert.assertEquals;
+
+import org.apache.shindig.expressions.Expressions;
+import org.apache.shindig.expressions.RootELResolver;
+import org.apache.shindig.gadgets.GadgetContext;
+import org.apache.shindig.gadgets.GadgetException;
+import org.apache.shindig.gadgets.parse.ParseModule;
+import org.apache.shindig.gadgets.parse.nekohtml.NekoSerializer;
+import org.apache.shindig.gadgets.parse.nekohtml.SocialMarkupHtmlParser;
+import org.json.JSONObject;
+import org.junit.Before;
+import org.junit.Test;
+import org.w3c.dom.Document;
+import org.w3c.dom.DocumentFragment;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Maps;
+
+import java.io.IOException;
+import java.util.Map;
+import java.util.Set;
+
+import javax.el.ELResolver;
+
+public class RenderTagHandlerTest {
+  
+  private Expressions expressions;
+
+  private TemplateContext context;
+  private DefaultTemplateProcessor processor;
+  private Map<String, JSONObject> variables;
+  private ELResolver resolver;
+  private TagRegistry registry;
+
+  private SocialMarkupHtmlParser parser;
+  
+  private static final String TEST_NS = "http://example.com";;
+  
+  @Before
+  public void setUp() throws Exception {
+    expressions = new Expressions();
+    variables = Maps.newHashMap();
+    Set<TagHandler> handlers = ImmutableSet.of((TagHandler) new 
RenderTagHandler());
+    registry = new TagRegistry(handlers);
+
+    processor = new DefaultTemplateProcessor(expressions);
+    resolver = new RootELResolver();
+    parser = new SocialMarkupHtmlParser(new 
ParseModule.DOMImplementationProvider().get());    
+    context = new TemplateContext(new GadgetContext(), variables);
+    
+    addVariable("foo", new JSONObject("{ title: 'bar' }"));
+  }
+
+  @Test
+  public void renderAllChildren() throws Exception {
+    runTest("Bar",
+        "[<os:Render/>]",
+        "<foo:Bar>Hello</foo:Bar>", "[Hello]");
+  }
+  
+  @Test
+  public void renderSingleChildren() throws Exception {
+    runTest("Panel",
+        "<os:Render content='header'/> <os:Render content='footer'/>",
+        "<foo:Panel><footer>Second</footer><header>First</header></foo:Panel>",
+        "First Second");
+  }
+  
+  private void runTest(String tagName, String tagMarkup, String 
templateMarkup, 
+      String expectedResult) throws GadgetException, IOException {
+    Element templateDef = parseTemplate(templateMarkup);
+    Element tagInstance = parseTemplate(tagMarkup);
+    
+    templateDef.getOwnerDocument().adoptNode(tagInstance);
+    TemplateBasedTagHandler tagHandler = new 
TemplateBasedTagHandler(tagInstance, TEST_NS, tagName);
+    TagRegistry reg = registry.addHandlers(ImmutableSet.of(tagHandler));
+    
+    DocumentFragment result = processor.processTemplate(templateDef, context, 
resolver, reg);
+    String output = serialize(result);
+    assertEquals(expectedResult, output);
+  }
+  
+  private Element parseTemplate(String markup) throws GadgetException {    
+    String content = "<script type=\"text/os-template\" xmlns:foo=\"" + 
TEST_NS + 
+        "\" xmlns:os=\"" + TagHandler.OPENSOCIAL_NAMESPACE + "\">" + markup + 
"</script>";
+    Document document = parser.parseDom(content);
+    return (Element) document.getElementsByTagName("script").item(0);
+  }
+  
+  private String serialize(Node node) throws IOException {
+    StringBuilder sb = new StringBuilder();
+    NodeList children = node.getChildNodes();
+    for (int i = 0; i < children.getLength(); i++) {
+      Node child = children.item(i);
+      NekoSerializer.serialize(child, sb);
+    }
+    return sb.toString();
+  }
+  
+  private void addVariable(String key, JSONObject value) {
+    variables.put(key, value);
+  }
+  
+}

Propchange: 
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/templates/RenderTagHandlerTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: 
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/templates/TemplateBasedTagHandlerTest.java
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/templates/TemplateBasedTagHandlerTest.java?rev=755772&view=auto
==============================================================================
--- 
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/templates/TemplateBasedTagHandlerTest.java
 (added)
+++ 
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/templates/TemplateBasedTagHandlerTest.java
 Wed Mar 18 23:05:16 2009
@@ -0,0 +1,179 @@
+/*
+ * 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.templates;
+
+import static org.junit.Assert.assertEquals;
+
+import org.apache.shindig.common.PropertiesModule;
+import org.apache.shindig.expressions.RootELResolver;
+import org.apache.shindig.gadgets.GadgetContext;
+import org.apache.shindig.gadgets.GadgetException;
+import org.apache.shindig.gadgets.parse.GadgetHtmlParser;
+import org.apache.shindig.gadgets.parse.ParseModule;
+import org.apache.shindig.gadgets.parse.nekohtml.NekoSerializer;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.w3c.dom.Document;
+import org.w3c.dom.DocumentFragment;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import java.io.IOException;
+
+import javax.el.ELResolver;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+
+/**
+ * Tests the behavior of template-based tag handlers.
+ */
+public class TemplateBasedTagHandlerTest {
+  
+  private TemplateContext context;
+  private TemplateProcessor processor;
+  private final ELResolver resolver = new RootELResolver();
+  private GadgetHtmlParser parser;
+  
+  private static final String TEST_NS = "http://example.com";;
+  
+  @Before
+  public void setUp() throws Exception {
+    Injector injector = Guice.createInjector(new ParseModule(), new 
TemplateModule(),
+        new PropertiesModule());
+    parser = injector.getInstance(GadgetHtmlParser.class);
+    processor = injector.getInstance(TemplateProcessor.class);
+    context = new TemplateContext(new GadgetContext(), null);
+  }
+
+  @Test
+  public void attributeInMy() throws Exception {
+    // Verify attribute EL retrieval
+    runTest("Bar",
+        "${My.attr}",
+        "<foo:Bar attr='Hello'/>", "Hello");
+  }
+
+  @Test
+  public void elementContentInMy() throws Exception {
+    // Verify element content EL retrieval
+    runTest("Bar",
+        "${My.element}",
+        "<foo:Bar><foo:element>Hello</foo:element></foo:Bar>", "Hello");
+  }
+
+  @Test
+  public void attrTakesPrecedenceInMy() throws Exception {
+    // Verify an attribute takes precedence over an element
+    runTest("Bar",
+        "${My.attr}",
+        "<foo:Bar attr='Hello'><foo:attr>Goodbye</foo:attr></foo:Bar>", 
"Hello");
+  }
+
+  @Test
+  public void elementAttributeInMy() throws Exception {
+    // Verify an attribute of an element is visible
+    runTest("Bar",
+        "${My.element.text}",
+        "<foo:Bar><foo:element text='Hello'/></foo:Bar>", "Hello");
+  }
+
+  @Test
+  public void descendantElementInMy() throws Exception {
+    // Verify the descendant of an element is visible
+    runTest("Bar",
+        "${My.element.child}",
+        
"<foo:Bar><foo:element><foo:child>Hello</foo:child></foo:element></foo:Bar>", 
"Hello");
+  }
+
+  @Test
+  public void descendantElementNotFoundIfNotFullReference() throws Exception {
+    // Verify the descendant element isn't visible unless directly referenced
+    runTest("Bar",
+        "${My.child}",
+        
"<foo:Bar><foo:element><foo:child>Hello</foo:child></foo:element></foo:Bar>", 
"");
+  }
+
+  @Test
+  public void missingElementPropertyIsNull() throws Exception {
+    // Verify the descendant element isn't visible unless directly referenced
+    runTest("Bar",
+        "${My.element.foo == null}",
+        "<foo:Bar><foo:element>Hello/foo:element></foo:Bar>", "true");
+  }
+
+  @Test
+  @Ignore("This currently returns [Hello,Goodbye].  Check the spec, and 
consider changing the spec.")
+  public void multipleElementContentInMy() throws Exception {
+    // Verify element content EL retrieval is concatenation for multiple 
elements
+    runTest("Bar",
+        "${My.element}",
+        
"<foo:Bar><foo:element>Hello</foo:element><foo:element>Goodbye</foo:element></foo:Bar>",
 "HelloGoodbye");
+  }
+
+  @Test
+  public void elementListRepeat() throws Exception {
+    // Verify a list of elements can be repeated over
+    runTest("Bar",
+        "<os:Repeat expression='${My.element}'>${text}</os:Repeat>",
+        "<foo:Bar><foo:element text='Hello'/><foo:element 
text='Goodbye'/></foo:Bar>", "HelloGoodbye");
+  }
+
+  @Test
+  public void singleElementRepeat() throws Exception {
+    // Verify a single element can be "repeated" over
+    runTest("Bar",
+        "<os:Repeat expression='${My.element}'>${text}</os:Repeat>",
+        "<foo:Bar><foo:element text='Hello'/></foo:Bar>", "Hello");
+  }
+
+  private void runTest(String tagName, String tagMarkup, String 
templateMarkup, 
+      String expectedResult) throws GadgetException, IOException {
+    Element templateDef = parseTemplate(templateMarkup);
+    Element tagInstance = parseTemplate(tagMarkup);
+    
+    templateDef.getOwnerDocument().adoptNode(tagInstance);
+    TagHandler tagHandler = new TemplateBasedTagHandler(tagInstance, TEST_NS, 
tagName);
+    TagRegistry reg = new TagRegistry(ImmutableSet.of(tagHandler, new 
RepeatTagHandler()));
+    
+    DocumentFragment result = processor.processTemplate(templateDef, context, 
resolver, reg);
+    String output = serialize(result);
+    assertEquals(expectedResult, output);
+  }
+  
+  private Element parseTemplate(String markup) throws GadgetException {    
+    String content = "<script type=\"text/os-template\" xmlns:foo=\"" + 
TEST_NS + 
+        "\" xmlns:os=\"" + TagHandler.OPENSOCIAL_NAMESPACE + "\">" + markup + 
"</script>";
+    Document document = parser.parseDom(content);
+    return (Element) document.getElementsByTagName("script").item(0);
+  }
+  
+  private String serialize(Node node) throws IOException {
+    StringBuilder sb = new StringBuilder();
+    NodeList children = node.getChildNodes();
+    for (int i = 0; i < children.getLength(); i++) {
+      Node child = children.item(i);
+      NekoSerializer.serialize(child, sb);
+    }
+    return sb.toString();
+  }
+}

Propchange: 
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/templates/TemplateBasedTagHandlerTest.java
------------------------------------------------------------------------------
    svn:eol-style = native


Reply via email to