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

svn commit: r741354 [1/2] - in /incubator/shindig/trunk: ./ java/common/ java/common/src/main/java/org/apache/shindig/config/ java/common/src/main/java/org/apache/shindig/expressions/ java/common/src/test/java/org/apache/shindig/config/ java/common/src...

Author: awiner
Date: Fri Feb  6 00:31:19 2009
New Revision: 741354

URL: http://svn.apache.org/viewvc?rev=741354&view=rev
Log:
SHINDIG-902: Support full expression syntax, not just "."
- Add JUEL, and use its implementation of the javax.el API.
- As there's no support for escaping "." in property names, switch from
    ${foo.some\.property} to ${foo['some.property']}
- Since there's no support for ['...'] at the top-level, switch from
    ${some\.property.foo} to ${Cur['some.property'].foo}

Benchmarks show this implementation is ca. 10% slower than the hand-coded version for concatenation and property access.

Added:
    incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/config/ContainerConfigELResolver.java
    incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/expressions/JsonELResolver.java
    incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/expressions/RootELResolver.java
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/GadgetELResolver.java
    incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/GadgetELResolverTest.java
Removed:
    incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/config/ContainerConfigExpressionContext.java
    incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/expressions/ElException.java
    incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/expressions/Expression.java
    incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/expressions/ExpressionContext.java
    incubator/shindig/trunk/java/common/src/test/java/org/apache/shindig/config/ContainerConfigExpressionContextTest.java
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/GadgetExpressionContext.java
    incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/GadgetExpressionContextTest.java
Modified:
    incubator/shindig/trunk/java/common/pom.xml
    incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/config/AbstractContainerConfig.java
    incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/config/DynamicConfigProperty.java
    incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/config/JsonContainerConfig.java
    incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/expressions/Expressions.java
    incubator/shindig/trunk/java/common/src/test/java/org/apache/shindig/config/JsonContainerConfigTest.java
    incubator/shindig/trunk/java/common/src/test/java/org/apache/shindig/expressions/ExpressionsTest.java
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/process/Processor.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/process/ProcessorTest.java
    incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/ActivityHandler.java
    incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/PersonHandler.java
    incubator/shindig/trunk/java/social-api/src/test/java/org/apache/shindig/social/opensocial/service/ActivityHandlerTest.java
    incubator/shindig/trunk/java/social-api/src/test/java/org/apache/shindig/social/opensocial/service/PersonHandlerTest.java
    incubator/shindig/trunk/pom.xml

Modified: incubator/shindig/trunk/java/common/pom.xml
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/common/pom.xml?rev=741354&r1=741353&r2=741354&view=diff
==============================================================================
--- incubator/shindig/trunk/java/common/pom.xml (original)
+++ incubator/shindig/trunk/java/common/pom.xml Fri Feb  6 00:31:19 2009
@@ -108,5 +108,9 @@
       <groupId>net.sf.ehcache</groupId>
       <artifactId>ehcache</artifactId>
     </dependency>
+    <dependency>
+      <groupId>de.odysseus.juel</groupId>
+      <artifactId>juel</artifactId>
+    </dependency>
   </dependencies>
 </project>

Modified: incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/config/AbstractContainerConfig.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/config/AbstractContainerConfig.java?rev=741354&r1=741353&r2=741354&view=diff
==============================================================================
--- incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/config/AbstractContainerConfig.java (original)
+++ incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/config/AbstractContainerConfig.java Fri Feb  6 00:31:19 2009
@@ -55,7 +55,9 @@
   public List<Object> getList(String container, String property) {
     Object value = getProperty(container, property);
     if (value instanceof List) {
-      return (List<Object>) value;
+      @SuppressWarnings("unchecked")
+      List<Object> listValue = (List<Object>) value;
+      return listValue;
     }
     return Collections.emptyList();
   }
@@ -63,7 +65,9 @@
   public Map<String, Object> getMap(String container, String property) {
     Object value = getProperty(container, property);
     if (value instanceof Map) {
-      return (Map<String, Object>) value;
+      @SuppressWarnings("unchecked")
+      Map<String, Object> mapValue = (Map<String, Object>) value;
+      return mapValue;
     }
     return Collections.emptyMap();
   }

Added: incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/config/ContainerConfigELResolver.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/config/ContainerConfigELResolver.java?rev=741354&view=auto
==============================================================================
--- incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/config/ContainerConfigELResolver.java (added)
+++ incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/config/ContainerConfigELResolver.java Fri Feb  6 00:31:19 2009
@@ -0,0 +1,131 @@
+/*
+ * 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.config;
+
+import java.beans.FeatureDescriptor;
+import java.util.Iterator;
+
+import javax.el.ELContext;
+import javax.el.ELResolver;
+import javax.el.PropertyNotWritableException;
+
+/**
+ * ELResolver that handles adds support for:
+ *   - the "Cur" property, for explicit reference to the current container config
+ *   - the "parent" property, for explicit and recursive reference to config parents
+ *   - implicit reference to top-level properties in the current container config
+ *     or inside any parents
+ */
+class ContainerConfigELResolver extends ELResolver {
+  /** Key for the current container. */
+  public static final String CURRENT_CONFIG_KEY = "Cur";
+  
+  private final ContainerConfig config;
+  private final String currentContainer;
+
+  public ContainerConfigELResolver(ContainerConfig config, String currentContainer) {
+    this.config = config;
+    this.currentContainer = currentContainer;
+  }
+  
+  @Override
+  public Class<?> getCommonPropertyType(ELContext context, Object base) {
+    if ((base == null) || (base instanceof ContainerReference)) {
+      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) {
+    if ((base == null) || (base instanceof ContainerReference)) {
+      context.setPropertyResolved(true);
+      Object value = getValue(context, base, property);
+      return (value == null) ? null : value.getClass();
+    }
+    
+    return null;
+  }
+
+  @Override
+  public Object getValue(ELContext context, Object base, Object property) {    
+    // Handle all requests off the base, and anything that is a reference to
+    // a container
+    String container = null;
+    if (base == null) {
+      container = currentContainer;
+    } else if (base instanceof ContainerReference) {
+      container = ((ContainerReference) base).containerName;
+    } else {
+      // Not ours - return without setPropertyResolved(true)
+      return null;
+    }
+
+    context.setPropertyResolved(true);
+    if (JsonContainerConfig.PARENT_KEY.equals(property)) {  
+      // "parent": find the parent of the base, and return a ContainerReference
+      String parent = config.getString(container, JsonContainerConfig.PARENT_KEY);
+      if (parent == null) {
+        return null;
+      } else {
+        ContainerReference reference = new ContainerReference();
+        reference.containerName = parent;
+        return reference;
+      }
+    } else if (CURRENT_CONFIG_KEY.equals(property) && base == null) {
+      // "Cur": return a reference to the current container
+      ContainerReference reference = new ContainerReference();
+      reference.containerName = currentContainer;
+      return reference;
+    } else {
+      // Referring to a property of an existing container
+      return config.getProperty(container, property.toString());
+    }
+  }
+
+  @Override
+  public boolean isReadOnly(ELContext context, Object base, Object property) {
+    if ((base == null) || (base instanceof ContainerReference)) {
+      context.setPropertyResolved(true);
+      return true;
+    }
+    
+    return false;
+  }
+
+  @Override
+  public void setValue(ELContext context, Object base, Object property, Object value) {
+    // No support for mutating container configs
+    if ((base == null) || (base instanceof ContainerReference)) {
+      throw new PropertyNotWritableException();
+    }
+  }
+
+  /** A reference to the container, for later EL evaluation */
+  private static class ContainerReference {
+    public String containerName;
+  }
+}

Modified: incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/config/DynamicConfigProperty.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/config/DynamicConfigProperty.java?rev=741354&r1=741353&r2=741354&view=diff
==============================================================================
--- incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/config/DynamicConfigProperty.java (original)
+++ incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/config/DynamicConfigProperty.java Fri Feb  6 00:31:19 2009
@@ -18,11 +18,15 @@
  */
 package org.apache.shindig.config;
 
-import org.apache.shindig.expressions.ElException;
-import org.apache.shindig.expressions.Expression;
-import org.apache.shindig.expressions.ExpressionContext;
 import org.apache.shindig.expressions.Expressions;
 
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.el.ELContext;
+import javax.el.ELException;
+import javax.el.ValueExpression;
+
 
 /**
  * String property that can be interpreted using a container context.
@@ -30,25 +34,21 @@
  * Implements CharSequence strictly as a marker. Only toString is supported.
  */
 public class DynamicConfigProperty implements CharSequence {
-  private final ExpressionContext context;
-  private final Expression<String> expression;
+  private static final Logger logger = Logger.getLogger(DynamicConfigProperty.class.getName()); 
+  private final ELContext context;
+  private final ValueExpression expression;
 
-  public DynamicConfigProperty(String value, ExpressionContext context) {
+  public DynamicConfigProperty(String value, Expressions expressions, ELContext context) {
     this.context = context;
-    Expression<String> expression = null;
-    try {
-      expression = Expressions.parse(value, String.class);
-    } catch (ElException e) {
-      expression = new Expressions.ConstantExpression<String>(value);
-    }
-    this.expression = expression;
+    this.expression = expressions.parse(value, String.class);
   }
 
   @Override
   public String toString() {
     try {
-      return expression.evaluate(context);
-    } catch (ElException e) {
+      return (String) expression.getValue(context);
+    } catch (ELException e) {
+      logger.log(Level.WARNING, "Evaluation of " + expression.getExpressionString() + " failed", e);
       return "";
     }
   }

Modified: incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/config/JsonContainerConfig.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/config/JsonContainerConfig.java?rev=741354&r1=741353&r2=741354&view=diff
==============================================================================
--- incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/config/JsonContainerConfig.java (original)
+++ incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/config/JsonContainerConfig.java Fri Feb  6 00:31:19 2009
@@ -19,19 +19,10 @@
 
 package org.apache.shindig.config;
 
+import org.apache.commons.lang.StringUtils;
 import org.apache.shindig.common.JsonSerializer;
 import org.apache.shindig.common.util.ResourceLoader;
-import org.apache.shindig.expressions.ElException;
-import org.apache.shindig.expressions.Expression;
-import org.apache.shindig.expressions.ExpressionContext;
 import org.apache.shindig.expressions.Expressions;
-
-import com.google.common.collect.Maps;
-import com.google.inject.Inject;
-import com.google.inject.Singleton;
-import com.google.inject.name.Named;
-
-import org.apache.commons.lang.StringUtils;
 import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
@@ -47,6 +38,15 @@
 import java.util.Map;
 import java.util.logging.Logger;
 
+import javax.el.ELContext;
+import javax.el.ELException;
+import javax.el.ValueExpression;
+
+import com.google.common.collect.Maps;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import com.google.inject.name.Named;
+
 /**
  * Represents a container configuration using JSON notation.
  *
@@ -67,18 +67,27 @@
   public static final String CONTAINER_KEY = "gadgets.container";
 
   private final Map<String, Map<String, Object>> config;
+  private final Expressions expressions;
 
   /**
-   * Creates a new, empty configuration.
-   * @param containers
+   * Creates a new configuration from files.
    * @throws ContainerConfigException
    */
   @Inject
-  public JsonContainerConfig(@Named("shindig.containers.default") String containers)
+  public JsonContainerConfig(@Named("shindig.containers.default") String containers, Expressions expressions)
       throws ContainerConfigException {
+    this.expressions = expressions;
     config = createContainers(loadContainers(containers));
   }
 
+  /**
+   * Creates a new configuration from a JSON Object,Êfor use in testing.
+   */
+  public JsonContainerConfig(JSONObject json, Expressions expressions) {
+    this.expressions = expressions;
+    config = createContainers(json);
+  }
+
   @Override
   public Collection<String> getContainers() {
     return Collections.unmodifiableSet(config.keySet());
@@ -94,9 +103,9 @@
     if (property.startsWith("${")) {
       // An expression!
       try {
-        Expression<String> expression = Expressions.parse(property, String.class);
-        return expression.evaluate(createExpressionContext(container));
-      } catch (ElException e) {
+        ValueExpression expression = expressions.parse(property, Object.class);
+        return expression.getValue(createExpressionContext(container));
+      } catch (ELException e) {
         return null;
       }
     }
@@ -114,8 +123,8 @@
   private Map<String, Map<String, Object>> createContainers(JSONObject json) {
     Map<String, Map<String, Object>> map = Maps.newHashMap();
     for (String container : JSONObject.getNames(json)) {
-      ExpressionContext context = createExpressionContext(container);
-      map.put(container, jsonToMap(json.optJSONObject(container), context));
+      ELContext context = createExpressionContext(container);
+      map.put(container, jsonToMap(json.optJSONObject(container), expressions, context));
     }
 
     return map;
@@ -124,35 +133,35 @@
   /**
    * Protected to allow overriding.
    */
-  protected ExpressionContext createExpressionContext(String container) {
-    return new ContainerConfigExpressionContext(container, this);
+  protected ELContext createExpressionContext(String container) {
+    return expressions.newELContext(new ContainerConfigELResolver(this, container));
   }
 
   /**
    * Convert a JSON value to a configuration value.
    */
-  private static Object jsonToConfig(Object json, ExpressionContext context) {
+  private static Object jsonToConfig(Object json, Expressions expressions, ELContext context) {
     if (json instanceof CharSequence) {
-      return new DynamicConfigProperty(json.toString(), context);
+      return new DynamicConfigProperty(json.toString(), expressions, context);
     } else if (json instanceof JSONArray) {
       JSONArray jsonArray = (JSONArray) json;
       List<Object> values = new ArrayList<Object>(jsonArray.length());
       for (int i = 0, j = jsonArray.length(); i < j; ++i) {
-        values.add(jsonToConfig(jsonArray.opt(i), context));
+        values.add(jsonToConfig(jsonArray.opt(i), expressions, context));
       }
       return Collections.unmodifiableList(values);
     } else if (json instanceof JSONObject) {
-      return jsonToMap((JSONObject) json, context);
+      return jsonToMap((JSONObject) json, expressions, context);
     }
 
     // A (boxed) primitive.
     return json;
   }
 
-  private static Map<String, Object> jsonToMap(JSONObject json, ExpressionContext context) {
+  private static Map<String, Object> jsonToMap(JSONObject json, Expressions expressions, ELContext context) {
     Map<String, Object> values = new HashMap<String, Object>(json.length(), 1);
     for (String key : JSONObject.getNames(json)) {
-      values.put(key, jsonToConfig(json.opt(key), context));
+      values.put(key, jsonToConfig(json.opt(key), expressions, context));
     }
     return Collections.unmodifiableMap(values);
   }

Modified: incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/expressions/Expressions.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/expressions/Expressions.java?rev=741354&r1=741353&r2=741354&view=diff
==============================================================================
--- incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/expressions/Expressions.java (original)
+++ incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/expressions/Expressions.java Fri Feb  6 00:31:19 2009
@@ -18,265 +18,192 @@
  */
 package org.apache.shindig.expressions;
 
-import com.google.common.base.Preconditions;
-import com.google.common.collect.Lists;
-
 import org.json.JSONArray;
-import org.json.JSONObject;
 
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
+import java.util.Properties;
 import java.util.StringTokenizer;
 
+import javax.el.ArrayELResolver;
+import javax.el.CompositeELResolver;
+import javax.el.ELContext;
+import javax.el.ELException;
+import javax.el.ELResolver;
+import javax.el.ExpressionFactory;
+import javax.el.ListELResolver;
+import javax.el.MapELResolver;
+import javax.el.PropertyNotWritableException;
+import javax.el.ValueExpression;
+
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+import de.odysseus.el.ExpressionFactoryImpl;
+import de.odysseus.el.util.SimpleContext;
+
 /**
- * Implementation of the small subset of JSP EL that OpenSocial supports:
- * - String concatenation
- * - The "." operator
- * - Limited type coercion
+ * A facade to the expressions functionality.
  */
+@Singleton
 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 = '}';
+  private static final Expressions sharedInstance = new Expressions();
+  
+  private final ExpressionFactory factory;
+  private final ELContext parseContext;
+  private final ELResolver defaultELResolver;
 
   /**
-   * 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.
+   * Return a shared instance.
+   * TODO: inject Expressions into the gadget spec code and get rid of
+   * this singleton.
    */
-  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);
-    }
+  public static Expressions sharedInstance() {
+    return sharedInstance;
+  }
+  
+  @Inject
+  public Expressions() {
+    factory = newExpressionFactory();
+    // Stub context with no FunctionMapper, used only to parse expressions
+    parseContext = new SimpleContext();
+    defaultELResolver = createDefaultELResolver();
+  }
 
-    if (start < text.length()) {
-      String constantText = text.substring(start);
-      expressions.add(new ConstantExpression<String>(constantText));
+  /**
+   * Creates an ELContext.
+   * @param customResolvers resolvers to be added to the chain
+   */
+  public ELContext newELContext(ELResolver... customResolvers) {
+    CompositeELResolver composite = new CompositeELResolver();
+    for (ELResolver customResolver : customResolvers) {
+      composite.add(customResolver);
     }
 
-    return new ConcatExpression<T>(expressions, type);
+    composite.add(defaultELResolver);
+    return new SimpleContext(composite);
   }
 
   /**
-   * Parses an EL expression within a string.
-   * @throws ElException
+   * Parse a value expression.
+   * @param expression the string expression.  This may be a literal
+   *     without any expressions.
+   * @param type the desired coercion type.
+   * @return a ValueExpression corresponding to the expression
    */
-  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 List<String> segments = splitSegments(text.substring(from, to));
-    return new Expression<T>() {
-
-      public T evaluate(ExpressionContext context) throws ElException {
-        Object value = context.getVariable(segments.get(0));
-        for (int i = 1; i < segments.size(); i++) {
-          if (value == null) {
-            throw new ElException("Could not find property \"" + segments.get(i - 1) + "\" in \""
-                + text + '\"');
-          }
-          value = getProperty(value, segments.get(i));
-        }
-
-        return coerce(value, type);
-      }
-
-    };
+  public ValueExpression parse(String expression, Class<?> type) {
+    if (type == JSONArray.class) {
+      // TODO: the coming version of JUEL offers support for custom type converters.  Use it!
+      return new CustomCoerce(factory.createValueExpression(parseContext, expression, String.class),
+          type);
+    }
+    return factory.createValueExpression(parseContext, expression, type);
+  }
+  
+  private ExpressionFactory newExpressionFactory() {
+    Properties properties = new Properties();
+    // TODO: configure cache size?
+    return new ExpressionFactoryImpl(properties);
   }
+  
+  /**
+   * @return a default ELResolver with functionality needed by all
+   * expression evaluation.
+   */
+  private ELResolver createDefaultELResolver() {
+    CompositeELResolver resolver = new CompositeELResolver();
+    // Resolvers, in the order they will be most commonly accessed.
+    // Moving JsonELResolver to the end makes JSON property resolution twice
+    // as slow, so this is quite important.
+    resolver.add(new JsonELResolver());
+    resolver.add(new MapELResolver());
+    resolver.add(new ListELResolver());
+    resolver.add(new ArrayELResolver());
+    // TODO: bean el resolver?
+    
+    return resolver;
+  }
+
+  /** 
+   * Class providing custom type coercion for getValue() where needed.
+   * This will be obsolete with JUEL 2.1.1.
+   */
+  static private class CustomCoerce extends ValueExpression {
 
-  private static List<String> splitSegments(String input) {
-    List<String> segments = new ArrayList<String>();
-    StringBuilder buf = new StringBuilder(16);
-    for (int i = 0, j = input.length(); i < j; ++i) {
-      char ch = input.charAt(i);
-      if (ch == '\\' && i < j && input.charAt(i + 1) == '.') {
-        // Escaped dot.
-        buf.append('.');
-        ++i;
-      } else if (ch == '.') {
-        // end of identifier
-        segments.add(buf.toString());
-        buf.setLength(0);
-      } else {
-        buf.append(input.charAt(i));
-      }
-    }
-
-    segments.add(buf.toString());
+    private final ValueExpression base;
+    private final Class<?> type;
 
-    return segments;
-  }
-
-  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);
-    } else if (value instanceof ExpressionContext) {
-      return ((ExpressionContext) value).getVariable(propertyName);
+    public CustomCoerce(ValueExpression base, Class<?> type) {
+      this.base = base;
+      this.type = type;
     }
 
-    throw new ElException("Unsupported property parent type " + value.getClass());
-  }
+    @Override
+    public Class<?> getExpectedType() {
+      return type;
+    }
 
-  /**
-   * 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;
+    @Override
+    public Class<?> getType(ELContext context) {
+      return type;
     }
 
-    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);
-        }
+    @Override
+    public Object getValue(ELContext context) {
+      Object value = base.getValue(context);
+      if (value == null) {
+        return null;
       }
-
-      @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();
+      
+      if (type == JSONArray.class) {
+        JSONArray 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);
+        return array;
       } else {
-        // TODO: especially this case...
-        boolValue = !"false".equalsIgnoreCase(value.toString());
+        throw new ELException("Can't coerce to type " + type.getName());
       }
-
-      @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);
+    @Override
+    public boolean isReadOnly(ELContext context) {
+      return true;
     }
-  }
-
-  /** 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 */
-  public static class ConstantExpression<T> implements Expression<T> {
-    private final T value;
 
-    public ConstantExpression(T value) {
-      this.value = value;
+    @Override
+    public void setValue(ELContext context, Object value) {
+      throw new PropertyNotWritableException();
     }
 
-    public T evaluate(ExpressionContext context) {
-      return value;
+    @Override
+    public String getExpressionString() {
+      return base.getExpressionString();
     }
-  }
-
-  /** Expression class for string concatenation */
-  public 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;
+    @Override
+    public boolean isLiteralText() {
+      return base.isLiteralText();
     }
 
-    public T evaluate(ExpressionContext context) throws ElException {
-      StringBuilder builder = new StringBuilder();
-      for (Expression<String> expression : expressions) {
-        builder.append(expression.evaluate(context));
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) {
+        return true;
+      }
+      
+      if (!(o instanceof CustomCoerce)) {
+        return false;
       }
+      
+      CustomCoerce that = (CustomCoerce) o;
+      return that.base.equals(this.base) && that.type.equals(this.type);
+    }
 
-      return coerce(builder.toString(), type);
+    @Override
+    public int hashCode() {
+      return base.hashCode();
     }
+    
   }
 }

Added: incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/expressions/JsonELResolver.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/expressions/JsonELResolver.java?rev=741354&view=auto
==============================================================================
--- incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/expressions/JsonELResolver.java (added)
+++ incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/expressions/JsonELResolver.java Fri Feb  6 00:31:19 2009
@@ -0,0 +1,131 @@
+/*
+ * 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.expressions;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.beans.FeatureDescriptor;
+import java.util.Iterator;
+
+import javax.el.ELContext;
+import javax.el.ELException;
+import javax.el.ELResolver;
+
+/**
+ * ELResolver implementation for JSONArray and JSONObject.
+ */
+class JsonELResolver extends ELResolver {
+
+  @Override
+  public Class<?> getCommonPropertyType(ELContext context, Object base) {
+    if (base instanceof JSONArray) {
+      return Integer.class;
+    }
+    
+    if (base instanceof JSONObject) {
+      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) {
+    if (isJson(base)) {
+      context.setPropertyResolved(true);
+      Object value = getValue(context, base, property);
+      return value == null ? null : value.getClass();
+    }
+    
+    return null;
+  }
+
+  @Override
+  public Object getValue(ELContext context, Object base, Object property) {
+    if (base instanceof JSONObject) {
+      context.setPropertyResolved(true);
+      return ((JSONObject) base).opt(String.valueOf(property));
+    }
+
+    if (base instanceof JSONArray) {
+      context.setPropertyResolved(true);
+      int index = toInt(property);
+      return ((JSONArray) base).opt(index);
+    }
+    
+    return null;
+  }
+
+  @Override
+  public boolean isReadOnly(ELContext context, Object base, Object property) {
+    if (isJson(base)) {
+      context.setPropertyResolved(true);
+    }
+    
+    return false;
+  }
+
+  @Override
+  public void setValue(ELContext context, Object base, Object property, Object value) {
+    if (base instanceof JSONObject) {
+      context.setPropertyResolved(true);
+      try {
+        ((JSONObject) base).put(String.valueOf(property), value);
+      } catch (JSONException e) {
+        throw new ELException(e);
+      }
+      context.setPropertyResolved(true);
+    }
+
+    if (base instanceof JSONArray) {
+      context.setPropertyResolved(true);
+      int index = toInt(property);
+      try {
+        ((JSONArray) base).put(index, value);
+      } catch (JSONException e) {
+        throw new ELException(e);
+      }
+      context.setPropertyResolved(true);
+    }
+  }
+
+  private int toInt(Object property) {
+    if (property instanceof Number) {
+      return ((Number) property).intValue();
+    }
+    
+    try {
+      return Integer.parseInt(String.valueOf(property));
+    } catch (NumberFormatException nfe) {
+      throw new ELException(nfe);
+    }
+  }
+
+  private boolean isJson(Object base) {
+    return (base instanceof JSONObject || base instanceof JSONArray);
+  }
+}

Added: incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/expressions/RootELResolver.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/expressions/RootELResolver.java?rev=741354&view=auto
==============================================================================
--- incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/expressions/RootELResolver.java (added)
+++ incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/expressions/RootELResolver.java Fri Feb  6 00:31:19 2009
@@ -0,0 +1,104 @@
+/*
+ * 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.expressions;
+
+import java.beans.FeatureDescriptor;
+import java.util.Iterator;
+import java.util.Map;
+
+import javax.el.ELContext;
+import javax.el.ELResolver;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Maps;
+
+/**
+ * ELResolver implementation that adds a map of top-level variables.
+ * New variables can be inserted after creation with:
+ * {@code context.getELResolver().setValue(context, null, name, value);}
+ * 
+ * TODO: should this be read-only?
+ * 
+ * @see Expressions#newELContext(ELResolver...)
+ */
+public class RootELResolver extends ELResolver {
+  private final Map<String, Object> map;
+
+  public RootELResolver() {
+    this(ImmutableMap.<String, Object>of());
+  }
+  
+  public RootELResolver(Map<String, ? extends Object> base) {
+    // TODO: if read-only is OK, then a copy is unnecessary
+    map = Maps.newHashMap(base);
+  }
+  
+  @Override
+  public Class<?> getCommonPropertyType(ELContext context, Object base) {
+    if (base == null) {
+      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) {
+    if (base == null) {
+      context.setPropertyResolved(true);
+      Object value = map.get(property);
+      return value == null ? null : value.getClass();
+    }
+    
+    return null;
+  }
+
+  @Override
+  public Object getValue(ELContext context, Object base, Object property) {
+    if (base == null) {
+      context.setPropertyResolved(true);
+      return map.get(property);
+    }
+    
+    return null;
+  }
+
+  @Override
+  public boolean isReadOnly(ELContext context, Object base, Object property) {
+    if (base == null) {
+      context.setPropertyResolved(true);
+    }
+    
+    return false;
+  }
+
+  @Override
+  public void setValue(ELContext context, Object base, Object property, Object value) {
+    if (base == null) {
+      context.setPropertyResolved(true);
+      map.put(property.toString(), value);
+    }
+  }
+}

Modified: incubator/shindig/trunk/java/common/src/test/java/org/apache/shindig/config/JsonContainerConfigTest.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/common/src/test/java/org/apache/shindig/config/JsonContainerConfigTest.java?rev=741354&r1=741353&r2=741354&view=diff
==============================================================================
--- incubator/shindig/trunk/java/common/src/test/java/org/apache/shindig/config/JsonContainerConfigTest.java (original)
+++ incubator/shindig/trunk/java/common/src/test/java/org/apache/shindig/config/JsonContainerConfigTest.java Fri Feb  6 00:31:19 2009
@@ -25,6 +25,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
 
+import org.apache.shindig.expressions.Expressions;
 import org.json.JSONObject;
 import org.junit.Test;
 
@@ -53,7 +54,7 @@
   private static final String ARRAY_NAME = "array value";
   private static final String[] ARRAY_VALUE = new String[]{"Hello", "World"};
   private static final String ARRAY_ALT_VALUE = "Not an array";
-
+  
   private File createContainer(JSONObject json) throws Exception {
     File file = File.createTempFile(getClass().getName(), ".json");
     file.deleteOnExit();
@@ -81,7 +82,8 @@
 
   @Test
   public void parseBasicConfig() throws Exception {
-    ContainerConfig config = new JsonContainerConfig(createDefaultContainer().getAbsolutePath());
+    ContainerConfig config = new JsonContainerConfig(createDefaultContainer().getAbsolutePath(),
+        new Expressions());
 
     assertEquals(1, config.getContainers().size());
     for (String container : config.getContainers()) {
@@ -106,7 +108,7 @@
     File childFile = createContainer(json);
 
     ContainerConfig config = new JsonContainerConfig(childFile.getAbsolutePath() +
-        JsonContainerConfig.FILE_SEPARATOR + parentFile.getAbsolutePath());
+        JsonContainerConfig.FILE_SEPARATOR + parentFile.getAbsolutePath(), new Expressions());
 
     assertEquals(NESTED_VALUE, config.getString(CONTAINER_A, NESTED_KEY));
     assertEquals(NESTED_VALUE, config.getString(CONTAINER_B, NESTED_KEY));
@@ -128,7 +130,7 @@
     File childFile = createContainer(json);
     File parentFile = createDefaultContainer();
     ContainerConfig config = new JsonContainerConfig(childFile.getAbsolutePath() +
-        JsonContainerConfig.FILE_SEPARATOR + parentFile.getAbsolutePath());
+        JsonContainerConfig.FILE_SEPARATOR + parentFile.getAbsolutePath(), new Expressions());
 
     String value = config.getString(CHILD_CONTAINER, TOP_LEVEL_NAME);
     assertEquals(TOP_LEVEL_VALUE, value);
@@ -154,7 +156,8 @@
 
   @Test
   public void invalidContainerReturnsNull() throws Exception {
-    ContainerConfig config = new JsonContainerConfig(createDefaultContainer().getAbsolutePath());
+    ContainerConfig config = new JsonContainerConfig(createDefaultContainer().getAbsolutePath(),
+        new Expressions());
     assertNull("Did not return null for invalid container.", config.getString("fake", PARENT_KEY));
   }
 
@@ -165,13 +168,13 @@
     json.put(PARENT_KEY, "bad bad bad parent!");
     json.put(ARRAY_NAME, ARRAY_ALT_VALUE);
 
-    new JsonContainerConfig(createContainer(json).getAbsolutePath());
+    new JsonContainerConfig(createContainer(json).getAbsolutePath(), new Expressions());
   }
 
   @Test
   public void pathQuery() throws Exception {
-    ContainerConfig config = new JsonContainerConfig(createDefaultContainer().getAbsolutePath());
-    String path = "${" + NESTED_KEY + '.' + NESTED_NAME + '}';
+    ContainerConfig config = new JsonContainerConfig(createDefaultContainer().getAbsolutePath(), new Expressions());
+    String path = "${" + NESTED_KEY + "['" + NESTED_NAME + "']}";
     String data = config.getString(DEFAULT_CONTAINER, path);
     assertEquals(NESTED_VALUE, data);
   }
@@ -184,8 +187,24 @@
     json.put("expression", "Hello, ${world}!");
     json.put("world", "Earth");
 
-    ContainerConfig config = new JsonContainerConfig(createContainer(json).getAbsolutePath());
+    ContainerConfig config = new JsonContainerConfig(createContainer(json).getAbsolutePath(), new Expressions());
 
     assertEquals("Hello, Earth!", config.getString(DEFAULT_CONTAINER, "expression"));
   }
+
+  @Test
+  public void expressionEvaluationUsingParent() throws Exception {
+    // We use a JSON Object here to guarantee that we're well formed up front.
+    JSONObject json = new JSONObject();
+    json.put(CONTAINER_KEY, new String[]{CHILD_CONTAINER});
+    json.put(PARENT_KEY, DEFAULT_CONTAINER);
+    json.put("parentExpression", "${parent['" + TOP_LEVEL_NAME + "']}");
+
+    File childFile = createContainer(json);
+    File parentFile = createDefaultContainer();
+    ContainerConfig config = new JsonContainerConfig(childFile.getAbsolutePath() +
+        JsonContainerConfig.FILE_SEPARATOR + parentFile.getAbsolutePath(), new Expressions());
+
+    assertEquals(TOP_LEVEL_VALUE, config.getString(CHILD_CONTAINER, "parentExpression"));
+  }
 }

Modified: incubator/shindig/trunk/java/common/src/test/java/org/apache/shindig/expressions/ExpressionsTest.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/common/src/test/java/org/apache/shindig/expressions/ExpressionsTest.java?rev=741354&r1=741353&r2=741354&view=diff
==============================================================================
--- incubator/shindig/trunk/java/common/src/test/java/org/apache/shindig/expressions/ExpressionsTest.java (original)
+++ incubator/shindig/trunk/java/common/src/test/java/org/apache/shindig/expressions/ExpressionsTest.java Fri Feb  6 00:31:19 2009
@@ -19,136 +19,84 @@
 package org.apache.shindig.expressions;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
-
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Maps;
 
+import org.json.JSONArray;
 import org.json.JSONObject;
 import org.junit.Before;
 import org.junit.Test;
 
-import java.util.Map;
+import javax.el.ELContext;
+import javax.el.ValueExpression;
 
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
 
-/**
- * Tests of the Expressions class.
- */
 public class ExpressionsTest {
-  private FakeContext context;
-
+  private Expressions expressions;
+  private ELContext context;
+  
   @Before
   public void setUp() {
-    context = new FakeContext();
+    expressions = new Expressions();
+    context = expressions.newELContext(new RootELResolver());
   }
-
+    
   @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());
+  public void arraySupport() {
+    addVariable("array", new String[]{"foo", "bar"});
+    String result = evaluate("${array[0]}${array[1]}", String.class);
+    assertEquals("foobar", result);
   }
-
+  
   @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());
+  public void listSupport() {
+    addVariable("list", ImmutableList.of("foo", "bar"));
+    String result = evaluate("${list[0]}${list[1]}", String.class);
+    assertEquals("foobar", result);
   }
-
+  
   @Test
-  public void variableNotFoundIsNull() throws Exception {
-    Expression<String> var = Expressions.parse("${var}", String.class);
-    assertNull(var.evaluate(context));
+  public void mapSupport() {
+    addVariable("map", ImmutableMap.of("foo", "bar"));
+    String result = evaluate("${map.foo}${map['foo']}", String.class);
+    assertEquals("barbar", result);
   }
 
   @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());
+  public void jsonObjectSupport() throws Exception {
+    addVariable("object", new JSONObject("{foo: 125}"));
+    int result = evaluate("${object.foo}", Integer.class);
+    assertEquals(125, result);
   }
 
   @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));
+  public void jsonArraySupport() throws Exception {
+    addVariable("array", new JSONArray("[1, 2]"));
+    int result = evaluate("${array[0] + array[1]}", Integer.class);
+    assertEquals(3, result);
   }
 
   @Test
-  public void propertyEvaluationForExpressionContext() throws Exception {
-    context.variables.put("var", new ExpressionContext() {
-      public Object getVariable(String name) {
-        return name.equals("top") ? "value" : null;
-      }
-    });
-
-    Expression<String> var = Expressions.parse("${var.top}", String.class);
-    assertEquals("value", var.evaluate(context));
+  public void jsonArrayCoercionOfStatic() throws Exception {
+    JSONArray result = evaluate("first,second", JSONArray.class);
+    JSONArray expected = new JSONArray("['first', 'second']");
+    assertEquals(expected.toString(), result.toString());
   }
-
+  
   @Test
-  public void propertyEvaluationWithEscapedDot() throws Exception {
-    context.variables.put("foo.bar", "baz");
-    Expression<String> var = Expressions.parse("${foo\\.bar}", String.class);
-    assertEquals("baz", var.evaluate(context));
+  public void jsonArrayCoercion() throws Exception {
+    addVariable("foo", "first,second");
+    JSONArray result = evaluate("${foo}", JSONArray.class);
+    JSONArray expected = new JSONArray("['first', 'second']");
+    assertEquals(expected.toString(), result.toString());
   }
-
-  @Test(expected = ElException.class)
-  public void propertyEvaluationWithLeadingDot() throws Exception {
-    context.variables.put("foo", "bar");
-    Expression<String> var = Expressions.parse("${.foo}", String.class);
-    var.evaluate(context);
+  
+  private <T> T evaluate(String expression, Class<T> type) {
+    ValueExpression expr = expressions.parse(expression, type);
+    return type.cast(expr.getValue(context));
   }
 
-  @Test(expected = ElException.class)
-  public void propertyEvaluationWithTrailingDot() throws Exception {
-    context.variables.put("foo", "bar");
-    Expression<String> var = Expressions.parse("${foo.}", String.class);
-    var.evaluate(context);
+  private void addVariable(String key, Object value) {
+    context.getELResolver().setValue(context, null, key, value);
   }
-
-  @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/main/java/org/apache/shindig/gadgets/GadgetELResolver.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/GadgetELResolver.java?rev=741354&view=auto
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/GadgetELResolver.java (added)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/GadgetELResolver.java Fri Feb  6 00:31:19 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;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.beans.FeatureDescriptor;
+import java.util.Iterator;
+import java.util.Map;
+
+import javax.el.ELContext;
+import javax.el.ELException;
+import javax.el.ELResolver;
+import javax.el.PropertyNotWritableException;
+
+import com.google.common.collect.ImmutableMap;
+
+/**
+ * ELResolver for the built-in gadget properties:
+ * - UserPrefs: the user preferences
+ * - ViewParams: view params (as a JSON object)
+ */
+public class GadgetELResolver extends ELResolver {
+  public static final String USER_PREFS_PROPERTY = "UserPrefs";
+  public static final String VIEW_PARAMS_PROPERTY = "ViewParams";
+  
+  private final GadgetContext gadgetContext;
+
+  public GadgetELResolver(GadgetContext context) {
+    this.gadgetContext = context;
+  }
+
+  @Override
+  public Class<?> getCommonPropertyType(ELContext context, Object base) {
+    if (base == null) {
+      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) {
+    if (base == null) {
+      if (VIEW_PARAMS_PROPERTY.equals(property)) {
+        context.setPropertyResolved(true);
+        return Object.class;
+      } else if (USER_PREFS_PROPERTY.equals(property)) {
+        context.setPropertyResolved(true);
+        return Map.class;
+      }
+    }
+    
+    return null;
+  }
+
+  @Override
+  public Object getValue(ELContext context, Object base, Object property) {
+    if (base == null) {
+      if (VIEW_PARAMS_PROPERTY.equals(property)) {
+        context.setPropertyResolved(true);
+        String params = gadgetContext.getParameter("view-params");
+        if (params != null && !"".equals(params)) {
+          try {
+            // TODO: immutable?
+            return new JSONObject(params);
+          } catch (JSONException e) {
+            throw new ELException(e);
+          }
+        }
+
+        // Return an empty map - this doesn't allocate anything, whereas an
+        // empty JSONObject would
+        return ImmutableMap.of();
+      } else if (USER_PREFS_PROPERTY.equals(property)) {
+        context.setPropertyResolved(true);
+        // TODO: immutable?
+        return gadgetContext.getUserPrefs().getPrefs();
+      }
+    }
+    
+    return null;
+  }
+
+  @Override
+  public boolean isReadOnly(ELContext context, Object base, Object property) {
+    if ((base == null) &&
+        (VIEW_PARAMS_PROPERTY.equals(property)
+        || USER_PREFS_PROPERTY.equals(property))) { 
+      context.setPropertyResolved(true);
+    }
+    
+    return true;
+  }
+
+  @Override
+  public void setValue(ELContext context, Object base, Object property, Object value) {
+    if ((base == null) &&
+        (VIEW_PARAMS_PROPERTY.equals(property)
+        || USER_PREFS_PROPERTY.equals(property))) { 
+      throw new PropertyNotWritableException("Cannot override " + property);
+    }
+  }
+}

Modified: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/process/Processor.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/process/Processor.java?rev=741354&r1=741353&r2=741354&view=diff
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/process/Processor.java (original)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/process/Processor.java Fri Feb  6 00:31:19 2009
@@ -98,7 +98,7 @@
     View view = spec.getView(viewName);
     if (view == null) {
       String container = context.getContainer();
-      String property = "${gadgets\\.features.views." + viewName + ".aliases}";
+      String property = "${Cur['gadgets.features'].views['" + viewName + "'].aliases}";
       for (Object alias : containerConfig.getList(container, property)) {
         viewName = alias.toString();
         view = spec.getView(viewName);

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=741354&r1=741353&r2=741354&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 Fri Feb  6 00:31:19 2009
@@ -17,23 +17,23 @@
  */
 package org.apache.shindig.gadgets.spec;
 
-import org.apache.shindig.common.uri.Uri;
-import org.apache.shindig.common.xml.XmlException;
-import org.apache.shindig.expressions.ElException;
-import org.apache.shindig.expressions.Expression;
-import org.apache.shindig.expressions.ExpressionContext;
-import org.apache.shindig.expressions.Expressions;
-import org.apache.shindig.gadgets.AuthType;
-import org.apache.shindig.gadgets.GadgetContext;
-import org.apache.shindig.gadgets.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 javax.el.ELContext;
+import javax.el.ELException;
+import javax.el.ValueExpression;
+
+import org.apache.shindig.common.uri.Uri;
+import org.apache.shindig.common.xml.XmlException;
+import org.apache.shindig.expressions.Expressions;
+import org.apache.shindig.gadgets.AuthType;
+import org.apache.shindig.gadgets.GadgetContext;
+import org.apache.shindig.gadgets.GadgetELResolver;
+import org.apache.shindig.gadgets.variables.Substitutions;
 import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
@@ -99,7 +99,7 @@
           // notImplemented
           throw new SpecParserException("Unknown element <os:" + elementName + '>');
         }
-      } catch (ElException ele) {
+      } catch (ELException ele) {
         throw new SpecParserException(new XmlException(ele));
       }
     }
@@ -135,11 +135,12 @@
   public Map<String, Object> getSocialPreloads(GadgetContext context) {
     Map<String, Object> evaluatedPreloads = Maps.newHashMapWithExpectedSize(
         socialPreloads.size());
-    ExpressionContext expressionContext = new GadgetExpressionContext(context);
+    Expressions expressions = Expressions.sharedInstance();
+    ELContext elContext = expressions.newELContext(new GadgetELResolver(context));
     for (Map.Entry<String, SocialData> preload : socialPreloads.entrySet()) {
       try {
-        evaluatedPreloads.put(preload.getKey(), preload.getValue().toJson(expressionContext));
-      } catch (ElException e) {
+        evaluatedPreloads.put(preload.getKey(), preload.getValue().toJson(elContext));
+      } catch (ELException e) {
         // TODO: Handle!?!
         throw new RuntimeException(e);
       }
@@ -151,11 +152,12 @@
   public Map<String, RequestAuthenticationInfo> getHttpPreloads(GadgetContext context) {
     Map<String, RequestAuthenticationInfo> evaluatedPreloads = Maps.newHashMapWithExpectedSize(
         httpPreloads.size());
-    ExpressionContext expressionContext = new GadgetExpressionContext(context);
+    Expressions expressions = Expressions.sharedInstance();
+    ELContext elContext = expressions.newELContext(new GadgetELResolver(context));
     for (Map.Entry<String, HttpData> preload : httpPreloads.entrySet()) {
       try {
-        evaluatedPreloads.put(preload.getKey(), preload.getValue().evaluate(expressionContext));
-      } catch (ElException e) {
+        evaluatedPreloads.put(preload.getKey(), preload.getValue().evaluate(elContext));
+      } catch (ELException e) {
         // TODO: Handle!?!
         throw new RuntimeException(e);
       }
@@ -173,7 +175,7 @@
   }
 
   /** Handle the os:PeopleRequest element */
-  private SocialData createPeopleRequest(Element child) throws ElException {
+  private SocialData createPeopleRequest(Element child) throws ELException {
     SocialData expression = new SocialData(child.getAttribute("key"), "people.get");
 
     copyAttribute("groupId", child, expression, String.class);
@@ -195,16 +197,16 @@
   }
 
   /** Handle the os:ViewerRequest element */
-  private SocialData createViewerRequest(Element child) throws ElException {
+  private SocialData createViewerRequest(Element child) throws ELException {
     return createPersonRequest(child, "@viewer");
   }
 
   /** Handle the os:OwnerRequest element */
-  private SocialData createOwnerRequest(Element child) throws ElException {
+  private SocialData createOwnerRequest(Element child) throws ELException {
     return createPersonRequest(child, "@owner");
   }
 
-  private SocialData createPersonRequest(Element child, String userId) throws ElException {
+  private SocialData createPersonRequest(Element child, String userId) throws ELException {
     SocialData expression = new SocialData(child.getAttribute("key"), "people.get");
 
     expression.addProperty("userId", userId, JSONArray.class);
@@ -215,7 +217,7 @@
   }
 
   /** Handle the os:PersonAppDataRequest element */
-  private SocialData createPersonAppDataRequest(Element child) throws ElException {
+  private SocialData createPersonAppDataRequest(Element child) throws ELException {
     SocialData expression = new SocialData(child.getAttribute("key"), "appdata.get");
 
     copyAttribute("groupId", child, expression, String.class);
@@ -228,7 +230,7 @@
   }
 
   /** Handle the os:ActivitiesRequest element */
-  private SocialData createActivityRequest(Element child) throws ElException {
+  private SocialData createActivityRequest(Element child) throws ELException {
     SocialData expression = new SocialData(child.getAttribute("key"), "activities.get");
 
     copyAttribute("groupId", child, expression, String.class);
@@ -243,7 +245,7 @@
   }
 
   /** Handle an os:MakeRequest element */
-  private HttpData createMakeRequest(Element child, Uri base) throws ElException {
+  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
@@ -262,7 +264,7 @@
   }
 
   private void copyAttribute(String name, Element element, SocialData expression, Class<?> type)
-    throws ElException {
+    throws ELException {
     if (element.hasAttribute(name)) {
       expression.addProperty(name, element.getAttribute(name), type);
     }
@@ -292,12 +294,12 @@
    * A single pipelined HTTP makerequest.
    */
   private static class HttpData {
-    private final Expression<String> authz;
+    private final 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 final boolean signOwner;
+    private final boolean signViewer;
+    private final Map<String, ValueExpression> attributes;
 
     private static final Set<String> KNOWN_ATTRIBUTES =
           ImmutableSet.of("authz", "href", "sign_owner", "sign_viewer");
@@ -305,25 +307,27 @@
     /**
      * Create an HttpData off an <os:makeRequest> element.
      */
-    public HttpData(Element element, Uri base) throws ElException {
+    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);
+      this.authz = element.hasAttribute("authz") ? element.getAttribute("authz") : "none";
 
       // 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();
+      // TODO: Spec question;  should sign_* default to true?
+      this.signOwner = booleanValue(element, "sign_owner", true);
+      this.signViewer = booleanValue(element, "sign_viewer", true);
+
+      Expressions expressions = Expressions.sharedInstance();
+      
+      // TODO: many of these attributes should not be EL enabled
+      Map<String, ValueExpression> 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));
+          attributes.put(attr.getNodeName(),
+              expressions.parse(attr.getNodeValue(), String.class));
         }
       }
 
@@ -346,20 +350,19 @@
 
     /**
      * Evaluate expressions and return a RequestAuthenticationInfo.
-     * @throws ElException if expression evaluation fails.
+     * @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);
+    public RequestAuthenticationInfo evaluate(ELContext context) throws ELException {
+      final AuthType authType = AuthType.parse(authz);
+      
+      Expressions expressions = Expressions.sharedInstance();
+      String hrefString = String.valueOf(expressions.parse(href, String.class)
+          .getValue(context));
+      final Uri evaluatedHref = base.resolve(Uri.parse(hrefString));
+
       final Map<String, String> evaluatedAttributes = Maps.newHashMap();
-      for (Map.Entry<String, Expression<String>> attr : attributes.entrySet()) {
-        evaluatedAttributes.put(attr.getKey(), attr.getValue().evaluate(context));
+      for (Map.Entry<String, ValueExpression> attr : attributes.entrySet()) {
+        evaluatedAttributes.put(attr.getKey(), (String) attr.getValue().getValue(context));
       }
 
       return new RequestAuthenticationInfo() {
@@ -376,38 +379,23 @@
         }
 
         public boolean isSignOwner() {
-          return evaluatedSignOwner;
+          return signOwner;
         }
 
         public boolean isSignViewer() {
-          return evaluatedSignViewer;
+          return signViewer;
         }
       };
     }
 
-    /** 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 {
+    private boolean booleanValue(Element element, String attrName,
+        boolean defaultValue) {
       if (!element.hasAttribute(attrName)) {
-        return null;
+        return defaultValue;
       }
 
-      return Expressions.parse(element.getAttribute(attrName), Boolean.class);
+      return "true".equalsIgnoreCase(element.getAttribute(attrName));
     }
   }
 
@@ -424,13 +412,13 @@
       this.method = method;
     }
 
-    public void addProperty(String name, String value, Class<?> type) throws ElException {
-      Expression<?> expression = Expressions.parse(value, type);
+    public void addProperty(String name, String value, Class<?> type) throws ELException {
+      ValueExpression expression = Expressions.sharedInstance().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 {
+    public JSONObject toJson(ELContext elContext) throws ELException {
       JSONObject object = new JSONObject();
       try {
         object.put("method", method);
@@ -438,11 +426,11 @@
 
         JSONObject params = new JSONObject();
         for (Property property : properties) {
-          property.set(context, params);
+          property.set(elContext, params);
         }
         object.put("params", params);
       } catch (JSONException je) {
-        throw new ElException(je);
+        throw new ELException(je);
       }
 
       return object;
@@ -450,22 +438,22 @@
 
     /** Single property for an expression */
     private static class Property {
-      private final Expression<?> expression;
+      private final ValueExpression expression;
       private final String name;
 
-      public Property(String name, Expression<?> expression) {
+      public Property(String name, ValueExpression expression) {
         this.name = name;
         this.expression = expression;
       }
 
-      public void set(ExpressionContext context, JSONObject object) throws ElException {
-        Object value = expression.evaluate(context);
+      public void set(ELContext elContext, JSONObject object) throws ELException {
+        Object value = expression.getValue(elContext);
         try {
           if (value != null) {
             object.put(name, value);
           }
         } catch (JSONException e) {
-          throw new ElException("Error parsing property \"" + name + '\"', e);
+          throw new ELException("Error parsing property \"" + name + '\"', e);
         }
       }
     }

Added: incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/GadgetELResolverTest.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/GadgetELResolverTest.java?rev=741354&view=auto
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/GadgetELResolverTest.java (added)
+++ incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/GadgetELResolverTest.java Fri Feb  6 00:31:19 2009
@@ -0,0 +1,100 @@
+/*
+ * 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;
+
+import static org.junit.Assert.assertEquals;
+
+import org.apache.shindig.expressions.Expressions;
+import org.junit.Before;
+import org.junit.Test;
+
+import javax.el.ELContext;
+import javax.el.ELResolver;
+import javax.el.ValueExpression;
+
+import com.google.common.collect.ImmutableMap;
+
+/**
+ * Test of GadgetELResolver.
+ */
+public class GadgetELResolverTest {
+  private UserPrefs userPrefs;
+  private String viewParams;
+  private ELResolver resolver;
+  private Expressions expressions;
+  private ELContext 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;
+      }
+    };
+    
+    resolver = new GadgetELResolver(gadgetContext);
+    expressions = new Expressions();
+    
+    context = expressions.newELContext(resolver);
+  }
+
+  @Test
+  public void getPrefs() {
+    userPrefs = new UserPrefs(ImmutableMap.of("foo", "bar"));
+    ValueExpression expression = expressions.parse("${UserPrefs.foo}", String.class);
+    
+    assertEquals("bar", expression.getValue(context));
+
+    expression = expressions.parse("${UserPrefs.wrongKey}", String.class);
+    assertEquals("", expression.getValue(context));
+  }
+
+  @Test
+  public void getPrefsEmpty() {
+    userPrefs = UserPrefs.EMPTY;
+    ValueExpression expression = expressions.parse("${UserPrefs.foo}", String.class);
+    assertEquals("", expression.getValue(context));
+  }
+
+  @Test
+  public void testViewParams() {
+    viewParams = "{foo: 'bar'}";
+    
+    ValueExpression expression = expressions.parse("${ViewParams.foo}", String.class);
+    assertEquals("bar", expression.getValue(context));
+
+    expression = expressions.parse("${ViewParams.wrongKey}", String.class);
+    assertEquals("", expression.getValue(context));
+  }
+
+  @Test
+  public void testViewParamsEmpty() {
+    ValueExpression expression = expressions.parse("${ViewParams.foo}", String.class);
+    assertEquals("", expression.getValue(context));
+  }
+}

Modified: incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/process/ProcessorTest.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/process/ProcessorTest.java?rev=741354&r1=741353&r2=741354&view=diff
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/process/ProcessorTest.java (original)
+++ incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/process/ProcessorTest.java Fri Feb  6 00:31:19 2009
@@ -22,7 +22,9 @@
 import static org.junit.Assert.assertTrue;
 
 import org.apache.shindig.common.uri.Uri;
-import org.apache.shindig.config.AbstractContainerConfig;
+import org.apache.shindig.config.ContainerConfig;
+import org.apache.shindig.config.JsonContainerConfig;
+import org.apache.shindig.expressions.Expressions;
 import org.apache.shindig.gadgets.Gadget;
 import org.apache.shindig.gadgets.GadgetBlacklist;
 import org.apache.shindig.gadgets.GadgetContext;
@@ -30,17 +32,11 @@
 import org.apache.shindig.gadgets.GadgetSpecFactory;
 import org.apache.shindig.gadgets.spec.GadgetSpec;
 import org.apache.shindig.gadgets.variables.VariableSubstituter;
-
-import com.google.common.collect.Maps;
-
+import org.json.JSONObject;
 import org.junit.Before;
 import org.junit.Test;
 
 import java.net.URI;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
 
 public class ProcessorTest {
   private static final Uri SPEC_URL = Uri.parse("http://example.org/gadget.xml");
@@ -58,12 +54,17 @@
   private final FakeVariableSubstituter substituter = new FakeVariableSubstituter();
   private final FakeBlacklist blacklist = new FakeBlacklist();
 
-  private FakeContainerConfig containerConfig;
+  private ContainerConfig containerConfig;
   private Processor processor;
 
   @Before
   public void setUp() throws Exception {
-    containerConfig = new FakeContainerConfig();
+    JSONObject config = new JSONObject("{"  + ContainerConfig.DEFAULT_CONTAINER + ":" +
+        "{'gadgets.features':{views:" +
+           "{aliased: {aliases: ['some-alias', 'alias']}}" +
+         "}}}");
+    
+    containerConfig = new JsonContainerConfig(config, new Expressions());
     processor = new Processor(gadgetSpecFactory, substituter, containerConfig, blacklist);
   }
 
@@ -102,8 +103,6 @@
 
   @Test
   public void doViewAliasing() throws Exception {
-    List<Object> aliases = Arrays.<Object>asList("some-alias", "alias");
-    containerConfig.data.put("${gadgets\\.features.views.aliased.aliases}", aliases);
     Gadget gadget = processor.process(makeContext("aliased"));
     assertEquals(BASIC_HTML_CONTENT, gadget.getCurrentView().getContent());
   }
@@ -161,25 +160,6 @@
     }
   }
 
-  private static class FakeContainerConfig extends AbstractContainerConfig {
-    protected final Map<String, Object> data = Maps.newHashMap();
-
-    @Override
-    public Object getProperty(String container, String parameter) {
-      return data.get(parameter);
-    }
-
-    @Override
-    public Collection<String> getContainers() {
-      return null;
-    }
-
-    @Override
-    public Map<String, Object> getProperties(String container) {
-      return null;
-    }
-  }
-
   private static class FakeGadgetSpecFactory implements GadgetSpecFactory {
     protected GadgetException exception;
 

Modified: incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/ActivityHandler.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/ActivityHandler.java?rev=741354&r1=741353&r2=741354&view=diff
==============================================================================
--- incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/ActivityHandler.java (original)
+++ incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/ActivityHandler.java Fri Feb  6 00:31:19 2009
@@ -24,21 +24,18 @@
 import org.apache.shindig.social.opensocial.spi.SocialSpiException;
 import org.apache.shindig.social.opensocial.spi.UserId;
 
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Future;
+
 import com.google.common.base.Objects;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
 import com.google.inject.Inject;
 
-import java.util.List;
-import java.util.Set;
-import java.util.concurrent.Future;
-import java.util.logging.Logger;
-
 @Service(name = "activities", path="/{userId}+/{groupId}/{appId}/{activityId}+")
 public class ActivityHandler  {
 
-  private static final Logger logger = Logger.getLogger(ActivityHandler.class.getName());
-
   private final ActivityService service;
   private final ContainerConfig config;
 
@@ -139,11 +136,11 @@
   }
 
   @Operation(httpMethods = "GET", path="/@supportedFields")
-  public List supportedFields(RequestItem request) {
+  public List<Object> supportedFields(RequestItem request) {
     // TODO: Would be nice if name in config matched name of service.
     String container = Objects.firstNonNull(request.getToken().getContainer(),
         ContainerConfig.DEFAULT_CONTAINER);
     return config.getList(container,
-        "${gadgets\\.features.opensocial-0\\.8.supportedFields.activity}");
+        "${Cur['gadgets.features']['opensocial-0.8'].supportedFields.activity}");
   }
 }

Modified: incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/PersonHandler.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/PersonHandler.java?rev=741354&r1=741353&r2=741354&view=diff
==============================================================================
--- incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/PersonHandler.java (original)
+++ incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/PersonHandler.java Fri Feb  6 00:31:19 2009
@@ -25,19 +25,17 @@
 import org.apache.shindig.social.opensocial.spi.SocialSpiException;
 import org.apache.shindig.social.opensocial.spi.UserId;
 
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Future;
+
 import com.google.common.base.Objects;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
 import com.google.inject.Inject;
 
-import java.util.List;
-import java.util.Set;
-import java.util.concurrent.Future;
-import java.util.logging.Logger;
-
 @Service(name = "people", path = "/{userId}+/{groupId}/{personId}+")
 public class PersonHandler {
-  private final static Logger logger = Logger.getLogger(PersonHandler.class.getName());
   private final PersonService personService;
   private final ContainerConfig config;
 
@@ -95,10 +93,10 @@
   }
 
   @Operation(httpMethods = "GET", path="/@supportedFields")
-  public List supportedFields(RequestItem request) {
+  public List<Object> supportedFields(RequestItem request) {
     // TODO: Would be nice if name in config matched name of service.
     String container = Objects.firstNonNull(request.getToken().getContainer(), "default");
     return config.getList(container,
-        "${gadgets\\.features.opensocial-0\\.8.supportedFields.person}");
+        "${Cur['gadgets.features']['opensocial-0.8'].supportedFields.person}");
   }
 }

Modified: incubator/shindig/trunk/java/social-api/src/test/java/org/apache/shindig/social/opensocial/service/ActivityHandlerTest.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/social-api/src/test/java/org/apache/shindig/social/opensocial/service/ActivityHandlerTest.java?rev=741354&r1=741353&r2=741354&view=diff
==============================================================================
--- incubator/shindig/trunk/java/social-api/src/test/java/org/apache/shindig/social/opensocial/service/ActivityHandlerTest.java (original)
+++ incubator/shindig/trunk/java/social-api/src/test/java/org/apache/shindig/social/opensocial/service/ActivityHandlerTest.java Fri Feb  6 00:31:19 2009
@@ -17,10 +17,15 @@
  */
 package org.apache.shindig.social.opensocial.service;
 
+import static org.easymock.EasyMock.eq;
+import static org.easymock.EasyMock.isNull;
+
 import org.apache.shindig.common.EasyMockTestCase;
 import org.apache.shindig.common.testing.FakeGadgetToken;
 import org.apache.shindig.common.util.ImmediateFuture;
 import org.apache.shindig.config.ContainerConfig;
+import org.apache.shindig.config.JsonContainerConfig;
+import org.apache.shindig.expressions.Expressions;
 import org.apache.shindig.social.core.model.ActivityImpl;
 import org.apache.shindig.social.core.util.BeanJsonConverter;
 import org.apache.shindig.social.opensocial.model.Activity;
@@ -30,20 +35,18 @@
 import org.apache.shindig.social.opensocial.spi.RestfulCollection;
 import org.apache.shindig.social.opensocial.spi.SocialSpiException;
 import org.apache.shindig.social.opensocial.spi.UserId;
-
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-import com.google.common.collect.Sets;
-import static org.easymock.EasyMock.expect;
-import static org.easymock.classextension.EasyMock.eq;
-import static org.easymock.classextension.EasyMock.isNull;
+import org.json.JSONObject;
 
 import java.io.StringReader;
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.Future;
 
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+
 public class ActivityHandlerTest extends EasyMockTestCase {
 
   private BeanJsonConverter converter;
@@ -68,7 +71,13 @@
 
     converter = mock(BeanJsonConverter.class);
     activityService = mock(ActivityService.class);
-    containerConfig = mock(ContainerConfig.class);
+
+    JSONObject config = new JSONObject("{"  + ContainerConfig.DEFAULT_CONTAINER + ":" +
+            "{'gadgets.features':{'opensocial-0.8':" +
+               "{supportedFields: {activity: ['id', 'title']}}" +
+             "}}}");
+
+    containerConfig = new JsonContainerConfig(config, new Expressions());
     handler = new ActivityHandler(activityService, containerConfig);
     registry = new DefaultHandlerRegistry(null, Lists.newArrayList(handler));
   }
@@ -140,7 +149,7 @@
     reset();
   }
 
-  private Future setupBodyRequest(String method) throws SocialSpiException {
+  private Future<?> setupBodyRequest(String method) throws SocialSpiException {
     String jsonActivity = "{title: hi mom!, etc etc}";
 
     String path = "/activities/john.doe/@self/@app";
@@ -160,14 +169,14 @@
   }
 
   public void testHandlePost() throws Exception {
-    Future future = setupBodyRequest("POST");
+    Future<?> future = setupBodyRequest("POST");
     assertNull(future.get());
     verify();
     reset();
   }
 
   public void testHandlePut() throws Exception {
-    Future future = setupBodyRequest("PUT");
+    Future<?> future = setupBodyRequest("PUT");
     assertNull(future.get());
     verify();
     reset();
@@ -193,13 +202,14 @@
     String path = "/activities/@supportedFields";
     RestHandler operation = registry.getRestHandler(path, "GET");
 
-    List<Object> list = ImmutableList.<Object>of("id", "title");
-    expect(containerConfig.getList(eq("default"),
-        eq("${gadgets\\.features.opensocial-0\\.8.supportedFields.activity}"))).andReturn(list);
-
     replay();
-    assertEquals(list, operation.execute(path, Maps.<String, String[]>newHashMap(), null,
-        token, converter).get());
+    @SuppressWarnings("unchecked")
+    List<Object> received = (List<Object>) operation.execute(path, Maps.<String, String[]>newHashMap(), null,
+            token, converter).get();
+    assertEquals(2, received.size());
+    assertEquals("id", received.get(0).toString());
+    assertEquals("title", received.get(1).toString());
+
     verify();
   }
 }