Author: awiner
Date: Thu Mar 12 21:12:24 2009
New Revision: 753019

URL: http://svn.apache.org/viewvc?rev=753019&view=rev
Log:
Add support for EL Functions.  By default, register two experimental functions, 
prefixed with "x":
- os:xParseJson: parses json objects or arrays
- os:xDecodeBase64: decodes a base64 string into ... another string
The Functions instance can be re-bound by Guice if needed

Added:
    
incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/expressions/Functions.java
   (with props)
    
incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/expressions/OpensocialFunctions.java
   (with props)
    
incubator/shindig/trunk/java/common/src/test/java/org/apache/shindig/expressions/FunctionsTest.java
   (with props)
    
incubator/shindig/trunk/java/common/src/test/java/org/apache/shindig/expressions/OpensocialFunctionsTest.java
   (with props)
Modified:
    
incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/expressions/Expressions.java

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=753019&r1=753018&r2=753019&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
 Thu Mar 12 21:12:24 2009
@@ -37,6 +37,7 @@
 import javax.el.ValueExpression;
 import javax.el.VariableMapper;
 
+import com.google.common.base.Nullable;
 import com.google.common.collect.Maps;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
@@ -51,9 +52,18 @@
   private final ExpressionFactory factory;
   private final ELContext parseContext;
   private final ELResolver defaultELResolver;
+  private final Functions functions;
 
-  @Inject
+  /**
+   * Convenience constructor that doesn't require any Functions.
+   */
   public Expressions() {
+    this(null);
+  }
+  
+  @Inject
+  public Expressions(@Nullable Functions functions) {
+    this.functions = functions;
     factory = newExpressionFactory();
     // Stub context with no FunctionMapper, used only to parse expressions
     parseContext = new Context(null);
@@ -123,7 +133,7 @@
    * sufficient if not for:
    * 
https://sourceforge.net/tracker2/?func=detail&aid=2590830&group_id=165179&atid=834616
    */
-  static private class Context extends ELContext {
+  private class Context extends ELContext {
     private final ELResolver resolver;
     private VariableMapper variables;
 
@@ -138,7 +148,7 @@
 
     @Override
     public FunctionMapper getFunctionMapper() {
-      return null;
+      return functions;
     }
 
     @Override

Added: 
incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/expressions/Functions.java
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/expressions/Functions.java?rev=753019&view=auto
==============================================================================
--- 
incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/expressions/Functions.java
 (added)
+++ 
incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/expressions/Functions.java
 Thu Mar 12 21:12: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.expressions;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.Map;
+
+import javax.el.FunctionMapper;
+
+import com.google.common.collect.Maps;
+import com.google.inject.ImplementedBy;
+import com.google.inject.Inject;
+
+/**
+ * An implementation of FunctionMapper that uses annotated static methods
+ * on classes to implement EL functions.
+ * <p>
+ * Each class passed to the constructor will have EL functions added
+ * for any static method annotated with the @Expose annotation.
+ * Each method can be exposed in one namespace prefix, with any number
+ * of method names.
+ * <p>
+ * The default Guice instance of the Functions class has the
+ * {...@link OpensocialFunctions} methods registered. 
+ */
+...@implementedby(Functions.DefaultFunctions.class)
+public class Functions extends FunctionMapper {
+  private final Map<String, Map<String, Method>> functions = Maps.newHashMap();
+
+  /** Annotation for static methods to be exposed as functions. */
+  @Retention(RetentionPolicy.RUNTIME)
+  @Target(ElementType.METHOD)
+  public @interface Expose {
+    /**
+     * The prefix to bind functions to.
+     */
+    String prefix();
+
+    /**
+     * The prefix to bind functions to.
+     */
+    String[] names() default {};
+  }
+  
+  /**
+   * Creates a Functions class with the specified
+   */
+  public Functions(Class<?>... functionClasses) {
+    for (Class<?> functionClass : functionClasses) {
+      for (Method m : functionClass.getMethods()) {
+        if ((m.getModifiers() & Modifier.STATIC) == 0) {
+          continue;
+        }
+        
+        addMethod(m);
+      }
+    }
+  }
+  
+  @Override
+  public Method resolveFunction(String prefix, String methodName) {
+    Map<String, Method> prefixMap = functions.get(prefix);
+    if (prefixMap == null) {
+      return null;
+    }
+    
+    return prefixMap.get(methodName);
+  }
+
+  /** Register functions for a single Method */
+  private void addMethod(Method m) {
+    Expose annotation = m.getAnnotation(Expose.class);
+    if (m.isAnnotationPresent(Expose.class)) {
+      String prefix = annotation.prefix();
+      Map<String, Method> prefixMap = functions.get(prefix);
+      if (prefixMap == null) {
+        prefixMap = Maps.newHashMap();
+        functions.put(prefix, prefixMap);
+      }
+      
+      for (String methodName : annotation.names()) {
+        Method priorMethod = prefixMap.put(methodName, m);
+        if (priorMethod != null) {
+          throw new IllegalStateException(
+              "Method " + prefix + ":" + methodName + " re-defined.");
+        }
+      }
+    }
+  }
+  
+  /**
+   * A default version for Guice;  includes the Opensocial functions.
+   */
+  static class DefaultFunctions extends Functions {
+    @Inject
+    public DefaultFunctions() {
+      super(OpensocialFunctions.class);
+    }
+  }
+}

Propchange: 
incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/expressions/Functions.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: 
incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/expressions/OpensocialFunctions.java
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/expressions/OpensocialFunctions.java?rev=753019&view=auto
==============================================================================
--- 
incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/expressions/OpensocialFunctions.java
 (added)
+++ 
incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/expressions/OpensocialFunctions.java
 Thu Mar 12 21:12:24 2009
@@ -0,0 +1,71 @@
+/*
+ * 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.apache.commons.codec.binary.Base64;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.UnsupportedEncodingException;
+
+import javax.el.ELException;
+
+/**
+ * Default functions in the "os:" namespace prefix.
+ */
+public class OpensocialFunctions {
+  private OpensocialFunctions() {
+  }
+  
+  /**
+   * Convert a string to a JSON Object or JSON Array.
+   */
+  @Functions.Expose(prefix = "os", names = {"xParseJson"})
+  public static Object parseJson(String text) {
+    if ((text == null) || "".equals(text)) {
+      return null;
+    }
+    
+    try {
+      if (text.startsWith("[")) {
+        return new JSONArray(text);
+      } else {
+        return new JSONObject(text);
+      }
+    } catch (JSONException je) {
+      throw new ELException(je);
+    }
+  }
+  
+  /**
+   * Decode a base-64 encoded string.
+   */
+  @Functions.Expose(prefix = "os", names = {"xDecodeBase64"})
+  public static String decodeBase64(String text) {
+    try {
+      // TODO: allow a charset to be passed in?
+      return new String(Base64.decodeBase64(text.getBytes("UTF-8")),
+          "UTF-8");
+    } catch (UnsupportedEncodingException uee) {
+      // UTF-8 will be supported everywhere
+      throw new RuntimeException(uee);
+    }
+  }
+}

Propchange: 
incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/expressions/OpensocialFunctions.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: 
incubator/shindig/trunk/java/common/src/test/java/org/apache/shindig/expressions/FunctionsTest.java
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/common/src/test/java/org/apache/shindig/expressions/FunctionsTest.java?rev=753019&view=auto
==============================================================================
--- 
incubator/shindig/trunk/java/common/src/test/java/org/apache/shindig/expressions/FunctionsTest.java
 (added)
+++ 
incubator/shindig/trunk/java/common/src/test/java/org/apache/shindig/expressions/FunctionsTest.java
 Thu Mar 12 21:12:24 2009
@@ -0,0 +1,108 @@
+/*
+ * 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.JSONObject;
+
+import java.lang.reflect.Method;
+
+import javax.el.ELContext;
+import javax.el.ValueExpression;
+
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+
+import junit.framework.TestCase;
+
+public class FunctionsTest extends TestCase {
+  private Functions functions;
+
+  @Override
+  protected void setUp() throws Exception {
+    functions = new Functions(FunctionsTest.class);
+  }
+
+  public void testExpose() throws Exception {
+    Method hi = functions.resolveFunction("test", "hi");
+    assertEquals("hi", hi.invoke(null));
+
+    Method hiAlternate = functions.resolveFunction("test", "hola");
+    assertEquals("hi", hiAlternate.invoke(null));
+
+    Method bonjour = functions.resolveFunction("other", "bonjour");
+    assertEquals("French hello", bonjour.invoke(null));
+  }
+  
+  public void testNonStaticNotExposed() {
+    assertNull(functions.resolveFunction("test", "goodbye"));
+  }
+  
+  public void testDefaultBinding() throws Exception {
+    Injector injector = Guice.createInjector();
+    functions = injector.getInstance(Functions.class);
+    
+    Method toJson = functions.resolveFunction("os", "xParseJson");
+    Object o = toJson.invoke(null, "{a : 1}");
+    assertTrue(o instanceof JSONObject);
+    assertEquals(1, ((JSONObject) o).getInt("a"));
+  }
+  
+  public void testExpressionEvaluation() {
+    Expressions expressions = new Expressions(functions);
+    ELContext context = expressions.newELContext();
+    ValueExpression expression = expressions.parse("${other:bonjour()}", 
String.class);
+    
+    assertEquals("French hello", expression.getValue(context));
+    
+    expression = expressions.parse("${test:add(1, 2)}", Integer.class);
+    assertEquals(3, expression.getValue(context));
+  }
+  
+  /**
+   * Static function, should be exposed under two names.
+   */
+  @Functions.Expose(prefix="test", names={"hi", "hola"})
+  public static String sayHi() {
+    return "hi";
+  }
+
+  /**
+   * Test with some arguments.
+   */
+  @Functions.Expose(prefix="test", names={"add"})
+  public static int add(int i, int j) {
+    return i + j;
+  }
+
+  /**
+   * Static function, should be exposed under two names.
+   */
+  @Functions.Expose(prefix="other", names={"bonjour"})
+  public static String sayHi2() {
+    return "French hello";
+  }
+
+  /**
+   * Non-static: shouldn't be exposed.
+   */
+  @Functions.Expose(prefix="test", names={"goodbye"})
+  public String sayGoodbye() {
+    return "goodbye";
+  }
+}

Propchange: 
incubator/shindig/trunk/java/common/src/test/java/org/apache/shindig/expressions/FunctionsTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: 
incubator/shindig/trunk/java/common/src/test/java/org/apache/shindig/expressions/OpensocialFunctionsTest.java
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/common/src/test/java/org/apache/shindig/expressions/OpensocialFunctionsTest.java?rev=753019&view=auto
==============================================================================
--- 
incubator/shindig/trunk/java/common/src/test/java/org/apache/shindig/expressions/OpensocialFunctionsTest.java
 (added)
+++ 
incubator/shindig/trunk/java/common/src/test/java/org/apache/shindig/expressions/OpensocialFunctionsTest.java
 Thu Mar 12 21:12:24 2009
@@ -0,0 +1,65 @@
+/*
+ * 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.apache.commons.codec.binary.Base64;
+
+import java.util.Map;
+
+import javax.el.ELContext;
+import javax.el.ValueExpression;
+
+import com.google.common.collect.Maps;
+
+import junit.framework.TestCase;
+
+public class OpensocialFunctionsTest extends TestCase {
+  private Expressions expressions;
+  private ELContext context;
+  private Map<String, Object> vars = Maps.newHashMap();
+  
+  @Override
+  protected void setUp() {
+    Functions functions = new Functions(OpensocialFunctions.class);
+    expressions = new Expressions(functions);
+    context = expressions.newELContext(new RootELResolver(vars));
+  }
+  
+  public void testParseJsonObject() {
+    ValueExpression testParseJsonObject =
+      expressions.parse("${os:xParseJson('{a: 1}').a}", Integer.class);
+    assertEquals(1, testParseJsonObject.getValue(context));
+  }
+
+  public void testParseJsonArray() {
+    ValueExpression testParseJsonArray =
+      expressions.parse("${os:xParseJson('[1, 2, 3]')[1]}", Integer.class);
+    assertEquals(2, testParseJsonArray.getValue(context));
+  }
+  
+  public void testDecodeBase64() throws Exception {
+    String test = "12345";
+    String encoded = new String(Base64.encodeBase64(test.getBytes("UTF-8")), 
"UTF-8");
+    vars.put("encoded", encoded);
+    
+    ValueExpression testDecodeBase64 =
+      expressions.parse("${os:xDecodeBase64(encoded)}", String.class);
+    assertEquals("12345", testDecodeBase64.getValue(context));
+  }
+}

Propchange: 
incubator/shindig/trunk/java/common/src/test/java/org/apache/shindig/expressions/OpensocialFunctionsTest.java
------------------------------------------------------------------------------
    svn:eol-style = native


Reply via email to