From 6e95cf5bf79f433f17bdb33a58787c97bf371c7e Mon Sep 17 00:00:00 2001 From: Aleksandr Mashchenko Date: Sat, 27 Sep 2014 17:50:19 +0300 Subject: [PATCH 01/41] use locale from action context --- .../test/java/org/apache/struts2/views/jsp/TextTagTest.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/src/test/java/org/apache/struts2/views/jsp/TextTagTest.java b/core/src/test/java/org/apache/struts2/views/jsp/TextTagTest.java index dcc6c6535..56e86f74c 100644 --- a/core/src/test/java/org/apache/struts2/views/jsp/TextTagTest.java +++ b/core/src/test/java/org/apache/struts2/views/jsp/TextTagTest.java @@ -115,7 +115,9 @@ public class TextTagTest extends AbstractTagTest { params.add(param2); params.add(param3); - String expected = MessageFormat.format(pattern, params.toArray()); + MessageFormat format = new MessageFormat(pattern, ActionContext.getContext().getLocale()); + String expected = format.format(params.toArray()); + tag.setName(key); tag.doStartTag(); ((Text) tag.component).addParameter(param1); From 61cffe68feda8a4c527d085647e9567b2f689fc7 Mon Sep 17 00:00:00 2001 From: Aleksandr Mashchenko Date: Mon, 13 Oct 2014 20:07:07 +0300 Subject: [PATCH 02/41] improved interceptor that accepts and maps JSON array --- .../apache/struts2/json/JSONInterceptor.java | 22 +++++++- .../org/apache/struts2/json/AnotherBean.java | 32 ++++++++++++ .../struts2/json/JSONInterceptorTest.java | 52 +++++++++++++++++++ .../org/apache/struts2/json/TestAction5.java | 52 +++++++++++++++++++ .../org/apache/struts2/json/json-12.txt | 8 +++ 5 files changed, 165 insertions(+), 1 deletion(-) create mode 100644 plugins/json/src/test/java/org/apache/struts2/json/AnotherBean.java create mode 100644 plugins/json/src/test/java/org/apache/struts2/json/TestAction5.java create mode 100644 plugins/json/src/test/resources/org/apache/struts2/json/json-12.txt diff --git a/plugins/json/src/main/java/org/apache/struts2/json/JSONInterceptor.java b/plugins/json/src/main/java/org/apache/struts2/json/JSONInterceptor.java index 69158bc01..f802af9eb 100644 --- a/plugins/json/src/main/java/org/apache/struts2/json/JSONInterceptor.java +++ b/plugins/json/src/main/java/org/apache/struts2/json/JSONInterceptor.java @@ -87,8 +87,8 @@ public class JSONInterceptor extends AbstractInterceptor { } Object rootObject = null; + final ValueStack stack = invocation.getStack(); if (this.root != null) { - ValueStack stack = invocation.getStack(); rootObject = stack.findValue(this.root); if (rootObject == null) { @@ -100,6 +100,26 @@ public class JSONInterceptor extends AbstractInterceptor { // load JSON object Object obj = JSONUtil.deserialize(request.getReader()); + // JSON array (this.root cannot be null in this case) + if(obj instanceof List && this.root != null) { + String mapKey = this.root; + rootObject = null; + + if(this.root.indexOf('.') != -1) { + mapKey = this.root.substring(this.root.lastIndexOf('.') + 1); + + rootObject = stack.findValue(this.root.substring(0, this.root.lastIndexOf('.'))); + if (rootObject == null) { + throw new RuntimeException("JSON array: Invalid root expression: '" + this.root + "'."); + } + } + + // create a map with a list inside + Map m = new HashMap(); + m.put(mapKey, new ArrayList((List) obj)); + obj = m; + } + if (obj instanceof Map) { Map json = (Map) obj; diff --git a/plugins/json/src/test/java/org/apache/struts2/json/AnotherBean.java b/plugins/json/src/test/java/org/apache/struts2/json/AnotherBean.java new file mode 100644 index 000000000..81ba27d10 --- /dev/null +++ b/plugins/json/src/test/java/org/apache/struts2/json/AnotherBean.java @@ -0,0 +1,32 @@ +package org.apache.struts2.json; + +import java.util.ArrayList; +import java.util.List; + +public class AnotherBean { + private List beans; + + private AnotherBean yetAnotherBean; + + public List getBeans() { + if (this.beans == null) { + this.beans = new ArrayList(); + } + return this.beans; + } + + public void setBeans(List beans) { + this.beans = beans; + } + + public AnotherBean getYetAnotherBean() { + if(this.yetAnotherBean == null) { + this.yetAnotherBean = new AnotherBean(); + } + return yetAnotherBean; + } + + public void setYetAnotherBean(AnotherBean yetAnotherBean) { + this.yetAnotherBean = yetAnotherBean; + } +} diff --git a/plugins/json/src/test/java/org/apache/struts2/json/JSONInterceptorTest.java b/plugins/json/src/test/java/org/apache/struts2/json/JSONInterceptorTest.java index 7836f849a..7bf53d328 100644 --- a/plugins/json/src/test/java/org/apache/struts2/json/JSONInterceptorTest.java +++ b/plugins/json/src/test/java/org/apache/struts2/json/JSONInterceptorTest.java @@ -459,7 +459,59 @@ public class JSONInterceptorTest extends StrutsTestCase { assertEquals(bean2.getDoubleField(), 10.1); assertEquals(bean2.getByteField(), 3); } + + public void testJSONArray() throws Exception { + setRequestContent("json-12.txt"); + this.request.addHeader("content-type", "application/json"); + // interceptor + JSONInterceptor interceptor = new JSONInterceptor(); + interceptor.setRoot("beans"); + TestAction5 action = new TestAction5(); + + this.invocation.setAction(action); + this.invocation.getStack().push(action); + + interceptor.intercept(this.invocation); + + List beans = action.getBeans(); + + assertNotNull(beans); + assertEquals(1, beans.size()); + assertTrue(beans.get(0).isBooleanField()); + assertEquals(beans.get(0).getStringField(), "test"); + assertEquals(beans.get(0).getIntField(), 10); + assertEquals(beans.get(0).getCharField(), 's'); + assertEquals(beans.get(0).getDoubleField(), 10.1); + assertEquals(beans.get(0).getByteField(), 3); + } + + public void testJSONArray2() throws Exception { + setRequestContent("json-12.txt"); + this.request.addHeader("content-type", "application/json"); + + // interceptor + JSONInterceptor interceptor = new JSONInterceptor(); + interceptor.setRoot("anotherBean.yetAnotherBean.beans"); + TestAction5 action = new TestAction5(); + + this.invocation.setAction(action); + this.invocation.getStack().push(action); + + interceptor.intercept(this.invocation); + + List beans = action.getAnotherBean().getYetAnotherBean().getBeans(); + + assertNotNull(beans); + assertEquals(1, beans.size()); + assertTrue(beans.get(0).isBooleanField()); + assertEquals(beans.get(0).getStringField(), "test"); + assertEquals(beans.get(0).getIntField(), 10); + assertEquals(beans.get(0).getCharField(), 's'); + assertEquals(beans.get(0).getDoubleField(), 10.1); + assertEquals(beans.get(0).getByteField(), 3); + } + @Override protected void setUp() throws Exception { super.setUp(); diff --git a/plugins/json/src/test/java/org/apache/struts2/json/TestAction5.java b/plugins/json/src/test/java/org/apache/struts2/json/TestAction5.java new file mode 100644 index 000000000..9c3321a2d --- /dev/null +++ b/plugins/json/src/test/java/org/apache/struts2/json/TestAction5.java @@ -0,0 +1,52 @@ +/* + * $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.json; + +import java.util.ArrayList; +import java.util.List; + +public class TestAction5 { + private List beans; + + private AnotherBean anotherBean; + + public List getBeans() { + if (this.beans == null) { + this.beans = new ArrayList(); + } + return this.beans; + } + + public void setBeans(List beans) { + this.beans = beans; + } + + public AnotherBean getAnotherBean() { + if(this.anotherBean == null) { + this.anotherBean = new AnotherBean(); + } + return anotherBean; + } + + public void setAnotherBean(AnotherBean anotherBean) { + this.anotherBean = anotherBean; + } +} diff --git a/plugins/json/src/test/resources/org/apache/struts2/json/json-12.txt b/plugins/json/src/test/resources/org/apache/struts2/json/json-12.txt new file mode 100644 index 000000000..abd0c7180 --- /dev/null +++ b/plugins/json/src/test/resources/org/apache/struts2/json/json-12.txt @@ -0,0 +1,8 @@ +[{ + "booleanField": true, + "stringField" : "test", + "intField" : 10, + "charField": "s", + "doubleField": 10.1, + "byteField": 3 +}] From 6291fa920d9b89a420def74b5c3a07854eb0e866 Mon Sep 17 00:00:00 2001 From: Michael Stevens Date: Wed, 12 Nov 2014 13:34:17 +0100 Subject: [PATCH 03/41] Fix Memory leak in CDI plugin The CDI plugin was using a single 'CreationalContext' for all beans created by the CDIObjectFactory. This is incorrect and the size of the 'dependentInstances' (in the WELD implementation) grows indefinately with no beans being removed. Fix is to use a new 'CreationalContext' for every bean created. In our tests with WELD this does not effect the applications performance. Idealy a single 'CreationalContext' should be created for the request lifecycle. However the struts ObjectFactory API does not appear to provide any information able the lifecycle. --- .../main/java/org/apache/struts2/cdi/CdiObjectFactory.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/plugins/cdi/src/main/java/org/apache/struts2/cdi/CdiObjectFactory.java b/plugins/cdi/src/main/java/org/apache/struts2/cdi/CdiObjectFactory.java index 22c81d8e5..241a7ab45 100644 --- a/plugins/cdi/src/main/java/org/apache/struts2/cdi/CdiObjectFactory.java +++ b/plugins/cdi/src/main/java/org/apache/struts2/cdi/CdiObjectFactory.java @@ -70,7 +70,6 @@ public class CdiObjectFactory extends ObjectFactory { } protected BeanManager beanManager; - protected CreationalContext ctx; Map, InjectionTarget> injectionTargetCache = new ConcurrentHashMap, InjectionTarget>(); @@ -79,7 +78,6 @@ public class CdiObjectFactory extends ObjectFactory { LOG.info("Initializing Struts2 CDI integration..."); this.beanManager = findBeanManager(); if (beanManager != null) { - this.ctx = buildNonContextualCreationalContext(beanManager); LOG.info("Struts2 CDI integration initialized."); } else { LOG.error("Struts2 CDI integration could not be initialized."); @@ -152,13 +150,16 @@ public class CdiObjectFactory extends ObjectFactory { } @Override - @SuppressWarnings("unchecked") + @SuppressWarnings({ "rawtypes", "unchecked" }) public Object buildBean(String className, Map extraContext, boolean injectInternal) throws Exception { Class clazz = getClassInstance(className); InjectionTarget injectionTarget = getInjectionTarget(clazz); + // a separate CreationalContext is required for every bean + final CreationalContext ctx = buildNonContextualCreationalContext(beanManager); + Object o = injectionTarget.produce(ctx); injectionTarget.inject(o, ctx); injectionTarget.postConstruct(o); From 7bce8ef6a84a2ddf5d79f89337e08bec223c3730 Mon Sep 17 00:00:00 2001 From: zhouyanming Date: Thu, 13 Nov 2014 16:06:00 +0800 Subject: [PATCH 04/41] Defend for NPE when performing async request when I combined spring async mvc with struts2 sitemesh plugin, It will throw NPE java.lang.NullPointerException at org.apache.struts2.ServletActionContext.getRequest(ServletActionContext.java:112) at org.apache.struts2.sitemesh.StrutsSiteMeshFactory.isInsideActionTag(StrutsSiteMeshFactory.java:25) at org.apache.struts2.sitemesh.StrutsSiteMeshFactory.shouldParsePage(StrutsSiteMeshFactory.java:21) at com.opensymphony.sitemesh.compatability.PageParser2ContentProcessor.handles(PageParser2ContentProcessor.java:45) at com.opensymphony.sitemesh.webapp.ContentBufferingResponse$1.shouldParsePage(ContentBufferingResponse.java:29) at com.opensymphony.module.sitemesh.filter.PageResponseWrapper.setContentType(PageResponseWrapper.java:63) at com.opensymphony.sitemesh.webapp.ContentBufferingResponse$2.setContentType(ContentBufferingResponse.java:39) at com.opensymphony.module.sitemesh.filter.PageResponseWrapper.addHeader(PageResponseWrapper.java:135) at javax.servlet.http.HttpServletResponseWrapper.addHeader(HttpServletResponseWrapper.java:173) at org.springframework.http.server.ServletServerHttpResponse.writeHeaders(ServletServerHttpResponse.java:103) at org.springframework.http.server.ServletServerHttpResponse.getBody(ServletServerHttpResponse.java:83) at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.writeInternal(AbstractJackson2HttpMessageConverter.java:217) at org.springframework.http.converter.AbstractHttpMessageConverter.write(AbstractHttpMessageConverter.java:208) at org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor.writeWithMessageConverters(AbstractMessageConverterMethodProcessor.java:161) at org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor.writeWithMessageConverters(AbstractMessageConverterMethodProcessor.java:101) at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.handleReturnValue(RequestResponseBodyMethodProcessor.java:199) at org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite.handleReturnValue(HandlerMethodReturnValueHandlerComposite.java:71) at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:128) at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandleMethod(RequestMappingHandlerAdapter.java:781) at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:721) at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:83) at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:943) at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:877) at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:966) at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:857) at javax.servlet.http.HttpServlet.service(HttpServlet.java:620) at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:842) at javax.servlet.http.HttpServlet.service(HttpServlet.java:727) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:303) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208) at org.apache.catalina.core.ApplicationDispatcher.invoke(ApplicationDispatcher.java:748) at org.apache.catalina.core.ApplicationDispatcher.doDispatch(ApplicationDispatcher.java:659) at org.apache.catalina.core.ApplicationDispatcher.dispatch(ApplicationDispatcher.java:625) at org.apache.catalina.core.AsyncContextImpl$1.run(AsyncContextImpl.java:239) at org.apache.catalina.core.AsyncContextImpl.doInternalDispatch(AsyncContextImpl.java:382) at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:215) at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:122) at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:171) at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:103) at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:116) at org.apache.catalina.connector.CoyoteAdapter.asyncDispatch(CoyoteAdapter.java:299) at org.apache.coyote.http11.AbstractHttp11Processor.asyncDispatch(AbstractHttp11Processor.java:1652) at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:603) at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:314) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615) at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) at java.lang.Thread.run(Thread.java:745) --- .../org/apache/struts2/sitemesh/StrutsSiteMeshFactory.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/sitemesh/src/main/java/org/apache/struts2/sitemesh/StrutsSiteMeshFactory.java b/plugins/sitemesh/src/main/java/org/apache/struts2/sitemesh/StrutsSiteMeshFactory.java index 458cbf7d4..29eb052f6 100644 --- a/plugins/sitemesh/src/main/java/org/apache/struts2/sitemesh/StrutsSiteMeshFactory.java +++ b/plugins/sitemesh/src/main/java/org/apache/struts2/sitemesh/StrutsSiteMeshFactory.java @@ -2,6 +2,7 @@ package org.apache.struts2.sitemesh; import com.opensymphony.module.sitemesh.Config; import com.opensymphony.module.sitemesh.factory.DefaultFactory; +import com.opensymphony.xwork2.ActionContext; import org.apache.commons.lang3.ObjectUtils; import org.apache.struts2.ServletActionContext; import org.apache.struts2.StrutsStatics; @@ -22,6 +23,8 @@ public class StrutsSiteMeshFactory extends DefaultFactory { } private boolean isInsideActionTag() { + if(ActionContext.getContext() == null) + return false; Object attribute = ServletActionContext.getRequest().getAttribute(StrutsStatics.STRUTS_ACTION_TAG_INVOCATION); return (Boolean) ObjectUtils.defaultIfNull(attribute, Boolean.FALSE); } From ae4e3d116b635b257835f0a5b1f67d30bc9472e6 Mon Sep 17 00:00:00 2001 From: Lukasz Lenart Date: Fri, 21 Nov 2014 21:55:40 +0100 Subject: [PATCH 05/41] Reformats source code --- pom.xml | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index 39b9c8e84..8d0937056 100644 --- a/pom.xml +++ b/pom.xml @@ -1,5 +1,6 @@ - + org.apache.struts @@ -31,8 +32,8 @@ scm:git:git://git.apache.org/struts.git scm:git:https://git-wip-us.apache.org/repos/asf/struts.git http://git.apache.org/struts.git - HEAD - + HEAD + JIRA @@ -74,7 +75,7 @@ The Apache Software License, Version 2.0 http://www.apache.org/licenses/LICENSE-2.0.txt repo - + @@ -292,11 +293,11 @@ false - - org.apache.maven.doxia - doxia-module-markdown - 1.3 - + + org.apache.maven.doxia + doxia-module-markdown + 1.3 + From 5f2898eadf8a731a0f47bb329bdc09a2bdfb5da2 Mon Sep 17 00:00:00 2001 From: Lukasz Lenart Date: Tue, 2 Dec 2014 12:41:06 +0100 Subject: [PATCH 06/41] Cleans up Maven generated website --- apps/pom.xml | 123 +-------- archetypes/pom.xml | 14 + assembly/pom.xml | 2 + bom/pom.xml | 14 + core/pom.xml | 22 -- core/src/site/site.xml | 24 +- plugins/cdi/src/site/site.xml | 57 ++++ plugins/codebehind/src/site/site.xml | 57 ++++ plugins/config-browser/src/site/site.xml | 57 ++++ plugins/convention/src/site/site.xml | 57 ++++ plugins/dojo/src/site/site.xml | 57 ++++ plugins/dwr/src/site/site.xml | 57 ++++ plugins/embeddedjsp/src/site/site.xml | 57 ++++ plugins/gxp/src/site/site.xml | 57 ++++ plugins/jasperreports/src/site/site.xml | 57 ++++ plugins/javatemplates/src/site/site.xml | 57 ++++ plugins/jfreechart/src/site/site.xml | 57 ++++ plugins/jsf/src/site/site.xml | 57 ++++ plugins/json/src/site/site.xml | 57 ++++ plugins/junit/src/site/site.xml | 57 ++++ plugins/osgi/src/site/site.xml | 57 ++++ plugins/oval/src/site/site.xml | 57 ++++ plugins/pell-multipart/src/site/site.xml | 57 ++++ plugins/plexus/src/site/site.xml | 57 ++++ plugins/pom.xml | 30 +- plugins/portlet-tiles/src/site/site.xml | 57 ++++ plugins/portlet/src/site/site.xml | 57 ++++ plugins/rest/src/site/site.xml | 57 ++++ plugins/sitegraph/src/site/site.xml | 57 ++++ plugins/sitemesh/src/site/site.xml | 57 ++++ plugins/spring/src/site/site.xml | 57 ++++ plugins/src/site/site.xml | 58 ++++ plugins/struts1/src/site/site.xml | 57 ++++ plugins/testng/src/site/site.xml | 57 ++++ plugins/tiles/src/site/site.xml | 57 ++++ plugins/tiles3/src/site/site.xml | 57 ++++ pom.xml | 39 ++- src/site/markdown/plugins.md | 37 --- src/site/resources/archetype-catalog.xml | 50 ---- src/site/resources/css/site.css | 71 ----- src/site/resources/images/download.gif | Bin 3782 -> 0 bytes src/site/resources/images/help.gif | Bin 4678 -> 0 bytes src/site/resources/images/plugins.gif | Bin 3265 -> 0 bytes src/site/resources/images/struts2-arch.png | Bin 19723 -> 0 bytes src/site/resources/images/struts2-merger.png | Bin 37645 -> 0 bytes src/site/resources/images/struts2-merger2.png | Bin 37958 -> 0 bytes src/site/resources/images/struts2.png | Bin 10133 -> 0 bytes src/site/site.xml | 87 ++---- src/site/xdoc/index.xml.vm | 260 ------------------ src/site/xdoc/jxr.xml | 46 ---- xwork-core/src/site/site.xml | 57 ++++ 51 files changed, 1827 insertions(+), 703 deletions(-) create mode 100644 plugins/cdi/src/site/site.xml create mode 100644 plugins/codebehind/src/site/site.xml create mode 100644 plugins/config-browser/src/site/site.xml create mode 100644 plugins/convention/src/site/site.xml create mode 100644 plugins/dojo/src/site/site.xml create mode 100644 plugins/dwr/src/site/site.xml create mode 100644 plugins/embeddedjsp/src/site/site.xml create mode 100644 plugins/gxp/src/site/site.xml create mode 100644 plugins/jasperreports/src/site/site.xml create mode 100644 plugins/javatemplates/src/site/site.xml create mode 100644 plugins/jfreechart/src/site/site.xml create mode 100644 plugins/jsf/src/site/site.xml create mode 100644 plugins/json/src/site/site.xml create mode 100644 plugins/junit/src/site/site.xml create mode 100644 plugins/osgi/src/site/site.xml create mode 100644 plugins/oval/src/site/site.xml create mode 100644 plugins/pell-multipart/src/site/site.xml create mode 100644 plugins/plexus/src/site/site.xml create mode 100644 plugins/portlet-tiles/src/site/site.xml create mode 100644 plugins/portlet/src/site/site.xml create mode 100644 plugins/rest/src/site/site.xml create mode 100644 plugins/sitegraph/src/site/site.xml create mode 100644 plugins/sitemesh/src/site/site.xml create mode 100644 plugins/spring/src/site/site.xml create mode 100644 plugins/src/site/site.xml create mode 100644 plugins/struts1/src/site/site.xml create mode 100644 plugins/testng/src/site/site.xml create mode 100644 plugins/tiles/src/site/site.xml create mode 100644 plugins/tiles3/src/site/site.xml delete mode 100644 src/site/markdown/plugins.md delete mode 100644 src/site/resources/archetype-catalog.xml delete mode 100644 src/site/resources/css/site.css delete mode 100644 src/site/resources/images/download.gif delete mode 100644 src/site/resources/images/help.gif delete mode 100644 src/site/resources/images/plugins.gif delete mode 100644 src/site/resources/images/struts2-arch.png delete mode 100644 src/site/resources/images/struts2-merger.png delete mode 100644 src/site/resources/images/struts2-merger2.png delete mode 100644 src/site/resources/images/struts2.png delete mode 100644 src/site/xdoc/index.xml.vm delete mode 100755 src/site/xdoc/jxr.xml create mode 100644 xwork-core/src/site/site.xml diff --git a/apps/pom.xml b/apps/pom.xml index c0741ea72..9aba17390 100644 --- a/apps/pom.xml +++ b/apps/pom.xml @@ -40,120 +40,6 @@ rest-showcase - - - hostedqa - - - com.hostedqa - hostedqa-remote-ant - 1.7 - test - - - - - codehaus - codehaus - http://repository.codehaus.org - - - maven-hostedqa - maven-hostedqa - - true - always - ignore - - - true - - http://maven.hostedqa.com - - - - - - - src/main/java - - **/*.properties - **/*.xml - - - - - - maven-antrun-plugin - org.apache.maven.plugins - - - package - - run - - - - - - - - - - - - - - com.hostedqa - hostedqa-remote-ant - 1.7 - - - - - - - - release - - - release - - - - - - true - org.codehaus.mojo - rat-maven-plugin - - - verify - - check - - - false - - - rat.analysis.license.ApacheSoftwareLicense20 - - - - pom.xml - - - src/** - - - - - - - - - - @@ -196,6 +82,14 @@ WEB-INF/classes/LICENSE.txt,WEB-INF/classes/NOTICE.txt + + maven-site-plugin + + true + true + false + + ${project.artifactId} @@ -203,7 +97,6 @@ - org.apache.struts struts2-core diff --git a/archetypes/pom.xml b/archetypes/pom.xml index b82c19eba..0bb9ae674 100644 --- a/archetypes/pom.xml +++ b/archetypes/pom.xml @@ -46,4 +46,18 @@ UTF-8 + + + + + maven-site-plugin + + true + true + false + + + + + diff --git a/assembly/pom.xml b/assembly/pom.xml index 9dd80d249..d07fc04f7 100644 --- a/assembly/pom.xml +++ b/assembly/pom.xml @@ -16,6 +16,8 @@ maven-site-plugin + true + true false diff --git a/bom/pom.xml b/bom/pom.xml index 753b19cce..702ab9f94 100644 --- a/bom/pom.xml +++ b/bom/pom.xml @@ -6,6 +6,7 @@ org.apache.struts struts-master 9 + ../struts-master struts2-bom @@ -27,6 +28,19 @@ 2.3.21-SNAPSHOT + + + + maven-site-plugin + + true + true + false + + + + + diff --git a/core/pom.xml b/core/pom.xml index 8b74f1df9..9d0efee40 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -76,28 +76,6 @@ - - - - org.codehaus.mojo - rat-maven-plugin - - - pom.xml - src/** - - - src/test/resources/org/apache/struts2/views/jsp/ui/* - src/main/resources/org/apache/struts2/static/domTT.js - src/test/resources/org/apache/struts2/interceptor/validation/* - src/site/resources/tags/** - src/main/resources/*LICENSE.txt - - - - - - alljars diff --git a/core/src/site/site.xml b/core/src/site/site.xml index 8944a2125..07a667ec7 100644 --- a/core/src/site/site.xml +++ b/core/src/site/site.xml @@ -1,8 +1,6 @@ + + org.apache.maven.skins + maven-fluido-skin + 1.3.1 + Apache Software Foundation http://www.apache.org/images/asf-logo.gif @@ -29,19 +32,26 @@ Apache Struts - http://struts.apache.org/images/struts.gif + http://struts.apache.org/img/struts-logo.svg http://struts.apache.org/ + + - - + + +
+
+ Apache Struts, Struts, Apache, the Apache feather logo, and the Apache Struts + project logos are trademarks of The Apache Software Foundation. +
+
+ diff --git a/plugins/cdi/src/site/site.xml b/plugins/cdi/src/site/site.xml new file mode 100644 index 000000000..07a667ec7 --- /dev/null +++ b/plugins/cdi/src/site/site.xml @@ -0,0 +1,57 @@ + + + + + org.apache.maven.skins + maven-fluido-skin + 1.3.1 + + + Apache Software Foundation + http://www.apache.org/images/asf-logo.gif + http://www.apache.org/ + + + Apache Struts + http://struts.apache.org/img/struts-logo.svg + http://struts.apache.org/ + + + + + + + + + + + + +
+
+ Apache Struts, Struts, Apache, the Apache feather logo, and the Apache Struts + project logos are trademarks of The Apache Software Foundation. +
+
+ + + diff --git a/plugins/codebehind/src/site/site.xml b/plugins/codebehind/src/site/site.xml new file mode 100644 index 000000000..07a667ec7 --- /dev/null +++ b/plugins/codebehind/src/site/site.xml @@ -0,0 +1,57 @@ + + + + + org.apache.maven.skins + maven-fluido-skin + 1.3.1 + + + Apache Software Foundation + http://www.apache.org/images/asf-logo.gif + http://www.apache.org/ + + + Apache Struts + http://struts.apache.org/img/struts-logo.svg + http://struts.apache.org/ + + + + + + + + + + + + +
+
+ Apache Struts, Struts, Apache, the Apache feather logo, and the Apache Struts + project logos are trademarks of The Apache Software Foundation. +
+
+ + + diff --git a/plugins/config-browser/src/site/site.xml b/plugins/config-browser/src/site/site.xml new file mode 100644 index 000000000..07a667ec7 --- /dev/null +++ b/plugins/config-browser/src/site/site.xml @@ -0,0 +1,57 @@ + + + + + org.apache.maven.skins + maven-fluido-skin + 1.3.1 + + + Apache Software Foundation + http://www.apache.org/images/asf-logo.gif + http://www.apache.org/ + + + Apache Struts + http://struts.apache.org/img/struts-logo.svg + http://struts.apache.org/ + + + + + + + + + + + + +
+
+ Apache Struts, Struts, Apache, the Apache feather logo, and the Apache Struts + project logos are trademarks of The Apache Software Foundation. +
+
+ + + diff --git a/plugins/convention/src/site/site.xml b/plugins/convention/src/site/site.xml new file mode 100644 index 000000000..07a667ec7 --- /dev/null +++ b/plugins/convention/src/site/site.xml @@ -0,0 +1,57 @@ + + + + + org.apache.maven.skins + maven-fluido-skin + 1.3.1 + + + Apache Software Foundation + http://www.apache.org/images/asf-logo.gif + http://www.apache.org/ + + + Apache Struts + http://struts.apache.org/img/struts-logo.svg + http://struts.apache.org/ + + + + + + + + + + + + +
+
+ Apache Struts, Struts, Apache, the Apache feather logo, and the Apache Struts + project logos are trademarks of The Apache Software Foundation. +
+
+ + + diff --git a/plugins/dojo/src/site/site.xml b/plugins/dojo/src/site/site.xml new file mode 100644 index 000000000..07a667ec7 --- /dev/null +++ b/plugins/dojo/src/site/site.xml @@ -0,0 +1,57 @@ + + + + + org.apache.maven.skins + maven-fluido-skin + 1.3.1 + + + Apache Software Foundation + http://www.apache.org/images/asf-logo.gif + http://www.apache.org/ + + + Apache Struts + http://struts.apache.org/img/struts-logo.svg + http://struts.apache.org/ + + + + + + + + + + + + +
+
+ Apache Struts, Struts, Apache, the Apache feather logo, and the Apache Struts + project logos are trademarks of The Apache Software Foundation. +
+
+ + + diff --git a/plugins/dwr/src/site/site.xml b/plugins/dwr/src/site/site.xml new file mode 100644 index 000000000..07a667ec7 --- /dev/null +++ b/plugins/dwr/src/site/site.xml @@ -0,0 +1,57 @@ + + + + + org.apache.maven.skins + maven-fluido-skin + 1.3.1 + + + Apache Software Foundation + http://www.apache.org/images/asf-logo.gif + http://www.apache.org/ + + + Apache Struts + http://struts.apache.org/img/struts-logo.svg + http://struts.apache.org/ + + + + + + + + + + + + +
+
+ Apache Struts, Struts, Apache, the Apache feather logo, and the Apache Struts + project logos are trademarks of The Apache Software Foundation. +
+
+ + + diff --git a/plugins/embeddedjsp/src/site/site.xml b/plugins/embeddedjsp/src/site/site.xml new file mode 100644 index 000000000..07a667ec7 --- /dev/null +++ b/plugins/embeddedjsp/src/site/site.xml @@ -0,0 +1,57 @@ + + + + + org.apache.maven.skins + maven-fluido-skin + 1.3.1 + + + Apache Software Foundation + http://www.apache.org/images/asf-logo.gif + http://www.apache.org/ + + + Apache Struts + http://struts.apache.org/img/struts-logo.svg + http://struts.apache.org/ + + + + + + + + + + + + +
+
+ Apache Struts, Struts, Apache, the Apache feather logo, and the Apache Struts + project logos are trademarks of The Apache Software Foundation. +
+
+ + + diff --git a/plugins/gxp/src/site/site.xml b/plugins/gxp/src/site/site.xml new file mode 100644 index 000000000..07a667ec7 --- /dev/null +++ b/plugins/gxp/src/site/site.xml @@ -0,0 +1,57 @@ + + + + + org.apache.maven.skins + maven-fluido-skin + 1.3.1 + + + Apache Software Foundation + http://www.apache.org/images/asf-logo.gif + http://www.apache.org/ + + + Apache Struts + http://struts.apache.org/img/struts-logo.svg + http://struts.apache.org/ + + + + + + + + + + + + +
+
+ Apache Struts, Struts, Apache, the Apache feather logo, and the Apache Struts + project logos are trademarks of The Apache Software Foundation. +
+
+ + + diff --git a/plugins/jasperreports/src/site/site.xml b/plugins/jasperreports/src/site/site.xml new file mode 100644 index 000000000..07a667ec7 --- /dev/null +++ b/plugins/jasperreports/src/site/site.xml @@ -0,0 +1,57 @@ + + + + + org.apache.maven.skins + maven-fluido-skin + 1.3.1 + + + Apache Software Foundation + http://www.apache.org/images/asf-logo.gif + http://www.apache.org/ + + + Apache Struts + http://struts.apache.org/img/struts-logo.svg + http://struts.apache.org/ + + + + + + + + + + + + +
+
+ Apache Struts, Struts, Apache, the Apache feather logo, and the Apache Struts + project logos are trademarks of The Apache Software Foundation. +
+
+ + + diff --git a/plugins/javatemplates/src/site/site.xml b/plugins/javatemplates/src/site/site.xml new file mode 100644 index 000000000..07a667ec7 --- /dev/null +++ b/plugins/javatemplates/src/site/site.xml @@ -0,0 +1,57 @@ + + + + + org.apache.maven.skins + maven-fluido-skin + 1.3.1 + + + Apache Software Foundation + http://www.apache.org/images/asf-logo.gif + http://www.apache.org/ + + + Apache Struts + http://struts.apache.org/img/struts-logo.svg + http://struts.apache.org/ + + + + + + + + + + + + +
+
+ Apache Struts, Struts, Apache, the Apache feather logo, and the Apache Struts + project logos are trademarks of The Apache Software Foundation. +
+
+ + + diff --git a/plugins/jfreechart/src/site/site.xml b/plugins/jfreechart/src/site/site.xml new file mode 100644 index 000000000..07a667ec7 --- /dev/null +++ b/plugins/jfreechart/src/site/site.xml @@ -0,0 +1,57 @@ + + + + + org.apache.maven.skins + maven-fluido-skin + 1.3.1 + + + Apache Software Foundation + http://www.apache.org/images/asf-logo.gif + http://www.apache.org/ + + + Apache Struts + http://struts.apache.org/img/struts-logo.svg + http://struts.apache.org/ + + + + + + + + + + + + +
+
+ Apache Struts, Struts, Apache, the Apache feather logo, and the Apache Struts + project logos are trademarks of The Apache Software Foundation. +
+
+ + + diff --git a/plugins/jsf/src/site/site.xml b/plugins/jsf/src/site/site.xml new file mode 100644 index 000000000..07a667ec7 --- /dev/null +++ b/plugins/jsf/src/site/site.xml @@ -0,0 +1,57 @@ + + + + + org.apache.maven.skins + maven-fluido-skin + 1.3.1 + + + Apache Software Foundation + http://www.apache.org/images/asf-logo.gif + http://www.apache.org/ + + + Apache Struts + http://struts.apache.org/img/struts-logo.svg + http://struts.apache.org/ + + + + + + + + + + + + +
+
+ Apache Struts, Struts, Apache, the Apache feather logo, and the Apache Struts + project logos are trademarks of The Apache Software Foundation. +
+
+ + + diff --git a/plugins/json/src/site/site.xml b/plugins/json/src/site/site.xml new file mode 100644 index 000000000..07a667ec7 --- /dev/null +++ b/plugins/json/src/site/site.xml @@ -0,0 +1,57 @@ + + + + + org.apache.maven.skins + maven-fluido-skin + 1.3.1 + + + Apache Software Foundation + http://www.apache.org/images/asf-logo.gif + http://www.apache.org/ + + + Apache Struts + http://struts.apache.org/img/struts-logo.svg + http://struts.apache.org/ + + + + + + + + + + + + +
+
+ Apache Struts, Struts, Apache, the Apache feather logo, and the Apache Struts + project logos are trademarks of The Apache Software Foundation. +
+
+ + + diff --git a/plugins/junit/src/site/site.xml b/plugins/junit/src/site/site.xml new file mode 100644 index 000000000..07a667ec7 --- /dev/null +++ b/plugins/junit/src/site/site.xml @@ -0,0 +1,57 @@ + + + + + org.apache.maven.skins + maven-fluido-skin + 1.3.1 + + + Apache Software Foundation + http://www.apache.org/images/asf-logo.gif + http://www.apache.org/ + + + Apache Struts + http://struts.apache.org/img/struts-logo.svg + http://struts.apache.org/ + + + + + + + + + + + + +
+
+ Apache Struts, Struts, Apache, the Apache feather logo, and the Apache Struts + project logos are trademarks of The Apache Software Foundation. +
+
+ + + diff --git a/plugins/osgi/src/site/site.xml b/plugins/osgi/src/site/site.xml new file mode 100644 index 000000000..07a667ec7 --- /dev/null +++ b/plugins/osgi/src/site/site.xml @@ -0,0 +1,57 @@ + + + + + org.apache.maven.skins + maven-fluido-skin + 1.3.1 + + + Apache Software Foundation + http://www.apache.org/images/asf-logo.gif + http://www.apache.org/ + + + Apache Struts + http://struts.apache.org/img/struts-logo.svg + http://struts.apache.org/ + + + + + + + + + + + + +
+
+ Apache Struts, Struts, Apache, the Apache feather logo, and the Apache Struts + project logos are trademarks of The Apache Software Foundation. +
+
+ + + diff --git a/plugins/oval/src/site/site.xml b/plugins/oval/src/site/site.xml new file mode 100644 index 000000000..07a667ec7 --- /dev/null +++ b/plugins/oval/src/site/site.xml @@ -0,0 +1,57 @@ + + + + + org.apache.maven.skins + maven-fluido-skin + 1.3.1 + + + Apache Software Foundation + http://www.apache.org/images/asf-logo.gif + http://www.apache.org/ + + + Apache Struts + http://struts.apache.org/img/struts-logo.svg + http://struts.apache.org/ + + + + + + + + + + + + +
+
+ Apache Struts, Struts, Apache, the Apache feather logo, and the Apache Struts + project logos are trademarks of The Apache Software Foundation. +
+
+ + + diff --git a/plugins/pell-multipart/src/site/site.xml b/plugins/pell-multipart/src/site/site.xml new file mode 100644 index 000000000..07a667ec7 --- /dev/null +++ b/plugins/pell-multipart/src/site/site.xml @@ -0,0 +1,57 @@ + + + + + org.apache.maven.skins + maven-fluido-skin + 1.3.1 + + + Apache Software Foundation + http://www.apache.org/images/asf-logo.gif + http://www.apache.org/ + + + Apache Struts + http://struts.apache.org/img/struts-logo.svg + http://struts.apache.org/ + + + + + + + + + + + + +
+
+ Apache Struts, Struts, Apache, the Apache feather logo, and the Apache Struts + project logos are trademarks of The Apache Software Foundation. +
+
+ + + diff --git a/plugins/plexus/src/site/site.xml b/plugins/plexus/src/site/site.xml new file mode 100644 index 000000000..07a667ec7 --- /dev/null +++ b/plugins/plexus/src/site/site.xml @@ -0,0 +1,57 @@ + + + + + org.apache.maven.skins + maven-fluido-skin + 1.3.1 + + + Apache Software Foundation + http://www.apache.org/images/asf-logo.gif + http://www.apache.org/ + + + Apache Struts + http://struts.apache.org/img/struts-logo.svg + http://struts.apache.org/ + + + + + + + + + + + + +
+
+ Apache Struts, Struts, Apache, the Apache feather logo, and the Apache Struts + project logos are trademarks of The Apache Software Foundation. +
+
+ + + diff --git a/plugins/pom.xml b/plugins/pom.xml index 276dd1dd0..55d4737cc 100644 --- a/plugins/pom.xml +++ b/plugins/pom.xml @@ -34,33 +34,33 @@ Struts Plugins + cdi codebehind - convention config-browser - javatemplates + convention + dojo + dwr + embeddedjsp + gxp jasperreports + javatemplates jfreechart jsf + json + junit + osgi + oval pell-multipart plexus + portlet + portlet-tiles + rest sitegraph sitemesh spring struts1 - tiles - dojo - rest - portlet - portlet-tiles - junit testng - dwr - oval - osgi - json - embeddedjsp - gxp - cdi + tiles tiles3 diff --git a/plugins/portlet-tiles/src/site/site.xml b/plugins/portlet-tiles/src/site/site.xml new file mode 100644 index 000000000..07a667ec7 --- /dev/null +++ b/plugins/portlet-tiles/src/site/site.xml @@ -0,0 +1,57 @@ + + + + + org.apache.maven.skins + maven-fluido-skin + 1.3.1 + + + Apache Software Foundation + http://www.apache.org/images/asf-logo.gif + http://www.apache.org/ + + + Apache Struts + http://struts.apache.org/img/struts-logo.svg + http://struts.apache.org/ + + + + + + + + + + + + +
+
+ Apache Struts, Struts, Apache, the Apache feather logo, and the Apache Struts + project logos are trademarks of The Apache Software Foundation. +
+
+ + + diff --git a/plugins/portlet/src/site/site.xml b/plugins/portlet/src/site/site.xml new file mode 100644 index 000000000..07a667ec7 --- /dev/null +++ b/plugins/portlet/src/site/site.xml @@ -0,0 +1,57 @@ + + + + + org.apache.maven.skins + maven-fluido-skin + 1.3.1 + + + Apache Software Foundation + http://www.apache.org/images/asf-logo.gif + http://www.apache.org/ + + + Apache Struts + http://struts.apache.org/img/struts-logo.svg + http://struts.apache.org/ + + + + + + + + + + + + +
+
+ Apache Struts, Struts, Apache, the Apache feather logo, and the Apache Struts + project logos are trademarks of The Apache Software Foundation. +
+
+ + + diff --git a/plugins/rest/src/site/site.xml b/plugins/rest/src/site/site.xml new file mode 100644 index 000000000..07a667ec7 --- /dev/null +++ b/plugins/rest/src/site/site.xml @@ -0,0 +1,57 @@ + + + + + org.apache.maven.skins + maven-fluido-skin + 1.3.1 + + + Apache Software Foundation + http://www.apache.org/images/asf-logo.gif + http://www.apache.org/ + + + Apache Struts + http://struts.apache.org/img/struts-logo.svg + http://struts.apache.org/ + + + + + + + + + + + + +
+
+ Apache Struts, Struts, Apache, the Apache feather logo, and the Apache Struts + project logos are trademarks of The Apache Software Foundation. +
+
+ + + diff --git a/plugins/sitegraph/src/site/site.xml b/plugins/sitegraph/src/site/site.xml new file mode 100644 index 000000000..07a667ec7 --- /dev/null +++ b/plugins/sitegraph/src/site/site.xml @@ -0,0 +1,57 @@ + + + + + org.apache.maven.skins + maven-fluido-skin + 1.3.1 + + + Apache Software Foundation + http://www.apache.org/images/asf-logo.gif + http://www.apache.org/ + + + Apache Struts + http://struts.apache.org/img/struts-logo.svg + http://struts.apache.org/ + + + + + + + + + + + + +
+
+ Apache Struts, Struts, Apache, the Apache feather logo, and the Apache Struts + project logos are trademarks of The Apache Software Foundation. +
+
+ + + diff --git a/plugins/sitemesh/src/site/site.xml b/plugins/sitemesh/src/site/site.xml new file mode 100644 index 000000000..07a667ec7 --- /dev/null +++ b/plugins/sitemesh/src/site/site.xml @@ -0,0 +1,57 @@ + + + + + org.apache.maven.skins + maven-fluido-skin + 1.3.1 + + + Apache Software Foundation + http://www.apache.org/images/asf-logo.gif + http://www.apache.org/ + + + Apache Struts + http://struts.apache.org/img/struts-logo.svg + http://struts.apache.org/ + + + + + + + + + + + + +
+
+ Apache Struts, Struts, Apache, the Apache feather logo, and the Apache Struts + project logos are trademarks of The Apache Software Foundation. +
+
+ + + diff --git a/plugins/spring/src/site/site.xml b/plugins/spring/src/site/site.xml new file mode 100644 index 000000000..07a667ec7 --- /dev/null +++ b/plugins/spring/src/site/site.xml @@ -0,0 +1,57 @@ + + + + + org.apache.maven.skins + maven-fluido-skin + 1.3.1 + + + Apache Software Foundation + http://www.apache.org/images/asf-logo.gif + http://www.apache.org/ + + + Apache Struts + http://struts.apache.org/img/struts-logo.svg + http://struts.apache.org/ + + + + + + + + + + + + +
+
+ Apache Struts, Struts, Apache, the Apache feather logo, and the Apache Struts + project logos are trademarks of The Apache Software Foundation. +
+
+ + + diff --git a/plugins/src/site/site.xml b/plugins/src/site/site.xml new file mode 100644 index 000000000..e03d26d5c --- /dev/null +++ b/plugins/src/site/site.xml @@ -0,0 +1,58 @@ + + + + + org.apache.maven.skins + maven-fluido-skin + 1.3.1 + + + Apache Software Foundation + http://www.apache.org/images/asf-logo.gif + http://www.apache.org/ + + + Apache Struts + http://struts.apache.org/img/struts-logo.svg + http://struts.apache.org/ + + + + + + + + + + + + + +
+
+ Apache Struts, Struts, Apache, the Apache feather logo, and the Apache Struts + project logos are trademarks of The Apache Software Foundation. +
+
+ + + diff --git a/plugins/struts1/src/site/site.xml b/plugins/struts1/src/site/site.xml new file mode 100644 index 000000000..07a667ec7 --- /dev/null +++ b/plugins/struts1/src/site/site.xml @@ -0,0 +1,57 @@ + + + + + org.apache.maven.skins + maven-fluido-skin + 1.3.1 + + + Apache Software Foundation + http://www.apache.org/images/asf-logo.gif + http://www.apache.org/ + + + Apache Struts + http://struts.apache.org/img/struts-logo.svg + http://struts.apache.org/ + + + + + + + + + + + + +
+
+ Apache Struts, Struts, Apache, the Apache feather logo, and the Apache Struts + project logos are trademarks of The Apache Software Foundation. +
+
+ + + diff --git a/plugins/testng/src/site/site.xml b/plugins/testng/src/site/site.xml new file mode 100644 index 000000000..07a667ec7 --- /dev/null +++ b/plugins/testng/src/site/site.xml @@ -0,0 +1,57 @@ + + + + + org.apache.maven.skins + maven-fluido-skin + 1.3.1 + + + Apache Software Foundation + http://www.apache.org/images/asf-logo.gif + http://www.apache.org/ + + + Apache Struts + http://struts.apache.org/img/struts-logo.svg + http://struts.apache.org/ + + + + + + + + + + + + +
+
+ Apache Struts, Struts, Apache, the Apache feather logo, and the Apache Struts + project logos are trademarks of The Apache Software Foundation. +
+
+ + + diff --git a/plugins/tiles/src/site/site.xml b/plugins/tiles/src/site/site.xml new file mode 100644 index 000000000..07a667ec7 --- /dev/null +++ b/plugins/tiles/src/site/site.xml @@ -0,0 +1,57 @@ + + + + + org.apache.maven.skins + maven-fluido-skin + 1.3.1 + + + Apache Software Foundation + http://www.apache.org/images/asf-logo.gif + http://www.apache.org/ + + + Apache Struts + http://struts.apache.org/img/struts-logo.svg + http://struts.apache.org/ + + + + + + + + + + + + +
+
+ Apache Struts, Struts, Apache, the Apache feather logo, and the Apache Struts + project logos are trademarks of The Apache Software Foundation. +
+
+ + + diff --git a/plugins/tiles3/src/site/site.xml b/plugins/tiles3/src/site/site.xml new file mode 100644 index 000000000..07a667ec7 --- /dev/null +++ b/plugins/tiles3/src/site/site.xml @@ -0,0 +1,57 @@ + + + + + org.apache.maven.skins + maven-fluido-skin + 1.3.1 + + + Apache Software Foundation + http://www.apache.org/images/asf-logo.gif + http://www.apache.org/ + + + Apache Struts + http://struts.apache.org/img/struts-logo.svg + http://struts.apache.org/ + + + + + + + + + + + + +
+
+ Apache Struts, Struts, Apache, the Apache feather logo, and the Apache Struts + project logos are trademarks of The Apache Software Foundation. +
+
+ + + diff --git a/pom.xml b/pom.xml index 8d0937056..673c53e5e 100644 --- a/pom.xml +++ b/pom.xml @@ -131,12 +131,12 @@ org.apache.maven.plugins maven-site-plugin - 3.2 + 3.3 org.apache.maven.plugins maven-project-info-reports-plugin - 2.4 + 2.6 org.codehaus.mojo @@ -287,7 +287,7 @@ org.apache.maven.plugins maven-site-plugin - 3.1 + 3.3 ${siteDeploy.url} false @@ -311,20 +311,37 @@ org.apache.maven.plugins maven-project-info-reports-plugin + 2.7 - diff --git a/src/site/markdown/plugins.md b/src/site/markdown/plugins.md deleted file mode 100644 index 332a102ab..000000000 --- a/src/site/markdown/plugins.md +++ /dev/null @@ -1,37 +0,0 @@ -# Apache Struts Plugins - -Apache Struts provides a plugin mechanism which allows to extend the framework -easily. A few selected plugins are bundled with Struts and are maintained by the -Struts team. These plugins are linked below. You can find community build plugins -linked on the [Struts 2 Plugin Wiki](https://cwiki.apache.org/S2PLUGINS/home.html). - -## API References - - * [CDI](struts2-plugins/struts2-cdi-browser-plugin/apidocs/index.html) - * [Codebehind](struts2-plugins/struts2-codebehind-browser-plugin/apidocs/index.html) - * [Config Browser](struts2-plugins/struts2-config-browser-plugin/apidocs/index.html) - * [Convention](struts2-plugins/struts2-convention-plugin/apidocs/index.html) - * [Dojo](struts2-plugins/struts2-dojo-plugin/apidocs/index.html) - * [DWR](struts2-plugins/struts2-dwr-plugin/apidocs/index.html) - * [Embedded JSP](struts2-plugins/struts2-embeddedjsp-plugin/apidocs/index.html) - * [GXP](struts2-plugins/struts2-gxp-plugin/apidocs/index.html) - * [JasperReports](struts2-plugins/struts2-jasperreports-plugin/apidocs/index.html) - * [JavaTemplates](struts2-plugins/struts2-javatemplates-plugin/apidocs/index.html) - * [JFreeChart](struts2-plugins/struts2-jfreechart-plugin/apidocs/index.html) - * [JavaServer Faces](struts2-plugins/struts2-jsf-plugin/apidocs/index.html) - * [JSON](struts2-plugins/struts2-json-plugin/apidocs/index.html) - * [JUnit](struts2-plugins/struts2-junit-plugin/apidocs/index.html) - * [OSGi](struts2-plugins/struts2-osgi-plugin/apidocs/index.html) - * [OVAL](struts2-plugins/struts2-oval-plugin/apidocs/index.html) - * [Pell Multipart](struts2-plugins/struts2-pell-multipart-plugin/apidocs/index.html) - * [Plexus](struts2-plugins/struts2-plexus-plugin/apidocs/index.html) - * [Portlet](struts2-plugins/struts2-portlet-plugin/apidocs/index.html) - * [Portlet Tiles](struts2-plugins/struts2-portlet-tiles-plugin/apidocs/index.html) - * [Rest](struts2-plugins/struts2-rest-plugin/apidocs/index.html) - * [Sitegraph](struts2-plugins/struts2-sitegraph-plugin/apidocs/index.html) - * [Sitemesh](struts2-plugins/struts2-sitemesh-plugin/apidocs/index.html) - * [Spring](struts2-plugins/struts2-spring-plugin/apidocs/index.html) - * [Struts 1](struts2-plugins/struts2-struts1-plugin/apidocs/index.html) - * [TestNG](struts2-plugins/struts2-testng-plugin/apidocs/index.html) - * [Tiles](struts2-plugins/struts2-tiles-plugin/apidocs/index.html) - * [Tiles 3](struts2-plugins/struts2-tiles3-plugin/apidocs/index.html) diff --git a/src/site/resources/archetype-catalog.xml b/src/site/resources/archetype-catalog.xml deleted file mode 100644 index fab8fde9a..000000000 --- a/src/site/resources/archetype-catalog.xml +++ /dev/null @@ -1,50 +0,0 @@ - - - - - org.apache.struts - struts2-archetype-blank - 2.3.16.2 - http://repo1.maven.org/maven2/ - Struts 2 Archetypes - Blank - - - org.apache.struts - struts2-archetype-convention - 2.3.16.2 - http://repo1.maven.org/maven2/ - Struts 2 Archetypes - Blank Convention - - - org.apache.struts - struts2-archetype-dbportlet - 2.3.16.2 - http://repo1.maven.org/maven2/ - Struts 2 Archetypes - Database Portlet - - - org.apache.struts - struts2-archetype-plugin - 2.3.16.2 - http://repo1.maven.org/maven2/ - Struts 2 Archetypes - Plugin - - - org.apache.struts - struts2-archetype-portlet - 2.3.16.2 - http://repo1.maven.org/maven2/ - Struts 2 Archetypes - Portlet - - - org.apache.struts - struts2-archetype-starter - 2.3.16.2 - http://repo1.maven.org/maven2/ - Struts 2 Archetypes - Starter - - - diff --git a/src/site/resources/css/site.css b/src/site/resources/css/site.css deleted file mode 100644 index 356442cb1..000000000 --- a/src/site/resources/css/site.css +++ /dev/null @@ -1,71 +0,0 @@ -/* - * $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 - */ - -a.externalLink, a.externalLink:link, a.externalLink:visited, a.externalLink:active, a.externalLink:hover { - background:none; - padding-right:0; -} - -img.poweredBy { - margin-left:17px; -} - -.hero-unit h1 { - font-size: 40px; - margin-top: 20px; -} - -.hero-unit { - padding: 20px; -} - -.right { - text-align: right; -} - -.section img { - margin: 20px; -} - -a:visited { - color: #00438a; -} - -a.btn-primary:visited, a.btn-info:visited { - color: #fff; -} - -.right { - float: right; -} - -#bannerRight img { margin-right: 80px; } - -.breadcrumb { padding-right: 130px; } - -#bodyColumn .row { - padding:20px; -} - -.huge { - font-size: 24px; - line-height: 52px; -} \ No newline at end of file diff --git a/src/site/resources/images/download.gif b/src/site/resources/images/download.gif deleted file mode 100644 index c67d21b9b9ce9b3b055dc61d9baf5eee8f6ed748..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3782 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 diff --git a/src/site/resources/images/help.gif b/src/site/resources/images/help.gif deleted file mode 100644 index 728f837737dbd086a6bafdccf60bd3bb7f979f1c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4678 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 diff --git a/src/site/resources/images/plugins.gif b/src/site/resources/images/plugins.gif deleted file mode 100644 index b1a8c1338b4019954a95553698d29e8e980d4907..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3265 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 diff --git a/src/site/resources/images/struts2-arch.png b/src/site/resources/images/struts2-arch.png deleted file mode 100644 index a9979bf29c2f66e880fc8c1d5a52598095b55ded..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19723 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 diff --git a/src/site/resources/images/struts2-merger.png b/src/site/resources/images/struts2-merger.png deleted file mode 100644 index 0891bd57ad3af184a2f64fc33f5a8ba8f3620b22..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 37645 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 diff --git a/src/site/site.xml b/src/site/site.xml index 85fb5a38f..7ce4095af 100644 --- a/src/site/site.xml +++ b/src/site/site.xml @@ -1,8 +1,6 @@ - - - - - Welcome - - - - -
- - -
-
- Struts 2 and WebWork merger -
-

- Apache Struts 2 is an elegant, extensible framework for creating enterprise-ready Java web applications. - The framework is designed to streamline the full development cycle, - from building, to deploying, to maintaining applications over time. -

- -

- Apache Struts 2 was originally known as WebWork 2. - After working independently for several years, - the WebWork and Struts communities joined forces to create Struts2. - This new version of Struts is simpler to use and - closer to how Struts was always meant to be. -

-
- -
- -
- - - - - - - - -
- Build! -
    -
  • - Easy startup - - Jumpstart new projects with our bootstrap tutorial and template application or Maven - archetype. -
  • -
  • - Improved Design - - Code clean against HTTP-independant framework interfaces. -
  • -
  • - Enhanced Tags - - Code less with stylesheet-driven form tags that provide their own markup. -
  • -
  • - Stateful Checkboxes - - Avoid special handling with smart checkboxes that know when they are toggled. -
  • -
  • - Flexible Cancel Buttons - - Go directly to a different action on cancel. -
  • -
  • - First-class AJAX support - - Add interactivity and flexibility with AJAX tags that look and feel just like standard - Struts tags. -
  • -
  • - Easy Spring integration - - Inject dependencies into Actions using Spring without glue code or red tape. (Plexus - support also available.) -
  • -
  • - Enhanced Results - - Do more with speciality results for JasperReports, JFreeChart, Action chaining, and - file downloading. -
  • -
  • - POJO forms - - No more ActionForms! Use any JavaBean to capture form input or - put properties directly on an Action class. Use both binary and String properties! -
  • -
  • - POJO Actions - - Use any class as an Action class -- even the - interface is optional! -
  • - -
-
- Deploy! -
    -
  • - Easy plugins
    - Add framework extensions by dropping in a JAR. - No manual configuration required! Bundled plugins add support for JavaServer Faces, - JasperReports, JFreeChart, Tiles, and more ... -
  • -
  • - Integrated profiling
    - Peek inside Struts2 - to find where the cycles are going! -
  • -
  • - Precise Error Reporting
    - Flip directly to the location and line of an error. -
  • - -
- -
- Maintain! -
    -
  • - Easy-to-test Actions
    - Test Struts2 - Actions directly, - without resorting to mock HTTP objects. -
  • -
  • - Intelligent Defaults
    - Skip obvious and redundant settings. Most framework configuration - elements have a default value that we can set and forget. Say it once! -
  • -
  • - Easy-to-customize controller
    - Customize the request handling - per action, if desired. Struts2 - only does what you want it to do! -
  • -
  • - Integrating Debugging
    - Research problem reports with built-in - debugging tools. -
  • -
  • - Easy-to-tweak tags
    - Customize tag markup by editing a FreeMarker - template. No need to grok the taglib API! - JSP, FreeMarker, and Velocity tags are fully supported. -
  • -
-
- - -

- To download the framework, visit - - Apache Struts Distributions. - - For more about Apache Struts 2, visit - - Getting Started. - - For more about framework extensions, visit the - - Struts 2 Plugin Registry. - - For help with migrating, visit our - - Migration Guide. - -

-
-
- -
- -

- Apache Struts 2 requires: -

- -
    -
  • Servlet API 2.4
  • -
  • JSP API 2.0
  • -
  • Java 5
  • -
- -

- For a full list of requirements, including dependencies used by optional plugins, - see Project Dependencies -

- -
- -
-

Apache Struts is distributed under the Apache License, Version 2.0

-
- - diff --git a/src/site/xdoc/jxr.xml b/src/site/xdoc/jxr.xml deleted file mode 100755 index f7f1024ce..000000000 --- a/src/site/xdoc/jxr.xml +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - Source Xref report - - - - -
- - - -
- - -
diff --git a/xwork-core/src/site/site.xml b/xwork-core/src/site/site.xml new file mode 100644 index 000000000..07a667ec7 --- /dev/null +++ b/xwork-core/src/site/site.xml @@ -0,0 +1,57 @@ + + + + + org.apache.maven.skins + maven-fluido-skin + 1.3.1 + + + Apache Software Foundation + http://www.apache.org/images/asf-logo.gif + http://www.apache.org/ + + + Apache Struts + http://struts.apache.org/img/struts-logo.svg + http://struts.apache.org/ + + + + + + + + + + + + +
+
+ Apache Struts, Struts, Apache, the Apache feather logo, and the Apache Struts + project logos are trademarks of The Apache Software Foundation. +
+
+ + + From adb7b8c73fbe683e13a90e5fd022fcea24a26118 Mon Sep 17 00:00:00 2001 From: Lukasz Lenart Date: Fri, 5 Dec 2014 11:44:02 +0100 Subject: [PATCH 07/41] Updates link which points to Confluence docs --- src/site/site.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/site/site.xml b/src/site/site.xml index 7ce4095af..4b8a308a4 100644 --- a/src/site/site.xml +++ b/src/site/site.xml @@ -48,17 +48,17 @@
- - - - + + + + - + From 6834b78fe9ec33e90530686e9c5101358750a854 Mon Sep 17 00:00:00 2001 From: Przemek Bruski Date: Tue, 9 Dec 2014 17:17:15 +0100 Subject: [PATCH 08/41] WW-4427 - Converters are no longer applied to values coming from the context - fix and UT --- .../com/opensymphony/xwork2/ognl/OgnlValueStack.java | 2 ++ .../opensymphony/xwork2/ognl/OgnlValueStackTest.java | 11 +++++++++++ 2 files changed, 13 insertions(+) diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/ognl/OgnlValueStack.java b/xwork-core/src/main/java/com/opensymphony/xwork2/ognl/OgnlValueStack.java index 90b1a543e..ce273b109 100644 --- a/xwork-core/src/main/java/com/opensymphony/xwork2/ognl/OgnlValueStack.java +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/ognl/OgnlValueStack.java @@ -351,6 +351,8 @@ public class OgnlValueStack implements Serializable, ValueStack, ClearableValueS value = getValue(expr, asType); if (value == null) { value = findInContext(expr); + final XWorkConverter conv = ((Container)getContext().get(ActionContext.CONTAINER)).getInstance(XWorkConverter.class); + return conv.convertValue(getContext(), value, asType); } } finally { context.remove(THROW_EXCEPTION_ON_FAILURE); diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/ognl/OgnlValueStackTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/ognl/OgnlValueStackTest.java index e0e949cec..769fcf76c 100644 --- a/xwork-core/src/test/java/com/opensymphony/xwork2/ognl/OgnlValueStackTest.java +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/ognl/OgnlValueStackTest.java @@ -93,6 +93,17 @@ public class OgnlValueStackTest extends XWorkTestCase { assertEquals("1, 2", vs.findValue("childAges", String.class)); } + public void testValuesFromContextAreConverted() { + final OgnlValueStack vs = createValueStack(); + vs.getContext().put(ActionContext.CONTAINER, container); + + final String propertyName = "dogName"; + final String propertyValue = "Rover"; + vs.getContext().put(propertyName, new String[]{propertyValue}); + + assertEquals(propertyValue, vs.findValue(propertyName, String.class)); + } + public void testFailOnException() { OgnlValueStack vs = createValueStack(); From 6a58778399dcdacbe061c55b37a581646871d4ce Mon Sep 17 00:00:00 2001 From: Przemek Bruski Date: Tue, 16 Dec 2014 16:58:38 +0100 Subject: [PATCH 09/41] WW-4427 - inject the converter instead of getting it directly --- .../java/com/opensymphony/xwork2/ognl/OgnlValueStack.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/ognl/OgnlValueStack.java b/xwork-core/src/main/java/com/opensymphony/xwork2/ognl/OgnlValueStack.java index ce273b109..48e524163 100644 --- a/xwork-core/src/main/java/com/opensymphony/xwork2/ognl/OgnlValueStack.java +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/ognl/OgnlValueStack.java @@ -63,6 +63,7 @@ public class OgnlValueStack implements Serializable, ValueStack, ClearableValueS Map overrides; transient OgnlUtil ognlUtil; transient SecurityMemberAccess securityMemberAccess; + private transient XWorkConverter converter; private boolean devMode; private boolean logMissingProperties; @@ -351,8 +352,7 @@ public class OgnlValueStack implements Serializable, ValueStack, ClearableValueS value = getValue(expr, asType); if (value == null) { value = findInContext(expr); - final XWorkConverter conv = ((Container)getContext().get(ActionContext.CONTAINER)).getInstance(XWorkConverter.class); - return conv.convertValue(getContext(), value, asType); + return converter.convertValue(getContext(), value, asType); } } finally { context.remove(THROW_EXCEPTION_ON_FAILURE); @@ -475,4 +475,8 @@ public class OgnlValueStack implements Serializable, ValueStack, ClearableValueS securityMemberAccess.setExcludeProperties(excludeProperties); } + @Inject + public void setXWorkConverter(final XWorkConverter converter) { + this.converter = converter; + } } From debaaa2443b854ee0c0cd34ea61ccdc88810e13d Mon Sep 17 00:00:00 2001 From: Przemek Bruski Date: Tue, 16 Dec 2014 17:07:10 +0100 Subject: [PATCH 10/41] WW-4427 - cleaned up UT --- .../java/com/opensymphony/xwork2/ognl/OgnlValueStackTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/ognl/OgnlValueStackTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/ognl/OgnlValueStackTest.java index 769fcf76c..fe045847b 100644 --- a/xwork-core/src/test/java/com/opensymphony/xwork2/ognl/OgnlValueStackTest.java +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/ognl/OgnlValueStackTest.java @@ -95,7 +95,6 @@ public class OgnlValueStackTest extends XWorkTestCase { public void testValuesFromContextAreConverted() { final OgnlValueStack vs = createValueStack(); - vs.getContext().put(ActionContext.CONTAINER, container); final String propertyName = "dogName"; final String propertyValue = "Rover"; From 5551a9bb60e86fced2c9cf2850a46f045d6728df Mon Sep 17 00:00:00 2001 From: Lukasz Lenart Date: Sat, 20 Dec 2014 08:54:31 +0100 Subject: [PATCH 11/41] Adds jenv specific file --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 313053b7b..e95318aaa 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,9 @@ # Scripts *.sh +# jenv +.java-version + # Maven core/target xwork-core/target From 3288096c2b33a35d5cfa18fc75f19d9ad3b7b60f Mon Sep 17 00:00:00 2001 From: Lukasz Lenart Date: Sat, 20 Dec 2014 08:55:55 +0100 Subject: [PATCH 12/41] Adds proper deprecation note --- .../main/java/com/opensymphony/xwork2/UnknownHandler.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/UnknownHandler.java b/xwork-core/src/main/java/com/opensymphony/xwork2/UnknownHandler.java index 8e9303a81..faabfc08c 100644 --- a/xwork-core/src/main/java/com/opensymphony/xwork2/UnknownHandler.java +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/UnknownHandler.java @@ -54,7 +54,10 @@ public interface UnknownHandler { * @param action The action object * @param methodName The method name to call * @return The result returned from invoking the action method, can return null - * @throws NoSuchMethodException If the method cannot be found (deprecated) - should return nunll instead + * @deprecated @throws NoSuchMethodException If the method cannot be found should return null instead, + * don't throw exception as other UnknownHandles won't be invoked + * 'throws NoSuchMethodException' signature will be removed with next + * major release */ public Object handleUnknownActionMethod(Object action, String methodName) throws NoSuchMethodException; } From bf7714f7ae648ef9e7e59ae00a9bbf34c746fffb Mon Sep 17 00:00:00 2001 From: Lukasz Lenart Date: Sat, 20 Dec 2014 09:02:28 +0100 Subject: [PATCH 13/41] Improves flow of exceptions and missing action method --- .../xwork2/DefaultActionInvocation.java | 42 ++++++++++++------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/DefaultActionInvocation.java b/xwork-core/src/main/java/com/opensymphony/xwork2/DefaultActionInvocation.java index dd44b140e..f2c28eaab 100644 --- a/xwork-core/src/main/java/com/opensymphony/xwork2/DefaultActionInvocation.java +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/DefaultActionInvocation.java @@ -421,23 +421,37 @@ public class DefaultActionInvocation implements ActionInvocation { Object methodResult; try { methodResult = ognlUtil.getValue(methodName + "()", getStack().getContext(), action); - } catch (OgnlException e) { - // hmm -- OK, try doXxx instead - try { - String altMethodName = "do" + methodName.substring(0, 1).toUpperCase() + methodName.substring(1) + "()"; - methodResult = ognlUtil.getValue(altMethodName, ActionContext.getContext().getContextMap(), action); - } catch (OgnlException e1) { - // well, give the unknown handler a shot - if (unknownHandlerManager.hasUnknownHandlers()) { - try { - methodResult = unknownHandlerManager.handleUnknownMethod(action, methodName); - } catch (NoSuchMethodException e2) { - // throw the original one + } catch (MethodFailedException e) { + // if reason is missing method, try find version with "do" prefix + if (e.getReason() instanceof NoSuchMethodException) { + try { + String altMethodName = "do" + methodName.substring(0, 1).toUpperCase() + methodName.substring(1) + "()"; + methodResult = ognlUtil.getValue(altMethodName, ActionContext.getContext().getContextMap(), action); + } catch (MethodFailedException e1) { + // if still method doesn't exist, try checking UnknownHandlers + if (e.getReason() instanceof NoSuchMethodException) { + if (unknownHandlerManager.hasUnknownHandlers()) { + try { + methodResult = unknownHandlerManager.handleUnknownMethod(action, methodName); + } catch (NoSuchMethodException e2) { + // throw the original one + throw e; + } + } else { + throw e; + } + // throw the original exception as UnknownHandlers weren't able to handle invocation as well + if (methodResult == null) { + throw e; + } + } else { + // exception isn't related to missing action method throw e; } - } else { - throw e; } + } else { + // exception isn't related to missing action method + throw e; } } return saveResult(actionConfig, methodResult); From 61ab8137ebbafd33a8c0c96d2c177db3e5267a0d Mon Sep 17 00:00:00 2001 From: Lukasz Lenart Date: Sat, 20 Dec 2014 09:02:56 +0100 Subject: [PATCH 14/41] Adds test cases to cover three use cases with flow of exceptions --- .../xwork2/DefaultActionInvocationTest.java | 106 +++++++++++++++++- 1 file changed, 103 insertions(+), 3 deletions(-) diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/DefaultActionInvocationTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/DefaultActionInvocationTest.java index e0aa8ba7e..d0ff2b525 100644 --- a/xwork-core/src/test/java/com/opensymphony/xwork2/DefaultActionInvocationTest.java +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/DefaultActionInvocationTest.java @@ -1,14 +1,12 @@ package com.opensymphony.xwork2; -import com.mockobjects.dynamic.Mock; import com.opensymphony.xwork2.config.entities.InterceptorMapping; import com.opensymphony.xwork2.mock.MockActionProxy; import com.opensymphony.xwork2.mock.MockContainer; import com.opensymphony.xwork2.mock.MockInterceptor; import com.opensymphony.xwork2.ognl.OgnlUtil; +import com.opensymphony.xwork2.util.ValueStack; import com.opensymphony.xwork2.util.ValueStackFactory; -import org.easymock.EasyMock; -import org.easymock.IMocksControl; import java.util.ArrayList; import java.util.HashMap; @@ -81,6 +79,108 @@ public class DefaultActionInvocationTest extends XWorkTestCase { assertEquals(mockContainer, deserializable.container); } + public void testInvokingExistingExecuteMethod() throws Exception { + // given + DefaultActionInvocation dai = new DefaultActionInvocation(new HashMap(), false) { + public ValueStack getStack() { + return new StubValueStack(); + } + }; + + SimpleAction action = new SimpleAction() { + @Override + public String execute() throws Exception { + return SUCCESS; + } + }; + MockActionProxy proxy = new MockActionProxy(); + proxy.setMethod("execute"); + + dai.proxy = proxy; + dai.ognlUtil = new OgnlUtil(); + + // when + String result = dai.invokeAction(action, null); + + // then + assertEquals("success", result); + } + + public void testInvokingMissingMethod() throws Exception { + // given + DefaultActionInvocation dai = new DefaultActionInvocation(new HashMap(), false) { + public ValueStack getStack() { + return new StubValueStack(); + } + }; + + SimpleAction action = new SimpleAction() { + @Override + public String execute() throws Exception { + return ERROR; + } + }; + MockActionProxy proxy = new MockActionProxy(); + proxy.setMethod("notExists"); + + UnknownHandlerManager uhm = new DefaultUnknownHandlerManager() { + @Override + public boolean hasUnknownHandlers() { + return false; + } + }; + + dai.proxy = proxy; + dai.ognlUtil = new OgnlUtil(); + dai.unknownHandlerManager = uhm; + + // when + Throwable expected = null; + try { + dai.invokeAction(action, null); + } catch (Exception e) { + expected = e; + } + + // then + assertNotNull(expected); + assertTrue(expected instanceof NoSuchMethodException); + } + + public void testInvokingExistingMethodThatThrowsException() throws Exception { + // given + DefaultActionInvocation dai = new DefaultActionInvocation(new HashMap(), false) { + public ValueStack getStack() { + return new StubValueStack(); + } + }; + + SimpleAction action = new SimpleAction() { + @Override + public String execute() throws Exception { + throw new IllegalArgumentException(); + } + }; + MockActionProxy proxy = new MockActionProxy(); + proxy.setMethod("execute"); + + dai.proxy = proxy; + dai.ognlUtil = new OgnlUtil(); + + // when + // when + Throwable expected = null; + try { + dai.invokeAction(action, null); + } catch (Exception e) { + expected = e; + } + + // then + assertNotNull(expected); + assertTrue(expected instanceof IllegalArgumentException); + } + } class DefaultActionInvocationTester extends DefaultActionInvocation { From 6774e840c8c48d992911cfd2375b00c1841baa0a Mon Sep 17 00:00:00 2001 From: Lukasz Lenart Date: Sat, 20 Dec 2014 21:44:18 +0100 Subject: [PATCH 15/41] Reverts to old version of ASM and adds ASM 5 as well --- pom.xml | 20 ++++++++++++++++++-- xwork-core/pom.xml | 4 ++-- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/pom.xml b/pom.xml index 673c53e5e..3801c2625 100644 --- a/pom.xml +++ b/pom.xml @@ -88,7 +88,8 @@ ${project.version} 3.0.5.RELEASE 3.0.6 - 5.0.2 + 3.3 + 5.0.2 2.0.6 @@ -514,6 +515,11 @@ struts2-osgi-demo-bundle ${project.version} + + org.apache.struts + struts2-java8-support-plugin + ${project.version} + org.freemarker @@ -585,11 +591,21 @@ org.ow2.asm asm - ${asm.version} + ${asm5.version} org.ow2.asm asm-commons + ${asm5.version} + + + asm + asm + ${asm.version} + + + asm + asm-commons ${asm.version} diff --git a/xwork-core/pom.xml b/xwork-core/pom.xml index 8f7a02492..f26c25d5d 100644 --- a/xwork-core/pom.xml +++ b/xwork-core/pom.xml @@ -126,11 +126,11 @@ ognl - org.ow2.asm + asm asm - org.ow2.asm + asm asm-commons From 6471eec0abcc90244eb9094b0e021aa5438f27b9 Mon Sep 17 00:00:00 2001 From: Lukasz Lenart Date: Sat, 20 Dec 2014 21:44:43 +0100 Subject: [PATCH 16/41] Defines new java8-plugin to support Java 8 --- plugins/java8-support/pom.xml | 61 +++++++++++++++++++++++++++++++++++ plugins/pom.xml | 1 + 2 files changed, 62 insertions(+) create mode 100644 plugins/java8-support/pom.xml diff --git a/plugins/java8-support/pom.xml b/plugins/java8-support/pom.xml new file mode 100644 index 000000000..b69286449 --- /dev/null +++ b/plugins/java8-support/pom.xml @@ -0,0 +1,61 @@ + + + 4.0.0 + + + org.apache.struts + struts2-plugins + 2.3.21-SNAPSHOT + + + struts2-java8-support-plugin + jar + Struts 2 Java 8 support plugin + + + + + org.apache.felix + maven-bundle-plugin + true + + + org.apache.struts2.osgi.StrutsActivator + META-INF + + + + + + + + + + org.apache.struts.xwork + xwork-core + + + asm + asm + + + asm + asm-commons + + + + + org.ow2.asm + asm + + + org.ow2.asm + asm-commons + + + + + UTF-8 + + diff --git a/plugins/pom.xml b/plugins/pom.xml index 55d4737cc..d12b17c01 100644 --- a/plugins/pom.xml +++ b/plugins/pom.xml @@ -43,6 +43,7 @@ embeddedjsp gxp jasperreports + java8-support javatemplates jfreechart jsf From 3ce21ac4832742241663a671163f40cf12083f99 Mon Sep 17 00:00:00 2001 From: Lukasz Lenart Date: Sat, 20 Dec 2014 21:45:30 +0100 Subject: [PATCH 17/41] Extracts interface with default implementation --- .../xwork2/util/finder/ClassFinder.java | 660 ++---------------- .../util/finder/DefaultClassFinder.java | 609 ++++++++++++++++ 2 files changed, 658 insertions(+), 611 deletions(-) create mode 100644 xwork-core/src/main/java/com/opensymphony/xwork2/util/finder/DefaultClassFinder.java diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/util/finder/ClassFinder.java b/xwork-core/src/main/java/com/opensymphony/xwork2/util/finder/ClassFinder.java index f337eb974..aed9981c7 100644 --- a/xwork-core/src/main/java/com/opensymphony/xwork2/util/finder/ClassFinder.java +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/util/finder/ClassFinder.java @@ -1,452 +1,68 @@ -/* - * Copyright 2002-2003,2009 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 com.opensymphony.xwork2.util.finder; -import com.opensymphony.xwork2.ActionContext; -import com.opensymphony.xwork2.FileManager; -import com.opensymphony.xwork2.FileManagerFactory; -import com.opensymphony.xwork2.XWorkException; -import com.opensymphony.xwork2.util.logging.Logger; -import com.opensymphony.xwork2.util.logging.LoggerFactory; -import org.apache.commons.lang3.StringUtils; -import org.objectweb.asm.AnnotationVisitor; -import org.objectweb.asm.ClassReader; -import org.objectweb.asm.FieldVisitor; -import org.objectweb.asm.MethodVisitor; -import org.objectweb.asm.ClassVisitor; -import org.objectweb.asm.Opcodes; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; import java.lang.annotation.Annotation; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; -import java.net.JarURLConnection; -import java.net.URL; -import java.net.URLDecoder; import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.LinkedHashMap; import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.jar.JarEntry; -import java.util.jar.JarInputStream; -/** - * ClassFinder searches the classpath of the specified ClassLoaderInterface for - * packages, classes, constructors, methods, or fields with specific annotations. - * - * For security reasons ASM is used to find the annotations. Classes are not - * loaded unless they match the requirements of a called findAnnotated* method. - * Once loaded, these classes are cached. - * - * The getClassesNotLoaded() method can be used immediately after any find* - * method to get a list of classes which matched the find requirements (i.e. - * contained the annotation), but were unable to be loaded. - * - * @author David Blevins - * @version $Rev$ $Date$ - */ -public class ClassFinder { - private static final Logger LOG = LoggerFactory.getLogger(ClassFinder.class); +public interface ClassFinder { - private final Map> annotated = new HashMap>(); - private final Map classInfos = new LinkedHashMap(); + boolean isAnnotationPresent(Class annotation); - private final List classesNotLoaded = new ArrayList(); + List getClassesNotLoaded(); - private boolean extractBaseInterfaces; - private ClassLoaderInterface classLoaderInterface; - private FileManager fileManager; + List findAnnotatedPackages(Class annotation); - public ClassFinder(ClassLoaderInterface classLoaderInterface, Collection urls, boolean extractBaseInterfaces, Set protocols, Test classNameFilter) { - this.classLoaderInterface = classLoaderInterface; - this.extractBaseInterfaces = extractBaseInterfaces; - this.fileManager = ActionContext.getContext().getInstance(FileManagerFactory.class).getFileManager(); + List findAnnotatedClasses(Class annotation); - List classNames = new ArrayList(); - for (URL location : urls) { - try { - if (protocols.contains(location.getProtocol())) { - classNames.addAll(jar(location)); - } else if ("file".equals(location.getProtocol())) { - try { - // See if it's actually a jar - URL jarUrl = new URL("jar", "", location.toExternalForm() + "!/"); - JarURLConnection juc = (JarURLConnection) jarUrl.openConnection(); - juc.getJarFile(); - classNames.addAll(jar(jarUrl)); - } catch (IOException e) { - classNames.addAll(file(location)); - } - } - } catch (Exception e) { - if (LOG.isErrorEnabled()) - LOG.error("Unable to read URL [#0]", e, location.toExternalForm()); - } - } + List findAnnotatedMethods(Class annotation); - for (String className : classNames) { - try { - if (classNameFilter.test(className)) - readClassDef(className); - } catch (Throwable e) { - if (LOG.isErrorEnabled()) - LOG.error("Unable to read class [#0]", e, className); - } - } + List findAnnotatedConstructors(Class annotation); + + List findAnnotatedFields(Class annotation); + + List findClassesInPackage(String packageName, boolean recursive); + + List findClasses(Test test); + + List findClasses(); + + ClassLoaderInterface getClassLoaderInterface(); + + public static interface Info { + String getName(); + + List getAnnotations(); } - public ClassFinder(Class... classes){ - this(Arrays.asList(classes)); - } + public class AnnotationInfo extends Annotatable implements Info { + private final String name; - public ClassFinder(List classes){ - this.classLoaderInterface = null; - List infos = new ArrayList(); - List packages = new ArrayList(); - for (Class clazz : classes) { - - Package aPackage = clazz.getPackage(); - if (aPackage != null && !packages.contains(aPackage)){ - infos.add(new PackageInfo(aPackage)); - packages.add(aPackage); - } - - ClassInfo classInfo = new ClassInfo(clazz); - infos.add(classInfo); - classInfos.put(classInfo.getName(), classInfo); - for (Method method : clazz.getDeclaredMethods()) { - infos.add(new MethodInfo(classInfo, method)); - } - - for (Constructor constructor : clazz.getConstructors()) { - infos.add(new MethodInfo(classInfo, constructor)); - } - - for (Field field : clazz.getDeclaredFields()) { - infos.add(new FieldInfo(classInfo, field)); - } + public AnnotationInfo(Annotation annotation){ + this(annotation.getClass().getName()); } - for (Info info : infos) { - for (AnnotationInfo annotation : info.getAnnotations()) { - List annotationInfos = getAnnotationInfos(annotation.getName()); - annotationInfos.add(info); - } - } - } - - public boolean isAnnotationPresent(Class annotation) { - List infos = annotated.get(annotation.getName()); - return infos != null && !infos.isEmpty(); - } - - /** - * Returns a list of classes that could not be loaded in last invoked findAnnotated* method. - *

- * The list will only contain entries of classes whose byte code matched the requirements - * of last invoked find* method, but were unable to be loaded and included in the results. - *

- * The list returned is unmodifiable. Once obtained, the returned list will be a live view of the - * results from the last findAnnotated* method call. - *

- * This method is not thread safe. - * @return an unmodifiable live view of classes that could not be loaded in previous findAnnotated* call. - */ - public List getClassesNotLoaded() { - return Collections.unmodifiableList(classesNotLoaded); - } - - public List findAnnotatedPackages(Class annotation) { - classesNotLoaded.clear(); - List packages = new ArrayList(); - List infos = getAnnotationInfos(annotation.getName()); - for (Info info : infos) { - if (info instanceof PackageInfo) { - PackageInfo packageInfo = (PackageInfo) info; - try { - Package pkg = packageInfo.get(); - // double check via proper reflection - if (pkg.isAnnotationPresent(annotation)) { - packages.add(pkg); - } - } catch (ClassNotFoundException e) { - classesNotLoaded.add(packageInfo.getName()); - } - } - } - return packages; - } - - public List findAnnotatedClasses(Class annotation) { - classesNotLoaded.clear(); - List classes = new ArrayList(); - List infos = getAnnotationInfos(annotation.getName()); - for (Info info : infos) { - if (info instanceof ClassInfo) { - ClassInfo classInfo = (ClassInfo) info; - try { - Class clazz = classInfo.get(); - // double check via proper reflection - if (clazz.isAnnotationPresent(annotation)) { - classes.add(clazz); - } - } catch (Throwable e) { - if (LOG.isErrorEnabled()) - LOG.error("Error loading class [#0]", e, classInfo.getName()); - classesNotLoaded.add(classInfo.getName()); - } - } - } - return classes; - } - - public List findAnnotatedMethods(Class annotation) { - classesNotLoaded.clear(); - List seen = new ArrayList(); - List methods = new ArrayList(); - List infos = getAnnotationInfos(annotation.getName()); - for (Info info : infos) { - if (info instanceof MethodInfo && !"".equals(info.getName())) { - MethodInfo methodInfo = (MethodInfo) info; - ClassInfo classInfo = methodInfo.getDeclaringClass(); - - if (seen.contains(classInfo)) continue; - - seen.add(classInfo); - - try { - Class clazz = classInfo.get(); - for (Method method : clazz.getDeclaredMethods()) { - if (method.isAnnotationPresent(annotation)) { - methods.add(method); - } - } - } catch (Throwable e) { - if (LOG.isErrorEnabled()) - LOG.error("Error loading class [#0]", e, classInfo.getName()); - classesNotLoaded.add(classInfo.getName()); - } - } - } - return methods; - } - - public List findAnnotatedConstructors(Class annotation) { - classesNotLoaded.clear(); - List seen = new ArrayList(); - List constructors = new ArrayList(); - List infos = getAnnotationInfos(annotation.getName()); - for (Info info : infos) { - if (info instanceof MethodInfo && "".equals(info.getName())) { - MethodInfo methodInfo = (MethodInfo) info; - ClassInfo classInfo = methodInfo.getDeclaringClass(); - - if (seen.contains(classInfo)) continue; - - seen.add(classInfo); - - try { - Class clazz = classInfo.get(); - for (Constructor constructor : clazz.getConstructors()) { - if (constructor.isAnnotationPresent(annotation)) { - constructors.add(constructor); - } - } - } catch (Throwable e) { - if (LOG.isErrorEnabled()) - LOG.error("Error loading class [#0]", e, classInfo.getName()); - classesNotLoaded.add(classInfo.getName()); - } - } - } - return constructors; - } - - public List findAnnotatedFields(Class annotation) { - classesNotLoaded.clear(); - List seen = new ArrayList(); - List fields = new ArrayList(); - List infos = getAnnotationInfos(annotation.getName()); - for (Info info : infos) { - if (info instanceof FieldInfo) { - FieldInfo fieldInfo = (FieldInfo) info; - ClassInfo classInfo = fieldInfo.getDeclaringClass(); - - if (seen.contains(classInfo)) continue; - - seen.add(classInfo); - - try { - Class clazz = classInfo.get(); - for (Field field : clazz.getDeclaredFields()) { - if (field.isAnnotationPresent(annotation)) { - fields.add(field); - } - } - } catch (Throwable e) { - if (LOG.isErrorEnabled()) - LOG.error("Error loading class [#0]", e, classInfo.getName()); - classesNotLoaded.add(classInfo.getName()); - } - } - } - return fields; - } - - public List findClassesInPackage(String packageName, boolean recursive) { - classesNotLoaded.clear(); - List classes = new ArrayList(); - for (ClassInfo classInfo : classInfos.values()) { - try { - if (recursive && classInfo.getPackageName().startsWith(packageName)){ - classes.add(classInfo.get()); - } else if (classInfo.getPackageName().equals(packageName)){ - classes.add(classInfo.get()); - } - } catch (Throwable e) { - if (LOG.isErrorEnabled()) - LOG.error("Error loading class [#0]", e, classInfo.getName()); - classesNotLoaded.add(classInfo.getName()); - } - } - return classes; - } - - public List findClasses(Test test) { - classesNotLoaded.clear(); - List classes = new ArrayList(); - for (ClassInfo classInfo : classInfos.values()) { - try { - if (test.test(classInfo)) { - classes.add(classInfo.get()); - } - } catch (Throwable e) { - if (LOG.isErrorEnabled()) - LOG.error("Error loading class [#0]", e, classInfo.getName()); - classesNotLoaded.add(classInfo.getName()); - } - } - return classes; - } - - public List findClasses() { - classesNotLoaded.clear(); - List classes = new ArrayList(); - for (ClassInfo classInfo : classInfos.values()) { - try { - classes.add(classInfo.get()); - } catch (Throwable e) { - if (LOG.isErrorEnabled()) - LOG.error("Error loading class [#0]", e, classInfo.getName()); - classesNotLoaded.add(classInfo.getName()); - } - } - return classes; - } - - private static List getURLs(ClassLoaderInterface classLoader, String[] dirNames) { - List urls = new ArrayList(); - for (String dirName : dirNames) { - try { - Enumeration classLoaderURLs = classLoader.getResources(dirName); - while (classLoaderURLs.hasMoreElements()) { - URL url = classLoaderURLs.nextElement(); - urls.add(url); - } - } catch (IOException ioe) { - if (LOG.isErrorEnabled()) - LOG.error("Could not read driectory [#0]", ioe, dirName); - } + public AnnotationInfo(Class annotation) { + this.name = annotation.getName().intern(); } - return urls; - } - - private List file(URL location) { - List classNames = new ArrayList(); - File dir = new File(URLDecoder.decode(location.getPath())); - if ("META-INF".equals(dir.getName())) { - dir = dir.getParentFile(); // Scrape "META-INF" off - } - if (dir.isDirectory()) { - scanDir(dir, classNames, ""); - } - return classNames; - } - - private void scanDir(File dir, List classNames, String packageName) { - File[] files = dir.listFiles(); - for (File file : files) { - if (file.isDirectory()) { - scanDir(file, classNames, packageName + file.getName() + "."); - } else if (file.getName().endsWith(".class")) { - String name = file.getName(); - name = name.replaceFirst(".class$", ""); - // Classes packaged in an exploded .war (e.g. in a VFS file system) should not - // have WEB-INF.classes in their package name. - classNames.add(StringUtils.removeStart(packageName, "WEB-INF.classes.") + name); - } - } - } - - private List jar(URL location) throws IOException { - URL url = fileManager.normalizeToFileProtocol(location); - if (url != null) { - InputStream in = url.openStream(); - try { - JarInputStream jarStream = new JarInputStream(in); - return jar(jarStream); - } finally { - in.close(); - } - } else if (LOG.isDebugEnabled()) - LOG.debug("Unable to read [#0]", location.toExternalForm()); - - return Collections.emptyList(); - } - - private List jar(JarInputStream jarStream) throws IOException { - List classNames = new ArrayList(); - - JarEntry entry; - while ((entry = jarStream.getNextJarEntry()) != null) { - if (entry.isDirectory() || !entry.getName().endsWith(".class")) { - continue; - } - String className = entry.getName(); - className = className.replaceFirst(".class$", ""); - - //war files are treated as .jar files, so takeout WEB-INF/classes - className = StringUtils.removeStart(className, "WEB-INF/classes/"); - - className = className.replace('/', '.'); - classNames.add(className); + public AnnotationInfo(String name) { + name = name.replaceAll("^L|;$", ""); + name = name.replace('/', '.'); + this.name = name.intern(); } - return classNames; + public String getName() { + return name; + } + + @Override + public String toString() { + return name; + } } public class Annotatable { @@ -467,12 +83,6 @@ public class ClassFinder { } - public static interface Info { - String getName(); - - List getAnnotations(); - } - public class PackageInfo extends Annotatable implements Info { private final String name; private final ClassInfo info; @@ -485,8 +95,8 @@ public class ClassFinder { this.info = null; } - public PackageInfo(String name) { - info = new ClassInfo(name, null); + public PackageInfo(String name, ClassFinder classFinder) { + info = new ClassInfo(name, null, classFinder); this.name = name; this.pkg = null; } @@ -509,19 +119,22 @@ public class ClassFinder { private final List superInterfaces = new ArrayList(); private final List fields = new ArrayList(); private Class clazz; + private ClassFinder classFinder; private ClassNotFoundException notFound; - public ClassInfo(Class clazz) { + public ClassInfo(Class clazz, ClassFinder classFinder) { super(clazz); this.clazz = clazz; + this.classFinder = classFinder; this.name = clazz.getName(); Class superclass = clazz.getSuperclass(); this.superType = superclass != null ? superclass.getName(): null; } - public ClassInfo(String name, String superType) { + public ClassInfo(String name, String superType, ClassFinder classFinder) { this.name = name; this.superType = superType; + this.classFinder = classFinder; } public String getPackageName(){ @@ -560,10 +173,10 @@ public class ClassFinder { if (clazz != null) return clazz; if (notFound != null) throw notFound; try { - this.clazz = classLoaderInterface.loadClass(name); + this.clazz = classFinder.getClassLoaderInterface().loadClass(name); return clazz; } catch (ClassNotFoundException notFound) { - classesNotLoaded.add(name); + classFinder.getClassesNotLoaded().add(name); this.notFound = notFound; throw notFound; } @@ -669,179 +282,4 @@ public class ClassFinder { } } - public class AnnotationInfo extends Annotatable implements Info { - private final String name; - - public AnnotationInfo(Annotation annotation){ - this(annotation.getClass().getName()); - } - - public AnnotationInfo(Class annotation) { - this.name = annotation.getName().intern(); - } - - public AnnotationInfo(String name) { - name = name.replaceAll("^L|;$", ""); - name = name.replace('/', '.'); - this.name = name.intern(); - } - - public String getName() { - return name; - } - - @Override - public String toString() { - return name; - } - } - - private List getAnnotationInfos(String name) { - List infos = annotated.get(name); - if (infos == null) { - infos = new ArrayList(); - annotated.put(name, infos); - } - return infos; - } - - private void readClassDef(String className) { - if (!className.endsWith(".class")) { - className = className.replace('.', '/') + ".class"; - } - try { - URL resource = classLoaderInterface.getResource(className); - if (resource != null) { - InputStream in = resource.openStream(); - try { - ClassReader classReader = new ClassReader(in); - classReader.accept(new InfoBuildingClassVisitor(), ClassReader.SKIP_DEBUG); - } finally { - in.close(); - } - } else { - throw new XWorkException("Could not load " + className); - } - } catch (IOException e) { - throw new XWorkException("Could not load " + className, e); - } - - } - - public class InfoBuildingClassVisitor extends ClassVisitor { - private Info info; - - public InfoBuildingClassVisitor() { - super(Opcodes.ASM5); - } - - public InfoBuildingClassVisitor(Info info) { - this(); - this.info = info; - } - - @Override - public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { - if (name.endsWith("package-info")) { - info = new PackageInfo(javaName(name)); - } else { - ClassInfo classInfo = new ClassInfo(javaName(name), javaName(superName)); - - for (String interfce : interfaces) { - classInfo.getInterfaces().add(javaName(interfce)); - } - info = classInfo; - classInfos.put(classInfo.getName(), classInfo); - - if (extractBaseInterfaces) - extractSuperInterfaces(classInfo); - } - } - - private void extractSuperInterfaces(ClassInfo classInfo) { - String superType = classInfo.getSuperType(); - - if (superType != null) { - ClassInfo base = classInfos.get(superType); - - if (base == null) { - //try to load base - String resource = superType.replace('.', '/') + ".class"; - readClassDef(resource); - base = classInfos.get(superType); - } - - if (base != null) { - List interfaces = classInfo.getSuperInterfaces(); - interfaces.addAll(base.getSuperInterfaces()); - interfaces.addAll(base.getInterfaces()); - } - } - } - - private String javaName(String name) { - return (name == null)? null:name.replace('/', '.'); - } - - @Override - public AnnotationVisitor visitAnnotation(String desc, boolean visible) { - AnnotationInfo annotationInfo = new AnnotationInfo(desc); - info.getAnnotations().add(annotationInfo); - getAnnotationInfos(annotationInfo.getName()).add(info); - return null; - } - - @Override - public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { - ClassInfo classInfo = ((ClassInfo) info); - FieldInfo fieldInfo = new FieldInfo(classInfo, name, desc); - classInfo.getFields().add(fieldInfo); - return null; - } - - @Override - public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { - ClassInfo classInfo = ((ClassInfo) info); - MethodInfo methodInfo = new MethodInfo(classInfo, name, desc); - classInfo.getMethods().add(methodInfo); - return new InfoBuildingMethodVisitor(methodInfo); - } - } - - public class InfoBuildingMethodVisitor extends MethodVisitor { - private Info info; - - public InfoBuildingMethodVisitor() { - super(Opcodes.ASM5); - } - - public InfoBuildingMethodVisitor(Info info) { - this(); - this.info = info; - } - - @Override - public AnnotationVisitor visitAnnotation(String desc, boolean visible) { - AnnotationInfo annotationInfo = new AnnotationInfo(desc); - info.getAnnotations().add(annotationInfo); - getAnnotationInfos(annotationInfo.getName()).add(info); - return null; - } - - @Override - public AnnotationVisitor visitParameterAnnotation(int param, String desc, boolean visible) { - MethodInfo methodInfo = ((MethodInfo) info); - List annotationInfos = methodInfo.getParameterAnnotations(param); - AnnotationInfo annotationInfo = new AnnotationInfo(desc); - annotationInfos.add(annotationInfo); - return null; - } - } - - private static final class DefaultClassnameFilterImpl implements Test { - public boolean test(String className) { - return true; - } - } } - diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/util/finder/DefaultClassFinder.java b/xwork-core/src/main/java/com/opensymphony/xwork2/util/finder/DefaultClassFinder.java new file mode 100644 index 000000000..192196a6c --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/util/finder/DefaultClassFinder.java @@ -0,0 +1,609 @@ +/* + * Copyright 2002-2003,2009 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 com.opensymphony.xwork2.util.finder; + +import com.opensymphony.xwork2.ActionContext; +import com.opensymphony.xwork2.FileManager; +import com.opensymphony.xwork2.FileManagerFactory; +import com.opensymphony.xwork2.XWorkException; +import com.opensymphony.xwork2.util.logging.Logger; +import com.opensymphony.xwork2.util.logging.LoggerFactory; +import org.apache.commons.lang3.StringUtils; +import org.objectweb.asm.AnnotationVisitor; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.FieldVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.commons.EmptyVisitor; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.lang.annotation.Annotation; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.net.JarURLConnection; +import java.net.URL; +import java.net.URLDecoder; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.jar.JarEntry; +import java.util.jar.JarInputStream; + +/** + * ClassFinder searches the classpath of the specified ClassLoaderInterface for + * packages, classes, constructors, methods, or fields with specific annotations. + * + * For security reasons ASM is used to find the annotations. Classes are not + * loaded unless they match the requirements of a called findAnnotated* method. + * Once loaded, these classes are cached. + * + * The getClassesNotLoaded() method can be used immediately after any find* + * method to get a list of classes which matched the find requirements (i.e. + * contained the annotation), but were unable to be loaded. + * + * @author David Blevins + * @version $Rev$ $Date$ + */ +public class DefaultClassFinder implements ClassFinder { + private static final Logger LOG = LoggerFactory.getLogger(DefaultClassFinder.class); + + private final Map> annotated = new HashMap>(); + private final Map classInfos = new LinkedHashMap(); + + private final List classesNotLoaded = new ArrayList(); + + private boolean extractBaseInterfaces; + private ClassLoaderInterface classLoaderInterface; + private FileManager fileManager; + + public DefaultClassFinder(ClassLoaderInterface classLoaderInterface, Collection urls, boolean extractBaseInterfaces, Set protocols, Test classNameFilter) { + this.classLoaderInterface = classLoaderInterface; + this.extractBaseInterfaces = extractBaseInterfaces; + this.fileManager = ActionContext.getContext().getInstance(FileManagerFactory.class).getFileManager(); + + List classNames = new ArrayList(); + for (URL location : urls) { + try { + if (protocols.contains(location.getProtocol())) { + classNames.addAll(jar(location)); + } else if ("file".equals(location.getProtocol())) { + try { + // See if it's actually a jar + URL jarUrl = new URL("jar", "", location.toExternalForm() + "!/"); + JarURLConnection juc = (JarURLConnection) jarUrl.openConnection(); + juc.getJarFile(); + classNames.addAll(jar(jarUrl)); + } catch (IOException e) { + classNames.addAll(file(location)); + } + } + } catch (Exception e) { + if (LOG.isErrorEnabled()) + LOG.error("Unable to read URL [#0]", e, location.toExternalForm()); + } + } + + for (String className : classNames) { + try { + if (classNameFilter.test(className)) + readClassDef(className); + } catch (Throwable e) { + if (LOG.isErrorEnabled()) + LOG.error("Unable to read class [#0]", e, className); + } + } + } + + public DefaultClassFinder(Class... classes){ + this(Arrays.asList(classes)); + } + + public DefaultClassFinder(List classes){ + this.classLoaderInterface = null; + List infos = new ArrayList(); + List packages = new ArrayList(); + for (Class clazz : classes) { + + Package aPackage = clazz.getPackage(); + if (aPackage != null && !packages.contains(aPackage)){ + infos.add(new PackageInfo(aPackage)); + packages.add(aPackage); + } + + ClassInfo classInfo = new ClassInfo(clazz, this); + infos.add(classInfo); + classInfos.put(classInfo.getName(), classInfo); + for (Method method : clazz.getDeclaredMethods()) { + infos.add(new MethodInfo(classInfo, method)); + } + + for (Constructor constructor : clazz.getConstructors()) { + infos.add(new MethodInfo(classInfo, constructor)); + } + + for (Field field : clazz.getDeclaredFields()) { + infos.add(new FieldInfo(classInfo, field)); + } + } + + for (Info info : infos) { + for (AnnotationInfo annotation : info.getAnnotations()) { + List annotationInfos = getAnnotationInfos(annotation.getName()); + annotationInfos.add(info); + } + } + } + + public ClassLoaderInterface getClassLoaderInterface() { + return classLoaderInterface; + } + + public boolean isAnnotationPresent(Class annotation) { + List infos = annotated.get(annotation.getName()); + return infos != null && !infos.isEmpty(); + } + + /** + * Returns a list of classes that could not be loaded in last invoked findAnnotated* method. + *

+ * The list will only contain entries of classes whose byte code matched the requirements + * of last invoked find* method, but were unable to be loaded and included in the results. + *

+ * The list returned is unmodifiable. Once obtained, the returned list will be a live view of the + * results from the last findAnnotated* method call. + *

+ * This method is not thread safe. + * @return an unmodifiable live view of classes that could not be loaded in previous findAnnotated* call. + */ + public List getClassesNotLoaded() { + return Collections.unmodifiableList(classesNotLoaded); + } + + public List findAnnotatedPackages(Class annotation) { + classesNotLoaded.clear(); + List packages = new ArrayList(); + List infos = getAnnotationInfos(annotation.getName()); + for (Info info : infos) { + if (info instanceof PackageInfo) { + PackageInfo packageInfo = (PackageInfo) info; + try { + Package pkg = packageInfo.get(); + // double check via proper reflection + if (pkg.isAnnotationPresent(annotation)) { + packages.add(pkg); + } + } catch (ClassNotFoundException e) { + classesNotLoaded.add(packageInfo.getName()); + } + } + } + return packages; + } + + public List findAnnotatedClasses(Class annotation) { + classesNotLoaded.clear(); + List classes = new ArrayList(); + List infos = getAnnotationInfos(annotation.getName()); + for (Info info : infos) { + if (info instanceof ClassInfo) { + ClassInfo classInfo = (ClassInfo) info; + try { + Class clazz = classInfo.get(); + // double check via proper reflection + if (clazz.isAnnotationPresent(annotation)) { + classes.add(clazz); + } + } catch (Throwable e) { + if (LOG.isErrorEnabled()) + LOG.error("Error loading class [#0]", e, classInfo.getName()); + classesNotLoaded.add(classInfo.getName()); + } + } + } + return classes; + } + + public List findAnnotatedMethods(Class annotation) { + classesNotLoaded.clear(); + List seen = new ArrayList(); + List methods = new ArrayList(); + List infos = getAnnotationInfos(annotation.getName()); + for (Info info : infos) { + if (info instanceof MethodInfo && !"".equals(info.getName())) { + MethodInfo methodInfo = (MethodInfo) info; + ClassInfo classInfo = methodInfo.getDeclaringClass(); + + if (seen.contains(classInfo)) continue; + + seen.add(classInfo); + + try { + Class clazz = classInfo.get(); + for (Method method : clazz.getDeclaredMethods()) { + if (method.isAnnotationPresent(annotation)) { + methods.add(method); + } + } + } catch (Throwable e) { + if (LOG.isErrorEnabled()) + LOG.error("Error loading class [#0]", e, classInfo.getName()); + classesNotLoaded.add(classInfo.getName()); + } + } + } + return methods; + } + + public List findAnnotatedConstructors(Class annotation) { + classesNotLoaded.clear(); + List seen = new ArrayList(); + List constructors = new ArrayList(); + List infos = getAnnotationInfos(annotation.getName()); + for (Info info : infos) { + if (info instanceof MethodInfo && "".equals(info.getName())) { + MethodInfo methodInfo = (MethodInfo) info; + ClassInfo classInfo = methodInfo.getDeclaringClass(); + + if (seen.contains(classInfo)) continue; + + seen.add(classInfo); + + try { + Class clazz = classInfo.get(); + for (Constructor constructor : clazz.getConstructors()) { + if (constructor.isAnnotationPresent(annotation)) { + constructors.add(constructor); + } + } + } catch (Throwable e) { + if (LOG.isErrorEnabled()) + LOG.error("Error loading class [#0]", e, classInfo.getName()); + classesNotLoaded.add(classInfo.getName()); + } + } + } + return constructors; + } + + public List findAnnotatedFields(Class annotation) { + classesNotLoaded.clear(); + List seen = new ArrayList(); + List fields = new ArrayList(); + List infos = getAnnotationInfos(annotation.getName()); + for (Info info : infos) { + if (info instanceof FieldInfo) { + FieldInfo fieldInfo = (FieldInfo) info; + ClassInfo classInfo = fieldInfo.getDeclaringClass(); + + if (seen.contains(classInfo)) continue; + + seen.add(classInfo); + + try { + Class clazz = classInfo.get(); + for (Field field : clazz.getDeclaredFields()) { + if (field.isAnnotationPresent(annotation)) { + fields.add(field); + } + } + } catch (Throwable e) { + if (LOG.isErrorEnabled()) + LOG.error("Error loading class [#0]", e, classInfo.getName()); + classesNotLoaded.add(classInfo.getName()); + } + } + } + return fields; + } + + public List findClassesInPackage(String packageName, boolean recursive) { + classesNotLoaded.clear(); + List classes = new ArrayList(); + for (ClassInfo classInfo : classInfos.values()) { + try { + if (recursive && classInfo.getPackageName().startsWith(packageName)){ + classes.add(classInfo.get()); + } else if (classInfo.getPackageName().equals(packageName)){ + classes.add(classInfo.get()); + } + } catch (Throwable e) { + if (LOG.isErrorEnabled()) + LOG.error("Error loading class [#0]", e, classInfo.getName()); + classesNotLoaded.add(classInfo.getName()); + } + } + return classes; + } + + public List findClasses(Test test) { + classesNotLoaded.clear(); + List classes = new ArrayList(); + for (ClassInfo classInfo : classInfos.values()) { + try { + if (test.test(classInfo)) { + classes.add(classInfo.get()); + } + } catch (Throwable e) { + if (LOG.isErrorEnabled()) + LOG.error("Error loading class [#0]", e, classInfo.getName()); + classesNotLoaded.add(classInfo.getName()); + } + } + return classes; + } + + public List findClasses() { + classesNotLoaded.clear(); + List classes = new ArrayList(); + for (ClassInfo classInfo : classInfos.values()) { + try { + classes.add(classInfo.get()); + } catch (Throwable e) { + if (LOG.isErrorEnabled()) + LOG.error("Error loading class [#0]", e, classInfo.getName()); + classesNotLoaded.add(classInfo.getName()); + } + } + return classes; + } + + private static List getURLs(ClassLoaderInterface classLoader, String[] dirNames) { + List urls = new ArrayList(); + for (String dirName : dirNames) { + try { + Enumeration classLoaderURLs = classLoader.getResources(dirName); + while (classLoaderURLs.hasMoreElements()) { + URL url = classLoaderURLs.nextElement(); + urls.add(url); + } + } catch (IOException ioe) { + if (LOG.isErrorEnabled()) + LOG.error("Could not read driectory [#0]", ioe, dirName); + } + } + + return urls; + } + + private List file(URL location) { + List classNames = new ArrayList(); + File dir = new File(URLDecoder.decode(location.getPath())); + if ("META-INF".equals(dir.getName())) { + dir = dir.getParentFile(); // Scrape "META-INF" off + } + if (dir.isDirectory()) { + scanDir(dir, classNames, ""); + } + return classNames; + } + + private void scanDir(File dir, List classNames, String packageName) { + File[] files = dir.listFiles(); + for (File file : files) { + if (file.isDirectory()) { + scanDir(file, classNames, packageName + file.getName() + "."); + } else if (file.getName().endsWith(".class")) { + String name = file.getName(); + name = name.replaceFirst(".class$", ""); + // Classes packaged in an exploded .war (e.g. in a VFS file system) should not + // have WEB-INF.classes in their package name. + classNames.add(StringUtils.removeStart(packageName, "WEB-INF.classes.") + name); + } + } + } + + private List jar(URL location) throws IOException { + URL url = fileManager.normalizeToFileProtocol(location); + if (url != null) { + InputStream in = url.openStream(); + try { + JarInputStream jarStream = new JarInputStream(in); + return jar(jarStream); + } finally { + in.close(); + } + } else if (LOG.isDebugEnabled()) + LOG.debug("Unable to read [#0]", location.toExternalForm()); + + return Collections.emptyList(); + } + + private List jar(JarInputStream jarStream) throws IOException { + List classNames = new ArrayList(); + + JarEntry entry; + while ((entry = jarStream.getNextJarEntry()) != null) { + if (entry.isDirectory() || !entry.getName().endsWith(".class")) { + continue; + } + String className = entry.getName(); + className = className.replaceFirst(".class$", ""); + + //war files are treated as .jar files, so takeout WEB-INF/classes + className = StringUtils.removeStart(className, "WEB-INF/classes/"); + + className = className.replace('/', '.'); + classNames.add(className); + } + + return classNames; + } + + public class PackageInfo extends Annotatable implements Info { + private final String name; + private final ClassInfo info; + private final Package pkg; + + public PackageInfo(Package pkg){ + super(pkg); + this.pkg = pkg; + this.name = pkg.getName(); + this.info = null; + } + + public PackageInfo(String name, ClassFinder classFinder) { + info = new ClassInfo(name, null, classFinder); + this.name = name; + this.pkg = null; + } + + public String getName() { + return name; + } + + public Package get() throws ClassNotFoundException { + return (pkg != null)?pkg:info.get().getPackage(); + } + } + + private List getAnnotationInfos(String name) { + List infos = annotated.get(name); + if (infos == null) { + infos = new ArrayList(); + annotated.put(name, infos); + } + return infos; + } + + private void readClassDef(String className) { + if (!className.endsWith(".class")) { + className = className.replace('.', '/') + ".class"; + } + try { + URL resource = classLoaderInterface.getResource(className); + if (resource != null) { + InputStream in = resource.openStream(); + try { + ClassReader classReader = new ClassReader(in); + classReader.accept(new InfoBuildingVisitor(this), ClassReader.SKIP_DEBUG); + } finally { + in.close(); + } + } else { + throw new XWorkException("Could not load " + className); + } + } catch (IOException e) { + throw new XWorkException("Could not load " + className, e); + } + + } + + public class InfoBuildingVisitor extends EmptyVisitor { + private Info info; + private ClassFinder classFinder; + + public InfoBuildingVisitor(ClassFinder classFinder) { + this.classFinder = classFinder; + } + + public InfoBuildingVisitor(Info info, ClassFinder classFinder) { + this.info = info; + this.classFinder = classFinder; + } + + @Override + public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { + if (name.endsWith("package-info")) { + info = new PackageInfo(javaName(name), classFinder); + } else { + ClassInfo classInfo = new ClassInfo(javaName(name), javaName(superName), classFinder); + + for (String interfce : interfaces) { + classInfo.getInterfaces().add(javaName(interfce)); + } + info = classInfo; + classInfos.put(classInfo.getName(), classInfo); + + if (extractBaseInterfaces) + extractSuperInterfaces(classInfo); + } + } + + private void extractSuperInterfaces(ClassInfo classInfo) { + String superType = classInfo.getSuperType(); + + if (superType != null) { + ClassInfo base = classInfos.get(superType); + + if (base == null) { + //try to load base + String resource = superType.replace('.', '/') + ".class"; + readClassDef(resource); + base = classInfos.get(superType); + } + + if (base != null) { + List interfaces = classInfo.getSuperInterfaces(); + interfaces.addAll(base.getSuperInterfaces()); + interfaces.addAll(base.getInterfaces()); + } + } + } + + private String javaName(String name) { + return (name == null)? null:name.replace('/', '.'); + } + + @Override + public AnnotationVisitor visitAnnotation(String desc, boolean visible) { + AnnotationInfo annotationInfo = new AnnotationInfo(desc); + info.getAnnotations().add(annotationInfo); + getAnnotationInfos(annotationInfo.getName()).add(info); + return new InfoBuildingVisitor(annotationInfo, classFinder); + } + + @Override + public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { + ClassInfo classInfo = ((ClassInfo) info); + FieldInfo fieldInfo = new FieldInfo(classInfo, name, desc); + classInfo.getFields().add(fieldInfo); + return new InfoBuildingVisitor(fieldInfo, classFinder); + } + + @Override + public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { + ClassInfo classInfo = ((ClassInfo) info); + MethodInfo methodInfo = new MethodInfo(classInfo, name, desc); + classInfo.getMethods().add(methodInfo); + return new InfoBuildingVisitor(methodInfo, classFinder); + } + + @Override + public AnnotationVisitor visitParameterAnnotation(int param, String desc, boolean visible) { + MethodInfo methodInfo = ((MethodInfo) info); + List annotationInfos = methodInfo.getParameterAnnotations(param); + AnnotationInfo annotationInfo = new AnnotationInfo(desc); + annotationInfos.add(annotationInfo); + return new InfoBuildingVisitor(annotationInfo, classFinder); + } + } + + private static final class DefaultClassnameFilterImpl implements Test { + public boolean test(String className) { + return true; + } + } +} + From a1941a8528cdb82501f1ea2611367275b66258ee Mon Sep 17 00:00:00 2001 From: Lukasz Lenart Date: Sat, 20 Dec 2014 21:46:25 +0100 Subject: [PATCH 18/41] Adds factory to allow create different versions of ClassFinder --- .../PackageBasedActionConfigBuilder.java | 26 ++++++++++++++++--- .../util/finder/ClassFinderFactory.java | 11 ++++++++ 2 files changed, 33 insertions(+), 4 deletions(-) create mode 100644 xwork-core/src/main/java/com/opensymphony/xwork2/util/finder/ClassFinderFactory.java diff --git a/plugins/convention/src/main/java/org/apache/struts2/convention/PackageBasedActionConfigBuilder.java b/plugins/convention/src/main/java/org/apache/struts2/convention/PackageBasedActionConfigBuilder.java index f6a43a88c..811df83e7 100644 --- a/plugins/convention/src/main/java/org/apache/struts2/convention/PackageBasedActionConfigBuilder.java +++ b/plugins/convention/src/main/java/org/apache/struts2/convention/PackageBasedActionConfigBuilder.java @@ -38,7 +38,8 @@ import com.opensymphony.xwork2.util.TextParseUtil; import com.opensymphony.xwork2.util.WildcardHelper; import com.opensymphony.xwork2.util.classloader.ReloadingClassLoader; import com.opensymphony.xwork2.util.finder.ClassFinder; -import com.opensymphony.xwork2.util.finder.ClassFinder.ClassInfo; +import com.opensymphony.xwork2.util.finder.ClassFinderFactory; +import com.opensymphony.xwork2.util.finder.DefaultClassFinder; import com.opensymphony.xwork2.util.finder.ClassLoaderInterface; import com.opensymphony.xwork2.util.finder.ClassLoaderInterfaceDelegate; import com.opensymphony.xwork2.util.finder.Test; @@ -113,6 +114,7 @@ public class PackageBasedActionConfigBuilder implements ActionConfigBuilder { private boolean eagerLoading = false; private FileManager fileManager; + private ClassFinderFactory classFinderFactory; /** * Constructs actions based on a list of packages. @@ -303,6 +305,11 @@ public class PackageBasedActionConfigBuilder implements ActionConfigBuilder { this.fileManager = fileManagerFactory.getFileManager(); } + @Inject(required = false) + public void setClassFinderFactory(ClassFinderFactory classFinderFactory) { + this.classFinderFactory = classFinderFactory; + } + protected void initReloadClassLoader() { //when the configuration is reloaded, a new classloader will be setup if (isReloadEnabled() && reloadingClassLoader == null) @@ -387,7 +394,7 @@ public class PackageBasedActionConfigBuilder implements ActionConfigBuilder { // specified by the user Test classPackageTest = getClassPackageTest(); List urls = readUrls(); - ClassFinder finder = new ClassFinder(getClassLoaderInterface(), urls, EXTRACT_BASE_INTERFACES, fileProtocols, classPackageTest); + ClassFinder finder = buildClassFinder(classPackageTest, urls); Test test = getActionClassTest(); classes.addAll(finder.findClasses(test)); @@ -400,6 +407,16 @@ public class PackageBasedActionConfigBuilder implements ActionConfigBuilder { return classes; } + protected ClassFinder buildClassFinder(Test classPackageTest, List urls) { + if (classFinderFactory != null) { + LOG.trace("Using ClassFinderFactory to create instance of ClassFinder!"); + return classFinderFactory.buildClassFinder(getClassLoaderInterface(), urls, EXTRACT_BASE_INTERFACES, fileProtocols, classPackageTest); + } else { + LOG.trace("ClassFinderFactory not defined, fallback to default ClassFinder implementation"); + return new DefaultClassFinder(getClassLoaderInterface(), urls, EXTRACT_BASE_INTERFACES, fileProtocols, classPackageTest); + } + } + private List readUrls() throws IOException { List resourceUrls = new ArrayList(); // Usually the "classes" dir. @@ -496,7 +513,7 @@ public class PackageBasedActionConfigBuilder implements ActionConfigBuilder { * goal is to avoid loading the class if we don't have to, the (actionSuffix * || implements Action) test will have to remain until later. See * {@link #getActionClassTest()} for the test performed on the loaded - * {@link ClassInfo} structure. + * {@link ClassFinder.ClassInfo} structure. * * @param className the name of the class to test * @return true if the specified class should be included in the @@ -588,7 +605,7 @@ public class PackageBasedActionConfigBuilder implements ActionConfigBuilder { /** * Construct a {@link Test} Object that determines if a specified class - * should be included in the package scan based on the full {@link ClassInfo} + * should be included in the package scan based on the full {@link ClassFinder.ClassInfo} * of the class. At this point, the class has been loaded, so it's ok to * perform tests such as checking annotations or looking at interfaces or * super-classes of the specified class. @@ -1121,4 +1138,5 @@ public class PackageBasedActionConfigBuilder implements ActionConfigBuilder { } else return false; } + } diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/util/finder/ClassFinderFactory.java b/xwork-core/src/main/java/com/opensymphony/xwork2/util/finder/ClassFinderFactory.java new file mode 100644 index 000000000..28e47c22e --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/util/finder/ClassFinderFactory.java @@ -0,0 +1,11 @@ +package com.opensymphony.xwork2.util.finder; + +import java.net.URL; +import java.util.Collection; +import java.util.Set; + +public interface ClassFinderFactory { + + ClassFinder buildClassFinder(ClassLoaderInterface classLoaderInterface, Collection urls, boolean extractBaseInterfaces, Set protocols, Test classNameFilter); + +} From 99e53d86428d5b32cadbebc1af0bb8dc62016070 Mon Sep 17 00:00:00 2001 From: Lukasz Lenart Date: Sat, 20 Dec 2014 21:46:42 +0100 Subject: [PATCH 19/41] Adds required legal files --- .../src/main/resources/LICENSE.txt | 174 ++++++++++++++++++ .../src/main/resources/NOTICE.txt | 5 + 2 files changed, 179 insertions(+) create mode 100644 plugins/java8-support/src/main/resources/LICENSE.txt create mode 100644 plugins/java8-support/src/main/resources/NOTICE.txt diff --git a/plugins/java8-support/src/main/resources/LICENSE.txt b/plugins/java8-support/src/main/resources/LICENSE.txt new file mode 100644 index 000000000..dd5b3a58a --- /dev/null +++ b/plugins/java8-support/src/main/resources/LICENSE.txt @@ -0,0 +1,174 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. diff --git a/plugins/java8-support/src/main/resources/NOTICE.txt b/plugins/java8-support/src/main/resources/NOTICE.txt new file mode 100644 index 000000000..bfba90c29 --- /dev/null +++ b/plugins/java8-support/src/main/resources/NOTICE.txt @@ -0,0 +1,5 @@ +Apache Struts +Copyright 2000-2011 The Apache Software Foundation + +This product includes software developed by +The Apache Software Foundation (http://www.apache.org/). \ No newline at end of file From a7cf4294792d898570a765b4f49310371a138f88 Mon Sep 17 00:00:00 2001 From: Lukasz Lenart Date: Sat, 20 Dec 2014 21:47:25 +0100 Subject: [PATCH 20/41] Adds site defintion --- plugins/java8-support/src/site/site.xml | 57 +++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 plugins/java8-support/src/site/site.xml diff --git a/plugins/java8-support/src/site/site.xml b/plugins/java8-support/src/site/site.xml new file mode 100644 index 000000000..07a667ec7 --- /dev/null +++ b/plugins/java8-support/src/site/site.xml @@ -0,0 +1,57 @@ + + + + + org.apache.maven.skins + maven-fluido-skin + 1.3.1 + + + Apache Software Foundation + http://www.apache.org/images/asf-logo.gif + http://www.apache.org/ + + + Apache Struts + http://struts.apache.org/img/struts-logo.svg + http://struts.apache.org/ + + + + + + + + + +

+ + +
+
+ Apache Struts, Struts, Apache, the Apache feather logo, and the Apache Struts + project logos are trademarks of The Apache Software Foundation. +
+
+ + + From 463a90e8b7ce2166ac3a6e25d82fa7e2378cb18c Mon Sep 17 00:00:00 2001 From: Lukasz Lenart Date: Sat, 20 Dec 2014 21:49:36 +0100 Subject: [PATCH 21/41] Adds Java8 specific ClassFinder with injection --- .../struts2/convention/Java8ClassFinder.java | 597 ++++++++++++++++++ .../convention/Java8ClassFinderFactory.java | 48 ++ .../src/main/resources/struts-plugin.xml | 33 + 3 files changed, 678 insertions(+) create mode 100644 plugins/java8-support/src/main/java/org/apache/struts2/convention/Java8ClassFinder.java create mode 100644 plugins/java8-support/src/main/java/org/apache/struts2/convention/Java8ClassFinderFactory.java create mode 100644 plugins/java8-support/src/main/resources/struts-plugin.xml diff --git a/plugins/java8-support/src/main/java/org/apache/struts2/convention/Java8ClassFinder.java b/plugins/java8-support/src/main/java/org/apache/struts2/convention/Java8ClassFinder.java new file mode 100644 index 000000000..21b772cf4 --- /dev/null +++ b/plugins/java8-support/src/main/java/org/apache/struts2/convention/Java8ClassFinder.java @@ -0,0 +1,597 @@ +/* + * Copyright 2002-2003,2009 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.convention; + +import com.opensymphony.xwork2.ActionContext; +import com.opensymphony.xwork2.FileManager; +import com.opensymphony.xwork2.FileManagerFactory; +import com.opensymphony.xwork2.XWorkException; +import com.opensymphony.xwork2.util.finder.ClassFinder; +import com.opensymphony.xwork2.util.finder.ClassLoaderInterface; +import com.opensymphony.xwork2.util.finder.Test; +import com.opensymphony.xwork2.util.logging.Logger; +import com.opensymphony.xwork2.util.logging.LoggerFactory; +import org.apache.commons.lang3.StringUtils; +import org.objectweb.asm.AnnotationVisitor; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.FieldVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.lang.annotation.Annotation; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.net.JarURLConnection; +import java.net.URL; +import java.net.URLDecoder; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.jar.JarEntry; +import java.util.jar.JarInputStream; + +/** + * Copy of {@link com.opensymphony.xwork2.util.finder.DefaultClassFinder} with proper support for Java8 + */ +public class Java8ClassFinder implements ClassFinder { + + private static final Logger LOG = LoggerFactory.getLogger(Java8ClassFinder.class); + + private final Map> annotated = new HashMap>(); + private final Map classInfos = new LinkedHashMap(); + + private final List classesNotLoaded = new ArrayList(); + + private boolean extractBaseInterfaces; + private ClassLoaderInterface classLoaderInterface; + private FileManager fileManager; + + public Java8ClassFinder(ClassLoaderInterface classLoaderInterface, Collection urls, boolean extractBaseInterfaces, Set protocols, Test classNameFilter) { + this.classLoaderInterface = classLoaderInterface; + this.extractBaseInterfaces = extractBaseInterfaces; + this.fileManager = ActionContext.getContext().getInstance(FileManagerFactory.class).getFileManager(); + + List classNames = new ArrayList(); + for (URL location : urls) { + try { + if (protocols.contains(location.getProtocol())) { + classNames.addAll(jar(location)); + } else if ("file".equals(location.getProtocol())) { + try { + // See if it's actually a jar + URL jarUrl = new URL("jar", "", location.toExternalForm() + "!/"); + JarURLConnection juc = (JarURLConnection) jarUrl.openConnection(); + juc.getJarFile(); + classNames.addAll(jar(jarUrl)); + } catch (IOException e) { + classNames.addAll(file(location)); + } + } + } catch (Exception e) { + if (LOG.isErrorEnabled()) + LOG.error("Unable to read URL [#0]", e, location.toExternalForm()); + } + } + + for (String className : classNames) { + try { + if (classNameFilter.test(className)) + readClassDef(className); + } catch (Throwable e) { + if (LOG.isErrorEnabled()) + LOG.error("Unable to read class [#0]", e, className); + } + } + } + + public Java8ClassFinder(Class... classes){ + this(Arrays.asList(classes)); + } + + public Java8ClassFinder(List classes){ + this.classLoaderInterface = null; + List infos = new ArrayList(); + List packages = new ArrayList(); + for (Class clazz : classes) { + + Package aPackage = clazz.getPackage(); + if (aPackage != null && !packages.contains(aPackage)){ + infos.add(new PackageInfo(aPackage)); + packages.add(aPackage); + } + + ClassInfo classInfo = new ClassInfo(clazz, this); + infos.add(classInfo); + classInfos.put(classInfo.getName(), classInfo); + for (Method method : clazz.getDeclaredMethods()) { + infos.add(new MethodInfo(classInfo, method)); + } + + for (Constructor constructor : clazz.getConstructors()) { + infos.add(new MethodInfo(classInfo, constructor)); + } + + for (Field field : clazz.getDeclaredFields()) { + infos.add(new FieldInfo(classInfo, field)); + } + } + + for (Info info : infos) { + for (AnnotationInfo annotation : info.getAnnotations()) { + List annotationInfos = getAnnotationInfos(annotation.getName()); + annotationInfos.add(info); + } + } + } + + public ClassLoaderInterface getClassLoaderInterface() { + return classLoaderInterface; + } + + public boolean isAnnotationPresent(Class annotation) { + List infos = annotated.get(annotation.getName()); + return infos != null && !infos.isEmpty(); + } + + /** + * Returns a list of classes that could not be loaded in last invoked findAnnotated* method. + *

+ * The list will only contain entries of classes whose byte code matched the requirements + * of last invoked find* method, but were unable to be loaded and included in the results. + *

+ * The list returned is unmodifiable. Once obtained, the returned list will be a live view of the + * results from the last findAnnotated* method call. + *

+ * This method is not thread safe. + * @return an unmodifiable live view of classes that could not be loaded in previous findAnnotated* call. + */ + public List getClassesNotLoaded() { + return Collections.unmodifiableList(classesNotLoaded); + } + + public List findAnnotatedPackages(Class annotation) { + classesNotLoaded.clear(); + List packages = new ArrayList(); + List infos = getAnnotationInfos(annotation.getName()); + for (Info info : infos) { + if (info instanceof PackageInfo) { + PackageInfo packageInfo = (PackageInfo) info; + try { + Package pkg = packageInfo.get(); + // double check via proper reflection + if (pkg.isAnnotationPresent(annotation)) { + packages.add(pkg); + } + } catch (ClassNotFoundException e) { + classesNotLoaded.add(packageInfo.getName()); + } + } + } + return packages; + } + + public List findAnnotatedClasses(Class annotation) { + classesNotLoaded.clear(); + List classes = new ArrayList(); + List infos = getAnnotationInfos(annotation.getName()); + for (Info info : infos) { + if (info instanceof ClassInfo) { + ClassInfo classInfo = (ClassInfo) info; + try { + Class clazz = classInfo.get(); + // double check via proper reflection + if (clazz.isAnnotationPresent(annotation)) { + classes.add(clazz); + } + } catch (Throwable e) { + if (LOG.isErrorEnabled()) + LOG.error("Error loading class [#0]", e, classInfo.getName()); + classesNotLoaded.add(classInfo.getName()); + } + } + } + return classes; + } + + public List findAnnotatedMethods(Class annotation) { + classesNotLoaded.clear(); + List seen = new ArrayList(); + List methods = new ArrayList(); + List infos = getAnnotationInfos(annotation.getName()); + for (Info info : infos) { + if (info instanceof MethodInfo && !"".equals(info.getName())) { + MethodInfo methodInfo = (MethodInfo) info; + ClassInfo classInfo = methodInfo.getDeclaringClass(); + + if (seen.contains(classInfo)) continue; + + seen.add(classInfo); + + try { + Class clazz = classInfo.get(); + for (Method method : clazz.getDeclaredMethods()) { + if (method.isAnnotationPresent(annotation)) { + methods.add(method); + } + } + } catch (Throwable e) { + if (LOG.isErrorEnabled()) + LOG.error("Error loading class [#0]", e, classInfo.getName()); + classesNotLoaded.add(classInfo.getName()); + } + } + } + return methods; + } + + public List findAnnotatedConstructors(Class annotation) { + classesNotLoaded.clear(); + List seen = new ArrayList(); + List constructors = new ArrayList(); + List infos = getAnnotationInfos(annotation.getName()); + for (Info info : infos) { + if (info instanceof MethodInfo && "".equals(info.getName())) { + MethodInfo methodInfo = (MethodInfo) info; + ClassInfo classInfo = methodInfo.getDeclaringClass(); + + if (seen.contains(classInfo)) continue; + + seen.add(classInfo); + + try { + Class clazz = classInfo.get(); + for (Constructor constructor : clazz.getConstructors()) { + if (constructor.isAnnotationPresent(annotation)) { + constructors.add(constructor); + } + } + } catch (Throwable e) { + if (LOG.isErrorEnabled()) + LOG.error("Error loading class [#0]", e, classInfo.getName()); + classesNotLoaded.add(classInfo.getName()); + } + } + } + return constructors; + } + + public List findAnnotatedFields(Class annotation) { + classesNotLoaded.clear(); + List seen = new ArrayList(); + List fields = new ArrayList(); + List infos = getAnnotationInfos(annotation.getName()); + for (Info info : infos) { + if (info instanceof FieldInfo) { + FieldInfo fieldInfo = (FieldInfo) info; + ClassInfo classInfo = fieldInfo.getDeclaringClass(); + + if (seen.contains(classInfo)) continue; + + seen.add(classInfo); + + try { + Class clazz = classInfo.get(); + for (Field field : clazz.getDeclaredFields()) { + if (field.isAnnotationPresent(annotation)) { + fields.add(field); + } + } + } catch (Throwable e) { + if (LOG.isErrorEnabled()) + LOG.error("Error loading class [#0]", e, classInfo.getName()); + classesNotLoaded.add(classInfo.getName()); + } + } + } + return fields; + } + + public List findClassesInPackage(String packageName, boolean recursive) { + classesNotLoaded.clear(); + List classes = new ArrayList(); + for (ClassInfo classInfo : classInfos.values()) { + try { + if (recursive && classInfo.getPackageName().startsWith(packageName)){ + classes.add(classInfo.get()); + } else if (classInfo.getPackageName().equals(packageName)){ + classes.add(classInfo.get()); + } + } catch (Throwable e) { + if (LOG.isErrorEnabled()) + LOG.error("Error loading class [#0]", e, classInfo.getName()); + classesNotLoaded.add(classInfo.getName()); + } + } + return classes; + } + + public List findClasses(Test test) { + classesNotLoaded.clear(); + List classes = new ArrayList(); + for (ClassInfo classInfo : classInfos.values()) { + try { + if (test.test(classInfo)) { + classes.add(classInfo.get()); + } + } catch (Throwable e) { + if (LOG.isErrorEnabled()) + LOG.error("Error loading class [#0]", e, classInfo.getName()); + classesNotLoaded.add(classInfo.getName()); + } + } + return classes; + } + + public List findClasses() { + classesNotLoaded.clear(); + List classes = new ArrayList(); + for (ClassInfo classInfo : classInfos.values()) { + try { + classes.add(classInfo.get()); + } catch (Throwable e) { + if (LOG.isErrorEnabled()) + LOG.error("Error loading class [#0]", e, classInfo.getName()); + classesNotLoaded.add(classInfo.getName()); + } + } + return classes; + } + + private static List getURLs(ClassLoaderInterface classLoader, String[] dirNames) { + List urls = new ArrayList(); + for (String dirName : dirNames) { + try { + Enumeration classLoaderURLs = classLoader.getResources(dirName); + while (classLoaderURLs.hasMoreElements()) { + URL url = classLoaderURLs.nextElement(); + urls.add(url); + } + } catch (IOException ioe) { + if (LOG.isErrorEnabled()) + LOG.error("Could not read driectory [#0]", ioe, dirName); + } + } + + return urls; + } + + private List file(URL location) { + List classNames = new ArrayList(); + File dir = new File(URLDecoder.decode(location.getPath())); + if ("META-INF".equals(dir.getName())) { + dir = dir.getParentFile(); // Scrape "META-INF" off + } + if (dir.isDirectory()) { + scanDir(dir, classNames, ""); + } + return classNames; + } + + private void scanDir(File dir, List classNames, String packageName) { + File[] files = dir.listFiles(); + for (File file : files) { + if (file.isDirectory()) { + scanDir(file, classNames, packageName + file.getName() + "."); + } else if (file.getName().endsWith(".class")) { + String name = file.getName(); + name = name.replaceFirst(".class$", ""); + // Classes packaged in an exploded .war (e.g. in a VFS file system) should not + // have WEB-INF.classes in their package name. + classNames.add(StringUtils.removeStart(packageName, "WEB-INF.classes.") + name); + } + } + } + + private List jar(URL location) throws IOException { + URL url = fileManager.normalizeToFileProtocol(location); + if (url != null) { + InputStream in = url.openStream(); + try { + JarInputStream jarStream = new JarInputStream(in); + return jar(jarStream); + } finally { + in.close(); + } + } else if (LOG.isDebugEnabled()) + LOG.debug("Unable to read [#0]", location.toExternalForm()); + + return Collections.emptyList(); + } + + private List jar(JarInputStream jarStream) throws IOException { + List classNames = new ArrayList(); + + JarEntry entry; + while ((entry = jarStream.getNextJarEntry()) != null) { + if (entry.isDirectory() || !entry.getName().endsWith(".class")) { + continue; + } + String className = entry.getName(); + className = className.replaceFirst(".class$", ""); + + //war files are treated as .jar files, so takeout WEB-INF/classes + className = StringUtils.removeStart(className, "WEB-INF/classes/"); + + className = className.replace('/', '.'); + classNames.add(className); + } + + return classNames; + } + + private List getAnnotationInfos(String name) { + List infos = annotated.get(name); + if (infos == null) { + infos = new ArrayList(); + annotated.put(name, infos); + } + return infos; + } + + private void readClassDef(String className) { + if (!className.endsWith(".class")) { + className = className.replace('.', '/') + ".class"; + } + try { + URL resource = classLoaderInterface.getResource(className); + if (resource != null) { + InputStream in = resource.openStream(); + try { + ClassReader classReader = new ClassReader(in); + classReader.accept(new InfoBuildingClassVisitor(this), ClassReader.SKIP_DEBUG); + } finally { + in.close(); + } + } else { + throw new XWorkException("Could not load " + className); + } + } catch (IOException e) { + throw new XWorkException("Could not load " + className, e); + } + + } + + public class InfoBuildingClassVisitor extends ClassVisitor { + private Info info; + private ClassFinder classFinder; + + public InfoBuildingClassVisitor(ClassFinder classFinder) { + super(Opcodes.ASM5); + this.classFinder = classFinder; + } + + public InfoBuildingClassVisitor(Info info, ClassFinder classFinder) { + this(classFinder); + this.info = info; + } + + @Override + public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { + if (name.endsWith("package-info")) { + info = new PackageInfo(javaName(name), classFinder); + } else { + ClassInfo classInfo = new ClassInfo(javaName(name), javaName(superName), classFinder); + + for (String interfce : interfaces) { + classInfo.getInterfaces().add(javaName(interfce)); + } + info = classInfo; + classInfos.put(classInfo.getName(), classInfo); + + if (extractBaseInterfaces) + extractSuperInterfaces(classInfo); + } + } + + private void extractSuperInterfaces(ClassInfo classInfo) { + String superType = classInfo.getSuperType(); + + if (superType != null) { + ClassInfo base = classInfos.get(superType); + + if (base == null) { + //try to load base + String resource = superType.replace('.', '/') + ".class"; + readClassDef(resource); + base = classInfos.get(superType); + } + + if (base != null) { + List interfaces = classInfo.getSuperInterfaces(); + interfaces.addAll(base.getSuperInterfaces()); + interfaces.addAll(base.getInterfaces()); + } + } + } + + private String javaName(String name) { + return (name == null)? null:name.replace('/', '.'); + } + + @Override + public AnnotationVisitor visitAnnotation(String desc, boolean visible) { + AnnotationInfo annotationInfo = new AnnotationInfo(desc); + info.getAnnotations().add(annotationInfo); + getAnnotationInfos(annotationInfo.getName()).add(info); + return null; + } + + @Override + public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { + ClassInfo classInfo = ((ClassInfo) info); + FieldInfo fieldInfo = new FieldInfo(classInfo, name, desc); + classInfo.getFields().add(fieldInfo); + return null; + } + + @Override + public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { + ClassInfo classInfo = ((ClassInfo) info); + MethodInfo methodInfo = new MethodInfo(classInfo, name, desc); + classInfo.getMethods().add(methodInfo); + return new InfoBuildingMethodVisitor(methodInfo); + } + } + + public class InfoBuildingMethodVisitor extends MethodVisitor { + private Info info; + + public InfoBuildingMethodVisitor() { + super(Opcodes.ASM5); + } + + public InfoBuildingMethodVisitor(Info info) { + this(); + this.info = info; + } + + @Override + public AnnotationVisitor visitAnnotation(String desc, boolean visible) { + AnnotationInfo annotationInfo = new AnnotationInfo(desc); + info.getAnnotations().add(annotationInfo); + getAnnotationInfos(annotationInfo.getName()).add(info); + return null; + } + + @Override + public AnnotationVisitor visitParameterAnnotation(int param, String desc, boolean visible) { + MethodInfo methodInfo = ((MethodInfo) info); + List annotationInfos = methodInfo.getParameterAnnotations(param); + AnnotationInfo annotationInfo = new AnnotationInfo(desc); + annotationInfos.add(annotationInfo); + return null; + } + } + + private static final class DefaultClassnameFilterImpl implements Test { + public boolean test(String className) { + return true; + } + } +} + diff --git a/plugins/java8-support/src/main/java/org/apache/struts2/convention/Java8ClassFinderFactory.java b/plugins/java8-support/src/main/java/org/apache/struts2/convention/Java8ClassFinderFactory.java new file mode 100644 index 000000000..3ab5f9eb2 --- /dev/null +++ b/plugins/java8-support/src/main/java/org/apache/struts2/convention/Java8ClassFinderFactory.java @@ -0,0 +1,48 @@ +/* + * Copyright 2002-2003,2009 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.convention; + +import com.opensymphony.xwork2.util.finder.ClassFinder; +import com.opensymphony.xwork2.util.finder.ClassFinderFactory; +import com.opensymphony.xwork2.util.finder.ClassLoaderInterface; +import com.opensymphony.xwork2.util.finder.Test; +import com.opensymphony.xwork2.util.logging.Logger; +import com.opensymphony.xwork2.util.logging.LoggerFactory; + +import java.net.URL; +import java.util.Collection; +import java.util.Set; + +public class Java8ClassFinderFactory implements ClassFinderFactory { + + private static final Logger LOG = LoggerFactory.getLogger(Java8ClassFinderFactory.class); + + public Java8ClassFinderFactory() { + try { + LOG.trace("Checking if ASM5 is on the classpath...."); + Class.forName("org.objectweb.asm.MethodVisitor"); + LOG.trace("Proper version of ASM5 is in use!"); + } catch (ClassNotFoundException e) { + LOG.warn("ASM5 is missing or older version is used! If you use Maven, please exclude asm.jar and asm-commons.jar version 3 from xwork!"); + } + } + + public ClassFinder buildClassFinder(ClassLoaderInterface classLoaderInterface, Collection urls, boolean extractBaseInterfaces, Set protocols, Test classNameFilter) { + LOG.debug("Creating new instance of Java8ClassFinder"); + return new Java8ClassFinder(classLoaderInterface, urls, extractBaseInterfaces, protocols, classNameFilter); + } + +} diff --git a/plugins/java8-support/src/main/resources/struts-plugin.xml b/plugins/java8-support/src/main/resources/struts-plugin.xml new file mode 100644 index 000000000..c765cf817 --- /dev/null +++ b/plugins/java8-support/src/main/resources/struts-plugin.xml @@ -0,0 +1,33 @@ + + + + + + + + + + From 43679fc3f4ae86a375989ad85bc5efcb087823b8 Mon Sep 17 00:00:00 2001 From: Lukasz Lenart Date: Sat, 20 Dec 2014 21:52:01 +0100 Subject: [PATCH 22/41] Adds some JavaDocs --- .../struts2/convention/Java8ClassFinder.java | 12 --------- .../xwork2/util/finder/ClassFinder.java | 24 +++++++++++++++++ .../util/finder/ClassFinderFactory.java | 18 +++++++++++++ .../util/finder/DefaultClassFinder.java | 27 ------------------- 4 files changed, 42 insertions(+), 39 deletions(-) diff --git a/plugins/java8-support/src/main/java/org/apache/struts2/convention/Java8ClassFinder.java b/plugins/java8-support/src/main/java/org/apache/struts2/convention/Java8ClassFinder.java index 21b772cf4..2d49e18bb 100644 --- a/plugins/java8-support/src/main/java/org/apache/struts2/convention/Java8ClassFinder.java +++ b/plugins/java8-support/src/main/java/org/apache/struts2/convention/Java8ClassFinder.java @@ -158,18 +158,6 @@ public class Java8ClassFinder implements ClassFinder { return infos != null && !infos.isEmpty(); } - /** - * Returns a list of classes that could not be loaded in last invoked findAnnotated* method. - *

- * The list will only contain entries of classes whose byte code matched the requirements - * of last invoked find* method, but were unable to be loaded and included in the results. - *

- * The list returned is unmodifiable. Once obtained, the returned list will be a live view of the - * results from the last findAnnotated* method call. - *

- * This method is not thread safe. - * @return an unmodifiable live view of classes that could not be loaded in previous findAnnotated* call. - */ public List getClassesNotLoaded() { return Collections.unmodifiableList(classesNotLoaded); } diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/util/finder/ClassFinder.java b/xwork-core/src/main/java/com/opensymphony/xwork2/util/finder/ClassFinder.java index aed9981c7..cf8fb9b56 100644 --- a/xwork-core/src/main/java/com/opensymphony/xwork2/util/finder/ClassFinder.java +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/util/finder/ClassFinder.java @@ -8,10 +8,34 @@ import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; +/** + * ClassFinder searches the classpath of the specified ClassLoaderInterface for + * packages, classes, constructors, methods, or fields with specific annotations. + * + * For security reasons ASM is used to find the annotations. Classes are not + * loaded unless they match the requirements of a called findAnnotated* method. + * Once loaded, these classes are cached. + * + * The getClassesNotLoaded() method can be used immediately after any find* + * method to get a list of classes which matched the find requirements (i.e. + * contained the annotation), but were unable to be loaded. + */ public interface ClassFinder { boolean isAnnotationPresent(Class annotation); + /** + * Returns a list of classes that could not be loaded in last invoked findAnnotated* method. + *

+ * The list will only contain entries of classes whose byte code matched the requirements + * of last invoked find* method, but were unable to be loaded and included in the results. + *

+ * The list returned is unmodifiable. Once obtained, the returned list will be a live view of the + * results from the last findAnnotated* method call. + *

+ * This method is not thread safe. + * @return an unmodifiable live view of classes that could not be loaded in previous findAnnotated* call. + */ List getClassesNotLoaded(); List findAnnotatedPackages(Class annotation); diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/util/finder/ClassFinderFactory.java b/xwork-core/src/main/java/com/opensymphony/xwork2/util/finder/ClassFinderFactory.java index 28e47c22e..7998c3cfd 100644 --- a/xwork-core/src/main/java/com/opensymphony/xwork2/util/finder/ClassFinderFactory.java +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/util/finder/ClassFinderFactory.java @@ -1,9 +1,27 @@ +/* + * Copyright 2002-2003,2009 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 com.opensymphony.xwork2.util.finder; import java.net.URL; import java.util.Collection; import java.util.Set; +/** + * Allows create different ClassFinders which should help support different Java versions + */ public interface ClassFinderFactory { ClassFinder buildClassFinder(ClassLoaderInterface classLoaderInterface, Collection urls, boolean extractBaseInterfaces, Set protocols, Test classNameFilter); diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/util/finder/DefaultClassFinder.java b/xwork-core/src/main/java/com/opensymphony/xwork2/util/finder/DefaultClassFinder.java index 192196a6c..a80849bdd 100644 --- a/xwork-core/src/main/java/com/opensymphony/xwork2/util/finder/DefaultClassFinder.java +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/util/finder/DefaultClassFinder.java @@ -51,21 +51,6 @@ import java.util.Set; import java.util.jar.JarEntry; import java.util.jar.JarInputStream; -/** - * ClassFinder searches the classpath of the specified ClassLoaderInterface for - * packages, classes, constructors, methods, or fields with specific annotations. - * - * For security reasons ASM is used to find the annotations. Classes are not - * loaded unless they match the requirements of a called findAnnotated* method. - * Once loaded, these classes are cached. - * - * The getClassesNotLoaded() method can be used immediately after any find* - * method to get a list of classes which matched the find requirements (i.e. - * contained the annotation), but were unable to be loaded. - * - * @author David Blevins - * @version $Rev$ $Date$ - */ public class DefaultClassFinder implements ClassFinder { private static final Logger LOG = LoggerFactory.getLogger(DefaultClassFinder.class); @@ -165,18 +150,6 @@ public class DefaultClassFinder implements ClassFinder { return infos != null && !infos.isEmpty(); } - /** - * Returns a list of classes that could not be loaded in last invoked findAnnotated* method. - *

- * The list will only contain entries of classes whose byte code matched the requirements - * of last invoked find* method, but were unable to be loaded and included in the results. - *

- * The list returned is unmodifiable. Once obtained, the returned list will be a live view of the - * results from the last findAnnotated* method call. - *

- * This method is not thread safe. - * @return an unmodifiable live view of classes that could not be loaded in previous findAnnotated* call. - */ public List getClassesNotLoaded() { return Collections.unmodifiableList(classesNotLoaded); } From 702738693ce9206f3023903d73094fe1522cb91c Mon Sep 17 00:00:00 2001 From: Lukasz Lenart Date: Sun, 21 Dec 2014 20:46:40 +0100 Subject: [PATCH 23/41] Adds additional use cases and fixes some minor issues --- .../xwork2/DefaultActionInvocation.java | 11 +- .../xwork2/DefaultActionInvocationTest.java | 177 +++++++++++++++++- .../com/opensymphony/xwork2/SimpleAction.java | 3 + 3 files changed, 177 insertions(+), 14 deletions(-) diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/DefaultActionInvocation.java b/xwork-core/src/main/java/com/opensymphony/xwork2/DefaultActionInvocation.java index f2c28eaab..1bf7ccf4c 100644 --- a/xwork-core/src/main/java/com/opensymphony/xwork2/DefaultActionInvocation.java +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/DefaultActionInvocation.java @@ -426,10 +426,10 @@ public class DefaultActionInvocation implements ActionInvocation { if (e.getReason() instanceof NoSuchMethodException) { try { String altMethodName = "do" + methodName.substring(0, 1).toUpperCase() + methodName.substring(1) + "()"; - methodResult = ognlUtil.getValue(altMethodName, ActionContext.getContext().getContextMap(), action); + methodResult = ognlUtil.getValue(altMethodName, getStack().getContext(), action); } catch (MethodFailedException e1) { // if still method doesn't exist, try checking UnknownHandlers - if (e.getReason() instanceof NoSuchMethodException) { + if (e1.getReason() instanceof NoSuchMethodException) { if (unknownHandlerManager.hasUnknownHandlers()) { try { methodResult = unknownHandlerManager.handleUnknownMethod(action, methodName); @@ -438,6 +438,7 @@ public class DefaultActionInvocation implements ActionInvocation { throw e; } } else { + // throw the original one throw e; } // throw the original exception as UnknownHandlers weren't able to handle invocation as well @@ -445,12 +446,12 @@ public class DefaultActionInvocation implements ActionInvocation { throw e; } } else { - // exception isn't related to missing action method - throw e; + // exception isn't related to missing action method, throw it + throw e1; } } } else { - // exception isn't related to missing action method + // exception isn't related to missing action method, throw it throw e; } } diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/DefaultActionInvocationTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/DefaultActionInvocationTest.java index d0ff2b525..42915381c 100644 --- a/xwork-core/src/test/java/com/opensymphony/xwork2/DefaultActionInvocationTest.java +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/DefaultActionInvocationTest.java @@ -106,6 +106,28 @@ public class DefaultActionInvocationTest extends XWorkTestCase { assertEquals("success", result); } + public void testInvokingExistingDoInputMethod() throws Exception { + // given + DefaultActionInvocation dai = new DefaultActionInvocation(new HashMap(), false) { + public ValueStack getStack() { + return new StubValueStack(); + } + }; + + SimpleAction action = new SimpleAction(); + MockActionProxy proxy = new MockActionProxy(); + proxy.setMethod("with"); + + dai.proxy = proxy; + dai.ognlUtil = new OgnlUtil(); + + // when + String result = dai.invokeAction(action, null); + + // then + assertEquals("with", result); + } + public void testInvokingMissingMethod() throws Exception { // given DefaultActionInvocation dai = new DefaultActionInvocation(new HashMap(), false) { @@ -135,16 +157,16 @@ public class DefaultActionInvocationTest extends XWorkTestCase { dai.unknownHandlerManager = uhm; // when - Throwable expected = null; + Throwable actual = null; try { dai.invokeAction(action, null); } catch (Exception e) { - expected = e; + actual = e; } // then - assertNotNull(expected); - assertTrue(expected instanceof NoSuchMethodException); + assertNotNull(actual); + assertTrue(actual instanceof NoSuchMethodException); } public void testInvokingExistingMethodThatThrowsException() throws Exception { @@ -168,17 +190,154 @@ public class DefaultActionInvocationTest extends XWorkTestCase { dai.ognlUtil = new OgnlUtil(); // when - // when - Throwable expected = null; + Throwable actual = null; try { dai.invokeAction(action, null); } catch (Exception e) { - expected = e; + actual = e; } // then - assertNotNull(expected); - assertTrue(expected instanceof IllegalArgumentException); + assertNotNull(actual); + assertTrue(actual instanceof IllegalArgumentException); + } + + public void testInvokingExistingDoMethodThatThrowsException() throws Exception { + // given + DefaultActionInvocation dai = new DefaultActionInvocation(new HashMap(), false) { + public ValueStack getStack() { + return new StubValueStack(); + } + }; + + UnknownHandlerManager uhm = new DefaultUnknownHandlerManager() { + @Override + public boolean hasUnknownHandlers() { + return false; + } + }; + + SimpleAction action = new SimpleAction() { + @Override + public String doWith() throws Exception { + throw new IllegalArgumentException(); + } + }; + MockActionProxy proxy = new MockActionProxy(); + proxy.setMethod("with"); + + dai.proxy = proxy; + dai.ognlUtil = new OgnlUtil(); + dai.unknownHandlerManager = uhm; + + // when + // when + Throwable actual = null; + try { + dai.invokeAction(action, null); + } catch (Exception e) { + actual = e; + } + + // then + assertNotNull(actual); + assertTrue(actual instanceof IllegalArgumentException); + } + + @Deprecated + public void testUnknownHandlerManagerThatThrowsException() throws Exception { + // given + DefaultActionInvocation dai = new DefaultActionInvocation(new HashMap(), false) { + public ValueStack getStack() { + return new StubValueStack(); + } + }; + + UnknownHandlerManager uhm = new DefaultUnknownHandlerManager() { + @Override + public boolean hasUnknownHandlers() { + return true; + } + + @Override + public Object handleUnknownMethod(Object action, String methodName) throws NoSuchMethodException { + throw new NoSuchMethodException(); + } + }; + + SimpleAction action = new SimpleAction() { + @Override + public String doWith() throws Exception { + throw new IllegalArgumentException(); + } + }; + MockActionProxy proxy = new MockActionProxy(); + proxy.setMethod("notExists"); + + dai.proxy = proxy; + dai.ognlUtil = new OgnlUtil(); + dai.unknownHandlerManager = uhm; + + // when + // when + Throwable actual = null; + try { + dai.invokeAction(action, null); + } catch (Exception e) { + actual = e; + } + + // then + assertNotNull(actual); + assertTrue(actual instanceof NoSuchMethodException); + } + + @Deprecated + public void testUnknownHandlerManagerThatReturnsNull() throws Exception { + // given + DefaultActionInvocation dai = new DefaultActionInvocation(new HashMap(), false) { + public ValueStack getStack() { + return new StubValueStack(); + } + }; + + UnknownHandlerManager uhm = new DefaultUnknownHandlerManager() { + @Override + public boolean hasUnknownHandlers() { + return true; + } + + @Override + public Object handleUnknownMethod(Object action, String methodName) throws NoSuchMethodException { + return null; + } + }; + + SimpleAction action = new SimpleAction() { + @Override + public String doWith() throws Exception { + throw new IllegalArgumentException(); + } + }; + MockActionProxy proxy = new MockActionProxy(); + proxy.setMethod("notExists"); + + dai.proxy = proxy; + dai.ognlUtil = new OgnlUtil(); + dai.unknownHandlerManager = uhm; + + // when + // when + Throwable actual = null; + try { + dai.invokeAction(action, null); + } catch (Exception e) { + actual = e; + } + + // then + assertNotNull(actual); + assertTrue(actual instanceof NoSuchMethodException); } } diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/SimpleAction.java b/xwork-core/src/test/java/com/opensymphony/xwork2/SimpleAction.java index d22d231c1..6a180a1e8 100644 --- a/xwork-core/src/test/java/com/opensymphony/xwork2/SimpleAction.java +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/SimpleAction.java @@ -249,6 +249,9 @@ public class SimpleAction extends ActionSupport { return INPUT; } + public String doWith() throws Exception { + return "with"; + } public long getLongFoo() { return longFoo; From adc2d5ccf9e3495164380de183422c0736fc39cd Mon Sep 17 00:00:00 2001 From: Lukasz Lenart Date: Tue, 23 Dec 2014 07:45:58 +0100 Subject: [PATCH 24/41] Adds fixed Jetty version --- apps/portlet/pom.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/portlet/pom.xml b/apps/portlet/pom.xml index cc311695d..bb403eb13 100644 --- a/apps/portlet/pom.xml +++ b/apps/portlet/pom.xml @@ -62,6 +62,7 @@ org.mortbay.jetty maven-jetty-plugin + 6.1.26 ${project.build.directory}/pluto-resources/web.xml src/main/webapp/WEB-INF/jetty-pluto-web-default.xml @@ -79,7 +80,7 @@ com.bekk.boss maven-jetty-pluto-embedded - 1.0 + 1.0.1 From b11bb2574bd406abad17ed3aacc9016d7ba9fd1d Mon Sep 17 00:00:00 2001 From: Lukasz Lenart Date: Tue, 23 Dec 2014 08:19:28 +0100 Subject: [PATCH 25/41] Updates docs url --- assembly/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assembly/pom.xml b/assembly/pom.xml index d07fc04f7..d2f3a30ed 100644 --- a/assembly/pom.xml +++ b/assembly/pom.xml @@ -102,7 +102,7 @@ - + From 22fbc800f22ba55a78084c57259226e3198ba181 Mon Sep 17 00:00:00 2001 From: Lukasz Lenart Date: Tue, 23 Dec 2014 09:35:31 +0100 Subject: [PATCH 26/41] Adds basic readme --- plugins/java8-support/README.adoc | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 plugins/java8-support/README.adoc diff --git a/plugins/java8-support/README.adoc b/plugins/java8-support/README.adoc new file mode 100644 index 000000000..beae27c45 --- /dev/null +++ b/plugins/java8-support/README.adoc @@ -0,0 +1,11 @@ += Struts 2 Java 8 Support plugin + +This plugin aims to add support for Java 8 specific features. +As Struts 2 core targets Java 6/7 and some features won't work and they have to be adjusted. +Below is a list of features supported by this plugin + +== Supported Java 8 features +- Lambada expressions in actions when using them with the Convention plugin + +== Installation +Just drop this plugin into `WEB-INF/lib` folder or add it as Maven dependency From 4964b74797971751bedad0ba0982f53ef0934a2c Mon Sep 17 00:00:00 2001 From: Lukasz Lenart Date: Tue, 23 Dec 2014 10:55:00 +0100 Subject: [PATCH 27/41] WW-4430 Resolves problem with missing setter and JasperException --- .../struts2/views/jsp/ui/AbstractUITagBeanInfo.java | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/org/apache/struts2/views/jsp/ui/AbstractUITagBeanInfo.java b/core/src/main/java/org/apache/struts2/views/jsp/ui/AbstractUITagBeanInfo.java index 87e410515..21c5d0029 100644 --- a/core/src/main/java/org/apache/struts2/views/jsp/ui/AbstractUITagBeanInfo.java +++ b/core/src/main/java/org/apache/struts2/views/jsp/ui/AbstractUITagBeanInfo.java @@ -45,15 +45,20 @@ public class AbstractUITagBeanInfo extends SimpleBeanInfo { List descriptors = new ArrayList(); // Add the tricky one first - Method setter = AbstractUITag.class.getMethod("setCssClass", String.class); - descriptors.add(new PropertyDescriptor("class", null, setter)); - descriptors.add(new PropertyDescriptor("cssClass", null, setter)); + Method classSetter = AbstractUITag.class.getMethod("setCssClass", String.class); + Method styleSetter = AbstractUITag.class.getMethod("setCssStyle", String.class); + + descriptors.add(new PropertyDescriptor("class", null, classSetter)); + descriptors.add(new PropertyDescriptor("cssClass", null, classSetter)); + + descriptors.add(new PropertyDescriptor("style", null, styleSetter)); + descriptors.add(new PropertyDescriptor("cssStyle", null, styleSetter)); for (Field field : AbstractUITag.class.getDeclaredFields()) { String fieldName = field.getName(); if (!"dynamicAttributes".equals(fieldName)) { String setterName = "set" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1); - setter = AbstractUITag.class.getMethod(setterName, String.class); + Method setter = AbstractUITag.class.getMethod(setterName, String.class); descriptors.add(new PropertyDescriptor(fieldName, null, setter)); } } From 40822d67f5b6b667bb2760986cb78efc9e2e3ac4 Mon Sep 17 00:00:00 2001 From: Lukasz Lenart Date: Tue, 23 Dec 2014 13:29:17 +0100 Subject: [PATCH 28/41] WW-4437 Fixes problem with accepted params --- .../interceptor/CookieInterceptor.java | 37 ++++++++++-------- .../interceptor/CookieInterceptorTest.java | 38 ++++++++++++------- 2 files changed, 45 insertions(+), 30 deletions(-) diff --git a/core/src/main/java/org/apache/struts2/interceptor/CookieInterceptor.java b/core/src/main/java/org/apache/struts2/interceptor/CookieInterceptor.java index ca195faa3..06c4c30ed 100644 --- a/core/src/main/java/org/apache/struts2/interceptor/CookieInterceptor.java +++ b/core/src/main/java/org/apache/struts2/interceptor/CookieInterceptor.java @@ -25,6 +25,7 @@ import com.opensymphony.xwork2.ActionContext; import com.opensymphony.xwork2.ActionInvocation; import com.opensymphony.xwork2.inject.Inject; import com.opensymphony.xwork2.interceptor.AbstractInterceptor; +import com.opensymphony.xwork2.security.AcceptedPatternsChecker; import com.opensymphony.xwork2.security.ExcludedPatternsChecker; import com.opensymphony.xwork2.util.TextParseUtil; import com.opensymphony.xwork2.util.ValueStack; @@ -37,7 +38,6 @@ import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; -import java.util.regex.Pattern; /** * @@ -174,16 +174,20 @@ public class CookieInterceptor extends AbstractInterceptor { private Set cookiesNameSet = Collections.emptySet(); private Set cookiesValueSet = Collections.emptySet(); - // Allowed names of cookies - private Pattern acceptedPattern = Pattern.compile(ACCEPTED_PATTERN, Pattern.CASE_INSENSITIVE); - private ExcludedPatternsChecker excludedPatternsChecker; + private AcceptedPatternsChecker acceptedPatternsChecker; @Inject public void setExcludedPatternsChecker(ExcludedPatternsChecker excludedPatternsChecker) { this.excludedPatternsChecker = excludedPatternsChecker; } + @Inject + public void setAcceptedPatternsChecker(AcceptedPatternsChecker acceptedPatternsChecker) { + this.acceptedPatternsChecker = acceptedPatternsChecker; + this.acceptedPatternsChecker.setAcceptedPatterns(ACCEPTED_PATTERN); + } + /** * Set the cookiesName which if matched will allow the cookie * to be injected into action, could be comma-separated string. @@ -208,12 +212,13 @@ public class CookieInterceptor extends AbstractInterceptor { } /** - * Set the acceptCookieNames pattern of allowed names of cookies to protect against remote command execution vulnerability + * Set the acceptCookieNames pattern of allowed names of cookies + * to protect against remote command execution vulnerability. * - * @param pattern used to check cookie name against + * @param commaDelimitedPattern is used to check cookie name against, can set of comma delimited patterns */ - public void setAcceptCookieNames(String pattern) { - acceptedPattern = Pattern.compile(pattern); + public void setAcceptCookieNames(String commaDelimitedPattern) { + acceptedPatternsChecker.setAcceptedPatterns(commaDelimitedPattern); } public String intercept(ActionInvocation invocation) throws Exception { @@ -280,17 +285,17 @@ public class CookieInterceptor extends AbstractInterceptor { * @return true|false */ protected boolean isAccepted(String name) { - boolean matches = acceptedPattern.matcher(name).matches(); - if (matches) { + AcceptedPatternsChecker.IsAccepted accepted = acceptedPatternsChecker.isAccepted(name); + if (accepted.isAccepted()) { if (LOG.isTraceEnabled()) { - LOG.trace("Cookie [#0] matches acceptedPattern [#1]", name, ACCEPTED_PATTERN); - } - } else { - if (LOG.isTraceEnabled()) { - LOG.trace("Cookie [#0] doesn't match acceptedPattern [#1]", name, ACCEPTED_PATTERN); + LOG.trace("Cookie [#0] matches acceptedPattern [#1]", name, accepted.getAcceptedPattern()); } + return true; } - return matches; + if (LOG.isTraceEnabled()) { + LOG.trace("Cookie [#0] doesn't match acceptedPattern [#1]", name, accepted.getAcceptedPattern()); + } + return false; } /** diff --git a/core/src/test/java/org/apache/struts2/interceptor/CookieInterceptorTest.java b/core/src/test/java/org/apache/struts2/interceptor/CookieInterceptorTest.java index a531a69d7..c73038224 100644 --- a/core/src/test/java/org/apache/struts2/interceptor/CookieInterceptorTest.java +++ b/core/src/test/java/org/apache/struts2/interceptor/CookieInterceptorTest.java @@ -27,6 +27,7 @@ import java.util.Map; import javax.servlet.http.Cookie; +import com.opensymphony.xwork2.security.DefaultAcceptedPatternsChecker; import com.opensymphony.xwork2.security.DefaultExcludedPatternsChecker; import com.opensymphony.xwork2.mock.MockActionInvocation; import org.easymock.MockControl; @@ -44,11 +45,11 @@ public class CookieInterceptorTest extends StrutsInternalTestCase { public void testIntercepDefault() throws Exception { MockHttpServletRequest request = new MockHttpServletRequest(); - request.setCookies(new Cookie[] { + request.setCookies( new Cookie("cookie1", "cookie1value"), new Cookie("cookie2", "cookie2value"), new Cookie("cookie3", "cookie3value") - }); + ); ServletActionContext.setRequest(request); MockActionWithCookieAware action = new MockActionWithCookieAware(); @@ -67,6 +68,7 @@ public class CookieInterceptorTest extends StrutsInternalTestCase { // by default the interceptor doesn't accept any cookies CookieInterceptor interceptor = new CookieInterceptor(); interceptor.setExcludedPatternsChecker(new DefaultExcludedPatternsChecker()); + interceptor.setAcceptedPatternsChecker(new DefaultAcceptedPatternsChecker()); interceptor.intercept(invocation); @@ -81,11 +83,11 @@ public class CookieInterceptorTest extends StrutsInternalTestCase { public void testInterceptAll1() throws Exception { MockHttpServletRequest request = new MockHttpServletRequest(); - request.setCookies(new Cookie[] { + request.setCookies( new Cookie("cookie1", "cookie1value"), new Cookie("cookie2", "cookie2value"), new Cookie("cookie3", "cookie3value") - }); + ); ServletActionContext.setRequest(request); MockActionWithCookieAware action = new MockActionWithCookieAware(); @@ -103,6 +105,7 @@ public class CookieInterceptorTest extends StrutsInternalTestCase { CookieInterceptor interceptor = new CookieInterceptor(); interceptor.setExcludedPatternsChecker(new DefaultExcludedPatternsChecker()); + interceptor.setAcceptedPatternsChecker(new DefaultAcceptedPatternsChecker()); interceptor.setCookiesName("*"); interceptor.setCookiesValue("*"); interceptor.intercept(invocation); @@ -123,11 +126,11 @@ public class CookieInterceptorTest extends StrutsInternalTestCase { public void testInterceptAll2() throws Exception { MockHttpServletRequest request = new MockHttpServletRequest(); - request.setCookies(new Cookie[] { + request.setCookies( new Cookie("cookie1", "cookie1value"), new Cookie("cookie2", "cookie2value"), new Cookie("cookie3", "cookie3value") - }); + ); ServletActionContext.setRequest(request); MockActionWithCookieAware action = new MockActionWithCookieAware(); @@ -145,6 +148,7 @@ public class CookieInterceptorTest extends StrutsInternalTestCase { CookieInterceptor interceptor = new CookieInterceptor(); interceptor.setExcludedPatternsChecker(new DefaultExcludedPatternsChecker()); + interceptor.setAcceptedPatternsChecker(new DefaultAcceptedPatternsChecker()); interceptor.setCookiesName("cookie1, cookie2, cookie3"); interceptor.setCookiesValue("cookie1value, cookie2value, cookie3value"); interceptor.intercept(invocation); @@ -164,11 +168,11 @@ public class CookieInterceptorTest extends StrutsInternalTestCase { public void testInterceptSelectedCookiesNameOnly1() throws Exception { MockHttpServletRequest request = new MockHttpServletRequest(); - request.setCookies(new Cookie[] { + request.setCookies( new Cookie("cookie1", "cookie1value"), new Cookie("cookie2", "cookie2value"), new Cookie("cookie3", "cookie3value") - }); + ); ServletActionContext.setRequest(request); MockActionWithCookieAware action = new MockActionWithCookieAware(); @@ -186,6 +190,7 @@ public class CookieInterceptorTest extends StrutsInternalTestCase { CookieInterceptor interceptor = new CookieInterceptor(); interceptor.setExcludedPatternsChecker(new DefaultExcludedPatternsChecker()); + interceptor.setAcceptedPatternsChecker(new DefaultAcceptedPatternsChecker()); interceptor.setCookiesName("cookie1, cookie3"); interceptor.setCookiesValue("cookie1value, cookie2value, cookie3value"); interceptor.intercept(invocation); @@ -205,11 +210,11 @@ public class CookieInterceptorTest extends StrutsInternalTestCase { public void testInterceptSelectedCookiesNameOnly2() throws Exception { MockHttpServletRequest request = new MockHttpServletRequest(); - request.setCookies(new Cookie[] { + request.setCookies( new Cookie("cookie1", "cookie1value"), new Cookie("cookie2", "cookie2value"), new Cookie("cookie3", "cookie3value") - }); + ); ServletActionContext.setRequest(request); MockActionWithCookieAware action = new MockActionWithCookieAware(); @@ -227,6 +232,7 @@ public class CookieInterceptorTest extends StrutsInternalTestCase { CookieInterceptor interceptor = new CookieInterceptor(); interceptor.setExcludedPatternsChecker(new DefaultExcludedPatternsChecker()); + interceptor.setAcceptedPatternsChecker(new DefaultAcceptedPatternsChecker()); interceptor.setCookiesName("cookie1, cookie3"); interceptor.setCookiesValue("*"); interceptor.intercept(invocation); @@ -246,11 +252,11 @@ public class CookieInterceptorTest extends StrutsInternalTestCase { public void testInterceptSelectedCookiesNameOnly3() throws Exception { MockHttpServletRequest request = new MockHttpServletRequest(); - request.setCookies(new Cookie[] { + request.setCookies( new Cookie("cookie1", "cookie1value"), new Cookie("cookie2", "cookie2value"), new Cookie("cookie3", "cookie3value") - }); + ); ServletActionContext.setRequest(request); MockActionWithCookieAware action = new MockActionWithCookieAware(); @@ -268,6 +274,7 @@ public class CookieInterceptorTest extends StrutsInternalTestCase { CookieInterceptor interceptor = new CookieInterceptor(); interceptor.setExcludedPatternsChecker(new DefaultExcludedPatternsChecker()); + interceptor.setAcceptedPatternsChecker(new DefaultAcceptedPatternsChecker()); interceptor.setCookiesName("cookie1, cookie3"); interceptor.setCookiesValue(""); interceptor.intercept(invocation); @@ -288,11 +295,11 @@ public class CookieInterceptorTest extends StrutsInternalTestCase { public void testInterceptSelectedCookiesNameAndValue() throws Exception { MockHttpServletRequest request = new MockHttpServletRequest(); - request.setCookies(new Cookie[] { + request.setCookies( new Cookie("cookie1", "cookie1value"), new Cookie("cookie2", "cookie2value"), new Cookie("cookie3", "cookie3value") - }); + ); ServletActionContext.setRequest(request); MockActionWithCookieAware action = new MockActionWithCookieAware(); @@ -310,6 +317,7 @@ public class CookieInterceptorTest extends StrutsInternalTestCase { CookieInterceptor interceptor = new CookieInterceptor(); interceptor.setExcludedPatternsChecker(new DefaultExcludedPatternsChecker()); + interceptor.setAcceptedPatternsChecker(new DefaultAcceptedPatternsChecker()); interceptor.setCookiesName("cookie1, cookie3"); interceptor.setCookiesValue("cookie1value"); interceptor.intercept(invocation); @@ -371,6 +379,7 @@ public class CookieInterceptorTest extends StrutsInternalTestCase { } }; interceptor.setExcludedPatternsChecker(new DefaultExcludedPatternsChecker()); + interceptor.setAcceptedPatternsChecker(new DefaultAcceptedPatternsChecker()); interceptor.setCookiesName("*"); MockActionInvocation invocation = new MockActionInvocation(); @@ -431,6 +440,7 @@ public class CookieInterceptorTest extends StrutsInternalTestCase { } }; interceptor.setExcludedPatternsChecker(new DefaultExcludedPatternsChecker()); + interceptor.setAcceptedPatternsChecker(new DefaultAcceptedPatternsChecker()); interceptor.setCookiesName("*"); MockActionInvocation invocation = new MockActionInvocation(); From 3a0350f0dc9f27141543bdad62f4881e7eaca6c0 Mon Sep 17 00:00:00 2001 From: Lukasz Lenart Date: Tue, 23 Dec 2014 16:07:58 +0100 Subject: [PATCH 29/41] Adds additional use case when value from conext is null --- .../opensymphony/xwork2/ognl/OgnlValueStackTest.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/ognl/OgnlValueStackTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/ognl/OgnlValueStackTest.java index fe045847b..8c7c3ae44 100644 --- a/xwork-core/src/test/java/com/opensymphony/xwork2/ognl/OgnlValueStackTest.java +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/ognl/OgnlValueStackTest.java @@ -103,6 +103,16 @@ public class OgnlValueStackTest extends XWorkTestCase { assertEquals(propertyValue, vs.findValue(propertyName, String.class)); } + public void testNullValueFromContextGetsConverted() { + final OgnlValueStack vs = createValueStack(); + + final String propertyName = "dogName"; + final String propertyValue = null; + vs.getContext().put(propertyName, propertyValue); + + assertEquals(propertyValue, vs.findValue(propertyName, String.class)); + } + public void testFailOnException() { OgnlValueStack vs = createValueStack(); From a35c3ef4f6211268e04f4c167f14650c88505628 Mon Sep 17 00:00:00 2001 From: Lukasz Lenart Date: Tue, 23 Dec 2014 17:04:26 +0100 Subject: [PATCH 30/41] WW-4416 Adds support to clear cache under Tomcat 8 --- .../xwork2/util/LocalizedTextUtil.java | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/util/LocalizedTextUtil.java b/xwork-core/src/main/java/com/opensymphony/xwork2/util/LocalizedTextUtil.java index b7e147f22..1e51dedfd 100644 --- a/xwork-core/src/main/java/com/opensymphony/xwork2/util/LocalizedTextUtil.java +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/util/LocalizedTextUtil.java @@ -88,6 +88,8 @@ public class LocalizedTextUtil { private static final Logger LOG = LoggerFactory.getLogger(LocalizedTextUtil.class); + private static final String TOMCAT_RESOURCE_ENTRIES_FIELD = "resourceEntries"; + private static final ConcurrentMap> classLoaderMap = new ConcurrentHashMap>(); private static boolean reloadBundles = false; @@ -839,15 +841,28 @@ public class LocalizedTextUtil { try { if ("org.apache.catalina.loader.WebappClassLoader".equals(cl.getName())) { - clearMap(cl, loader, "resourceEntries"); + clearMap(cl, loader, TOMCAT_RESOURCE_ENTRIES_FIELD); } else { if (LOG.isDebugEnabled()) { LOG.debug("class loader " + cl.getName() + " is not tomcat loader."); } } + } catch (NoSuchFieldException nsfe) { + if ("org.apache.catalina.loader.WebappClassLoaderBase".equals(cl.getSuperclass().getName())) { + if (LOG.isDebugEnabled()) { + LOG.debug("Base class #0 doesn't contain '#1' field, trying with parent!", nsfe, cl.getName(), TOMCAT_RESOURCE_ENTRIES_FIELD); + } + try { + clearMap(cl.getSuperclass(), loader, TOMCAT_RESOURCE_ENTRIES_FIELD); + } catch (Exception e) { + if (LOG.isWarnEnabled()) { + LOG.warn("Couldn't clear tomcat cache using #0", e, cl.getSuperclass().getName()); + } + } + } } catch (Exception e) { if (LOG.isWarnEnabled()) { - LOG.warn("couldn't clear tomcat cache", e); + LOG.warn("Couldn't clear tomcat cache", e, cl.getName()); } } } From 532841d40f164a8d8ae6ac0b85b60d3cf6db0011 Mon Sep 17 00:00:00 2001 From: Lukasz Lenart Date: Tue, 23 Dec 2014 21:29:30 +0100 Subject: [PATCH 31/41] WW-4429 Fixes support for accessing static methods --- .../xwork2/ognl/SecurityMemberAccess.java | 14 ++++++- .../xwork2/ognl/SecurityMemberAccessTest.java | 37 ++++++++++++++++++- 2 files changed, 48 insertions(+), 3 deletions(-) diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/ognl/SecurityMemberAccess.java b/xwork-core/src/main/java/com/opensymphony/xwork2/ognl/SecurityMemberAccess.java index a172237f4..6c9d64c43 100644 --- a/xwork-core/src/main/java/com/opensymphony/xwork2/ognl/SecurityMemberAccess.java +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/ognl/SecurityMemberAccess.java @@ -60,14 +60,24 @@ public class SecurityMemberAccess extends DefaultMemberAccess { return true; } - if (isPackageExcluded(target.getClass().getPackage(), member.getDeclaringClass().getPackage())) { + Class targetClass = target.getClass(); + Class memberClass = member.getDeclaringClass(); + + if (Modifier.isStatic(member.getModifiers()) && allowStaticMethodAccess) { + if (LOG.isWarnEnabled()) { + LOG.warn("Support for accessing static methods is deprecated! Please refactor your application!"); + } + targetClass = member.getDeclaringClass(); + } + + if (isPackageExcluded(targetClass.getPackage(), memberClass.getPackage())) { if (LOG.isWarnEnabled()) { LOG.warn("Package of target [#0] or package of member [#1] are excluded!", target, member); } return false; } - if (isClassExcluded(target.getClass(), member.getDeclaringClass())) { + if (isClassExcluded(targetClass, memberClass)) { if (LOG.isWarnEnabled()) { LOG.warn("Target class [#0] or declaring class of member type [#1] are excluded!", target, member); } diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/ognl/SecurityMemberAccessTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/ognl/SecurityMemberAccessTest.java index 61a91a057..11ff9d0aa 100644 --- a/xwork-core/src/test/java/com/opensymphony/xwork2/ognl/SecurityMemberAccessTest.java +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/ognl/SecurityMemberAccessTest.java @@ -3,6 +3,7 @@ package com.opensymphony.xwork2.ognl; import junit.framework.TestCase; import java.lang.reflect.Member; +import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Map; @@ -202,6 +203,32 @@ public class SecurityMemberAccessTest extends TestCase { assertTrue("Access to enums is blocked!", actual); } + public void testAccessStatic() throws Exception { + // given + SecurityMemberAccess sma = new SecurityMemberAccess(true); + sma.setExcludedClasses(new HashSet>(Arrays.>asList(Class.class))); + + // when + Member method = StaticTester.class.getMethod("sayHello"); + boolean actual = sma.isAccessible(context, Class.class, method, null); + + // then + assertTrue("Access to static is blocked!", actual); + } + + public void testBlockStaticAccess() throws Exception { + // given + SecurityMemberAccess sma = new SecurityMemberAccess(false); + sma.setExcludedClasses(new HashSet>(Arrays.>asList(Class.class))); + + // when + Member method = StaticTester.class.getMethod("sayHello"); + boolean actual = sma.isAccessible(context, Class.class, method, null); + + // then + assertFalse("Access to static isn't blocked!", actual); + } + } class FooBar implements FooBarInterface { @@ -249,4 +276,12 @@ interface FooBarInterface extends FooInterface, BarInterface { enum MyValues { ONE, TWO, THREE -} \ No newline at end of file +} + +class StaticTester { + + public static String sayHello() { + return "Hello"; + } + +} From 2b150d87759d08cde08aa90ab78d28946836c7f8 Mon Sep 17 00:00:00 2001 From: Lukasz Lenart Date: Tue, 23 Dec 2014 21:48:43 +0100 Subject: [PATCH 32/41] Adds logging and proper header with licence --- .../sitemesh/StrutsSiteMeshFactory.java | 32 +++++++++++++++++-- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/plugins/sitemesh/src/main/java/org/apache/struts2/sitemesh/StrutsSiteMeshFactory.java b/plugins/sitemesh/src/main/java/org/apache/struts2/sitemesh/StrutsSiteMeshFactory.java index 29eb052f6..96405fb37 100644 --- a/plugins/sitemesh/src/main/java/org/apache/struts2/sitemesh/StrutsSiteMeshFactory.java +++ b/plugins/sitemesh/src/main/java/org/apache/struts2/sitemesh/StrutsSiteMeshFactory.java @@ -1,15 +1,36 @@ +/* + * 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.sitemesh; import com.opensymphony.module.sitemesh.Config; import com.opensymphony.module.sitemesh.factory.DefaultFactory; import com.opensymphony.xwork2.ActionContext; +import com.opensymphony.xwork2.util.logging.Logger; +import com.opensymphony.xwork2.util.logging.LoggerFactory; import org.apache.commons.lang3.ObjectUtils; import org.apache.struts2.ServletActionContext; import org.apache.struts2.StrutsStatics; - public class StrutsSiteMeshFactory extends DefaultFactory { + private static final Logger LOG = LoggerFactory.getLogger(StrutsSiteMeshFactory.class); + public StrutsSiteMeshFactory(Config config) { super(config); } @@ -23,9 +44,14 @@ public class StrutsSiteMeshFactory extends DefaultFactory { } private boolean isInsideActionTag() { - if(ActionContext.getContext() == null) - return false; + if(ActionContext.getContext() == null) { + if (LOG.isTraceEnabled()) { + LOG.trace("ActionContext is null! Not a user request?"); + } + return false; + } Object attribute = ServletActionContext.getRequest().getAttribute(StrutsStatics.STRUTS_ACTION_TAG_INVOCATION); return (Boolean) ObjectUtils.defaultIfNull(attribute, Boolean.FALSE); } + } From 2bea99e96b448585caa0080ac1dc5faa1db85826 Mon Sep 17 00:00:00 2001 From: Lukasz Lenart Date: Tue, 23 Dec 2014 21:54:01 +0100 Subject: [PATCH 33/41] WW-4436 Fixes NPE and uses String.valueOf() instead of .toString() --- .../opensymphony/xwork2/interceptor/ParametersInterceptor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/ParametersInterceptor.java b/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/ParametersInterceptor.java index d26d09457..8317feb1a 100644 --- a/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/ParametersInterceptor.java +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/ParametersInterceptor.java @@ -362,7 +362,7 @@ public class ParametersInterceptor extends MethodFilterInterceptor { } boolean result = true; for (Object obj : values) { - if (isExcluded(obj.toString())) { + if (isExcluded(String.valueOf(obj))) { result = false; } } From ddac7f3a54917fd7249703e69c37ee96f79d27f7 Mon Sep 17 00:00:00 2001 From: Lukasz Lenart Date: Tue, 23 Dec 2014 22:07:51 +0100 Subject: [PATCH 34/41] WW-4432 Fixes access to javax.servlet package --- core/src/main/resources/struts-default.xml | 2 +- .../SecurityMemberAccessInServletsTest.java | 81 +++++++++++++++++++ 2 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 core/src/test/java/org/apache/struts2/util/SecurityMemberAccessInServletsTest.java diff --git a/core/src/main/resources/struts-default.xml b/core/src/main/resources/struts-default.xml index 43f69ed42..c6eec3496 100644 --- a/core/src/main/resources/struts-default.xml +++ b/core/src/main/resources/struts-default.xml @@ -52,7 +52,7 @@ ognl.TypeConverter, com.opensymphony.xwork2.ActionContext" /> - + diff --git a/core/src/test/java/org/apache/struts2/util/SecurityMemberAccessInServletsTest.java b/core/src/test/java/org/apache/struts2/util/SecurityMemberAccessInServletsTest.java new file mode 100644 index 000000000..3a8526875 --- /dev/null +++ b/core/src/test/java/org/apache/struts2/util/SecurityMemberAccessInServletsTest.java @@ -0,0 +1,81 @@ +/* + * $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.util; + +import com.opensymphony.xwork2.ognl.SecurityMemberAccess; +import org.apache.struts2.StrutsInternalTestCase; +import org.apache.struts2.TestAction; + +import javax.servlet.jsp.tagext.TagSupport; +import java.lang.reflect.Member; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.regex.Pattern; + +public class SecurityMemberAccessInServletsTest extends StrutsInternalTestCase { + + private Map context; + + @Override + public void setUp() throws Exception { + context = new HashMap(); + } + + public void testJavaxServletPackageAccess() throws Exception { + // given + SecurityMemberAccess sma = new SecurityMemberAccess(false); + + Set excluded = new HashSet(); + excluded.add(Pattern.compile("^(?!javax\\.servlet\\..+)(javax\\..+)")); + sma.setExcludedPackageNamePatterns(excluded); + + String propertyName = "value"; + Member member = TagSupport.class.getMethod("doStartTag"); + + // when + boolean actual = sma.isAccessible(context, new TestAction(), member, propertyName); + + // then + assertTrue("javax.servlet package isn't accessible!", actual); + } + + public void testJavaxServletPackageExclusion() throws Exception { + // given + SecurityMemberAccess sma = new SecurityMemberAccess(false); + + Set excluded = new HashSet(); + excluded.add(Pattern.compile("^javax\\..+")); + sma.setExcludedPackageNamePatterns(excluded); + + String propertyName = "value"; + Member member = TagSupport.class.getMethod("doStartTag"); + + // when + boolean actual = sma.isAccessible(context, new TestAction(), member, propertyName); + + // then + assertFalse("javax.servlet package is accessible!", actual); + } + +} From f1c04d6f4d4f2af6244977a15714aca18fdc3224 Mon Sep 17 00:00:00 2001 From: Lukasz Lenart Date: Tue, 23 Dec 2014 22:18:30 +0100 Subject: [PATCH 35/41] WW-4424 Fixes log message to properly report missing property --- .../main/java/com/opensymphony/xwork2/ognl/OgnlValueStack.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/ognl/OgnlValueStack.java b/xwork-core/src/main/java/com/opensymphony/xwork2/ognl/OgnlValueStack.java index 48e524163..7fa70f7c8 100644 --- a/xwork-core/src/main/java/com/opensymphony/xwork2/ognl/OgnlValueStack.java +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/ognl/OgnlValueStack.java @@ -331,7 +331,7 @@ public class OgnlValueStack implements Serializable, ValueStack, ClearableValueS Object ret = findInContext(expr); if (ret == null) { if (shouldLogMissingPropertyWarning(e)) { - LOG.warn("Could not find property [" + ((NoSuchPropertyException) e).getName() + "]"); + LOG.warn("Could not find property [#0]!", e, expr); } if (throwExceptionOnFailure) { throw new XWorkException(e); From 095018c3a022fbc867ace58942139c395a272fd8 Mon Sep 17 00:00:00 2001 From: Lukasz Lenart Date: Fri, 26 Dec 2014 20:48:44 +0100 Subject: [PATCH 36/41] WW-4429 Simplifies isClassExcluded interface --- .../xwork2/ognl/SecurityMemberAccess.java | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/ognl/SecurityMemberAccess.java b/xwork-core/src/main/java/com/opensymphony/xwork2/ognl/SecurityMemberAccess.java index 6c9d64c43..78882458f 100644 --- a/xwork-core/src/main/java/com/opensymphony/xwork2/ognl/SecurityMemberAccess.java +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/ognl/SecurityMemberAccess.java @@ -20,7 +20,6 @@ import com.opensymphony.xwork2.util.logging.LoggerFactory; import ognl.DefaultMemberAccess; import java.lang.reflect.Member; -import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.Collections; import java.util.Map; @@ -67,7 +66,9 @@ public class SecurityMemberAccess extends DefaultMemberAccess { if (LOG.isWarnEnabled()) { LOG.warn("Support for accessing static methods is deprecated! Please refactor your application!"); } - targetClass = member.getDeclaringClass(); + if (!isClassExcluded(member.getDeclaringClass())) { + targetClass = member.getDeclaringClass(); + } } if (isPackageExcluded(targetClass.getPackage(), memberClass.getPackage())) { @@ -77,9 +78,16 @@ public class SecurityMemberAccess extends DefaultMemberAccess { return false; } - if (isClassExcluded(targetClass, memberClass)) { + if (isClassExcluded(targetClass)) { if (LOG.isWarnEnabled()) { - LOG.warn("Target class [#0] or declaring class of member type [#1] are excluded!", target, member); + LOG.warn("Target class [#0] is excluded!", target); + } + return false; + } + + if (isClassExcluded(memberClass)) { + if (LOG.isWarnEnabled()) { + LOG.warn("Declaring class of member type [#0] is excluded!", member); } return false; } @@ -128,12 +136,12 @@ public class SecurityMemberAccess extends DefaultMemberAccess { return false; } - protected boolean isClassExcluded(Class targetClass, Class declaringClass) { - if (targetClass == Object.class || declaringClass == Object.class) { + protected boolean isClassExcluded(Class clazz) { + if (clazz == Object.class) { return true; } for (Class excludedClass : excludedClasses) { - if (targetClass.isAssignableFrom(excludedClass) || declaringClass.isAssignableFrom(excludedClass)) { + if (clazz.isAssignableFrom(excludedClass)) { return true; } } From f4918d1e2fc254a4805963ffa91c6f7c5f5e5988 Mon Sep 17 00:00:00 2001 From: Lukasz Lenart Date: Fri, 26 Dec 2014 20:49:26 +0100 Subject: [PATCH 37/41] WW-4429 Adds additional tests to cover unsecure access --- .../xwork2/ognl/SecurityMemberAccessTest.java | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/ognl/SecurityMemberAccessTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/ognl/SecurityMemberAccessTest.java index 11ff9d0aa..69dceca28 100644 --- a/xwork-core/src/test/java/com/opensymphony/xwork2/ognl/SecurityMemberAccessTest.java +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/ognl/SecurityMemberAccessTest.java @@ -229,6 +229,32 @@ public class SecurityMemberAccessTest extends TestCase { assertFalse("Access to static isn't blocked!", actual); } + public void testBlockStaticAccessIfClassIsExcluded() throws Exception { + // given + SecurityMemberAccess sma = new SecurityMemberAccess(false); + sma.setExcludedClasses(new HashSet>(Arrays.>asList(Class.class))); + + // when + Member method = Class.class.getMethod("getClassLoader"); + boolean actual = sma.isAccessible(context, Class.class, method, null); + + // then + assertFalse("Access to static method of excluded class isn't blocked!", actual); + } + + public void testAllowStaticAccessIfClassIsNotExcluded() throws Exception { + // given + SecurityMemberAccess sma = new SecurityMemberAccess(false); + sma.setExcludedClasses(new HashSet>(Arrays.>asList(ClassLoader.class))); + + // when + Member method = Class.class.getMethod("getClassLoader"); + boolean actual = sma.isAccessible(context, Class.class, method, null); + + // then + assertTrue("Invalid test! Access to static method of excluded class is blocked!", actual); + } + } class FooBar implements FooBarInterface { From 64234907c6e766bc14283ce5fef59e073276ffe2 Mon Sep 17 00:00:00 2001 From: Lukasz Lenart Date: Tue, 30 Dec 2014 09:23:23 +0100 Subject: [PATCH 38/41] WW-4055 Reverts commit dfb2bd3 --- .../PackageBasedActionConfigBuilder.java | 56 +++++++++++-------- 1 file changed, 32 insertions(+), 24 deletions(-) diff --git a/plugins/convention/src/main/java/org/apache/struts2/convention/PackageBasedActionConfigBuilder.java b/plugins/convention/src/main/java/org/apache/struts2/convention/PackageBasedActionConfigBuilder.java index 811df83e7..d1ad0c028 100644 --- a/plugins/convention/src/main/java/org/apache/struts2/convention/PackageBasedActionConfigBuilder.java +++ b/plugins/convention/src/main/java/org/apache/struts2/convention/PackageBasedActionConfigBuilder.java @@ -94,7 +94,7 @@ public class PackageBasedActionConfigBuilder implements ActionConfigBuilder { private String[] actionPackages; private String[] excludePackages; private String[] packageLocators; - private String[] includeJars = new String[] { ".*?\\.jar(!/|/)?" }; + private String[] includeJars; private String packageLocatorsBasePackage; private boolean disableActionScanning = false; private boolean disablePackageLocatorsScanning = false; @@ -475,35 +475,43 @@ public class PackageBasedActionConfigBuilder implements ActionConfigBuilder { urlSet = urlSet.excludePaths(System.getProperty("sun.boot.class.path", "")); urlSet = urlSet.exclude(".*/JavaVM.framework/.*"); - List rawIncludedUrls = urlSet.getUrls(); - Set includeUrls = new HashSet(); - boolean[] patternUsed = new boolean[includeJars.length]; + if (includeJars == null) { + urlSet = urlSet.exclude(".*?\\.jar(!/|/)?"); + } else { + LOG.debug("jar urls regexes were specified: #0", Arrays.asList(includeJars)); - for (URL url : rawIncludedUrls) { - if (fileProtocols.contains(url.getProtocol())) { - //it is a jar file, make sure it macthes at least a url regex - for (int i = 0; i < includeJars.length; i++) { - String includeJar = includeJars[i]; - if (Pattern.matches(includeJar, url.toExternalForm())) { - includeUrls.add(url); - patternUsed[i] = true; - break; + List rawIncludedUrls = urlSet.getUrls(); + Set includeUrls = new HashSet(); + boolean[] patternUsed = new boolean[includeJars.length]; + + for (URL url : rawIncludedUrls) { + if (fileProtocols.contains(url.getProtocol())) { + //it is a jar file, make sure it macthes at least a url regex + for (int i = 0; i < includeJars.length; i++) { + String includeJar = includeJars[i]; + if (Pattern.matches(includeJar, url.toExternalForm())) { + includeUrls.add(url); + patternUsed[i] = true; + break; + } + } + } else { + LOG.debug("It is not a jar [#0]", url); + includeUrls.add(url); + } + } + + if (LOG.isWarnEnabled()) { + for (int i = 0; i < patternUsed.length; i++) { + if (!patternUsed[i]) { + LOG.warn("The includeJars pattern [#0] did not match any jars in the classpath", includeJars[i]); } } - } else { - //it is not a jar - includeUrls.add(url); } + return new UrlSet(includeUrls); } - if (LOG.isWarnEnabled()) { - for (int i = 0; i < patternUsed.length; i++) { - if (!patternUsed[i]) { - LOG.warn("The includeJars pattern [#0] did not match any jars in the classpath", includeJars[i]); - } - } - } - return new UrlSet(includeUrls); + return urlSet; } /** From 9bca437b9aeea7e5d7eabc2291a47b1dded5cf30 Mon Sep 17 00:00:00 2001 From: Lukasz Lenart Date: Tue, 30 Dec 2014 20:47:46 +0100 Subject: [PATCH 39/41] WW-4434 Adds basic version of missing ftl --- .../resources/template/simple/datetext.ftl | 21 +++++++++++++++++ .../resources/template/xhtml/datetext.ftl | 23 +++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 core/src/main/resources/template/simple/datetext.ftl create mode 100644 core/src/main/resources/template/xhtml/datetext.ftl diff --git a/core/src/main/resources/template/simple/datetext.ftl b/core/src/main/resources/template/simple/datetext.ftl new file mode 100644 index 000000000..6d8396c9b --- /dev/null +++ b/core/src/main/resources/template/simple/datetext.ftl @@ -0,0 +1,21 @@ +<#-- +/* + * 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. + */ +--> +<#lt/>

Tag
<s:datetext/>
works only with the JavaTemplates Plugin!
<#rt/> diff --git a/core/src/main/resources/template/xhtml/datetext.ftl b/core/src/main/resources/template/xhtml/datetext.ftl new file mode 100644 index 000000000..d15b24a8c --- /dev/null +++ b/core/src/main/resources/template/xhtml/datetext.ftl @@ -0,0 +1,23 @@ +<#-- +/* + * 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. + */ +--> +<#include "/${parameters.templateDir}/${parameters.expandTheme}/controlheader.ftl" /> +<#include "/${parameters.templateDir}/simple/datetext.ftl" /> +<#include "/${parameters.templateDir}/${parameters.expandTheme}/controlfooter.ftl" /> From a40e9a90bf8b5039728ff312852991e7d580bff4 Mon Sep 17 00:00:00 2001 From: Lukasz Lenart Date: Wed, 31 Dec 2014 14:53:04 +0100 Subject: [PATCH 40/41] [maven-release-plugin] prepare release STRUTS_2_3_21 --- apps/blank/pom.xml | 2 +- apps/jboss-blank/pom.xml | 2 +- apps/mailreader/pom.xml | 2 +- apps/pom.xml | 2 +- apps/portlet/pom.xml | 2 +- apps/rest-showcase/pom.xml | 4 ++-- apps/showcase/pom.xml | 2 +- archetypes/pom.xml | 2 +- archetypes/struts2-archetype-angularjs/pom.xml | 2 +- archetypes/struts2-archetype-blank/pom.xml | 2 +- archetypes/struts2-archetype-convention/pom.xml | 2 +- archetypes/struts2-archetype-dbportlet/pom.xml | 2 +- archetypes/struts2-archetype-plugin/pom.xml | 2 +- archetypes/struts2-archetype-portlet/pom.xml | 2 +- archetypes/struts2-archetype-starter/pom.xml | 2 +- assembly/pom.xml | 2 +- bom/pom.xml | 8 ++++++-- bundles/admin/pom.xml | 2 +- bundles/demo/pom.xml | 2 +- bundles/pom.xml | 2 +- core/pom.xml | 2 +- plugins/cdi/pom.xml | 2 +- plugins/codebehind/pom.xml | 2 +- plugins/config-browser/pom.xml | 2 +- plugins/convention/pom.xml | 2 +- plugins/dojo/pom.xml | 2 +- plugins/dwr/pom.xml | 2 +- plugins/embeddedjsp/pom.xml | 2 +- plugins/gxp/pom.xml | 2 +- plugins/jasperreports/pom.xml | 2 +- plugins/java8-support/pom.xml | 5 ++--- plugins/javatemplates/pom.xml | 2 +- plugins/jfreechart/pom.xml | 2 +- plugins/jsf/pom.xml | 2 +- plugins/json/pom.xml | 2 +- plugins/junit/pom.xml | 2 +- plugins/osgi/pom.xml | 2 +- plugins/oval/pom.xml | 2 +- plugins/pell-multipart/pom.xml | 2 +- plugins/plexus/pom.xml | 2 +- plugins/pom.xml | 2 +- plugins/portlet-tiles/pom.xml | 2 +- plugins/portlet/pom.xml | 2 +- plugins/rest/pom.xml | 2 +- plugins/sitegraph/pom.xml | 2 +- plugins/sitemesh/pom.xml | 2 +- plugins/spring/pom.xml | 2 +- plugins/struts1/pom.xml | 2 +- plugins/testng/pom.xml | 2 +- plugins/tiles/pom.xml | 2 +- plugins/tiles3/pom.xml | 2 +- pom.xml | 9 ++++----- xwork-core/pom.xml | 2 +- 53 files changed, 63 insertions(+), 61 deletions(-) diff --git a/apps/blank/pom.xml b/apps/blank/pom.xml index 5d036ba0b..d0daec592 100644 --- a/apps/blank/pom.xml +++ b/apps/blank/pom.xml @@ -26,7 +26,7 @@ org.apache.struts struts2-apps - 2.3.21-SNAPSHOT + 2.3.21 struts2-blank diff --git a/apps/jboss-blank/pom.xml b/apps/jboss-blank/pom.xml index a31f04ed3..4bd8f91a4 100644 --- a/apps/jboss-blank/pom.xml +++ b/apps/jboss-blank/pom.xml @@ -26,7 +26,7 @@ org.apache.struts struts2-apps - 2.3.21-SNAPSHOT + 2.3.21 struts2-jboss-blank diff --git a/apps/mailreader/pom.xml b/apps/mailreader/pom.xml index d0366ed43..6c18e0d60 100644 --- a/apps/mailreader/pom.xml +++ b/apps/mailreader/pom.xml @@ -26,7 +26,7 @@ org.apache.struts struts2-apps - 2.3.21-SNAPSHOT + 2.3.21 struts2-mailreader diff --git a/apps/pom.xml b/apps/pom.xml index 9aba17390..9b40d01eb 100644 --- a/apps/pom.xml +++ b/apps/pom.xml @@ -26,7 +26,7 @@ org.apache.struts struts2-parent - 2.3.21-SNAPSHOT + 2.3.21 struts2-apps pom diff --git a/apps/portlet/pom.xml b/apps/portlet/pom.xml index bb403eb13..cf1ad4b22 100644 --- a/apps/portlet/pom.xml +++ b/apps/portlet/pom.xml @@ -26,7 +26,7 @@ org.apache.struts struts2-apps - 2.3.21-SNAPSHOT + 2.3.21 struts2-portlet diff --git a/apps/rest-showcase/pom.xml b/apps/rest-showcase/pom.xml index 36296b5ce..c83a76e09 100644 --- a/apps/rest-showcase/pom.xml +++ b/apps/rest-showcase/pom.xml @@ -26,12 +26,12 @@ org.apache.struts struts2-apps - 2.3.21-SNAPSHOT + 2.3.21 struts2-rest-showcase war - 2.3.21-SNAPSHOT + 2.3.21 Struts 2 Rest Showcase Webapp Struts 2 Rest Showcase Example diff --git a/apps/showcase/pom.xml b/apps/showcase/pom.xml index 4c504977c..1668f7052 100644 --- a/apps/showcase/pom.xml +++ b/apps/showcase/pom.xml @@ -26,7 +26,7 @@ org.apache.struts struts2-apps - 2.3.21-SNAPSHOT + 2.3.21 struts2-showcase diff --git a/archetypes/pom.xml b/archetypes/pom.xml index 0bb9ae674..801ddb525 100644 --- a/archetypes/pom.xml +++ b/archetypes/pom.xml @@ -26,7 +26,7 @@ org.apache.struts struts2-parent - 2.3.21-SNAPSHOT + 2.3.21 struts2-archetypes diff --git a/archetypes/struts2-archetype-angularjs/pom.xml b/archetypes/struts2-archetype-angularjs/pom.xml index affeda82f..9d8e8e08d 100644 --- a/archetypes/struts2-archetype-angularjs/pom.xml +++ b/archetypes/struts2-archetype-angularjs/pom.xml @@ -2,7 +2,7 @@ org.apache.struts struts2-archetypes - 2.3.21-SNAPSHOT + 2.3.21 4.0.0 diff --git a/archetypes/struts2-archetype-blank/pom.xml b/archetypes/struts2-archetype-blank/pom.xml index ec2397bb8..6a84682a4 100644 --- a/archetypes/struts2-archetype-blank/pom.xml +++ b/archetypes/struts2-archetype-blank/pom.xml @@ -3,7 +3,7 @@ org.apache.struts struts2-archetypes - 2.3.21-SNAPSHOT + 2.3.21 4.0.0 diff --git a/archetypes/struts2-archetype-convention/pom.xml b/archetypes/struts2-archetype-convention/pom.xml index 9d9375620..8882a4b7f 100644 --- a/archetypes/struts2-archetype-convention/pom.xml +++ b/archetypes/struts2-archetype-convention/pom.xml @@ -2,7 +2,7 @@ org.apache.struts struts2-archetypes - 2.3.21-SNAPSHOT + 2.3.21 4.0.0 diff --git a/archetypes/struts2-archetype-dbportlet/pom.xml b/archetypes/struts2-archetype-dbportlet/pom.xml index 447b398ef..7072b2c33 100644 --- a/archetypes/struts2-archetype-dbportlet/pom.xml +++ b/archetypes/struts2-archetype-dbportlet/pom.xml @@ -2,7 +2,7 @@ org.apache.struts struts2-archetypes - 2.3.21-SNAPSHOT + 2.3.21 4.0.0 diff --git a/archetypes/struts2-archetype-plugin/pom.xml b/archetypes/struts2-archetype-plugin/pom.xml index dda4a482c..7ed20997e 100644 --- a/archetypes/struts2-archetype-plugin/pom.xml +++ b/archetypes/struts2-archetype-plugin/pom.xml @@ -2,7 +2,7 @@ org.apache.struts struts2-archetypes - 2.3.21-SNAPSHOT + 2.3.21 4.0.0 diff --git a/archetypes/struts2-archetype-portlet/pom.xml b/archetypes/struts2-archetype-portlet/pom.xml index e1e20cddc..ca69e24f8 100644 --- a/archetypes/struts2-archetype-portlet/pom.xml +++ b/archetypes/struts2-archetype-portlet/pom.xml @@ -2,7 +2,7 @@ org.apache.struts struts2-archetypes - 2.3.21-SNAPSHOT + 2.3.21 4.0.0 diff --git a/archetypes/struts2-archetype-starter/pom.xml b/archetypes/struts2-archetype-starter/pom.xml index b19b5185a..545d061b0 100644 --- a/archetypes/struts2-archetype-starter/pom.xml +++ b/archetypes/struts2-archetype-starter/pom.xml @@ -4,7 +4,7 @@ org.apache.struts struts2-archetypes - 2.3.21-SNAPSHOT + 2.3.21 4.0.0 diff --git a/assembly/pom.xml b/assembly/pom.xml index d2f3a30ed..b89289536 100644 --- a/assembly/pom.xml +++ b/assembly/pom.xml @@ -3,7 +3,7 @@ org.apache.struts struts2-parent - 2.3.21-SNAPSHOT + 2.3.21 struts2-assembly diff --git a/bom/pom.xml b/bom/pom.xml index 702ab9f94..2a4623415 100644 --- a/bom/pom.xml +++ b/bom/pom.xml @@ -10,7 +10,7 @@ struts2-bom - 2.3.21-SNAPSHOT + 2.3.21 pom Struts 2 Bill of Materials @@ -25,7 +25,7 @@ - 2.3.21-SNAPSHOT + 2.3.21 @@ -195,4 +195,8 @@ + + + STRUTS_2_3_21 + diff --git a/bundles/admin/pom.xml b/bundles/admin/pom.xml index ff70599eb..40d8d0064 100644 --- a/bundles/admin/pom.xml +++ b/bundles/admin/pom.xml @@ -4,7 +4,7 @@ org.apache.struts struts2-osgi-bundles - 2.3.21-SNAPSHOT + 2.3.21 struts2-osgi-admin-bundle diff --git a/bundles/demo/pom.xml b/bundles/demo/pom.xml index 397e3d15a..8921df4de 100644 --- a/bundles/demo/pom.xml +++ b/bundles/demo/pom.xml @@ -4,7 +4,7 @@ org.apache.struts struts2-osgi-bundles - 2.3.21-SNAPSHOT + 2.3.21 struts2-osgi-demo-bundle diff --git a/bundles/pom.xml b/bundles/pom.xml index 672c46b70..ddb9b74ca 100755 --- a/bundles/pom.xml +++ b/bundles/pom.xml @@ -26,7 +26,7 @@ org.apache.struts struts2-parent - 2.3.21-SNAPSHOT + 2.3.21 struts2-osgi-bundles diff --git a/core/pom.xml b/core/pom.xml index 9d0efee40..ff652187b 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -26,7 +26,7 @@ org.apache.struts struts2-parent - 2.3.21-SNAPSHOT + 2.3.21 struts2-core jar diff --git a/plugins/cdi/pom.xml b/plugins/cdi/pom.xml index db60c1667..7cbf10f23 100644 --- a/plugins/cdi/pom.xml +++ b/plugins/cdi/pom.xml @@ -25,7 +25,7 @@ org.apache.struts struts2-plugins - 2.3.21-SNAPSHOT + 2.3.21 struts2-cdi-plugin diff --git a/plugins/codebehind/pom.xml b/plugins/codebehind/pom.xml index 824c1a140..94b2c469c 100644 --- a/plugins/codebehind/pom.xml +++ b/plugins/codebehind/pom.xml @@ -26,7 +26,7 @@ org.apache.struts struts2-plugins - 2.3.21-SNAPSHOT + 2.3.21 struts2-codebehind-plugin diff --git a/plugins/config-browser/pom.xml b/plugins/config-browser/pom.xml index 6489fc16f..622506e29 100644 --- a/plugins/config-browser/pom.xml +++ b/plugins/config-browser/pom.xml @@ -26,7 +26,7 @@ org.apache.struts struts2-plugins - 2.3.21-SNAPSHOT + 2.3.21 struts2-config-browser-plugin diff --git a/plugins/convention/pom.xml b/plugins/convention/pom.xml index 1961e91f6..e3f4d635e 100644 --- a/plugins/convention/pom.xml +++ b/plugins/convention/pom.xml @@ -3,7 +3,7 @@ org.apache.struts struts2-plugins - 2.3.21-SNAPSHOT + 2.3.21 struts2-convention-plugin diff --git a/plugins/dojo/pom.xml b/plugins/dojo/pom.xml index 3cb068771..28b0fd6b2 100644 --- a/plugins/dojo/pom.xml +++ b/plugins/dojo/pom.xml @@ -25,7 +25,7 @@ struts2-plugins org.apache.struts - 2.3.21-SNAPSHOT + 2.3.21 4.0.0 diff --git a/plugins/dwr/pom.xml b/plugins/dwr/pom.xml index cb399e2bc..ece13c6b4 100644 --- a/plugins/dwr/pom.xml +++ b/plugins/dwr/pom.xml @@ -26,7 +26,7 @@ org.apache.struts struts2-plugins - 2.3.21-SNAPSHOT + 2.3.21 struts2-dwr-plugin diff --git a/plugins/embeddedjsp/pom.xml b/plugins/embeddedjsp/pom.xml index b77292070..2cb17ee0a 100644 --- a/plugins/embeddedjsp/pom.xml +++ b/plugins/embeddedjsp/pom.xml @@ -26,7 +26,7 @@ org.apache.struts struts2-plugins - 2.3.21-SNAPSHOT + 2.3.21 struts2-embeddedjsp-plugin diff --git a/plugins/gxp/pom.xml b/plugins/gxp/pom.xml index 89f4f9e42..63e1db561 100644 --- a/plugins/gxp/pom.xml +++ b/plugins/gxp/pom.xml @@ -3,7 +3,7 @@ org.apache.struts struts2-plugins - 2.3.21-SNAPSHOT + 2.3.21 struts2-gxp-plugin diff --git a/plugins/jasperreports/pom.xml b/plugins/jasperreports/pom.xml index 15bea415f..8e8d0cf6a 100644 --- a/plugins/jasperreports/pom.xml +++ b/plugins/jasperreports/pom.xml @@ -26,7 +26,7 @@ org.apache.struts struts2-plugins - 2.3.21-SNAPSHOT + 2.3.21 struts2-jasperreports-plugin diff --git a/plugins/java8-support/pom.xml b/plugins/java8-support/pom.xml index b69286449..278b79747 100644 --- a/plugins/java8-support/pom.xml +++ b/plugins/java8-support/pom.xml @@ -1,12 +1,11 @@ - + 4.0.0 org.apache.struts struts2-plugins - 2.3.21-SNAPSHOT + 2.3.21 struts2-java8-support-plugin diff --git a/plugins/javatemplates/pom.xml b/plugins/javatemplates/pom.xml index 30a2b264b..ff1708165 100644 --- a/plugins/javatemplates/pom.xml +++ b/plugins/javatemplates/pom.xml @@ -25,7 +25,7 @@ org.apache.struts struts2-plugins - 2.3.21-SNAPSHOT + 2.3.21 struts2-javatemplates-plugin diff --git a/plugins/jfreechart/pom.xml b/plugins/jfreechart/pom.xml index 8e6597a99..f49f48faf 100644 --- a/plugins/jfreechart/pom.xml +++ b/plugins/jfreechart/pom.xml @@ -26,7 +26,7 @@ org.apache.struts struts2-plugins - 2.3.21-SNAPSHOT + 2.3.21 struts2-jfreechart-plugin diff --git a/plugins/jsf/pom.xml b/plugins/jsf/pom.xml index c2fb01f5a..f64d355f9 100644 --- a/plugins/jsf/pom.xml +++ b/plugins/jsf/pom.xml @@ -26,7 +26,7 @@ org.apache.struts struts2-plugins - 2.3.21-SNAPSHOT + 2.3.21 struts2-jsf-plugin diff --git a/plugins/json/pom.xml b/plugins/json/pom.xml index c7fdd9b63..253bfaaf5 100644 --- a/plugins/json/pom.xml +++ b/plugins/json/pom.xml @@ -26,7 +26,7 @@ org.apache.struts struts2-plugins - 2.3.21-SNAPSHOT + 2.3.21 struts2-json-plugin diff --git a/plugins/junit/pom.xml b/plugins/junit/pom.xml index fed1b51ec..bf0de5fcf 100644 --- a/plugins/junit/pom.xml +++ b/plugins/junit/pom.xml @@ -26,7 +26,7 @@ org.apache.struts struts2-plugins - 2.3.21-SNAPSHOT + 2.3.21 struts2-junit-plugin diff --git a/plugins/osgi/pom.xml b/plugins/osgi/pom.xml index de33bc079..5be28e966 100644 --- a/plugins/osgi/pom.xml +++ b/plugins/osgi/pom.xml @@ -4,7 +4,7 @@ org.apache.struts struts2-plugins - 2.3.21-SNAPSHOT + 2.3.21 struts2-osgi-plugin diff --git a/plugins/oval/pom.xml b/plugins/oval/pom.xml index cad0ada7e..1411fb8c5 100644 --- a/plugins/oval/pom.xml +++ b/plugins/oval/pom.xml @@ -26,7 +26,7 @@ org.apache.struts struts2-plugins - 2.3.21-SNAPSHOT + 2.3.21 struts2-oval-plugin diff --git a/plugins/pell-multipart/pom.xml b/plugins/pell-multipart/pom.xml index 0378e3b7d..073895ded 100644 --- a/plugins/pell-multipart/pom.xml +++ b/plugins/pell-multipart/pom.xml @@ -26,7 +26,7 @@ org.apache.struts struts2-plugins - 2.3.21-SNAPSHOT + 2.3.21 struts2-pell-multipart-plugin diff --git a/plugins/plexus/pom.xml b/plugins/plexus/pom.xml index f7f838f4a..91730575f 100644 --- a/plugins/plexus/pom.xml +++ b/plugins/plexus/pom.xml @@ -26,7 +26,7 @@ org.apache.struts struts2-plugins - 2.3.21-SNAPSHOT + 2.3.21 struts2-plexus-plugin diff --git a/plugins/pom.xml b/plugins/pom.xml index d12b17c01..5ec9ed63f 100644 --- a/plugins/pom.xml +++ b/plugins/pom.xml @@ -26,7 +26,7 @@ org.apache.struts struts2-parent - 2.3.21-SNAPSHOT + 2.3.21 struts2-plugins diff --git a/plugins/portlet-tiles/pom.xml b/plugins/portlet-tiles/pom.xml index d05c7bfa9..d743266bc 100644 --- a/plugins/portlet-tiles/pom.xml +++ b/plugins/portlet-tiles/pom.xml @@ -26,7 +26,7 @@ org.apache.struts struts2-plugins - 2.3.21-SNAPSHOT + 2.3.21 struts2-portlet-tiles-plugin diff --git a/plugins/portlet/pom.xml b/plugins/portlet/pom.xml index e8d0f9a4d..a0efdccfb 100644 --- a/plugins/portlet/pom.xml +++ b/plugins/portlet/pom.xml @@ -3,7 +3,7 @@ org.apache.struts struts2-plugins - 2.3.21-SNAPSHOT + 2.3.21 struts2-portlet-plugin diff --git a/plugins/rest/pom.xml b/plugins/rest/pom.xml index 4644252f8..27b8e1da6 100644 --- a/plugins/rest/pom.xml +++ b/plugins/rest/pom.xml @@ -26,7 +26,7 @@ org.apache.struts struts2-plugins - 2.3.21-SNAPSHOT + 2.3.21 struts2-rest-plugin diff --git a/plugins/sitegraph/pom.xml b/plugins/sitegraph/pom.xml index 884002a3b..f2b27bf65 100644 --- a/plugins/sitegraph/pom.xml +++ b/plugins/sitegraph/pom.xml @@ -26,7 +26,7 @@ org.apache.struts struts2-plugins - 2.3.21-SNAPSHOT + 2.3.21 struts2-sitegraph-plugin diff --git a/plugins/sitemesh/pom.xml b/plugins/sitemesh/pom.xml index 9ed7b5361..2bfd65a8c 100644 --- a/plugins/sitemesh/pom.xml +++ b/plugins/sitemesh/pom.xml @@ -26,7 +26,7 @@ org.apache.struts struts2-plugins - 2.3.21-SNAPSHOT + 2.3.21 struts2-sitemesh-plugin diff --git a/plugins/spring/pom.xml b/plugins/spring/pom.xml index 12359c755..5e2ed9353 100644 --- a/plugins/spring/pom.xml +++ b/plugins/spring/pom.xml @@ -26,7 +26,7 @@ org.apache.struts struts2-plugins - 2.3.21-SNAPSHOT + 2.3.21 struts2-spring-plugin diff --git a/plugins/struts1/pom.xml b/plugins/struts1/pom.xml index 9948a8315..6f9453238 100644 --- a/plugins/struts1/pom.xml +++ b/plugins/struts1/pom.xml @@ -26,7 +26,7 @@ org.apache.struts struts2-plugins - 2.3.21-SNAPSHOT + 2.3.21 struts2-struts1-plugin diff --git a/plugins/testng/pom.xml b/plugins/testng/pom.xml index 753116cad..27e8f2f69 100644 --- a/plugins/testng/pom.xml +++ b/plugins/testng/pom.xml @@ -26,7 +26,7 @@ org.apache.struts struts2-plugins - 2.3.21-SNAPSHOT + 2.3.21 struts2-testng-plugin diff --git a/plugins/tiles/pom.xml b/plugins/tiles/pom.xml index 6e5613e7d..c4a9e7f34 100644 --- a/plugins/tiles/pom.xml +++ b/plugins/tiles/pom.xml @@ -26,7 +26,7 @@ org.apache.struts struts2-plugins - 2.3.21-SNAPSHOT + 2.3.21 struts2-tiles-plugin diff --git a/plugins/tiles3/pom.xml b/plugins/tiles3/pom.xml index 75feaaa33..f3b14b5bc 100644 --- a/plugins/tiles3/pom.xml +++ b/plugins/tiles3/pom.xml @@ -26,7 +26,7 @@ org.apache.struts struts2-plugins - 2.3.21-SNAPSHOT + 2.3.21 struts2-tiles3-plugin diff --git a/pom.xml b/pom.xml index 3801c2625..72fbd8ee1 100644 --- a/pom.xml +++ b/pom.xml @@ -1,6 +1,5 @@ - + org.apache.struts @@ -10,7 +9,7 @@ 4.0.0 struts2-parent - 2.3.21-SNAPSHOT + 2.3.21 pom Struts 2 http://struts.apache.org/ @@ -32,7 +31,7 @@ scm:git:git://git.apache.org/struts.git scm:git:https://git-wip-us.apache.org/repos/asf/struts.git http://git.apache.org/struts.git - HEAD + STRUTS_2_3_21 @@ -75,7 +74,7 @@ The Apache Software License, Version 2.0 http://www.apache.org/licenses/LICENSE-2.0.txt repo - + diff --git a/xwork-core/pom.xml b/xwork-core/pom.xml index f26c25d5d..4b662dfe9 100644 --- a/xwork-core/pom.xml +++ b/xwork-core/pom.xml @@ -5,7 +5,7 @@ org.apache.struts struts2-parent - 2.3.21-SNAPSHOT + 2.3.21 org.apache.struts.xwork From 22cbf40fc24432cb8fe3b5c6de92bd48ac4f265c Mon Sep 17 00:00:00 2001 From: Lukasz Lenart Date: Wed, 31 Dec 2014 14:53:22 +0100 Subject: [PATCH 41/41] [maven-release-plugin] prepare for next development iteration --- apps/blank/pom.xml | 2 +- apps/jboss-blank/pom.xml | 2 +- apps/mailreader/pom.xml | 2 +- apps/pom.xml | 2 +- apps/portlet/pom.xml | 2 +- apps/rest-showcase/pom.xml | 4 ++-- apps/showcase/pom.xml | 2 +- archetypes/pom.xml | 2 +- archetypes/struts2-archetype-angularjs/pom.xml | 2 +- archetypes/struts2-archetype-blank/pom.xml | 2 +- archetypes/struts2-archetype-convention/pom.xml | 2 +- archetypes/struts2-archetype-dbportlet/pom.xml | 2 +- archetypes/struts2-archetype-plugin/pom.xml | 2 +- archetypes/struts2-archetype-portlet/pom.xml | 2 +- archetypes/struts2-archetype-starter/pom.xml | 2 +- assembly/pom.xml | 2 +- bom/pom.xml | 8 ++------ bundles/admin/pom.xml | 2 +- bundles/demo/pom.xml | 2 +- bundles/pom.xml | 2 +- core/pom.xml | 2 +- plugins/cdi/pom.xml | 2 +- plugins/codebehind/pom.xml | 2 +- plugins/config-browser/pom.xml | 2 +- plugins/convention/pom.xml | 2 +- plugins/dojo/pom.xml | 2 +- plugins/dwr/pom.xml | 2 +- plugins/embeddedjsp/pom.xml | 2 +- plugins/gxp/pom.xml | 2 +- plugins/jasperreports/pom.xml | 2 +- plugins/java8-support/pom.xml | 2 +- plugins/javatemplates/pom.xml | 2 +- plugins/jfreechart/pom.xml | 2 +- plugins/jsf/pom.xml | 2 +- plugins/json/pom.xml | 2 +- plugins/junit/pom.xml | 2 +- plugins/osgi/pom.xml | 2 +- plugins/oval/pom.xml | 2 +- plugins/pell-multipart/pom.xml | 2 +- plugins/plexus/pom.xml | 2 +- plugins/pom.xml | 2 +- plugins/portlet-tiles/pom.xml | 2 +- plugins/portlet/pom.xml | 2 +- plugins/rest/pom.xml | 2 +- plugins/sitegraph/pom.xml | 2 +- plugins/sitemesh/pom.xml | 2 +- plugins/spring/pom.xml | 2 +- plugins/struts1/pom.xml | 2 +- plugins/testng/pom.xml | 2 +- plugins/tiles/pom.xml | 2 +- plugins/tiles3/pom.xml | 2 +- pom.xml | 4 ++-- xwork-core/pom.xml | 2 +- 53 files changed, 56 insertions(+), 60 deletions(-) diff --git a/apps/blank/pom.xml b/apps/blank/pom.xml index d0daec592..a8d1cf8cd 100644 --- a/apps/blank/pom.xml +++ b/apps/blank/pom.xml @@ -26,7 +26,7 @@ org.apache.struts struts2-apps - 2.3.21 + 2.3.22-SNAPSHOT struts2-blank diff --git a/apps/jboss-blank/pom.xml b/apps/jboss-blank/pom.xml index 4bd8f91a4..e3da1b52f 100644 --- a/apps/jboss-blank/pom.xml +++ b/apps/jboss-blank/pom.xml @@ -26,7 +26,7 @@ org.apache.struts struts2-apps - 2.3.21 + 2.3.22-SNAPSHOT struts2-jboss-blank diff --git a/apps/mailreader/pom.xml b/apps/mailreader/pom.xml index 6c18e0d60..e239bdb92 100644 --- a/apps/mailreader/pom.xml +++ b/apps/mailreader/pom.xml @@ -26,7 +26,7 @@ org.apache.struts struts2-apps - 2.3.21 + 2.3.22-SNAPSHOT struts2-mailreader diff --git a/apps/pom.xml b/apps/pom.xml index 9b40d01eb..ae0c0179c 100644 --- a/apps/pom.xml +++ b/apps/pom.xml @@ -26,7 +26,7 @@ org.apache.struts struts2-parent - 2.3.21 + 2.3.22-SNAPSHOT struts2-apps pom diff --git a/apps/portlet/pom.xml b/apps/portlet/pom.xml index cf1ad4b22..38c45d20f 100644 --- a/apps/portlet/pom.xml +++ b/apps/portlet/pom.xml @@ -26,7 +26,7 @@ org.apache.struts struts2-apps - 2.3.21 + 2.3.22-SNAPSHOT struts2-portlet diff --git a/apps/rest-showcase/pom.xml b/apps/rest-showcase/pom.xml index c83a76e09..dae55d40f 100644 --- a/apps/rest-showcase/pom.xml +++ b/apps/rest-showcase/pom.xml @@ -26,12 +26,12 @@ org.apache.struts struts2-apps - 2.3.21 + 2.3.22-SNAPSHOT struts2-rest-showcase war - 2.3.21 + 2.3.22-SNAPSHOT Struts 2 Rest Showcase Webapp Struts 2 Rest Showcase Example diff --git a/apps/showcase/pom.xml b/apps/showcase/pom.xml index 1668f7052..f5b744925 100644 --- a/apps/showcase/pom.xml +++ b/apps/showcase/pom.xml @@ -26,7 +26,7 @@ org.apache.struts struts2-apps - 2.3.21 + 2.3.22-SNAPSHOT struts2-showcase diff --git a/archetypes/pom.xml b/archetypes/pom.xml index 801ddb525..feccd5268 100644 --- a/archetypes/pom.xml +++ b/archetypes/pom.xml @@ -26,7 +26,7 @@ org.apache.struts struts2-parent - 2.3.21 + 2.3.22-SNAPSHOT struts2-archetypes diff --git a/archetypes/struts2-archetype-angularjs/pom.xml b/archetypes/struts2-archetype-angularjs/pom.xml index 9d8e8e08d..ff146a152 100644 --- a/archetypes/struts2-archetype-angularjs/pom.xml +++ b/archetypes/struts2-archetype-angularjs/pom.xml @@ -2,7 +2,7 @@ org.apache.struts struts2-archetypes - 2.3.21 + 2.3.22-SNAPSHOT 4.0.0 diff --git a/archetypes/struts2-archetype-blank/pom.xml b/archetypes/struts2-archetype-blank/pom.xml index 6a84682a4..473faf749 100644 --- a/archetypes/struts2-archetype-blank/pom.xml +++ b/archetypes/struts2-archetype-blank/pom.xml @@ -3,7 +3,7 @@ org.apache.struts struts2-archetypes - 2.3.21 + 2.3.22-SNAPSHOT 4.0.0 diff --git a/archetypes/struts2-archetype-convention/pom.xml b/archetypes/struts2-archetype-convention/pom.xml index 8882a4b7f..7706e2551 100644 --- a/archetypes/struts2-archetype-convention/pom.xml +++ b/archetypes/struts2-archetype-convention/pom.xml @@ -2,7 +2,7 @@ org.apache.struts struts2-archetypes - 2.3.21 + 2.3.22-SNAPSHOT 4.0.0 diff --git a/archetypes/struts2-archetype-dbportlet/pom.xml b/archetypes/struts2-archetype-dbportlet/pom.xml index 7072b2c33..f0d08f0db 100644 --- a/archetypes/struts2-archetype-dbportlet/pom.xml +++ b/archetypes/struts2-archetype-dbportlet/pom.xml @@ -2,7 +2,7 @@ org.apache.struts struts2-archetypes - 2.3.21 + 2.3.22-SNAPSHOT 4.0.0 diff --git a/archetypes/struts2-archetype-plugin/pom.xml b/archetypes/struts2-archetype-plugin/pom.xml index 7ed20997e..d46db4983 100644 --- a/archetypes/struts2-archetype-plugin/pom.xml +++ b/archetypes/struts2-archetype-plugin/pom.xml @@ -2,7 +2,7 @@ org.apache.struts struts2-archetypes - 2.3.21 + 2.3.22-SNAPSHOT 4.0.0 diff --git a/archetypes/struts2-archetype-portlet/pom.xml b/archetypes/struts2-archetype-portlet/pom.xml index ca69e24f8..50e4fe6c3 100644 --- a/archetypes/struts2-archetype-portlet/pom.xml +++ b/archetypes/struts2-archetype-portlet/pom.xml @@ -2,7 +2,7 @@ org.apache.struts struts2-archetypes - 2.3.21 + 2.3.22-SNAPSHOT 4.0.0 diff --git a/archetypes/struts2-archetype-starter/pom.xml b/archetypes/struts2-archetype-starter/pom.xml index 545d061b0..44688f21c 100644 --- a/archetypes/struts2-archetype-starter/pom.xml +++ b/archetypes/struts2-archetype-starter/pom.xml @@ -4,7 +4,7 @@ org.apache.struts struts2-archetypes - 2.3.21 + 2.3.22-SNAPSHOT 4.0.0 diff --git a/assembly/pom.xml b/assembly/pom.xml index b89289536..75a1a135f 100644 --- a/assembly/pom.xml +++ b/assembly/pom.xml @@ -3,7 +3,7 @@ org.apache.struts struts2-parent - 2.3.21 + 2.3.22-SNAPSHOT struts2-assembly diff --git a/bom/pom.xml b/bom/pom.xml index 2a4623415..ccd3e5757 100644 --- a/bom/pom.xml +++ b/bom/pom.xml @@ -10,7 +10,7 @@ struts2-bom - 2.3.21 + 2.3.22-SNAPSHOT pom Struts 2 Bill of Materials @@ -25,7 +25,7 @@ - 2.3.21 + 2.3.22-SNAPSHOT @@ -195,8 +195,4 @@ - - - STRUTS_2_3_21 - diff --git a/bundles/admin/pom.xml b/bundles/admin/pom.xml index 40d8d0064..e4d4f3ee3 100644 --- a/bundles/admin/pom.xml +++ b/bundles/admin/pom.xml @@ -4,7 +4,7 @@ org.apache.struts struts2-osgi-bundles - 2.3.21 + 2.3.22-SNAPSHOT struts2-osgi-admin-bundle diff --git a/bundles/demo/pom.xml b/bundles/demo/pom.xml index 8921df4de..cdbe7626c 100644 --- a/bundles/demo/pom.xml +++ b/bundles/demo/pom.xml @@ -4,7 +4,7 @@ org.apache.struts struts2-osgi-bundles - 2.3.21 + 2.3.22-SNAPSHOT struts2-osgi-demo-bundle diff --git a/bundles/pom.xml b/bundles/pom.xml index ddb9b74ca..803015ba5 100755 --- a/bundles/pom.xml +++ b/bundles/pom.xml @@ -26,7 +26,7 @@ org.apache.struts struts2-parent - 2.3.21 + 2.3.22-SNAPSHOT struts2-osgi-bundles diff --git a/core/pom.xml b/core/pom.xml index ff652187b..c310c6abc 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -26,7 +26,7 @@ org.apache.struts struts2-parent - 2.3.21 + 2.3.22-SNAPSHOT struts2-core jar diff --git a/plugins/cdi/pom.xml b/plugins/cdi/pom.xml index 7cbf10f23..0de0e32d4 100644 --- a/plugins/cdi/pom.xml +++ b/plugins/cdi/pom.xml @@ -25,7 +25,7 @@ org.apache.struts struts2-plugins - 2.3.21 + 2.3.22-SNAPSHOT struts2-cdi-plugin diff --git a/plugins/codebehind/pom.xml b/plugins/codebehind/pom.xml index 94b2c469c..61cad58c6 100644 --- a/plugins/codebehind/pom.xml +++ b/plugins/codebehind/pom.xml @@ -26,7 +26,7 @@ org.apache.struts struts2-plugins - 2.3.21 + 2.3.22-SNAPSHOT struts2-codebehind-plugin diff --git a/plugins/config-browser/pom.xml b/plugins/config-browser/pom.xml index 622506e29..0a36c52ea 100644 --- a/plugins/config-browser/pom.xml +++ b/plugins/config-browser/pom.xml @@ -26,7 +26,7 @@ org.apache.struts struts2-plugins - 2.3.21 + 2.3.22-SNAPSHOT struts2-config-browser-plugin diff --git a/plugins/convention/pom.xml b/plugins/convention/pom.xml index e3f4d635e..a8a8a4e58 100644 --- a/plugins/convention/pom.xml +++ b/plugins/convention/pom.xml @@ -3,7 +3,7 @@ org.apache.struts struts2-plugins - 2.3.21 + 2.3.22-SNAPSHOT struts2-convention-plugin diff --git a/plugins/dojo/pom.xml b/plugins/dojo/pom.xml index 28b0fd6b2..c3a48ef14 100644 --- a/plugins/dojo/pom.xml +++ b/plugins/dojo/pom.xml @@ -25,7 +25,7 @@ struts2-plugins org.apache.struts - 2.3.21 + 2.3.22-SNAPSHOT 4.0.0 diff --git a/plugins/dwr/pom.xml b/plugins/dwr/pom.xml index ece13c6b4..446db222c 100644 --- a/plugins/dwr/pom.xml +++ b/plugins/dwr/pom.xml @@ -26,7 +26,7 @@ org.apache.struts struts2-plugins - 2.3.21 + 2.3.22-SNAPSHOT struts2-dwr-plugin diff --git a/plugins/embeddedjsp/pom.xml b/plugins/embeddedjsp/pom.xml index 2cb17ee0a..541db6c6b 100644 --- a/plugins/embeddedjsp/pom.xml +++ b/plugins/embeddedjsp/pom.xml @@ -26,7 +26,7 @@ org.apache.struts struts2-plugins - 2.3.21 + 2.3.22-SNAPSHOT struts2-embeddedjsp-plugin diff --git a/plugins/gxp/pom.xml b/plugins/gxp/pom.xml index 63e1db561..e2ca54e8b 100644 --- a/plugins/gxp/pom.xml +++ b/plugins/gxp/pom.xml @@ -3,7 +3,7 @@ org.apache.struts struts2-plugins - 2.3.21 + 2.3.22-SNAPSHOT struts2-gxp-plugin diff --git a/plugins/jasperreports/pom.xml b/plugins/jasperreports/pom.xml index 8e8d0cf6a..5bcf4701e 100644 --- a/plugins/jasperreports/pom.xml +++ b/plugins/jasperreports/pom.xml @@ -26,7 +26,7 @@ org.apache.struts struts2-plugins - 2.3.21 + 2.3.22-SNAPSHOT struts2-jasperreports-plugin diff --git a/plugins/java8-support/pom.xml b/plugins/java8-support/pom.xml index 278b79747..c99b3686a 100644 --- a/plugins/java8-support/pom.xml +++ b/plugins/java8-support/pom.xml @@ -5,7 +5,7 @@ org.apache.struts struts2-plugins - 2.3.21 + 2.3.22-SNAPSHOT struts2-java8-support-plugin diff --git a/plugins/javatemplates/pom.xml b/plugins/javatemplates/pom.xml index ff1708165..5b0cb57a2 100644 --- a/plugins/javatemplates/pom.xml +++ b/plugins/javatemplates/pom.xml @@ -25,7 +25,7 @@ org.apache.struts struts2-plugins - 2.3.21 + 2.3.22-SNAPSHOT struts2-javatemplates-plugin diff --git a/plugins/jfreechart/pom.xml b/plugins/jfreechart/pom.xml index f49f48faf..04dcef190 100644 --- a/plugins/jfreechart/pom.xml +++ b/plugins/jfreechart/pom.xml @@ -26,7 +26,7 @@ org.apache.struts struts2-plugins - 2.3.21 + 2.3.22-SNAPSHOT struts2-jfreechart-plugin diff --git a/plugins/jsf/pom.xml b/plugins/jsf/pom.xml index f64d355f9..616c39b74 100644 --- a/plugins/jsf/pom.xml +++ b/plugins/jsf/pom.xml @@ -26,7 +26,7 @@ org.apache.struts struts2-plugins - 2.3.21 + 2.3.22-SNAPSHOT struts2-jsf-plugin diff --git a/plugins/json/pom.xml b/plugins/json/pom.xml index 253bfaaf5..4050b4423 100644 --- a/plugins/json/pom.xml +++ b/plugins/json/pom.xml @@ -26,7 +26,7 @@ org.apache.struts struts2-plugins - 2.3.21 + 2.3.22-SNAPSHOT struts2-json-plugin diff --git a/plugins/junit/pom.xml b/plugins/junit/pom.xml index bf0de5fcf..0a22bbfe7 100644 --- a/plugins/junit/pom.xml +++ b/plugins/junit/pom.xml @@ -26,7 +26,7 @@ org.apache.struts struts2-plugins - 2.3.21 + 2.3.22-SNAPSHOT struts2-junit-plugin diff --git a/plugins/osgi/pom.xml b/plugins/osgi/pom.xml index 5be28e966..6695e118e 100644 --- a/plugins/osgi/pom.xml +++ b/plugins/osgi/pom.xml @@ -4,7 +4,7 @@ org.apache.struts struts2-plugins - 2.3.21 + 2.3.22-SNAPSHOT struts2-osgi-plugin diff --git a/plugins/oval/pom.xml b/plugins/oval/pom.xml index 1411fb8c5..7c3ad347c 100644 --- a/plugins/oval/pom.xml +++ b/plugins/oval/pom.xml @@ -26,7 +26,7 @@ org.apache.struts struts2-plugins - 2.3.21 + 2.3.22-SNAPSHOT struts2-oval-plugin diff --git a/plugins/pell-multipart/pom.xml b/plugins/pell-multipart/pom.xml index 073895ded..57bd06e53 100644 --- a/plugins/pell-multipart/pom.xml +++ b/plugins/pell-multipart/pom.xml @@ -26,7 +26,7 @@ org.apache.struts struts2-plugins - 2.3.21 + 2.3.22-SNAPSHOT struts2-pell-multipart-plugin diff --git a/plugins/plexus/pom.xml b/plugins/plexus/pom.xml index 91730575f..bf5b7d1c6 100644 --- a/plugins/plexus/pom.xml +++ b/plugins/plexus/pom.xml @@ -26,7 +26,7 @@ org.apache.struts struts2-plugins - 2.3.21 + 2.3.22-SNAPSHOT struts2-plexus-plugin diff --git a/plugins/pom.xml b/plugins/pom.xml index 5ec9ed63f..2debf2694 100644 --- a/plugins/pom.xml +++ b/plugins/pom.xml @@ -26,7 +26,7 @@ org.apache.struts struts2-parent - 2.3.21 + 2.3.22-SNAPSHOT struts2-plugins diff --git a/plugins/portlet-tiles/pom.xml b/plugins/portlet-tiles/pom.xml index d743266bc..146d577ba 100644 --- a/plugins/portlet-tiles/pom.xml +++ b/plugins/portlet-tiles/pom.xml @@ -26,7 +26,7 @@ org.apache.struts struts2-plugins - 2.3.21 + 2.3.22-SNAPSHOT struts2-portlet-tiles-plugin diff --git a/plugins/portlet/pom.xml b/plugins/portlet/pom.xml index a0efdccfb..88d06cbf7 100644 --- a/plugins/portlet/pom.xml +++ b/plugins/portlet/pom.xml @@ -3,7 +3,7 @@ org.apache.struts struts2-plugins - 2.3.21 + 2.3.22-SNAPSHOT struts2-portlet-plugin diff --git a/plugins/rest/pom.xml b/plugins/rest/pom.xml index 27b8e1da6..7b183b81b 100644 --- a/plugins/rest/pom.xml +++ b/plugins/rest/pom.xml @@ -26,7 +26,7 @@ org.apache.struts struts2-plugins - 2.3.21 + 2.3.22-SNAPSHOT struts2-rest-plugin diff --git a/plugins/sitegraph/pom.xml b/plugins/sitegraph/pom.xml index f2b27bf65..01156123c 100644 --- a/plugins/sitegraph/pom.xml +++ b/plugins/sitegraph/pom.xml @@ -26,7 +26,7 @@ org.apache.struts struts2-plugins - 2.3.21 + 2.3.22-SNAPSHOT struts2-sitegraph-plugin diff --git a/plugins/sitemesh/pom.xml b/plugins/sitemesh/pom.xml index 2bfd65a8c..753ed3f09 100644 --- a/plugins/sitemesh/pom.xml +++ b/plugins/sitemesh/pom.xml @@ -26,7 +26,7 @@ org.apache.struts struts2-plugins - 2.3.21 + 2.3.22-SNAPSHOT struts2-sitemesh-plugin diff --git a/plugins/spring/pom.xml b/plugins/spring/pom.xml index 5e2ed9353..f2b1aff27 100644 --- a/plugins/spring/pom.xml +++ b/plugins/spring/pom.xml @@ -26,7 +26,7 @@ org.apache.struts struts2-plugins - 2.3.21 + 2.3.22-SNAPSHOT struts2-spring-plugin diff --git a/plugins/struts1/pom.xml b/plugins/struts1/pom.xml index 6f9453238..c732a76c3 100644 --- a/plugins/struts1/pom.xml +++ b/plugins/struts1/pom.xml @@ -26,7 +26,7 @@ org.apache.struts struts2-plugins - 2.3.21 + 2.3.22-SNAPSHOT struts2-struts1-plugin diff --git a/plugins/testng/pom.xml b/plugins/testng/pom.xml index 27e8f2f69..bb68d5510 100644 --- a/plugins/testng/pom.xml +++ b/plugins/testng/pom.xml @@ -26,7 +26,7 @@ org.apache.struts struts2-plugins - 2.3.21 + 2.3.22-SNAPSHOT struts2-testng-plugin diff --git a/plugins/tiles/pom.xml b/plugins/tiles/pom.xml index c4a9e7f34..0cd82837f 100644 --- a/plugins/tiles/pom.xml +++ b/plugins/tiles/pom.xml @@ -26,7 +26,7 @@ org.apache.struts struts2-plugins - 2.3.21 + 2.3.22-SNAPSHOT struts2-tiles-plugin diff --git a/plugins/tiles3/pom.xml b/plugins/tiles3/pom.xml index f3b14b5bc..209304f2f 100644 --- a/plugins/tiles3/pom.xml +++ b/plugins/tiles3/pom.xml @@ -26,7 +26,7 @@ org.apache.struts struts2-plugins - 2.3.21 + 2.3.22-SNAPSHOT struts2-tiles3-plugin diff --git a/pom.xml b/pom.xml index 72fbd8ee1..94f9fba78 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ 4.0.0 struts2-parent - 2.3.21 + 2.3.22-SNAPSHOT pom Struts 2 http://struts.apache.org/ @@ -31,7 +31,7 @@ scm:git:git://git.apache.org/struts.git scm:git:https://git-wip-us.apache.org/repos/asf/struts.git http://git.apache.org/struts.git - STRUTS_2_3_21 + HEAD diff --git a/xwork-core/pom.xml b/xwork-core/pom.xml index 4b662dfe9..b6424145c 100644 --- a/xwork-core/pom.xml +++ b/xwork-core/pom.xml @@ -5,7 +5,7 @@ org.apache.struts struts2-parent - 2.3.21 + 2.3.22-SNAPSHOT org.apache.struts.xwork