You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@shindig.apache.org by et...@apache.org on 2009/01/14 02:41:25 UTC

svn commit: r734296 - in /incubator/shindig/trunk/java/gadgets/src: main/java/org/apache/shindig/gadgets/expressions/ main/java/org/apache/shindig/gadgets/preload/ main/java/org/apache/shindig/gadgets/spec/ test/java/org/apache/shindig/gadgets/expressi...

Author: etnu
Date: Tue Jan 13 17:41:24 2009
New Revision: 734296

URL: http://svn.apache.org/viewvc?rev=734296&view=rev
Log:
Applied patch for SHINDIG-814 from Adam Winer.


Added:
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/expressions/
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/expressions/ElException.java
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/expressions/Expression.java
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/expressions/ExpressionContext.java
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/expressions/Expressions.java
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/expressions/GadgetExpressionContext.java
    incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/expressions/
    incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/expressions/ExpressionsTest.java
    incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/expressions/GadgetExpressionContextTest.java
Modified:
    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/spec/PipelinedData.java
    incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/preload/PipelinedDataPreloaderTest.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

Added: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/expressions/ElException.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/expressions/ElException.java?rev=734296&view=auto
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/expressions/ElException.java (added)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/expressions/ElException.java Tue Jan 13 17:41:24 2009
@@ -0,0 +1,36 @@
+/*
+ * 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.expressions;
+
+/**
+ * Exceptions thrown while evaluating expressions.
+ */
+public class ElException extends Exception {
+  public ElException(String message) {
+    super(message);
+  }
+
+  public ElException(Throwable cause) {
+    super(cause);
+  }
+
+  public ElException(String message, Throwable cause) {
+    super(message, cause);
+  }
+}

Added: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/expressions/Expression.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/expressions/Expression.java?rev=734296&view=auto
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/expressions/Expression.java (added)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/expressions/Expression.java Tue Jan 13 17:41:24 2009
@@ -0,0 +1,34 @@
+/*
+ * 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.expressions;
+
+/**
+ * A single expression.
+ * 
+ * @param <T> return type of the expression.
+ */
+public interface Expression<T> {
+  /**
+   * Evaluates the expression.
+   * @param context the context providing top-level variables.
+   * @return the result of expression evaluation.
+   * @throws ElException if evaluation fails.
+   */
+  T evaluate(ExpressionContext context) throws ElException;
+}

Added: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/expressions/ExpressionContext.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/expressions/ExpressionContext.java?rev=734296&view=auto
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/expressions/ExpressionContext.java (added)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/expressions/ExpressionContext.java Tue Jan 13 17:41:24 2009
@@ -0,0 +1,31 @@
+/*
+ * 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.expressions;
+
+/**
+ * Context
+ */
+public interface ExpressionContext {
+  /**
+   * Provide a top-level variable.
+   * @param name name of the top-level variable.
+   * @return the variable, or null if no variable exists with that name.
+   */
+  Object getVariable(String name);
+}

Added: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/expressions/Expressions.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/expressions/Expressions.java?rev=734296&view=auto
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/expressions/Expressions.java (added)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/expressions/Expressions.java Tue Jan 13 17:41:24 2009
@@ -0,0 +1,256 @@
+/*
+ * 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.expressions;
+
+import java.util.List;
+import java.util.Map;
+import java.util.StringTokenizer;
+
+import org.json.JSONArray;
+import org.json.JSONObject;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Lists;
+
+/**
+ * Implementation of the small subset of JSP EL that OpenSocial supports:
+ * - String concatenation
+ * - The "." operator
+ * - Limited type coercion
+ */
+public class Expressions {
+  /** Constant for the start of an expression */
+  private static final String EXPRESSION_START = "${";
+  /** Constant for the end of an expression */
+  private static final char EXPRESSION_END = '}';
+
+  /**
+   * Parse a string into an Expression object.
+   * @param <T> type of the expression 
+   * @param text the text in the expression
+   * @param type type of object the expression will return
+   * @throws ElException if errors are found in the expression while parsing.
+   *     Unterminated expressions, empty expressions, and constant values that
+   *     cannot be properly coerced will result in exceptions. 
+   */
+  public static <T> Expression<T> parse(String text, Class<T> type) throws ElException {
+    Preconditions.checkNotNull(text);
+    Preconditions.checkNotNull(type);
+
+    // Check for constant text (no EL), or one big EL expression
+    int nextExpressionIndex = nextExpression(text, 0);
+    if (nextExpressionIndex < 0) {
+      T value = coerce(text, type);
+      return new ConstantExpression<T>(value);
+    } else if (nextExpressionIndex == 0 && endOfExpression(text, 2) == text.length() - 1) {
+      return parseEL(text, 2, text.length() - 1, type);
+    }
+
+    // String concatenation case;  find each expression
+    List<Expression<String>> expressions = Lists.newArrayList();
+    int start = 0;
+    while (nextExpressionIndex >= 0) {
+      // Add a constant expression if needed
+      if (start < nextExpressionIndex) {
+        String constantText = text.substring(start, nextExpressionIndex);
+        expressions.add(new ConstantExpression<String>(constantText));
+      }
+
+      int endOfExpression = endOfExpression(text, nextExpressionIndex + 2);
+      if (endOfExpression < 0) {
+        throw new ElException("Unterminated expression in " + text);
+      }
+
+      expressions.add(parseEL(text, nextExpressionIndex + 2, endOfExpression, String.class));
+      start = endOfExpression + 1;
+      nextExpressionIndex = nextExpression(text, start);
+    }
+
+    if (start < text.length()) {
+      String constantText = text.substring(start);
+      expressions.add(new ConstantExpression<String>(constantText));
+    }
+
+    return new ConcatExpression<T>(expressions, type);
+  }
+
+  /**
+   * Parses an EL expression within a string.
+   * @throws ElException 
+   */
+  private static <T> Expression<T> parseEL(final String text, int from, int to,
+      final Class<T> type) throws ElException {
+    if (from == to) {
+      throw new ElException("Empty expression in " + text);
+    }
+
+    // TODO: the spec only describes support for "a.b", not "a.b.c.d".  Update
+    // the spec, or limit this function?
+    final String[] segments = text.substring(from, to).split("\\.");
+    return new Expression<T>() {
+
+      public T evaluate(ExpressionContext context) throws ElException {
+        Object value = context.getVariable(segments[0]);
+        for (int i = 1; i < segments.length; i++) {
+          if (value == null) {
+            throw new ElException("Could not find property \"" + segments[i - 1] + "\" in \""
+                + text + "\"");
+          }
+          value = getProperty(value, segments[i]);
+        }
+
+        return coerce(value, type);
+      }
+
+    };
+  }
+
+  private static Object getProperty(Object value, String propertyName) throws ElException {
+    if (value instanceof Map) {
+      Map<?, ?> map = (Map<?, ?>) value;
+      return map.get(propertyName);
+    } else if (value instanceof JSONObject) {
+      return ((JSONObject) value).opt(propertyName);
+    }
+
+    throw new ElException("Unsupported property parent type " + value.getClass());
+  }
+
+  /**
+   * Hardcoded Object-to-Object coercion logic.
+   * - Strings are parsed with toString().
+   * - Integers are parsed from numbers with intValue(), and with toString() and
+   *   Integer.parseInt() for all other types.
+   * - Booleans are false if they are numeric and equal to 0, if they are
+   *   Boolean.FALSE, or if they are case-insensitive equal to "false".
+   * - Arrays are parsed with comma separators. 
+   *      
+   */
+  static <T> T coerce(Object value, Class<T> type) throws ElException {
+    if (value == null) {
+      return null;
+    }
+
+    if (type == String.class) {
+      @SuppressWarnings("unchecked")
+      T string = (T) value.toString();
+      return string;
+    } else if (type == Integer.class) {
+      int intValue;
+      if (value instanceof Number) {
+        intValue = ((Number) value).intValue();
+      } else {
+        try {
+          intValue = Integer.parseInt(value.toString());
+        } catch (NumberFormatException nfe) {
+          throw new ElException(nfe);
+        }
+      }
+
+      @SuppressWarnings("unchecked")
+      T integer = (T) Integer.valueOf(intValue);
+      return integer;
+    } else if (type == JSONArray.class) {
+      JSONArray array;
+      if (value instanceof JSONArray) {
+        array = (JSONArray) value;
+      } else {
+        array = new JSONArray();
+        StringTokenizer tokenizer = new StringTokenizer(value.toString(), ",");
+        while (tokenizer.hasMoreTokens()) {
+          array.put(tokenizer.nextToken());
+        }
+      }
+
+      @SuppressWarnings("unchecked")
+      T t = (T) array;
+      return t;
+    } else if (type == Boolean.class) {
+      boolean boolValue;
+      // TODO: spec question, does this coercion make sense?
+      if (value instanceof Number) {
+        boolValue = ((Number) value).intValue() != 0;
+      } else if (value instanceof Boolean) {
+        boolValue = Boolean.TRUE.equals(value);
+      } else {
+        // TODO: especially this case...
+        boolValue = !"false".equalsIgnoreCase(value.toString());
+      }
+      
+      @SuppressWarnings("unchecked")
+      T t = (T) Boolean.valueOf(boolValue);
+      return t;
+    }
+
+    // Fallback, see if the type is already correct
+    try {
+      return type.cast(value);
+    } catch (ClassCastException cce) {
+      throw new ElException("Could not cast " + value + " to " + type.getCanonicalName(),
+          cce);
+    }
+  }
+
+  /** Find the start of the next expression */
+  private static int nextExpression(String text, int after) {
+    // TODO: JSP EL supports escaping.  The Pipelining spec does not.
+    // Add detection of "\${" to the spec?
+    return text.indexOf(EXPRESSION_START, after);
+  }
+
+  /** Find the end of the current expression */
+  private static int endOfExpression(String text, int after) {
+    // TODO: support escaping
+    return text.indexOf(EXPRESSION_END, after);
+  }
+
+  /** Expression class for constant values */
+  private static class ConstantExpression<T> implements Expression<T> {
+    private final T value;
+
+    public ConstantExpression(T value) {
+      this.value = value;
+    }
+
+    public T evaluate(ExpressionContext context) {
+      return value;
+    }
+  }
+
+  /** Expression class for string concatenation */
+  private static class ConcatExpression<T> implements Expression<T> {
+
+    private final List<Expression<String>> expressions;
+    private final Class<T> type;
+
+    public ConcatExpression(List<Expression<String>> expressions, Class<T> type) {
+      this.expressions = expressions;
+      this.type = type;
+    }
+
+    public T evaluate(ExpressionContext context) throws ElException {
+      StringBuilder builder = new StringBuilder();
+      for (Expression<String> expression : expressions) {
+        builder.append(expression.evaluate(context));
+      }
+
+      return coerce(builder.toString(), type);
+    }
+  }
+}
\ No newline at end of file

Added: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/expressions/GadgetExpressionContext.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/expressions/GadgetExpressionContext.java?rev=734296&view=auto
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/expressions/GadgetExpressionContext.java (added)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/expressions/GadgetExpressionContext.java Tue Jan 13 17:41:24 2009
@@ -0,0 +1,74 @@
+/*
+ * 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.expressions;
+
+import org.apache.shindig.gadgets.GadgetContext;
+
+import java.util.logging.Logger;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+/**
+ * Implementation of ExpressionContext for gadgets.  Supports
+ * two variables, "UserPrefs" and "ViewParams".
+ */
+public class GadgetExpressionContext implements ExpressionContext {
+  private static final Logger log = Logger.getLogger(GadgetExpressionContext.class.getName());
+
+  /** GadgetContext */
+  private final GadgetContext context;
+  
+  /** Lazily evaluated view params */
+  private JSONObject viewParams;
+
+  public GadgetExpressionContext(GadgetContext context) {
+    this.context = context;
+  }
+
+  public Object getVariable(String name) {
+    if ("UserPrefs".equals(name)) {
+      return context.getUserPrefs().getPrefs();
+    } else if ("ViewParams".equals(name)) {
+      return getViewParams();
+    }
+    
+    return null;
+  }
+
+  /** Instantiate and return a JSONObject off of view params */
+  private JSONObject getViewParams() {
+    if (viewParams == null) {
+      String params = context.getParameter("view-params");
+      if (params == null || "".equals(params)) {
+        viewParams = new JSONObject();
+      } else {
+        try {
+          viewParams = new JSONObject(params);
+        } catch (JSONException e) {
+          log.warning("Could not parse " + params);
+          viewParams = new JSONObject();
+        }
+      }
+    }
+
+    return viewParams;
+  }
+
+}

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=734296&r1=734295&r2=734296&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 Tue Jan 13 17:41:24 2009
@@ -28,6 +28,7 @@
 import org.apache.shindig.gadgets.http.RequestPipeline;
 import org.apache.shindig.gadgets.spec.GadgetSpec;
 import org.apache.shindig.gadgets.spec.Preload;
+import org.apache.shindig.gadgets.spec.RequestAuthenticationInfo;
 import org.apache.shindig.gadgets.spec.View;
 
 import com.google.common.collect.ImmutableMap;
@@ -69,7 +70,7 @@
         && phase == PreloaderService.PreloadPhase.PROXY_FETCH) {
 
       List<Callable<PreloadedData>> preloadList = Lists.newArrayList();
-      Map<String, Object> socialPreloads = view.getPipelinedData().getSocialPreloads();
+      Map<String, Object> socialPreloads = view.getPipelinedData().getSocialPreloads(context);
 
       // Load any social preloads into a JSONArray for delivery to
       // JsonRpcServlet
@@ -83,9 +84,11 @@
         preloadList.add(preloader);
       }
 
-      Map<String, Preload> httpPreloads = view.getPipelinedData().getHttpPreloads();
+      Map<String, RequestAuthenticationInfo> httpPreloads =
+          view.getPipelinedData().getHttpPreloads(context);
       if (!httpPreloads.isEmpty()) {
-        for (Map.Entry<String, Preload> httpPreloadEntry : httpPreloads.entrySet()) {
+        for (Map.Entry<String, RequestAuthenticationInfo> httpPreloadEntry
+            : httpPreloads.entrySet()) {
           preloadList.add(new HttpPreloadTask(context,  httpPreloadEntry.getValue(),
               httpPreloadEntry.getKey()));
         }
@@ -147,10 +150,10 @@
   // A task for preloading os:MakeRequest
   class HttpPreloadTask implements Callable<PreloadedData> {
     private final GadgetContext context;
-    private final Preload preload;
+    private final RequestAuthenticationInfo preload;
     private final String key;
 
-    public HttpPreloadTask(GadgetContext context, Preload preload, String key) {
+    public HttpPreloadTask(GadgetContext context, RequestAuthenticationInfo preload, String key) {
       this.context = context;
       this.preload = preload;
       this.key = key;
@@ -158,6 +161,23 @@
 
     public PreloadedData call() throws Exception {
       HttpRequest request = HttpPreloader.newHttpRequest(context, preload);
+      String refreshIntervalStr = preload.getAttributes().get("refreshInterval");
+      if (refreshIntervalStr != null) {
+        try {
+          int refreshInterval = Integer.parseInt(refreshIntervalStr);
+          request.setCacheTtl(refreshInterval);
+        } catch (NumberFormatException nfe) {
+          // Ignore, and use the HTTP response interval
+        }
+      }
+      
+      // TODO: support non GET/POST methods
+      String method = preload.getAttributes().get("method");
+      if (method != null) {
+        request.setMethod(method);
+      }
+      
+      // TODO: support params
       return new Data(requestPipeline.execute(request));
     }
 

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=734296&r1=734295&r2=734296&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 Tue Jan 13 17:41:24 2009
@@ -17,38 +17,49 @@
  */
 package org.apache.shindig.gadgets.spec;
 
-import com.google.common.collect.Maps;
 import org.apache.shindig.common.uri.Uri;
 import org.apache.shindig.common.xml.XmlException;
 import org.apache.shindig.gadgets.AuthType;
+import org.apache.shindig.gadgets.GadgetContext;
+import org.apache.shindig.gadgets.expressions.ElException;
+import org.apache.shindig.gadgets.expressions.Expression;
+import org.apache.shindig.gadgets.expressions.ExpressionContext;
+import org.apache.shindig.gadgets.expressions.Expressions;
+import org.apache.shindig.gadgets.expressions.GadgetExpressionContext;
 import org.apache.shindig.gadgets.variables.Substitutions;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.StringTokenizer;
+
 import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
 import org.w3c.dom.Element;
 import org.w3c.dom.Node;
 
-import java.util.Collections;
-import java.util.Map;
-import java.util.StringTokenizer;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
 
 /**
  * Parsing code for <os:*> elements.
  */
 public class PipelinedData {
-
-  private final Map<String, Object> socialPreloads;
-  private final Map<String, Preload> httpPreloads;
+  private final Map<String, SocialData> socialPreloads;
+  private final Map<String, HttpData> httpPreloads;
 
   private boolean needsViewer;
   private boolean needsOwner;
 
   public static final String OPENSOCIAL_NAMESPACE = "http://ns.opensocial.org/2008/markup";
 
-
   public PipelinedData(Element element, Uri base) throws SpecParserException {
-    Map<String, Object> socialPreloads = Maps.newHashMap();
-    Map<String, Preload> httpPreloads = Maps.newHashMap();
+    Map<String, SocialData> socialPreloads = Maps.newHashMap();
+    Map<String, HttpData> httpPreloads = Maps.newHashMap();
 
     // TODO: extract this loop into XmlUtils.getChildrenWithNamespace
     for (Node node = element.getFirstChild(); node != null; node = node.getNextSibling()) {
@@ -57,7 +68,7 @@
       }
 
       Element child = (Element) node;
-      // Ignore elements not in the namesapce
+      // Ignore elements not in the namespace
       if (!OPENSOCIAL_NAMESPACE.equals(child.getNamespaceURI())) {
         continue;
       }
@@ -77,18 +88,19 @@
         } else if ("OwnerRequest".equals(elementName)) {
           socialPreloads.put(key, createOwnerRequest(child));
         } else if ("PersonAppDataRequest".equals(elementName)) {
+          // TODO: delete when 0.9 app data retrieval is supported
           socialPreloads.put(key, createPersonAppDataRequest(child));
         } else if ("ActivitiesRequest".equals(elementName)) {
           socialPreloads.put(key, createActivityRequest(child));
         } else if ("MakeRequest".equals(elementName)) {
           httpPreloads.put(key, createMakeRequest(child, base));
         } else {
-          // TODO: This is wrong - the spec should parse, but should preload notImplemented
+          // TODO: This is wrong - the spec should parse, but should preload
+          // notImplemented
           throw new SpecParserException("Unknown element <os:" + elementName + ">");
         }
-        // TODO: implement DataRequest
-      } catch (JSONException je) {
-        throw new SpecParserException(new XmlException(je));
+      } catch (ElException ele) {
+        throw new SpecParserException(new XmlException(ele));
       }
     }
 
@@ -97,21 +109,21 @@
   }
 
   private PipelinedData(PipelinedData socialData, Substitutions substituter) {
-    Map<String, Object> socialPreloads = Maps.newHashMap();
-    Map<String, Preload> httpPreloads = Maps.newHashMap();
+    Map<String, SocialData> socialPreloads = Maps.newHashMap();
+    Map<String, HttpData> httpPreloads = Maps.newHashMap();
 
     // TODO: support hangman substitutions for social preloads?
     socialPreloads.putAll(socialData.socialPreloads);
-    
-    for (Map.Entry<String, Preload> httpPreload : socialData.httpPreloads.entrySet()) {
+
+    for (Map.Entry<String, HttpData> httpPreload : socialData.httpPreloads.entrySet()) {
       httpPreloads.put(httpPreload.getKey(), httpPreload.getValue().substitute(substituter));
     }
 
     this.socialPreloads = Collections.unmodifiableMap(socialPreloads);
     this.httpPreloads = Collections.unmodifiableMap(httpPreloads);
 
-  }
-  
+  } 
+
   /**
    * Allows the creation of a view from an existing view so that localization
    * can be performed.
@@ -120,12 +132,36 @@
     return new PipelinedData(this, substituter);
   }
 
-  public Map<String, Object> getSocialPreloads() {
-    return socialPreloads;
+  public Map<String, Object> getSocialPreloads(GadgetContext context) {
+    Map<String, Object> evaluatedPreloads = Maps.newHashMapWithExpectedSize(
+        socialPreloads.size());
+    ExpressionContext expressionContext = new GadgetExpressionContext(context);
+    for (Map.Entry<String, SocialData> preload : socialPreloads.entrySet()) {
+      try {
+        evaluatedPreloads.put(preload.getKey(), preload.getValue().toJson(expressionContext));
+      } catch (ElException e) {
+        // TODO: Handle!?!
+        throw new RuntimeException(e);
+      }
+    }
+
+    return evaluatedPreloads;
   }
 
-  public Map<String, Preload> getHttpPreloads() {
-    return httpPreloads;
+  public Map<String, RequestAuthenticationInfo> getHttpPreloads(GadgetContext context) {
+    Map<String, RequestAuthenticationInfo> evaluatedPreloads = Maps.newHashMapWithExpectedSize(
+        httpPreloads.size());
+    ExpressionContext expressionContext = new GadgetExpressionContext(context);
+    for (Map.Entry<String, HttpData> preload : httpPreloads.entrySet()) {
+      try {
+        evaluatedPreloads.put(preload.getKey(), preload.getValue().evaluate(expressionContext));
+      } catch (ElException e) {
+        // TODO: Handle!?!
+        throw new RuntimeException(e);
+      }
+    }
+
+    return evaluatedPreloads;
   }
 
   public boolean needsViewer() {
@@ -137,86 +173,81 @@
   }
 
   /** Handle the os:PeopleRequest element */
-  private Object createPeopleRequest(Element child) throws JSONException {
-    JSONObject params = new JSONObject();
-    copyAttribute("groupId", child, params);
-    copyArrayAttribute("userId", child, params, true);
-    copyArrayAttribute("personId", child, params, true);
-
-    copyIntegerAttribute("startIndex", child, params);
-    copyIntegerAttribute("count", child, params);
-    copyAttribute("sortBy", child, params);
-    copyAttribute("sortOrder", child, params);
-    copyAttribute("filterBy", child, params);
-    copyAttribute("filterOperation", child, params);
-    copyAttribute("filterValue", child, params);
-    copyArrayAttribute("fields", child, params, false);
-
-    JSONObject peopleRequest = new JSONObject();
-    peopleRequest.put("method", "people.get");
-    peopleRequest.put("params", params);
-    peopleRequest.put("id", child.getAttribute("key"));
-    return peopleRequest;
+  private SocialData createPeopleRequest(Element child) throws ElException {
+    SocialData expression = new SocialData(child.getAttribute("key"), "people.get");
+
+    copyAttribute("groupId", child, expression, String.class);
+    copyAttribute("userId", child, expression, JSONArray.class);
+    updateUserArrayState("userId", child);
+    copyAttribute("personId", child, expression, JSONArray.class);
+    updateUserArrayState("personId", child);
+
+    copyAttribute("startIndex", child, expression, Integer.class);
+    copyAttribute("count", child, expression, Integer.class);
+    copyAttribute("sortBy", child, expression, String.class);
+    copyAttribute("sortOrder", child, expression, String.class);
+    copyAttribute("filterBy", child, expression, String.class);
+    copyAttribute("filterOperation", child, expression, String.class);
+    copyAttribute("filterValue", child, expression, String.class);
+    copyAttribute("fields", child, expression, JSONArray.class);
+
+    return expression;
   }
 
   /** Handle the os:ViewerRequest element */
-  private Object createViewerRequest(Element child) throws JSONException {
+  private SocialData createViewerRequest(Element child) throws ElException {
     return createPersonRequest(child, "@viewer");
   }
 
   /** Handle the os:OwnerRequest element */
-  private Object createOwnerRequest(Element child) throws JSONException {
+  private SocialData createOwnerRequest(Element child) throws ElException {
     return createPersonRequest(child, "@owner");
   }
 
-  private Object createPersonRequest(Element child, String userId) throws JSONException {
-    JSONObject params = new JSONObject();
+  private SocialData createPersonRequest(Element child, String userId) throws ElException {
+    SocialData expression = new SocialData(child.getAttribute("key"), "people.get");
 
-    params.put("userId", new JSONArray().put(userId));
+    expression.addProperty("userId", userId, JSONArray.class);
     updateUserState(userId);
-    copyArrayAttribute("fields", child, params, false);
+    copyAttribute("fields", child, expression, JSONArray.class);
 
-    JSONObject peopleRequest = new JSONObject();
-    peopleRequest.put("method", "people.get");
-    peopleRequest.put("params", params);
-    peopleRequest.put("id", child.getAttribute("key"));
-    return peopleRequest;
+    return expression;
   }
 
   /** Handle the os:PersonAppDataRequest element */
-  private Object createPersonAppDataRequest(Element child) throws JSONException {
-    JSONObject params = new JSONObject();
-    copyAttribute("groupId", child, params);
-    copyArrayAttribute("userId", child, params, true);
-    copyAttribute("appId", child, params);
-    copyArrayAttribute("fields", child, params, false);
-
-    JSONObject appDataRequest = new JSONObject();
-    appDataRequest.put("method", "appdata.get");
-    appDataRequest.put("params", params);
-    appDataRequest.put("id", child.getAttribute("key"));
-    return appDataRequest;
+  private SocialData createPersonAppDataRequest(Element child) throws ElException {
+    SocialData expression = new SocialData(child.getAttribute("key"), "appdata.get");
+    
+    copyAttribute("groupId", child, expression, String.class);
+    copyAttribute("userId", child, expression, JSONArray.class);
+    updateUserArrayState("userId", child);
+    copyAttribute("appId", child, expression, String.class);
+    copyAttribute("fields", child, expression, JSONArray.class);
+
+    return expression;
   }
 
   /** Handle the os:ActivitiesRequest element */
-  private Object createActivityRequest(Element child) throws JSONException {
-    JSONObject params = new JSONObject();
-    copyAttribute("groupId", child, params);
-    copyArrayAttribute("userId", child, params, true);
-    copyAttribute("appId", child, params);
-    copyArrayAttribute("activitityId", child, params, false);
-    copyArrayAttribute("fields", child, params, false);
-
-    JSONObject appDataRequest = new JSONObject();
-    appDataRequest.put("method", "activities.get");
-    appDataRequest.put("params", params);
-    appDataRequest.put("id", child.getAttribute("key"));
-    return appDataRequest;
+  private SocialData createActivityRequest(Element child) throws ElException {
+    SocialData expression = new SocialData(child.getAttribute("key"), "activities.get");
+    
+    copyAttribute("groupId", child, expression, String.class);
+    copyAttribute("userId", child, expression, JSONArray.class);
+    updateUserArrayState("userId", child);
+    copyAttribute("appId", child, expression, String.class);
+    // TODO: should be activityIds?
+    copyAttribute("activityId", child, expression, JSONArray.class);
+    copyAttribute("fields", child, expression, JSONArray.class);
+
+    return expression;
   }
 
   /** Handle an os:MakeRequest element */
-  private Preload createMakeRequest(Element child, Uri base) throws SpecParserException {
-    Preload preload = new Preload(child, base);
+  private HttpData createMakeRequest(Element child, Uri base) throws ElException {
+    HttpData data = new HttpData(child, base);
+
+    /* TODO: check auth type, and sign-by-owner/viewer, once spec agrees
+     * to remove support for EL on @authz and @sign_*.
     if (preload.getAuthType() != AuthType.NONE) {
       if (preload.isSignOwner()) {
         needsOwner = true;
@@ -225,46 +256,26 @@
       if (preload.isSignViewer()) {
         needsViewer = true;
       }
-    }
-
-    return preload;
-  }
+    }*/
 
-  private void copyAttribute(String name, Element element, JSONObject jsonObject)
-      throws JSONException {
-    if (element.hasAttribute(name)) {
-      jsonObject.put(name, element.getAttribute(name));
-    }
+    return data;
   }
 
-  private void copyIntegerAttribute(String name, Element element, JSONObject jsonObject)
-      throws JSONException {
+  private void copyAttribute(String name, Element element, SocialData expression, Class<?> type)
+    throws ElException {
     if (element.hasAttribute(name)) {
-      String valueStr = element.getAttribute(name);
-      try {
-        int value = Integer.parseInt(valueStr);
-        jsonObject.put(name, value);
-      } catch (NumberFormatException nfe) {
-        throw new JSONException(String.format("Value \"%s\" of attribute \"%s\" is not an integer",
-            valueStr, name));
-      }
+      expression.addProperty(name, element.getAttribute(name), type);
     }
   }
 
-  private void copyArrayAttribute(String name, Element element, JSONObject jsonObject,
-      boolean isUserAttribute) throws JSONException {
+  /** Look for @viewer, @owner within a userId attribute */
+  private void updateUserArrayState(String name, Element element) {
     if (element.hasAttribute(name)) {
-      StringTokenizer tokenizer = new StringTokenizer(element.getAttribute(name), ",");
-      JSONArray array = new JSONArray();
-      while (tokenizer.hasMoreTokens()) {
-        String token = tokenizer.nextToken();
-        array.put(token);
-        if (isUserAttribute) {
-          updateUserState(token);
-        }
+      // TODO: check after Expression evaluation?
+      StringTokenizer tokens = new StringTokenizer(element.getAttribute(name), ",");
+      while (tokens.hasMoreTokens()) {
+        updateUserState(tokens.nextToken());
       }
-
-      jsonObject.put(name, array);
     }
   }
 
@@ -276,4 +287,187 @@
       needsViewer = true;
     }
   }
+
+  /**
+   * A single pipelined HTTP makerequest.
+   */
+  private static class HttpData {
+    private final Expression<String> authz;
+    private final Uri base;
+    private final String href;
+    private final Expression<Boolean> signOwner;
+    private final Expression<Boolean> signViewer;
+    private final Map<String, Expression<String>> attributes;
+    
+    private static final Set<String> KNOWN_ATTRIBUTES =
+          ImmutableSet.of("authz", "href", "sign_owner", "sign_viewer");
+
+    /**
+     * Create an HttpData off an <os:makeRequest> element.
+     */
+    public HttpData(Element element, Uri base) throws ElException {
+      this.base = base;
+      
+      // TODO: Spec question;  should authz be EL enabled?
+      String authz = element.hasAttribute("authz") ? element.getAttribute("authz") : "none";
+      this.authz = Expressions.parse(authz, String.class);
+      
+      // TODO: Spec question;  should EL values be URL escaped?
+      this.href = element.getAttribute("href");
+      
+      // TODO: Spec question;  should sign_* be EL enabled?
+      this.signOwner = booleanExpression(element, "sign_owner");
+      this.signViewer = booleanExpression(element, "sign_viewer");
+      
+      Map<String, Expression<String>> attributes = Maps.newHashMap();
+      for (int i = 0; i < element.getAttributes().getLength(); i++) {
+        Node attr = element.getAttributes().item(i);
+        if (!KNOWN_ATTRIBUTES.contains(attr.getNodeName())) {
+          attributes.put(attr.getNodeName(), Expressions.parse(attr.getNodeValue(), String.class));
+        }
+      }
+      
+      this.attributes = ImmutableMap.copyOf(attributes);
+    }
+    
+    private HttpData(HttpData data, Substitutions substituter) {
+      this.base = data.base;
+      this.authz = data.authz;
+      this.href = substituter.substituteString(data.href);
+      this.signOwner = data.signOwner;
+      this.signViewer = data.signViewer;
+      this.attributes = data.attributes;
+    }
+    
+    /** Run substitutions over an HttpData */
+    public HttpData substitute(Substitutions substituter) {
+      return new HttpData(this, substituter);
+    }
+    
+    /**
+     * Evaluate expressions and return a RequestAuthenticationInfo.
+     * @throws ElException if expression evaluation fails.
+     */
+    public RequestAuthenticationInfo evaluate(ExpressionContext context) throws ElException {
+      final AuthType authType = AuthType.parse(authz.evaluate(context));
+      Expression<String> hrefExpression = Expressions.parse(href, String.class);
+      final Uri evaluatedHref = base.resolve(Uri.parse(hrefExpression.evaluate(context)));
+
+      final boolean evaluatedSignOwner = evaluateBooleanExpression(context,
+          this.signOwner, true);
+      final boolean evaluatedSignViewer = evaluateBooleanExpression(context,
+          this.signViewer, true);
+      final Map<String, String> evaluatedAttributes = Maps.newHashMap();
+      for (Map.Entry<String, Expression<String>> attr : attributes.entrySet()) {
+        evaluatedAttributes.put(attr.getKey(), attr.getValue().evaluate(context));
+      }
+      
+      return new RequestAuthenticationInfo() {
+        public Map<String, String> getAttributes() {
+          return evaluatedAttributes;
+        }
+
+        public AuthType getAuthType() {
+          return authType;
+        }
+
+        public Uri getHref() {
+          return evaluatedHref;
+        }
+
+        public boolean isSignOwner() {
+          return evaluatedSignOwner;
+        }
+
+        public boolean isSignViewer() {
+          return evaluatedSignViewer;
+        }
+      };
+    }
+    
+    /** Evaluate a boolean expression, choosing a default value if needed */
+    private boolean evaluateBooleanExpression(ExpressionContext context,
+        Expression<Boolean> expression, boolean defaultValue) throws ElException {
+      if (expression == null) {
+        return defaultValue;
+      }
+
+      Boolean value = expression.evaluate(context);
+      if (value == null) {
+        return defaultValue;
+      }
+      
+      return value;
+    }
+
+    /** Parse a boolean expression off an XML attribute. */
+    private Expression<Boolean> booleanExpression(Element element, String attrName)
+        throws ElException {
+      if (!element.hasAttribute(attrName)) {
+        return null;
+      }
+      
+      return Expressions.parse(element.getAttribute(attrName), Boolean.class);
+    }
+  }
+  
+  /**
+   * A single social data request.
+   */
+  private static class SocialData {
+    private final List<Property> properties = Lists.newArrayList();
+    private final String id;
+    private final String method;
+
+    public SocialData(String id, String method) {
+      this.id = id;
+      this.method = method;
+    }
+
+    public void addProperty(String name, String value, Class<?> type) throws ElException {
+      Expression<?> expression = Expressions.parse(value, type);
+      properties.add(new Property(name, expression));
+    }
+
+    /** Create the JSON request form for the social data */
+    public JSONObject toJson(ExpressionContext context) throws ElException {
+      JSONObject object = new JSONObject();
+      try {
+        object.put("method", method);
+        object.put("id", id);
+
+        JSONObject params = new JSONObject();
+        for (Property property : properties) {
+          property.set(context, params);
+        }
+        object.put("params", params);
+      } catch (JSONException je) {
+        throw new ElException(je);
+      }
+
+      return object;
+    }
+
+    /** Single property for an expression */
+    private static class Property {
+      private final Expression<?> expression;
+      private final String name;
+
+      public Property(String name, Expression<?> expression) {
+        this.name = name;
+        this.expression = expression;
+      }
+
+      public void set(ExpressionContext context, JSONObject object) throws ElException {
+        Object value = expression.evaluate(context);
+        try {
+          if (value != null) {
+            object.put(name, value);
+          }
+        } catch (JSONException e) {
+          throw new ElException("Error parsing property \"" + name + "\"", e);
+        }
+      }
+    }
+  }
 }

Added: incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/expressions/ExpressionsTest.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/expressions/ExpressionsTest.java?rev=734296&view=auto
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/expressions/ExpressionsTest.java (added)
+++ incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/expressions/ExpressionsTest.java Tue Jan 13 17:41:24 2009
@@ -0,0 +1,121 @@
+/*
+ * 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.expressions;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import java.util.Map;
+
+import org.json.JSONObject;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Maps;
+
+
+/**
+ * Tests of the Expressions class.
+ */
+public class ExpressionsTest {
+  private FakeContext context;
+
+  @Before
+  public void setUp() {
+    context = new FakeContext();
+  }
+  
+  @Test
+  public void constantExpressions() throws Exception {
+    assertEquals("foo", Expressions.parse("foo", String.class).evaluate(context));
+    assertEquals(1, Expressions.parse("1", Integer.class).evaluate(context).intValue());
+  }
+
+  @Test
+  public void simpleExpressions() throws Exception {
+    context.variables.put("var", "value");
+    context.variables.put("int", 1);
+    
+    Expression<String> var = Expressions.parse("${var}", String.class);
+    assertEquals("value", var.evaluate(context));
+    
+    Expression<Integer> intExpression = Expressions.parse("${int}", Integer.class);
+    assertEquals(1, intExpression.evaluate(context).intValue());
+  }
+
+  @Test
+  public void variableNotFoundIsNull() throws Exception {
+    Expression<String> var = Expressions.parse("${var}", String.class);
+    assertNull(var.evaluate(context));
+  }
+
+  @Test
+  public void propertyEvaluationForMaps() throws Exception {
+    context.variables.put("var", ImmutableMap.of("one", 1, "two", 2));
+    
+    Expression<Integer> var = Expressions.parse("${var.one}${var.two}", Integer.class);
+    // 1 and 2 concatenated make 12, not 3
+    assertEquals(12, var.evaluate(context).intValue());    
+  }
+
+  @Test
+  public void propertyEvaluationForJson() throws Exception {
+    context.variables.put("var", new JSONObject("{top: {middle: {inner: 'value'}}}"));
+    
+    Expression<String> var = Expressions.parse("${var.top.middle.inner}", String.class);
+    assertEquals("value", var.evaluate(context));    
+  }
+
+  @Test(expected = ElException.class)
+  public void exceptionWhenCoercionFails() throws Exception {
+    context.variables.put("var", "value");
+    
+    Expression<Integer> var = Expressions.parse("${var}", Integer.class);
+    var.evaluate(context);
+  }
+
+  @Test(expected = ElException.class)
+  public void exceptionIfExpressionNotClosed() throws Exception {
+    Expressions.parse("${var${foo", Integer.class);
+  }
+
+  @Test(expected = ElException.class)
+  public void exceptionWhenPropertyEvaluationFails() throws Exception {
+    Expression<String> var = Expressions.parse("${var.property}", String.class);
+    var.evaluate(context);
+  }
+
+  @Test
+  public void concatExpressions() throws Exception {
+    context.variables.put("var", "value");
+    
+    Expression<String> concat = Expressions.parse("foo${var}bar", String.class);
+    assertEquals("foovaluebar", concat.evaluate(context));
+  }
+
+  static public class FakeContext implements ExpressionContext {
+    public final Map<String, Object> variables = Maps.newHashMap();
+    
+    public Object getVariable(String name) {
+      return variables.get(name);
+    }
+  }
+
+}

Added: incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/expressions/GadgetExpressionContextTest.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/expressions/GadgetExpressionContextTest.java?rev=734296&view=auto
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/expressions/GadgetExpressionContextTest.java (added)
+++ incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/expressions/GadgetExpressionContextTest.java Tue Jan 13 17:41:24 2009
@@ -0,0 +1,97 @@
+/*
+ * 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.expressions;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import org.apache.shindig.gadgets.GadgetContext;
+import org.apache.shindig.gadgets.UserPrefs;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import com.google.common.collect.ImmutableMap;
+
+/**
+ * Test of GadgetExpressionContext.
+ */
+public class GadgetExpressionContextTest {
+
+  private UserPrefs userPrefs;
+  private String viewParams;
+  private GadgetExpressionContext context;
+
+  @Before
+  public void setUp() throws Exception {
+    GadgetContext gadgetContext = new GadgetContext() {
+      @Override
+      public String getParameter(String name) {
+        if ("view-params".equals(name)) {
+          return viewParams;
+        }
+        
+        return null;
+      }
+
+      @Override
+      public UserPrefs getUserPrefs() {
+        return userPrefs;
+      }
+    };
+    
+    context = new GadgetExpressionContext(gadgetContext);
+  }
+
+  @Test
+  public void getPrefs() throws ElException {
+    userPrefs = new UserPrefs(ImmutableMap.of("foo", "bar"));
+    Expression<String> expression = Expressions.parse("${UserPrefs.foo}", String.class);
+    
+    assertEquals("bar", expression.evaluate(context));
+
+    expression = Expressions.parse("${UserPrefs.wrongKey}", String.class);
+    assertNull(expression.evaluate(context));
+  }
+
+  @Test
+  public void getPrefsEmpty() throws ElException {
+    userPrefs = UserPrefs.EMPTY;
+    Expression<String> expression = Expressions.parse("${UserPrefs.foo}", String.class);
+    assertNull(expression.evaluate(context));
+  }
+
+
+  @Test
+  public void testViewParams() throws ElException {
+    viewParams = "{foo: 'bar'}";
+    
+    Expression<String> expression = Expressions.parse("${ViewParams.foo}", String.class);
+    assertEquals("bar", expression.evaluate(context));
+
+    expression = Expressions.parse("${ViewParams.wrongKey}", String.class);
+    assertNull(expression.evaluate(context));
+  }
+
+  @Test
+  public void testViewParamsEmpty() throws ElException {
+    Expression<String> expression = Expressions.parse("${ViewParams.foo}", String.class);
+    assertNull(expression.evaluate(context));
+  }
+}

Modified: incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/preload/PipelinedDataPreloaderTest.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/preload/PipelinedDataPreloaderTest.java?rev=734296&r1=734295&r2=734296&view=diff
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/preload/PipelinedDataPreloaderTest.java (original)
+++ incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/preload/PipelinedDataPreloaderTest.java Tue Jan 13 17:41:24 2009
@@ -47,18 +47,25 @@
 public class PipelinedDataPreloaderTest extends PreloaderTestFixture {
   private ContainerConfig containerConfig;
 
-  private static final String XML = "<Module xmlns:os=\"" + PipelinedData.OPENSOCIAL_NAMESPACE + "\">"
-      + "<ModulePrefs title=\"Title\"/>"
+  private static final String XML = "<Module xmlns:os=\"" + PipelinedData.OPENSOCIAL_NAMESPACE
+      + "\">" + "<ModulePrefs title=\"Title\"/>"
       + "<Content href=\"http://example.org/proxied.php\" view=\"profile\">"
       + "  <os:PeopleRequest key=\"p\" userIds=\"you\"/>"
-      + "  <os:PersonAppDataRequest key=\"a\" userId=\"she\"/>"
-      + "</Content></Module>";
+      + "  <os:PersonAppDataRequest key=\"a\" userId=\"she\"/>" + "</Content></Module>";
+
+  private static final String MAKE_REQUEST_URL =  "http://example.org/preload.html";
+  private static final String XML_WITH_MAKE_REQUEST = "<Module xmlns:os=\""
+      + PipelinedData.OPENSOCIAL_NAMESPACE + "\">"
+      + "<ModulePrefs title=\"Title\"/>"
+      + "<Content href=\"http://example.org/proxied.php\" view=\"profile\">"
+      + "  <os:MakeRequest key=\"p\" href=\"" + MAKE_REQUEST_URL + "\" "
+      + "refreshInterval=\"60\" method=\"POST\"/>" + "</Content></Module>";
 
   @Before
   public void createContainerConfig() {
     containerConfig = EasyMock.createMock(ContainerConfig.class);
-    EasyMock.expect(containerConfig.get(CONTAINER, "gadgets.osDataUri"))
-        .andStubReturn("http://%host%/social/rpc");
+    EasyMock.expect(containerConfig.get(CONTAINER, "gadgets.osDataUri")).andStubReturn(
+        "http://%host%/social/rpc");
     EasyMock.replay(containerConfig);
   }
 
@@ -72,8 +79,8 @@
     view = "profile";
     contextParams.put("st", "token");
 
-    Collection<Callable<PreloadedData>> tasks =
-        preloader.createPreloadTasks(context, spec, PreloaderService.PreloadPhase.PROXY_FETCH);
+    Collection<Callable<PreloadedData>> tasks = preloader.createPreloadTasks(context, spec,
+        PreloaderService.PreloadPhase.PROXY_FETCH);
     assertEquals(1, tasks.size());
     // Nothing fetched yet
     assertEquals(0, pipeline.requests.size());
@@ -90,13 +97,43 @@
     assertEquals(1, pipeline.requests.size());
     HttpRequest request = pipeline.requests.get(0);
 
-    assertEquals("http://" + context.getHost() + "/social/rpc?st=token",
-        request.getUri().toString());
+    assertEquals("http://" + context.getHost() + "/social/rpc?st=token", request.getUri()
+        .toString());
     assertEquals("POST", request.getMethod());
     assertTrue(request.getContentType().startsWith("application/json"));
   }
 
   @Test
+  public void testHttpPreload() throws Exception {
+    GadgetSpec spec = new GadgetSpec(GADGET_URL, XML_WITH_MAKE_REQUEST);
+
+    String httpResult = "{foo: 'bar'}";
+    RecordingRequestPipeline pipeline = new RecordingRequestPipeline(httpResult);
+    PipelinedDataPreloader preloader = new PipelinedDataPreloader(pipeline, containerConfig);
+    view = "profile";
+
+    Collection<Callable<PreloadedData>> tasks = preloader.createPreloadTasks(context, spec,
+        PreloaderService.PreloadPhase.PROXY_FETCH);
+    assertEquals(1, tasks.size());
+    // Nothing fetched yet
+    assertEquals(0, pipeline.requests.size());
+
+    Map<String, Object> result = tasks.iterator().next().call().toJson();
+    assertEquals(1, result.size());
+
+    String expectedResult = "{data: {foo: 'bar'}, id: 'p'}";
+    assertEquals(new JSONObject(expectedResult).toString(), result.get("p").toString());
+
+    // Should have only fetched one request
+    assertEquals(1, pipeline.requests.size());
+    HttpRequest request = pipeline.requests.get(0);
+
+    assertEquals(MAKE_REQUEST_URL, request.getUri().toString());
+    assertEquals("POST", request.getMethod());
+    assertEquals(60, request.getCacheTtl());
+  }
+
+  @Test
   public void testSocialPreloadForOtherView() throws Exception {
     GadgetSpec spec = new GadgetSpec(GADGET_URL, XML);
 
@@ -106,8 +143,8 @@
     view = "canvas";
     contextParams.put("st", "token");
 
-    Collection<Callable<PreloadedData>> tasks =
-        preloader.createPreloadTasks(context, spec, PreloaderService.PreloadPhase.PROXY_FETCH);
+    Collection<Callable<PreloadedData>> tasks = preloader.createPreloadTasks(context, spec,
+        PreloaderService.PreloadPhase.PROXY_FETCH);
     assertTrue(tasks.isEmpty());
   }
 
@@ -121,8 +158,8 @@
     view = "profile";
     contextParams.put("st", "token");
 
-    Collection<Callable<PreloadedData>> tasks =
-        preloader.createPreloadTasks(context, spec, PreloaderService.PreloadPhase.HTML_RENDER);
+    Collection<Callable<PreloadedData>> tasks = preloader.createPreloadTasks(context, spec,
+        PreloaderService.PreloadPhase.HTML_RENDER);
     assertTrue(tasks.isEmpty());
   }
 
@@ -138,9 +175,7 @@
 
     public HttpResponse execute(HttpRequest request) {
       requests.add(request);
-      return new HttpResponseBuilder()
-          .setResponseString(content)
-          .create();
+      return new HttpResponseBuilder().setResponseString(content).create();
     }
   }
 }

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=734296&r1=734295&r2=734296&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 Tue Jan 13 17:41:24 2009
@@ -23,13 +23,27 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
+import org.apache.shindig.common.uri.Uri;
 import org.apache.shindig.common.xml.XmlUtil;
+import org.apache.shindig.gadgets.AuthType;
+import org.apache.shindig.gadgets.GadgetContext;
+
+import java.util.Map;
+
 import org.json.JSONObject;
+import org.junit.Before;
 import org.junit.Test;
 
 public class PipelinedDataTest {
   //TODO: test os:MakeRequest
 
+  private GadgetContext context;
+
+  @Before
+  public void setUp() {
+    context = new GadgetContext();
+  }
+  
   @Test
   public void testPeopleRequest() throws Exception {
     String xml = "<Content><PeopleRequest xmlns=\"" + PipelinedData.OPENSOCIAL_NAMESPACE + "\" "
@@ -53,8 +67,35 @@
         + "fields: ['name','id']"
         + "}}");
 
-    assertEquals(1, socialData.getSocialPreloads().size());
-    assertEquals(expected.toString(), socialData.getSocialPreloads().get("key").toString());
+    assertEquals(1, socialData.getSocialPreloads(context).size());
+    assertEquals(expected.toString(), socialData.getSocialPreloads(context).get("key").toString());
+  }
+  
+  @Test
+  public void testPeopleRequestWithExpressions() throws Exception {
+    String xml = "<Content><PeopleRequest xmlns=\"" + PipelinedData.OPENSOCIAL_NAMESPACE + "\" "
+        + " key=\"key\""
+        + " groupId=\"group\""
+        + " userId=\"first,second\""
+        + " startIndex=\"20\""
+        + " count=\"10\""
+        + " fields=\"name,id\""
+        + "/></Content>";
+
+    PipelinedData socialData = new PipelinedData(XmlUtil.parse(xml), null);
+    assertFalse(socialData.needsOwner());
+    assertFalse(socialData.needsViewer());
+
+    JSONObject expected = new JSONObject("{method: 'people.get', id: 'key', params:"
+        + "{groupId: 'group',"
+        + "userId: ['first','second'],"
+        + "startIndex: 20,"
+        + "count: 10,"
+        + "fields: ['name','id']"
+        + "}}");
+
+    assertEquals(1, socialData.getSocialPreloads(context).size());
+    assertEquals(expected.toString(), socialData.getSocialPreloads(context).get("key").toString());
   }
 
   @Test
@@ -73,8 +114,8 @@
         + "fields: ['name','id']"
         + "}}");
 
-    assertEquals(1, socialData.getSocialPreloads().size());
-    assertEquals(expected.toString(), socialData.getSocialPreloads().get("key").toString());
+    assertEquals(1, socialData.getSocialPreloads(context).size());
+    assertEquals(expected.toString(), socialData.getSocialPreloads(context).get("key").toString());
   }
 
   @Test
@@ -93,8 +134,8 @@
         + "fields: ['name','id']"
         + "}}");
 
-    assertEquals(1, socialData.getSocialPreloads().size());
-    assertEquals(expected.toString(), socialData.getSocialPreloads().get("key").toString());
+    assertEquals(1, socialData.getSocialPreloads(context).size());
+    assertEquals(expected.toString(), socialData.getSocialPreloads(context).get("key").toString());
   }
 
   @Test
@@ -114,8 +155,8 @@
         + "fields: ['foo','bar']"
         + "}}");
 
-    assertEquals(1, socialData.getSocialPreloads().size());
-    assertEquals(expected.toString(), socialData.getSocialPreloads().get("key").toString());
+    assertEquals(1, socialData.getSocialPreloads(context).size());
+    assertEquals(expected.toString(), socialData.getSocialPreloads(context).get("key").toString());
   }
 
   @Test
@@ -135,8 +176,8 @@
         + "fields: ['foo','bar']"
         + "}}");
 
-    assertEquals(1, socialData.getSocialPreloads().size());
-    assertEquals(expected.toString(), socialData.getSocialPreloads().get("key").toString());
+    assertEquals(1, socialData.getSocialPreloads(context).size());
+    assertEquals(expected.toString(), socialData.getSocialPreloads(context).get("key").toString());
   }
 
   @Test
@@ -150,7 +191,7 @@
     PipelinedData socialData = new PipelinedData(XmlUtil.parse(xml), null);
     assertFalse(socialData.needsOwner());
 
-    assertTrue(socialData.getSocialPreloads().isEmpty());
+    assertTrue(socialData.getSocialPreloads(context).isEmpty());
   }
 
   @Test(expected = SpecParserException.class)
@@ -160,4 +201,43 @@
 
     new PipelinedData(XmlUtil.parse(xml), null);
   }
+  
+  @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);
+    
+    assertEquals(1, httpPreloads.size());
+    RequestAuthenticationInfo preload = httpPreloads.get("key");
+    assertEquals(AuthType.NONE, preload.getAuthType());
+    assertEquals(Uri.parse("http://example.org/example.html"), preload.getHref());    
+  }
+
+  @Test
+  public void makeRequestDefaultsSigned() throws Exception {
+    String xml = "<Content><MakeRequest xmlns=\"" + PipelinedData.OPENSOCIAL_NAMESPACE + "\" "
+        + " key=\"key\""
+        + " href=\"/example.html\""
+        + " authz=\"signed\""
+        + " sign_owner=\"false\""
+        + "/></Content>";
+
+    PipelinedData pipelinedData = new PipelinedData(
+        XmlUtil.parse(xml), Uri.parse("http://example.org/"));
+    Map<String, RequestAuthenticationInfo> httpPreloads = 
+        pipelinedData.getHttpPreloads(context);
+    
+    assertEquals(1, httpPreloads.size());
+    RequestAuthenticationInfo preload = httpPreloads.get("key");
+    assertEquals(AuthType.SIGNED, preload.getAuthType());
+    assertTrue(preload.isSignViewer());
+    assertFalse(preload.isSignOwner());
+  }
 }

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=734296&r1=734295&r2=734296&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 Tue Jan 13 17:41:24 2009
@@ -27,6 +27,7 @@
 
 import org.apache.shindig.common.uri.Uri;
 import org.apache.shindig.common.xml.XmlUtil;
+import org.apache.shindig.gadgets.GadgetContext;
 import org.apache.shindig.gadgets.variables.Substitutions;
 import org.apache.shindig.gadgets.variables.Substitutions.Type;
 import org.junit.Test;
@@ -207,9 +208,10 @@
         + " 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().size());
-    assertTrue(view.getPipelinedData().getSocialPreloads().containsKey("key"));
+    assertEquals(1, view.getPipelinedData().getSocialPreloads(context).size());
+    assertTrue(view.getPipelinedData().getSocialPreloads(context).containsKey("key"));
   }
 
   @Test(expected = SpecParserException.class)