diff --git a/apps/showcase/src/main/java/org/apache/struts2/showcase/integration/GangsterForm.java b/apps/showcase/src/main/java/org/apache/struts2/showcase/integration/GangsterForm.java index c1151b254..477135f48 100644 --- a/apps/showcase/src/main/java/org/apache/struts2/showcase/integration/GangsterForm.java +++ b/apps/showcase/src/main/java/org/apache/struts2/showcase/integration/GangsterForm.java @@ -6,8 +6,9 @@ import org.apache.struts.action.ActionErrors; import org.apache.struts.action.ActionForm; import org.apache.struts.action.ActionMapping; import org.apache.struts.action.ActionMessage; +import org.apache.struts.validator.ValidatorForm; -public class GangsterForm extends ActionForm { +public class GangsterForm extends ValidatorForm { private String name; private String age; @@ -26,11 +27,12 @@ public class GangsterForm extends ActionForm { * @see org.apache.struts.action.ActionForm#validate(org.apache.struts.action.ActionMapping, javax.servlet.http.HttpServletRequest) */ @Override - public ActionErrors validate(ActionMapping arg0, HttpServletRequest arg1) { - ActionErrors errors = new ActionErrors(); + public ActionErrors validate(ActionMapping mapping, HttpServletRequest request) { + ActionErrors errors = super.validate(mapping, request); if (name == null || name.length() == 0) { errors.add("name", new ActionMessage("The name must not be blank")); } + return errors; } diff --git a/apps/showcase/src/main/resources/struts-integration.xml b/apps/showcase/src/main/resources/struts-integration.xml index 552d054e1..e6fdbe647 100644 --- a/apps/showcase/src/main/resources/struts-integration.xml +++ b/apps/showcase/src/main/resources/struts-integration.xml @@ -10,6 +10,10 @@ org.apache.struts2.showcase.integration.GangsterForm + gangsterForm + + + /org/apache/struts/validator/validator-rules.xml,/WEB-INF/validation.xml @@ -18,7 +22,7 @@ - + diff --git a/apps/showcase/src/main/webapp/WEB-INF/validation.xml b/apps/showcase/src/main/webapp/WEB-INF/validation.xml new file mode 100644 index 000000000..284975364 --- /dev/null +++ b/apps/showcase/src/main/webapp/WEB-INF/validation.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + +
+ + + +
+ +
+ +
diff --git a/integration/src/main/java/org/apache/struts2/s1/ActionFormValidationInterceptor.java b/integration/src/main/java/org/apache/struts2/s1/ActionFormValidationInterceptor.java index b9dbe5178..bc92f1e5f 100644 --- a/integration/src/main/java/org/apache/struts2/s1/ActionFormValidationInterceptor.java +++ b/integration/src/main/java/org/apache/struts2/s1/ActionFormValidationInterceptor.java @@ -18,15 +18,36 @@ package org.apache.struts2.s1; +import java.io.IOException; +import java.net.URL; +import java.util.ArrayList; +import java.util.List; +import java.util.StringTokenizer; + +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.UnavailableException; import javax.servlet.http.HttpServletRequest; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.commons.validator.ValidatorResources; +import org.apache.struts.Globals; import org.apache.struts.action.ActionErrors; import org.apache.struts.action.ActionForm; import org.apache.struts.action.ActionMapping; +import org.apache.struts.action.ActionServlet; +import org.apache.struts.config.ModuleConfig; +import org.apache.struts.validator.ValidatorPlugIn; import org.apache.struts2.ServletActionContext; +import org.apache.struts2.StrutsException; import org.apache.struts2.dispatcher.Dispatcher; +import org.apache.struts2.util.ServletContextAware; +import org.xml.sax.SAXException; +import com.opensymphony.xwork2.ActionContext; import com.opensymphony.xwork2.ActionInvocation; +import com.opensymphony.xwork2.TextProvider; import com.opensymphony.xwork2.interceptor.AbstractInterceptor; import com.opensymphony.xwork2.interceptor.ScopedModelDriven; @@ -37,8 +58,52 @@ import com.opensymphony.xwork2.interceptor.ScopedModelDriven; */ public class ActionFormValidationInterceptor extends AbstractInterceptor { + private String pathnames; + private boolean stopOnFirstError; + private boolean initialized = false; + + private static final Log log = LogFactory.getLog(ActionFormValidationInterceptor.class); + + /** + * Delimitter for Validator resources. + */ + private final static String RESOURCE_DELIM = ","; + + /** + * Initializes the validation resources + */ + private void initResources(ServletContext servletContext) { + if (pathnames != null) { + ActionContext ctx = ActionContext.getContext(); + try { + + ValidatorResources resources = this.loadResources(servletContext); + + + String prefix = ctx.getActionInvocation().getProxy().getNamespace(); + + + servletContext.setAttribute(ValidatorPlugIn.VALIDATOR_KEY + prefix, resources); + + servletContext.setAttribute(ValidatorPlugIn.STOP_ON_ERROR_KEY + '.' + + prefix, + (this.stopOnFirstError ? Boolean.TRUE : Boolean.FALSE)); + } catch (Exception e) { + throw new StrutsException( + "Cannot load a validator resource from '" + pathnames + "'", e); + } + } + } + @Override public String intercept(ActionInvocation invocation) throws Exception { + // Lazy load the resources because the servlet context isn't available at init() time + synchronized (this) { + if (!initialized) { + initResources(ServletActionContext.getServletContext()); + initialized = true; + } + } Object action = invocation.getAction(); @@ -47,13 +112,114 @@ public class ActionFormValidationInterceptor extends AbstractInterceptor { ScopedModelDriven modelDriven = (ScopedModelDriven) action; Object model = modelDriven.getModel(); if (model != null) { + HttpServletRequest req = ServletActionContext.getRequest(); Struts1Factory strutsFactory = new Struts1Factory(Dispatcher.getInstance().getConfigurationManager().getConfiguration()); ActionMapping mapping = strutsFactory.createActionMapping(invocation.getProxy().getConfig()); - HttpServletRequest req = ServletActionContext.getRequest(); - ActionErrors errors = ((ActionForm)model).validate(mapping, req); + ModuleConfig moduleConfig = strutsFactory.createModuleConfig(invocation.getProxy().getConfig().getPackageName()); + req.setAttribute(Globals.MODULE_KEY, moduleConfig); + req.setAttribute(Globals.MESSAGES_KEY, new WrapperMessageResources((TextProvider)invocation.getAction())); + + mapping.setAttribute(modelDriven.getScopeKey()); + + ActionForm form = (ActionForm) model; + form.setServlet(new ActionServlet(){ + public ServletContext getServletContext() { + return ServletActionContext.getServletContext(); + } + }); + ActionErrors errors = form.validate(mapping, req); strutsFactory.convertErrors(errors, action); } } return invocation.invoke(); } + + /** + * Initialize the validator resources for this module. + * + * @throws IOException if an input/output error is encountered + * @throws ServletException if we cannot initialize these resources + */ + protected ValidatorResources loadResources(ServletContext ctx) + throws IOException, ServletException { + if ((pathnames == null) || (pathnames.length() <= 0)) { + return null; + } + + StringTokenizer st = new StringTokenizer(pathnames, RESOURCE_DELIM); + + List urlList = new ArrayList(); + ValidatorResources resources = null; + try { + while (st.hasMoreTokens()) { + String validatorRules = st.nextToken().trim(); + + if (log.isInfoEnabled()) { + log.info("Loading validation rules file from '" + + validatorRules + "'"); + } + + URL input = + ctx.getResource(validatorRules); + + // If the config isn't in the servlet context, try the class + // loader which allows the config files to be stored in a jar + if (input == null) { + input = getClass().getResource(validatorRules); + } + + if (input != null) { + urlList.add(input); + } else { + throw new ServletException( + "Skipping validation rules file from '" + + validatorRules + "'. No url could be located."); + } + } + + int urlSize = urlList.size(); + String[] urlArray = new String[urlSize]; + + for (int urlIndex = 0; urlIndex < urlSize; urlIndex++) { + URL url = (URL) urlList.get(urlIndex); + + urlArray[urlIndex] = url.toExternalForm(); + } + + resources = new ValidatorResources(urlArray); + } catch (SAXException sex) { + log.error("Skipping all validation", sex); + throw new StrutsException("Skipping all validation because the validation files cannot be loaded", sex); + } + return resources; + } + + /** + * @return the pathnames + */ + public String getPathnames() { + return pathnames; + } + + /** + * @param pathnames the pathnames to set + */ + public void setPathnames(String pathNames) { + this.pathnames = pathNames; + } + + /** + * @return the stopOnFirstError + */ + public boolean isStopOnFirstError() { + return stopOnFirstError; + } + + /** + * @param stopOnFirstError the stopOnFirstError to set + */ + public void setStopOnFirstError(boolean stopOnFirstError) { + this.stopOnFirstError = stopOnFirstError; + } + } diff --git a/integration/src/main/java/org/apache/struts2/s1/LegacyAction.java b/integration/src/main/java/org/apache/struts2/s1/Struts1Action.java similarity index 91% rename from integration/src/main/java/org/apache/struts2/s1/LegacyAction.java rename to integration/src/main/java/org/apache/struts2/s1/Struts1Action.java index f4ef5da31..99d817e37 100644 --- a/integration/src/main/java/org/apache/struts2/s1/LegacyAction.java +++ b/integration/src/main/java/org/apache/struts2/s1/Struts1Action.java @@ -55,11 +55,12 @@ import com.opensymphony.xwork2.interceptor.ScopedModelDriven; *
  • Most everything else...
  • * */ -public class LegacyAction extends DefaultActionSupport implements ScopedModelDriven { +public class Struts1Action extends DefaultActionSupport implements ScopedModelDriven { private ActionForm actionForm; private String className; private boolean validate; + private String scopeKey; public String execute() throws Exception { ActionContext ctx = ActionContext.getContext(); @@ -73,7 +74,7 @@ public class LegacyAction extends DefaultActionSupport implements ScopedModelDri // We should call setServlet() here, but let's stub that out later - StrutsFactory strutsFactory = new StrutsFactory(Dispatcher.getInstance().getConfigurationManager().getConfiguration()); + Struts1Factory strutsFactory = new Struts1Factory(Dispatcher.getInstance().getConfigurationManager().getConfiguration()); ActionMapping mapping = strutsFactory.createActionMapping(actionConfig); HttpServletRequest request = ServletActionContext.getRequest(); HttpServletResponse response = ServletActionContext.getResponse(); @@ -126,4 +127,12 @@ public class LegacyAction extends DefaultActionSupport implements ScopedModelDri public void setClassName(String className) { this.className = className; } + + public String getScopeKey() { + return scopeKey; + } + + public void setScopeKey(String key) { + this.scopeKey = key; + } } diff --git a/integration/src/main/java/org/apache/struts2/s1/StrutsFactory.java b/integration/src/main/java/org/apache/struts2/s1/Struts1Factory.java similarity index 96% rename from integration/src/main/java/org/apache/struts2/s1/StrutsFactory.java rename to integration/src/main/java/org/apache/struts2/s1/Struts1Factory.java index 533bac735..29bfe2841 100644 --- a/integration/src/main/java/org/apache/struts2/s1/StrutsFactory.java +++ b/integration/src/main/java/org/apache/struts2/s1/Struts1Factory.java @@ -15,7 +15,6 @@ * limitations under the License. * */ - package org.apache.struts2.s1; import com.opensymphony.xwork2.*; @@ -23,22 +22,27 @@ import com.opensymphony.xwork2.config.Configuration; import com.opensymphony.xwork2.config.entities.ActionConfig; import com.opensymphony.xwork2.config.entities.ResultConfig; import com.opensymphony.xwork2.config.entities.ExceptionMappingConfig; + +import org.apache.struts.Globals; import org.apache.struts.action.*; import org.apache.struts.config.*; import java.util.Iterator; import java.util.Arrays; +import java.util.Map; + +import javax.servlet.ServletContext; /** * Provides conversion methods between the Struts Action 1.x and XWork * classes. */ -public class StrutsFactory { +public class Struts1Factory { private Configuration configuration; - public StrutsFactory(Configuration config) { + public Struts1Factory(Configuration config) { this.configuration = config; } @@ -53,7 +57,7 @@ public class StrutsFactory { assert packageName != null; return new WrapperModuleConfig(this, configuration.getPackageConfig(packageName)); } - + /** * Create a Struts 1.x ActionMapping from an XWork ActionConfig. * diff --git a/integration/src/main/java/org/apache/struts2/s1/WrapperActionMapping.java b/integration/src/main/java/org/apache/struts2/s1/WrapperActionMapping.java index 0fa749365..48214481d 100644 --- a/integration/src/main/java/org/apache/struts2/s1/WrapperActionMapping.java +++ b/integration/src/main/java/org/apache/struts2/s1/WrapperActionMapping.java @@ -39,6 +39,7 @@ class WrapperActionMapping extends ActionMapping { private ActionConfig delegate; private String actionPath; + private String attribute; private Struts1Factory strutsFactory; public WrapperActionMapping(Struts1Factory factory, ActionConfig delegate) { @@ -135,11 +136,11 @@ class WrapperActionMapping extends ActionMapping { } public String getAttribute() { - throw new UnsupportedOperationException("NYI"); + return attribute; } public void setAttribute(String attribute) { - throw new UnsupportedOperationException("Not implemented - immutable"); + this.attribute = attribute; } public String getForward() { diff --git a/integration/src/main/java/org/apache/struts2/s1/WrapperMessageResources.java b/integration/src/main/java/org/apache/struts2/s1/WrapperMessageResources.java new file mode 100644 index 000000000..9146c870d --- /dev/null +++ b/integration/src/main/java/org/apache/struts2/s1/WrapperMessageResources.java @@ -0,0 +1,44 @@ +/* + * $Id$ + * Copyright 2004 The Apache Software Foundation. + * + * Licensed 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.s1; + +import java.util.Locale; + +import org.apache.struts.util.MessageResources; + +import com.opensymphony.xwork2.TextProvider; + +/** + * Wraps the Struts 1 message resources, delegating to Struts 2 resources + */ +public class WrapperMessageResources extends MessageResources { + + private TextProvider textProvider; + + public WrapperMessageResources(TextProvider provider) { + super(null, null, true); + this.textProvider = provider; + } + + @Override + public String getMessage(Locale locale, String key) { + String msg = textProvider.getText(key); + return msg; + } + +} diff --git a/integration/src/test/java/org/apache/struts2/s1/StrutsFactoryTest.java b/integration/src/test/java/org/apache/struts2/s1/Struts1FactoryTest.java similarity index 96% rename from integration/src/test/java/org/apache/struts2/s1/StrutsFactoryTest.java rename to integration/src/test/java/org/apache/struts2/s1/Struts1FactoryTest.java index e08930710..e1441b53d 100644 --- a/integration/src/test/java/org/apache/struts2/s1/StrutsFactoryTest.java +++ b/integration/src/test/java/org/apache/struts2/s1/Struts1FactoryTest.java @@ -23,22 +23,22 @@ import com.opensymphony.xwork2.config.entities.PackageConfig; import com.opensymphony.xwork2.config.entities.ResultConfig; /** - * Test of StrutsFactory, which creates Struts 1.x wrappers around XWork config objects. + * Test of Struts1Factory, which creates Struts 1.x wrappers around XWork config objects. */ -public class StrutsFactoryTest extends TestCase { +public class Struts1FactoryTest extends TestCase { private static final String PACKAGE_NAME = "org/apache/struts2/s1"; - protected StrutsFactory factory = null; + protected Struts1Factory factory = null; protected Configuration config; - public StrutsFactoryTest(String name) throws Exception { + public Struts1FactoryTest(String name) throws Exception { super(name); } public static void main(String args[]) { - junit.textui.TestRunner.run(StrutsFactoryTest.class); + junit.textui.TestRunner.run(Struts1FactoryTest.class); } /** @@ -49,7 +49,7 @@ public class StrutsFactoryTest extends TestCase { ConfigurationProvider provider = new StrutsXmlConfigurationProvider(PACKAGE_NAME + "/test-struts-factory.xml", true); manager.addConfigurationProvider(provider); config = manager.getConfiguration(); - factory = new StrutsFactory(config); + factory = new Struts1Factory(config); } /** @@ -137,7 +137,6 @@ public class StrutsFactoryTest extends TestCase { // These methods are currently not implemented -- replace as functionality is added. assertNYI(mapping, "getInputForward", null); - assertNYI(mapping, "getAttribute", null); assertNYI(mapping, "getForward", null); assertNYI(mapping, "getInclude", null); assertNYI(mapping, "getInput", null);