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