You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@shindig.apache.org by zh...@apache.org on 2010/08/04 04:41:30 UTC
svn commit: r982106 - in /shindig/trunk/java:
common/src/main/java/org/apache/shindig/protocol/conversion/
common/src/test/java/org/apache/shindig/protocol/conversion/
gadgets/src/main/java/org/apache/shindig/gadgets/servlet/
gadgets/src/test/java/org/...
Author: zhoresh
Date: Wed Aug 4 02:41:30 2010
New Revision: 982106
URL: http://svn.apache.org/viewvc?rev=982106&view=rev
Log:
Separate GadgetHandler interface from implementation classes
http://codereview.appspot.com/1844046/show
Added:
shindig/trunk/java/common/src/main/java/org/apache/shindig/protocol/conversion/BeanDelegator.java
shindig/trunk/java/common/src/main/java/org/apache/shindig/protocol/conversion/BeanFilter.java
shindig/trunk/java/common/src/test/java/org/apache/shindig/protocol/conversion/BeanDelegatorTest.java
shindig/trunk/java/common/src/test/java/org/apache/shindig/protocol/conversion/BeanFilterTest.java
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/GadgetsHandlerApi.java
Modified:
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/GadgetsHandler.java
shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/servlet/GadgetsHandlerTest.java
Added: shindig/trunk/java/common/src/main/java/org/apache/shindig/protocol/conversion/BeanDelegator.java
URL: http://svn.apache.org/viewvc/shindig/trunk/java/common/src/main/java/org/apache/shindig/protocol/conversion/BeanDelegator.java?rev=982106&view=auto
==============================================================================
--- shindig/trunk/java/common/src/main/java/org/apache/shindig/protocol/conversion/BeanDelegator.java (added)
+++ shindig/trunk/java/common/src/main/java/org/apache/shindig/protocol/conversion/BeanDelegator.java Wed Aug 4 02:41:30 2010
@@ -0,0 +1,268 @@
+/*
+ * 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.protocol.conversion;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Maps;
+
+import org.apache.shindig.common.uri.Uri;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Proxy;
+import java.lang.reflect.Type;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Class to create a delegator (proxy) from an interface to a class.
+ * It is used by the GadgetHandler to provide easy separation from interface
+ * to actual implementation classes.
+ * It uses Java reflection which require the usage of interfaces.
+ * The validate function should be used in the test code to validate
+ * that all API functions are implemented by the actual data, and it will
+ * warn us if actual implementation change and break the API.
+ * Delegation support composition, and will create a proxy for fields according
+ * To table of classes to proxy.
+ */
+public class BeanDelegator {
+
+ /** List of Classes that are considered primitives and are not proxied **/
+ public static final ImmutableSet<Class<?>> PRIMITIVE_TYPE_CLASSES = ImmutableSet.of(
+ String.class, Integer.class, Long.class, Boolean.class, Uri.class);
+
+ /** Map from classes to proxy to the interface they are proxied by */
+ private final Map<Class<?>, Class<?>> delegatedClasses;
+
+ private final Map<Enum<?>, Enum<?>> enumConvertionMap;
+
+ public BeanDelegator(Map<Class<?>, Class<?>> delegatedClasses,
+ Map<Enum<?>, Enum<?>> enumConvertionMap) {
+ this.delegatedClasses = delegatedClasses;
+ this.enumConvertionMap = enumConvertionMap;
+ }
+
+ /**
+ * Create a proxy for the real object.
+ * @param source item to proxy
+ * @return proxied object according to map of classes to proxy
+ */
+ public Object createDelegator(Object source) {
+ if (source == null || delegatedClasses == null || delegatedClasses.size() == 0) {
+ return null;
+ }
+
+ // For enum, return the converted enum
+ if (source instanceof Enum<?> && delegatedClasses.containsKey(source.getClass())) {
+ return convertEnum((Enum<?>) source);
+ }
+
+ // Proxy each item in a map (map key is not proxied)
+ if (source instanceof Map<?, ?>) {
+ Map<?, ?> mapSource = (Map<?, ?>) source;
+ if (mapSource.size() > 0 && delegatedClasses.containsKey(
+ mapSource.values().iterator().next().getClass())) {
+ // Convert Map:
+ ImmutableMap.Builder<Object, Object> mapBuilder = ImmutableMap.builder();
+ for (Map.Entry<?, ?> entry : mapSource.entrySet()) {
+ mapBuilder.put(entry.getKey(), createDelegator(entry.getValue()));
+ }
+ return mapBuilder.build();
+ } else {
+ return source;
+ }
+ }
+
+ // Proxy each item in a list
+ if (source instanceof List<?>) {
+ List<?> listSource = (List<?>) source;
+ if (listSource.size() > 0 && delegatedClasses.containsKey(
+ listSource.get(0).getClass())) {
+ // Convert Map:
+ ImmutableList.Builder<Object> listBuilder = ImmutableList.builder();
+ for (Object entry : listSource) {
+ listBuilder.add(createDelegator(entry));
+ }
+ return listBuilder.build();
+ } else {
+ return source;
+ }
+ }
+
+ if (delegatedClasses.containsKey(source.getClass())) {
+ Class<?> apiInterface = delegatedClasses.get(source.getClass());
+
+ return Proxy.newProxyInstance( apiInterface.getClassLoader(),
+ new Class[] { apiInterface }, new DelegateInvocationHandler(source));
+ }
+ return source;
+ }
+
+ public Enum<?> convertEnum(Enum<?> value) {
+ if (enumConvertionMap.containsKey(value)) {
+ return enumConvertionMap.get(value);
+ }
+ throw new UnsupportedOperationException("Unknown enum value " + value.toString());
+ }
+
+ protected class DelegateInvocationHandler implements InvocationHandler {
+ /** Proxied object */
+ private final Object source;
+
+ public DelegateInvocationHandler(Object source) {
+ Preconditions.checkNotNull(source);
+ this.source = source;
+ }
+
+ /**
+ * Proxy the interface function to the source object
+ * @throw UnsupportedOperationException if method is not supported by source
+ */
+ public Object invoke(Object proxy, Method method, Object[] args) {
+ Class<?> sourceClass = source.getClass();
+ try {
+ Method sourceMethod = sourceClass.getMethod(
+ method.getName(), method.getParameterTypes());
+ Object result = sourceMethod.invoke(source, args);
+ return createDelegator(result);
+ } catch (NoSuchMethodException e) {
+ // Will throw unsupported method below
+ } catch (IllegalArgumentException e) {
+ // Will throw unsupported method below
+ } catch (IllegalAccessException e) {
+ // Will throw unsupported method below
+ } catch (InvocationTargetException e) {
+ // Will throw unsupported method below
+ }
+ throw new UnsupportedOperationException("Unsupported function: " + method.getName());
+ }
+ }
+
+ /**
+ * Validate all proxied classes to see that all required functions are implemented.
+ * Throws exception if failed validation.
+ * @throws SecurityException
+ * @throws NoSuchMethodException
+ * @throws NoSuchFieldException
+ */
+ public void validate() throws SecurityException, NoSuchMethodException, NoSuchFieldException {
+ for (Map.Entry<Class<?>, Class<?>> entry : delegatedClasses.entrySet()) {
+ if (!entry.getKey().isEnum()) {
+ validate(entry.getKey(), entry.getValue());
+ }
+ }
+ }
+
+ public void validate(Class<?> dataClass, Class<?> interfaceClass)
+ throws SecurityException, NoSuchMethodException, NoSuchFieldException {
+ for (Method method : interfaceClass.getMethods()) {
+ Method dataMethod = dataClass.getMethod(method.getName(), method.getParameterTypes());
+ if (dataMethod == null) {
+ throw new NoSuchMethodException("Method " + method.getName()
+ + " is not implemented by " + dataClass.getName());
+ }
+ if (!validateTypes(dataMethod.getGenericReturnType(), method.getGenericReturnType())) {
+ throw new NoSuchMethodException("Method " + method.getName()
+ + " has wrong return type by " + dataClass.getName());
+ }
+ }
+ }
+
+ private boolean validateTypes(Type dataType, Type interfaceType)
+ throws NoSuchFieldException {
+
+ // Handle Map and List parameterized types
+ if (dataType instanceof ParameterizedType) {
+ ParameterizedType dataParamType = (ParameterizedType) dataType;
+ ParameterizedType interfaceParamType = (ParameterizedType) interfaceType;
+
+ if (List.class.isAssignableFrom((Class<?>) dataParamType.getRawType()) &&
+ List.class.isAssignableFrom((Class<?>) interfaceParamType.getRawType())) {
+
+ dataType = dataParamType.getActualTypeArguments()[0];
+ interfaceType = interfaceParamType.getActualTypeArguments()[0];
+ return validateTypes(dataType, interfaceType);
+ }
+ if (Map.class.isAssignableFrom((Class<?>) dataParamType.getRawType()) &&
+ Map.class.isAssignableFrom((Class<?>) interfaceParamType.getRawType())) {
+ Type dataKeyType = dataParamType.getActualTypeArguments()[0];
+ Type interfaceKeyType = interfaceParamType.getActualTypeArguments()[0];
+ if (dataKeyType != interfaceKeyType || !PRIMITIVE_TYPE_CLASSES.contains(dataKeyType)) {
+ return false;
+ }
+ dataType = dataParamType.getActualTypeArguments()[1];
+ interfaceType = interfaceParamType.getActualTypeArguments()[1];
+ return validateTypes(dataType, interfaceType);
+ }
+ // Only support Map and List generics
+ return false;
+ }
+
+ // Primitive types
+ if (dataType == interfaceType) {
+ if (!PRIMITIVE_TYPE_CLASSES.contains(dataType) && !((Class<?>) dataType).isPrimitive()) {
+ return false;
+ }
+ return true;
+ }
+
+ // Check all enum values are accounted for
+ Class<?> dataClass = (Class<?>)dataType;
+ if (dataClass.isEnum()) {
+ for (Object f : dataClass.getEnumConstants()) {
+ if (!enumConvertionMap.containsKey(f) ||
+ enumConvertionMap.get(f).getClass() != interfaceType) {
+ throw new NoSuchFieldException("Enum " + dataClass.getName()
+ + " don't have mapping for value " + f.toString());
+ }
+ }
+ }
+ return (delegatedClasses.get(dataType) == interfaceType);
+ }
+
+
+ /**
+ * Utility function to auto generate mapping between two enums that have same values (toString)
+ * All values in the sourceEnum must have values in targetEnum,
+ * otherwise {@link RuntimeException} is thrown
+ */
+ public static Map<Enum<?>, Enum<?>> createDefaultEnumMap(
+ Class<? extends Enum<?>> sourceEnum, Class<? extends Enum<?>> targetEnum) {
+ Map<String, Enum<?>> values2Map = Maps.newHashMap();
+ for (Object val2 : targetEnum.getEnumConstants()) {
+ values2Map.put(val2.toString(), (Enum<?>) val2);
+ }
+ Enum<?>[] values1 = sourceEnum.getEnumConstants();
+ ImmutableMap.Builder<Enum<?>, Enum<?>> mapBuilder = ImmutableMap.builder();
+ for (Enum<?> val1 : sourceEnum.getEnumConstants()) {
+ if (values2Map.containsKey(val1.toString())) {
+ mapBuilder.put(val1, values2Map.get(val1.toString()));
+ } else {
+ throw new RuntimeException("Missing enum value " + val1.toString()
+ + " for enum " + targetEnum.getName());
+ }
+ }
+ return mapBuilder.build();
+ }
+}
Added: shindig/trunk/java/common/src/main/java/org/apache/shindig/protocol/conversion/BeanFilter.java
URL: http://svn.apache.org/viewvc/shindig/trunk/java/common/src/main/java/org/apache/shindig/protocol/conversion/BeanFilter.java?rev=982106&view=auto
==============================================================================
--- shindig/trunk/java/common/src/main/java/org/apache/shindig/protocol/conversion/BeanFilter.java (added)
+++ shindig/trunk/java/common/src/main/java/org/apache/shindig/protocol/conversion/BeanFilter.java Wed Aug 4 02:41:30 2010
@@ -0,0 +1,219 @@
+/*
+ * 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.protocol.conversion;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+
+import org.apache.commons.lang.StringUtils;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Proxy;
+import java.lang.reflect.Type;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Filter content of a bean according to fields list.
+ * Fields list should be in lower case. And support sub objects using dot notation.
+ * For example to get only the "name" field of the object in the "view" field,
+ * specify "view.name" (and also specify "view" to get the view itself).
+ * Use "*" to get all fields, or "view.*" all sub fields of view (see tests).
+ * Note that specifying "view" does NOT imply "view.*" and that
+ * specifying "view.*" require specifying "view" in order to get the view itself.
+ * (Note that the processBeanFilter resolve the last limitation)
+ *
+ * Note this code create a new object for each filtered object.
+ * Filtering can be done also using cglib.InterfaceMaker and reflect.Proxy.makeProxyInstance
+ * That results with an object that have same finger print as source, but cannot be cast to it.
+ */
+public class BeanFilter {
+
+ public static final String ALL_FIELDS = "*";
+ public static final String DELIMITER = ".";
+
+ /** Annotation for required field that should not be filtered */
+ @Target(ElementType.METHOD)
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface Required {}
+
+ /**
+ * Create a proxy object that filter object fields according to set of fields.
+ * If a field is not specified in the set, the get method will return null.
+ * (Primitive returned type cannot be filtered)
+ * The filter is done recursively on sub items.
+ * @param data the object to filter
+ * @param fields list of fields to pass through.
+ */
+ public Object createFilteredBean(Object data, Set<String> fields) {
+ return createFilteredBean(data, fields, "");
+ }
+
+ @SuppressWarnings("unchecked")
+ private Object createFilteredBean(Object data, Set<String> fields, String fieldName) {
+ // For null, atomic object or for all fields just return original.
+ if (data == null || fields == null
+ || BeanDelegator.PRIMITIVE_TYPE_CLASSES.contains(data.getClass())
+ || fields.contains(ALL_FIELDS)) {
+ return data;
+ }
+
+ // For map, generate a new map with filtered objects
+ if (data instanceof Map<? ,?>) {
+ Map<Object, Object> oldMap = (Map<Object, Object>) data;
+ Map<Object, Object> newMap = Maps.newHashMapWithExpectedSize(oldMap.size());
+ for (Map.Entry<Object, Object> entry : oldMap.entrySet()) {
+ newMap.put(entry.getKey(), createFilteredBean(entry.getValue(), fields, fieldName));
+ }
+ return newMap;
+ }
+
+ // For list, generate a new list of filtered objects
+ if (data instanceof List<?>) {
+ List<Object> oldList = (List<Object>) data;
+ List<Object> newList = Lists.newArrayListWithCapacity(oldList.size());
+ for (Object entry : oldList) {
+ newList.add(createFilteredBean(entry, fields, fieldName));
+ }
+ return newList;
+ }
+
+ // Create a new intercepted object:
+ return Proxy.newProxyInstance( data.getClass().getClassLoader(),
+ data.getClass().getInterfaces(), new FilterInvocationHandler(data, fields, fieldName));
+ }
+
+ /**
+ * Invocation handler to filter fields. It return null to fields that are not in the list.
+ * It invokes method on original object. It does not filter primitive types.
+ * And it create bean filter proxy for return objects
+ */
+ private class FilterInvocationHandler implements InvocationHandler {
+ private final String prefix;
+ private final Set<String> fields;
+ private final Object origData;
+
+ FilterInvocationHandler(Object origData, Set<String> fields, String fieldName) {
+ this.fields = fields;
+ this.prefix = StringUtils.isEmpty(fieldName) ? "" : fieldName + DELIMITER;
+ this.origData = origData;
+ }
+
+ public Object invoke(Object data, Method method, Object[] args) {
+ String fieldName = null;
+ Object result = null;
+ if (method.getName().startsWith("get")
+ // Do not filter out primitive types, it will result in NPE
+ && !method.getReturnType().isPrimitive()) {
+ // Look for Required annotation
+ boolean required = (method.getAnnotation(Required.class) != null);
+ fieldName = prefix + method.getName().substring(3).toLowerCase();
+ if (!required && !fields.contains(fieldName)) {
+ return null;
+ }
+ }
+ try {
+ result = method.invoke(origData, args);
+ } catch (IllegalArgumentException e) {
+ throw new RuntimeException(e);
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e);
+ } catch (InvocationTargetException e) {
+ throw new RuntimeException(e);
+ }
+ if (result != null && fieldName != null
+ // if the request ask for all fields, we don't need to filter them
+ && !fields.contains(fieldName + DELIMITER + ALL_FIELDS)) {
+ return createFilteredBean(result, fields, fieldName);
+ // TODO: Consider improving the above by saving the filtered bean in a local map for reuse
+ // for current use the get is called once, so it would actually create overhead
+ }
+ return result;
+ }
+ }
+
+ public Set<String> processBeanFields(Set<String> fields) {
+ ImmutableSet.Builder<String> builder = ImmutableSet.builder();
+ for (String field : fields) {
+ builder.add(field.toLowerCase());
+ while (field.contains(DELIMITER)) {
+ field = field.substring(0, field.lastIndexOf(DELIMITER));
+ builder.add(field.toLowerCase());
+ }
+ }
+ return builder.build();
+ }
+
+ /**
+ * Provide list of all fields for a specific bean
+ * @param bean the class to list fields for
+ * @param depth maximum depth of recursive (mainly for infinite loop protection)
+ */
+ public List<String> getBeanFields(Class<?> bean, int depth) {
+ List<String> fields = Lists.newLinkedList();
+ for (Method method : bean.getMethods()) {
+ if (method.getName().startsWith("get")) {
+ String fieldName = method.getName().substring(3);
+ fields.add(fieldName);
+ Class<?> returnType = method.getReturnType();
+ // Get the type of list:
+ if (List.class.isAssignableFrom(returnType)) {
+ ParameterizedType aType = (ParameterizedType) method.getGenericReturnType();
+ Type[] parameterArgTypes = aType.getActualTypeArguments();
+ if (parameterArgTypes.length > 0) {
+ returnType = (Class<?>) parameterArgTypes[0];
+ } else {
+ returnType = null;
+ }
+ }
+ // Get the type of map value
+ if (Map.class.isAssignableFrom(returnType)) {
+ ParameterizedType aType = (ParameterizedType) method.getGenericReturnType();
+ Type[] parameterArgTypes = aType.getActualTypeArguments();
+ if (parameterArgTypes.length > 1) {
+ returnType = (Class<?>) parameterArgTypes[1];
+ } else {
+ returnType = null;
+ }
+ }
+ // Get member fields and append fields using dot notation
+ if (depth > 1 && returnType != null && !returnType.isPrimitive()
+ && !returnType.isEnum()
+ && !BeanDelegator.PRIMITIVE_TYPE_CLASSES.contains(returnType)) {
+ List<String> subFields = getBeanFields(returnType, depth - 1);
+ for (String field : subFields) {
+ fields.add(fieldName + DELIMITER + field);
+ }
+ }
+ }
+ }
+ return fields;
+ }
+
+}
Added: shindig/trunk/java/common/src/test/java/org/apache/shindig/protocol/conversion/BeanDelegatorTest.java
URL: http://svn.apache.org/viewvc/shindig/trunk/java/common/src/test/java/org/apache/shindig/protocol/conversion/BeanDelegatorTest.java?rev=982106&view=auto
==============================================================================
--- shindig/trunk/java/common/src/test/java/org/apache/shindig/protocol/conversion/BeanDelegatorTest.java (added)
+++ shindig/trunk/java/common/src/test/java/org/apache/shindig/protocol/conversion/BeanDelegatorTest.java Wed Aug 4 02:41:30 2010
@@ -0,0 +1,205 @@
+/*
+ * 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.protocol.conversion;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+import junit.framework.Assert;
+
+import org.apache.shindig.protocol.conversion.BeanFilter.Required;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.List;
+import java.util.Map;
+
+public class BeanDelegatorTest extends Assert {
+
+ // Note, this classes also used by the BeanFilter tests
+ public static interface SimpleBeanInterface {
+ public int getI();
+ public SimpleBeanInterface setI(int i);
+ public String getS();
+ // Test list conversions:
+ public List<String> getList();
+ public List<SimpleBeanInterface> getBeanList();
+ // Test Map conversion
+ public Map<String, String> getMap();
+ public Map<String, SimpleBeanInterface> getBeanMap();
+ // Test error cases
+ public String getUnknown(); // delegated class doesn't have this
+ public int getWrongType(); // delegated class return different type
+ public String getPrivateData(); // delegated class method is private
+
+ // Test enum
+ public enum Style { A, B; }
+ public Style getStyle();
+
+ // Test of required
+ @Required
+ public String getRequired();
+ }
+
+ public static class SimpleBean {
+ private int i;
+ private String s;
+ private List<String> l;
+ private List<SimpleBean> beanList;
+ private Map<String, String> stringMap;
+ private Map<String, SimpleBean> beanMap;
+
+ public int getI() { return i; }
+ public SimpleBean setI(int ni) { i = ni; return this; }
+
+ public String getS() { return s; }
+ public SimpleBean setS(String ns) { s = ns; return this; }
+
+ public List<String> getList() { return l; }
+ public SimpleBean setList(List<String> nl) { l = nl; return this; }
+
+ public List<SimpleBean> getBeanList() { return beanList; }
+ public SimpleBean setBeanList(List<SimpleBean> nl) { beanList = nl; return this; }
+
+ public Map<String, String> getMap() { return stringMap; }
+ public SimpleBean setMap(Map<String, String> nm) { stringMap = nm; return this; }
+
+ public Map<String, SimpleBean> getBeanMap() { return beanMap; }
+ public SimpleBean setBeanMap(Map<String, SimpleBean> nm) { beanMap = nm; return this; }
+
+ public String getWrongType() { return "this is string"; }
+
+ @SuppressWarnings("unused")
+ private String getPrivateData() { return "this is private"; }
+
+ // Enum data:
+ public enum RealStyle { R_A, R_B; }
+ RealStyle style;
+ public RealStyle getStyle() { return style; }
+ public SimpleBean setStyle(RealStyle style) { this.style = style; return this; }
+
+ // Test of required
+ public String getRequired() { return "required"; }
+ }
+
+ private BeanDelegator beanDelegator;
+ private SimpleBean source;
+ private SimpleBeanInterface proxy;
+
+ public static BeanDelegator createSimpleDelegator() {
+ BeanDelegator beanDelegator = new BeanDelegator(
+ ImmutableMap.<Class<?>, Class<?>>of(SimpleBean.class, SimpleBeanInterface.class,
+ SimpleBean.RealStyle.class, SimpleBeanInterface.Style.class),
+ ImmutableMap.<Enum<?>, Enum<?>>of(SimpleBean.RealStyle.R_A, SimpleBeanInterface.Style.A,
+ SimpleBean.RealStyle.R_B, SimpleBeanInterface.Style.B));
+ return beanDelegator;
+ }
+
+ @Before
+ public void setUp() {
+ beanDelegator = createSimpleDelegator();
+ source = new SimpleBean();
+ proxy = (SimpleBeanInterface) beanDelegator.createDelegator(source);
+ }
+
+ @Test
+ public void testSimpleBean() {
+ String s = "test";
+ source.setS(s);
+ assertEquals(s, proxy.getS());
+
+ proxy.setI(5);
+ assertEquals(5, proxy.getI());
+ assertEquals(5, source.getI());
+
+ source.setStyle(SimpleBean.RealStyle.R_A);
+ assertEquals(SimpleBeanInterface.Style.A, proxy.getStyle());
+ }
+
+ @Test(expected = UnsupportedOperationException.class)
+ public void testUnimplementedFunction() {
+ proxy.getUnknown();
+ }
+
+ @Test(expected = ClassCastException.class)
+ public void testWrontType() {
+ proxy.getWrongType();
+ }
+
+ @Test(expected = UnsupportedOperationException.class)
+ public void testPrivateAccess() {
+ proxy.getPrivateData();
+ }
+
+ @Test
+ public void testStringList() {
+ assertNull(proxy.getList());
+ List<String> stringList = ImmutableList.of("item1", "item2");
+ source.setList(stringList);
+ assertEquals(stringList, proxy.getList());
+ stringList = ImmutableList.of();
+ source.setList(stringList);
+ assertEquals(stringList, proxy.getList());
+ }
+
+ @Test
+ public void testBeanList() {
+ List<SimpleBean> beanList = ImmutableList.of();
+ source.setBeanList(beanList);
+ assertEquals(beanList, proxy.getBeanList());
+
+ SimpleBean item = new SimpleBean().setS("item");
+ beanList = ImmutableList.of(item);
+ source.setBeanList(beanList);
+ List<SimpleBeanInterface> interList = proxy.getBeanList();
+ assertEquals(1, interList.size());
+ assertEquals(item.getS(), interList.get(0).getS());
+ }
+
+ @Test
+ public void testStringMap() {
+ assertNull(proxy.getMap());
+ Map<String, String> stringMap = ImmutableMap.of("item1", "v1", "item2", "v2");
+ source.setMap(stringMap);
+ assertEquals(stringMap, proxy.getMap());
+ stringMap = ImmutableMap.of();
+ source.setMap(stringMap);
+ assertEquals(stringMap, proxy.getMap());
+ }
+
+ @Test
+ public void testBeanMap() {
+ Map<String, SimpleBean> beanMap = ImmutableMap.of();
+ source.setBeanMap(beanMap);
+ assertEquals(beanMap, proxy.getBeanMap());
+
+ SimpleBean item = new SimpleBean().setS("item");
+ beanMap = ImmutableMap.of("item", item);
+ source.setBeanMap(beanMap);
+ Map<String, SimpleBeanInterface> interMap = proxy.getBeanMap();
+ assertEquals(1, interMap.size());
+ assertEquals(item.getS(), interMap.get("item").getS());
+ }
+
+ // Make sure validate will actually fail
+ @Test(expected = NoSuchMethodException.class)
+ public void tesValidate() throws Exception {
+ beanDelegator.validate();
+ }
+}
Added: shindig/trunk/java/common/src/test/java/org/apache/shindig/protocol/conversion/BeanFilterTest.java
URL: http://svn.apache.org/viewvc/shindig/trunk/java/common/src/test/java/org/apache/shindig/protocol/conversion/BeanFilterTest.java?rev=982106&view=auto
==============================================================================
--- shindig/trunk/java/common/src/test/java/org/apache/shindig/protocol/conversion/BeanFilterTest.java (added)
+++ shindig/trunk/java/common/src/test/java/org/apache/shindig/protocol/conversion/BeanFilterTest.java Wed Aug 4 02:41:30 2010
@@ -0,0 +1,178 @@
+/*
+ * 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.protocol.conversion;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+
+import org.apache.shindig.protocol.conversion.BeanDelegatorTest.SimpleBean;
+import org.apache.shindig.protocol.conversion.BeanDelegatorTest.SimpleBeanInterface;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public class BeanFilterTest extends Assert {
+
+ private BeanFilter beanFilter;
+ private BeanDelegator beanDelegator;
+
+ @Before
+ public void setUp() {
+ beanFilter = new BeanFilter();
+ beanDelegator = BeanDelegatorTest.createSimpleDelegator();
+ }
+
+ @Test
+ public void testNull() throws Exception {
+ assertNull(beanFilter.createFilteredBean(null, ImmutableSet.<String>of("s")));
+ }
+
+ @Test
+ public void testSimple() throws Exception {
+ String data = "data";
+
+ String newData = (String) beanFilter.createFilteredBean(data, null);
+ assertSame(data, newData);
+ }
+
+ @Test
+ public void testInt() throws Exception {
+ SimpleBean data = new SimpleBean().setI(5);
+ SimpleBeanInterface dataBean = (SimpleBeanInterface) beanDelegator.createDelegator(data);
+
+ SimpleBeanInterface newData = (SimpleBeanInterface) beanFilter.createFilteredBean(
+ dataBean, ImmutableSet.<String>of("i"));
+ assertEquals(5, newData.getI());
+
+ newData = (SimpleBeanInterface) beanFilter.createFilteredBean(
+ dataBean, ImmutableSet.<String>of("s"));
+ // Filter is ignored for primitive types:
+ assertEquals(5, newData.getI());
+ }
+
+ @Test
+ public void testString() throws Exception {
+ SimpleBean data = new SimpleBean().setS("data");
+ SimpleBeanInterface dataBean = (SimpleBeanInterface) beanDelegator.createDelegator(data);
+
+ SimpleBeanInterface newData = (SimpleBeanInterface) beanFilter.createFilteredBean(
+ dataBean, ImmutableSet.<String>of("s"));
+ assertEquals("data", newData.getS());
+ newData = (SimpleBeanInterface) beanFilter.createFilteredBean(
+ dataBean, ImmutableSet.<String>of("i"));
+ assertNull("S is filtered out", newData.getS());
+ assertNotNull("Required field", newData.getRequired());
+ }
+
+ @Test
+ public void testList() throws Exception {
+ SimpleBean data = new SimpleBean().setList(ImmutableList.<String>of("d1", "d2"));
+ SimpleBeanInterface dataBean = (SimpleBeanInterface) beanDelegator.createDelegator(data);
+
+ SimpleBeanInterface newData = (SimpleBeanInterface) beanFilter.createFilteredBean(
+ dataBean, ImmutableSet.<String>of("s"));
+ assertEquals(null, newData.getList());
+
+ newData = (SimpleBeanInterface) beanFilter.createFilteredBean(
+ dataBean, ImmutableSet.<String>of("list"));
+ assertArrayEquals(data.getList().toArray(), newData.getList().toArray());
+ }
+
+ @Test
+ public void testMap() throws Exception {
+ List<String> list = ImmutableList.<String>of("test");
+ SimpleBean data = new SimpleBean().setS("Main").setBeanMap(
+ ImmutableMap.<String, SimpleBean>of( "s1", new SimpleBean().setS("sub1").setList(list),
+ "s2", new SimpleBean().setS("sub2").setList(list).setBeanMap(
+ ImmutableMap.of("s2s1", new SimpleBean().setS("sub2-sub1"))
+ )));
+ SimpleBeanInterface dataBean = (SimpleBeanInterface) beanDelegator.createDelegator(data);
+
+ SimpleBeanInterface newData = (SimpleBeanInterface) beanFilter.createFilteredBean(dataBean,
+ ImmutableSet.<String>of("beanmap"));
+ assertEquals(2, newData.getBeanMap().size());
+ assertEquals(null, newData.getBeanMap().get("s1").getS());
+
+ newData = (SimpleBeanInterface) beanFilter.createFilteredBean(dataBean,
+ ImmutableSet.<String>of("beanmap", "beanmap.s"));
+ assertNotSame(dataBean.getBeanMap().getClass(), newData.getBeanMap().getClass());
+ assertEquals(2, newData.getBeanMap().size());
+ assertEquals("sub1", newData.getBeanMap().get("s1").getS());
+ assertNull("List is filtered out", newData.getBeanMap().get("s1").getList());
+
+ newData = (SimpleBeanInterface) beanFilter.createFilteredBean(dataBean,
+ ImmutableSet.<String>of("beanmap", "beanmap.*"));
+ // Verify filter is a simple pass through.
+ // can only check class since each time different delegator is created
+ assertSame(dataBean.getBeanMap().getClass(), newData.getBeanMap().getClass());
+
+ newData = (SimpleBeanInterface) beanFilter.createFilteredBean(dataBean,
+ ImmutableSet.<String>of("beanmap", "beanmap.beanmap", "beanmap.beanmap.s"));
+ assertEquals(2, newData.getBeanMap().size());
+ Map<String, SimpleBeanInterface> subSubMap = newData.getBeanMap().get("s2").getBeanMap();
+ assertEquals(1, subSubMap.size());
+ assertEquals("sub2-sub1", subSubMap.get("s2s1").getS());
+ assertNull("list is filtered", subSubMap.get("s2s1").getList());
+
+ newData = (SimpleBeanInterface) beanFilter.createFilteredBean(dataBean,
+ ImmutableSet.<String>of("beanmap", "beanmap.beanmap", "beanmap.beanmap.*"));
+ assertEquals(2, newData.getBeanMap().size());
+ assertNotSame(dataBean.getBeanMap().getClass(), newData.getBeanMap().getClass());
+ assertSame(data.getBeanMap().get("s2").getBeanMap().getClass(),
+ newData.getBeanMap().get("s2").getBeanMap().getClass());
+ }
+
+ @Test
+ public void testProcessFields() {
+ Set<String> srcFields = ImmutableSet.of("A", "b", "c.d.e.f", "Case", "cAse", "CASE");
+ Set<String> newFields = beanFilter.processBeanFields(srcFields);
+ assertEquals(7, newFields.size());
+ assertTrue(newFields.contains("a"));
+ assertTrue(newFields.contains("b"));
+ assertTrue(newFields.contains("c"));
+ assertTrue(newFields.contains("c.d"));
+ assertTrue(newFields.contains("c.d.e"));
+ assertTrue(newFields.contains("c.d.e.f"));
+ assertTrue(newFields.contains("case"));
+ }
+
+ @Test
+ public void testListFields() {
+ List<String> fields = beanFilter.getBeanFields(SimpleBeanInterface.class, 3);
+ assertTrue(fields.contains("Map"));
+ assertTrue(fields.contains("I"));
+ assertTrue(fields.contains("S"));
+ assertTrue(fields.contains("Style"));
+ assertTrue(fields.contains("List"));
+ assertTrue(fields.contains("BeanList.List"));
+ assertTrue(fields.contains("Map"));
+ assertTrue(fields.contains("BeanMap.List"));
+ assertTrue(fields.contains("BeanMap.BeanMap.BeanMap"));
+ assertFalse(fields.contains("BeanMap.BeanMap.BeanMap.BeanMap"));
+ assertEquals(77, fields.size());
+ // If failed use next prints to verify and fix
+ // System.out.println(fields.size());
+ // System.out.println(fields.toString());
+ }
+}
Modified: shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/GadgetsHandler.java
URL: http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/GadgetsHandler.java?rev=982106&r1=982105&r2=982106&view=diff
==============================================================================
--- shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/GadgetsHandler.java (original)
+++ shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/GadgetsHandler.java Wed Aug 4 02:41:30 2010
@@ -31,16 +31,21 @@ import org.apache.shindig.gadgets.Gadget
import org.apache.shindig.gadgets.GadgetContext;
import org.apache.shindig.gadgets.RenderingContext;
import org.apache.shindig.gadgets.process.Processor;
+import org.apache.shindig.gadgets.spec.Feature;
import org.apache.shindig.gadgets.spec.GadgetSpec;
+import org.apache.shindig.gadgets.spec.LinkSpec;
import org.apache.shindig.gadgets.spec.ModulePrefs;
import org.apache.shindig.gadgets.spec.UserPref;
import org.apache.shindig.gadgets.spec.View;
+import org.apache.shindig.gadgets.spec.UserPref.EnumValuePair;
import org.apache.shindig.gadgets.uri.IframeUriManager;
import org.apache.shindig.protocol.BaseRequestItem;
import org.apache.shindig.protocol.Operation;
import org.apache.shindig.protocol.ProtocolException;
import org.apache.shindig.protocol.RequestItem;
import org.apache.shindig.protocol.Service;
+import org.apache.shindig.protocol.conversion.BeanDelegator;
+import org.apache.shindig.protocol.conversion.BeanFilter;
import java.util.Locale;
import java.util.Map;
@@ -60,59 +65,98 @@ public class GadgetsHandler {
@VisibleForTesting
static final String FAILURE_TOKEN = "Failed to get gadget token.";
- private static final Set<String> ALL_METADATA_FIELDS = ImmutableSet.of(
- "iframeUrl", "userPrefs", "modulePrefs", "views", "views.name", "views.type",
- "views.type", "views.href", "views.quirks", "views.content",
- "views.preferredHeight", "views.preferredWidth",
- "views.needsUserPrefsSubstituted", "views.attributes");
- private static final Set<String> DEFAULT_METADATA_FIELDS = ImmutableSet.of(
- "iframeUrl", "userPrefs", "modulePrefs", "views");
+ private static final Set<String> DEFAULT_METADATA_FIELDS =
+ ImmutableSet.of("iframeUrl", "userPrefs.*", "modulePrefs.*", "views.*", "token");
protected final ExecutorService executor;
protected final Processor processor;
protected final IframeUriManager iframeUriManager;
protected final SecurityTokenCodec securityTokenCodec;
+ protected final BeanDelegator beanDelegator;
+ protected final BeanFilter beanFilter;
+
+ // Map shindig data class to API interfaces
+ @VisibleForTesting
+ static final Map<Class<?>, Class<?>> apiClasses =
+ new ImmutableMap.Builder<Class<?>, Class<?>>()
+ .put(BaseResponseData.class, GadgetsHandlerApi.BaseResponse.class)
+ .put(MetadataResponseData.class, GadgetsHandlerApi.MetadataResponse.class)
+ .put(TokenResponseData.class, GadgetsHandlerApi.TokenResponse.class)
+ .put(View.class, GadgetsHandlerApi.View.class)
+ .put(UserPref.class, GadgetsHandlerApi.UserPref.class)
+ .put(EnumValuePair.class, GadgetsHandlerApi.EnumValuePair.class)
+ .put(ModulePrefs.class, GadgetsHandlerApi.ModulePrefs.class)
+ .put(Feature.class, GadgetsHandlerApi.Feature.class)
+ .put(LinkSpec.class, GadgetsHandlerApi.LinkSpec.class)
+ // Enums
+ .put(View.ContentType.class, GadgetsHandlerApi.ViewContentType.class)
+ .put(UserPref.DataType.class, GadgetsHandlerApi.UserPrefDataType.class)
+ .build();
+
+ // Provide mapping for internal enums to api enums
+ @VisibleForTesting
+ static final Map<Enum<?>, Enum<?>> enumConversionMap =
+ new ImmutableMap.Builder<Enum<?>, Enum<?>>()
+ // View.ContentType mapping
+ .putAll(BeanDelegator.createDefaultEnumMap(View.ContentType.class,
+ GadgetsHandlerApi.ViewContentType.class))
+ // UserPref.DataType mapping
+ .putAll(BeanDelegator.createDefaultEnumMap(UserPref.DataType.class,
+ GadgetsHandlerApi.UserPrefDataType.class))
+ .build();
+
@Inject
- public GadgetsHandler(
- ExecutorService executor,
- Processor processor,
- IframeUriManager iframeUriManager,
- SecurityTokenCodec securityTokenCodec) {
+ public GadgetsHandler(ExecutorService executor, Processor processor,
+ IframeUriManager iframeUriManager, SecurityTokenCodec securityTokenCodec,
+ BeanFilter beanFilter) {
this.executor = executor;
this.processor = processor;
this.iframeUriManager = iframeUriManager;
this.securityTokenCodec = securityTokenCodec;
+ this.beanFilter = beanFilter;
+
+ // TODO: maybe make this injectable
+ this.beanDelegator = new BeanDelegator(apiClasses, enumConversionMap);
}
@Operation(httpMethods = {"POST", "GET"}, path = "metadata.get")
- public Map<String, MetadataResponse> metadata(BaseRequestItem request)
+ public Map<String, GadgetsHandlerApi.MetadataResponse> metadata(BaseRequestItem request)
throws ProtocolException {
- return new AbstractExecutor<MetadataResponse>() {
+ return new AbstractExecutor<GadgetsHandlerApi.MetadataResponse>() {
@Override
- protected Callable<MetadataResponse> createJob(String url, BaseRequestItem request) {
+ protected Callable<GadgetsHandlerApi.MetadataResponse> createJob(String url,
+ BaseRequestItem request) {
return createMetadataJob(url, request);
}
}.execute(request);
}
@Operation(httpMethods = {"POST", "GET"}, path = "token.get")
- public Map<String, TokenResponse> token(BaseRequestItem request)
+ public Map<String, GadgetsHandlerApi.TokenResponse> token(BaseRequestItem request)
throws ProtocolException {
- return new AbstractExecutor<TokenResponse>() {
+ return new AbstractExecutor<GadgetsHandlerApi.TokenResponse>() {
@Override
- protected Callable<TokenResponse> createJob(String url, BaseRequestItem request) {
+ protected Callable<GadgetsHandlerApi.TokenResponse> createJob(String url,
+ BaseRequestItem request) {
return createTokenJob(url, request);
}
}.execute(request);
}
- @Operation(httpMethods = "GET", path="/@metadata.supportedFields")
+ @Operation(httpMethods = "GET", path = "/@metadata.supportedFields")
public Set<String> supportedFields(RequestItem request) {
- return ALL_METADATA_FIELDS;
+ return ImmutableSet.copyOf(beanFilter
+ .getBeanFields(GadgetsHandlerApi.MetadataResponse.class, 5));
+ }
+
+ @Operation(httpMethods = "GET", path = "/@token.supportedFields")
+ public Set<String> tokenSupportedFields(RequestItem request) {
+ return ImmutableSet.copyOf(
+ beanFilter.getBeanFields(GadgetsHandlerApi.TokenResponse.class, 5));
}
- private abstract class AbstractExecutor<R extends BaseResponse> {
+ private abstract class AbstractExecutor<R extends GadgetsHandlerApi.BaseResponse> {
@SuppressWarnings("unchecked")
public Map<String, R> execute(BaseRequestItem request) {
Set<String> gadgetUrls = ImmutableSet.copyOf(request.getListParameter("ids"));
@@ -133,20 +177,19 @@ public class GadgetsHandler {
response = completionService.take().get();
builder.put(response.getUrl(), response);
} catch (InterruptedException e) {
- throw new ProtocolException(
- HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
+ throw new ProtocolException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
"Processing interrupted.", e);
} catch (ExecutionException e) {
if (!(e.getCause() instanceof RpcException)) {
- throw new ProtocolException(
- HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
+ throw new ProtocolException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
"Processing error.", e);
}
RpcException cause = (RpcException) e.getCause();
GadgetContext context = cause.getContext();
if (context != null) {
String url = context.getUrl().toString();
- R errorResponse = (R) new BaseResponse(url, cause.getMessage());
+ R errorResponse =
+ (R) beanDelegator.createDelegator(new BaseResponseData(url, cause.getMessage()));
builder.put(url, errorResponse);
}
}
@@ -158,17 +201,22 @@ public class GadgetsHandler {
}
// Hook to override in sub-class.
- protected Callable<MetadataResponse> createMetadataJob(String url, BaseRequestItem request) {
+ protected Callable<GadgetsHandlerApi.MetadataResponse> createMetadataJob(String url,
+ BaseRequestItem request) {
final GadgetContext context = new MetadataGadgetContext(url, request);
- final Set<String> fields = request.getFields(DEFAULT_METADATA_FIELDS);
- return new Callable<MetadataResponse>() {
- public MetadataResponse call() throws Exception {
+ final Set<String> fields =
+ beanFilter.processBeanFields(request.getFields(DEFAULT_METADATA_FIELDS));
+ return new Callable<GadgetsHandlerApi.MetadataResponse>() {
+ public GadgetsHandlerApi.MetadataResponse call() throws Exception {
try {
Gadget gadget = processor.process(context);
- String iframeUrl = fields.contains("iframeUrl")
- ? iframeUriManager.makeRenderingUri(gadget).toString() : null;
- return new MetadataResponse(context.getUrl().toString(), gadget.getSpec(),
- iframeUrl, fields);
+ String iframeUrl =
+ fields.contains("iframeurl") ? iframeUriManager.makeRenderingUri(gadget).toString()
+ : null;
+ MetadataResponseData response =
+ new MetadataResponseData(context.getUrl().toString(), gadget.getSpec(), iframeUrl);
+ return (GadgetsHandlerApi.MetadataResponse) beanFilter.createFilteredBean(beanDelegator
+ .createDelegator(response), fields);
} catch (Exception e) {
// Note: this error message is publicly visible in JSON-RPC response.
throw new RpcException(context, FAILURE_METADATA, e);
@@ -178,13 +226,18 @@ public class GadgetsHandler {
}
// Hook to override in sub-class.
- protected Callable<TokenResponse> createTokenJob(String url, BaseRequestItem request) {
+ protected Callable<GadgetsHandlerApi.TokenResponse> createTokenJob(String url,
+ BaseRequestItem request) {
final TokenGadgetContext context = new TokenGadgetContext(url, request);
- return new Callable<TokenResponse>() {
- public TokenResponse call() throws Exception {
+ final Set<String> fields =
+ beanFilter.processBeanFields(request.getFields(DEFAULT_METADATA_FIELDS));
+ return new Callable<GadgetsHandlerApi.TokenResponse>() {
+ public GadgetsHandlerApi.TokenResponse call() throws Exception {
try {
String token = securityTokenCodec.encodeToken(context.getToken());
- return new TokenResponse(context.getUrl().toString(), token);
+ TokenResponseData response = new TokenResponseData(context.getUrl().toString(), token);
+ return (GadgetsHandlerApi.TokenResponse) beanFilter.createFilteredBean(beanDelegator
+ .createDelegator(response), fields);
} catch (Exception e) {
// Note: this error message is publicly visible in JSON-RPC response.
throw new RpcException(context, FAILURE_TOKEN, e);
@@ -194,8 +247,8 @@ public class GadgetsHandler {
}
/**
- * Gadget context classes used to translate JSON BaseRequestItem into a
- * more meaningful model objects that Java can work with.
+ * Gadget context classes used to translate JSON BaseRequestItem into a more
+ * meaningful model objects that Java can work with.
*/
private abstract class AbstractGadgetContext extends GadgetContext {
@@ -234,9 +287,9 @@ public class GadgetsHandler {
super(url, request);
String lang = request.getParameter("language");
String country = request.getParameter("country");
- this.locale = (lang != null && country != null) ? new Locale(lang,country) :
- (lang != null) ? new Locale(lang) :
- GadgetSpec.DEFAULT_LOCALE;
+ this.locale =
+ (lang != null && country != null) ? new Locale(lang, country) : (lang != null)
+ ? new Locale(lang) : GadgetSpec.DEFAULT_LOCALE;
this.ignoreCache = Boolean.valueOf(request.getParameter("ignoreCache"));
this.debug = Boolean.valueOf(request.getParameter("debug"));
}
@@ -288,18 +341,18 @@ public class GadgetsHandler {
* container JS. They must be public for reflection to work.
*/
- public static class BaseResponse {
+ public static class BaseResponseData {
private final String url;
private final String error;
// Call this to indicate an error.
- public BaseResponse(String url, String error) {
+ public BaseResponseData(String url, String error) {
this.url = url;
this.error = error;
}
// Have sub-class call this to indicate a success response.
- protected BaseResponse(String url) {
+ protected BaseResponseData(String url) {
this(url, null);
}
@@ -312,118 +365,42 @@ public class GadgetsHandler {
}
}
- public static class MetadataResponse extends BaseResponse {
+ public static class MetadataResponseData extends BaseResponseData {
private final GadgetSpec spec;
private final String iframeUrl;
- private final Map<String, ViewResponse> views;
- private final Set<String> fields;
- public MetadataResponse(String url, GadgetSpec spec, String iframeUrl, Set<String> fields) {
+ public MetadataResponseData(String url, GadgetSpec spec, String iframeUrl) {
super(url);
this.spec = spec;
this.iframeUrl = iframeUrl;
- this.fields = fields;
-
- // Do we need view data?
- boolean viewsRequested = fields.contains("views");
- for (String f: fields) {
- if (f.startsWith("views")) {
- viewsRequested = true;
- }
- }
- if (viewsRequested) {
- ImmutableMap.Builder<String, ViewResponse> builder = ImmutableMap.builder();
- for (Map.Entry<String,View> entry : spec.getViews().entrySet()) {
- builder.put(entry.getKey(), new ViewResponse(entry.getValue(), fields));
- }
- views = builder.build();
- } else {
- views = null;
- }
}
public String getIframeUrl() {
- return fields.contains("iframeUrl") ? iframeUrl : null;
+ return iframeUrl;
}
public String getChecksum() {
- return fields.contains("checksum") ? spec.getChecksum() : null;
+ return spec.getChecksum();
}
public ModulePrefs getModulePrefs() {
- return fields.contains("modulePrefs") ? spec.getModulePrefs() : null;
+ return spec.getModulePrefs();
}
public Map<String, UserPref> getUserPrefs() {
- return fields.contains("userPrefs") ? spec.getUserPrefs() : null;
+ return spec.getUserPrefs();
}
- public Map<String, ViewResponse> getViews() {
- return views;
+ public Map<String, View> getViews() {
+ return spec.getViews();
}
}
- public static class ViewResponse {
- private final View view;
- private final Set<String> fields;
-
- /**
- * Return the actual item if the requested fields contains "views" or param
- * @param item any item
- * @param param a field to test for
- * @param <T> any type
- * @return Returns item if fields contains "views" or param
- */
- private <T> T filter(T item, String param) {
- return (fields.contains("views") || fields.contains(param)) ? item : null;
- }
-
- public ViewResponse(View view, Set<String> fields) {
- this.view = view;
- this.fields = fields;
- }
-
- public String getName() {
- return filter(view.getName(), "views.name");
- }
-
- public View.ContentType getType() {
- return filter(view.getType(), "views.type");
- }
-
- public Uri getHref() {
- return filter(view.getHref(), "views.href");
- }
-
- public Boolean getQuirks() {
- return filter(view.getQuirks(), "views.quirks");
- }
-
- public String getContent() {
- return fields.contains("views.content") ? view.getContent() : null;
- }
-
- public Integer getPreferredHeight() {
- return filter(view.getPreferredHeight(), "views.preferredHeight");
- }
-
- public Integer getPreferredWidth() {
- return filter(view.getPreferredWidth(), "views.preferredWidth");
- }
-
- public Boolean needsUserPrefSubstitution() {
- return filter(view.needsUserPrefSubstitution(), "views.needsUserPrefSubstitution");
- }
-
- public Map<String, String> getAttributes() {
- return filter(view.getAttributes(), "views.attributes");
- }
- }
- public static class TokenResponse extends BaseResponse {
+ public static class TokenResponseData extends BaseResponseData {
private final String token;
- public TokenResponse(String url, String token) {
+ public TokenResponseData(String url, String token) {
super(url);
this.token = token;
}
@@ -433,4 +410,4 @@ public class GadgetsHandler {
}
}
-}
\ No newline at end of file
+}
Added: shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/GadgetsHandlerApi.java
URL: http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/GadgetsHandlerApi.java?rev=982106&view=auto
==============================================================================
--- shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/GadgetsHandlerApi.java (added)
+++ shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/GadgetsHandlerApi.java Wed Aug 4 02:41:30 2010
@@ -0,0 +1,136 @@
+/*
+ * 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.servlet;
+
+import org.apache.shindig.common.uri.Uri;
+import org.apache.shindig.protocol.conversion.BeanFilter.Required;
+// Keep imports clean, so it is clear what is used by API
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Gadget Handler Interface data.
+ * Classes in here specify the API data.
+ * Please do not reference run time classes, instead create new interface (keep imports clean!).
+ * Please avoid changes if possible, you might break external system that depend on the API.
+ */
+public class GadgetsHandlerApi {
+
+ public interface BaseResponse {
+ @Required
+ public String getUrl();
+ @Required
+ public String getError();
+ }
+
+ public interface MetadataResponse extends BaseResponse {
+ public String getIframeUrl();
+ public String getChecksum();
+ public ModulePrefs getModulePrefs();
+ public Map<String, UserPref> getUserPrefs();
+ public Map<String, View> getViews();
+ }
+
+ public enum ViewContentType {
+ HTML("html"), URL("url"), HTML_SANITIZED("x-html-sanitized");
+
+ private final String name;
+ private ViewContentType(String name) {
+ this.name = name;
+ }
+ @Override
+ public String toString() {
+ return name;
+ }
+ }
+
+ public interface View {
+ public String getName();
+ public ViewContentType getType();
+ public Uri getHref();
+ public boolean getQuirks();
+ public String getContent();
+ public int getPreferredHeight();
+ public int getPreferredWidth();
+ public boolean needsUserPrefSubstitution();
+ public Map<String, String> getAttributes();
+ }
+
+ public enum UserPrefDataType {
+ STRING, HIDDEN, BOOL, ENUM, LIST, NUMBER;
+ }
+
+ public interface UserPref {
+ public String getName();
+ public String getDisplayName();
+ public String getDefaultValue();
+ public boolean getRequired();
+ public UserPrefDataType getDataType();
+ public Map<String, String> getEnumValues();
+ public List<EnumValuePair> getOrderedEnumValues();
+ }
+
+ public interface EnumValuePair {
+ public String getValue();
+ public String getDisplayValue();
+ }
+
+ public interface ModulePrefs {
+ public String getTitle();
+ public Uri getTitleUrl();
+ public String getDescription();
+ public String getAuthor();
+ public String getAuthorEmail();
+ public Uri getScreenshot();
+ public Uri getThumbnail();
+ public String getDirectoryTitle();
+ public String getAuthorAffiliation();
+ public String getAuthorLocation();
+ public Uri getAuthorPhoto();
+ public String getAuthorAboutme();
+ public String getAuthorQuote();
+ public Uri getAuthorLink();
+ public boolean getScaling();
+ public boolean getScrolling();
+ public int getWidth();
+ public int getHeight();
+ public List<String> getCategories();
+ public Map<String, Feature> getFeatures();
+ public Map<String, LinkSpec> getLinks();
+ // TODO: Provide better interface for locale if needed
+ // public Map<Locale, LocaleSpec> getLocales();
+ }
+
+ public interface Feature {
+ public String getName();
+ public boolean getRequired();
+ // TODO: Handle multi map if params are needed
+ // public Multimap<String, String> getParams();
+ }
+
+ public interface LinkSpec {
+ public String getRel();
+ public Uri getHref();
+ }
+
+ public interface TokenResponse extends BaseResponse {
+ public String getToken();
+ }
+}
Modified: shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/servlet/GadgetsHandlerTest.java
URL: http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/servlet/GadgetsHandlerTest.java?rev=982106&r1=982105&r2=982106&view=diff
==============================================================================
--- shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/servlet/GadgetsHandlerTest.java (original)
+++ shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/servlet/GadgetsHandlerTest.java Wed Aug 4 02:41:30 2010
@@ -33,6 +33,8 @@ import org.apache.shindig.protocol.Defau
import org.apache.shindig.protocol.HandlerExecutionListener;
import org.apache.shindig.protocol.HandlerRegistry;
import org.apache.shindig.protocol.RpcHandler;
+import org.apache.shindig.protocol.conversion.BeanDelegator;
+import org.apache.shindig.protocol.conversion.BeanFilter;
import org.apache.shindig.protocol.conversion.BeanJsonConverter;
import org.apache.shindig.protocol.multipart.FormDataItem;
import org.easymock.EasyMock;
@@ -73,7 +75,8 @@ public class GadgetsHandlerTest extends
private void registerGadgetsHandler(SecurityTokenCodec codec) {
GadgetsHandler handler =
- new GadgetsHandler(new TestExecutorService(), processor, urlGenerator, codec);
+ new GadgetsHandler(new TestExecutorService(), processor, urlGenerator, codec,
+ new BeanFilter());
registry = new DefaultHandlerRegistry(
injector, converter, new HandlerExecutionListener.NoOpHandler());
registry.addHandlers(ImmutableSet.<Object> of(handler));
@@ -272,4 +275,16 @@ public class GadgetsHandlerTest extends
assertNotNull("got gadget2", gadget2);
assertEquals(GadgetsHandler.FAILURE_METADATA, gadget2.getString("error"));
}
+
+ // Next test verify that the API data classes are configured correctly.
+ // The mapping is done using reflection in runtime, so this test verify mapping is complete
+ // this test will prevent from not intended change to the API.
+ // DO NOT REMOVE TEST
+ @Test
+ public void testHandlerDataDelegation() throws Exception {
+ BeanDelegator delegator = new BeanDelegator(
+ GadgetsHandler.apiClasses, GadgetsHandler.enumConversionMap);
+ delegator.validate();
+ }
+
}