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 u...@code useInstances} parameter to + * {...@code true}: + * <pre> + * <result-types> + * <result-type name="gxp" class="org.apache.struts2.views.gxp.GxpResult"> + * <param name="useInstances">true</param> + * </result-type> + * </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> + * <result-types> + * <result-type name="gxp" class="org.apache.struts2.views.gxp.GxpResult"/> + * </result-types> + * </pre> + * <p/> + * <p>Or if you want to output XML instead of HTML:</p> + * <p/> + * <pre> + * <result-types> + * <result-type name="gxp" class="org.apache.struts2.views.gxp.GxpResult"> + * <param name="outputXml">true</param> + * </result-type> + * </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> + * <result name="success" type="gxp">/myPackage/MyGxp.gxp</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