You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@struts.apache.org by lu...@apache.org on 2010/01/27 08:44:32 UTC

svn commit: r903559 - in /struts/sandbox/trunk/struts2-gxp-plugin: ./ src/ src/main/ src/main/java/ src/main/java/org/ src/main/java/org/apache/ src/main/java/org/apache/struts2/ src/main/java/org/apache/struts2/views/ src/main/java/org/apache/struts2/...

Author: lukaszlenart
Date: Wed Jan 27 07:44:32 2010
New Revision: 903559

URL: http://svn.apache.org/viewvc?rev=903559&view=rev
Log:
Initial commit of all code donated by Google, all packages were renamed to org.apache.struts

Added:
    struts/sandbox/trunk/struts2-gxp-plugin/
    struts/sandbox/trunk/struts2-gxp-plugin/pom.xml
    struts/sandbox/trunk/struts2-gxp-plugin/src/
    struts/sandbox/trunk/struts2-gxp-plugin/src/main/
    struts/sandbox/trunk/struts2-gxp-plugin/src/main/java/
    struts/sandbox/trunk/struts2-gxp-plugin/src/main/java/org/
    struts/sandbox/trunk/struts2-gxp-plugin/src/main/java/org/apache/
    struts/sandbox/trunk/struts2-gxp-plugin/src/main/java/org/apache/struts2/
    struts/sandbox/trunk/struts2-gxp-plugin/src/main/java/org/apache/struts2/views/
    struts/sandbox/trunk/struts2-gxp-plugin/src/main/java/org/apache/struts2/views/gxp/
    struts/sandbox/trunk/struts2-gxp-plugin/src/main/java/org/apache/struts2/views/gxp/AbstractGxp.java
    struts/sandbox/trunk/struts2-gxp-plugin/src/main/java/org/apache/struts2/views/gxp/AbstractGxpResult.java
    struts/sandbox/trunk/struts2-gxp-plugin/src/main/java/org/apache/struts2/views/gxp/Gxp.java
    struts/sandbox/trunk/struts2-gxp-plugin/src/main/java/org/apache/struts2/views/gxp/GxpInstance.java
    struts/sandbox/trunk/struts2-gxp-plugin/src/main/java/org/apache/struts2/views/gxp/GxpResult.java
    struts/sandbox/trunk/struts2-gxp-plugin/src/main/java/org/apache/struts2/views/gxp/Param.java
    struts/sandbox/trunk/struts2-gxp-plugin/src/test/
    struts/sandbox/trunk/struts2-gxp-plugin/src/test/java/

Added: struts/sandbox/trunk/struts2-gxp-plugin/pom.xml
URL: http://svn.apache.org/viewvc/struts/sandbox/trunk/struts2-gxp-plugin/pom.xml?rev=903559&view=auto
==============================================================================
--- struts/sandbox/trunk/struts2-gxp-plugin/pom.xml (added)
+++ struts/sandbox/trunk/struts2-gxp-plugin/pom.xml Wed Jan 27 07:44:32 2010
@@ -0,0 +1,71 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.apache.struts</groupId>
+        <artifactId>struts2-plugins</artifactId>
+        <version>2.2.0-SNAPSHOT</version>
+    </parent>
+
+    <groupId>org.apache.struts</groupId>
+    <artifactId>struts2-gxp-plugin</artifactId>
+    <packaging>jar</packaging>
+    <name>Struts 2 GXP Plugin</name>
+    <url>http://struts.apache.org</url>
+
+    <scm>
+        <connection>scm:svn:http://svn.apache.org/repos/asf/struts/sandbox/trunk/struts2-gxp-plugin</connection>
+        <developerConnection>scm:svn:https://svn.apache.org/repos/asf/struts/sandbox/trunk/struts2-gxp-plugin</developerConnection>
+        <url>http://svn.apache.org/viewcvs.cgi/struts/sandbox/trunk/struts2-gxp-plugin</url>
+    </scm>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.struts</groupId>
+            <artifactId>struts2-core</artifactId>
+            <version>2.2.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>javax.servlet</groupId>
+            <artifactId>servlet-api</artifactId>
+            <version>2.4</version>
+            <scope>provided</scope>
+        </dependency>
+        <!--
+          Until GXP is added to the Google Maven Repository in early 2010 (http://code.google.com/p/gxp/issues/detail?id=5)
+          Download http://gxp.googlecode.com/files/gxp-0.2.4-beta.jar
+          Run the following at the command line (do not use WindowsPowershell)
+          mvn install:install-file -DgroupId=com.google -DartifactId=gxp -Dversion=0.2.4-BETA -Dpackaging=jar -Dfile=gxp-0.2.4-beta.jar
+        -->
+        <dependency>
+            <groupId>com.google</groupId>
+            <artifactId>gxp</artifactId>
+            <version>0.2.4-BETA</version>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <version>3.8.1</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.google.collections</groupId>
+            <artifactId>google-collections</artifactId>
+            <version>1.0</version>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <defaultGoal>install</defaultGoal>
+        <plugins>
+            <plugin>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <configuration>
+                    <source>1.5</source>
+                    <target>1.5</target>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>

Added: struts/sandbox/trunk/struts2-gxp-plugin/src/main/java/org/apache/struts2/views/gxp/AbstractGxp.java
URL: http://svn.apache.org/viewvc/struts/sandbox/trunk/struts2-gxp-plugin/src/main/java/org/apache/struts2/views/gxp/AbstractGxp.java?rev=903559&view=auto
==============================================================================
--- struts/sandbox/trunk/struts2-gxp-plugin/src/main/java/org/apache/struts2/views/gxp/AbstractGxp.java (added)
+++ struts/sandbox/trunk/struts2-gxp-plugin/src/main/java/org/apache/struts2/views/gxp/AbstractGxp.java Wed Jan 27 07:44:32 2010
@@ -0,0 +1,348 @@
+/*
+ * $Id$
+ *
+ * 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.struts2.views.gxp;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.gxp.base.GxpContext;
+import com.google.gxp.base.MarkupClosure;
+import com.opensymphony.xwork2.ActionContext;
+import com.opensymphony.xwork2.inject.Inject;
+import com.opensymphony.xwork2.util.ValueStack;
+import com.opensymphony.xwork2.util.ValueStackFactory;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Struts2 to GXP adapter. Can be used to write a GXP or create a
+ * {@link MarkupClosure}. Pulls GXP parameters from Struts2 value stack.
+ *
+ * @author Bob Lee
+ */
+public abstract class AbstractGxp<T extends MarkupClosure> {
+
+    ValueStackFactory valueStackFactory;
+    Map defaultValues = new HashMap();
+    List<Param> params;
+    Class gxpClass;
+    Method writeMethod;
+    Method getGxpClosureMethod;
+    boolean hasBodyParam;
+
+    protected AbstractGxp(Class gxpClass) {
+        this(gxpClass, lookupMethodByName(gxpClass, "write"), lookupMethodByName(gxpClass, "getGxpClosure"));
+    }
+
+    protected AbstractGxp(Class gxpClass, Method writeMethod, Method getGxpClosureMethod) {
+        this.gxpClass = gxpClass;
+        this.writeMethod = writeMethod;
+        this.getGxpClosureMethod = getGxpClosureMethod;
+        this.params = lookupParams();
+    }
+
+    /**
+     * Writes GXP. Pulls GXP parameters from Struts2's value stack.
+     */
+    public void write(Appendable out, GxpContext gxpContext) {
+        write(out, gxpContext, null);
+    }
+
+    /**
+     * Writes GXP. Pulls GXP parameters from Struts2's value stack.
+     *
+     * @param overrides parameter map pushed onto the value stack
+     */
+    protected void write(Appendable out, GxpContext gxpContext, Map overrides) {
+        Object[] args = getArgs(out, gxpContext, overrides);
+
+        try {
+            writeMethod.invoke(getGxpInstance(), args);
+        } catch (Exception e) {
+            throw new RuntimeException(createDebugString(args, e), e);
+        }
+    }
+
+    protected Object[] getArgs(Appendable out, GxpContext gxpContext, Map overrides) {
+        List<Object> argList = getArgListFromValueStack(overrides);
+        Object[] args = new Object[argList.size() + 2];
+        args[0] = out;
+        args[1] = gxpContext;
+        int index = 2;
+        for (Iterator<Object> i = argList.iterator(); i.hasNext(); index++) {
+            args[index] = i.next();
+        }
+        return args;
+    }
+
+    /**
+     * @return the object on which to call the write and getGxpClosure methods. If
+     *         the methods are static, this can return {@code null}
+     */
+    protected Object getGxpInstance() {
+        return null;
+    }
+
+    /**
+     * Creates GXP closure. Pulls GXP parameters from Struts 2 value stack.
+     */
+    public T getGxpClosure() {
+        return getGxpClosure(null, null);
+    }
+
+    /**
+     * Creates GXP closure. Pulls GXP parameters from Struts 2 value stack.
+     *
+     * @param body   is pushed onto the stack if this GXP has a
+     *               {@link MarkupClosure} (or subclass) parameter named "body".
+     * @param params comes first on the value stack.
+     */
+    @SuppressWarnings("unchecked")
+    protected T getGxpClosure(T body, Map params) {
+        final Map overrides = getOverrides(body, params);
+
+        Object[] args = getArgListFromValueStack(overrides).toArray();
+
+        try {
+            return (T) getGxpClosureMethod.invoke(getGxpInstance(), args);
+        } catch (IllegalArgumentException e) {
+            throw new RuntimeException(createDebugString(args, e), e);
+        } catch (IllegalAccessException e) {
+            throw new RuntimeException(createDebugString(args, e), e);
+        } catch (InvocationTargetException e) {
+            throw new RuntimeException(createDebugString(args, e), e);
+        }
+    }
+
+    protected Map getOverrides(T body, Map params) {
+        final Map overrides = new HashMap();
+        if (hasBodyParam && body != null) {
+            overrides.put(Param.BODY_PARAM_NAME, body);
+        }
+        if (params != null) {
+            overrides.putAll(params);
+        }
+        return overrides;
+    }
+
+    /**
+     * Iterates over GXP parameters, pulls value from value stack for each
+     * parameter, and appends the values to an argument list which will
+     * be passed to a method on a GXP.
+     *
+     * @param overrides parameter map pushed onto the value stack
+     */
+    List getArgListFromValueStack(Map overrides) {
+
+        ValueStack valueStack = valueStackFactory.createValueStack(ActionContext.getContext().getValueStack());
+
+        // add default values to the bottom of the stack. if no action provides
+        // a getter for a param, the default value will be used.
+        valueStack.getRoot().add(this.defaultValues);
+
+        // push override parameters onto the stack.
+        if (overrides != null && !overrides.isEmpty()) {
+            valueStack.push(overrides);
+        }
+
+        List args = new ArrayList(params.size());
+        for (Param param : getParams()) {
+            try {
+                args.add(valueStack.findValue(param.getName(), param.getType()));
+            } catch (Exception e) {
+                throw new RuntimeException("Exception while finding '" + param.getName() + "'.", e);
+            }
+        }
+
+        return args;
+    }
+
+    /**
+     * Combines parameter names and types into <code>Param</code> objects.
+     */
+    List<Param> lookupParams() {
+        List<Param> params = new ArrayList<Param>();
+
+        List<String> parameterNames = lookupParameterNames();
+        List<Class<?>> parameterTypes = lookupParameterTypes();
+        Iterator<Class<?>> parameterTypeIterator = parameterTypes.iterator();
+
+        // If there are more parameter names than parameter types it means that we are
+        // using instantiable GXPs and there are 1 or more constructor parameters.
+        // Constructor params will always be first in the list, so just drop an appropriate
+        // number of elements from the beginning of the list.
+        if (parameterNames.size() > parameterTypes.size()) {
+            parameterNames = parameterNames.subList(parameterNames.size() - parameterTypes.size(), parameterNames.size());
+        }
+
+        for (String name : parameterNames) {
+            Class paramType = parameterTypeIterator.next();
+            Param param = new Param(gxpClass, name, paramType);
+            params.add(param);
+
+            if (param.isBody()) {
+                hasBodyParam = true;
+            }
+
+            if (param.isOptional()) {
+                defaultValues.put(param.getName(), param.getDefaultValue());
+            }
+        }
+
+        this.defaultValues = Collections.unmodifiableMap(this.defaultValues);
+        return Collections.unmodifiableList(params);
+    }
+
+    /**
+     * Gets list of parameter types.
+     */
+    List<Class<?>> lookupParameterTypes() {
+        List<Class<?>> parameterTypes = Arrays.asList(writeMethod.getParameterTypes());
+        // skip the first two, gxp_out and gxp_context. they are for internal use.
+        return parameterTypes.subList(2, parameterTypes.size());
+    }
+
+    /**
+     * Gets list of parameter names.
+     */
+    List<String> lookupParameterNames() {
+        try {
+            return (List<String>) gxpClass.getMethod("getArgList").invoke(null);
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Returns first method with the given name. Should not be used if the
+     * method is overloaded.
+     */
+    protected static Method lookupMethodByName(Class clazz, String name) {
+        Method[] methods = clazz.getMethods();
+        for (int i = 0; i < methods.length; i++) {
+            if (methods[i].getName().equals(name)) {
+                return methods[i];
+            }
+        }
+        throw new RuntimeException("No " + name + "(...) method found for "
+                + clazz.getName() + ".");
+    }
+
+    public Class getGxpClass() {
+        return this.gxpClass;
+    }
+
+    /**
+     * Returns list of parameters requested by GXP.
+     */
+    public List<Param> getParams() {
+        return params;
+    }
+
+    /**
+     * Returns generated GXP class given an absolute path to a GXP file.
+     * The current implementation assumes that the GXP and generated Java source
+     * file share the same name with different extensions.
+     */
+    @VisibleForTesting
+    public static Class getGxpClassForPath(String gxpPath) {
+        int offset = (gxpPath.charAt(0) == '/') ? 1 : 0;
+        String className = gxpPath.substring(offset, gxpPath.length() - 4).replace('/', '.');
+        try {
+            return getClassLoader().loadClass(className);
+        } catch (ClassNotFoundException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    static ClassLoader getClassLoader() {
+        ClassLoader loader = Thread.currentThread().getContextClassLoader();
+        return (loader == null) ? ClassLoader.getSystemClassLoader() : loader;
+    }
+
+    /**
+     * Creates debug String which can be tacked onto an exception.
+     */
+    String createDebugString(Object[] args, Exception exception) {
+        StringBuffer buffer = new StringBuffer();
+        printExceptionTraceToBuffer(exception, buffer);
+        buffer.append("\nException in GXP: ").append(gxpClass.getName()).append(". Params:");
+        int index = 2;
+        for (Param param : getParams()) {
+            try {
+                Object arg = args[index++];
+                String typesMatch = "n/a (null)";
+                if (arg != null) {
+                    if (doesArgumentTypeMatchParamType(param, arg)) {
+                        typesMatch = "YES";
+                    } else {
+                        typesMatch = "NO";
+                    }
+                }
+                buffer.append("\n  ")
+                        .append(param.toString())
+                        .append(" = ")
+                        .append(arg)
+                        .append("; ")
+                        .append("[types match? ")
+                        .append(typesMatch)
+                        .append("]");
+            } catch (Exception e) {
+                buffer.append(" >Error getting information for param # ").append(index).append("< ");
+            }
+        }
+        buffer.append("\nStack trace: ");
+        return buffer.toString();
+    }
+
+    private void printExceptionTraceToBuffer(Exception e,
+                                             StringBuffer buffer) {
+        StringWriter out = new StringWriter();
+        e.printStackTrace(new PrintWriter(out));
+        buffer.append(out.getBuffer().toString());
+    }
+
+    private boolean doesArgumentTypeMatchParamType(Param param, Object arg) {
+        Class paramType = param.getType();
+        Class<? extends Object> argClass = arg.getClass();
+
+        // TODO(jpelly): Handle all primitive unwrapping (ie, Boolean --> boolean).
+        if (boolean.class.equals(paramType) && Boolean.class.equals(argClass)) {
+            return true;
+        } else if (char.class.equals(paramType) && Character.class.equals(argClass)) {
+            return true;
+        }
+        return paramType.isAssignableFrom(argClass);
+    }
+
+    @Inject
+    public void setValueStackFactory(ValueStackFactory valueStackFactory) {
+        this.valueStackFactory = valueStackFactory;
+    }
+}

Added: struts/sandbox/trunk/struts2-gxp-plugin/src/main/java/org/apache/struts2/views/gxp/AbstractGxpResult.java
URL: http://svn.apache.org/viewvc/struts/sandbox/trunk/struts2-gxp-plugin/src/main/java/org/apache/struts2/views/gxp/AbstractGxpResult.java?rev=903559&view=auto
==============================================================================
--- struts/sandbox/trunk/struts2-gxp-plugin/src/main/java/org/apache/struts2/views/gxp/AbstractGxpResult.java (added)
+++ struts/sandbox/trunk/struts2-gxp-plugin/src/main/java/org/apache/struts2/views/gxp/AbstractGxpResult.java Wed Jan 27 07:44:32 2010
@@ -0,0 +1,126 @@
+/*
+ * $Id$
+ *
+ * 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.struts2.views.gxp;
+
+import com.opensymphony.xwork2.Result;
+import org.apache.struts2.ServletActionContext;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.Writer;
+import java.util.Locale;
+
+/**
+ * The abstract base class for our Struts 2 GXP result type implementation. It
+ * outputs GXP, and pulls GXP parameters from Struts 2's value stack. Implementing
+ * classes have to:
+ * <ol>
+ * <li>Implement <code>execute(ActionInvocation)</code>, which must instruct the
+ * GXP to write itself to the output stream. See {@link GxpResult} for a
+ * sample implementation.</li>
+ * <li>Add a <code>public static final</code> field <b>DEFAULT_PARAM</b> with
+ * the value 'gxpName'. Struts 2 needs this to set the name of your
+ * template into this object.</li>
+ * </ol>
+ * <p/>
+ * <p>If you want to use instantiated GXPs (using the nested
+ * {@code Interface}), you can set the use{@code useInstances} parameter to
+ * {@code true}:
+ * <pre>
+ *     &lt;result-types>
+ *       &lt;result-type name="gxp" class="org.apache.struts2.views.gxp.GxpResult">
+ *         &lt;param name="useInstances">true&lt;/param>
+ *       &lt;/result-type>
+ *     &lt;/result-types>
+ * </pre>
+ * This means that Struts 2 will attempt to instantiate the {@code Interface}
+ * using the {@link com.opensymphony.xwork2.ObjectFactory}. If
+ * {@link com.google.webwork.GuiceWebWorkIntegrationModule} is installed, or
+ * {@link com.google.webwork.ContainerObjectFactory} is set as the static
+ * {@code ObjectFactory} instance, then Guice will be used to instantiate the
+ * GXP instance; otherwise, only GXPs with no constructor parameters will work.
+ *
+ * @author Bob Lee
+ */
+public abstract class AbstractGxpResult implements Result {
+
+    private boolean useInstances = false;
+    private String gxpName;
+
+    public void setGxpName(String gxpName) {
+        this.gxpName = gxpName;
+    }
+
+    protected final String getGxpName() {
+        return gxpName;
+    }
+
+    public void setUseInstances(boolean useInstances) {
+        this.useInstances = useInstances;
+    }
+
+    protected final boolean getUseInstances() {
+        return useInstances;
+    }
+
+    /**
+     * Provides resources necessary to execute a GXP.
+     */
+    protected interface GxpResourceProvider {
+        Writer getWriter() throws IOException;
+
+        Locale getLocale();
+    }
+
+    /**
+     * Uses reasonable defaults to provide resources.
+     */
+    protected static class DefaultProvider implements GxpResourceProvider {
+
+        private final String contentType;
+
+        public DefaultProvider(String contentType) {
+            this.contentType = contentType;
+        }
+
+        public Writer getWriter() throws IOException {
+            setContentType();
+            return ServletActionContext.getResponse().getWriter();
+        }
+
+        public Locale getLocale() {
+            return ServletActionContext.getRequest().getLocale();
+        }
+
+        void setContentType() {
+            HttpServletResponse response = ServletActionContext.getResponse();
+            // set content type if it hasn't already been set.
+            if (response.getContentType() == null || response.getContentType().isEmpty()) {
+                response.setContentType(contentType);
+            }
+            // If no character encoding was set in the content type, default to UTF-8.
+            if (response.getCharacterEncoding() == null || response.getCharacterEncoding().isEmpty()) {
+                response.setCharacterEncoding("UTF-8");
+            }
+        }
+    }
+
+}

Added: struts/sandbox/trunk/struts2-gxp-plugin/src/main/java/org/apache/struts2/views/gxp/Gxp.java
URL: http://svn.apache.org/viewvc/struts/sandbox/trunk/struts2-gxp-plugin/src/main/java/org/apache/struts2/views/gxp/Gxp.java?rev=903559&view=auto
==============================================================================
--- struts/sandbox/trunk/struts2-gxp-plugin/src/main/java/org/apache/struts2/views/gxp/Gxp.java (added)
+++ struts/sandbox/trunk/struts2-gxp-plugin/src/main/java/org/apache/struts2/views/gxp/Gxp.java Wed Jan 27 07:44:32 2010
@@ -0,0 +1,82 @@
+/*
+ * $Id$
+ *
+ * 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.struts2.views.gxp;
+
+import com.google.common.base.Function;
+import com.google.common.collect.MapMaker;
+import com.google.gxp.html.HtmlClosure;
+
+import java.lang.reflect.Method;
+import java.util.Map;
+
+/**
+ * Struts 2 to GXP adapter. Can be used to write a GXP or create
+ * a HtmlClosure. Pulls GXP parameters from Struts 2 value stack.
+ *
+ * @author Bob Lee
+ */
+public class Gxp extends AbstractGxp<HtmlClosure> {
+
+    Gxp(Class gxpClass) {
+        this(gxpClass, lookupMethodByName(gxpClass, "write"), lookupMethodByName(gxpClass, "getGxpClosure"));
+    }
+
+    Gxp(Class gxpClass, Method writeMethod, Method getGxpClosureMethod) {
+        super(gxpClass, writeMethod, getGxpClosureMethod);
+    }
+
+    static final Map<Class, Gxp> classToGxp = new MapMaker().weakKeys().softValues().makeComputingMap(new Function<Class, Gxp>() {
+        public Gxp apply(Class from) {
+            return classToGxp.containsKey(from) ? classToGxp.get(from) : new Gxp(from);
+        }
+    });
+
+    static final Map<String, Gxp> pathToGxp = new MapMaker().softValues().makeComputingMap(new Function<String, Gxp>() {
+        public Gxp apply(String from) {
+            return pathToGxp.containsKey(from) ? pathToGxp.get(from) : getInstance(getGxpClassForPath(from));
+        }
+    });
+
+    /**
+     * Looks up Gxp instance for GXP with given path.
+     */
+    public static Gxp getInstance(String gxpPath) {
+        try {
+            return pathToGxp.get(gxpPath);
+        } catch (RuntimeException e) {
+            if (e.getCause() instanceof ClassNotFoundException) {
+                // Couldn't find or load the GXP class.  Return null.
+                // It would be simpler if classToGxp.create() could return null,
+                // but the contract of ReferenceCache doesn't allow it to.
+                return null;
+            }
+            throw e;
+        }
+    }
+
+    /**
+     * Looks up Gxp instance for the given GXP class.
+     */
+    public static Gxp getInstance(Class gxpClass) {
+        return classToGxp.get(gxpClass);
+    }
+
+}

Added: struts/sandbox/trunk/struts2-gxp-plugin/src/main/java/org/apache/struts2/views/gxp/GxpInstance.java
URL: http://svn.apache.org/viewvc/struts/sandbox/trunk/struts2-gxp-plugin/src/main/java/org/apache/struts2/views/gxp/GxpInstance.java?rev=903559&view=auto
==============================================================================
--- struts/sandbox/trunk/struts2-gxp-plugin/src/main/java/org/apache/struts2/views/gxp/GxpInstance.java (added)
+++ struts/sandbox/trunk/struts2-gxp-plugin/src/main/java/org/apache/struts2/views/gxp/GxpInstance.java Wed Jan 27 07:44:32 2010
@@ -0,0 +1,138 @@
+/*
+ * $Id$
+ *
+ * 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.struts2.views.gxp;
+
+import com.google.common.base.Function;
+import com.google.common.collect.MapMaker;
+import com.opensymphony.xwork2.ObjectFactory;
+import com.opensymphony.xwork2.inject.Inject;
+
+import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Struts 2 to GXP adapter that uses instances of GXP Interfaces, as created by
+ * the {@link ObjectFactory}. Can be used to write a GXP or create a
+ * HtmlClosure. Pulls non-constructor GXP parameters from Struts 2 value stack.
+ *
+ * @author David P. Baker
+ */
+public class GxpInstance extends Gxp {
+
+    private static final Logger logger = Logger.getLogger(GxpInstance.class.getCanonicalName());
+
+    private Class<?> gxpInterface;
+    private Class<?> gxpInstance;
+    private ObjectFactory objectFactory;
+
+    GxpInstance(Class<?> gxpClass) {
+        super(gxpClass,
+                lookupMethodByName(getNestedClass(gxpClass, "Interface"), "write"),
+                lookupMethodByName(getNestedClass(gxpClass, "Interface"), "getGxpClosure"));
+        this.gxpInterface = getNestedClass(gxpClass, "Interface");
+        this.gxpInstance = getNestedClass(gxpClass, "Instance");
+    }
+
+    private static Class<?> getNestedClass(Class<?> clazz, String nestedClassName) {
+        for (Class<?> nested : clazz.getDeclaredClasses()) {
+            if (nestedClassName.equals(nested.getSimpleName())) {
+                return nested;
+            }
+        }
+        throw new IllegalArgumentException(String.format("Cannot find class %s.%s", clazz.getCanonicalName(), nestedClassName));
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p> This implementation uses the {@link ObjectFactory} to try to create an
+     * instance of the {@code Interface} class that is nested within the GXP
+     * class. If that doesn't work, it falls back to trying to use the
+     * {@code ObjectFactory} to create an instance of the nested {@code Instance}
+     * class, in case there is no binding for the {@code Interface}.
+     */
+    @Override
+    protected Object getGxpInstance() {
+        try {
+            return objectFactory.buildBean(gxpInterface, null);
+        } catch (Exception e) {
+            logger.log(
+                    Level.INFO, "Error instantiating {0}; trying {1}",
+                    new Object[]{gxpInterface.getCanonicalName(), gxpInstance.getCanonicalName(),});
+            try {
+                return objectFactory.buildBean(gxpInstance, null);
+            } catch (Exception e1) {
+                throw new RuntimeException(String.format("Error instantiating %s",
+                        gxpInterface.getCanonicalName(), gxpInstance.getCanonicalName()),
+                        e1);
+            }
+        }
+    }
+
+    @Override
+    public Class<?> getGxpClass() {
+        return this.gxpInterface;
+    }
+
+    private static final Map<Class<?>, GxpInstance> classToGxpInstance = new MapMaker().weakKeys().softValues()
+            .makeComputingMap(new Function<Class<?>, GxpInstance>() {
+                public GxpInstance apply(Class<?> from) {
+                    return classToGxpInstance.containsKey(from) ? classToGxpInstance.get(from) : new GxpInstance(from);
+                }
+            });
+
+    private static final Map<String, GxpInstance> pathToGxpInstance = new MapMaker().softValues()
+            .makeComputingMap(new Function<String, GxpInstance>() {
+                public GxpInstance apply(String from) {
+                    return pathToGxpInstance.containsKey(from) ? pathToGxpInstance.get(from) : getInstance(getGxpClassForPath(from));
+                }
+            });
+
+    /**
+     * Looks up Gxp instance for GXP with given path.
+     */
+    public static GxpInstance getInstance(String gxpPath) {
+        try {
+            return pathToGxpInstance.get(gxpPath);
+        } catch (RuntimeException e) {
+            if (e.getCause() instanceof ClassNotFoundException) {
+                // Couldn't find or load the GXP class.  Return null.
+                // It would be simpler if classToGxp.create() could return null,
+                // but the contract of ReferenceCache doesn't allow it to.
+                return null;
+            }
+            throw e;
+        }
+    }
+
+    /**
+     * Looks up {@code GxpInstance} instance for the given GXP class.
+     */
+    public static GxpInstance getInstance(Class<?> gxpClass) {
+        return classToGxpInstance.get(gxpClass);
+    }
+
+    @Inject
+    public void setObjectFactory(ObjectFactory objectFactory) {
+        this.objectFactory = objectFactory;
+    }
+
+}

Added: struts/sandbox/trunk/struts2-gxp-plugin/src/main/java/org/apache/struts2/views/gxp/GxpResult.java
URL: http://svn.apache.org/viewvc/struts/sandbox/trunk/struts2-gxp-plugin/src/main/java/org/apache/struts2/views/gxp/GxpResult.java?rev=903559&view=auto
==============================================================================
--- struts/sandbox/trunk/struts2-gxp-plugin/src/main/java/org/apache/struts2/views/gxp/GxpResult.java (added)
+++ struts/sandbox/trunk/struts2-gxp-plugin/src/main/java/org/apache/struts2/views/gxp/GxpResult.java Wed Jan 27 07:44:32 2010
@@ -0,0 +1,145 @@
+/*
+ * $Id$
+ *
+ * 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.struts2.views.gxp;
+
+import com.google.gxp.base.GxpContext;
+import com.google.gxp.html.HtmlClosure;
+import com.opensymphony.xwork2.ActionInvocation;
+import com.opensymphony.xwork2.inject.Container;
+import com.opensymphony.xwork2.inject.Inject;
+
+import java.io.IOException;
+
+/**
+ * Struts 2 GXP result type implementation. Outputs GXP. Pulls GXP parameters
+ * from Struts 2's value stack.
+ * <p/>
+ * <p>Declare the GXP result type for your package in the xwork.xml file:</p>
+ * <p/>
+ * <pre>
+ *     &lt;result-types>
+ *       &lt;result-type name="gxp" class="org.apache.struts2.views.gxp.GxpResult"/>
+ *     &lt;/result-types>
+ * </pre>
+ * <p/>
+ * <p>Or if you want to output XML instead of HTML:</p>
+ * <p/>
+ * <pre>
+ *     &lt;result-types>
+ *       &lt;result-type name="gxp" class="org.apache.struts2.views.gxp.GxpResult">
+ *         &lt;param name="outputXml">true&lt;/param>
+ *       &lt;/result-type>
+ *     &lt;/result-types>
+ * </pre>
+ * <p/>
+ * <p>Outputting XML changes the content type from text/html to application/xml
+ * and configures the {@link GxpContext} to output XML. This is useful in
+ * situations like specifying the doctype of your GXP to be 'mobile'.</p>
+ * <p/>
+ * <p>Use the GXP result type for the result of an action. For example:</p>
+ * <p/>
+ * <pre>
+ *   &lt;result name="success" type="gxp">/myPackage/MyGxp.gxp&lt;/result>
+ * </pre>
+ *
+ * @author Bob Lee
+ * @see org.apache.struts2.views.gxp.AbstractGxpResult
+ */
+public class GxpResult extends AbstractGxpResult {
+
+    /**
+     * Tells Struts 2 which parameter name to use if it's not already specified.
+     */
+    public static final String DEFAULT_PARAM = "gxpName";
+
+    private Container container;
+    private boolean outputXml = false;
+
+    /**
+     * Whether or not this GXP should output XML.
+     */
+    public void setOutputXml(boolean outputXml) {
+        this.outputXml = outputXml;
+    }
+
+    protected HtmlClosure getGxpClosure() {
+        final Gxp gxp = getUseInstances() ? GxpInstance.getInstance(getGxpName()) : Gxp.getInstance(getGxpName());
+
+        if (null == gxp) {
+            // TODO(lwerner): OGNL or Struts 2 seems to be swallowing this exception
+            // rather than logging or rethrowing it, so you never see this message
+            // TODO(dpb): Is this true now that this work is not done in a setter?
+            throw new NullPointerException("The GXP " + getGxpName()
+                    + " could not be loaded.  This is probably because you have"
+                    + " a typo in your config.");
+        }
+        container.inject(gxp);
+        return new HtmlClosure() {
+            public void write(Appendable out, GxpContext gxpContext) throws IOException {
+                gxp.write(out, gxpContext);
+            }
+        };
+    }
+
+    /**
+     * Tells the GXP to write itself to the output stream.
+     */
+    public void execute(ActionInvocation actionInvocation) {
+        GxpResourceProvider provider = getProvider();
+        try {
+            getGxpClosure().write(provider.getWriter(), new GxpContext(provider.getLocale(), outputXml));
+        } catch (Exception e) {
+            throw new RuntimeException("Exception while rendering "
+                    + getGxpName()
+                    + " coming from "
+                    + actionInvocation.getAction().getClass().getName() + ".",
+                    e);
+        }
+    }
+
+    /**
+     * Gets appropriate provider.
+     */
+    GxpResourceProvider getProvider() {
+        return new HtmlOrXmlProvider(outputXml);
+    }
+
+    /**
+     * Uses reasonable defaults to provide resources.
+     */
+    private static class HtmlOrXmlProvider extends DefaultProvider {
+
+        /**
+         * Default content-type to use for responses.
+         */
+        private static final String HTML_CONTENT_TYPE = "text/html; charset=UTF-8";
+        private static final String XML_CONTENT_TYPE = "application/xml; charset=UTF-8";
+
+        HtmlOrXmlProvider(boolean outputXml) {
+            super(outputXml ? XML_CONTENT_TYPE : HTML_CONTENT_TYPE);
+        }
+    }
+
+    @Inject
+    public void setContainer(Container container) {
+        this.container = container;
+    }
+}

Added: struts/sandbox/trunk/struts2-gxp-plugin/src/main/java/org/apache/struts2/views/gxp/Param.java
URL: http://svn.apache.org/viewvc/struts/sandbox/trunk/struts2-gxp-plugin/src/main/java/org/apache/struts2/views/gxp/Param.java?rev=903559&view=auto
==============================================================================
--- struts/sandbox/trunk/struts2-gxp-plugin/src/main/java/org/apache/struts2/views/gxp/Param.java (added)
+++ struts/sandbox/trunk/struts2-gxp-plugin/src/main/java/org/apache/struts2/views/gxp/Param.java Wed Jan 27 07:44:32 2010
@@ -0,0 +1,96 @@
+/*
+ * $Id$
+ *
+ * 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.struts2.views.gxp;
+
+import com.google.gxp.html.HtmlClosure;
+
+import java.lang.reflect.Method;
+
+/**
+ * @author Bob Lee
+ */
+public class Param {
+
+    public static final String BODY_PARAM_NAME = "body";
+
+    String name;
+    Class type;
+    Class gxpClass;
+    boolean optional;
+    Object defaultValue;
+
+    Param(Class gxpClass, String name, Class type) {
+        this.gxpClass = gxpClass;
+        this.name = name;
+        this.type = type;
+
+        // if you specify a default parameter value in a GXP, a getDefaultXxx()
+        // method will be present.
+        try {
+            Method defaultGetter = gxpClass.getMethod("getDefault" + capitalize(name));
+            this.defaultValue = defaultGetter.invoke(null);
+            this.optional = true;
+        } catch (NoSuchMethodException ignored) {
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private static String capitalize(String s) {
+        if (s.isEmpty()) {
+            return s;
+        }
+        char first = s.charAt(0);
+        char capitalized = Character.toUpperCase(first);
+        return (first == capitalized) ? s : capitalized + s.substring(1);
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public Class getType() {
+        return type;
+    }
+
+    boolean isBody() {
+        return name.equals(BODY_PARAM_NAME) && type.equals(HtmlClosure.class);
+    }
+
+    public String toString() {
+        return "Param[name: " + name
+                + ", type: " + type.getName()
+                + ", optional: " + optional
+                + (optional ? ", defaultValue: " + defaultValue : "")
+                + "]";
+    }
+
+    public boolean isOptional() {
+        return optional;
+    }
+
+    public Object getDefaultValue() {
+        if (!optional)
+            throw new RuntimeException("Parameter '" + name + "' in " + gxpClass.getName() + " is not optional.");
+        return defaultValue;
+    }
+
+}
\ No newline at end of file