From 04eaa47ec10ead550e166c6cdb8bc49f1a8910b3 Mon Sep 17 00:00:00 2001 From: Arjen Poutsma Date: Fri, 24 Oct 2008 09:42:55 +0000 Subject: [PATCH] Initial import of web module --- org.springframework.web/build.xml | 6 + org.springframework.web/ivy.xml | 47 ++ org.springframework.web/pom.xml | 59 ++ .../caucho/BurlapClientInterceptor.java | 187 +++++ .../remoting/caucho/BurlapExporter.java | 110 +++ .../caucho/BurlapProxyFactoryBean.java | 66 ++ .../caucho/BurlapServiceExporter.java | 75 ++ .../caucho/Hessian1SkeletonInvoker.java | 88 ++ .../caucho/Hessian2SkeletonInvoker.java | 123 +++ .../caucho/HessianClientInterceptor.java | 259 ++++++ .../remoting/caucho/HessianExporter.java | 149 ++++ .../caucho/HessianProxyFactoryBean.java | 66 ++ .../caucho/HessianServiceExporter.java | 77 ++ .../caucho/HessianSkeletonInvoker.java | 67 ++ .../caucho/SimpleBurlapServiceExporter.java | 73 ++ .../caucho/SimpleHessianServiceExporter.java | 73 ++ .../remoting/caucho/package.html | 17 + .../AbstractHttpInvokerRequestExecutor.java | 299 +++++++ .../CommonsHttpInvokerRequestExecutor.java | 258 ++++++ .../HttpInvokerClientConfiguration.java | 42 + .../HttpInvokerClientInterceptor.java | 216 +++++ .../HttpInvokerProxyFactoryBean.java | 77 ++ .../HttpInvokerRequestExecutor.java | 59 ++ .../HttpInvokerServiceExporter.java | 199 +++++ .../SimpleHttpInvokerRequestExecutor.java | 192 +++++ .../SimpleHttpInvokerServiceExporter.java | 177 ++++ .../remoting/httpinvoker/package.html | 14 + .../jaxrpc/JaxRpcPortClientInterceptor.java | 757 ++++++++++++++++++ .../jaxrpc/JaxRpcPortProxyFactoryBean.java | 87 ++ .../jaxrpc/JaxRpcServicePostProcessor.java | 51 ++ .../jaxrpc/JaxRpcSoapFaultException.java | 65 ++ .../jaxrpc/LocalJaxRpcServiceFactory.java | 321 ++++++++ .../jaxrpc/LocalJaxRpcServiceFactoryBean.java | 62 ++ .../jaxrpc/ServletEndpointSupport.java | 150 ++++ .../remoting/jaxrpc/package.html | 10 + .../AxisBeanMappingServicePostProcessor.java | 210 +++++ .../remoting/jaxrpc/support/package.html | 8 + .../jaxws/AbstractJaxWsServiceExporter.java | 148 ++++ .../jaxws/JaxWsPortClientInterceptor.java | 426 ++++++++++ .../jaxws/JaxWsPortProxyFactoryBean.java | 72 ++ .../jaxws/JaxWsSoapFaultException.java | 66 ++ .../jaxws/LocalJaxWsServiceFactory.java | 156 ++++ .../jaxws/LocalJaxWsServiceFactoryBean.java | 59 ++ .../SimpleHttpServerJaxWsServiceExporter.java | 186 +++++ .../jaxws/SimpleJaxWsServiceExporter.java | 70 ++ .../remoting/jaxws/package.html | 9 + .../web/HttpRequestHandler.java | 90 +++ ...ttpRequestMethodNotSupportedException.java | 88 ++ .../web/HttpSessionRequiredException.java | 37 + .../ConfigurableWebApplicationContext.java | 95 +++ .../web/context/ContextLoader.java | 386 +++++++++ .../web/context/ContextLoader.properties | 5 + .../web/context/ContextLoaderListener.java | 74 ++ .../web/context/ContextLoaderServlet.java | 129 +++ .../web/context/ServletConfigAware.java | 48 ++ .../web/context/ServletContextAware.java | 44 + .../web/context/WebApplicationContext.java | 81 ++ .../springframework/web/context/package.html | 8 + .../request/AbstractRequestAttributes.java | 104 +++ .../AbstractRequestAttributesScope.java | 77 ++ .../request/FacesRequestAttributes.java | 149 ++++ .../web/context/request/FacesWebRequest.java | 117 +++ ...g4jNestedDiagnosticContextInterceptor.java | 91 +++ .../web/context/request/NativeWebRequest.java | 47 ++ .../context/request/RequestAttributes.java | 124 +++ .../context/request/RequestContextHolder.java | 145 ++++ .../request/RequestContextListener.java | 94 +++ .../web/context/request/RequestScope.java | 58 ++ .../request/ServletRequestAttributes.java | 273 +++++++ .../context/request/ServletWebRequest.java | 166 ++++ .../web/context/request/SessionScope.java | 101 +++ .../web/context/request/WebRequest.java | 129 +++ .../request/WebRequestInterceptor.java | 89 ++ .../web/context/request/package.html | 8 + ...tractRefreshableWebApplicationContext.java | 173 ++++ .../ContextExposingHttpServletRequest.java | 99 +++ .../support/GenericWebApplicationContext.java | 137 ++++ .../support/HttpRequestHandlerServlet.java | 79 ++ .../support/PerformanceMonitorListener.java | 57 ++ .../context/support/RequestHandledEvent.java | 157 ++++ .../ServletContextAttributeExporter.java | 84 ++ .../ServletContextAttributeFactoryBean.java | 76 ++ .../support/ServletContextAwareProcessor.java | 87 ++ .../support/ServletContextFactoryBean.java | 65 ++ .../ServletContextParameterFactoryBean.java | 71 ++ ...tContextPropertyPlaceholderConfigurer.java | 158 ++++ .../support/ServletContextResource.java | 200 +++++ .../support/ServletContextResourceLoader.java | 61 ++ ...ServletContextResourcePatternResolver.java | 126 +++ .../support/ServletRequestHandledEvent.java | 142 ++++ .../support/SpringBeanAutowiringSupport.java | 94 +++ .../support/StaticWebApplicationContext.java | 172 ++++ .../support/WebApplicationContextUtils.java | 149 ++++ .../support/WebApplicationObjectSupport.java | 140 ++++ .../support/XmlWebApplicationContext.java | 143 ++++ .../web/context/support/package.html | 8 + .../filter/AbstractRequestLoggingFilter.java | 231 ++++++ .../web/filter/CharacterEncodingFilter.java | 99 +++ .../filter/CommonsRequestLoggingFilter.java | 54 ++ .../web/filter/DelegatingFilterProxy.java | 252 ++++++ .../web/filter/GenericFilterBean.java | 304 +++++++ .../Log4jNestedDiagnosticContextFilter.java | 82 ++ .../web/filter/OncePerRequestFilter.java | 125 +++ .../web/filter/RequestContextFilter.java | 95 +++ .../ServletContextRequestLoggingFilter.java | 50 ++ .../springframework/web/filter/package.html | 7 + .../web/jsf/DecoratingNavigationHandler.java | 151 ++++ .../jsf/DelegatingNavigationHandlerProxy.java | 167 ++++ .../DelegatingPhaseListenerMulticaster.java | 125 +++ .../web/jsf/DelegatingVariableResolver.java | 169 ++++ .../web/jsf/FacesContextUtils.java | 118 +++ .../web/jsf/SpringBeanVariableResolver.java | 53 ++ ...WebApplicationContextVariableResolver.java | 113 +++ .../web/jsf/el/SpringBeanFacesELResolver.java | 93 +++ .../WebApplicationContextFacesELResolver.java | 175 ++++ .../springframework/web/jsf/el/package.html | 11 + .../org/springframework/web/jsf/package.html | 11 + .../java/org/springframework/web/package.html | 8 + .../web/util/CookieGenerator.java | 204 +++++ .../web/util/ExpressionEvaluationUtils.java | 456 +++++++++++ .../web/util/HtmlCharacterEntityDecoder.java | 153 ++++ .../util/HtmlCharacterEntityReferences.java | 138 ++++ .../HtmlCharacterEntityReferences.properties | 268 +++++++ .../springframework/web/util/HtmlUtils.java | 165 ++++ .../web/util/HttpSessionMutexListener.java | 64 ++ .../web/util/IntrospectorCleanupListener.java | 83 ++ .../web/util/JavaScriptUtils.java | 85 ++ .../web/util/Log4jConfigListener.java | 54 ++ .../web/util/Log4jConfigServlet.java | 82 ++ .../web/util/Log4jWebConfigurer.java | 190 +++++ .../web/util/NestedServletException.java | 78 ++ .../springframework/web/util/TagUtils.java | 136 ++++ .../web/util/UrlPathHelper.java | 357 +++++++++ .../web/util/WebAppRootListener.java | 63 ++ .../springframework/web/util/WebUtils.java | 679 ++++++++++++++++ .../org/springframework/web/util/package.html | 8 + .../src/main/java/overview.html | 7 + .../src/test/resources/log4j.xml | 28 + org.springframework.web/template.mf | 74 ++ 139 files changed, 17188 insertions(+) create mode 100644 org.springframework.web/build.xml create mode 100644 org.springframework.web/ivy.xml create mode 100644 org.springframework.web/pom.xml create mode 100644 org.springframework.web/src/main/java/org/springframework/remoting/caucho/BurlapClientInterceptor.java create mode 100644 org.springframework.web/src/main/java/org/springframework/remoting/caucho/BurlapExporter.java create mode 100644 org.springframework.web/src/main/java/org/springframework/remoting/caucho/BurlapProxyFactoryBean.java create mode 100644 org.springframework.web/src/main/java/org/springframework/remoting/caucho/BurlapServiceExporter.java create mode 100644 org.springframework.web/src/main/java/org/springframework/remoting/caucho/Hessian1SkeletonInvoker.java create mode 100644 org.springframework.web/src/main/java/org/springframework/remoting/caucho/Hessian2SkeletonInvoker.java create mode 100644 org.springframework.web/src/main/java/org/springframework/remoting/caucho/HessianClientInterceptor.java create mode 100644 org.springframework.web/src/main/java/org/springframework/remoting/caucho/HessianExporter.java create mode 100644 org.springframework.web/src/main/java/org/springframework/remoting/caucho/HessianProxyFactoryBean.java create mode 100644 org.springframework.web/src/main/java/org/springframework/remoting/caucho/HessianServiceExporter.java create mode 100644 org.springframework.web/src/main/java/org/springframework/remoting/caucho/HessianSkeletonInvoker.java create mode 100644 org.springframework.web/src/main/java/org/springframework/remoting/caucho/SimpleBurlapServiceExporter.java create mode 100644 org.springframework.web/src/main/java/org/springframework/remoting/caucho/SimpleHessianServiceExporter.java create mode 100644 org.springframework.web/src/main/java/org/springframework/remoting/caucho/package.html create mode 100644 org.springframework.web/src/main/java/org/springframework/remoting/httpinvoker/AbstractHttpInvokerRequestExecutor.java create mode 100644 org.springframework.web/src/main/java/org/springframework/remoting/httpinvoker/CommonsHttpInvokerRequestExecutor.java create mode 100644 org.springframework.web/src/main/java/org/springframework/remoting/httpinvoker/HttpInvokerClientConfiguration.java create mode 100644 org.springframework.web/src/main/java/org/springframework/remoting/httpinvoker/HttpInvokerClientInterceptor.java create mode 100644 org.springframework.web/src/main/java/org/springframework/remoting/httpinvoker/HttpInvokerProxyFactoryBean.java create mode 100644 org.springframework.web/src/main/java/org/springframework/remoting/httpinvoker/HttpInvokerRequestExecutor.java create mode 100644 org.springframework.web/src/main/java/org/springframework/remoting/httpinvoker/HttpInvokerServiceExporter.java create mode 100644 org.springframework.web/src/main/java/org/springframework/remoting/httpinvoker/SimpleHttpInvokerRequestExecutor.java create mode 100644 org.springframework.web/src/main/java/org/springframework/remoting/httpinvoker/SimpleHttpInvokerServiceExporter.java create mode 100644 org.springframework.web/src/main/java/org/springframework/remoting/httpinvoker/package.html create mode 100644 org.springframework.web/src/main/java/org/springframework/remoting/jaxrpc/JaxRpcPortClientInterceptor.java create mode 100644 org.springframework.web/src/main/java/org/springframework/remoting/jaxrpc/JaxRpcPortProxyFactoryBean.java create mode 100644 org.springframework.web/src/main/java/org/springframework/remoting/jaxrpc/JaxRpcServicePostProcessor.java create mode 100644 org.springframework.web/src/main/java/org/springframework/remoting/jaxrpc/JaxRpcSoapFaultException.java create mode 100644 org.springframework.web/src/main/java/org/springframework/remoting/jaxrpc/LocalJaxRpcServiceFactory.java create mode 100644 org.springframework.web/src/main/java/org/springframework/remoting/jaxrpc/LocalJaxRpcServiceFactoryBean.java create mode 100644 org.springframework.web/src/main/java/org/springframework/remoting/jaxrpc/ServletEndpointSupport.java create mode 100644 org.springframework.web/src/main/java/org/springframework/remoting/jaxrpc/package.html create mode 100644 org.springframework.web/src/main/java/org/springframework/remoting/jaxrpc/support/AxisBeanMappingServicePostProcessor.java create mode 100644 org.springframework.web/src/main/java/org/springframework/remoting/jaxrpc/support/package.html create mode 100644 org.springframework.web/src/main/java/org/springframework/remoting/jaxws/AbstractJaxWsServiceExporter.java create mode 100644 org.springframework.web/src/main/java/org/springframework/remoting/jaxws/JaxWsPortClientInterceptor.java create mode 100644 org.springframework.web/src/main/java/org/springframework/remoting/jaxws/JaxWsPortProxyFactoryBean.java create mode 100644 org.springframework.web/src/main/java/org/springframework/remoting/jaxws/JaxWsSoapFaultException.java create mode 100644 org.springframework.web/src/main/java/org/springframework/remoting/jaxws/LocalJaxWsServiceFactory.java create mode 100644 org.springframework.web/src/main/java/org/springframework/remoting/jaxws/LocalJaxWsServiceFactoryBean.java create mode 100644 org.springframework.web/src/main/java/org/springframework/remoting/jaxws/SimpleHttpServerJaxWsServiceExporter.java create mode 100644 org.springframework.web/src/main/java/org/springframework/remoting/jaxws/SimpleJaxWsServiceExporter.java create mode 100644 org.springframework.web/src/main/java/org/springframework/remoting/jaxws/package.html create mode 100644 org.springframework.web/src/main/java/org/springframework/web/HttpRequestHandler.java create mode 100644 org.springframework.web/src/main/java/org/springframework/web/HttpRequestMethodNotSupportedException.java create mode 100644 org.springframework.web/src/main/java/org/springframework/web/HttpSessionRequiredException.java create mode 100644 org.springframework.web/src/main/java/org/springframework/web/context/ConfigurableWebApplicationContext.java create mode 100644 org.springframework.web/src/main/java/org/springframework/web/context/ContextLoader.java create mode 100644 org.springframework.web/src/main/java/org/springframework/web/context/ContextLoader.properties create mode 100644 org.springframework.web/src/main/java/org/springframework/web/context/ContextLoaderListener.java create mode 100644 org.springframework.web/src/main/java/org/springframework/web/context/ContextLoaderServlet.java create mode 100644 org.springframework.web/src/main/java/org/springframework/web/context/ServletConfigAware.java create mode 100644 org.springframework.web/src/main/java/org/springframework/web/context/ServletContextAware.java create mode 100644 org.springframework.web/src/main/java/org/springframework/web/context/WebApplicationContext.java create mode 100644 org.springframework.web/src/main/java/org/springframework/web/context/package.html create mode 100644 org.springframework.web/src/main/java/org/springframework/web/context/request/AbstractRequestAttributes.java create mode 100644 org.springframework.web/src/main/java/org/springframework/web/context/request/AbstractRequestAttributesScope.java create mode 100644 org.springframework.web/src/main/java/org/springframework/web/context/request/FacesRequestAttributes.java create mode 100644 org.springframework.web/src/main/java/org/springframework/web/context/request/FacesWebRequest.java create mode 100644 org.springframework.web/src/main/java/org/springframework/web/context/request/Log4jNestedDiagnosticContextInterceptor.java create mode 100644 org.springframework.web/src/main/java/org/springframework/web/context/request/NativeWebRequest.java create mode 100644 org.springframework.web/src/main/java/org/springframework/web/context/request/RequestAttributes.java create mode 100644 org.springframework.web/src/main/java/org/springframework/web/context/request/RequestContextHolder.java create mode 100644 org.springframework.web/src/main/java/org/springframework/web/context/request/RequestContextListener.java create mode 100644 org.springframework.web/src/main/java/org/springframework/web/context/request/RequestScope.java create mode 100644 org.springframework.web/src/main/java/org/springframework/web/context/request/ServletRequestAttributes.java create mode 100644 org.springframework.web/src/main/java/org/springframework/web/context/request/ServletWebRequest.java create mode 100644 org.springframework.web/src/main/java/org/springframework/web/context/request/SessionScope.java create mode 100644 org.springframework.web/src/main/java/org/springframework/web/context/request/WebRequest.java create mode 100644 org.springframework.web/src/main/java/org/springframework/web/context/request/WebRequestInterceptor.java create mode 100644 org.springframework.web/src/main/java/org/springframework/web/context/request/package.html create mode 100644 org.springframework.web/src/main/java/org/springframework/web/context/support/AbstractRefreshableWebApplicationContext.java create mode 100644 org.springframework.web/src/main/java/org/springframework/web/context/support/ContextExposingHttpServletRequest.java create mode 100644 org.springframework.web/src/main/java/org/springframework/web/context/support/GenericWebApplicationContext.java create mode 100644 org.springframework.web/src/main/java/org/springframework/web/context/support/HttpRequestHandlerServlet.java create mode 100644 org.springframework.web/src/main/java/org/springframework/web/context/support/PerformanceMonitorListener.java create mode 100644 org.springframework.web/src/main/java/org/springframework/web/context/support/RequestHandledEvent.java create mode 100644 org.springframework.web/src/main/java/org/springframework/web/context/support/ServletContextAttributeExporter.java create mode 100644 org.springframework.web/src/main/java/org/springframework/web/context/support/ServletContextAttributeFactoryBean.java create mode 100644 org.springframework.web/src/main/java/org/springframework/web/context/support/ServletContextAwareProcessor.java create mode 100644 org.springframework.web/src/main/java/org/springframework/web/context/support/ServletContextFactoryBean.java create mode 100644 org.springframework.web/src/main/java/org/springframework/web/context/support/ServletContextParameterFactoryBean.java create mode 100644 org.springframework.web/src/main/java/org/springframework/web/context/support/ServletContextPropertyPlaceholderConfigurer.java create mode 100644 org.springframework.web/src/main/java/org/springframework/web/context/support/ServletContextResource.java create mode 100644 org.springframework.web/src/main/java/org/springframework/web/context/support/ServletContextResourceLoader.java create mode 100644 org.springframework.web/src/main/java/org/springframework/web/context/support/ServletContextResourcePatternResolver.java create mode 100644 org.springframework.web/src/main/java/org/springframework/web/context/support/ServletRequestHandledEvent.java create mode 100644 org.springframework.web/src/main/java/org/springframework/web/context/support/SpringBeanAutowiringSupport.java create mode 100644 org.springframework.web/src/main/java/org/springframework/web/context/support/StaticWebApplicationContext.java create mode 100644 org.springframework.web/src/main/java/org/springframework/web/context/support/WebApplicationContextUtils.java create mode 100644 org.springframework.web/src/main/java/org/springframework/web/context/support/WebApplicationObjectSupport.java create mode 100644 org.springframework.web/src/main/java/org/springframework/web/context/support/XmlWebApplicationContext.java create mode 100644 org.springframework.web/src/main/java/org/springframework/web/context/support/package.html create mode 100644 org.springframework.web/src/main/java/org/springframework/web/filter/AbstractRequestLoggingFilter.java create mode 100644 org.springframework.web/src/main/java/org/springframework/web/filter/CharacterEncodingFilter.java create mode 100644 org.springframework.web/src/main/java/org/springframework/web/filter/CommonsRequestLoggingFilter.java create mode 100644 org.springframework.web/src/main/java/org/springframework/web/filter/DelegatingFilterProxy.java create mode 100644 org.springframework.web/src/main/java/org/springframework/web/filter/GenericFilterBean.java create mode 100644 org.springframework.web/src/main/java/org/springframework/web/filter/Log4jNestedDiagnosticContextFilter.java create mode 100644 org.springframework.web/src/main/java/org/springframework/web/filter/OncePerRequestFilter.java create mode 100644 org.springframework.web/src/main/java/org/springframework/web/filter/RequestContextFilter.java create mode 100644 org.springframework.web/src/main/java/org/springframework/web/filter/ServletContextRequestLoggingFilter.java create mode 100644 org.springframework.web/src/main/java/org/springframework/web/filter/package.html create mode 100644 org.springframework.web/src/main/java/org/springframework/web/jsf/DecoratingNavigationHandler.java create mode 100644 org.springframework.web/src/main/java/org/springframework/web/jsf/DelegatingNavigationHandlerProxy.java create mode 100644 org.springframework.web/src/main/java/org/springframework/web/jsf/DelegatingPhaseListenerMulticaster.java create mode 100644 org.springframework.web/src/main/java/org/springframework/web/jsf/DelegatingVariableResolver.java create mode 100644 org.springframework.web/src/main/java/org/springframework/web/jsf/FacesContextUtils.java create mode 100644 org.springframework.web/src/main/java/org/springframework/web/jsf/SpringBeanVariableResolver.java create mode 100644 org.springframework.web/src/main/java/org/springframework/web/jsf/WebApplicationContextVariableResolver.java create mode 100644 org.springframework.web/src/main/java/org/springframework/web/jsf/el/SpringBeanFacesELResolver.java create mode 100644 org.springframework.web/src/main/java/org/springframework/web/jsf/el/WebApplicationContextFacesELResolver.java create mode 100644 org.springframework.web/src/main/java/org/springframework/web/jsf/el/package.html create mode 100644 org.springframework.web/src/main/java/org/springframework/web/jsf/package.html create mode 100644 org.springframework.web/src/main/java/org/springframework/web/package.html create mode 100644 org.springframework.web/src/main/java/org/springframework/web/util/CookieGenerator.java create mode 100644 org.springframework.web/src/main/java/org/springframework/web/util/ExpressionEvaluationUtils.java create mode 100644 org.springframework.web/src/main/java/org/springframework/web/util/HtmlCharacterEntityDecoder.java create mode 100644 org.springframework.web/src/main/java/org/springframework/web/util/HtmlCharacterEntityReferences.java create mode 100644 org.springframework.web/src/main/java/org/springframework/web/util/HtmlCharacterEntityReferences.properties create mode 100644 org.springframework.web/src/main/java/org/springframework/web/util/HtmlUtils.java create mode 100644 org.springframework.web/src/main/java/org/springframework/web/util/HttpSessionMutexListener.java create mode 100644 org.springframework.web/src/main/java/org/springframework/web/util/IntrospectorCleanupListener.java create mode 100644 org.springframework.web/src/main/java/org/springframework/web/util/JavaScriptUtils.java create mode 100644 org.springframework.web/src/main/java/org/springframework/web/util/Log4jConfigListener.java create mode 100644 org.springframework.web/src/main/java/org/springframework/web/util/Log4jConfigServlet.java create mode 100644 org.springframework.web/src/main/java/org/springframework/web/util/Log4jWebConfigurer.java create mode 100644 org.springframework.web/src/main/java/org/springframework/web/util/NestedServletException.java create mode 100644 org.springframework.web/src/main/java/org/springframework/web/util/TagUtils.java create mode 100644 org.springframework.web/src/main/java/org/springframework/web/util/UrlPathHelper.java create mode 100644 org.springframework.web/src/main/java/org/springframework/web/util/WebAppRootListener.java create mode 100644 org.springframework.web/src/main/java/org/springframework/web/util/WebUtils.java create mode 100644 org.springframework.web/src/main/java/org/springframework/web/util/package.html create mode 100644 org.springframework.web/src/main/java/overview.html create mode 100644 org.springframework.web/src/test/resources/log4j.xml create mode 100644 org.springframework.web/template.mf diff --git a/org.springframework.web/build.xml b/org.springframework.web/build.xml new file mode 100644 index 0000000000..ac4c8153a5 --- /dev/null +++ b/org.springframework.web/build.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/org.springframework.web/ivy.xml b/org.springframework.web/ivy.xml new file mode 100644 index 0000000000..3242c99383 --- /dev/null +++ b/org.springframework.web/ivy.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/org.springframework.web/pom.xml b/org.springframework.web/pom.xml new file mode 100644 index 0000000000..b4cee14adc --- /dev/null +++ b/org.springframework.web/pom.xml @@ -0,0 +1,59 @@ + + + 4.0.0 + org.springframework + org.springframework.core + jar + Spring Core Abstractions and Utilities + 3.0.0.M1 + + + com.springsource.repository.bundles.external + SpringSource Enterprise Bundle Repository - External Bundle Releases + http://repository.springsource.com/maven/bundles/external + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.5 + 1.5 + + + + + + + org.apache.commons + com.springsource.org.apache.commons.logging + 1.1.1 + + + org.apache.log4j + com.springsource.org.apache.log4j + 1.2.15 + true + + + org.apache.commons + com.springsource.org.apache.commons.collections + 3.2.0 + true + + + org.aspectj + com.springsource.org.aspectj.weaver + 1.6.2.RELEASE + true + + + org.objectweb.asm + com.springsource.org.objectweb.asm.commons + 2.2.3 + true + + + \ No newline at end of file diff --git a/org.springframework.web/src/main/java/org/springframework/remoting/caucho/BurlapClientInterceptor.java b/org.springframework.web/src/main/java/org/springframework/remoting/caucho/BurlapClientInterceptor.java new file mode 100644 index 0000000000..f9fc168e5a --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/remoting/caucho/BurlapClientInterceptor.java @@ -0,0 +1,187 @@ +/* + * Copyright 2002-2008 the original author or authors. + * + * 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.springframework.remoting.caucho; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.UndeclaredThrowableException; +import java.net.ConnectException; +import java.net.MalformedURLException; + +import com.caucho.burlap.client.BurlapProxyFactory; +import com.caucho.burlap.client.BurlapRuntimeException; +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; + +import org.springframework.remoting.RemoteAccessException; +import org.springframework.remoting.RemoteConnectFailureException; +import org.springframework.remoting.RemoteLookupFailureException; +import org.springframework.remoting.RemoteProxyFailureException; +import org.springframework.remoting.support.UrlBasedRemoteAccessor; +import org.springframework.util.Assert; + +/** + * {@link org.aopalliance.intercept.MethodInterceptor} for accessing a Burlap service. + * Supports authentication via username and password. + * The service URL must be an HTTP URL exposing a Burlap service. + * + *

Burlap is a slim, XML-based RPC protocol. + * For information on Burlap, see the + * Burlap website + * + *

Note: There is no requirement for services accessed with this proxy factory + * to have been exported using Spring's {@link BurlapServiceExporter}, as there is + * no special handling involved. As a consequence, you can also access services that + * have been exported using Caucho's {@link com.caucho.burlap.server.BurlapServlet}. + * + * @author Juergen Hoeller + * @since 29.09.2003 + * @see #setServiceInterface + * @see #setServiceUrl + * @see #setUsername + * @see #setPassword + * @see BurlapServiceExporter + * @see BurlapProxyFactoryBean + * @see com.caucho.burlap.client.BurlapProxyFactory + * @see com.caucho.burlap.server.BurlapServlet + */ +public class BurlapClientInterceptor extends UrlBasedRemoteAccessor implements MethodInterceptor { + + private BurlapProxyFactory proxyFactory = new BurlapProxyFactory(); + + private Object burlapProxy; + + + /** + * Set the BurlapProxyFactory instance to use. + * If not specified, a default BurlapProxyFactory will be created. + *

Allows to use an externally configured factory instance, + * in particular a custom BurlapProxyFactory subclass. + */ + public void setProxyFactory(BurlapProxyFactory proxyFactory) { + this.proxyFactory = (proxyFactory != null ? proxyFactory : new BurlapProxyFactory()); + } + + /** + * Set the username that this factory should use to access the remote service. + * Default is none. + *

The username will be sent by Burlap via HTTP Basic Authentication. + * @see com.caucho.burlap.client.BurlapProxyFactory#setUser + */ + public void setUsername(String username) { + this.proxyFactory.setUser(username); + } + + /** + * Set the password that this factory should use to access the remote service. + * Default is none. + *

The password will be sent by Burlap via HTTP Basic Authentication. + * @see com.caucho.burlap.client.BurlapProxyFactory#setPassword + */ + public void setPassword(String password) { + this.proxyFactory.setPassword(password); + } + + /** + * Set whether overloaded methods should be enabled for remote invocations. + * Default is "false". + * @see com.caucho.burlap.client.BurlapProxyFactory#setOverloadEnabled + */ + public void setOverloadEnabled(boolean overloadEnabled) { + this.proxyFactory.setOverloadEnabled(overloadEnabled); + } + + + public void afterPropertiesSet() { + super.afterPropertiesSet(); + prepare(); + } + + /** + * Initialize the Burlap proxy for this interceptor. + * @throws RemoteLookupFailureException if the service URL is invalid + */ + public void prepare() throws RemoteLookupFailureException { + try { + this.burlapProxy = createBurlapProxy(this.proxyFactory); + } + catch (MalformedURLException ex) { + throw new RemoteLookupFailureException("Service URL [" + getServiceUrl() + "] is invalid", ex); + } + } + + /** + * Create the Burlap proxy that is wrapped by this interceptor. + * @param proxyFactory the proxy factory to use + * @return the Burlap proxy + * @throws MalformedURLException if thrown by the proxy factory + * @see com.caucho.burlap.client.BurlapProxyFactory#create + */ + protected Object createBurlapProxy(BurlapProxyFactory proxyFactory) throws MalformedURLException { + Assert.notNull(getServiceInterface(), "Property 'serviceInterface' is required"); + return proxyFactory.create(getServiceInterface(), getServiceUrl()); + } + + + public Object invoke(MethodInvocation invocation) throws Throwable { + if (this.burlapProxy == null) { + throw new IllegalStateException("BurlapClientInterceptor is not properly initialized - " + + "invoke 'prepare' before attempting any operations"); + } + + ClassLoader originalClassLoader = overrideThreadContextClassLoader(); + try { + return invocation.getMethod().invoke(this.burlapProxy, invocation.getArguments()); + } + catch (InvocationTargetException ex) { + if (ex.getTargetException() instanceof BurlapRuntimeException) { + BurlapRuntimeException bre = (BurlapRuntimeException) ex.getTargetException(); + Throwable rootCause = (bre.getRootCause() != null ? bre.getRootCause() : bre); + throw convertBurlapAccessException(rootCause); + } + else if (ex.getTargetException() instanceof UndeclaredThrowableException) { + UndeclaredThrowableException utex = (UndeclaredThrowableException) ex.getTargetException(); + throw convertBurlapAccessException(utex.getUndeclaredThrowable()); + } + throw ex.getTargetException(); + } + catch (Throwable ex) { + throw new RemoteProxyFailureException( + "Failed to invoke Burlap proxy for remote service [" + getServiceUrl() + "]", ex); + } + finally { + resetThreadContextClassLoader(originalClassLoader); + } + } + + /** + * Convert the given Burlap access exception to an appropriate + * Spring RemoteAccessException. + * @param ex the exception to convert + * @return the RemoteAccessException to throw + */ + protected RemoteAccessException convertBurlapAccessException(Throwable ex) { + if (ex instanceof ConnectException) { + return new RemoteConnectFailureException( + "Cannot connect to Burlap remote service at [" + getServiceUrl() + "]", ex); + } + else { + return new RemoteAccessException( + "Cannot access Burlap remote service at [" + getServiceUrl() + "]", ex); + } + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/remoting/caucho/BurlapExporter.java b/org.springframework.web/src/main/java/org/springframework/remoting/caucho/BurlapExporter.java new file mode 100644 index 0000000000..6d2d245db2 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/remoting/caucho/BurlapExporter.java @@ -0,0 +1,110 @@ +/* + * Copyright 2002-2008 the original author or authors. + * + * 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.springframework.remoting.caucho; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.lang.reflect.Constructor; + +import com.caucho.burlap.io.BurlapInput; +import com.caucho.burlap.io.BurlapOutput; +import com.caucho.burlap.server.BurlapSkeleton; + +import org.springframework.beans.factory.BeanInitializationException; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.remoting.support.RemoteExporter; +import org.springframework.util.Assert; + +/** + * General stream-based protocol exporter for a Burlap endpoint. + * + *

Burlap is a slim, XML-based RPC protocol. + * For information on Burlap, see the + * Burlap website. + * + *

This exporter will work with both Burlap 2.x and 3.x (respectively + * Resin 2.x and 3.x), autodetecting the corresponding skeleton class. + * + * @author Juergen Hoeller + * @since 2.5.1 + * @see #invoke(java.io.InputStream, java.io.OutputStream) + * @see BurlapServiceExporter + * @see SimpleBurlapServiceExporter + */ +public class BurlapExporter extends RemoteExporter implements InitializingBean { + + private BurlapSkeleton skeleton; + + + public void afterPropertiesSet() { + prepare(); + } + + /** + * Initialize this service exporter. + */ + public void prepare() { + try { + try { + // Try Burlap 3.x (with service interface argument). + Constructor ctor = BurlapSkeleton.class.getConstructor(new Class[] {Object.class, Class.class}); + checkService(); + checkServiceInterface(); + this.skeleton = (BurlapSkeleton) + ctor.newInstance(new Object[] {getProxyForService(), getServiceInterface()}); + } + catch (NoSuchMethodException ex) { + // Fall back to Burlap 2.x (without service interface argument). + Constructor ctor = BurlapSkeleton.class.getConstructor(new Class[] {Object.class}); + this.skeleton = (BurlapSkeleton) ctor.newInstance(new Object[] {getProxyForService()}); + } + } + catch (Exception ex) { + throw new BeanInitializationException("Burlap skeleton initialization failed", ex); + } + } + + + /** + * Perform an invocation on the exported object. + * @param inputStream the request stream + * @param outputStream the response stream + * @throws Throwable if invocation failed + */ + public void invoke(InputStream inputStream, OutputStream outputStream) throws Throwable { + Assert.notNull(this.skeleton, "Burlap exporter has not been initialized"); + ClassLoader originalClassLoader = overrideThreadContextClassLoader(); + try { + this.skeleton.invoke(new BurlapInput(inputStream), new BurlapOutput(outputStream)); + } + finally { + try { + inputStream.close(); + } + catch (IOException ex) { + } + try { + outputStream.close(); + } + catch (IOException ex) { + } + resetThreadContextClassLoader(originalClassLoader); + } + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/remoting/caucho/BurlapProxyFactoryBean.java b/org.springframework.web/src/main/java/org/springframework/remoting/caucho/BurlapProxyFactoryBean.java new file mode 100644 index 0000000000..a34b5718ea --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/remoting/caucho/BurlapProxyFactoryBean.java @@ -0,0 +1,66 @@ +/* + * Copyright 2002-2008 the original author or authors. + * + * 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.springframework.remoting.caucho; + +import org.springframework.aop.framework.ProxyFactory; +import org.springframework.beans.factory.FactoryBean; + +/** + * FactoryBean for Burlap proxies. Exposes the proxied service for + * use as a bean reference, using the specified service interface. + * + *

Burlap is a slim, XML-based RPC protocol. + * For information on Burlap, see the + * Burlap website + * + *

The service URL must be an HTTP URL exposing a Burlap service. + * For details, see the {@link BurlapClientInterceptor} javadoc. + * + * @author Juergen Hoeller + * @since 13.05.2003 + * @see #setServiceInterface + * @see #setServiceUrl + * @see BurlapClientInterceptor + * @see BurlapServiceExporter + * @see org.springframework.remoting.caucho.HessianProxyFactoryBean + * @see org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean + * @see org.springframework.remoting.rmi.RmiProxyFactoryBean + */ +public class BurlapProxyFactoryBean extends BurlapClientInterceptor implements FactoryBean { + + private Object serviceProxy; + + + public void afterPropertiesSet() { + super.afterPropertiesSet(); + this.serviceProxy = new ProxyFactory(getServiceInterface(), this).getProxy(getBeanClassLoader()); + } + + + public Object getObject() { + return this.serviceProxy; + } + + public Class getObjectType() { + return getServiceInterface(); + } + + public boolean isSingleton() { + return true; + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/remoting/caucho/BurlapServiceExporter.java b/org.springframework.web/src/main/java/org/springframework/remoting/caucho/BurlapServiceExporter.java new file mode 100644 index 0000000000..4f1dc14a6b --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/remoting/caucho/BurlapServiceExporter.java @@ -0,0 +1,75 @@ +/* + * Copyright 2002-2008 the original author or authors. + * + * 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.springframework.remoting.caucho; + +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.web.HttpRequestHandler; +import org.springframework.web.HttpRequestMethodNotSupportedException; +import org.springframework.web.util.NestedServletException; + +/** + * Servlet-API-based HTTP request handler that exports the specified service bean + * as Burlap service endpoint, accessible via a Burlap proxy. + * + *

Note: Spring also provides an alternative version of this exporter, + * for Sun's JRE 1.6 HTTP server: {@link SimpleBurlapServiceExporter}. + * + *

Burlap is a slim, XML-based RPC protocol. + * For information on Burlap, see the + * Burlap website. + * + *

This exporter will work with both Burlap 2.x and 3.x (respectively + * Resin 2.x and 3.x), autodetecting the corresponding skeleton class. + * + *

Note: Burlap services exported with this class can be accessed by + * any Burlap client, as there isn't any special handling involved. + * + * @author Juergen Hoeller + * @since 13.05.2003 + * @see BurlapClientInterceptor + * @see BurlapProxyFactoryBean + * @see org.springframework.remoting.caucho.HessianServiceExporter + * @see org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter + * @see org.springframework.remoting.rmi.RmiServiceExporter + */ +public class BurlapServiceExporter extends BurlapExporter implements HttpRequestHandler { + + /** + * Processes the incoming Burlap request and creates a Burlap response. + */ + public void handleRequest(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + + if (!"POST".equals(request.getMethod())) { + throw new HttpRequestMethodNotSupportedException(request.getMethod(), + new String[] {"POST"}, "BurlapServiceExporter only supports POST requests"); + } + + try { + invoke(request.getInputStream(), response.getOutputStream()); + } + catch (Throwable ex) { + throw new NestedServletException("Burlap skeleton invocation failed", ex); + } + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/remoting/caucho/Hessian1SkeletonInvoker.java b/org.springframework.web/src/main/java/org/springframework/remoting/caucho/Hessian1SkeletonInvoker.java new file mode 100644 index 0000000000..517d2242d9 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/remoting/caucho/Hessian1SkeletonInvoker.java @@ -0,0 +1,88 @@ +/* + * Copyright 2002-2008 the original author or authors. + * + * 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.springframework.remoting.caucho; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.lang.reflect.Method; + +import com.caucho.hessian.io.HessianInput; +import com.caucho.hessian.io.HessianOutput; +import com.caucho.hessian.io.SerializerFactory; +import com.caucho.hessian.server.HessianSkeleton; + +import org.springframework.util.ClassUtils; + +/** + * Concrete HessianSkeletonInvoker for the Hessian 1 protocol + * (version 3.0.19 or lower). + * + * @author Juergen Hoeller + * @since 2.0 + */ +class Hessian1SkeletonInvoker extends HessianSkeletonInvoker { + + private static final Method invokeMethod; + + private static final boolean applySerializerFactoryToOutput; + + static { + invokeMethod = ClassUtils.getMethodIfAvailable( + HessianSkeleton.class, "invoke", new Class[] {HessianInput.class, HessianOutput.class}); + applySerializerFactoryToOutput = + ClassUtils.hasMethod(HessianOutput.class, "setSerializerFactory", new Class[] {SerializerFactory.class}); + } + + + public Hessian1SkeletonInvoker(HessianSkeleton skeleton, SerializerFactory serializerFactory) { + super(skeleton, serializerFactory); + if (invokeMethod == null) { + throw new IllegalStateException("Hessian 1 (version 3.0.19-) not present"); + } + } + + public void invoke(InputStream inputStream, OutputStream outputStream) throws Throwable { + HessianInput in = new HessianInput(inputStream); + HessianOutput out = new HessianOutput(outputStream); + if (this.serializerFactory != null) { + in.setSerializerFactory(this.serializerFactory); + if (applySerializerFactoryToOutput) { + out.setSerializerFactory(this.serializerFactory); + } + } + try { + invokeMethod.invoke(this.skeleton, new Object[] {in, out}); + } + finally { + try { + in.close(); + inputStream.close(); + } + catch (IOException ex) { + } + try { + out.close(); + outputStream.close(); + } + catch (IOException ex) { + } + } + + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/remoting/caucho/Hessian2SkeletonInvoker.java b/org.springframework.web/src/main/java/org/springframework/remoting/caucho/Hessian2SkeletonInvoker.java new file mode 100644 index 0000000000..c491dbc1c7 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/remoting/caucho/Hessian2SkeletonInvoker.java @@ -0,0 +1,123 @@ +/* + * Copyright 2002-2008 the original author or authors. + * + * 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.springframework.remoting.caucho; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PrintWriter; + +import com.caucho.hessian.io.AbstractHessianOutput; +import com.caucho.hessian.io.Hessian2Input; +import com.caucho.hessian.io.Hessian2Output; +import com.caucho.hessian.io.HessianDebugInputStream; +import com.caucho.hessian.io.HessianDebugOutputStream; +import com.caucho.hessian.io.HessianOutput; +import com.caucho.hessian.io.SerializerFactory; +import com.caucho.hessian.server.HessianSkeleton; +import org.apache.commons.logging.Log; + +import org.springframework.util.ClassUtils; +import org.springframework.util.CommonsLogWriter; + +/** + * Concrete HessianSkeletonInvoker for the Hessian 2 protocol + * (version 3.0.20 or higher). + * + * @author Juergen Hoeller + * @author Andy Piper + * @since 2.0 + */ +class Hessian2SkeletonInvoker extends HessianSkeletonInvoker { + + private static final boolean debugOutputStreamAvailable = ClassUtils.isPresent( + "com.caucho.hessian.io.HessianDebugOutputStream", Hessian2SkeletonInvoker.class.getClassLoader()); + + private final Log debugLogger; + + + public Hessian2SkeletonInvoker(HessianSkeleton skeleton, SerializerFactory serializerFactory, Log debugLog) { + super(skeleton, serializerFactory); + this.debugLogger = debugLog; + } + + public void invoke(final InputStream inputStream, final OutputStream outputStream) throws Throwable { + InputStream isToUse = inputStream; + OutputStream osToUse = outputStream; + + if (this.debugLogger != null && this.debugLogger.isDebugEnabled()) { + PrintWriter debugWriter = new PrintWriter(new CommonsLogWriter(this.debugLogger)); + isToUse = new HessianDebugInputStream(inputStream, debugWriter); + if (debugOutputStreamAvailable) { + osToUse = DebugStreamFactory.createDebugOutputStream(outputStream, debugWriter); + } + } + + Hessian2Input in = new Hessian2Input(isToUse); + if (this.serializerFactory != null) { + in.setSerializerFactory(this.serializerFactory); + } + + int code = in.read(); + if (code != 'c') { + throw new IOException("expected 'c' in hessian input at " + code); + } + + AbstractHessianOutput out = null; + int major = in.read(); + int minor = in.read(); + if (major >= 2) { + out = new Hessian2Output(osToUse); + } + else { + out = new HessianOutput(osToUse); + } + if (this.serializerFactory != null) { + out.setSerializerFactory(this.serializerFactory); + } + + try { + this.skeleton.invoke(in, out); + } + finally { + try { + in.close(); + isToUse.close(); + } + catch (IOException ex) { + } + try { + out.close(); + osToUse.close(); + } + catch (IOException ex) { + } + } + } + + + /** + * Inner class to avoid hard dependency on Hessian 3.1.3's HessianDebugOutputStream. + */ + private static class DebugStreamFactory { + + public static OutputStream createDebugOutputStream(OutputStream os, PrintWriter debug) { + return new HessianDebugOutputStream(os, debug); + } + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/remoting/caucho/HessianClientInterceptor.java b/org.springframework.web/src/main/java/org/springframework/remoting/caucho/HessianClientInterceptor.java new file mode 100644 index 0000000000..7883ff0da6 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/remoting/caucho/HessianClientInterceptor.java @@ -0,0 +1,259 @@ +/* + * Copyright 2002-2008 the original author or authors. + * + * 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.springframework.remoting.caucho; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.UndeclaredThrowableException; +import java.net.ConnectException; +import java.net.MalformedURLException; + +import com.caucho.hessian.client.HessianProxyFactory; +import com.caucho.hessian.client.HessianRuntimeException; +import com.caucho.hessian.io.SerializerFactory; +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; + +import org.springframework.remoting.RemoteAccessException; +import org.springframework.remoting.RemoteConnectFailureException; +import org.springframework.remoting.RemoteLookupFailureException; +import org.springframework.remoting.RemoteProxyFailureException; +import org.springframework.remoting.support.UrlBasedRemoteAccessor; +import org.springframework.util.Assert; + +/** + * {@link org.aopalliance.intercept.MethodInterceptor} for accessing a Hessian service. + * Supports authentication via username and password. + * The service URL must be an HTTP URL exposing a Hessian service. + * + *

Hessian is a slim, binary RPC protocol. + * For information on Hessian, see the + * Hessian website + * + *

Note: There is no requirement for services accessed with this proxy factory + * to have been exported using Spring's {@link HessianServiceExporter}, as there is + * no special handling involved. As a consequence, you can also access services that + * have been exported using Caucho's {@link com.caucho.hessian.server.HessianServlet}. + * + * @author Juergen Hoeller + * @since 29.09.2003 + * @see #setServiceInterface + * @see #setServiceUrl + * @see #setUsername + * @see #setPassword + * @see HessianServiceExporter + * @see HessianProxyFactoryBean + * @see com.caucho.hessian.client.HessianProxyFactory + * @see com.caucho.hessian.server.HessianServlet + */ +public class HessianClientInterceptor extends UrlBasedRemoteAccessor implements MethodInterceptor { + + private HessianProxyFactory proxyFactory = new HessianProxyFactory(); + + private Object hessianProxy; + + + /** + * Set the HessianProxyFactory instance to use. + * If not specified, a default HessianProxyFactory will be created. + *

Allows to use an externally configured factory instance, + * in particular a custom HessianProxyFactory subclass. + */ + public void setProxyFactory(HessianProxyFactory proxyFactory) { + this.proxyFactory = (proxyFactory != null ? proxyFactory : new HessianProxyFactory()); + } + + /** + * Specify the Hessian SerializerFactory to use. + *

This will typically be passed in as an inner bean definition + * of type com.caucho.hessian.io.SerializerFactory, + * with custom bean property values applied. + */ + public void setSerializerFactory(SerializerFactory serializerFactory) { + this.proxyFactory.setSerializerFactory(serializerFactory); + } + + /** + * Set whether to send the Java collection type for each serialized + * collection. Default is "true". + */ + public void setSendCollectionType(boolean sendCollectionType) { + this.proxyFactory.getSerializerFactory().setSendCollectionType(sendCollectionType); + } + + /** + * Set whether overloaded methods should be enabled for remote invocations. + * Default is "false". + * @see com.caucho.hessian.client.HessianProxyFactory#setOverloadEnabled + */ + public void setOverloadEnabled(boolean overloadEnabled) { + this.proxyFactory.setOverloadEnabled(overloadEnabled); + } + + /** + * Set the username that this factory should use to access the remote service. + * Default is none. + *

The username will be sent by Hessian via HTTP Basic Authentication. + * @see com.caucho.hessian.client.HessianProxyFactory#setUser + */ + public void setUsername(String username) { + this.proxyFactory.setUser(username); + } + + /** + * Set the password that this factory should use to access the remote service. + * Default is none. + *

The password will be sent by Hessian via HTTP Basic Authentication. + * @see com.caucho.hessian.client.HessianProxyFactory#setPassword + */ + public void setPassword(String password) { + this.proxyFactory.setPassword(password); + } + + /** + * Set whether Hessian's debug mode should be enabled. + * Default is "false". + * @see com.caucho.hessian.client.HessianProxyFactory#setDebug + */ + public void setDebug(boolean debug) { + this.proxyFactory.setDebug(debug); + } + + /** + * Set whether to use a chunked post for sending a Hessian request. + * @see com.caucho.hessian.client.HessianProxyFactory#setChunkedPost + */ + public void setChunkedPost(boolean chunkedPost) { + this.proxyFactory.setChunkedPost(chunkedPost); + } + + /** + * Set the timeout to use when waiting for a reply from the Hessian service. + * @see com.caucho.hessian.client.HessianProxyFactory#setReadTimeout + */ + public void setReadTimeout(long timeout) { + this.proxyFactory.setReadTimeout(timeout); + } + + /** + * Set whether version 2 of the Hessian protocol should be used for + * parsing requests and replies. Default is "false". + * @see com.caucho.hessian.client.HessianProxyFactory#setHessian2Request + */ + public void setHessian2(boolean hessian2) { + this.proxyFactory.setHessian2Request(hessian2); + this.proxyFactory.setHessian2Reply(hessian2); + } + + /** + * Set whether version 2 of the Hessian protocol should be used for + * parsing requests. Default is "false". + * @see com.caucho.hessian.client.HessianProxyFactory#setHessian2Request + */ + public void setHessian2Request(boolean hessian2) { + this.proxyFactory.setHessian2Request(hessian2); + } + + /** + * Set whether version 2 of the Hessian protocol should be used for + * parsing replies. Default is "false". + * @see com.caucho.hessian.client.HessianProxyFactory#setHessian2Reply + */ + public void setHessian2Reply(boolean hessian2) { + this.proxyFactory.setHessian2Reply(hessian2); + } + + + public void afterPropertiesSet() { + super.afterPropertiesSet(); + prepare(); + } + + /** + * Initialize the Hessian proxy for this interceptor. + * @throws RemoteLookupFailureException if the service URL is invalid + */ + public void prepare() throws RemoteLookupFailureException { + try { + this.hessianProxy = createHessianProxy(this.proxyFactory); + } + catch (MalformedURLException ex) { + throw new RemoteLookupFailureException("Service URL [" + getServiceUrl() + "] is invalid", ex); + } + } + + /** + * Create the Hessian proxy that is wrapped by this interceptor. + * @param proxyFactory the proxy factory to use + * @return the Hessian proxy + * @throws MalformedURLException if thrown by the proxy factory + * @see com.caucho.hessian.client.HessianProxyFactory#create + */ + protected Object createHessianProxy(HessianProxyFactory proxyFactory) throws MalformedURLException { + Assert.notNull(getServiceInterface(), "'serviceInterface' is required"); + return proxyFactory.create(getServiceInterface(), getServiceUrl()); + } + + + public Object invoke(MethodInvocation invocation) throws Throwable { + if (this.hessianProxy == null) { + throw new IllegalStateException("HessianClientInterceptor is not properly initialized - " + + "invoke 'prepare' before attempting any operations"); + } + + ClassLoader originalClassLoader = overrideThreadContextClassLoader(); + try { + return invocation.getMethod().invoke(this.hessianProxy, invocation.getArguments()); + } + catch (InvocationTargetException ex) { + if (ex.getTargetException() instanceof HessianRuntimeException) { + HessianRuntimeException hre = (HessianRuntimeException) ex.getTargetException(); + Throwable rootCause = (hre.getRootCause() != null ? hre.getRootCause() : hre); + throw convertHessianAccessException(rootCause); + } + else if (ex.getTargetException() instanceof UndeclaredThrowableException) { + UndeclaredThrowableException utex = (UndeclaredThrowableException) ex.getTargetException(); + throw convertHessianAccessException(utex.getUndeclaredThrowable()); + } + throw ex.getTargetException(); + } + catch (Throwable ex) { + throw new RemoteProxyFailureException( + "Failed to invoke Hessian proxy for remote service [" + getServiceUrl() + "]", ex); + } + finally { + resetThreadContextClassLoader(originalClassLoader); + } + } + + /** + * Convert the given Hessian access exception to an appropriate + * Spring RemoteAccessException. + * @param ex the exception to convert + * @return the RemoteAccessException to throw + */ + protected RemoteAccessException convertHessianAccessException(Throwable ex) { + if (ex instanceof ConnectException) { + return new RemoteConnectFailureException( + "Cannot connect to Hessian remote service at [" + getServiceUrl() + "]", ex); + } + else { + return new RemoteAccessException( + "Cannot access Hessian remote service at [" + getServiceUrl() + "]", ex); + } + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/remoting/caucho/HessianExporter.java b/org.springframework.web/src/main/java/org/springframework/remoting/caucho/HessianExporter.java new file mode 100644 index 0000000000..0608a5fe89 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/remoting/caucho/HessianExporter.java @@ -0,0 +1,149 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.remoting.caucho; + +import java.io.InputStream; +import java.io.OutputStream; +import java.lang.reflect.Constructor; + +import com.caucho.hessian.io.SerializerFactory; +import com.caucho.hessian.server.HessianSkeleton; +import org.apache.commons.logging.Log; + +import org.springframework.beans.factory.BeanInitializationException; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.remoting.support.RemoteExporter; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; + +/** + * General stream-based protocol exporter for a Hessian endpoint. + * + *

Hessian is a slim, binary RPC protocol. + * For information on Hessian, see the + * Hessian website. + * + *

This exporter will work with both Hessian 2.x and 3.x (respectively + * Resin 2.x and 3.x), autodetecting the corresponding skeleton class. + * As of Spring 2.0, it is also compatible with the new Hessian 2 protocol + * (a.k.a. Hessian 3.0.20+), while remaining compatible with older versions. + * + * @author Juergen Hoeller + * @since 2.5.1 + * @see #invoke(java.io.InputStream, java.io.OutputStream) + * @see HessianServiceExporter + * @see SimpleHessianServiceExporter + */ +public class HessianExporter extends RemoteExporter implements InitializingBean { + + private static final boolean hessian2Available = + ClassUtils.isPresent("com.caucho.hessian.io.Hessian2Input", HessianServiceExporter.class.getClassLoader()); + + + private SerializerFactory serializerFactory = new SerializerFactory(); + + private Log debugLogger; + + private HessianSkeletonInvoker skeletonInvoker; + + + /** + * Specify the Hessian SerializerFactory to use. + *

This will typically be passed in as an inner bean definition + * of type com.caucho.hessian.io.SerializerFactory, + * with custom bean property values applied. + */ + public void setSerializerFactory(SerializerFactory serializerFactory) { + this.serializerFactory = (serializerFactory != null ? serializerFactory : new SerializerFactory()); + } + + /** + * Set whether to send the Java collection type for each serialized + * collection. Default is "true". + */ + public void setSendCollectionType(boolean sendCollectionType) { + this.serializerFactory.setSendCollectionType(sendCollectionType); + } + + /** + * Set whether Hessian's debug mode should be enabled, logging to + * this exporter's Commons Logging log. Default is "false". + * @see com.caucho.hessian.client.HessianProxyFactory#setDebug + */ + public void setDebug(boolean debug) { + this.debugLogger = (debug ? logger : null); + } + + + public void afterPropertiesSet() { + prepare(); + } + + /** + * Initialize this exporter. + */ + public void prepare() { + HessianSkeleton skeleton = null; + + try { + try { + // Try Hessian 3.x (with service interface argument). + Constructor ctor = HessianSkeleton.class.getConstructor(new Class[] {Object.class, Class.class}); + checkService(); + checkServiceInterface(); + skeleton = (HessianSkeleton) + ctor.newInstance(new Object[] {getProxyForService(), getServiceInterface()}); + } + catch (NoSuchMethodException ex) { + // Fall back to Hessian 2.x (without service interface argument). + Constructor ctor = HessianSkeleton.class.getConstructor(new Class[] {Object.class}); + skeleton = (HessianSkeleton) ctor.newInstance(new Object[] {getProxyForService()}); + } + } + catch (Throwable ex) { + throw new BeanInitializationException("Hessian skeleton initialization failed", ex); + } + + if (hessian2Available) { + // Hessian 2 (version 3.0.20+). + this.skeletonInvoker = new Hessian2SkeletonInvoker(skeleton, this.serializerFactory, this.debugLogger); + } + else { + // Hessian 1 (version 3.0.19-). + this.skeletonInvoker = new Hessian1SkeletonInvoker(skeleton, this.serializerFactory); + } + } + + + /** + * Perform an invocation on the exported object. + * @param inputStream the request stream + * @param outputStream the response stream + * @throws Throwable if invocation failed + */ + public void invoke(InputStream inputStream, OutputStream outputStream) throws Throwable { + Assert.notNull(this.skeletonInvoker, "Hessian exporter has not been initialized"); + ClassLoader originalClassLoader = overrideThreadContextClassLoader(); + try { + this.skeletonInvoker.invoke(inputStream, outputStream); + } + finally { + resetThreadContextClassLoader(originalClassLoader); + } + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/remoting/caucho/HessianProxyFactoryBean.java b/org.springframework.web/src/main/java/org/springframework/remoting/caucho/HessianProxyFactoryBean.java new file mode 100644 index 0000000000..58b5a6cb04 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/remoting/caucho/HessianProxyFactoryBean.java @@ -0,0 +1,66 @@ +/* + * Copyright 2002-2008 the original author or authors. + * + * 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.springframework.remoting.caucho; + +import org.springframework.aop.framework.ProxyFactory; +import org.springframework.beans.factory.FactoryBean; + +/** + * FactoryBean for Hessian proxies. Exposes the proxied service for + * use as a bean reference, using the specified service interface. + * + *

Hessian is a slim, binary RPC protocol. + * For information on Hessian, see the + * Hessian website + * + *

The service URL must be an HTTP URL exposing a Hessian service. + * For details, see the {@link HessianClientInterceptor} javadoc. + * + * @author Juergen Hoeller + * @since 13.05.2003 + * @see #setServiceInterface + * @see #setServiceUrl + * @see HessianClientInterceptor + * @see HessianServiceExporter + * @see org.springframework.remoting.caucho.BurlapProxyFactoryBean + * @see org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean + * @see org.springframework.remoting.rmi.RmiProxyFactoryBean + */ +public class HessianProxyFactoryBean extends HessianClientInterceptor implements FactoryBean { + + private Object serviceProxy; + + + public void afterPropertiesSet() { + super.afterPropertiesSet(); + this.serviceProxy = new ProxyFactory(getServiceInterface(), this).getProxy(getBeanClassLoader()); + } + + + public Object getObject() { + return this.serviceProxy; + } + + public Class getObjectType() { + return getServiceInterface(); + } + + public boolean isSingleton() { + return true; + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/remoting/caucho/HessianServiceExporter.java b/org.springframework.web/src/main/java/org/springframework/remoting/caucho/HessianServiceExporter.java new file mode 100644 index 0000000000..751fbc9a73 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/remoting/caucho/HessianServiceExporter.java @@ -0,0 +1,77 @@ +/* + * Copyright 2002-2008 the original author or authors. + * + * 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.springframework.remoting.caucho; + +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.web.HttpRequestHandler; +import org.springframework.web.HttpRequestMethodNotSupportedException; +import org.springframework.web.util.NestedServletException; + +/** + * Servlet-API-based HTTP request handler that exports the specified service bean + * as Hessian service endpoint, accessible via a Hessian proxy. + * + *

Note: Spring also provides an alternative version of this exporter, + * for Sun's JRE 1.6 HTTP server: {@link SimpleHessianServiceExporter}. + * + *

Hessian is a slim, binary RPC protocol. + * For information on Hessian, see the + * Hessian website. + * + *

This exporter will work with both Hessian 2.x and 3.x (respectively + * Resin 2.x and 3.x), autodetecting the corresponding skeleton class. + * As of Spring 2.0, it is also compatible with the new Hessian 2 protocol + * (a.k.a. Hessian 3.0.20+), while remaining compatible with older versions. + * + *

Note: Hessian services exported with this class can be accessed by + * any Hessian client, as there isn't any special handling involved. + * + * @author Juergen Hoeller + * @since 13.05.2003 + * @see HessianClientInterceptor + * @see HessianProxyFactoryBean + * @see org.springframework.remoting.caucho.BurlapServiceExporter + * @see org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter + * @see org.springframework.remoting.rmi.RmiServiceExporter + */ +public class HessianServiceExporter extends HessianExporter implements HttpRequestHandler { + + /** + * Processes the incoming Hessian request and creates a Hessian response. + */ + public void handleRequest(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + + if (!"POST".equals(request.getMethod())) { + throw new HttpRequestMethodNotSupportedException(request.getMethod(), + new String[] {"POST"}, "HessianServiceExporter only supports POST requests"); + } + + try { + invoke(request.getInputStream(), response.getOutputStream()); + } + catch (Throwable ex) { + throw new NestedServletException("Hessian skeleton invocation failed", ex); + } + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/remoting/caucho/HessianSkeletonInvoker.java b/org.springframework.web/src/main/java/org/springframework/remoting/caucho/HessianSkeletonInvoker.java new file mode 100644 index 0000000000..86d9bf98a3 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/remoting/caucho/HessianSkeletonInvoker.java @@ -0,0 +1,67 @@ +/* + * Copyright 2002-2006 the original author or authors. + * + * 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.springframework.remoting.caucho; + +import java.io.InputStream; +import java.io.OutputStream; + +import com.caucho.hessian.server.HessianSkeleton; +import com.caucho.hessian.io.SerializerFactory; + +import org.springframework.util.Assert; + +/** + * Internal invoker strategy for a Hessian skeleton. + * Allows for common handling of Hessian protocol version 1 and 2. + * + * @author Juergen Hoeller + * @since 2.0 + */ +abstract class HessianSkeletonInvoker { + + /** + * Wrapped HessianSkeleton, available to subclasses. + */ + protected final HessianSkeleton skeleton; + + /** + * Hessian SerializerFactory (if any), available to subclasses. + */ + protected final SerializerFactory serializerFactory; + + + /** + * Create a new HessianSkeletonInvoker for the given skeleton. + * @param skeleton the HessianSkeleton to wrap + * @param serializerFactory the Hessian SerializerFactory to use, if any + */ + public HessianSkeletonInvoker(HessianSkeleton skeleton, SerializerFactory serializerFactory) { + Assert.notNull(skeleton, "HessianSkeleton must not be null"); + this.skeleton = skeleton; + this.serializerFactory = serializerFactory; + } + + + /** + * Invoke the given skeleton based on the given input/output streams. + * @param inputStream the stream containing the Hessian input + * @param outputStream the stream to receive the Hessian output + * @throws Throwable if the skeleton invocation failed + */ + public abstract void invoke(InputStream inputStream, OutputStream outputStream) throws Throwable; + +} diff --git a/org.springframework.web/src/main/java/org/springframework/remoting/caucho/SimpleBurlapServiceExporter.java b/org.springframework.web/src/main/java/org/springframework/remoting/caucho/SimpleBurlapServiceExporter.java new file mode 100644 index 0000000000..e3776bb294 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/remoting/caucho/SimpleBurlapServiceExporter.java @@ -0,0 +1,73 @@ +/* + * Copyright 2002-2008 the original author or authors. + * + * 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.springframework.remoting.caucho; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; + +import org.springframework.util.FileCopyUtils; + +/** + * HTTP request handler that exports the specified service bean as + * Burlap service endpoint, accessible via a Burlap proxy. + * Designed for Sun's JRE 1.6 HTTP server, implementing the + * {@link com.sun.net.httpserver.HttpHandler} interface. + * + *

Burlap is a slim, XML-based RPC protocol. + * For information on Burlap, see the + * Burlap website. + * This exporter requires Burlap 3.x. + * + *

Note: Burlap services exported with this class can be accessed by + * any Burlap client, as there isn't any special handling involved. + * + * @author Juergen Hoeller + * @since 2.5.1 + * @see org.springframework.remoting.caucho.BurlapClientInterceptor + * @see org.springframework.remoting.caucho.BurlapProxyFactoryBean + * @see SimpleHessianServiceExporter + * @see org.springframework.remoting.httpinvoker.SimpleHttpInvokerServiceExporter + */ +public class SimpleBurlapServiceExporter extends BurlapExporter implements HttpHandler { + + /** + * Processes the incoming Burlap request and creates a Burlap response. + */ + public void handle(HttpExchange exchange) throws IOException { + if (!"POST".equals(exchange.getRequestMethod())) { + exchange.getResponseHeaders().set("Allow", "POST"); + exchange.sendResponseHeaders(405, -1); + return; + } + + ByteArrayOutputStream output = new ByteArrayOutputStream(1024); + try { + invoke(exchange.getRequestBody(), output); + } + catch (Throwable ex) { + exchange.sendResponseHeaders(500, -1); + throw new IOException("Burlap skeleton invocation failed", ex); + } + + exchange.sendResponseHeaders(200, output.size()); + FileCopyUtils.copy(output.toByteArray(), exchange.getResponseBody()); + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/remoting/caucho/SimpleHessianServiceExporter.java b/org.springframework.web/src/main/java/org/springframework/remoting/caucho/SimpleHessianServiceExporter.java new file mode 100644 index 0000000000..a9dfb8d772 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/remoting/caucho/SimpleHessianServiceExporter.java @@ -0,0 +1,73 @@ +/* + * Copyright 2002-2008 the original author or authors. + * + * 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.springframework.remoting.caucho; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; + +import org.springframework.util.FileCopyUtils; + +/** + * HTTP request handler that exports the specified service bean as + * Hessian service endpoint, accessible via a Hessian proxy. + * Designed for Sun's JRE 1.6 HTTP server, implementing the + * {@link com.sun.net.httpserver.HttpHandler} interface. + * + *

Hessian is a slim, binary RPC protocol. + * For information on Hessian, see the + * Hessian website. + * This exporter requires Hessian 3.0.20 or above. + * + *

Note: Hessian services exported with this class can be accessed by + * any Hessian client, as there isn't any special handling involved. + * + * @author Juergen Hoeller + * @since 2.5.1 + * @see org.springframework.remoting.caucho.HessianClientInterceptor + * @see org.springframework.remoting.caucho.HessianProxyFactoryBean + * @see SimpleBurlapServiceExporter + * @see org.springframework.remoting.httpinvoker.SimpleHttpInvokerServiceExporter + */ +public class SimpleHessianServiceExporter extends HessianExporter implements HttpHandler { + + /** + * Processes the incoming Hessian request and creates a Hessian response. + */ + public void handle(HttpExchange exchange) throws IOException { + if (!"POST".equals(exchange.getRequestMethod())) { + exchange.getResponseHeaders().set("Allow", "POST"); + exchange.sendResponseHeaders(405, -1); + return; + } + + ByteArrayOutputStream output = new ByteArrayOutputStream(1024); + try { + invoke(exchange.getRequestBody(), output); + } + catch (Throwable ex) { + exchange.sendResponseHeaders(500, -1); + throw new IOException("Hessian skeleton invocation failed", ex); + } + + exchange.sendResponseHeaders(200, output.size()); + FileCopyUtils.copy(output.toByteArray(), exchange.getResponseBody()); + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/remoting/caucho/package.html b/org.springframework.web/src/main/java/org/springframework/remoting/caucho/package.html new file mode 100644 index 0000000000..0f023b27be --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/remoting/caucho/package.html @@ -0,0 +1,17 @@ + + + +This package provides remoting classes for Caucho's Hessian and Burlap +protocols: a proxy factory for accessing Hessian/Burlap services, +and an exporter for making beans available to Hessian/Burlap clients. + +

Hessian is a slim, binary RPC protocol over HTTP. +For information on Hessian, see the +Hessian website + +

Burlap is a slim, XML-based RPC protocol over HTTP. +For information on Burlap, see the +Burlap website + + + diff --git a/org.springframework.web/src/main/java/org/springframework/remoting/httpinvoker/AbstractHttpInvokerRequestExecutor.java b/org.springframework.web/src/main/java/org/springframework/remoting/httpinvoker/AbstractHttpInvokerRequestExecutor.java new file mode 100644 index 0000000000..af6f42d54a --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/remoting/httpinvoker/AbstractHttpInvokerRequestExecutor.java @@ -0,0 +1,299 @@ +/* + * Copyright 2002-2008 the original author or authors. + * + * 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.springframework.remoting.httpinvoker; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.OutputStream; +import java.rmi.RemoteException; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.beans.factory.BeanClassLoaderAware; +import org.springframework.remoting.rmi.CodebaseAwareObjectInputStream; +import org.springframework.remoting.support.RemoteInvocation; +import org.springframework.remoting.support.RemoteInvocationResult; +import org.springframework.util.Assert; + +/** + * Abstract base implementation of the HttpInvokerRequestExecutor interface. + * + *

Pre-implements serialization of RemoteInvocation objects and + * deserialization of RemoteInvocationResults objects. + * + * @author Juergen Hoeller + * @since 1.1 + * @see #doExecuteRequest + */ +public abstract class AbstractHttpInvokerRequestExecutor + implements HttpInvokerRequestExecutor, BeanClassLoaderAware { + + /** + * Default content type: "application/x-java-serialized-object" + */ + public static final String CONTENT_TYPE_SERIALIZED_OBJECT = "application/x-java-serialized-object"; + + + protected static final String HTTP_METHOD_POST = "POST"; + + protected static final String HTTP_HEADER_ACCEPT_LANGUAGE = "Accept-Language"; + + protected static final String HTTP_HEADER_ACCEPT_ENCODING = "Accept-Encoding"; + + protected static final String HTTP_HEADER_CONTENT_ENCODING = "Content-Encoding"; + + protected static final String HTTP_HEADER_CONTENT_TYPE = "Content-Type"; + + protected static final String HTTP_HEADER_CONTENT_LENGTH = "Content-Length"; + + protected static final String ENCODING_GZIP = "gzip"; + + + private static final int SERIALIZED_INVOCATION_BYTE_ARRAY_INITIAL_SIZE = 1024; + + + protected final Log logger = LogFactory.getLog(getClass()); + + private String contentType = CONTENT_TYPE_SERIALIZED_OBJECT; + + private boolean acceptGzipEncoding = true; + + private ClassLoader beanClassLoader; + + + /** + * Specify the content type to use for sending HTTP invoker requests. + *

Default is "application/x-java-serialized-object". + */ + public void setContentType(String contentType) { + Assert.notNull(contentType, "'contentType' must not be null"); + this.contentType = contentType; + } + + /** + * Return the content type to use for sending HTTP invoker requests. + */ + public String getContentType() { + return this.contentType; + } + + /** + * Set whether to accept GZIP encoding, that is, whether to + * send the HTTP "Accept-Encoding" header with "gzip" as value. + *

Default is "true". Turn this flag off if you do not want + * GZIP response compression even if enabled on the HTTP server. + */ + public void setAcceptGzipEncoding(boolean acceptGzipEncoding) { + this.acceptGzipEncoding = acceptGzipEncoding; + } + + /** + * Return whether to accept GZIP encoding, that is, whether to + * send the HTTP "Accept-Encoding" header with "gzip" as value. + */ + public boolean isAcceptGzipEncoding() { + return this.acceptGzipEncoding; + } + + public void setBeanClassLoader(ClassLoader classLoader) { + this.beanClassLoader = classLoader; + } + + /** + * Return the bean ClassLoader that this executor is supposed to use. + */ + protected ClassLoader getBeanClassLoader() { + return this.beanClassLoader; + } + + + public final RemoteInvocationResult executeRequest( + HttpInvokerClientConfiguration config, RemoteInvocation invocation) throws Exception { + + ByteArrayOutputStream baos = getByteArrayOutputStream(invocation); + if (logger.isDebugEnabled()) { + logger.debug("Sending HTTP invoker request for service at [" + config.getServiceUrl() + + "], with size " + baos.size()); + } + return doExecuteRequest(config, baos); + } + + /** + * Serialize the given RemoteInvocation into a ByteArrayOutputStream. + * @param invocation the RemoteInvocation object + * @return a ByteArrayOutputStream with the serialized RemoteInvocation + * @throws IOException if thrown by I/O methods + */ + protected ByteArrayOutputStream getByteArrayOutputStream(RemoteInvocation invocation) throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(SERIALIZED_INVOCATION_BYTE_ARRAY_INITIAL_SIZE); + writeRemoteInvocation(invocation, baos); + return baos; + } + + /** + * Serialize the given RemoteInvocation to the given OutputStream. + *

The default implementation gives decorateOutputStream a chance + * to decorate the stream first (for example, for custom encryption or compression). + * Creates an ObjectOutputStream for the final stream and calls + * doWriteRemoteInvocation to actually write the object. + *

Can be overridden for custom serialization of the invocation. + * @param invocation the RemoteInvocation object + * @param os the OutputStream to write to + * @throws IOException if thrown by I/O methods + * @see #decorateOutputStream + * @see #doWriteRemoteInvocation + */ + protected void writeRemoteInvocation(RemoteInvocation invocation, OutputStream os) throws IOException { + ObjectOutputStream oos = new ObjectOutputStream(decorateOutputStream(os)); + try { + doWriteRemoteInvocation(invocation, oos); + oos.flush(); + } + finally { + oos.close(); + } + } + + /** + * Return the OutputStream to use for writing remote invocations, + * potentially decorating the given original OutputStream. + *

The default implementation returns the given stream as-is. + * Can be overridden, for example, for custom encryption or compression. + * @param os the original OutputStream + * @return the potentially decorated OutputStream + */ + protected OutputStream decorateOutputStream(OutputStream os) throws IOException { + return os; + } + + /** + * Perform the actual writing of the given invocation object to the + * given ObjectOutputStream. + *

The default implementation simply calls writeObject. + * Can be overridden for serialization of a custom wrapper object rather + * than the plain invocation, for example an encryption-aware holder. + * @param invocation the RemoteInvocation object + * @param oos the ObjectOutputStream to write to + * @throws IOException if thrown by I/O methods + * @see java.io.ObjectOutputStream#writeObject + */ + protected void doWriteRemoteInvocation(RemoteInvocation invocation, ObjectOutputStream oos) throws IOException { + oos.writeObject(invocation); + } + + + /** + * Execute a request to send the given serialized remote invocation. + *

Implementations will usually call readRemoteInvocationResult + * to deserialize a returned RemoteInvocationResult object. + * @param config the HTTP invoker configuration that specifies the + * target service + * @param baos the ByteArrayOutputStream that contains the serialized + * RemoteInvocation object + * @return the RemoteInvocationResult object + * @throws IOException if thrown by I/O operations + * @throws ClassNotFoundException if thrown during deserialization + * @throws Exception in case of general errors + * @see #readRemoteInvocationResult(java.io.InputStream, String) + */ + protected abstract RemoteInvocationResult doExecuteRequest( + HttpInvokerClientConfiguration config, ByteArrayOutputStream baos) + throws Exception; + + /** + * Deserialize a RemoteInvocationResult object from the given InputStream. + *

Gives decorateInputStream a chance to decorate the stream + * first (for example, for custom encryption or compression). Creates an + * ObjectInputStream via createObjectInputStream and + * calls doReadRemoteInvocationResult to actually read the object. + *

Can be overridden for custom serialization of the invocation. + * @param is the InputStream to read from + * @param codebaseUrl the codebase URL to load classes from if not found locally + * @return the RemoteInvocationResult object + * @throws IOException if thrown by I/O methods + * @throws ClassNotFoundException if thrown during deserialization + * @see #decorateInputStream + * @see #createObjectInputStream + * @see #doReadRemoteInvocationResult + */ + protected RemoteInvocationResult readRemoteInvocationResult(InputStream is, String codebaseUrl) + throws IOException, ClassNotFoundException { + + ObjectInputStream ois = createObjectInputStream(decorateInputStream(is), codebaseUrl); + try { + return doReadRemoteInvocationResult(ois); + } + finally { + ois.close(); + } + } + + /** + * Return the InputStream to use for reading remote invocation results, + * potentially decorating the given original InputStream. + *

The default implementation returns the given stream as-is. + * Can be overridden, for example, for custom encryption or compression. + * @param is the original InputStream + * @return the potentially decorated InputStream + */ + protected InputStream decorateInputStream(InputStream is) throws IOException { + return is; + } + + /** + * Create an ObjectInputStream for the given InputStream and codebase. + * The default implementation creates a CodebaseAwareObjectInputStream. + * @param is the InputStream to read from + * @param codebaseUrl the codebase URL to load classes from if not found locally + * (can be null) + * @return the new ObjectInputStream instance to use + * @throws IOException if creation of the ObjectInputStream failed + * @see org.springframework.remoting.rmi.CodebaseAwareObjectInputStream + */ + protected ObjectInputStream createObjectInputStream(InputStream is, String codebaseUrl) throws IOException { + return new CodebaseAwareObjectInputStream(is, getBeanClassLoader(), codebaseUrl); + } + + /** + * Perform the actual reading of an invocation object from the + * given ObjectInputStream. + *

The default implementation simply calls readObject. + * Can be overridden for deserialization of a custom wrapper object rather + * than the plain invocation, for example an encryption-aware holder. + * @param ois the ObjectInputStream to read from + * @return the RemoteInvocationResult object + * @throws IOException if thrown by I/O methods + * @throws ClassNotFoundException if the class name of a serialized object + * couldn't get resolved + * @see java.io.ObjectOutputStream#writeObject + */ + protected RemoteInvocationResult doReadRemoteInvocationResult(ObjectInputStream ois) + throws IOException, ClassNotFoundException { + + Object obj = ois.readObject(); + if (!(obj instanceof RemoteInvocationResult)) { + throw new RemoteException("Deserialized object needs to be assignable to type [" + + RemoteInvocationResult.class.getName() + "]: " + obj); + } + return (RemoteInvocationResult) obj; + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/remoting/httpinvoker/CommonsHttpInvokerRequestExecutor.java b/org.springframework.web/src/main/java/org/springframework/remoting/httpinvoker/CommonsHttpInvokerRequestExecutor.java new file mode 100644 index 0000000000..1c8a32031d --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/remoting/httpinvoker/CommonsHttpInvokerRequestExecutor.java @@ -0,0 +1,258 @@ +/* + * Copyright 2002-2008 the original author or authors. + * + * 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.springframework.remoting.httpinvoker; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.zip.GZIPInputStream; + +import org.apache.commons.httpclient.Header; +import org.apache.commons.httpclient.HttpClient; +import org.apache.commons.httpclient.HttpException; +import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager; +import org.apache.commons.httpclient.methods.ByteArrayRequestEntity; +import org.apache.commons.httpclient.methods.PostMethod; + +import org.springframework.context.i18n.LocaleContext; +import org.springframework.context.i18n.LocaleContextHolder; +import org.springframework.remoting.support.RemoteInvocationResult; +import org.springframework.util.StringUtils; + +/** + * {@link HttpInvokerRequestExecutor} implementation that uses + * Jakarta Commons HttpClient + * to execute POST requests. Requires Commons HttpClient 3.0 or higher. + * + *

Allows to use a pre-configured {@link org.apache.commons.httpclient.HttpClient} + * instance, potentially with authentication, HTTP connection pooling, etc. + * Also designed for easy subclassing, providing specific template methods. + * + * @author Juergen Hoeller + * @author Mark Fisher + * @since 1.1 + * @see SimpleHttpInvokerRequestExecutor + */ +public class CommonsHttpInvokerRequestExecutor extends AbstractHttpInvokerRequestExecutor { + + /** + * Default timeout value if no HttpClient is explicitly provided. + */ + private static final int DEFAULT_READ_TIMEOUT_MILLISECONDS = (60 * 1000); + + private HttpClient httpClient; + + + /** + * Create a new CommonsHttpInvokerRequestExecutor with a default + * HttpClient that uses a default MultiThreadedHttpConnectionManager. + * Sets the socket read timeout to {@link #DEFAULT_READ_TIMEOUT_MILLISECONDS}. + * @see org.apache.commons.httpclient.HttpClient + * @see org.apache.commons.httpclient.MultiThreadedHttpConnectionManager + */ + public CommonsHttpInvokerRequestExecutor() { + this.httpClient = new HttpClient(new MultiThreadedHttpConnectionManager()); + this.setReadTimeout(DEFAULT_READ_TIMEOUT_MILLISECONDS); + } + + /** + * Create a new CommonsHttpInvokerRequestExecutor with the given + * HttpClient instance. The socket read timeout of the provided + * HttpClient will not be changed. + * @param httpClient the HttpClient instance to use for this request executor + */ + public CommonsHttpInvokerRequestExecutor(HttpClient httpClient) { + this.httpClient = httpClient; + } + + + /** + * Set the HttpClient instance to use for this request executor. + */ + public void setHttpClient(HttpClient httpClient) { + this.httpClient = httpClient; + } + + /** + * Return the HttpClient instance that this request executor uses. + */ + public HttpClient getHttpClient() { + return this.httpClient; + } + + /** + * Set the socket read timeout for the underlying HttpClient. A value + * of 0 means never timeout. + * @param timeout the timeout value in milliseconds + * @see org.apache.commons.httpclient.params.HttpConnectionManagerParams#setSoTimeout(int) + * @see #DEFAULT_READ_TIMEOUT_MILLISECONDS + */ + public void setReadTimeout(int timeout) { + if (timeout < 0) { + throw new IllegalArgumentException("timeout must be a non-negative value"); + } + this.httpClient.getHttpConnectionManager().getParams().setSoTimeout(timeout); + } + + + /** + * Execute the given request through Commons HttpClient. + *

This method implements the basic processing workflow: + * The actual work happens in this class's template methods. + * @see #createPostMethod + * @see #setRequestBody + * @see #executePostMethod + * @see #validateResponse + * @see #getResponseBody + */ + protected RemoteInvocationResult doExecuteRequest( + HttpInvokerClientConfiguration config, ByteArrayOutputStream baos) + throws IOException, ClassNotFoundException { + + PostMethod postMethod = createPostMethod(config); + try { + setRequestBody(config, postMethod, baos); + executePostMethod(config, getHttpClient(), postMethod); + validateResponse(config, postMethod); + InputStream responseBody = getResponseBody(config, postMethod); + return readRemoteInvocationResult(responseBody, config.getCodebaseUrl()); + } + finally { + // Need to explicitly release because it might be pooled. + postMethod.releaseConnection(); + } + } + + /** + * Create a PostMethod for the given configuration. + *

The default implementation creates a standard PostMethod with + * "application/x-java-serialized-object" as "Content-Type" header. + * @param config the HTTP invoker configuration that specifies the + * target service + * @return the PostMethod instance + * @throws IOException if thrown by I/O methods + */ + protected PostMethod createPostMethod(HttpInvokerClientConfiguration config) throws IOException { + PostMethod postMethod = new PostMethod(config.getServiceUrl()); + LocaleContext locale = LocaleContextHolder.getLocaleContext(); + if (locale != null) { + postMethod.addRequestHeader(HTTP_HEADER_ACCEPT_LANGUAGE, StringUtils.toLanguageTag(locale.getLocale())); + } + if (isAcceptGzipEncoding()) { + postMethod.addRequestHeader(HTTP_HEADER_ACCEPT_ENCODING, ENCODING_GZIP); + } + return postMethod; + } + + /** + * Set the given serialized remote invocation as request body. + *

The default implementation simply sets the serialized invocation + * as the PostMethod's request body. This can be overridden, for example, + * to write a specific encoding and potentially set appropriate HTTP + * request headers. + * @param config the HTTP invoker configuration that specifies the target service + * @param postMethod the PostMethod to set the request body on + * @param baos the ByteArrayOutputStream that contains the serialized + * RemoteInvocation object + * @throws IOException if thrown by I/O methods + * @see org.apache.commons.httpclient.methods.PostMethod#setRequestBody(java.io.InputStream) + * @see org.apache.commons.httpclient.methods.PostMethod#setRequestEntity + * @see org.apache.commons.httpclient.methods.InputStreamRequestEntity + */ + protected void setRequestBody( + HttpInvokerClientConfiguration config, PostMethod postMethod, ByteArrayOutputStream baos) + throws IOException { + + postMethod.setRequestEntity(new ByteArrayRequestEntity(baos.toByteArray(), getContentType())); + } + + /** + * Execute the given PostMethod instance. + * @param config the HTTP invoker configuration that specifies the target service + * @param httpClient the HttpClient to execute on + * @param postMethod the PostMethod to execute + * @throws IOException if thrown by I/O methods + * @see org.apache.commons.httpclient.HttpClient#executeMethod(org.apache.commons.httpclient.HttpMethod) + */ + protected void executePostMethod( + HttpInvokerClientConfiguration config, HttpClient httpClient, PostMethod postMethod) + throws IOException { + + httpClient.executeMethod(postMethod); + } + + /** + * Validate the given response as contained in the PostMethod object, + * throwing an exception if it does not correspond to a successful HTTP response. + *

Default implementation rejects any HTTP status code beyond 2xx, to avoid + * parsing the response body and trying to deserialize from a corrupted stream. + * @param config the HTTP invoker configuration that specifies the target service + * @param postMethod the executed PostMethod to validate + * @throws IOException if validation failed + * @see org.apache.commons.httpclient.methods.PostMethod#getStatusCode() + * @see org.apache.commons.httpclient.HttpException + */ + protected void validateResponse(HttpInvokerClientConfiguration config, PostMethod postMethod) + throws IOException { + + if (postMethod.getStatusCode() >= 300) { + throw new HttpException( + "Did not receive successful HTTP response: status code = " + postMethod.getStatusCode() + + ", status message = [" + postMethod.getStatusText() + "]"); + } + } + + /** + * Extract the response body from the given executed remote invocation + * request. + *

The default implementation simply fetches the PostMethod's response + * body stream. If the response is recognized as GZIP response, the + * InputStream will get wrapped in a GZIPInputStream. + * @param config the HTTP invoker configuration that specifies the target service + * @param postMethod the PostMethod to read the response body from + * @return an InputStream for the response body + * @throws IOException if thrown by I/O methods + * @see #isGzipResponse + * @see java.util.zip.GZIPInputStream + * @see org.apache.commons.httpclient.methods.PostMethod#getResponseBodyAsStream() + * @see org.apache.commons.httpclient.methods.PostMethod#getResponseHeader(String) + */ + protected InputStream getResponseBody(HttpInvokerClientConfiguration config, PostMethod postMethod) + throws IOException { + + if (isGzipResponse(postMethod)) { + return new GZIPInputStream(postMethod.getResponseBodyAsStream()); + } + else { + return postMethod.getResponseBodyAsStream(); + } + } + + /** + * Determine whether the given response indicates a GZIP response. + *

Default implementation checks whether the HTTP "Content-Encoding" + * header contains "gzip" (in any casing). + * @param postMethod the PostMethod to check + * @return whether the given response indicates a GZIP response + */ + protected boolean isGzipResponse(PostMethod postMethod) { + Header encodingHeader = postMethod.getResponseHeader(HTTP_HEADER_CONTENT_ENCODING); + return (encodingHeader != null && encodingHeader.getValue() != null && + encodingHeader.getValue().toLowerCase().indexOf(ENCODING_GZIP) != -1); + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/remoting/httpinvoker/HttpInvokerClientConfiguration.java b/org.springframework.web/src/main/java/org/springframework/remoting/httpinvoker/HttpInvokerClientConfiguration.java new file mode 100644 index 0000000000..db048e24e3 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/remoting/httpinvoker/HttpInvokerClientConfiguration.java @@ -0,0 +1,42 @@ +/* + * Copyright 2002-2005 the original author or authors. + * + * 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.springframework.remoting.httpinvoker; + +/** + * Configuration interface for executing HTTP invoker requests. + * + * @author Juergen Hoeller + * @since 1.1 + * @see HttpInvokerRequestExecutor + * @see HttpInvokerClientInterceptor + */ +public interface HttpInvokerClientConfiguration { + + /** + * Return the HTTP URL of the target service. + */ + String getServiceUrl(); + + /** + * Return the codebase URL to download classes from if not found locally. + * Can consist of multiple URLs, separated by spaces. + * @return the codebase URL, or null if none + * @see java.rmi.server.RMIClassLoader + */ + String getCodebaseUrl(); + +} diff --git a/org.springframework.web/src/main/java/org/springframework/remoting/httpinvoker/HttpInvokerClientInterceptor.java b/org.springframework.web/src/main/java/org/springframework/remoting/httpinvoker/HttpInvokerClientInterceptor.java new file mode 100644 index 0000000000..c99028dc85 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/remoting/httpinvoker/HttpInvokerClientInterceptor.java @@ -0,0 +1,216 @@ +/* + * Copyright 2002-2008 the original author or authors. + * + * 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.springframework.remoting.httpinvoker; + +import java.io.IOException; +import java.io.InvalidClassException; +import java.net.ConnectException; + +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; + +import org.springframework.aop.support.AopUtils; +import org.springframework.remoting.RemoteAccessException; +import org.springframework.remoting.RemoteConnectFailureException; +import org.springframework.remoting.RemoteInvocationFailureException; +import org.springframework.remoting.support.RemoteInvocation; +import org.springframework.remoting.support.RemoteInvocationBasedAccessor; +import org.springframework.remoting.support.RemoteInvocationResult; + +/** + * {@link org.aopalliance.intercept.MethodInterceptor} for accessing an + * HTTP invoker service. The service URL must be an HTTP URL exposing + * an HTTP invoker service. + * + *

Serializes remote invocation objects and deserializes remote invocation + * result objects. Uses Java serialization just like RMI, but provides the + * same ease of setup as Caucho's HTTP-based Hessian and Burlap protocols. + * + *

HTTP invoker is a very extensible and customizable protocol. + * It supports the RemoteInvocationFactory mechanism, like RMI invoker, + * allowing to include additional invocation attributes (for example, + * a security context). Furthermore, it allows to customize request + * execution via the {@link HttpInvokerRequestExecutor} strategy. + * + *

Can use the JDK's {@link java.rmi.server.RMIClassLoader} to load + * classes from a given {@link #setCodebaseUrl codebase}, performing + * on-demand dynamic code download from a remote location. The codebase + * can consist of multiple URLs, separated by spaces. Note that + * RMIClassLoader requires a SecurityManager to be set, analogous to + * when using dynamic class download with standard RMI! + * (See the RMI documentation for details.) + * + * @author Juergen Hoeller + * @since 1.1 + * @see #setServiceUrl + * @see #setCodebaseUrl + * @see #setRemoteInvocationFactory + * @see #setHttpInvokerRequestExecutor + * @see HttpInvokerServiceExporter + * @see HttpInvokerProxyFactoryBean + * @see java.rmi.server.RMIClassLoader + */ +public class HttpInvokerClientInterceptor extends RemoteInvocationBasedAccessor + implements MethodInterceptor, HttpInvokerClientConfiguration { + + private String codebaseUrl; + + private HttpInvokerRequestExecutor httpInvokerRequestExecutor; + + + /** + * Set the codebase URL to download classes from if not found locally. + * Can consists of multiple URLs, separated by spaces. + *

Follows RMI's codebase conventions for dynamic class download. + * In contrast to RMI, where the server determines the URL for class download + * (via the "java.rmi.server.codebase" system property), it's the client + * that determines the codebase URL here. The server will usually be the + * same as for the service URL, just pointing to a different path there. + * @see #setServiceUrl + * @see org.springframework.remoting.rmi.CodebaseAwareObjectInputStream + * @see java.rmi.server.RMIClassLoader + */ + public void setCodebaseUrl(String codebaseUrl) { + this.codebaseUrl = codebaseUrl; + } + + /** + * Return the codebase URL to download classes from if not found locally. + */ + public String getCodebaseUrl() { + return this.codebaseUrl; + } + + /** + * Set the HttpInvokerRequestExecutor implementation to use for executing + * remote invocations. + *

Default is {@link SimpleHttpInvokerRequestExecutor}. Alternatively, + * consider using {@link CommonsHttpInvokerRequestExecutor} for more + * sophisticated needs. + * @see SimpleHttpInvokerRequestExecutor + * @see CommonsHttpInvokerRequestExecutor + */ + public void setHttpInvokerRequestExecutor(HttpInvokerRequestExecutor httpInvokerRequestExecutor) { + this.httpInvokerRequestExecutor = httpInvokerRequestExecutor; + } + + /** + * Return the HttpInvokerRequestExecutor used by this remote accessor. + *

Creates a default SimpleHttpInvokerRequestExecutor if no executor + * has been initialized already. + */ + public HttpInvokerRequestExecutor getHttpInvokerRequestExecutor() { + if (this.httpInvokerRequestExecutor == null) { + SimpleHttpInvokerRequestExecutor executor = new SimpleHttpInvokerRequestExecutor(); + executor.setBeanClassLoader(getBeanClassLoader()); + this.httpInvokerRequestExecutor = executor; + } + return this.httpInvokerRequestExecutor; + } + + public void afterPropertiesSet() { + super.afterPropertiesSet(); + + // Eagerly initialize the default HttpInvokerRequestExecutor, if needed. + getHttpInvokerRequestExecutor(); + } + + + public Object invoke(MethodInvocation methodInvocation) throws Throwable { + if (AopUtils.isToStringMethod(methodInvocation.getMethod())) { + return "HTTP invoker proxy for service URL [" + getServiceUrl() + "]"; + } + + RemoteInvocation invocation = createRemoteInvocation(methodInvocation); + RemoteInvocationResult result = null; + try { + result = executeRequest(invocation, methodInvocation); + } + catch (Throwable ex) { + throw convertHttpInvokerAccessException(ex); + } + try { + return recreateRemoteInvocationResult(result); + } + catch (Throwable ex) { + if (result.hasInvocationTargetException()) { + throw ex; + } + else { + throw new RemoteInvocationFailureException("Invocation of method [" + methodInvocation.getMethod() + + "] failed in HTTP invoker remote service at [" + getServiceUrl() + "]", ex); + } + } + } + + /** + * Execute the given remote invocation via the HttpInvokerRequestExecutor. + *

This implementation delegates to {@link #executeRequest(RemoteInvocation)}. + * Can be overridden to react to the specific original MethodInvocation. + * @param invocation the RemoteInvocation to execute + * @param originalInvocation the original MethodInvocation (can e.g. be cast + * to the ProxyMethodInvocation interface for accessing user attributes) + * @return the RemoteInvocationResult object + * @throws Exception in case of errors + */ + protected RemoteInvocationResult executeRequest( + RemoteInvocation invocation, MethodInvocation originalInvocation) throws Exception { + + return executeRequest(invocation); + } + + /** + * Execute the given remote invocation via the HttpInvokerRequestExecutor. + *

Can be overridden in subclasses to pass a different configuration object + * to the executor. Alternatively, add further configuration properties in a + * subclass of this accessor: By default, the accessor passed itself as + * configuration object to the executor. + * @param invocation the RemoteInvocation to execute + * @return the RemoteInvocationResult object + * @throws IOException if thrown by I/O operations + * @throws ClassNotFoundException if thrown during deserialization + * @throws Exception in case of general errors + * @see #getHttpInvokerRequestExecutor + * @see HttpInvokerClientConfiguration + */ + protected RemoteInvocationResult executeRequest(RemoteInvocation invocation) throws Exception { + return getHttpInvokerRequestExecutor().executeRequest(this, invocation); + } + + /** + * Convert the given HTTP invoker access exception to an appropriate + * Spring RemoteAccessException. + * @param ex the exception to convert + * @return the RemoteAccessException to throw + */ + protected RemoteAccessException convertHttpInvokerAccessException(Throwable ex) { + if (ex instanceof ConnectException) { + throw new RemoteConnectFailureException( + "Could not connect to HTTP invoker remote service at [" + getServiceUrl() + "]", ex); + } + else if (ex instanceof ClassNotFoundException || ex instanceof NoClassDefFoundError || + ex instanceof InvalidClassException) { + throw new RemoteAccessException( + "Could not deserialize result from HTTP invoker remote service [" + getServiceUrl() + "]", ex); + } + else { + throw new RemoteAccessException( + "Could not access HTTP invoker remote service at [" + getServiceUrl() + "]", ex); + } + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/remoting/httpinvoker/HttpInvokerProxyFactoryBean.java b/org.springframework.web/src/main/java/org/springframework/remoting/httpinvoker/HttpInvokerProxyFactoryBean.java new file mode 100644 index 0000000000..3c62cb6b9a --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/remoting/httpinvoker/HttpInvokerProxyFactoryBean.java @@ -0,0 +1,77 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.remoting.httpinvoker; + +import org.springframework.aop.framework.ProxyFactory; +import org.springframework.beans.factory.FactoryBean; + +/** + * FactoryBean for HTTP invoker proxies. Exposes the proxied service for + * use as a bean reference, using the specified service interface. + * + *

The service URL must be an HTTP URL exposing an HTTP invoker service. + * Optionally, a codebase URL can be specified for on-demand dynamic code download + * from a remote location. For details, see HttpInvokerClientInterceptor docs. + * + *

Serializes remote invocation objects and deserializes remote invocation + * result objects. Uses Java serialization just like RMI, but provides the + * same ease of setup as Caucho's HTTP-based Hessian and Burlap protocols. + * + *

HTTP invoker is the recommended protocol for Java-to-Java remoting. + * It is more powerful and more extensible than Hessian and Burlap, at the + * expense of being tied to Java. Nevertheless, it is as easy to set up as + * Hessian and Burlap, which is its main advantage compared to RMI. + * + * @author Juergen Hoeller + * @since 1.1 + * @see #setServiceInterface + * @see #setServiceUrl + * @see #setCodebaseUrl + * @see HttpInvokerClientInterceptor + * @see HttpInvokerServiceExporter + * @see org.springframework.remoting.rmi.RmiProxyFactoryBean + * @see org.springframework.remoting.caucho.HessianProxyFactoryBean + * @see org.springframework.remoting.caucho.BurlapProxyFactoryBean + */ +public class HttpInvokerProxyFactoryBean extends HttpInvokerClientInterceptor + implements FactoryBean { + + private Object serviceProxy; + + + public void afterPropertiesSet() { + super.afterPropertiesSet(); + if (getServiceInterface() == null) { + throw new IllegalArgumentException("Property 'serviceInterface' is required"); + } + this.serviceProxy = new ProxyFactory(getServiceInterface(), this).getProxy(getBeanClassLoader()); + } + + + public Object getObject() { + return this.serviceProxy; + } + + public Class getObjectType() { + return getServiceInterface(); + } + + public boolean isSingleton() { + return true; + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/remoting/httpinvoker/HttpInvokerRequestExecutor.java b/org.springframework.web/src/main/java/org/springframework/remoting/httpinvoker/HttpInvokerRequestExecutor.java new file mode 100644 index 0000000000..8910bc62ac --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/remoting/httpinvoker/HttpInvokerRequestExecutor.java @@ -0,0 +1,59 @@ +/* + * Copyright 2002-2005 the original author or authors. + * + * 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.springframework.remoting.httpinvoker; + +import java.io.IOException; + +import org.springframework.remoting.support.RemoteInvocation; +import org.springframework.remoting.support.RemoteInvocationResult; + +/** + * Strategy interface for actual execution of an HTTP invoker request. + * Used by HttpInvokerClientInterceptor and its subclass + * HttpInvokerProxyFactoryBean. + * + *

Two implementations are provided out of the box: + *

+ * + * @author Juergen Hoeller + * @since 1.1 + * @see HttpInvokerClientInterceptor#setHttpInvokerRequestExecutor + */ +public interface HttpInvokerRequestExecutor { + + /** + * Execute a request to send the given remote invocation. + * @param config the HTTP invoker configuration that specifies the + * target service + * @param invocation the RemoteInvocation to execute + * @return the RemoteInvocationResult object + * @throws IOException if thrown by I/O operations + * @throws ClassNotFoundException if thrown during deserialization + * @throws Exception in case of general errors + */ + RemoteInvocationResult executeRequest(HttpInvokerClientConfiguration config, RemoteInvocation invocation) + throws Exception; + +} diff --git a/org.springframework.web/src/main/java/org/springframework/remoting/httpinvoker/HttpInvokerServiceExporter.java b/org.springframework.web/src/main/java/org/springframework/remoting/httpinvoker/HttpInvokerServiceExporter.java new file mode 100644 index 0000000000..34ee3b6a3e --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/remoting/httpinvoker/HttpInvokerServiceExporter.java @@ -0,0 +1,199 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.remoting.httpinvoker; + +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.OutputStream; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.remoting.rmi.RemoteInvocationSerializingExporter; +import org.springframework.remoting.support.RemoteInvocation; +import org.springframework.remoting.support.RemoteInvocationResult; +import org.springframework.web.HttpRequestHandler; +import org.springframework.web.util.NestedServletException; + +/** + * Servlet-API-based HTTP request handler that exports the specified service bean + * as HTTP invoker service endpoint, accessible via an HTTP invoker proxy. + * + *

Note: Spring also provides an alternative version of this exporter, + * for Sun's JRE 1.6 HTTP server: {@link SimpleHttpInvokerServiceExporter}. + * + *

Deserializes remote invocation objects and serializes remote invocation + * result objects. Uses Java serialization just like RMI, but provides the + * same ease of setup as Caucho's HTTP-based Hessian and Burlap protocols. + * + *

HTTP invoker is the recommended protocol for Java-to-Java remoting. + * It is more powerful and more extensible than Hessian and Burlap, at the + * expense of being tied to Java. Nevertheless, it is as easy to set up as + * Hessian and Burlap, which is its main advantage compared to RMI. + * + * @author Juergen Hoeller + * @since 1.1 + * @see HttpInvokerClientInterceptor + * @see HttpInvokerProxyFactoryBean + * @see org.springframework.remoting.rmi.RmiServiceExporter + * @see org.springframework.remoting.caucho.HessianServiceExporter + * @see org.springframework.remoting.caucho.BurlapServiceExporter + */ +public class HttpInvokerServiceExporter extends RemoteInvocationSerializingExporter + implements HttpRequestHandler { + + /** + * Reads a remote invocation from the request, executes it, + * and writes the remote invocation result to the response. + * @see #readRemoteInvocation(HttpServletRequest) + * @see #invokeAndCreateResult(org.springframework.remoting.support.RemoteInvocation, Object) + * @see #writeRemoteInvocationResult(HttpServletRequest, HttpServletResponse, RemoteInvocationResult) + */ + public void handleRequest(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + + try { + RemoteInvocation invocation = readRemoteInvocation(request); + RemoteInvocationResult result = invokeAndCreateResult(invocation, getProxy()); + writeRemoteInvocationResult(request, response, result); + } + catch (ClassNotFoundException ex) { + throw new NestedServletException("Class not found during deserialization", ex); + } + } + + /** + * Read a RemoteInvocation from the given HTTP request. + *

Delegates to + * {@link #readRemoteInvocation(javax.servlet.http.HttpServletRequest, java.io.InputStream)} + * with the + * {@link javax.servlet.ServletRequest#getInputStream() servlet request's input stream}. + * @param request current HTTP request + * @return the RemoteInvocation object + * @throws IOException in case of I/O failure + * @throws ClassNotFoundException if thrown by deserialization + */ + protected RemoteInvocation readRemoteInvocation(HttpServletRequest request) + throws IOException, ClassNotFoundException { + + return readRemoteInvocation(request, request.getInputStream()); + } + + /** + * Deserialize a RemoteInvocation object from the given InputStream. + *

Gives {@link #decorateInputStream} a chance to decorate the stream + * first (for example, for custom encryption or compression). Creates a + * {@link org.springframework.remoting.rmi.CodebaseAwareObjectInputStream} + * and calls {@link #doReadRemoteInvocation} to actually read the object. + *

Can be overridden for custom serialization of the invocation. + * @param request current HTTP request + * @param is the InputStream to read from + * @return the RemoteInvocation object + * @throws IOException in case of I/O failure + * @throws ClassNotFoundException if thrown during deserialization + */ + protected RemoteInvocation readRemoteInvocation(HttpServletRequest request, InputStream is) + throws IOException, ClassNotFoundException { + + ObjectInputStream ois = createObjectInputStream(decorateInputStream(request, is)); + try { + return doReadRemoteInvocation(ois); + } + finally { + ois.close(); + } + } + + /** + * Return the InputStream to use for reading remote invocations, + * potentially decorating the given original InputStream. + *

The default implementation returns the given stream as-is. + * Can be overridden, for example, for custom encryption or compression. + * @param request current HTTP request + * @param is the original InputStream + * @return the potentially decorated InputStream + * @throws IOException in case of I/O failure + */ + protected InputStream decorateInputStream(HttpServletRequest request, InputStream is) throws IOException { + return is; + } + + /** + * Write the given RemoteInvocationResult to the given HTTP response. + * @param request current HTTP request + * @param response current HTTP response + * @param result the RemoteInvocationResult object + * @throws IOException in case of I/O failure + */ + protected void writeRemoteInvocationResult( + HttpServletRequest request, HttpServletResponse response, RemoteInvocationResult result) + throws IOException { + + response.setContentType(getContentType()); + writeRemoteInvocationResult(request, response, result, response.getOutputStream()); + } + + /** + * Serialize the given RemoteInvocation to the given OutputStream. + *

The default implementation gives {@link #decorateOutputStream} a chance + * to decorate the stream first (for example, for custom encryption or compression). + * Creates an {@link java.io.ObjectOutputStream} for the final stream and calls + * {@link #doWriteRemoteInvocationResult} to actually write the object. + *

Can be overridden for custom serialization of the invocation. + * @param request current HTTP request + * @param response current HTTP response + * @param result the RemoteInvocationResult object + * @param os the OutputStream to write to + * @throws IOException in case of I/O failure + * @see #decorateOutputStream + * @see #doWriteRemoteInvocationResult + */ + protected void writeRemoteInvocationResult( + HttpServletRequest request, HttpServletResponse response, RemoteInvocationResult result, OutputStream os) + throws IOException { + + ObjectOutputStream oos = createObjectOutputStream(decorateOutputStream(request, response, os)); + try { + doWriteRemoteInvocationResult(result, oos); + oos.flush(); + } + finally { + oos.close(); + } + } + + /** + * Return the OutputStream to use for writing remote invocation results, + * potentially decorating the given original OutputStream. + *

The default implementation returns the given stream as-is. + * Can be overridden, for example, for custom encryption or compression. + * @param request current HTTP request + * @param response current HTTP response + * @param os the original OutputStream + * @return the potentially decorated OutputStream + * @throws IOException in case of I/O failure + */ + protected OutputStream decorateOutputStream( + HttpServletRequest request, HttpServletResponse response, OutputStream os) throws IOException { + + return os; + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/remoting/httpinvoker/SimpleHttpInvokerRequestExecutor.java b/org.springframework.web/src/main/java/org/springframework/remoting/httpinvoker/SimpleHttpInvokerRequestExecutor.java new file mode 100644 index 0000000000..db72c2d04f --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/remoting/httpinvoker/SimpleHttpInvokerRequestExecutor.java @@ -0,0 +1,192 @@ +/* + * Copyright 2002-2008 the original author or authors. + * + * 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.springframework.remoting.httpinvoker; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLConnection; +import java.util.zip.GZIPInputStream; + +import org.springframework.context.i18n.LocaleContext; +import org.springframework.context.i18n.LocaleContextHolder; +import org.springframework.remoting.support.RemoteInvocationResult; +import org.springframework.util.StringUtils; + +/** + * HttpInvokerRequestExecutor implementation that uses standard J2SE facilities + * to execute POST requests, without support for HTTP authentication or + * advanced configuration options. + * + *

Designed for easy subclassing, customizing specific template methods. + * However, consider CommonsHttpInvokerRequestExecutor for more sophisticated + * needs: The J2SE HttpURLConnection is rather limited in its capabilities. + * + * @author Juergen Hoeller + * @since 1.1 + * @see CommonsHttpInvokerRequestExecutor + * @see java.net.HttpURLConnection + */ +public class SimpleHttpInvokerRequestExecutor extends AbstractHttpInvokerRequestExecutor { + + /** + * Execute the given request through a standard J2SE HttpURLConnection. + *

This method implements the basic processing workflow: + * The actual work happens in this class's template methods. + * @see #openConnection + * @see #prepareConnection + * @see #writeRequestBody + * @see #validateResponse + * @see #readResponseBody + */ + protected RemoteInvocationResult doExecuteRequest( + HttpInvokerClientConfiguration config, ByteArrayOutputStream baos) + throws IOException, ClassNotFoundException { + + HttpURLConnection con = openConnection(config); + prepareConnection(con, baos.size()); + writeRequestBody(config, con, baos); + validateResponse(config, con); + InputStream responseBody = readResponseBody(config, con); + + return readRemoteInvocationResult(responseBody, config.getCodebaseUrl()); + } + + /** + * Open an HttpURLConnection for the given remote invocation request. + * @param config the HTTP invoker configuration that specifies the + * target service + * @return the HttpURLConnection for the given request + * @throws IOException if thrown by I/O methods + * @see java.net.URL#openConnection() + */ + protected HttpURLConnection openConnection(HttpInvokerClientConfiguration config) throws IOException { + URLConnection con = new URL(config.getServiceUrl()).openConnection(); + if (!(con instanceof HttpURLConnection)) { + throw new IOException("Service URL [" + config.getServiceUrl() + "] is not an HTTP URL"); + } + return (HttpURLConnection) con; + } + + /** + * Prepare the given HTTP connection. + *

The default implementation specifies POST as method, + * "application/x-java-serialized-object" as "Content-Type" header, + * and the given content length as "Content-Length" header. + * @param con the HTTP connection to prepare + * @param contentLength the length of the content to send + * @throws IOException if thrown by HttpURLConnection methods + * @see java.net.HttpURLConnection#setRequestMethod + * @see java.net.HttpURLConnection#setRequestProperty + */ + protected void prepareConnection(HttpURLConnection con, int contentLength) throws IOException { + con.setDoOutput(true); + con.setRequestMethod(HTTP_METHOD_POST); + con.setRequestProperty(HTTP_HEADER_CONTENT_TYPE, getContentType()); + con.setRequestProperty(HTTP_HEADER_CONTENT_LENGTH, Integer.toString(contentLength)); + LocaleContext locale = LocaleContextHolder.getLocaleContext(); + if (locale != null) { + con.setRequestProperty(HTTP_HEADER_ACCEPT_LANGUAGE, StringUtils.toLanguageTag(locale.getLocale())); + } + if (isAcceptGzipEncoding()) { + con.setRequestProperty(HTTP_HEADER_ACCEPT_ENCODING, ENCODING_GZIP); + } + } + + /** + * Set the given serialized remote invocation as request body. + *

The default implementation simply write the serialized invocation to the + * HttpURLConnection's OutputStream. This can be overridden, for example, to write + * a specific encoding and potentially set appropriate HTTP request headers. + * @param config the HTTP invoker configuration that specifies the target service + * @param con the HttpURLConnection to write the request body to + * @param baos the ByteArrayOutputStream that contains the serialized + * RemoteInvocation object + * @throws IOException if thrown by I/O methods + * @see java.net.HttpURLConnection#getOutputStream() + * @see java.net.HttpURLConnection#setRequestProperty + */ + protected void writeRequestBody( + HttpInvokerClientConfiguration config, HttpURLConnection con, ByteArrayOutputStream baos) + throws IOException { + + baos.writeTo(con.getOutputStream()); + } + + /** + * Validate the given response as contained in the HttpURLConnection object, + * throwing an exception if it does not correspond to a successful HTTP response. + *

Default implementation rejects any HTTP status code beyond 2xx, to avoid + * parsing the response body and trying to deserialize from a corrupted stream. + * @param config the HTTP invoker configuration that specifies the target service + * @param con the HttpURLConnection to validate + * @throws IOException if validation failed + * @see java.net.HttpURLConnection#getResponseCode() + */ + protected void validateResponse(HttpInvokerClientConfiguration config, HttpURLConnection con) + throws IOException { + + if (con.getResponseCode() >= 300) { + throw new IOException( + "Did not receive successful HTTP response: status code = " + con.getResponseCode() + + ", status message = [" + con.getResponseMessage() + "]"); + } + } + + /** + * Extract the response body from the given executed remote invocation + * request. + *

The default implementation simply reads the serialized invocation + * from the HttpURLConnection's InputStream. If the response is recognized + * as GZIP response, the InputStream will get wrapped in a GZIPInputStream. + * @param config the HTTP invoker configuration that specifies the target service + * @param con the HttpURLConnection to read the response body from + * @return an InputStream for the response body + * @throws IOException if thrown by I/O methods + * @see #isGzipResponse + * @see java.util.zip.GZIPInputStream + * @see java.net.HttpURLConnection#getInputStream() + * @see java.net.HttpURLConnection#getHeaderField(int) + * @see java.net.HttpURLConnection#getHeaderFieldKey(int) + */ + protected InputStream readResponseBody(HttpInvokerClientConfiguration config, HttpURLConnection con) + throws IOException { + + if (isGzipResponse(con)) { + // GZIP response found - need to unzip. + return new GZIPInputStream(con.getInputStream()); + } + else { + // Plain response found. + return con.getInputStream(); + } + } + + /** + * Determine whether the given response is a GZIP response. + *

Default implementation checks whether the HTTP "Content-Encoding" + * header contains "gzip" (in any casing). + * @param con the HttpURLConnection to check + */ + protected boolean isGzipResponse(HttpURLConnection con) { + String encodingHeader = con.getHeaderField(HTTP_HEADER_CONTENT_ENCODING); + return (encodingHeader != null && encodingHeader.toLowerCase().indexOf(ENCODING_GZIP) != -1); + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/remoting/httpinvoker/SimpleHttpInvokerServiceExporter.java b/org.springframework.web/src/main/java/org/springframework/remoting/httpinvoker/SimpleHttpInvokerServiceExporter.java new file mode 100644 index 0000000000..dd18c2d164 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/remoting/httpinvoker/SimpleHttpInvokerServiceExporter.java @@ -0,0 +1,177 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.remoting.httpinvoker; + +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.OutputStream; + +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; + +import org.springframework.remoting.rmi.RemoteInvocationSerializingExporter; +import org.springframework.remoting.support.RemoteInvocation; +import org.springframework.remoting.support.RemoteInvocationResult; + +/** + * HTTP request handler that exports the specified service bean as + * HTTP invoker service endpoint, accessible via an HTTP invoker proxy. + * Designed for Sun's JRE 1.6 HTTP server, implementing the + * {@link com.sun.net.httpserver.HttpHandler} interface. + * + *

Deserializes remote invocation objects and serializes remote invocation + * result objects. Uses Java serialization just like RMI, but provides the + * same ease of setup as Caucho's HTTP-based Hessian and Burlap protocols. + * + *

HTTP invoker is the recommended protocol for Java-to-Java remoting. + * It is more powerful and more extensible than Hessian and Burlap, at the + * expense of being tied to Java. Nevertheless, it is as easy to set up as + * Hessian and Burlap, which is its main advantage compared to RMI. + * + * @author Juergen Hoeller + * @since 2.5.1 + * @see org.springframework.remoting.httpinvoker.HttpInvokerClientInterceptor + * @see org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean + * @see org.springframework.remoting.caucho.SimpleHessianServiceExporter + * @see org.springframework.remoting.caucho.SimpleBurlapServiceExporter + */ +public class SimpleHttpInvokerServiceExporter extends RemoteInvocationSerializingExporter + implements HttpHandler { + + /** + * Reads a remote invocation from the request, executes it, + * and writes the remote invocation result to the response. + * @see #readRemoteInvocation(com.sun.net.httpserver.HttpExchange) + * @see #invokeAndCreateResult(org.springframework.remoting.support.RemoteInvocation, Object) + * @see #writeRemoteInvocationResult(com.sun.net.httpserver.HttpExchange, org.springframework.remoting.support.RemoteInvocationResult) + */ + public void handle(HttpExchange exchange) throws IOException { + try { + RemoteInvocation invocation = readRemoteInvocation(exchange); + RemoteInvocationResult result = invokeAndCreateResult(invocation, getProxy()); + writeRemoteInvocationResult(exchange, result); + exchange.close(); + } + catch (ClassNotFoundException ex) { + throw new IOException("Class not found during deserialization", ex); + } + } + + /** + * Read a RemoteInvocation from the given HTTP request. + *

Delegates to + * {@link #readRemoteInvocation(com.sun.net.httpserver.HttpExchange, java.io.InputStream)} + * with the + * {@link com.sun.net.httpserver.HttpExchange#getRequestBody()} request's input stream}. + * @param exchange current HTTP request/response + * @return the RemoteInvocation object + * @throws java.io.IOException in case of I/O failure + * @throws ClassNotFoundException if thrown by deserialization + */ + protected RemoteInvocation readRemoteInvocation(HttpExchange exchange) + throws IOException, ClassNotFoundException { + + return readRemoteInvocation(exchange, exchange.getRequestBody()); + } + + /** + * Deserialize a RemoteInvocation object from the given InputStream. + *

Gives {@link #decorateInputStream} a chance to decorate the stream + * first (for example, for custom encryption or compression). Creates a + * {@link org.springframework.remoting.rmi.CodebaseAwareObjectInputStream} + * and calls {@link #doReadRemoteInvocation} to actually read the object. + *

Can be overridden for custom serialization of the invocation. + * @param exchange current HTTP request/response + * @param is the InputStream to read from + * @return the RemoteInvocation object + * @throws java.io.IOException in case of I/O failure + * @throws ClassNotFoundException if thrown during deserialization + */ + protected RemoteInvocation readRemoteInvocation(HttpExchange exchange, InputStream is) + throws IOException, ClassNotFoundException { + + ObjectInputStream ois = createObjectInputStream(decorateInputStream(exchange, is)); + return doReadRemoteInvocation(ois); + } + + /** + * Return the InputStream to use for reading remote invocations, + * potentially decorating the given original InputStream. + *

The default implementation returns the given stream as-is. + * Can be overridden, for example, for custom encryption or compression. + * @param exchange current HTTP request/response + * @param is the original InputStream + * @return the potentially decorated InputStream + * @throws java.io.IOException in case of I/O failure + */ + protected InputStream decorateInputStream(HttpExchange exchange, InputStream is) throws IOException { + return is; + } + + /** + * Write the given RemoteInvocationResult to the given HTTP response. + * @param exchange current HTTP request/response + * @param result the RemoteInvocationResult object + * @throws java.io.IOException in case of I/O failure + */ + protected void writeRemoteInvocationResult(HttpExchange exchange, RemoteInvocationResult result) + throws IOException { + + exchange.getResponseHeaders().set("Content-Type", getContentType()); + exchange.sendResponseHeaders(200, 0); + writeRemoteInvocationResult(exchange, result, exchange.getResponseBody()); + } + + /** + * Serialize the given RemoteInvocation to the given OutputStream. + *

The default implementation gives {@link #decorateOutputStream} a chance + * to decorate the stream first (for example, for custom encryption or compression). + * Creates an {@link java.io.ObjectOutputStream} for the final stream and calls + * {@link #doWriteRemoteInvocationResult} to actually write the object. + *

Can be overridden for custom serialization of the invocation. + * @param exchange current HTTP request/response + * @param result the RemoteInvocationResult object + * @param os the OutputStream to write to + * @throws java.io.IOException in case of I/O failure + * @see #decorateOutputStream + * @see #doWriteRemoteInvocationResult + */ + protected void writeRemoteInvocationResult( + HttpExchange exchange, RemoteInvocationResult result, OutputStream os) throws IOException { + + ObjectOutputStream oos = createObjectOutputStream(decorateOutputStream(exchange, os)); + doWriteRemoteInvocationResult(result, oos); + oos.flush(); + } + + /** + * Return the OutputStream to use for writing remote invocation results, + * potentially decorating the given original OutputStream. + *

The default implementation returns the given stream as-is. + * Can be overridden, for example, for custom encryption or compression. + * @param exchange current HTTP request/response + * @param os the original OutputStream + * @return the potentially decorated OutputStream + * @throws java.io.IOException in case of I/O failure + */ + protected OutputStream decorateOutputStream(HttpExchange exchange, OutputStream os) throws IOException { + return os; + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/remoting/httpinvoker/package.html b/org.springframework.web/src/main/java/org/springframework/remoting/httpinvoker/package.html new file mode 100644 index 0000000000..3ffc013ad1 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/remoting/httpinvoker/package.html @@ -0,0 +1,14 @@ + + + +Remoting classes for transparent Java-to-Java remoting via HTTP invokers. +Uses Java serialization just like RMI, but provides the same ease of setup +as Caucho's HTTP-based Hessian and Burlap protocols. + +

HTTP invoker is the recommended protocol for Java-to-Java remoting. +It is more powerful and more extensible than Hessian and Burlap, at the +expense of being tied to Java. Neverthelesss, it is as easy to set up as +Hessian and Burlap, which is its main advantage compared to RMI. + + + diff --git a/org.springframework.web/src/main/java/org/springframework/remoting/jaxrpc/JaxRpcPortClientInterceptor.java b/org.springframework.web/src/main/java/org/springframework/remoting/jaxrpc/JaxRpcPortClientInterceptor.java new file mode 100644 index 0000000000..8ba9ac9bc8 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/remoting/jaxrpc/JaxRpcPortClientInterceptor.java @@ -0,0 +1,757 @@ +/* + * Copyright 2002-2008 the original author or authors. + * + * 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.springframework.remoting.jaxrpc; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.rmi.Remote; +import java.rmi.RemoteException; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Properties; + +import javax.xml.namespace.QName; +import javax.xml.rpc.Call; +import javax.xml.rpc.JAXRPCException; +import javax.xml.rpc.Service; +import javax.xml.rpc.ServiceException; +import javax.xml.rpc.Stub; +import javax.xml.rpc.soap.SOAPFaultException; + +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; + +import org.springframework.aop.support.AopUtils; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.remoting.RemoteLookupFailureException; +import org.springframework.remoting.RemoteProxyFailureException; +import org.springframework.remoting.rmi.RmiClientInterceptorUtils; +import org.springframework.util.CollectionUtils; +import org.springframework.util.ReflectionUtils; + +/** + * {@link org.aopalliance.intercept.MethodInterceptor} for accessing a specific port + * of a JAX-RPC service. Uses either {@link LocalJaxRpcServiceFactory}'s facilities + * underneath or takes an explicit reference to an existing JAX-RPC Service instance + * (e.g. obtained via a {@link org.springframework.jndi.JndiObjectFactoryBean}). + * + *

Allows to set JAX-RPC's standard stub properties directly, via the + * "username", "password", "endpointAddress" and "maintainSession" properties. + * For typical usage, it is not necessary to specify those. + * + *

In standard JAX-RPC style, this invoker is used with an RMI service interface. + * Alternatively, this invoker can also proxy a JAX-RPC service with a matching + * non-RMI business interface, that is, an interface that declares the service methods + * without RemoteExceptions. In the latter case, RemoteExceptions thrown by JAX-RPC + * will automatically get converted to Spring's unchecked RemoteAccessException. + * + *

Setting "serviceInterface" is usually sufficient: The invoker will automatically + * use JAX-RPC "dynamic invocations" via the Call API in this case, no matter whether + * the specified interface is an RMI or non-RMI interface. Alternatively, a corresponding + * JAX-RPC port interface can be specified as "portInterface", which will turn this + * invoker into "static invocation" mode (operating on a standard JAX-RPC port stub). + * + * @author Juergen Hoeller + * @since 15.12.2003 + * @see #setPortName + * @see #setServiceInterface + * @see #setPortInterface + * @see javax.xml.rpc.Service#createCall + * @see javax.xml.rpc.Service#getPort + * @see org.springframework.remoting.RemoteAccessException + * @see org.springframework.jndi.JndiObjectFactoryBean + */ +public class JaxRpcPortClientInterceptor extends LocalJaxRpcServiceFactory + implements MethodInterceptor, InitializingBean { + + private Service jaxRpcService; + + private Service serviceToUse; + + private String portName; + + private String username; + + private String password; + + private String endpointAddress; + + private boolean maintainSession; + + /** Map of custom properties, keyed by property name (String) */ + private final Map customPropertyMap = new HashMap(); + + private Class serviceInterface; + + private Class portInterface; + + private boolean lookupServiceOnStartup = true; + + private boolean refreshServiceAfterConnectFailure = false; + + private QName portQName; + + private Remote portStub; + + private final Object preparationMonitor = new Object(); + + + /** + * Set a reference to an existing JAX-RPC Service instance, + * for example obtained via {@link org.springframework.jndi.JndiObjectFactoryBean}. + * If not set, {@link LocalJaxRpcServiceFactory}'s properties have to be specified. + * @see #setServiceFactoryClass + * @see #setWsdlDocumentUrl + * @see #setNamespaceUri + * @see #setServiceName + * @see org.springframework.jndi.JndiObjectFactoryBean + */ + public void setJaxRpcService(Service jaxRpcService) { + this.jaxRpcService = jaxRpcService; + } + + /** + * Return a reference to an existing JAX-RPC Service instance, if any. + */ + public Service getJaxRpcService() { + return this.jaxRpcService; + } + + /** + * Set the name of the port. + * Corresponds to the "wsdl:port" name. + */ + public void setPortName(String portName) { + this.portName = portName; + } + + /** + * Return the name of the port. + */ + public String getPortName() { + return this.portName; + } + + /** + * Set the username to specify on the stub or call. + * @see javax.xml.rpc.Stub#USERNAME_PROPERTY + * @see javax.xml.rpc.Call#USERNAME_PROPERTY + */ + public void setUsername(String username) { + this.username = username; + } + + /** + * Return the username to specify on the stub or call. + */ + public String getUsername() { + return this.username; + } + + /** + * Set the password to specify on the stub or call. + * @see javax.xml.rpc.Stub#PASSWORD_PROPERTY + * @see javax.xml.rpc.Call#PASSWORD_PROPERTY + */ + public void setPassword(String password) { + this.password = password; + } + + /** + * Return the password to specify on the stub or call. + */ + public String getPassword() { + return this.password; + } + + /** + * Set the endpoint address to specify on the stub or call. + * @see javax.xml.rpc.Stub#ENDPOINT_ADDRESS_PROPERTY + * @see javax.xml.rpc.Call#setTargetEndpointAddress + */ + public void setEndpointAddress(String endpointAddress) { + this.endpointAddress = endpointAddress; + } + + /** + * Return the endpoint address to specify on the stub or call. + */ + public String getEndpointAddress() { + return this.endpointAddress; + } + + /** + * Set the maintain session flag to specify on the stub or call. + * @see javax.xml.rpc.Stub#SESSION_MAINTAIN_PROPERTY + * @see javax.xml.rpc.Call#SESSION_MAINTAIN_PROPERTY + */ + public void setMaintainSession(boolean maintainSession) { + this.maintainSession = maintainSession; + } + + /** + * Return the maintain session flag to specify on the stub or call. + */ + public boolean isMaintainSession() { + return this.maintainSession; + } + + /** + * Set custom properties to be set on the stub or call. + *

Can be populated with a String "value" (parsed via PropertiesEditor) + * or a "props" element in XML bean definitions. + * @see javax.xml.rpc.Stub#_setProperty + * @see javax.xml.rpc.Call#setProperty + */ + public void setCustomProperties(Properties customProperties) { + CollectionUtils.mergePropertiesIntoMap(customProperties, this.customPropertyMap); + } + + /** + * Set custom properties to be set on the stub or call. + *

Can be populated with a "map" or "props" element in XML bean definitions. + * @see javax.xml.rpc.Stub#_setProperty + * @see javax.xml.rpc.Call#setProperty + */ + public void setCustomPropertyMap(Map customProperties) { + if (customProperties != null) { + Iterator it = customProperties.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry entry = (Map.Entry) it.next(); + if (!(entry.getKey() instanceof String)) { + throw new IllegalArgumentException( + "Illegal property key [" + entry.getKey() + "]: only Strings allowed"); + } + addCustomProperty((String) entry.getKey(), entry.getValue()); + } + } + } + + /** + * Allow Map access to the custom properties to be set on the stub + * or call, with the option to add or override specific entries. + *

Useful for specifying entries directly, for example via + * "customPropertyMap[myKey]". This is particularly useful for + * adding or overriding entries in child bean definitions. + */ + public Map getCustomPropertyMap() { + return this.customPropertyMap; + } + + /** + * Add a custom property to this JAX-RPC Stub/Call. + * @param name the name of the attribute to expose + * @param value the attribute value to expose + * @see javax.xml.rpc.Stub#_setProperty + * @see javax.xml.rpc.Call#setProperty + */ + public void addCustomProperty(String name, Object value) { + this.customPropertyMap.put(name, value); + } + + /** + * Set the interface of the service that this factory should create a proxy for. + * This will typically be a non-RMI business interface, although you can also + * use an RMI port interface as recommended by JAX-RPC here. + *

Calls on the specified service interface will either be translated to the + * underlying RMI port interface (in case of a "portInterface" being specified) + * or to dynamic calls (using the JAX-RPC Dynamic Invocation Interface). + *

The dynamic call mechanism has the advantage that you don't need to + * maintain an RMI port interface in addition to an existing non-RMI business + * interface. In terms of configuration, specifying the business interface + * as "serviceInterface" will be enough; this interceptor will automatically + * use dynamic calls in such a scenario. + * @see javax.xml.rpc.Service#createCall + * @see #setPortInterface + */ + public void setServiceInterface(Class serviceInterface) { + if (serviceInterface != null && !serviceInterface.isInterface()) { + throw new IllegalArgumentException("'serviceInterface' must be an interface"); + } + this.serviceInterface = serviceInterface; + } + + /** + * Return the interface of the service that this factory should create a proxy for. + */ + public Class getServiceInterface() { + return this.serviceInterface; + } + + /** + * Set the JAX-RPC port interface to use. Only needs to be set if a JAX-RPC + * port stub should be used instead of the dynamic call mechanism. + * See the javadoc of the "serviceInterface" property for more details. + *

The interface must be suitable for a JAX-RPC port, that is, it must be + * an RMI service interface (that extends java.rmi.Remote). + *

NOTE: Check whether your JAX-RPC provider returns thread-safe + * port stubs. If not, use the dynamic call mechanism instead, which will + * always be thread-safe. In particular, do not use JAX-RPC port stubs + * with Apache Axis, whose port stubs are known to be non-thread-safe. + * @see javax.xml.rpc.Service#getPort + * @see java.rmi.Remote + * @see #setServiceInterface + */ + public void setPortInterface(Class portInterface) { + if (portInterface != null && + (!portInterface.isInterface() || !Remote.class.isAssignableFrom(portInterface))) { + throw new IllegalArgumentException( + "'portInterface' must be an interface derived from [java.rmi.Remote]"); + } + this.portInterface = portInterface; + } + + /** + * Return the JAX-RPC port interface to use. + */ + public Class getPortInterface() { + return this.portInterface; + } + + /** + * Set whether to look up the JAX-RPC service on startup. + *

Default is "true". Turn this flag off to allow for late start + * of the target server. In this case, the JAX-RPC service will be + * lazily fetched on first access. + */ + public void setLookupServiceOnStartup(boolean lookupServiceOnStartup) { + this.lookupServiceOnStartup = lookupServiceOnStartup; + } + + /** + * Set whether to refresh the JAX-RPC service on connect failure, + * that is, whenever a JAX-RPC invocation throws a RemoteException. + *

Default is "false", keeping a reference to the JAX-RPC service + * in any case, retrying the next invocation on the same service + * even in case of failure. Turn this flag on to reinitialize the + * entire service in case of connect failures. + */ + public void setRefreshServiceAfterConnectFailure(boolean refreshServiceAfterConnectFailure) { + this.refreshServiceAfterConnectFailure = refreshServiceAfterConnectFailure; + } + + + /** + * Prepares the JAX-RPC service and port if the "lookupServiceOnStartup" + * is turned on (which it is by default). + */ + public void afterPropertiesSet() { + if (this.lookupServiceOnStartup) { + prepare(); + } + } + + /** + * Create and initialize the JAX-RPC service for the specified port. + *

Prepares a JAX-RPC stub if possible (if an RMI interface is available); + * falls back to JAX-RPC dynamic calls else. Using dynamic calls can be enforced + * through overriding {@link #alwaysUseJaxRpcCall} to return true. + *

{@link #postProcessJaxRpcService} and {@link #postProcessPortStub} + * hooks are available for customization in subclasses. When using dynamic calls, + * each can be post-processed via {@link #postProcessJaxRpcCall}. + * @throws RemoteLookupFailureException if service initialization or port stub creation failed + */ + public void prepare() throws RemoteLookupFailureException { + if (getPortName() == null) { + throw new IllegalArgumentException("Property 'portName' is required"); + } + + synchronized (this.preparationMonitor) { + this.serviceToUse = null; + + // Cache the QName for the port. + this.portQName = getQName(getPortName()); + + try { + Service service = getJaxRpcService(); + if (service == null) { + service = createJaxRpcService(); + } + else { + postProcessJaxRpcService(service); + } + + Class portInterface = getPortInterface(); + if (portInterface != null && !alwaysUseJaxRpcCall()) { + // JAX-RPC-compliant port interface -> using JAX-RPC stub for port. + + if (logger.isDebugEnabled()) { + logger.debug("Creating JAX-RPC proxy for JAX-RPC port [" + this.portQName + + "], using port interface [" + portInterface.getName() + "]"); + } + Remote remoteObj = service.getPort(this.portQName, portInterface); + + if (logger.isDebugEnabled()) { + Class serviceInterface = getServiceInterface(); + if (serviceInterface != null) { + boolean isImpl = serviceInterface.isInstance(remoteObj); + logger.debug("Using service interface [" + serviceInterface.getName() + "] for JAX-RPC port [" + + this.portQName + "] - " + (!isImpl ? "not" : "") + " directly implemented"); + } + } + + if (!(remoteObj instanceof Stub)) { + throw new RemoteLookupFailureException("Port stub of class [" + remoteObj.getClass().getName() + + "] is not a valid JAX-RPC stub: it does not implement interface [javax.xml.rpc.Stub]"); + } + Stub stub = (Stub) remoteObj; + + // Apply properties to JAX-RPC stub. + preparePortStub(stub); + + // Allow for custom post-processing in subclasses. + postProcessPortStub(stub); + + this.portStub = remoteObj; + } + + else { + // No JAX-RPC-compliant port interface -> using JAX-RPC dynamic calls. + if (logger.isDebugEnabled()) { + logger.debug("Using JAX-RPC dynamic calls for JAX-RPC port [" + this.portQName + "]"); + } + } + + this.serviceToUse = service; + } + catch (ServiceException ex) { + throw new RemoteLookupFailureException( + "Failed to initialize service for JAX-RPC port [" + this.portQName + "]", ex); + } + } + } + + /** + * Return whether to always use JAX-RPC dynamic calls. + * Called by afterPropertiesSet. + *

Default is "false"; if an RMI interface is specified as "portInterface" + * or "serviceInterface", it will be used to create a JAX-RPC port stub. + *

Can be overridden to enforce the use of the JAX-RPC Call API, + * for example if there is a need to customize at the Call level. + * This just necessary if you you want to use an RMI interface as + * "serviceInterface", though; in case of only a non-RMI interface being + * available, this interceptor will fall back to the Call API anyway. + * @see #postProcessJaxRpcCall + */ + protected boolean alwaysUseJaxRpcCall() { + return false; + } + + /** + * Reset the prepared service of this interceptor, + * allowing for reinitialization on next access. + */ + protected void reset() { + synchronized (this.preparationMonitor) { + this.serviceToUse = null; + } + } + + /** + * Return whether this client interceptor has already been prepared, + * i.e. has already looked up the JAX-RPC service and port. + */ + protected boolean isPrepared() { + synchronized (this.preparationMonitor) { + return (this.serviceToUse != null); + } + } + + /** + * Return the prepared QName for the port. + * @see #setPortName + * @see #getQName + */ + protected final QName getPortQName() { + return this.portQName; + } + + + /** + * Prepare the given JAX-RPC port stub, applying properties to it. + * Called by {@link #prepare}. + *

Just applied when actually creating a JAX-RPC port stub, in case of a + * compliant port interface. Else, JAX-RPC dynamic calls will be used. + * @param stub the current JAX-RPC port stub + * @see #setUsername + * @see #setPassword + * @see #setEndpointAddress + * @see #setMaintainSession + * @see #setCustomProperties + * @see #setPortInterface + * @see #prepareJaxRpcCall + */ + protected void preparePortStub(Stub stub) { + String username = getUsername(); + if (username != null) { + stub._setProperty(Stub.USERNAME_PROPERTY, username); + } + String password = getPassword(); + if (password != null) { + stub._setProperty(Stub.PASSWORD_PROPERTY, password); + } + String endpointAddress = getEndpointAddress(); + if (endpointAddress != null) { + stub._setProperty(Stub.ENDPOINT_ADDRESS_PROPERTY, endpointAddress); + } + if (isMaintainSession()) { + stub._setProperty(Stub.SESSION_MAINTAIN_PROPERTY, Boolean.TRUE); + } + if (this.customPropertyMap != null) { + for (Iterator it = this.customPropertyMap.keySet().iterator(); it.hasNext();) { + String key = (String) it.next(); + stub._setProperty(key, this.customPropertyMap.get(key)); + } + } + } + + /** + * Post-process the given JAX-RPC port stub. Called by {@link #prepare}. + *

The default implementation is empty. + *

Just applied when actually creating a JAX-RPC port stub, in case of a + * compliant port interface. Else, JAX-RPC dynamic calls will be used. + * @param stub the current JAX-RPC port stub + * (can be cast to an implementation-specific class if necessary) + * @see #setPortInterface + * @see #postProcessJaxRpcCall + */ + protected void postProcessPortStub(Stub stub) { + } + + /** + * Return the underlying JAX-RPC port stub that this interceptor delegates to + * for each method invocation on the proxy. + */ + protected Remote getPortStub() { + return this.portStub; + } + + + /** + * Translates the method invocation into a JAX-RPC service invocation. + *

Prepares the service on the fly, if necessary, in case of lazy + * lookup or a connect failure having happened. + * @see #prepare() + * @see #doInvoke + */ + public Object invoke(MethodInvocation invocation) throws Throwable { + if (AopUtils.isToStringMethod(invocation.getMethod())) { + return "JAX-RPC proxy for port [" + getPortName() + "] of service [" + getServiceName() + "]"; + } + // Lazily prepare service and stub if necessary. + synchronized (this.preparationMonitor) { + if (!isPrepared()) { + prepare(); + } + } + return doInvoke(invocation); + } + + /** + * Perform a JAX-RPC service invocation based on the given method invocation. + *

Uses traditional RMI stub invocation if a JAX-RPC port stub is available; + * falls back to JAX-RPC dynamic calls else. + * @param invocation the AOP method invocation + * @return the invocation result, if any + * @throws Throwable in case of invocation failure + * @see #getPortStub() + * @see #doInvoke(org.aopalliance.intercept.MethodInvocation, java.rmi.Remote) + * @see #performJaxRpcCall(org.aopalliance.intercept.MethodInvocation, javax.xml.rpc.Service) + */ + protected Object doInvoke(MethodInvocation invocation) throws Throwable { + Remote stub = getPortStub(); + try { + if (stub != null) { + // JAX-RPC port stub available -> traditional RMI stub invocation. + if (logger.isTraceEnabled()) { + logger.trace("Invoking operation '" + invocation.getMethod().getName() + "' on JAX-RPC port stub"); + } + return doInvoke(invocation, stub); + } + else { + // No JAX-RPC stub -> using JAX-RPC dynamic calls. + if (logger.isTraceEnabled()) { + logger.trace("Invoking operation '" + invocation.getMethod().getName() + "' as JAX-RPC dynamic call"); + } + return performJaxRpcCall(invocation, this.serviceToUse); + } + } + catch (RemoteException ex) { + throw handleRemoteException(invocation.getMethod(), ex); + } + catch (SOAPFaultException ex) { + throw new JaxRpcSoapFaultException(ex); + } + catch (JAXRPCException ex) { + throw new RemoteProxyFailureException("Invalid JAX-RPC call configuration", ex); + } + } + + /** + * Perform a JAX-RPC service invocation on the given port stub. + * @param invocation the AOP method invocation + * @param portStub the RMI port stub to invoke + * @return the invocation result, if any + * @throws Throwable in case of invocation failure + * @see #getPortStub() + * @see #doInvoke(org.aopalliance.intercept.MethodInvocation, java.rmi.Remote) + * @see #performJaxRpcCall + */ + protected Object doInvoke(MethodInvocation invocation, Remote portStub) throws Throwable { + try { + return RmiClientInterceptorUtils.invokeRemoteMethod(invocation, portStub); + } + catch (InvocationTargetException ex) { + throw ex.getTargetException(); + } + } + + /** + * Perform a JAX-RPC dynamic call for the given AOP method invocation. + * Delegates to {@link #prepareJaxRpcCall} and + * {@link #postProcessJaxRpcCall} for setting up the call object. + *

The default implementation uses method name as JAX-RPC operation name + * and method arguments as arguments for the JAX-RPC call. Can be + * overridden in subclasses for custom operation names and/or arguments. + * @param invocation the current AOP MethodInvocation that should + * be converted to a JAX-RPC call + * @param service the JAX-RPC Service to use for the call + * @return the return value of the invocation, if any + * @throws Throwable the exception thrown by the invocation, if any + * @see #prepareJaxRpcCall + * @see #postProcessJaxRpcCall + */ + protected Object performJaxRpcCall(MethodInvocation invocation, Service service) throws Throwable { + Method method = invocation.getMethod(); + QName portQName = this.portQName; + + // Create JAX-RPC call object, using the method name as operation name. + // Synchronized because of non-thread-safe Axis implementation! + Call call = null; + synchronized (service) { + call = service.createCall(portQName, method.getName()); + } + + // Apply properties to JAX-RPC stub. + prepareJaxRpcCall(call); + + // Allow for custom post-processing in subclasses. + postProcessJaxRpcCall(call, invocation); + + // Perform actual invocation. + return call.invoke(invocation.getArguments()); + } + + /** + * Prepare the given JAX-RPC call, applying properties to it. Called by {@link #invoke}. + *

Just applied when actually using JAX-RPC dynamic calls, i.e. if no compliant + * port interface was specified. Else, a JAX-RPC port stub will be used. + * @param call the current JAX-RPC call object + * @see #setUsername + * @see #setPassword + * @see #setEndpointAddress + * @see #setMaintainSession + * @see #setCustomProperties + * @see #setPortInterface + * @see #preparePortStub + */ + protected void prepareJaxRpcCall(Call call) { + String username = getUsername(); + if (username != null) { + call.setProperty(Call.USERNAME_PROPERTY, username); + } + String password = getPassword(); + if (password != null) { + call.setProperty(Call.PASSWORD_PROPERTY, password); + } + String endpointAddress = getEndpointAddress(); + if (endpointAddress != null) { + call.setTargetEndpointAddress(endpointAddress); + } + if (isMaintainSession()) { + call.setProperty(Call.SESSION_MAINTAIN_PROPERTY, Boolean.TRUE); + } + if (this.customPropertyMap != null) { + for (Iterator it = this.customPropertyMap.keySet().iterator(); it.hasNext();) { + String key = (String) it.next(); + call.setProperty(key, this.customPropertyMap.get(key)); + } + } + } + + /** + * Post-process the given JAX-RPC call. Called by {@link #invoke}. + *

The default implementation is empty. + *

Just applied when actually using JAX-RPC dynamic calls, i.e. if no compliant + * port interface was specified. Else, a JAX-RPC port stub will be used. + * @param call the current JAX-RPC call object + * (can be cast to an implementation-specific class if necessary) + * @param invocation the current AOP MethodInvocation that the call was + * created for (can be used to check method name, method parameters + * and/or passed-in arguments) + * @see #setPortInterface + * @see #postProcessPortStub + */ + protected void postProcessJaxRpcCall(Call call, MethodInvocation invocation) { + } + + /** + * Handle the given RemoteException that was thrown from a JAX-RPC port stub + * or JAX-RPC call invocation. + * @param method the service interface method that we invoked + * @param ex the original RemoteException + * @return the exception to rethrow (may be the original RemoteException + * or an extracted/wrapped exception, but never null) + */ + protected Throwable handleRemoteException(Method method, RemoteException ex) { + boolean isConnectFailure = isConnectFailure(ex); + if (isConnectFailure && this.refreshServiceAfterConnectFailure) { + reset(); + } + Throwable cause = ex.getCause(); + if (cause != null && ReflectionUtils.declaresException(method, cause.getClass())) { + if (logger.isDebugEnabled()) { + logger.debug("Rethrowing wrapped exception of type [" + cause.getClass().getName() + "] as-is"); + } + // Declared on the service interface: probably a wrapped business exception. + return ex.getCause(); + } + else { + // Throw either a RemoteAccessException or the original RemoteException, + // depending on what the service interface declares. + return RmiClientInterceptorUtils.convertRmiAccessException( + method, ex, isConnectFailure, this.portQName.toString()); + } + } + + /** + * Determine whether the given RMI exception indicates a connect failure. + *

The default implementation returns true unless the + * exception class name (or exception superclass name) contains the term + * "Fault" (e.g. "AxisFault"), assuming that the JAX-RPC provider only + * throws RemoteException in case of WSDL faults and connect failures. + * @param ex the RMI exception to check + * @return whether the exception should be treated as connect failure + * @see org.springframework.remoting.rmi.RmiClientInterceptorUtils#isConnectFailure + */ + protected boolean isConnectFailure(RemoteException ex) { + return (ex.getClass().getName().indexOf("Fault") == -1 && + ex.getClass().getSuperclass().getName().indexOf("Fault") == -1); + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/remoting/jaxrpc/JaxRpcPortProxyFactoryBean.java b/org.springframework.web/src/main/java/org/springframework/remoting/jaxrpc/JaxRpcPortProxyFactoryBean.java new file mode 100644 index 0000000000..34214cef06 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/remoting/jaxrpc/JaxRpcPortProxyFactoryBean.java @@ -0,0 +1,87 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.remoting.jaxrpc; + +import org.springframework.aop.framework.ProxyFactory; +import org.springframework.beans.factory.BeanClassLoaderAware; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.util.ClassUtils; + +/** + * {@link org.springframework.beans.factory.FactoryBean} for a specific port of a + * JAX-RPC service. Exposes a proxy for the port, to be used for bean references. + * Inherits configuration properties from {@link JaxRpcPortClientInterceptor}. + * + *

This factory is typically used with an RMI service interface. Alternatively, + * this factory can also proxy a JAX-RPC service with a matching non-RMI business + * interface, i.e. an interface that mirrors the RMI service methods but does not + * declare RemoteExceptions. In the latter case, RemoteExceptions thrown by the + * JAX-RPC stub will automatically get converted to Spring's unchecked + * RemoteAccessException. + * + *

If exposing the JAX-RPC port interface (i.e. an RMI interface) directly, + * setting "serviceInterface" is sufficient. If exposing a non-RMI business + * interface, the business interface needs to be set as "serviceInterface", + * and the JAX-RPC port interface as "portInterface". + * + * @author Juergen Hoeller + * @since 15.12.2003 + * @see #setServiceInterface + * @see #setPortInterface + * @see LocalJaxRpcServiceFactoryBean + */ +public class JaxRpcPortProxyFactoryBean extends JaxRpcPortClientInterceptor + implements FactoryBean, BeanClassLoaderAware { + + private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader(); + + private Object serviceProxy; + + + public void setBeanClassLoader(ClassLoader classLoader) { + this.beanClassLoader = classLoader; + } + + public void afterPropertiesSet() { + if (getServiceInterface() == null) { + // Use JAX-RPC port interface (a traditional RMI interface) + // as service interface if none explicitly specified. + if (getPortInterface() != null) { + setServiceInterface(getPortInterface()); + } + else { + throw new IllegalArgumentException("Property 'serviceInterface' is required"); + } + } + super.afterPropertiesSet(); + this.serviceProxy = new ProxyFactory(getServiceInterface(), this).getProxy(this.beanClassLoader); + } + + + public Object getObject() { + return this.serviceProxy; + } + + public Class getObjectType() { + return getServiceInterface(); + } + + public boolean isSingleton() { + return true; + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/remoting/jaxrpc/JaxRpcServicePostProcessor.java b/org.springframework.web/src/main/java/org/springframework/remoting/jaxrpc/JaxRpcServicePostProcessor.java new file mode 100644 index 0000000000..8ef0fe0631 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/remoting/jaxrpc/JaxRpcServicePostProcessor.java @@ -0,0 +1,51 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.remoting.jaxrpc; + +import javax.xml.rpc.Service; + +/** + * Callback interface for post-processing a JAX-RPC Service. + * + *

Implementations can be registered with {@link LocalJaxRpcServiceFactory} + * or one of its subclasses: {@link LocalJaxRpcServiceFactoryBean}, + * {@link JaxRpcPortClientInterceptor}, or {@link JaxRpcPortProxyFactoryBean}. + * + *

Useful, for example, to register custom type mappings. See the + * {@link org.springframework.remoting.jaxrpc.support.AxisBeanMappingServicePostProcessor} + * class that registers Axis-specific bean mappings for specified bean classes. + * This is defined for the domain objects in the JPetStore same application, + * for example. + * + * @author Juergen Hoeller + * @since 1.1.4 + * @see LocalJaxRpcServiceFactory#setServicePostProcessors + * @see LocalJaxRpcServiceFactoryBean#setServicePostProcessors + * @see JaxRpcPortClientInterceptor#setServicePostProcessors + * @see JaxRpcPortProxyFactoryBean#setServicePostProcessors + * @see javax.xml.rpc.Service#getTypeMappingRegistry + */ +public interface JaxRpcServicePostProcessor { + + /** + * Post-process the given JAX-RPC {@link Service}. + * @param service the current JAX-RPC Service + * (can be cast to an implementation-specific class if necessary) + */ + void postProcessJaxRpcService(Service service); + +} diff --git a/org.springframework.web/src/main/java/org/springframework/remoting/jaxrpc/JaxRpcSoapFaultException.java b/org.springframework.web/src/main/java/org/springframework/remoting/jaxrpc/JaxRpcSoapFaultException.java new file mode 100644 index 0000000000..16349e2929 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/remoting/jaxrpc/JaxRpcSoapFaultException.java @@ -0,0 +1,65 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.remoting.jaxrpc; + +import javax.xml.namespace.QName; +import javax.xml.rpc.soap.SOAPFaultException; + +import org.springframework.remoting.soap.SoapFaultException; + +/** + * Spring SoapFaultException adapter for the JAX-RPC + * {@link javax.xml.rpc.soap.SOAPFaultException} class. + * + * @author Juergen Hoeller + * @since 2.5 + */ +public class JaxRpcSoapFaultException extends SoapFaultException { + + /** + * Constructor for JaxRpcSoapFaultException. + * @param original the original JAX-RPC SOAPFaultException to wrap + */ + public JaxRpcSoapFaultException(SOAPFaultException original) { + super(original.getMessage(), original); + } + + /** + * Return the wrapped JAX-RPC SOAPFaultException. + */ + public final SOAPFaultException getOriginalException() { + return (SOAPFaultException) getCause(); + } + + + public String getFaultCode() { + return getOriginalException().getFaultCode().toString(); + } + + public QName getFaultCodeAsQName() { + return getOriginalException().getFaultCode(); + } + + public String getFaultString() { + return getOriginalException().getFaultString(); + } + + public String getFaultActor() { + return getOriginalException().getFaultActor(); + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/remoting/jaxrpc/LocalJaxRpcServiceFactory.java b/org.springframework.web/src/main/java/org/springframework/remoting/jaxrpc/LocalJaxRpcServiceFactory.java new file mode 100644 index 0000000000..3c35155265 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/remoting/jaxrpc/LocalJaxRpcServiceFactory.java @@ -0,0 +1,321 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.remoting.jaxrpc; + +import java.net.URL; +import java.util.Properties; + +import javax.xml.namespace.QName; +import javax.xml.rpc.Service; +import javax.xml.rpc.ServiceException; +import javax.xml.rpc.ServiceFactory; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.beans.BeanUtils; + +/** + * Factory for locally defined JAX-RPC {@link javax.xml.rpc.Service} references. + * Uses a JAX-RPC {@link javax.xml.rpc.ServiceFactory} underneath. + * + *

Serves as base class for {@link LocalJaxRpcServiceFactoryBean} as well as + * {@link JaxRpcPortClientInterceptor} and {@link JaxRpcPortProxyFactoryBean}. + * + * @author Juergen Hoeller + * @since 15.12.2003 + * @see javax.xml.rpc.ServiceFactory + * @see javax.xml.rpc.Service + * @see LocalJaxRpcServiceFactoryBean + * @see JaxRpcPortClientInterceptor + * @see JaxRpcPortProxyFactoryBean + */ +public class LocalJaxRpcServiceFactory { + + /** Logger available to subclasses */ + protected final Log logger = LogFactory.getLog(getClass()); + + private ServiceFactory serviceFactory; + + private Class serviceFactoryClass; + + private URL wsdlDocumentUrl; + + private String namespaceUri; + + private String serviceName; + + private Class jaxRpcServiceInterface; + + private Properties jaxRpcServiceProperties; + + private JaxRpcServicePostProcessor[] servicePostProcessors; + + + /** + * Set the ServiceFactory instance to use. + *

This is an alternative to the common "serviceFactoryClass" property, + * allowing for a pre-initialized ServiceFactory instance to be specified. + * @see #setServiceFactoryClass + */ + public void setServiceFactory(ServiceFactory serviceFactory) { + this.serviceFactory = serviceFactory; + } + + /** + * Return the specified ServiceFactory instance, if any. + */ + public ServiceFactory getServiceFactory() { + return this.serviceFactory; + } + + /** + * Set the ServiceFactory class to use, for example + * "org.apache.axis.client.ServiceFactory". + *

Does not need to be set if the JAX-RPC implementation has registered + * itself with the JAX-RPC system property "SERVICEFACTORY_PROPERTY". + * @see javax.xml.rpc.ServiceFactory + */ + public void setServiceFactoryClass(Class serviceFactoryClass) { + if (serviceFactoryClass != null && !ServiceFactory.class.isAssignableFrom(serviceFactoryClass)) { + throw new IllegalArgumentException("'serviceFactoryClass' must implement [javax.xml.rpc.ServiceFactory]"); + } + this.serviceFactoryClass = serviceFactoryClass; + } + + /** + * Return the ServiceFactory class to use, or null if default. + */ + public Class getServiceFactoryClass() { + return this.serviceFactoryClass; + } + + /** + * Set the URL of the WSDL document that describes the service. + */ + public void setWsdlDocumentUrl(URL wsdlDocumentUrl) { + this.wsdlDocumentUrl = wsdlDocumentUrl; + } + + /** + * Return the URL of the WSDL document that describes the service. + */ + public URL getWsdlDocumentUrl() { + return this.wsdlDocumentUrl; + } + + /** + * Set the namespace URI of the service. + * Corresponds to the WSDL "targetNamespace". + */ + public void setNamespaceUri(String namespaceUri) { + this.namespaceUri = (namespaceUri != null ? namespaceUri.trim() : null); + } + + /** + * Return the namespace URI of the service. + */ + public String getNamespaceUri() { + return this.namespaceUri; + } + + /** + * Set the name of the service to look up. + * Corresponds to the "wsdl:service" name. + * @see javax.xml.rpc.ServiceFactory#createService(javax.xml.namespace.QName) + * @see javax.xml.rpc.ServiceFactory#createService(java.net.URL, javax.xml.namespace.QName) + * @see javax.xml.rpc.ServiceFactory#loadService(java.net.URL, javax.xml.namespace.QName, java.util.Properties) + */ + public void setServiceName(String serviceName) { + this.serviceName = serviceName; + } + + /** + * Return the name of the service. + */ + public String getServiceName() { + return this.serviceName; + } + + /** + * Set the JAX-RPC service interface to use for looking up the service. + * If specified, this will override a "serviceName" setting. + *

The specified interface will usually be a generated JAX-RPC service + * interface that directly corresponds to the WSDL service declaration. + * Note that this is not a port interface or the application-level service + * interface to be exposed by a port proxy! + *

Only supported by JAX-RPC 1.1 providers. + * @see #setServiceName + * @see javax.xml.rpc.ServiceFactory#loadService(Class) + * @see javax.xml.rpc.ServiceFactory#loadService(java.net.URL, Class, java.util.Properties) + */ + public void setJaxRpcServiceInterface(Class jaxRpcServiceInterface) { + this.jaxRpcServiceInterface = jaxRpcServiceInterface; + } + + /** + * Return the JAX-RPC service interface to use for looking up the service. + */ + public Class getJaxRpcServiceInterface() { + return this.jaxRpcServiceInterface; + } + + /** + * Set JAX-RPC service properties to be passed to the ServiceFactory, if any. + *

Only supported by JAX-RPC 1.1 providers. + * @see javax.xml.rpc.ServiceFactory#loadService(java.net.URL, javax.xml.namespace.QName, java.util.Properties) + * @see javax.xml.rpc.ServiceFactory#loadService(java.net.URL, Class, java.util.Properties) + */ + public void setJaxRpcServiceProperties(Properties jaxRpcServiceProperties) { + this.jaxRpcServiceProperties = jaxRpcServiceProperties; + } + + /** + * Return JAX-RPC service properties to be passed to the ServiceFactory, if any. + */ + public Properties getJaxRpcServiceProperties() { + return this.jaxRpcServiceProperties; + } + + /** + * Set the JaxRpcServicePostProcessors to be applied to JAX-RPC Service + * instances created by this factory. + *

Such post-processors can, for example, register custom type mappings. + * They are reusable across all pre-built subclasses of this factory: + * LocalJaxRpcServiceFactoryBean, JaxRpcPortClientInterceptor, + * JaxRpcPortProxyFactoryBean. + * @see LocalJaxRpcServiceFactoryBean + * @see JaxRpcPortClientInterceptor + * @see JaxRpcPortProxyFactoryBean + */ + public void setServicePostProcessors(JaxRpcServicePostProcessor[] servicePostProcessors) { + this.servicePostProcessors = servicePostProcessors; + } + + /** + * Return the JaxRpcServicePostProcessors to be applied to JAX-RPC Service + * instances created by this factory. + */ + public JaxRpcServicePostProcessor[] getServicePostProcessors() { + return this.servicePostProcessors; + } + + + /** + * Create a JAX-RPC Service according to the parameters of this factory. + * @see #setServiceName + * @see #setWsdlDocumentUrl + * @see #postProcessJaxRpcService + */ + public Service createJaxRpcService() throws ServiceException { + ServiceFactory serviceFactory = getServiceFactory(); + if (serviceFactory == null) { + serviceFactory = createServiceFactory(); + } + + // Create service based on this factory's settings. + Service service = createService(serviceFactory); + + // Allow for custom post-processing in subclasses. + postProcessJaxRpcService(service); + + return service; + } + + /** + * Return a QName for the given name, relative to the namespace URI + * of this factory, if given. + * @see #setNamespaceUri + */ + protected QName getQName(String name) { + return (getNamespaceUri() != null ? new QName(getNamespaceUri(), name) : new QName(name)); + } + + /** + * Create a JAX-RPC ServiceFactory, either of the specified class + * or the default. + * @throws ServiceException if thrown by JAX-RPC methods + * @see #setServiceFactoryClass + * @see javax.xml.rpc.ServiceFactory#newInstance() + */ + protected ServiceFactory createServiceFactory() throws ServiceException { + if (getServiceFactoryClass() != null) { + return (ServiceFactory) BeanUtils.instantiateClass(getServiceFactoryClass()); + } + else { + return ServiceFactory.newInstance(); + } + } + + /** + * Actually create the JAX-RPC Service instance, + * based on this factory's settings. + * @param serviceFactory the JAX-RPC ServiceFactory to use + * @return the newly created JAX-RPC Service + * @throws ServiceException if thrown by JAX-RPC methods + * @see javax.xml.rpc.ServiceFactory#createService + * @see javax.xml.rpc.ServiceFactory#loadService + */ + protected Service createService(ServiceFactory serviceFactory) throws ServiceException { + if (getServiceName() == null && getJaxRpcServiceInterface() == null) { + throw new IllegalArgumentException("Either 'serviceName' or 'jaxRpcServiceInterface' is required"); + } + + if (getJaxRpcServiceInterface() != null) { + // Create service via generated JAX-RPC service interface. + // Only supported on JAX-RPC 1.1 + if (getWsdlDocumentUrl() != null || getJaxRpcServiceProperties() != null) { + return serviceFactory.loadService( + getWsdlDocumentUrl(), getJaxRpcServiceInterface(), getJaxRpcServiceProperties()); + } + return serviceFactory.loadService(getJaxRpcServiceInterface()); + } + + // Create service via specified JAX-RPC service name. + QName serviceQName = getQName(getServiceName()); + if (getJaxRpcServiceProperties() != null) { + // Only supported on JAX-RPC 1.1 + return serviceFactory.loadService(getWsdlDocumentUrl(), serviceQName, getJaxRpcServiceProperties()); + } + if (getWsdlDocumentUrl() != null) { + return serviceFactory.createService(getWsdlDocumentUrl(), serviceQName); + } + return serviceFactory.createService(serviceQName); + } + + /** + * Post-process the given JAX-RPC Service. Called by {@link #createJaxRpcService}. + * Useful, for example, to register custom type mappings. + *

The default implementation delegates to all registered + * {@link JaxRpcServicePostProcessor JaxRpcServicePostProcessors}. + * It is usually preferable to implement custom type mappings etc there rather + * than in a subclass of this factory, to allow for reuse of the post-processors. + * @param service the current JAX-RPC Service + * (can be cast to an implementation-specific class if necessary) + * @see #setServicePostProcessors + * @see javax.xml.rpc.Service#getTypeMappingRegistry() + */ + protected void postProcessJaxRpcService(Service service) { + JaxRpcServicePostProcessor[] postProcessors = getServicePostProcessors(); + if (postProcessors != null) { + for (int i = 0; i < postProcessors.length; i++) { + postProcessors[i].postProcessJaxRpcService(service); + } + } + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/remoting/jaxrpc/LocalJaxRpcServiceFactoryBean.java b/org.springframework.web/src/main/java/org/springframework/remoting/jaxrpc/LocalJaxRpcServiceFactoryBean.java new file mode 100644 index 0000000000..a4e26be5b5 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/remoting/jaxrpc/LocalJaxRpcServiceFactoryBean.java @@ -0,0 +1,62 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.remoting.jaxrpc; + +import javax.xml.rpc.Service; +import javax.xml.rpc.ServiceException; + +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.InitializingBean; + +/** + * {@link org.springframework.beans.factory.FactoryBean} for locally + * defined JAX-RPC Service references. + * Uses {@link LocalJaxRpcServiceFactory}'s facilities underneath. + * + *

Alternatively, JAX-RPC Service references can be looked up + * in the JNDI environment of the J2EE container. + * + * @author Juergen Hoeller + * @since 15.12.2003 + * @see javax.xml.rpc.Service + * @see org.springframework.jndi.JndiObjectFactoryBean + * @see JaxRpcPortProxyFactoryBean + */ +public class LocalJaxRpcServiceFactoryBean extends LocalJaxRpcServiceFactory + implements FactoryBean, InitializingBean { + + private Service service; + + + public void afterPropertiesSet() throws ServiceException { + this.service = createJaxRpcService(); + } + + + public Object getObject() throws Exception { + return this.service; + } + + public Class getObjectType() { + return (this.service != null ? this.service.getClass() : Service.class); + } + + public boolean isSingleton() { + return true; + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/remoting/jaxrpc/ServletEndpointSupport.java b/org.springframework.web/src/main/java/org/springframework/remoting/jaxrpc/ServletEndpointSupport.java new file mode 100644 index 0000000000..4755de6d17 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/remoting/jaxrpc/ServletEndpointSupport.java @@ -0,0 +1,150 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.remoting.jaxrpc; + +import java.io.File; + +import javax.servlet.ServletContext; +import javax.xml.rpc.ServiceException; +import javax.xml.rpc.server.ServiceLifecycle; +import javax.xml.rpc.server.ServletEndpointContext; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.context.ApplicationContext; +import org.springframework.context.support.MessageSourceAccessor; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.context.support.WebApplicationContextUtils; +import org.springframework.web.util.WebUtils; + +/** + * Convenience base class for JAX-RPC servlet endpoint implementations. + * Provides a reference to the current Spring application context, + * e.g. for bean lookup or resource loading. + * + *

The Web Service servlet needs to run in the same web application + * as the Spring context to allow for access to Spring's facilities. + * In case of Axis, copy the AxisServlet definition into your web.xml, + * and set up the endpoint in "server-config.wsdd" (or use the deploy tool). + * + *

This class does not extend + * {@link org.springframework.web.context.support.WebApplicationObjectSupport} + * to not expose any public setters. For some reason, Axis tries to + * resolve public setters in a special way... + * + *

JAX-RPC service endpoints are usually required to implement an + * RMI port interface. However, many JAX-RPC implementations accept plain + * service endpoint classes too, avoiding the need to maintain an RMI port + * interface in addition to an existing non-RMI business interface. + * Therefore, implementing the business interface will usually be sufficient. + * + * @author Juergen Hoeller + * @since 16.12.2003 + * @see #init + * @see #getWebApplicationContext + */ +public abstract class ServletEndpointSupport implements ServiceLifecycle { + + protected final Log logger = LogFactory.getLog(getClass()); + + private ServletEndpointContext servletEndpointContext; + + private WebApplicationContext webApplicationContext; + + private MessageSourceAccessor messageSourceAccessor; + + + /** + * Initialize this JAX-RPC servlet endpoint. + * Calls onInit after successful context initialization. + * @param context ServletEndpointContext + * @throws ServiceException if the context is not a ServletEndpointContext + * @see #onInit + */ + public final void init(Object context) throws ServiceException { + if (!(context instanceof ServletEndpointContext)) { + throw new ServiceException("ServletEndpointSupport needs ServletEndpointContext, not [" + context + "]"); + } + this.servletEndpointContext = (ServletEndpointContext) context; + ServletContext servletContext = this.servletEndpointContext.getServletContext(); + this.webApplicationContext = WebApplicationContextUtils.getRequiredWebApplicationContext(servletContext); + this.messageSourceAccessor = new MessageSourceAccessor(this.webApplicationContext); + onInit(); + } + + /** + * Return the current JAX-RPC ServletEndpointContext. + */ + protected final ServletEndpointContext getServletEndpointContext() { + return this.servletEndpointContext; + } + + /** + * Return the current Spring ApplicationContext. + */ + protected final ApplicationContext getApplicationContext() { + return this.webApplicationContext; + } + + /** + * Return the current Spring WebApplicationContext. + */ + protected final WebApplicationContext getWebApplicationContext() { + return this.webApplicationContext; + } + + /** + * Return a MessageSourceAccessor for the application context + * used by this object, for easy message access. + */ + protected final MessageSourceAccessor getMessageSourceAccessor() { + return this.messageSourceAccessor; + } + + /** + * Return the current ServletContext. + */ + protected final ServletContext getServletContext() { + return this.webApplicationContext.getServletContext(); + } + + /** + * Return the temporary directory for the current web application, + * as provided by the servlet container. + * @return the File representing the temporary directory + */ + protected final File getTempDir() { + return WebUtils.getTempDir(getServletContext()); + } + + /** + * Callback for custom initialization after the context has been set up. + * @throws ServiceException if initialization failed + */ + protected void onInit() throws ServiceException { + } + + + /** + * This implementation of destroy is empty. + * Can be overridden in subclasses. + */ + public void destroy() { + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/remoting/jaxrpc/package.html b/org.springframework.web/src/main/java/org/springframework/remoting/jaxrpc/package.html new file mode 100644 index 0000000000..836d4c76a0 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/remoting/jaxrpc/package.html @@ -0,0 +1,10 @@ + + + +Remoting classes for Web Services via JAX-RPC. +This package provides proxy factories for accessing JAX-RPC +services and ports, and a support class for implementing +JAX-RPC Servlet endpoints. + + + diff --git a/org.springframework.web/src/main/java/org/springframework/remoting/jaxrpc/support/AxisBeanMappingServicePostProcessor.java b/org.springframework.web/src/main/java/org/springframework/remoting/jaxrpc/support/AxisBeanMappingServicePostProcessor.java new file mode 100644 index 0000000000..5d86abbb95 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/remoting/jaxrpc/support/AxisBeanMappingServicePostProcessor.java @@ -0,0 +1,210 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.remoting.jaxrpc.support; + +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Properties; + +import javax.xml.namespace.QName; +import javax.xml.rpc.Service; +import javax.xml.rpc.encoding.TypeMapping; +import javax.xml.rpc.encoding.TypeMappingRegistry; + +import org.apache.axis.encoding.ser.BeanDeserializerFactory; +import org.apache.axis.encoding.ser.BeanSerializerFactory; + +import org.springframework.beans.factory.BeanClassLoaderAware; +import org.springframework.remoting.jaxrpc.JaxRpcServicePostProcessor; +import org.springframework.util.ClassUtils; + +/** + * Axis-specific {@link JaxRpcServicePostProcessor} that registers bean + * mappings for domain objects that follow the JavaBean pattern. + * + *

The same mappings are usually also registered at the server in + * Axis' "server-config.wsdd" file. + * + *

To be registered as a service post-processor on a + * {@link org.springframework.remoting.jaxrpc.LocalJaxRpcServiceFactoryBean} or + * {@link org.springframework.remoting.jaxrpc.JaxRpcPortProxyFactoryBean}, + * carrying appropriate configuration. + * + *

Note: Without such explicit bean mappings, a complex type like a + * domain object cannot be transferred via SOAP. + * + * @author Juergen Hoeller + * @since 2.0 + * @see org.apache.axis.encoding.ser.BeanSerializerFactory + * @see org.apache.axis.encoding.ser.BeanDeserializerFactory + * @see org.springframework.remoting.jaxrpc.LocalJaxRpcServiceFactoryBean#setServicePostProcessors + * @see org.springframework.remoting.jaxrpc.JaxRpcPortProxyFactoryBean#setServicePostProcessors + */ +public class AxisBeanMappingServicePostProcessor implements JaxRpcServicePostProcessor, BeanClassLoaderAware { + + private String encodingStyleUri; + + private String typeNamespaceUri; + + private Map beanMappings; + + private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader(); + + + /** + * Set the encoding style URI to use for the type mapping. + *

A typical value is "http://schemas.xmlsoap.org/soap/encoding/", + * as suggested by the JAX-RPC javadoc. However, note that the default + * behavior of this post-processor is to register the type mapping + * as JAX-RPC default if no explicit encoding style URI is given. + * @see javax.xml.rpc.encoding.TypeMappingRegistry#register + * @see javax.xml.rpc.encoding.TypeMappingRegistry#registerDefault + */ + public void setEncodingStyleUri(String encodingStyleUri) { + this.encodingStyleUri = encodingStyleUri; + } + + /** + * Set the application-specific namespace to use for XML types, + * for example "urn:JPetStore". + * @see javax.xml.rpc.encoding.TypeMapping#register + */ + public void setTypeNamespaceUri(String typeNamespaceUri) { + this.typeNamespaceUri = typeNamespaceUri; + } + + /** + * Specify the bean mappings to register as String-String pairs, + * with the Java type name as key and the WSDL type name as value. + */ + public void setBeanMappings(Properties beanMappingProps) { + if (beanMappingProps != null) { + this.beanMappings = new HashMap(beanMappingProps.size()); + Enumeration propertyNames = beanMappingProps.propertyNames(); + while (propertyNames.hasMoreElements()) { + String javaTypeName = (String) propertyNames.nextElement(); + String wsdlTypeName = beanMappingProps.getProperty(javaTypeName); + this.beanMappings.put(javaTypeName, wsdlTypeName); + } + } + else { + this.beanMappings = null; + } + } + + /** + * Specify the bean mappings to register as Java types, + * with the WSDL type names inferred from the Java type names + * (using the short, that is, non-fully-qualified class name). + */ + public void setBeanClasses(Class[] beanClasses) { + if (beanClasses != null) { + this.beanMappings = new HashMap(beanClasses.length); + for (int i = 0; i < beanClasses.length; i++) { + Class beanClass = beanClasses[i]; + String wsdlTypeName = ClassUtils.getShortName(beanClass); + this.beanMappings.put(beanClass, wsdlTypeName); + } + } + else { + this.beanMappings = null; + } + } + + public void setBeanClassLoader(ClassLoader beanClassLoader) { + this.beanClassLoader = beanClassLoader; + } + + + /** + * Register the specified bean mappings on the given Service's + * {@link TypeMappingRegistry}. + * @see javax.xml.rpc.Service#getTypeMappingRegistry() + * @see #setBeanMappings + * @see #registerBeanMappings(javax.xml.rpc.encoding.TypeMapping) + */ + public void postProcessJaxRpcService(Service service) { + TypeMappingRegistry registry = service.getTypeMappingRegistry(); + TypeMapping mapping = registry.createTypeMapping(); + + registerBeanMappings(mapping); + + if (this.encodingStyleUri != null) { + registry.register(this.encodingStyleUri, mapping); + } + else { + registry.registerDefault(mapping); + } + } + + /** + * Perform the actual bean mapping registration. + * @param mapping the JAX-RPC {@link TypeMapping} to operate on + * @see #setBeanMappings + * @see #registerBeanMapping(javax.xml.rpc.encoding.TypeMapping, Class, String) + */ + protected void registerBeanMappings(TypeMapping mapping) { + if (this.beanMappings != null) { + for (Iterator it = this.beanMappings.entrySet().iterator(); it.hasNext();) { + Map.Entry entry = (Map.Entry) it.next(); + Object key = entry.getKey(); + Class javaType = null; + if (key instanceof Class) { + javaType = (Class) key; + } + else { + javaType = ClassUtils.resolveClassName((String) key, this.beanClassLoader); + } + String wsdlTypeName = (String) entry.getValue(); + registerBeanMapping(mapping, javaType, wsdlTypeName); + } + } + } + + /** + * Register a bean mapping for the given Java type and WSDL type name. + * @param mapping the JAX-RPC {@link TypeMapping} to operate on + * @param javaType the Java type + * @param wsdlTypeName the WSDL type name (as a {@link String}) + */ + protected void registerBeanMapping(TypeMapping mapping, Class javaType, String wsdlTypeName) { + registerBeanMapping(mapping, javaType, getTypeQName(wsdlTypeName)); + } + + /** + * Register a bean mapping for the given Java type and WSDL type. + * @param mapping the JAX-RPC {@link TypeMapping} to operate on + * @param javaType the Java type + * @param wsdlType the WSDL type (as XML {@link QName}) + */ + protected void registerBeanMapping(TypeMapping mapping, Class javaType, QName wsdlType) { + mapping.register(javaType, wsdlType, + new BeanSerializerFactory(javaType, wsdlType), + new BeanDeserializerFactory(javaType, wsdlType)); + } + + /** + * Return a {@link QName} for the given name, relative to the + * {@link #setTypeNamespaceUri namespace URI} of this post-processor, if given. + */ + protected final QName getTypeQName(String name) { + return (this.typeNamespaceUri != null ? new QName(this.typeNamespaceUri, name) : new QName(name)); + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/remoting/jaxrpc/support/package.html b/org.springframework.web/src/main/java/org/springframework/remoting/jaxrpc/support/package.html new file mode 100644 index 0000000000..62722f8b13 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/remoting/jaxrpc/support/package.html @@ -0,0 +1,8 @@ + + + +Support for specific JAX-RPC providers. Contains an Axis-specific +JaxRpcServicePostProcessor for declaratively registering bean mappings. + + + diff --git a/org.springframework.web/src/main/java/org/springframework/remoting/jaxws/AbstractJaxWsServiceExporter.java b/org.springframework.web/src/main/java/org/springframework/remoting/jaxws/AbstractJaxWsServiceExporter.java new file mode 100644 index 0000000000..36b9a08b55 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/remoting/jaxws/AbstractJaxWsServiceExporter.java @@ -0,0 +1,148 @@ +/* + * Copyright 2002-2008 the original author or authors. + * + * 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.springframework.remoting.jaxws; + +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.Executor; + +import javax.jws.WebService; +import javax.xml.ws.Endpoint; + +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.ListableBeanFactory; +import org.springframework.core.task.TaskExecutor; +import org.springframework.core.task.support.ConcurrentExecutorAdapter; + +/** + * Abstract exporter for JAX-WS services, autodetecting annotated service beans + * (through the JAX-WS {@link javax.jws.WebService} annotation). Subclasses + * need to implement the {@link #publishEndpoint} template method for actual + * endpoint exposure. + * + * @author Juergen Hoeller + * @since 2.5.5 + * @see javax.jws.WebService + * @see javax.xml.ws.Endpoint + * @see SimpleJaxWsServiceExporter + * @see SimpleHttpServerJaxWsServiceExporter + */ +public abstract class AbstractJaxWsServiceExporter implements BeanFactoryAware, InitializingBean, DisposableBean { + + private Map endpointProperties; + + private Executor executor; + + private ListableBeanFactory beanFactory; + + private final Set publishedEndpoints = new LinkedHashSet(); + + + /** + * Set the property bag for the endpoint, including properties such as + * "javax.xml.ws.wsdl.service" or "javax.xml.ws.wsdl.port". + * @see javax.xml.ws.Endpoint#setProperties + * @see javax.xml.ws.Endpoint#WSDL_SERVICE + * @see javax.xml.ws.Endpoint#WSDL_PORT + */ + public void setEndpointProperties(Map endpointProperties) { + this.endpointProperties = endpointProperties; + } + + /** + * Set the JDK concurrent executor to use for dispatching incoming requests + * to exported service instances. + * @see javax.xml.ws.Endpoint#setExecutor + */ + public void setExecutor(Executor executor) { + this.executor = executor; + } + + /** + * Set the Spring TaskExecutor to use for dispatching incoming requests + * to exported service instances. + * @see javax.xml.ws.Endpoint#setExecutor + */ + public void setTaskExecutor(TaskExecutor executor) { + this.executor = new ConcurrentExecutorAdapter(executor); + } + + /** + * Obtains all web service beans and publishes them as JAX-WS endpoints. + */ + public void setBeanFactory(BeanFactory beanFactory) { + if (!(beanFactory instanceof ListableBeanFactory)) { + throw new IllegalStateException(getClass().getSimpleName() + " requires a ListableBeanFactory"); + } + this.beanFactory = (ListableBeanFactory) beanFactory; + } + + + /** + * Immediately publish all endpoints when fully configured. + * @see #publishEndpoints() + */ + public void afterPropertiesSet() throws Exception { + publishEndpoints(); + } + + /** + * Publish all {@link javax.jws.WebService} annotated beans in the + * containing BeanFactory. + * @see #publishEndpoint + */ + public void publishEndpoints() { + String[] beanNames = this.beanFactory.getBeanNamesForType(Object.class, false, false); + for (String beanName : beanNames) { + Class type = this.beanFactory.getType(beanName); + WebService annotation = type.getAnnotation(WebService.class); + if (annotation != null) { + Endpoint endpoint = Endpoint.create(this.beanFactory.getBean(beanName)); + if (this.endpointProperties != null) { + endpoint.setProperties(this.endpointProperties); + } + if (this.executor != null) { + endpoint.setExecutor(this.executor); + } + publishEndpoint(endpoint, annotation); + this.publishedEndpoints.add(endpoint); + } + } + } + + /** + * Actually publish the given endpoint. To be implemented by subclasses. + * @param endpoint the JAX-WS Endpoint object + * @param annotation the service bean's WebService annotation + */ + protected abstract void publishEndpoint(Endpoint endpoint, WebService annotation); + + + /** + * Stops all published endpoints, taking the web services offline. + */ + public void destroy() { + for (Endpoint endpoint : this.publishedEndpoints) { + endpoint.stop(); + } + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/remoting/jaxws/JaxWsPortClientInterceptor.java b/org.springframework.web/src/main/java/org/springframework/remoting/jaxws/JaxWsPortClientInterceptor.java new file mode 100644 index 0000000000..3074abdcaf --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/remoting/jaxws/JaxWsPortClientInterceptor.java @@ -0,0 +1,426 @@ +/* + * Copyright 2002-2008 the original author or authors. + * + * 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.springframework.remoting.jaxws; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; + +import javax.xml.namespace.QName; +import javax.xml.ws.BindingProvider; +import javax.xml.ws.ProtocolException; +import javax.xml.ws.Service; +import javax.xml.ws.WebServiceException; +import javax.xml.ws.soap.SOAPFaultException; + +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; + +import org.springframework.aop.support.AopUtils; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.remoting.RemoteAccessException; +import org.springframework.remoting.RemoteConnectFailureException; +import org.springframework.remoting.RemoteLookupFailureException; +import org.springframework.remoting.RemoteProxyFailureException; + +/** + * {@link org.aopalliance.intercept.MethodInterceptor} for accessing a + * specific port of a JAX-WS service. + * + *

Uses either {@link LocalJaxWsServiceFactory}'s facilities underneath, + * or takes an explicit reference to an existing JAX-WS Service instance + * (e.g. obtained via {@link org.springframework.jndi.JndiObjectFactoryBean}). + * + * @author Juergen Hoeller + * @since 2.5 + * @see #setPortName + * @see #setServiceInterface + * @see javax.xml.ws.Service#getPort + * @see org.springframework.remoting.RemoteAccessException + * @see org.springframework.jndi.JndiObjectFactoryBean + */ +public class JaxWsPortClientInterceptor extends LocalJaxWsServiceFactory + implements MethodInterceptor, InitializingBean { + + private Service jaxWsService; + + private String portName; + + private String username; + + private String password; + + private String endpointAddress; + + private boolean maintainSession; + + private boolean useSoapAction; + + private String soapActionUri; + + private Map customProperties; + + private Class serviceInterface; + + private boolean lookupServiceOnStartup = true; + + private QName portQName; + + private Object portStub; + + private final Object preparationMonitor = new Object(); + + + /** + * Set a reference to an existing JAX-WS Service instance, + * for example obtained via {@link org.springframework.jndi.JndiObjectFactoryBean}. + * If not set, {@link LocalJaxWsServiceFactory}'s properties have to be specified. + * @see #setWsdlDocumentUrl + * @see #setNamespaceUri + * @see #setServiceName + * @see org.springframework.jndi.JndiObjectFactoryBean + */ + public void setJaxWsService(Service jaxWsService) { + this.jaxWsService = jaxWsService; + } + + /** + * Return a reference to an existing JAX-WS Service instance, if any. + */ + public Service getJaxWsService() { + return this.jaxWsService; + } + + /** + * Set the name of the port. + * Corresponds to the "wsdl:port" name. + */ + public void setPortName(String portName) { + this.portName = portName; + } + + /** + * Return the name of the port. + */ + public String getPortName() { + return this.portName; + } + + /** + * Set the username to specify on the stub. + * @see javax.xml.ws.BindingProvider#USERNAME_PROPERTY + */ + public void setUsername(String username) { + this.username = username; + } + + /** + * Return the username to specify on the stub. + */ + public String getUsername() { + return this.username; + } + + /** + * Set the password to specify on the stub. + * @see javax.xml.ws.BindingProvider#PASSWORD_PROPERTY + */ + public void setPassword(String password) { + this.password = password; + } + + /** + * Return the password to specify on the stub. + */ + public String getPassword() { + return this.password; + } + + /** + * Set the endpoint address to specify on the stub. + * @see javax.xml.ws.BindingProvider#ENDPOINT_ADDRESS_PROPERTY + */ + public void setEndpointAddress(String endpointAddress) { + this.endpointAddress = endpointAddress; + } + + /** + * Return the endpoint address to specify on the stub. + */ + public String getEndpointAddress() { + return this.endpointAddress; + } + + /** + * Set the "session.maintain" flag to specify on the stub. + * @see javax.xml.ws.BindingProvider#SESSION_MAINTAIN_PROPERTY + */ + public void setMaintainSession(boolean maintainSession) { + this.maintainSession = maintainSession; + } + + /** + * Return the "session.maintain" flag to specify on the stub. + */ + public boolean isMaintainSession() { + return this.maintainSession; + } + + /** + * Set the "soapaction.use" flag to specify on the stub. + * @see javax.xml.ws.BindingProvider#SOAPACTION_USE_PROPERTY + */ + public void setUseSoapAction(boolean useSoapAction) { + this.useSoapAction = useSoapAction; + } + + /** + * Return the "soapaction.use" flag to specify on the stub. + */ + public boolean isUseSoapAction() { + return this.useSoapAction; + } + + /** + * Set the SOAP action URI to specify on the stub. + * @see javax.xml.ws.BindingProvider#SOAPACTION_URI_PROPERTY + */ + public void setSoapActionUri(String soapActionUri) { + this.soapActionUri = soapActionUri; + } + + /** + * Return the SOAP action URI to specify on the stub. + */ + public String getSoapActionUri() { + return this.soapActionUri; + } + + /** + * Set custom properties to be set on the stub. + *

Can be populated with a String "value" (parsed via PropertiesEditor) + * or a "props" element in XML bean definitions. + * @see javax.xml.ws.BindingProvider#getRequestContext() + */ + public void setCustomProperties(Map customProperties) { + this.customProperties = customProperties; + } + + /** + * Allow Map access to the custom properties to be set on the stub, + * with the option to add or override specific entries. + *

Useful for specifying entries directly, for example via + * "customProperties[myKey]". This is particularly useful for + * adding or overriding entries in child bean definitions. + */ + public Map getCustomProperties() { + if (this.customProperties == null) { + this.customProperties = new HashMap(); + } + return this.customProperties; + } + + /** + * Add a custom property to this JAX-WS BindingProvider. + * @param name the name of the attribute to expose + * @param value the attribute value to expose + * @see javax.xml.ws.BindingProvider#getRequestContext() + */ + public void addCustomProperty(String name, Object value) { + getCustomProperties().put(name, value); + } + + /** + * Set the interface of the service that this factory should create a proxy for. + */ + public void setServiceInterface(Class serviceInterface) { + if (serviceInterface != null && !serviceInterface.isInterface()) { + throw new IllegalArgumentException("'serviceInterface' must be an interface"); + } + this.serviceInterface = serviceInterface; + } + + /** + * Return the interface of the service that this factory should create a proxy for. + */ + public Class getServiceInterface() { + return this.serviceInterface; + } + + /** + * Set whether to look up the JAX-WS service on startup. + *

Default is "true". Turn this flag off to allow for late start + * of the target server. In this case, the JAX-WS service will be + * lazily fetched on first access. + */ + public void setLookupServiceOnStartup(boolean lookupServiceOnStartup) { + this.lookupServiceOnStartup = lookupServiceOnStartup; + } + + + public void afterPropertiesSet() { + if (this.lookupServiceOnStartup) { + prepare(); + } + } + + public void prepare() { + if (getServiceInterface() == null) { + throw new IllegalArgumentException("Property 'serviceInterface' is required"); + } + Service serviceToUse = getJaxWsService(); + if (serviceToUse == null) { + serviceToUse = createJaxWsService(); + } + this.portQName = getQName(getPortName() != null ? getPortName() : getServiceInterface().getName()); + Object stub = (getPortName() != null ? + serviceToUse.getPort(this.portQName, getServiceInterface()) : serviceToUse.getPort(getServiceInterface())); + preparePortStub(stub); + this.portStub = stub; + } + + /** + * Return whether this client interceptor has already been prepared, + * i.e. has already looked up the JAX-WS service and port. + */ + protected boolean isPrepared() { + synchronized (this.preparationMonitor) { + return (this.portStub != null); + } + } + + /** + * Return the prepared QName for the port. + * @see #setPortName + * @see #getQName + */ + protected final QName getPortQName() { + return this.portQName; + } + + /** + * Prepare the given JAX-WS port stub, applying properties to it. + * Called by {@link #prepare}. + * @param stub the current JAX-WS port stub + * @see #setUsername + * @see #setPassword + * @see #setEndpointAddress + * @see #setMaintainSession + * @see #setCustomProperties + */ + protected void preparePortStub(Object stub) { + Map stubProperties = new HashMap(); + String username = getUsername(); + if (username != null) { + stubProperties.put(BindingProvider.USERNAME_PROPERTY, username); + } + String password = getPassword(); + if (password != null) { + stubProperties.put(BindingProvider.PASSWORD_PROPERTY, password); + } + String endpointAddress = getEndpointAddress(); + if (endpointAddress != null) { + stubProperties.put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, endpointAddress); + } + if (isMaintainSession()) { + stubProperties.put(BindingProvider.SESSION_MAINTAIN_PROPERTY, Boolean.TRUE); + } + if (isUseSoapAction()) { + stubProperties.put(BindingProvider.SOAPACTION_USE_PROPERTY, Boolean.TRUE); + } + String soapActionUri = getSoapActionUri(); + if (soapActionUri != null) { + stubProperties.put(BindingProvider.SOAPACTION_URI_PROPERTY, soapActionUri); + } + stubProperties.putAll(getCustomProperties()); + if (!stubProperties.isEmpty()) { + if (!(stub instanceof BindingProvider)) { + throw new RemoteLookupFailureException("Port stub of class [" + stub.getClass().getName() + + "] is not a customizable JAX-WS stub: it does not implement interface [javax.xml.ws.BindingProvider]"); + } + ((BindingProvider) stub).getRequestContext().putAll(stubProperties); + } + } + + /** + * Return the underlying JAX-WS port stub that this interceptor delegates to + * for each method invocation on the proxy. + */ + protected Object getPortStub() { + return this.portStub; + } + + + public Object invoke(MethodInvocation invocation) throws Throwable { + if (AopUtils.isToStringMethod(invocation.getMethod())) { + return "JAX-WS proxy for port [" + getPortName() + "] of service [" + getServiceName() + "]"; + } + // Lazily prepare service and stub if necessary. + synchronized (this.preparationMonitor) { + if (!isPrepared()) { + prepare(); + } + } + return doInvoke(invocation); + } + + /** + * Perform a JAX-WS service invocation based on the given method invocation. + * @param invocation the AOP method invocation + * @return the invocation result, if any + * @throws Throwable in case of invocation failure + * @see #getPortStub() + * @see #doInvoke(org.aopalliance.intercept.MethodInvocation, Object) + */ + protected Object doInvoke(MethodInvocation invocation) throws Throwable { + try { + return doInvoke(invocation, getPortStub()); + } + catch (SOAPFaultException ex) { + throw new JaxWsSoapFaultException(ex); + } + catch (ProtocolException ex) { + throw new RemoteConnectFailureException("Could not connect to remote service [" + this.portQName + "]", ex); + } + catch (WebServiceException ex) { + throw new RemoteAccessException("Could not access remote service at [" + this.portQName + "]", ex); + } + } + + /** + * Perform a JAX-WS service invocation on the given port stub. + * @param invocation the AOP method invocation + * @param portStub the RMI port stub to invoke + * @return the invocation result, if any + * @throws Throwable in case of invocation failure + * @see #getPortStub() + */ + protected Object doInvoke(MethodInvocation invocation, Object portStub) throws Throwable { + Method method = invocation.getMethod(); + try { + return method.invoke(portStub, invocation.getArguments()); + } + catch (InvocationTargetException ex) { + throw ex.getTargetException(); + } + catch (Throwable ex) { + throw new RemoteProxyFailureException("Invocation of stub method failed: " + method, ex); + } + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/remoting/jaxws/JaxWsPortProxyFactoryBean.java b/org.springframework.web/src/main/java/org/springframework/remoting/jaxws/JaxWsPortProxyFactoryBean.java new file mode 100644 index 0000000000..7826787594 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/remoting/jaxws/JaxWsPortProxyFactoryBean.java @@ -0,0 +1,72 @@ +/* + * Copyright 2002-2008 the original author or authors. + * + * 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.springframework.remoting.jaxws; + +import javax.xml.ws.BindingProvider; + +import org.springframework.aop.framework.ProxyFactory; +import org.springframework.beans.factory.BeanClassLoaderAware; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.util.ClassUtils; + +/** + * {@link org.springframework.beans.factory.FactoryBean} for a specific port of a + * JAX-WS service. Exposes a proxy for the port, to be used for bean references. + * Inherits configuration properties from {@link JaxWsPortClientInterceptor}. + * + * @author Juergen Hoeller + * @since 2.5 + * @see #setServiceInterface + * @see LocalJaxWsServiceFactoryBean + */ +public class JaxWsPortProxyFactoryBean extends JaxWsPortClientInterceptor + implements FactoryBean, BeanClassLoaderAware { + + private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader(); + + private Object serviceProxy; + + + public void setBeanClassLoader(ClassLoader classLoader) { + this.beanClassLoader = classLoader; + } + + public void afterPropertiesSet() { + super.afterPropertiesSet(); + + // Build a proxy that also exposes the JAX-WS BindingProvider interface. + ProxyFactory pf = new ProxyFactory(); + pf.addInterface(getServiceInterface()); + pf.addInterface(BindingProvider.class); + pf.addAdvice(this); + this.serviceProxy = pf.getProxy(this.beanClassLoader); + } + + + public Object getObject() { + return this.serviceProxy; + } + + public Class getObjectType() { + return getServiceInterface(); + } + + public boolean isSingleton() { + return true; + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/remoting/jaxws/JaxWsSoapFaultException.java b/org.springframework.web/src/main/java/org/springframework/remoting/jaxws/JaxWsSoapFaultException.java new file mode 100644 index 0000000000..038e3c5b19 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/remoting/jaxws/JaxWsSoapFaultException.java @@ -0,0 +1,66 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.remoting.jaxws; + +import javax.xml.namespace.QName; +import javax.xml.soap.SOAPFault; +import javax.xml.ws.soap.SOAPFaultException; + +import org.springframework.remoting.soap.SoapFaultException; + +/** + * Spring SoapFaultException adapter for the JAX-WS + * {@link javax.xml.ws.soap.SOAPFaultException} class. + * + * @author Juergen Hoeller + * @since 2.5 + */ +public class JaxWsSoapFaultException extends SoapFaultException { + + /** + * Constructor for JaxWsSoapFaultException. + * @param original the original JAX-WS SOAPFaultException to wrap + */ + public JaxWsSoapFaultException(SOAPFaultException original) { + super(original.getMessage(), original); + } + + /** + * Return the wrapped JAX-WS SOAPFault. + */ + public final SOAPFault getFault() { + return ((SOAPFaultException) getCause()).getFault(); + } + + + public String getFaultCode() { + return getFault().getFaultCode(); + } + + public QName getFaultCodeAsQName() { + return getFault().getFaultCodeAsQName(); + } + + public String getFaultString() { + return getFault().getFaultString(); + } + + public String getFaultActor() { + return getFault().getFaultActor(); + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/remoting/jaxws/LocalJaxWsServiceFactory.java b/org.springframework.web/src/main/java/org/springframework/remoting/jaxws/LocalJaxWsServiceFactory.java new file mode 100644 index 0000000000..ecab0d89de --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/remoting/jaxws/LocalJaxWsServiceFactory.java @@ -0,0 +1,156 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.remoting.jaxws; + +import java.net.URL; +import java.util.concurrent.Executor; + +import javax.xml.namespace.QName; +import javax.xml.ws.Service; +import javax.xml.ws.handler.HandlerResolver; + +import org.springframework.core.task.TaskExecutor; +import org.springframework.core.task.support.ConcurrentExecutorAdapter; + +/** + * Factory for locally defined JAX-WS {@link javax.xml.ws.Service} references. + * Uses the JAX-WS {@link javax.xml.ws.Service#create} factory API underneath. + * + *

Serves as base class for {@link LocalJaxWsServiceFactoryBean} as well as + * {@link JaxWsPortClientInterceptor} and {@link JaxWsPortProxyFactoryBean}. + * + * @author Juergen Hoeller + * @since 2.5 + * @see javax.xml.ws.Service + * @see LocalJaxWsServiceFactoryBean + * @see JaxWsPortClientInterceptor + * @see JaxWsPortProxyFactoryBean + */ +public class LocalJaxWsServiceFactory { + + private URL wsdlDocumentUrl; + + private String namespaceUri; + + private String serviceName; + + private Executor executor; + + private HandlerResolver handlerResolver; + + + /** + * Set the URL of the WSDL document that describes the service. + */ + public void setWsdlDocumentUrl(URL wsdlDocumentUrl) { + this.wsdlDocumentUrl = wsdlDocumentUrl; + } + + /** + * Return the URL of the WSDL document that describes the service. + */ + public URL getWsdlDocumentUrl() { + return this.wsdlDocumentUrl; + } + + /** + * Set the namespace URI of the service. + * Corresponds to the WSDL "targetNamespace". + */ + public void setNamespaceUri(String namespaceUri) { + this.namespaceUri = (namespaceUri != null ? namespaceUri.trim() : null); + } + + /** + * Return the namespace URI of the service. + */ + public String getNamespaceUri() { + return this.namespaceUri; + } + + /** + * Set the name of the service to look up. + * Corresponds to the "wsdl:service" name. + */ + public void setServiceName(String serviceName) { + this.serviceName = serviceName; + } + + /** + * Return the name of the service. + */ + public String getServiceName() { + return this.serviceName; + } + + /** + * Set the JDK concurrent executor to use for asynchronous executions + * that require callbacks. + * @see javax.xml.ws.Service#setExecutor + */ + public void setExecutor(Executor executor) { + this.executor = executor; + } + + /** + * Set the Spring TaskExecutor to use for asynchronous executions + * that require callbacks. + * @see javax.xml.ws.Service#setExecutor + */ + public void setTaskExecutor(TaskExecutor executor) { + this.executor = new ConcurrentExecutorAdapter(executor); + } + + /** + * Set the JAX-WS HandlerResolver to use for all proxies and dispatchers + * created through this factory. + * @see javax.xml.ws.Service#setHandlerResolver + */ + public void setHandlerResolver(HandlerResolver handlerResolver) { + this.handlerResolver = handlerResolver; + } + + + /** + * Create a JAX-WS Service according to the parameters of this factory. + * @see #setServiceName + * @see #setWsdlDocumentUrl + */ + public Service createJaxWsService() { + Service service = (this.wsdlDocumentUrl != null ? + Service.create(this.wsdlDocumentUrl, getQName(this.serviceName)) : + Service.create(getQName(this.serviceName))); + + if (this.executor != null) { + service.setExecutor(this.executor); + } + if (this.handlerResolver != null) { + service.setHandlerResolver(this.handlerResolver); + } + return service; + } + + /** + * Return a QName for the given name, relative to the namespace URI + * of this factory, if given. + * @see #setNamespaceUri + */ + protected QName getQName(String name) { + return (getNamespaceUri() != null ? new QName(getNamespaceUri(), name) : new QName(name)); + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/remoting/jaxws/LocalJaxWsServiceFactoryBean.java b/org.springframework.web/src/main/java/org/springframework/remoting/jaxws/LocalJaxWsServiceFactoryBean.java new file mode 100644 index 0000000000..ab18e3a3fe --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/remoting/jaxws/LocalJaxWsServiceFactoryBean.java @@ -0,0 +1,59 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.remoting.jaxws; + +import javax.xml.ws.Service; + +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.InitializingBean; + +/** + * {@link org.springframework.beans.factory.FactoryBean} for locally + * defined JAX-WS Service references. + * Uses {@link LocalJaxWsServiceFactory}'s facilities underneath. + * + *

Alternatively, JAX-WS Service references can be looked up + * in the JNDI environment of the J2EE container. + * + * @author Juergen Hoeller + * @since 2.5 + * @see javax.xml.ws.Service + * @see org.springframework.jndi.JndiObjectFactoryBean + * @see JaxWsPortProxyFactoryBean + */ +public class LocalJaxWsServiceFactoryBean extends LocalJaxWsServiceFactory implements FactoryBean, InitializingBean { + + private Service service; + + + public void afterPropertiesSet() { + this.service = createJaxWsService(); + } + + public Object getObject() throws Exception { + return this.service; + } + + public Class getObjectType() { + return (this.service != null ? this.service.getClass() : Service.class); + } + + public boolean isSingleton() { + return true; + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/remoting/jaxws/SimpleHttpServerJaxWsServiceExporter.java b/org.springframework.web/src/main/java/org/springframework/remoting/jaxws/SimpleHttpServerJaxWsServiceExporter.java new file mode 100644 index 0000000000..9999c0384e --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/remoting/jaxws/SimpleHttpServerJaxWsServiceExporter.java @@ -0,0 +1,186 @@ +/* + * Copyright 2002-2008 the original author or authors. + * + * 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.springframework.remoting.jaxws; + +import java.net.InetSocketAddress; +import java.util.List; + +import javax.jws.WebService; +import javax.xml.ws.Endpoint; + +import com.sun.net.httpserver.Authenticator; +import com.sun.net.httpserver.Filter; +import com.sun.net.httpserver.HttpContext; +import com.sun.net.httpserver.HttpServer; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Simple exporter for JAX-WS services, autodetecting annotated service beans + * (through the JAX-WS {@link javax.jws.WebService} annotation) and exporting + * them through the HTTP server included in Sun's JDK 1.6. The full address + * for each service will consist of the server's base address with the + * service name appended (e.g. "http://localhost:8080/OrderService"). + * + *

Note that this exporter will only work on Sun's JDK 1.6 or higher, as well + * as on JDKs that ship Sun's entire class library as included in the Sun JDK. + * For a portable JAX-WS exporter, have a look at {@link SimpleJaxWsServiceExporter}. + * + * @author Juergen Hoeller + * @since 2.5.5 + * @see javax.jws.WebService + * @see javax.xml.ws.Endpoint#publish(Object) + * @see SimpleJaxWsServiceExporter + */ +public class SimpleHttpServerJaxWsServiceExporter extends AbstractJaxWsServiceExporter { + + protected final Log logger = LogFactory.getLog(getClass()); + + private HttpServer server; + + private int port = 8080; + + private String hostname; + + private int backlog = -1; + + private int shutdownDelay = 0; + + private String basePath = "/"; + + private List filters; + + private Authenticator authenticator; + + private boolean localServer = false; + + + /** + * Specify an existing HTTP server to register the web service contexts + * with. This will typically be a server managed by the general Spring + * {@link org.springframework.remoting.support.SimpleHttpServerFactoryBean}. + *

Alternatively, configure a local HTTP server through the + * {@link #setPort "port"}, {@link #setHostname "hostname"} and + * {@link #setBacklog "backlog"} properties (or rely on the defaults there). + */ + public void setServer(HttpServer server) { + this.server = server; + } + + /** + * Specify the HTTP server's port. Default is 8080. + *

Only applicable for a locally configured HTTP server. + * Ignored when the {@link #setServer "server"} property has been specified. + */ + public void setPort(int port) { + this.port = port; + } + + /** + * Specify the HTTP server's hostname to bind to. Default is localhost; + * can be overridden with a specific network address to bind to. + *

Only applicable for a locally configured HTTP server. + * Ignored when the {@link #setServer "server"} property has been specified. + */ + public void setHostname(String hostname) { + this.hostname = hostname; + } + + /** + * Specify the HTTP server's TCP backlog. Default is -1, + * indicating the system's default value. + *

Only applicable for a locally configured HTTP server. + * Ignored when the {@link #setServer "server"} property has been specified. + */ + public void setBacklog(int backlog) { + this.backlog = backlog; + } + + /** + * Specify the number of seconds to wait until HTTP exchanges have + * completed when shutting down the HTTP server. Default is 0. + *

Only applicable for a locally configured HTTP server. + * Ignored when the {@link #setServer "server"} property has been specified. + */ + public void setShutdownDelay(int shutdownDelay) { + this.shutdownDelay = shutdownDelay; + } + + /** + * Set the base path for context publication. Default is "/". + *

For each context publication path, the service name will be + * appended to this base address. E.g. service name "OrderService" + * -> "/OrderService". + * @see javax.xml.ws.Endpoint#publish(Object) + * @see javax.jws.WebService#serviceName() + */ + public void setBasePath(String basePath) { + this.basePath = basePath; + } + + /** + * Register common {@link com.sun.net.httpserver.Filter Filters} to be + * applied to all detected {@link javax.jws.WebService} annotated beans. + */ + public void setFilters(List filters) { + this.filters = filters; + } + + /** + * Register a common {@link com.sun.net.httpserver.Authenticator} to be + * applied to all detected {@link javax.jws.WebService} annotated beans. + */ + public void setAuthenticator(Authenticator authenticator) { + this.authenticator = authenticator; + } + + + public void afterPropertiesSet() throws Exception { + if (this.server == null) { + InetSocketAddress address = (this.hostname != null ? + new InetSocketAddress(this.hostname, this.port) : new InetSocketAddress(this.port)); + this.server = HttpServer.create(address, this.backlog); + if (this.logger.isInfoEnabled()) { + this.logger.info("Starting HttpServer at address " + address); + } + this.server.start(); + this.localServer = true; + } + super.afterPropertiesSet(); + } + + protected void publishEndpoint(Endpoint endpoint, WebService annotation) { + String fullPath = this.basePath + annotation.serviceName(); + HttpContext httpContext = this.server.createContext(fullPath); + if (this.filters != null) { + httpContext.getFilters().addAll(this.filters); + } + if (this.authenticator != null) { + httpContext.setAuthenticator(this.authenticator); + } + endpoint.publish(httpContext); + } + + public void destroy() { + super.destroy(); + if (this.localServer) { + logger.info("Stopping HttpServer"); + this.server.stop(this.shutdownDelay); + } + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/remoting/jaxws/SimpleJaxWsServiceExporter.java b/org.springframework.web/src/main/java/org/springframework/remoting/jaxws/SimpleJaxWsServiceExporter.java new file mode 100644 index 0000000000..3a7567be25 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/remoting/jaxws/SimpleJaxWsServiceExporter.java @@ -0,0 +1,70 @@ +/* + * Copyright 2002-2008 the original author or authors. + * + * 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.springframework.remoting.jaxws; + +import javax.jws.WebService; +import javax.xml.ws.Endpoint; + +/** + * Simple exporter for JAX-WS services, autodetecting annotated service beans + * (through the JAX-WS {@link javax.jws.WebService} annotation) and exporting + * them with a configured base address (by default "http://localhost:8080/") + * using the JAX-WS provider's built-in publication support. The full address + * for each service will consist of the base address with the service name + * appended (e.g. "http://localhost:8080/OrderService"). + * + *

Note that this exporter will only work if the JAX-WS runtime actually + * supports publishing with an address argument, i.e. if the JAX-WS runtime + * ships an internal HTTP server. This is the case with the JAX-WS runtime + * that's inclued in Sun's JDK 1.6 but not with the standalone JAX-WS 2.1 RI. + * + *

For explicit configuration of JAX-WS endpoints with Sun's JDK 1.6 + * HTTP server, consider using {@link SimpleHttpServerJaxWsServiceExporter}! + * + * @author Juergen Hoeller + * @since 2.5 + * @see javax.jws.WebService + * @see javax.xml.ws.Endpoint#publish(String) + * @see SimpleHttpServerJaxWsServiceExporter + */ +public class SimpleJaxWsServiceExporter extends AbstractJaxWsServiceExporter { + + public static final String DEFAULT_BASE_ADDRESS = "http://localhost:8080/"; + + private String baseAddress = DEFAULT_BASE_ADDRESS; + + + /** + * Set the base address for exported services. + * Default is "http://localhost:8080/". + *

For each actual publication address, the service name will be + * appended to this base address. E.g. service name "OrderService" + * -> "http://localhost:8080/OrderService". + * @see javax.xml.ws.Endpoint#publish(String) + * @see javax.jws.WebService#serviceName() + */ + public void setBaseAddress(String baseAddress) { + this.baseAddress = baseAddress; + } + + + protected void publishEndpoint(Endpoint endpoint, WebService annotation) { + String fullAddress = this.baseAddress + annotation.serviceName(); + endpoint.publish(fullAddress); + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/remoting/jaxws/package.html b/org.springframework.web/src/main/java/org/springframework/remoting/jaxws/package.html new file mode 100644 index 0000000000..5f61a3e629 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/remoting/jaxws/package.html @@ -0,0 +1,9 @@ + + + +Remoting classes for Web Services via JAX-WS (the successor of JAX-RPC), +as included in Java 6 and Java EE 5. This package provides proxy +factories for accessing JAX-WS services and ports. + + + diff --git a/org.springframework.web/src/main/java/org/springframework/web/HttpRequestHandler.java b/org.springframework.web/src/main/java/org/springframework/web/HttpRequestHandler.java new file mode 100644 index 0000000000..6fe84204a0 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/HttpRequestHandler.java @@ -0,0 +1,90 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.web; + +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * Plain handler interface for components that process HTTP requests, + * analogous to a Servlet. Only declares {@link javax.servlet.ServletException} + * and {@link java.io.IOException}, to allow for usage within any + * {@link javax.servlet.http.HttpServlet}}. This interface is ssentially the + * direct equivalent of an HttpServlet, reduced to a central handle method. + * + *

The easiest way to expose an HttpRequestHandler bean in Spring style + * is to define it in Spring's root web application context and define + * an {@link org.springframework.web.context.support.HttpRequestHandlerServlet} + * in web.xml, pointing at the target HttpRequestHandler bean + * through its servlet-name which needs to match the target bean name. + * + *

Supported as a handler type within Spring's + * {@link org.springframework.web.servlet.DispatcherServlet}, being able + * to interact with the dispatcher's advanced mapping and interception + * facilities. This is the recommended way of exposing an HttpRequestHandler, + * while keeping the handler implementations free of direct dependencies + * on a DispatcherServlet environment. + * + *

Typically implemented to generate binary responses directly, + * with no separate view resource involved. This differentiates it from a + * {@link org.springframework.web.servlet.mvc.Controller} within Spring's Web MVC + * framework. The lack of a {@link org.springframework.web.servlet.ModelAndView} + * return value gives a clearer signature to callers other than the + * DispatcherServlet, indicating that there will never be a view to render. + * + *

As of Spring 2.0, Spring's HTTP-based remote exporters, such as + * {@link org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter} + * and {@link org.springframework.remoting.caucho.HessianServiceExporter}, + * implement this interface rather than the more extensive Controller interface, + * for minimal dependencies on Spring-specific web infrastructure. + * + *

Note that HttpRequestHandlers may optionally implement the + * {@link org.springframework.web.servlet.mvc.LastModified} interface, + * just like Controllers can, provided that they run within Spring's + * DispatcherServlet. However, this is usually not necessary, since + * HttpRequestHandlers typically only support POST requests to begin with. + * Alternatively, a handler may implement the "If-Modified-Since" HTTP + * header processing manually within its handle method. + * + * @author Juergen Hoeller + * @since 2.0 + * @see org.springframework.web.context.support.HttpRequestHandlerServlet + * @see org.springframework.web.servlet.DispatcherServlet + * @see org.springframework.web.servlet.ModelAndView + * @see org.springframework.web.servlet.mvc.Controller + * @see org.springframework.web.servlet.mvc.LastModified + * @see org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter + * @see org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter + * @see org.springframework.remoting.caucho.HessianServiceExporter + * @see org.springframework.remoting.caucho.BurlapServiceExporter + */ +public interface HttpRequestHandler { + + /** + * Process the given request, generating a response. + * @param request current HTTP request + * @param response current HTTP response + * @throws ServletException in case of general errors + * @throws IOException in case of I/O errors + */ + void handleRequest(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException; + +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/HttpRequestMethodNotSupportedException.java b/org.springframework.web/src/main/java/org/springframework/web/HttpRequestMethodNotSupportedException.java new file mode 100644 index 0000000000..de5d672820 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/HttpRequestMethodNotSupportedException.java @@ -0,0 +1,88 @@ +/* + * Copyright 2002-2008 the original author or authors. + * + * 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.springframework.web; + +import javax.servlet.ServletException; + +/** + * Exception thrown when a request handler does not support a + * specific request method. + * + * @author Juergen Hoeller + * @since 2.0 + */ +public class HttpRequestMethodNotSupportedException extends ServletException { + + private String method; + + private String[] supportedMethods; + + + /** + * Create a new HttpRequestMethodNotSupportedException. + * @param method the unsupported HTTP request method + */ + public HttpRequestMethodNotSupportedException(String method) { + this(method, (String[]) null); + } + + /** + * Create a new HttpRequestMethodNotSupportedException. + * @param method the unsupported HTTP request method + * @param supportedMethods the actually supported HTTP methods + */ + public HttpRequestMethodNotSupportedException(String method, String[] supportedMethods) { + this(method, supportedMethods, "Request method '" + method + "' not supported"); + } + + /** + * Create a new HttpRequestMethodNotSupportedException. + * @param method the unsupported HTTP request method + * @param msg the detail message + */ + public HttpRequestMethodNotSupportedException(String method, String msg) { + this(method, null, msg); + } + + /** + * Create a new HttpRequestMethodNotSupportedException. + * @param method the unsupported HTTP request method + * @param supportedMethods the actually supported HTTP methods + * @param msg the detail message + */ + public HttpRequestMethodNotSupportedException(String method, String[] supportedMethods, String msg) { + super(msg); + this.method = method; + this.supportedMethods = supportedMethods; + } + + + /** + * Return the HTTP request method that caused the failure. + */ + public String getMethod() { + return this.method; + } + + /** + * Return the actually supported HTTP methods, if known. + */ + public String[] getSupportedMethods() { + return this.supportedMethods; + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/HttpSessionRequiredException.java b/org.springframework.web/src/main/java/org/springframework/web/HttpSessionRequiredException.java new file mode 100644 index 0000000000..1f056a21ab --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/HttpSessionRequiredException.java @@ -0,0 +1,37 @@ +/* + * Copyright 2002-2006 the original author or authors. + * + * 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.springframework.web; + +import javax.servlet.ServletException; + +/** + * Exception thrown when an HTTP request handler requires a pre-existing session. + * + * @author Juergen Hoeller + * @since 2.0 + */ +public class HttpSessionRequiredException extends ServletException { + + /** + * Create a new HttpSessionRequiredException. + * @param msg the detail message + */ + public HttpSessionRequiredException(String msg) { + super(msg); + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/context/ConfigurableWebApplicationContext.java b/org.springframework.web/src/main/java/org/springframework/web/context/ConfigurableWebApplicationContext.java new file mode 100644 index 0000000000..119703c035 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/context/ConfigurableWebApplicationContext.java @@ -0,0 +1,95 @@ +/* + * Copyright 2002-2008 the original author or authors. + * + * 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.springframework.web.context; + +import javax.servlet.ServletConfig; +import javax.servlet.ServletContext; + +import org.springframework.context.ConfigurableApplicationContext; + +/** + * Interface to be implemented by configurable web application contexts. + * Supported by {@link ContextLoader} and + * {@link org.springframework.web.servlet.FrameworkServlet}. + * + *

Note: The setters of this interface need to be called before an + * invocation of the {@link #refresh} method inherited from + * {@link org.springframework.context.ConfigurableApplicationContext}. + * They do not cause an initialization of the context on their own. + * + * @author Juergen Hoeller + * @since 05.12.2003 + * @see #refresh + * @see ContextLoader#createWebApplicationContext + * @see org.springframework.web.servlet.FrameworkServlet#createWebApplicationContext + */ +public interface ConfigurableWebApplicationContext extends WebApplicationContext, ConfigurableApplicationContext { + + /** + * Set the ServletContext for this web application context. + *

Does not cause an initialization of the context: refresh needs to be + * called after the setting of all configuration properties. + * @see #refresh() + */ + void setServletContext(ServletContext servletContext); + + /** + * Set the ServletConfig for this web application context. + * Only called for a WebApplicationContext that belongs to a specific Servlet. + * @see #refresh() + */ + void setServletConfig(ServletConfig servletConfig); + + /** + * Return the ServletConfig for this web application context, if any. + */ + ServletConfig getServletConfig(); + + /** + * Set the namespace for this web application context, + * to be used for building a default context config location. + * The root web application context does not have a namespace. + */ + void setNamespace(String namespace); + + /** + * Return the namespace for this web application context, if any. + */ + String getNamespace(); + + /** + * Set the config locations for this web application context in init-param style, + * i.e. with distinct locations separated by commas, semicolons or whitespace. + *

If not set, the implementation is supposed to use a default for the + * given namespace or the root web application context, as appropriate. + */ + void setConfigLocation(String configLocation); + + /** + * Set the config locations for this web application context. + *

If not set, the implementation is supposed to use a default for the + * given namespace or the root web application context, as appropriate. + */ + void setConfigLocations(String[] configLocations); + + /** + * Return the config locations for this web application context, + * or null if none specified. + */ + String[] getConfigLocations(); + +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/context/ContextLoader.java b/org.springframework.web/src/main/java/org/springframework/web/context/ContextLoader.java new file mode 100644 index 0000000000..4f0c26f220 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/context/ContextLoader.java @@ -0,0 +1,386 @@ +/* + * Copyright 2002-2008 the original author or authors. + * + * 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.springframework.web.context; + +import java.io.IOException; +import java.util.Map; +import java.util.Properties; + +import javax.servlet.ServletContext; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.beans.BeanUtils; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.access.BeanFactoryLocator; +import org.springframework.beans.factory.access.BeanFactoryReference; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextException; +import org.springframework.context.access.ContextSingletonBeanFactoryLocator; +import org.springframework.core.CollectionFactory; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.support.PropertiesLoaderUtils; +import org.springframework.util.ClassUtils; + +/** + * Performs the actual initialization work for the root application context. + * Called by {@link ContextLoaderListener} and {@link ContextLoaderServlet}. + * + *

Looks for a {@link #CONTEXT_CLASS_PARAM "contextClass"} parameter + * at the web.xml context-param level to specify the context + * class type, falling back to the default of + * {@link org.springframework.web.context.support.XmlWebApplicationContext} + * if not found. With the default ContextLoader implementation, any context class + * specified needs to implement the ConfigurableWebApplicationContext interface. + * + *

Processes a {@link #CONFIG_LOCATION_PARAM "contextConfigLocation"} + * context-param and passes its value to the context instance, parsing it into + * potentially multiple file paths which can be separated by any number of + * commas and spaces, e.g. "WEB-INF/applicationContext1.xml, + * WEB-INF/applicationContext2.xml". Ant-style path patterns are supported as well, + * e.g. "WEB-INF/*Context.xml,WEB-INF/spring*.xml" or "WEB-INF/**/*Context.xml". + * If not explicitly specified, the context implementation is supposed to use a + * default location (with XmlWebApplicationContext: "/WEB-INF/applicationContext.xml"). + * + *

Note: In case of multiple config locations, later bean definitions will + * override ones defined in previously loaded files, at least when using one of + * Spring's default ApplicationContext implementations. This can be leveraged + * to deliberately override certain bean definitions via an extra XML file. + * + *

Above and beyond loading the root application context, this class + * can optionally load or obtain and hook up a shared parent context to + * the root application context. See the + * {@link #loadParentContext(ServletContext)} method for more information. + * + * @author Juergen Hoeller + * @author Colin Sampaleanu + * @author Sam Brannen + * @since 17.02.2003 + * @see ContextLoaderListener + * @see ContextLoaderServlet + * @see ConfigurableWebApplicationContext + * @see org.springframework.web.context.support.XmlWebApplicationContext + */ +public class ContextLoader { + + /** + * Config param for the root WebApplicationContext implementation class to + * use: "contextClass" + */ + public static final String CONTEXT_CLASS_PARAM = "contextClass"; + + /** + * Name of servlet context parameter (i.e., "contextConfigLocation") + * that can specify the config location for the root context, falling back + * to the implementation's default otherwise. + * @see org.springframework.web.context.support.XmlWebApplicationContext#DEFAULT_CONFIG_LOCATION + */ + public static final String CONFIG_LOCATION_PARAM = "contextConfigLocation"; + + /** + * Optional servlet context parameter (i.e., "locatorFactorySelector") + * used only when obtaining a parent context using the default implementation + * of {@link #loadParentContext(ServletContext servletContext)}. + * Specifies the 'selector' used in the + * {@link ContextSingletonBeanFactoryLocator#getInstance(String selector)} + * method call, which is used to obtain the BeanFactoryLocator instance from + * which the parent context is obtained. + *

The default is classpath*:beanRefContext.xml, + * matching the default applied for the + * {@link ContextSingletonBeanFactoryLocator#getInstance()} method. + * Supplying the "parentContextKey" parameter is sufficient in this case. + */ + public static final String LOCATOR_FACTORY_SELECTOR_PARAM = "locatorFactorySelector"; + + /** + * Optional servlet context parameter (i.e., "parentContextKey") + * used only when obtaining a parent context using the default implementation + * of {@link #loadParentContext(ServletContext servletContext)}. + * Specifies the 'factoryKey' used in the + * {@link BeanFactoryLocator#useBeanFactory(String factoryKey)} method call, + * obtaining the parent application context from the BeanFactoryLocator instance. + *

Supplying this "parentContextKey" parameter is sufficient when relying + * on the default classpath*:beanRefContext.xml selector for + * candidate factory references. + */ + public static final String LOCATOR_FACTORY_KEY_PARAM = "parentContextKey"; + + /** + * Name of the class path resource (relative to the ContextLoader class) + * that defines ContextLoader's default strategy names. + */ + private static final String DEFAULT_STRATEGIES_PATH = "ContextLoader.properties"; + + + private static final Properties defaultStrategies; + + static { + // Load default strategy implementations from properties file. + // This is currently strictly internal and not meant to be customized + // by application developers. + try { + ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class); + defaultStrategies = PropertiesLoaderUtils.loadProperties(resource); + } + catch (IOException ex) { + throw new IllegalStateException("Could not load 'ContextLoader.properties': " + ex.getMessage()); + } + } + + + private static final Log logger = LogFactory.getLog(ContextLoader.class); + + /** + * Map from (thread context) ClassLoader to WebApplicationContext. + * Often just holding one reference - if the ContextLoader class is + * deployed in the web app ClassLoader itself! + */ + private static final Map currentContextPerThread = CollectionFactory.createConcurrentMapIfPossible(1); + + /** + * The root WebApplicationContext instance that this loader manages. + */ + private WebApplicationContext context; + + /** + * Holds BeanFactoryReference when loading parent factory via + * ContextSingletonBeanFactoryLocator. + */ + private BeanFactoryReference parentContextRef; + + + /** + * Initialize Spring's web application context for the given servlet context, + * according to the "{@link #CONTEXT_CLASS_PARAM contextClass}" and + * "{@link #CONFIG_LOCATION_PARAM contextConfigLocation}" context-params. + * @param servletContext current servlet context + * @return the new WebApplicationContext + * @throws IllegalStateException if there is already a root application context present + * @throws BeansException if the context failed to initialize + * @see #CONTEXT_CLASS_PARAM + * @see #CONFIG_LOCATION_PARAM + */ + public WebApplicationContext initWebApplicationContext(ServletContext servletContext) + throws IllegalStateException, BeansException { + + if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) { + throw new IllegalStateException( + "Cannot initialize context because there is already a root application context present - " + + "check whether you have multiple ContextLoader* definitions in your web.xml!"); + } + + servletContext.log("Initializing Spring root WebApplicationContext"); + if (logger.isInfoEnabled()) { + logger.info("Root WebApplicationContext: initialization started"); + } + long startTime = System.currentTimeMillis(); + + try { + // Determine parent for root web application context, if any. + ApplicationContext parent = loadParentContext(servletContext); + + // Store context in local instance variable, to guarantee that + // it is available on ServletContext shutdown. + this.context = createWebApplicationContext(servletContext, parent); + servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context); + currentContextPerThread.put(Thread.currentThread().getContextClassLoader(), this.context); + + if (logger.isDebugEnabled()) { + logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" + + WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]"); + } + if (logger.isInfoEnabled()) { + long elapsedTime = System.currentTimeMillis() - startTime; + logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms"); + } + + return this.context; + } + catch (RuntimeException ex) { + logger.error("Context initialization failed", ex); + servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex); + throw ex; + } + catch (Error err) { + logger.error("Context initialization failed", err); + servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err); + throw err; + } + } + + /** + * Instantiate the root WebApplicationContext for this loader, either the + * default context class or a custom context class if specified. + *

This implementation expects custom contexts to implement the + * {@link ConfigurableWebApplicationContext} interface. + * Can be overridden in subclasses. + *

In addition, {@link #customizeContext} gets called prior to refreshing the + * context, allowing subclasses to perform custom modifications to the context. + * @param servletContext current servlet context + * @param parent the parent ApplicationContext to use, or null if none + * @return the root WebApplicationContext + * @throws BeansException if the context couldn't be initialized + * @see ConfigurableWebApplicationContext + */ + protected WebApplicationContext createWebApplicationContext( + ServletContext servletContext, ApplicationContext parent) throws BeansException { + + Class contextClass = determineContextClass(servletContext); + if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) { + throw new ApplicationContextException("Custom context class [" + contextClass.getName() + + "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]"); + } + + ConfigurableWebApplicationContext wac = + (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass); + wac.setParent(parent); + wac.setServletContext(servletContext); + wac.setConfigLocation(servletContext.getInitParameter(CONFIG_LOCATION_PARAM)); + customizeContext(servletContext, wac); + wac.refresh(); + + return wac; + } + + /** + * Return the WebApplicationContext implementation class to use, either the + * default XmlWebApplicationContext or a custom context class if specified. + * @param servletContext current servlet context + * @return the WebApplicationContext implementation class to use + * @throws ApplicationContextException if the context class couldn't be loaded + * @see #CONTEXT_CLASS_PARAM + * @see org.springframework.web.context.support.XmlWebApplicationContext + */ + protected Class determineContextClass(ServletContext servletContext) throws ApplicationContextException { + String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM); + if (contextClassName != null) { + try { + return ClassUtils.forName(contextClassName); + } + catch (ClassNotFoundException ex) { + throw new ApplicationContextException( + "Failed to load custom context class [" + contextClassName + "]", ex); + } + } + else { + contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName()); + try { + return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader()); + } + catch (ClassNotFoundException ex) { + throw new ApplicationContextException( + "Failed to load default context class [" + contextClassName + "]", ex); + } + } + } + + /** + * Customize the {@link ConfigurableWebApplicationContext} created by this + * ContextLoader after config locations have been supplied to the context + * but before the context is refreshed. + *

The default implementation is empty but can be overridden in subclasses + * to customize the application context. + * @param servletContext the current servlet context + * @param applicationContext the newly created application context + * @see #createWebApplicationContext(ServletContext, ApplicationContext) + */ + protected void customizeContext( + ServletContext servletContext, ConfigurableWebApplicationContext applicationContext) { + } + + /** + * Template method with default implementation (which may be overridden by a + * subclass), to load or obtain an ApplicationContext instance which will be + * used as the parent context of the root WebApplicationContext. If the + * return value from the method is null, no parent context is set. + *

The main reason to load a parent context here is to allow multiple root + * web application contexts to all be children of a shared EAR context, or + * alternately to also share the same parent context that is visible to + * EJBs. For pure web applications, there is usually no need to worry about + * having a parent context to the root web application context. + *

The default implementation uses + * {@link org.springframework.context.access.ContextSingletonBeanFactoryLocator}, + * configured via {@link #LOCATOR_FACTORY_SELECTOR_PARAM} and + * {@link #LOCATOR_FACTORY_KEY_PARAM}, to load a parent context + * which will be shared by all other users of ContextsingletonBeanFactoryLocator + * which also use the same configuration parameters. + * @param servletContext current servlet context + * @return the parent application context, or null if none + * @throws BeansException if the context couldn't be initialized + * @see org.springframework.context.access.ContextSingletonBeanFactoryLocator + */ + protected ApplicationContext loadParentContext(ServletContext servletContext) + throws BeansException { + + ApplicationContext parentContext = null; + String locatorFactorySelector = servletContext.getInitParameter(LOCATOR_FACTORY_SELECTOR_PARAM); + String parentContextKey = servletContext.getInitParameter(LOCATOR_FACTORY_KEY_PARAM); + + if (parentContextKey != null) { + // locatorFactorySelector may be null, indicating the default "classpath*:beanRefContext.xml" + BeanFactoryLocator locator = ContextSingletonBeanFactoryLocator.getInstance(locatorFactorySelector); + if (logger.isDebugEnabled()) { + logger.debug("Getting parent context definition: using parent context key of '" + + parentContextKey + "' with BeanFactoryLocator"); + } + this.parentContextRef = locator.useBeanFactory(parentContextKey); + parentContext = (ApplicationContext) this.parentContextRef.getFactory(); + } + + return parentContext; + } + + /** + * Close Spring's web application context for the given servlet context. If + * the default {@link #loadParentContext(ServletContext)} implementation, + * which uses ContextSingletonBeanFactoryLocator, has loaded any shared + * parent context, release one reference to that shared parent context. + *

If overriding {@link #loadParentContext(ServletContext)}, you may have + * to override this method as well. + * @param servletContext the ServletContext that the WebApplicationContext runs in + */ + public void closeWebApplicationContext(ServletContext servletContext) { + servletContext.log("Closing Spring root WebApplicationContext"); + try { + if (this.context instanceof ConfigurableWebApplicationContext) { + ((ConfigurableWebApplicationContext) this.context).close(); + } + } + finally { + currentContextPerThread.remove(Thread.currentThread().getContextClassLoader()); + servletContext.removeAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE); + if (this.parentContextRef != null) { + this.parentContextRef.release(); + } + } + } + + + /** + * Obtain the Spring root web application context for the current thread + * (i.e. for the current thread's context ClassLoader, which needs to be + * the web application's ClassLoader). + * @return the current root web application context, or null + * if none found + * @see org.springframework.web.context.support.SpringBeanAutowiringSupport + */ + public static WebApplicationContext getCurrentWebApplicationContext() { + return (WebApplicationContext) currentContextPerThread.get(Thread.currentThread().getContextClassLoader()); + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/context/ContextLoader.properties b/org.springframework.web/src/main/java/org/springframework/web/context/ContextLoader.properties new file mode 100644 index 0000000000..6cd24b294a --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/context/ContextLoader.properties @@ -0,0 +1,5 @@ +# Default WebApplicationContext implementation class for ContextLoader. +# Used as fallback when no explicit context implementation has been specified as context-param. +# Not meant to be customized by application developers. + +org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext diff --git a/org.springframework.web/src/main/java/org/springframework/web/context/ContextLoaderListener.java b/org.springframework.web/src/main/java/org/springframework/web/context/ContextLoaderListener.java new file mode 100644 index 0000000000..d754d73ffb --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/context/ContextLoaderListener.java @@ -0,0 +1,74 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.web.context; + +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; + +/** + * Bootstrap listener to start up Spring's root {@link WebApplicationContext}. + * Simply delegates to {@link ContextLoader}. + * + *

This listener should be registered after + * {@link org.springframework.web.util.Log4jConfigListener} + * in web.xml, if the latter is used. + * + * @author Juergen Hoeller + * @since 17.02.2003 + * @see ContextLoaderServlet + * @see org.springframework.web.util.Log4jConfigListener + */ +public class ContextLoaderListener implements ServletContextListener { + + private ContextLoader contextLoader; + + + /** + * Initialize the root web application context. + */ + public void contextInitialized(ServletContextEvent event) { + this.contextLoader = createContextLoader(); + this.contextLoader.initWebApplicationContext(event.getServletContext()); + } + + /** + * Create the ContextLoader to use. Can be overridden in subclasses. + * @return the new ContextLoader + */ + protected ContextLoader createContextLoader() { + return new ContextLoader(); + } + + /** + * Return the ContextLoader used by this listener. + * @return the current ContextLoader + */ + public ContextLoader getContextLoader() { + return this.contextLoader; + } + + + /** + * Close the root web application context. + */ + public void contextDestroyed(ServletContextEvent event) { + if (this.contextLoader != null) { + this.contextLoader.closeWebApplicationContext(event.getServletContext()); + } + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/context/ContextLoaderServlet.java b/org.springframework.web/src/main/java/org/springframework/web/context/ContextLoaderServlet.java new file mode 100644 index 0000000000..9359c8849c --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/context/ContextLoaderServlet.java @@ -0,0 +1,129 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.web.context; + +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * Bootstrap servlet to start up Spring's root {@link WebApplicationContext}. + * Simply delegates to {@link ContextLoader}. + * + *

This servlet should have a lower load-on-startup value + * in web.xml than any servlets that access the root web + * application context. + * + *

Note that this class has been deprecated for containers implementing + * Servlet API 2.4 or higher, in favor of {@link ContextLoaderListener}.
+ * According to Servlet 2.4, listeners must be initialized before load-on-startup + * servlets. Many Servlet 2.3 containers already enforce this behavior. If you + * use such a container, this servlet can be replaced with ContextLoaderListener. + * + *

Servlet 2.3 containers known to work with bootstrap listeners are: + *

+ * For working with any of them, ContextLoaderListener is recommended. + * + *

Servlet 2.3 containers known not to work with bootstrap listeners are: + *

+ * If you happen to work with such a server, this servlet has to be used. + * + *

So unfortunately, the only context initialization option that is compatible + * with all Servlet 2.3 containers is this servlet. + * + *

Note that a startup failure of this servlet will not stop the rest of the + * web application from starting, in contrast to a listener failure. This can + * lead to peculiar side effects if other servlets get started that depend on + * initialization of the root web application context. + * + * @author Juergen Hoeller + * @author Darren Davison + * @see ContextLoaderListener + * @see org.springframework.web.util.Log4jConfigServlet + */ +public class ContextLoaderServlet extends HttpServlet { + + private ContextLoader contextLoader; + + + /** + * Initialize the root web application context. + */ + public void init() throws ServletException { + this.contextLoader = createContextLoader(); + this.contextLoader.initWebApplicationContext(getServletContext()); + } + + /** + * Create the ContextLoader to use. Can be overridden in subclasses. + * @return the new ContextLoader + */ + protected ContextLoader createContextLoader() { + return new ContextLoader(); + } + + /** + * Return the ContextLoader used by this servlet. + * @return the current ContextLoader + */ + public ContextLoader getContextLoader() { + return this.contextLoader; + } + + + /** + * Close the root web application context. + */ + public void destroy() { + if (this.contextLoader != null) { + this.contextLoader.closeWebApplicationContext(getServletContext()); + } + } + + + /** + * This should never even be called since no mapping to this servlet should + * ever be created in web.xml. That's why a correctly invoked Servlet 2.3 + * listener is much more appropriate for initialization work ;-) + */ + public void service(HttpServletRequest request, HttpServletResponse response) throws IOException { + getServletContext().log( + "Attempt to call service method on ContextLoaderServlet as [" + + request.getRequestURI() + "] was ignored"); + response.sendError(HttpServletResponse.SC_BAD_REQUEST); + } + + + public String getServletInfo() { + return "ContextLoaderServlet for Servlet API 2.3 " + + "(deprecated in favor of ContextLoaderListener for Servlet API 2.4)"; + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/context/ServletConfigAware.java b/org.springframework.web/src/main/java/org/springframework/web/context/ServletConfigAware.java new file mode 100644 index 0000000000..196e3db434 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/context/ServletConfigAware.java @@ -0,0 +1,48 @@ +/* + * Copyright 2002-2006 the original author or authors. + * + * 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.springframework.web.context; + +import javax.servlet.ServletConfig; + +/** + * Interface to be implemented by any object that wishes to be notified + * of the ServletConfig (typically determined by the WebApplicationContext) + * that it runs in. + * + *

Only satisfied if actually running within a Servlet-specific + * WebApplicationContext. If this callback interface is encountered + * elsewhere, an exception will be thrown on bean creation. + * + * @author Juergen Hoeller + * @since 2.0 + * @see ServletContextAware + */ +public interface ServletConfigAware { + + /** + * Set the ServletConfig that this object runs in. + *

Invoked after population of normal bean properties but before an init + * callback like InitializingBean's afterPropertiesSet or a + * custom init-method. Invoked after ApplicationContextAware's + * setApplicationContext. + * @param servletConfig ServletConfig object to be used by this object + * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet + * @see org.springframework.context.ApplicationContextAware#setApplicationContext + */ + void setServletConfig(ServletConfig servletConfig); + +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/context/ServletContextAware.java b/org.springframework.web/src/main/java/org/springframework/web/context/ServletContextAware.java new file mode 100644 index 0000000000..16a6e709a9 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/context/ServletContextAware.java @@ -0,0 +1,44 @@ +/* + * Copyright 2002-2005 the original author or authors. + * + * 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.springframework.web.context; + +import javax.servlet.ServletContext; + +/** + * Interface to be implemented by any object that wishes to be notified + * of the ServletContext (typically determined by the WebApplicationContext) + * that it runs in. + * + * @author Juergen Hoeller + * @since 12.03.2004 + * @see ServletConfigAware + */ +public interface ServletContextAware { + + /** + * Set the ServletContext that this object runs in. + *

Invoked after population of normal bean properties but before an init + * callback like InitializingBean's afterPropertiesSet or a + * custom init-method. Invoked after ApplicationContextAware's + * setApplicationContext. + * @param servletContext ServletContext object to be used by this object + * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet + * @see org.springframework.context.ApplicationContextAware#setApplicationContext + */ + void setServletContext(ServletContext servletContext); + +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/context/WebApplicationContext.java b/org.springframework.web/src/main/java/org/springframework/web/context/WebApplicationContext.java new file mode 100644 index 0000000000..1a6bd9f6fe --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/context/WebApplicationContext.java @@ -0,0 +1,81 @@ +/* + * Copyright 2002-2006 the original author or authors. + * + * 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.springframework.web.context; + +import javax.servlet.ServletContext; + +import org.springframework.context.ApplicationContext; + +/** + * Interface to provide configuration for a web application. This is read-only while + * the application is running, but may be reloaded if the implementation supports this. + * + *

This interface adds a getServletContext method to the generic ApplicationContext + * interface, and defines a well-known application attribute name that the root + * context must be bound to in the bootstrap process. + * + *

Like generic application contexts, web application contexts are hierarchical. + * There is a single root context per application, while each servlet in the application + * (including a dispatcher servlet in the MVC framework) has its own child context. + * + *

In addition to standard application context lifecycle capabilities, + * WebApplicationContext implementations need to detect ServletContextAware + * beans and invoke the setServletContext method accordingly. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @since January 19, 2001 + * @see ServletContextAware#setServletContext + */ +public interface WebApplicationContext extends ApplicationContext { + + /** + * Context attribute to bind root WebApplicationContext to on successful startup. + *

Note: If the startup of the root context fails, this attribute can contain + * an exception or error as value. Use WebApplicationContextUtils for convenient + * lookup of the root WebApplicationContext. + * @see org.springframework.web.context.support.WebApplicationContextUtils#getWebApplicationContext + * @see org.springframework.web.context.support.WebApplicationContextUtils#getRequiredWebApplicationContext + */ + String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT"; + + + /** + * Scope identifier for request scope: "request". + * Supported in addition to the standard scopes "singleton" and "prototype". + */ + String SCOPE_REQUEST = "request"; + + /** + * Scope identifier for session scope: "session". + * Supported in addition to the standard scopes "singleton" and "prototype". + */ + String SCOPE_SESSION = "session"; + + /** + * Scope identifier for global session scope: "globalSession". + * Supported in addition to the standard scopes "singleton" and "prototype". + */ + String SCOPE_GLOBAL_SESSION = "globalSession"; + + + /** + * Return the standard Servlet API ServletContext for this application. + */ + ServletContext getServletContext(); + +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/context/package.html b/org.springframework.web/src/main/java/org/springframework/web/context/package.html new file mode 100644 index 0000000000..d7ddcd09e9 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/context/package.html @@ -0,0 +1,8 @@ + + + +Contains a variant of the application context interface for web applications, +and the ContextLoaderListener that bootstraps a root web application context. + + + diff --git a/org.springframework.web/src/main/java/org/springframework/web/context/request/AbstractRequestAttributes.java b/org.springframework.web/src/main/java/org/springframework/web/context/request/AbstractRequestAttributes.java new file mode 100644 index 0000000000..31b407ed0f --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/context/request/AbstractRequestAttributes.java @@ -0,0 +1,104 @@ +/* + * Copyright 2002-2008 the original author or authors. + * + * 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.springframework.web.context.request; + +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; + +import org.springframework.util.Assert; + +/** + * Abstract support class for RequestAttributes implementations, + * offering a request completion mechanism for request-specific destruction + * callbacks and for updating accessed session attributes. + * + * @author Juergen Hoeller + * @since 2.0 + * @see #requestCompleted() + */ +public abstract class AbstractRequestAttributes implements RequestAttributes { + + /** Map from attribute name String to destruction callback Runnable */ + protected final Map requestDestructionCallbacks = new LinkedHashMap(8); + + private volatile boolean requestActive = true; + + + /** + * Signal that the request has been completed. + *

Executes all request destruction callbacks and updates the + * session attributes that have been accessed during request processing. + */ + public void requestCompleted() { + executeRequestDestructionCallbacks(); + updateAccessedSessionAttributes(); + this.requestActive = false; + } + + /** + * Determine whether the original request is still active. + * @see #requestCompleted() + */ + protected final boolean isRequestActive() { + return this.requestActive; + } + + /** + * Register the given callback as to be executed after request completion. + * @param name the name of the attribute to register the callback for + * @param callback the callback to be executed for destruction + */ + protected final void registerRequestDestructionCallback(String name, Runnable callback) { + Assert.notNull(name, "Name must not be null"); + Assert.notNull(callback, "Callback must not be null"); + synchronized (this.requestDestructionCallbacks) { + this.requestDestructionCallbacks.put(name, callback); + } + } + + /** + * Remove the request destruction callback for the specified attribute, if any. + * @param name the name of the attribute to remove the callback for + */ + protected final void removeRequestDestructionCallback(String name) { + Assert.notNull(name, "Name must not be null"); + synchronized (this.requestDestructionCallbacks) { + this.requestDestructionCallbacks.remove(name); + } + } + + /** + * Execute all callbacks that have been registered for execution + * after request completion. + */ + private void executeRequestDestructionCallbacks() { + synchronized (this.requestDestructionCallbacks) { + for (Iterator it = this.requestDestructionCallbacks.values().iterator(); it.hasNext();) { + ((Runnable) it.next()).run(); + } + this.requestDestructionCallbacks.clear(); + } + } + + /** + * Update all session attributes that have been accessed during request processing, + * to expose their potentially updated state to the underlying session manager. + */ + protected abstract void updateAccessedSessionAttributes(); + +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/context/request/AbstractRequestAttributesScope.java b/org.springframework.web/src/main/java/org/springframework/web/context/request/AbstractRequestAttributesScope.java new file mode 100644 index 0000000000..1b81a41f0a --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/context/request/AbstractRequestAttributesScope.java @@ -0,0 +1,77 @@ +/* + * Copyright 2002-2006 the original author or authors. + * + * 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.springframework.web.context.request; + +import org.springframework.beans.factory.ObjectFactory; +import org.springframework.beans.factory.config.Scope; + +/** + * Abstract {@link Scope} implementation that reads from a particular scope + * in the current thread-bound {@link RequestAttributes} object. + * + *

Subclasses simply need to implement {@link #getScope()} to instruct + * this class which {@link RequestAttributes} scope to read attributes from. + * + *

Subclasses may wish to override the {@link #get} and {@link #remove} + * methods to add synchronization around the call back into this super class. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @author Rob Harrop + * @since 2.0 + */ +public abstract class AbstractRequestAttributesScope implements Scope { + + public Object get(String name, ObjectFactory objectFactory) { + RequestAttributes attributes = RequestContextHolder.currentRequestAttributes(); + Object scopedObject = attributes.getAttribute(name, getScope()); + if (scopedObject == null) { + scopedObject = objectFactory.getObject(); + attributes.setAttribute(name, scopedObject, getScope()); + } + return scopedObject; + } + + public Object remove(String name) { + RequestAttributes attributes = RequestContextHolder.currentRequestAttributes(); + Object scopedObject = attributes.getAttribute(name, getScope()); + if (scopedObject != null) { + attributes.removeAttribute(name, getScope()); + return scopedObject; + } + else { + return null; + } + } + + public void registerDestructionCallback(String name, Runnable callback) { + RequestAttributes attributes = RequestContextHolder.currentRequestAttributes(); + attributes.registerDestructionCallback(name, callback, getScope()); + } + + + /** + * Template method that determines the actual target scope. + * @return the target scope, in the form of an appropriate + * {@link RequestAttributes} constant + * @see RequestAttributes#SCOPE_REQUEST + * @see RequestAttributes#SCOPE_SESSION + * @see RequestAttributes#SCOPE_GLOBAL_SESSION + */ + protected abstract int getScope(); + +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/context/request/FacesRequestAttributes.java b/org.springframework.web/src/main/java/org/springframework/web/context/request/FacesRequestAttributes.java new file mode 100644 index 0000000000..6db824c88a --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/context/request/FacesRequestAttributes.java @@ -0,0 +1,149 @@ +/* + * Copyright 2002-2008 the original author or authors. + * + * 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.springframework.web.context.request; + +import java.lang.reflect.Method; +import java.util.Map; + +import javax.faces.context.ExternalContext; +import javax.faces.context.FacesContext; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.util.Assert; +import org.springframework.util.ReflectionUtils; +import org.springframework.util.StringUtils; +import org.springframework.web.util.WebUtils; + +/** + * {@link RequestAttributes} adapter for a JSF {@link javax.faces.context.FacesContext}. + * Used as default in a JSF environment, wrapping the current FacesContext. + * + *

NOTE: In contrast to {@link ServletRequestAttributes}, this variant does + * not support destruction callbacks for scoped attributes, neither for the + * request scope nor for the session scope. If you rely on such implicit destruction + * callbacks, consider defining a Spring {@link RequestContextListener} in your + * web.xml. + * + * @author Juergen Hoeller + * @since 2.5.2 + * @see javax.faces.context.FacesContext#getExternalContext() + * @see javax.faces.context.ExternalContext#getRequestMap() + * @see javax.faces.context.ExternalContext#getSessionMap() + * @see RequestContextHolder#currentRequestAttributes() + */ +public class FacesRequestAttributes implements RequestAttributes { + + /** + * We'll create a lot of these objects, so we don't want a new logger every time. + */ + private static final Log logger = LogFactory.getLog(FacesRequestAttributes.class); + + private final FacesContext facesContext; + + + /** + * Create a new FacesRequestAttributes adapter for the given FacesContext. + * @param facesContext the current FacesContext + * @see javax.faces.context.FacesContext#getCurrentInstance() + */ + public FacesRequestAttributes(FacesContext facesContext) { + Assert.notNull(facesContext, "FacesContext must not be null"); + this.facesContext = facesContext; + } + + + /** + * Return the JSF FacesContext that this adapter operates on. + */ + protected FacesContext getFacesContext() { + return this.facesContext; + } + + /** + * Return the JSF ExternalContext that this adapter operates on. + * @see javax.faces.context.FacesContext#getExternalContext() + */ + protected ExternalContext getExternalContext() { + return getFacesContext().getExternalContext(); + } + + /** + * Return the JSF attribute Map for the specified scope + * @param scope constant indicating request or session scope + * @return the Map representation of the attributes in the specified scope + * @see #SCOPE_REQUEST + * @see #SCOPE_SESSION + */ + protected Map getAttributeMap(int scope) { + if (scope == SCOPE_REQUEST) { + return getExternalContext().getRequestMap(); + } + else { + return getExternalContext().getSessionMap(); + } + } + + + public Object getAttribute(String name, int scope) { + return getAttributeMap(scope).get(name); + } + + public void setAttribute(String name, Object value, int scope) { + getAttributeMap(scope).put(name, value); + } + + public void removeAttribute(String name, int scope) { + getAttributeMap(scope).remove(name); + } + + public String[] getAttributeNames(int scope) { + return StringUtils.toStringArray(getAttributeMap(scope).entrySet()); + } + + public void registerDestructionCallback(String name, Runnable callback, int scope) { + if (logger.isWarnEnabled()) { + logger.warn("Could not register destruction callback [" + callback + "] for attribute '" + name + + "' because FacesRequestAttributes does not support such callbacks"); + } + } + + public String getSessionId() { + Object session = getExternalContext().getSession(true); + try { + // Both HttpSession and PortletSession have a getId() method. + Method getIdMethod = session.getClass().getMethod("getId", new Class[0]); + return ReflectionUtils.invokeMethod(getIdMethod, session).toString(); + } + catch (NoSuchMethodException ex) { + throw new IllegalStateException("Session object [" + session + "] does not have a getId() method"); + } + } + + public Object getSessionMutex() { + // Enforce presence of a session first to allow listeners + // to create the mutex attribute, if any. + Object session = getExternalContext().getSession(true); + Object mutex = getExternalContext().getSessionMap().get(WebUtils.SESSION_MUTEX_ATTRIBUTE); + if (mutex == null) { + mutex = session; + } + return mutex; + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/context/request/FacesWebRequest.java b/org.springframework.web/src/main/java/org/springframework/web/context/request/FacesWebRequest.java new file mode 100644 index 0000000000..69e5f6042a --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/context/request/FacesWebRequest.java @@ -0,0 +1,117 @@ +/* + * Copyright 2002-2008 the original author or authors. + * + * 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.springframework.web.context.request; + +import java.security.Principal; +import java.util.Locale; +import java.util.Map; + +import javax.faces.context.ExternalContext; +import javax.faces.context.FacesContext; + +import org.springframework.util.StringUtils; + +/** + * {@link WebRequest} adapter for a JSF {@link javax.faces.context.FacesContext}. + * + * @author Juergen Hoeller + * @since 2.5.2 + */ +public class FacesWebRequest extends FacesRequestAttributes implements NativeWebRequest { + + /** + * Create a new FacesWebRequest adapter for the given FacesContext. + * @param facesContext the current FacesContext + * @see javax.faces.context.FacesContext#getCurrentInstance() + */ + public FacesWebRequest(FacesContext facesContext) { + super(facesContext); + } + + + public Object getNativeRequest() { + return getExternalContext().getRequest(); + } + + public Object getNativeResponse() { + return getExternalContext().getResponse(); + } + + + public String getParameter(String paramName) { + return (String) getExternalContext().getRequestParameterMap().get(paramName); + } + + public String[] getParameterValues(String paramName) { + return (String[]) getExternalContext().getRequestParameterMap().get(paramName); + } + + public Map getParameterMap() { + return getExternalContext().getRequestParameterMap(); + } + + public Locale getLocale() { + return getFacesContext().getExternalContext().getRequestLocale(); + } + + public String getContextPath() { + return getFacesContext().getExternalContext().getRequestContextPath(); + } + + public String getRemoteUser() { + return getFacesContext().getExternalContext().getRemoteUser(); + } + + public Principal getUserPrincipal() { + return getFacesContext().getExternalContext().getUserPrincipal(); + } + + public boolean isUserInRole(String role) { + return getFacesContext().getExternalContext().isUserInRole(role); + } + + public boolean isSecure() { + return false; + } + + public boolean checkNotModified(long lastModifiedTimestamp) { + return false; + } + + + public String getDescription(boolean includeClientInfo) { + ExternalContext externalContext = getExternalContext(); + StringBuffer buffer = new StringBuffer(); + buffer.append("context=").append(externalContext.getRequestContextPath()); + if (includeClientInfo) { + Object session = externalContext.getSession(false); + if (session != null) { + buffer.append(";session=").append(getSessionId()); + } + String user = externalContext.getRemoteUser(); + if (StringUtils.hasLength(user)) { + buffer.append(";user=").append(user); + } + } + return buffer.toString(); + } + + public String toString() { + return "FacesWebRequest: " + getDescription(true); + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/context/request/Log4jNestedDiagnosticContextInterceptor.java b/org.springframework.web/src/main/java/org/springframework/web/context/request/Log4jNestedDiagnosticContextInterceptor.java new file mode 100644 index 0000000000..2bf99cbe2a --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/context/request/Log4jNestedDiagnosticContextInterceptor.java @@ -0,0 +1,91 @@ +/* + * Copyright 2002-2008 the original author or authors. + * + * 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.springframework.web.context.request; + +import org.apache.log4j.Logger; +import org.apache.log4j.NDC; + +import org.springframework.ui.ModelMap; + +/** + * Request logging interceptor that adds a request context message to the + * Log4J nested diagnostic context (NDC) before the request is processed, + * removing it again after the request is processed. + * + * @author Juergen Hoeller + * @since 2.5 + * @see org.apache.log4j.NDC#push(String) + * @see org.apache.log4j.NDC#pop() + */ +public class Log4jNestedDiagnosticContextInterceptor implements WebRequestInterceptor { + + /** Logger available to subclasses */ + protected final Logger log4jLogger = Logger.getLogger(getClass()); + + private boolean includeClientInfo = false; + + + /** + * Set whether or not the session id and user name should be included + * in the log message. + */ + public void setIncludeClientInfo(boolean includeClientInfo) { + this.includeClientInfo = includeClientInfo; + } + + /** + * Return whether or not the session id and user name should be included + * in the log message. + */ + protected boolean isIncludeClientInfo() { + return this.includeClientInfo; + } + + + /** + * Adds a message the Log4J NDC before the request is processed. + */ + public void preHandle(WebRequest request) throws Exception { + NDC.push(getNestedDiagnosticContextMessage(request)); + } + + /** + * Determine the message to be pushed onto the Log4J nested diagnostic context. + *

Default is the request object's getDescription result. + * @param request current HTTP request + * @return the message to be pushed onto the Log4J NDC + * @see WebRequest#getDescription + * @see #isIncludeClientInfo() + */ + protected String getNestedDiagnosticContextMessage(WebRequest request) { + return request.getDescription(isIncludeClientInfo()); + } + + public void postHandle(WebRequest request, ModelMap model) throws Exception { + } + + /** + * Removes the log message from the Log4J NDC after the request is processed. + */ + public void afterCompletion(WebRequest request, Exception ex) throws Exception { + NDC.pop(); + if (NDC.getDepth() == 0) { + NDC.remove(); + } + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/context/request/NativeWebRequest.java b/org.springframework.web/src/main/java/org/springframework/web/context/request/NativeWebRequest.java new file mode 100644 index 0000000000..f3b0855e21 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/context/request/NativeWebRequest.java @@ -0,0 +1,47 @@ +/* + * Copyright 2002-2008 the original author or authors. + * + * 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.springframework.web.context.request; + +/** + * Extension of the {@link WebRequest} interface, exposing the + * native request and response objects in a generic fashion. + * + *

Mainly intended for framework-internal usage, + * in particular for generic argument resolution code. + * + * @author Juergen Hoeller + * @since 2.5.2 + */ +public interface NativeWebRequest extends WebRequest { + + /** + * Return the underlying native request object, if available. + * @see javax.servlet.http.HttpServletRequest + * @see javax.portlet.ActionRequest + * @see javax.portlet.RenderRequest + */ + Object getNativeRequest(); + + /** + * Return the underlying native response object, if available. + * @see javax.servlet.http.HttpServletResponse + * @see javax.portlet.ActionResponse + * @see javax.portlet.RenderResponse + */ + Object getNativeResponse(); + +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/context/request/RequestAttributes.java b/org.springframework.web/src/main/java/org/springframework/web/context/request/RequestAttributes.java new file mode 100644 index 0000000000..2621117970 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/context/request/RequestAttributes.java @@ -0,0 +1,124 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.web.context.request; + +/** + * Abstraction for accessing attribute objects associated with a request. + * Supports access to request-scoped attributes as well as to session-scoped + * attributes, with the optional notion of a "global session". + * + *

Can be implemented for any kind of request/session mechanism, + * in particular for servlet requests and portlet requests. + * + * @author Juergen Hoeller + * @since 2.0 + * @see ServletRequestAttributes + * @see org.springframework.web.portlet.context.PortletRequestAttributes + */ +public interface RequestAttributes { + + /** + * Constant that indicates request scope. + */ + int SCOPE_REQUEST = 0; + + /** + * Constant that indicates session scope. + *

This preferably refers to a locally isolated session, if such + * a distinction is available (for example, in a Portlet environment). + * Else, it simply refers to the common session. + */ + int SCOPE_SESSION = 1; + + /** + * Constant that indicates global session scope. + *

This explicitly refers to a globally shared session, if such + * a distinction is available (for example, in a Portlet environment). + * Else, it simply refers to the common session. + */ + int SCOPE_GLOBAL_SESSION = 2; + + + /** + * Return the value for the scoped attribute of the given name, if any. + * @param name the name of the attribute + * @param scope the scope identifier + * @return the current attribute value, or null if not found + */ + Object getAttribute(String name, int scope); + + /** + * Set the value for the scoped attribute of the given name, + * replacing an existing value (if any). + * @param name the name of the attribute + * @param scope the scope identifier + * @param value the value for the attribute + */ + void setAttribute(String name, Object value, int scope); + + /** + * Remove the scoped attribute of the given name, if it exists. + *

Note that an implementation should also remove a registered destruction + * callback for the specified attribute, if any. It does, however, not + * need to execute a registered destruction callback in this case, + * since the object will be destroyed by the caller (if appropriate). + * @param name the name of the attribute + * @param scope the scope identifier + */ + void removeAttribute(String name, int scope); + + /** + * Retrieve the names of all attributes in the scope. + * @param scope the scope identifier + * @return the attribute names as String array + */ + String[] getAttributeNames(int scope); + + /** + * Register a callback to be executed on destruction of the + * specified attribute in the given scope. + *

Implementations should do their best to execute the callback + * at the appropriate time: that is, at request completion or session + * termination, respectively. If such a callback is not supported by the + * underlying runtime environment, the callback must be ignored + * and a corresponding warning should be logged. + *

Note that 'destruction' usually corresponds to destruction of the + * entire scope, not to the individual attribute having been explicitly + * removed by the application. If an attribute gets removed via this + * facade's {@link #removeAttribute(String, int)} method, any registered + * destruction callback should be disabled as well, assuming that the + * removed object will be reused or manually destroyed. + * @param name the name of the attribute to register the callback for + * @param callback the destruction callback to be executed + * @param scope the scope identifier + */ + void registerDestructionCallback(String name, Runnable callback, int scope); + + /** + * Return an id for the current underlying session. + * @return the session id as String (never null + */ + String getSessionId(); + + /** + * Expose the best available mutex for the underlying session: + * that is, an object to synchronize on for the underlying session. + * @return the session mutex to use (never null + */ + Object getSessionMutex(); + +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/context/request/RequestContextHolder.java b/org.springframework.web/src/main/java/org/springframework/web/context/request/RequestContextHolder.java new file mode 100644 index 0000000000..0943294c4e --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/context/request/RequestContextHolder.java @@ -0,0 +1,145 @@ +/* + * Copyright 2002-2008 the original author or authors. + * + * 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.springframework.web.context.request; + +import javax.faces.context.FacesContext; + +import org.springframework.core.NamedInheritableThreadLocal; +import org.springframework.core.NamedThreadLocal; +import org.springframework.util.ClassUtils; + +/** + * Holder class to expose the web request in the form of a thread-bound + * {@link RequestAttributes} object. + * + *

Use {@link RequestContextListener} or + * {@link org.springframework.web.filter.RequestContextFilter} to expose + * the current web request. Note that + * {@link org.springframework.web.servlet.DispatcherServlet} and + * {@link org.springframework.web.portlet.DispatcherPortlet} already + * expose the current request by default. + * + * @author Juergen Hoeller + * @author Rod Johnson + * @since 2.0 + * @see RequestContextListener + * @see org.springframework.web.filter.RequestContextFilter + * @see org.springframework.web.servlet.DispatcherServlet + * @see org.springframework.web.portlet.DispatcherPortlet + */ +public abstract class RequestContextHolder { + + private static final boolean jsfPresent = + ClassUtils.isPresent("javax.faces.context.FacesContext", RequestContextHolder.class.getClassLoader()); + + private static final ThreadLocal requestAttributesHolder = new NamedThreadLocal("Request attributes"); + + private static final ThreadLocal inheritableRequestAttributesHolder = + new NamedInheritableThreadLocal("Request context"); + + + /** + * Reset the RequestAttributes for the current thread. + */ + public static void resetRequestAttributes() { + requestAttributesHolder.set(null); + inheritableRequestAttributesHolder.set(null); + } + + /** + * Bind the given RequestAttributes to the current thread, + * not exposing it as inheritable for child threads. + * @param attributes the RequestAttributes to expose + * @see #setRequestAttributes(RequestAttributes, boolean) + */ + public static void setRequestAttributes(RequestAttributes attributes) { + setRequestAttributes(attributes, false); + } + + /** + * Bind the given RequestAttributes to the current thread. + * @param attributes the RequestAttributes to expose + * @param inheritable whether to expose the RequestAttributes as inheritable + * for child threads (using an {@link java.lang.InheritableThreadLocal}) + */ + public static void setRequestAttributes(RequestAttributes attributes, boolean inheritable) { + if (inheritable) { + inheritableRequestAttributesHolder.set(attributes); + requestAttributesHolder.set(null); + } + else { + requestAttributesHolder.set(attributes); + inheritableRequestAttributesHolder.set(null); + } + } + + /** + * Return the RequestAttributes currently bound to the thread. + * @return the RequestAttributes currently bound to the thread, + * or null if none bound + */ + public static RequestAttributes getRequestAttributes() { + RequestAttributes attributes = (RequestAttributes) requestAttributesHolder.get(); + if (attributes == null) { + attributes = (RequestAttributes) inheritableRequestAttributesHolder.get(); + } + return attributes; + } + + /** + * Return the RequestAttributes currently bound to the thread. + *

Exposes the previously bound RequestAttributes instance, if any. + * Falls back to the current JSF FacesContext, if any. + * @return the RequestAttributes currently bound to the thread + * @throws IllegalStateException if no RequestAttributes object + * is bound to the current thread + * @see #setRequestAttributes + * @see ServletRequestAttributes + * @see FacesRequestAttributes + * @see javax.faces.context.FacesContext#getCurrentInstance() + */ + public static RequestAttributes currentRequestAttributes() throws IllegalStateException { + RequestAttributes attributes = getRequestAttributes(); + if (attributes == null) { + if (jsfPresent) { + attributes = FacesRequestAttributesFactory.getFacesRequestAttributes(); + } + if (attributes == null) { + throw new IllegalStateException("No thread-bound request found: " + + "Are you referring to request attributes outside of an actual web request, " + + "or processing a request outside of the originally receiving thread? " + + "If you are actually operating within a web request and still receive this message, " + + "your code is probably running outside of DispatcherServlet/DispatcherPortlet: " + + "In this case, use RequestContextListener or RequestContextFilter to expose the current request."); + } + } + return attributes; + } + + + /** + * Inner class to avoid hard-coded JSF dependency. + */ + private static class FacesRequestAttributesFactory { + + public static RequestAttributes getFacesRequestAttributes() { + FacesContext facesContext = FacesContext.getCurrentInstance(); + return (facesContext != null ? new FacesRequestAttributes(facesContext) : null); + } + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/context/request/RequestContextListener.java b/org.springframework.web/src/main/java/org/springframework/web/context/request/RequestContextListener.java new file mode 100644 index 0000000000..34f273f4f2 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/context/request/RequestContextListener.java @@ -0,0 +1,94 @@ +/* + * Copyright 2002-2008 the original author or authors. + * + * 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.springframework.web.context.request; + +import javax.servlet.ServletRequestEvent; +import javax.servlet.ServletRequestListener; +import javax.servlet.http.HttpServletRequest; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.context.i18n.LocaleContextHolder; + +/** + * Servlet 2.4+ listener that exposes the request to the current thread, + * through both {@link org.springframework.context.i18n.LocaleContextHolder} and + * {@link RequestContextHolder}. To be registered as listener in web.xml. + * + *

Alternatively, Spring's {@link org.springframework.web.filter.RequestContextFilter} + * and Spring's {@link org.springframework.web.servlet.DispatcherServlet} also expose + * the same request context to the current thread. In contrast to this listener, + * advanced options are available there (e.g. "threadContextInheritable"). + * + *

This listener is mainly for use with third-party servlets, e.g. the JSF FacesServlet. + * Within Spring's own web support, DispatcherServlet's processing is perfectly sufficient. + * + * @author Juergen Hoeller + * @since 2.0 + * @see javax.servlet.ServletRequestListener + * @see org.springframework.context.i18n.LocaleContextHolder + * @see org.springframework.web.context.request.RequestContextHolder + * @see org.springframework.web.filter.RequestContextFilter + * @see org.springframework.web.servlet.DispatcherServlet + */ +public class RequestContextListener implements ServletRequestListener { + + private static final String REQUEST_ATTRIBUTES_ATTRIBUTE = + RequestContextListener.class.getName() + ".REQUEST_ATTRIBUTES"; + + /** Logger available to subclasses */ + protected final Log logger = LogFactory.getLog(getClass()); + + + public void requestInitialized(ServletRequestEvent requestEvent) { + if (!(requestEvent.getServletRequest() instanceof HttpServletRequest)) { + throw new IllegalArgumentException( + "Request is not an HttpServletRequest: " + requestEvent.getServletRequest()); + } + HttpServletRequest request = (HttpServletRequest) requestEvent.getServletRequest(); + ServletRequestAttributes attributes = new ServletRequestAttributes(request); + request.setAttribute(REQUEST_ATTRIBUTES_ATTRIBUTE, attributes); + LocaleContextHolder.setLocale(request.getLocale()); + RequestContextHolder.setRequestAttributes(attributes); + if (logger.isDebugEnabled()) { + logger.debug("Bound request context to thread: " + request); + } + } + + public void requestDestroyed(ServletRequestEvent requestEvent) { + ServletRequestAttributes attributes = + (ServletRequestAttributes) requestEvent.getServletRequest().getAttribute(REQUEST_ATTRIBUTES_ATTRIBUTE); + ServletRequestAttributes threadAttributes = + (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + if (threadAttributes != null) { + // We're assumably within the original request thread... + if (attributes == null) { + attributes = threadAttributes; + } + RequestContextHolder.resetRequestAttributes(); + LocaleContextHolder.resetLocaleContext(); + } + if (attributes != null) { + attributes.requestCompleted(); + if (logger.isDebugEnabled()) { + logger.debug("Cleared thread-bound request context: " + requestEvent.getServletRequest()); + } + } + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/context/request/RequestScope.java b/org.springframework.web/src/main/java/org/springframework/web/context/request/RequestScope.java new file mode 100644 index 0000000000..4257f850a1 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/context/request/RequestScope.java @@ -0,0 +1,58 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.web.context.request; + +/** + * Request-backed {@link org.springframework.beans.factory.config.Scope} + * implementation. + * + *

Relies on a thread-bound {@link RequestAttributes} instance, which + * can be exported through {@link RequestContextListener}, + * {@link org.springframework.web.filter.RequestContextFilter} or + * {@link org.springframework.web.servlet.DispatcherServlet}. + * + *

This Scope will also work for Portlet environments, + * through an alternate RequestAttributes implementation + * (as exposed out-of-the-box by Spring's + * {@link org.springframework.web.portlet.DispatcherPortlet}. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @author Rob Harrop + * @since 2.0 + * @see RequestContextHolder#currentRequestAttributes() + * @see RequestAttributes#SCOPE_REQUEST + * @see RequestContextListener + * @see org.springframework.web.filter.RequestContextFilter + * @see org.springframework.web.servlet.DispatcherServlet + * @see org.springframework.web.portlet.DispatcherPortlet + */ +public class RequestScope extends AbstractRequestAttributesScope { + + protected int getScope() { + return RequestAttributes.SCOPE_REQUEST; + } + + /** + * There is no conversation id concept for a request, so this method + * returns null. + */ + public String getConversationId() { + return null; + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/context/request/ServletRequestAttributes.java b/org.springframework.web/src/main/java/org/springframework/web/context/request/ServletRequestAttributes.java new file mode 100644 index 0000000000..cf4506d873 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/context/request/ServletRequestAttributes.java @@ -0,0 +1,273 @@ +/* + * Copyright 2002-2008 the original author or authors. + * + * 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.springframework.web.context.request; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpSession; +import javax.servlet.http.HttpSessionBindingEvent; +import javax.servlet.http.HttpSessionBindingListener; + +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; +import org.springframework.web.util.WebUtils; + +/** + * Servlet-based implementation of the {@link RequestAttributes} interface. + * + *

Accesses objects from servlet request and HTTP session scope, + * with no distinction between "session" and "global session". + * + * @author Juergen Hoeller + * @since 2.0 + * @see javax.servlet.ServletRequest#getAttribute + * @see javax.servlet.http.HttpSession#getAttribute + */ +public class ServletRequestAttributes extends AbstractRequestAttributes { + + /** + * Constant identifying the {@link String} prefixed to the name of a + * destruction callback when it is stored in a {@link HttpSession}. + */ + public static final String DESTRUCTION_CALLBACK_NAME_PREFIX = + ServletRequestAttributes.class.getName() + ".DESTRUCTION_CALLBACK."; + + + private final HttpServletRequest request; + + private volatile HttpSession session; + + private final Map sessionAttributesToUpdate = new HashMap(); + + + /** + * Create a new ServletRequestAttributes instance for the given request. + * @param request current HTTP request + */ + public ServletRequestAttributes(HttpServletRequest request) { + Assert.notNull(request, "Request must not be null"); + this.request = request; + } + + + /** + * Exposes the native {@link HttpServletRequest} that we're wrapping. + */ + public final HttpServletRequest getRequest() { + return this.request; + } + + /** + * Exposes the {@link HttpSession} that we're wrapping. + * @param allowCreate whether to allow creation of a new session if none exists yet + */ + protected final HttpSession getSession(boolean allowCreate) { + if (isRequestActive()) { + return this.request.getSession(allowCreate); + } + else { + // Access through stored session reference, if any... + if (this.session == null && allowCreate) { + throw new IllegalStateException( + "No session found and request already completed - cannot create new session!"); + } + return this.session; + } + } + + + public Object getAttribute(String name, int scope) { + if (scope == SCOPE_REQUEST) { + if (!isRequestActive()) { + throw new IllegalStateException( + "Cannot ask for request attribute - request is not active anymore!"); + } + return this.request.getAttribute(name); + } + else { + HttpSession session = getSession(false); + if (session != null) { + try { + Object value = session.getAttribute(name); + if (value != null) { + synchronized (this.sessionAttributesToUpdate) { + this.sessionAttributesToUpdate.put(name, value); + } + } + return value; + } + catch (IllegalStateException ex) { + // Session invalidated - shouldn't usually happen. + } + } + return null; + } + } + + public void setAttribute(String name, Object value, int scope) { + if (scope == SCOPE_REQUEST) { + if (!isRequestActive()) { + throw new IllegalStateException( + "Cannot set request attribute - request is not active anymore!"); + } + this.request.setAttribute(name, value); + } + else { + HttpSession session = getSession(true); + synchronized (this.sessionAttributesToUpdate) { + this.sessionAttributesToUpdate.remove(name); + } + session.setAttribute(name, value); + } + } + + public void removeAttribute(String name, int scope) { + if (scope == SCOPE_REQUEST) { + if (isRequestActive()) { + this.request.removeAttribute(name); + removeRequestDestructionCallback(name); + } + } + else { + HttpSession session = getSession(false); + if (session != null) { + synchronized (this.sessionAttributesToUpdate) { + this.sessionAttributesToUpdate.remove(name); + } + try { + session.removeAttribute(name); + // Remove any registered destruction callback as well. + session.removeAttribute(DESTRUCTION_CALLBACK_NAME_PREFIX + name); + } + catch (IllegalStateException ex) { + // Session invalidated - shouldn't usually happen. + } + } + } + } + + public String[] getAttributeNames(int scope) { + if (scope == SCOPE_REQUEST) { + if (!isRequestActive()) { + throw new IllegalStateException( + "Cannot ask for request attributes - request is not active anymore!"); + } + return StringUtils.toStringArray(this.request.getAttributeNames()); + } + else { + HttpSession session = getSession(false); + if (session != null) { + try { + return StringUtils.toStringArray(session.getAttributeNames()); + } + catch (IllegalStateException ex) { + // Session invalidated - shouldn't usually happen. + } + } + return new String[0]; + } + } + + public void registerDestructionCallback(String name, Runnable callback, int scope) { + if (scope == SCOPE_REQUEST) { + registerRequestDestructionCallback(name, callback); + } + else { + registerSessionDestructionCallback(name, callback); + } + } + + public String getSessionId() { + return getSession(true).getId(); + } + + public Object getSessionMutex() { + return WebUtils.getSessionMutex(getSession(true)); + } + + + /** + * Update all accessed session attributes through session.setAttribute + * calls, explicitly indicating to the container that they might have been modified. + */ + protected void updateAccessedSessionAttributes() { + // Store session reference for access after request completion. + this.session = this.request.getSession(false); + // Update all affected session attributes. + synchronized (this.sessionAttributesToUpdate) { + if (this.session != null) { + try { + for (Iterator it = this.sessionAttributesToUpdate.entrySet().iterator(); it.hasNext();) { + Map.Entry entry = (Map.Entry) it.next(); + String name = (String) entry.getKey(); + Object newValue = entry.getValue(); + Object oldValue = this.session.getAttribute(name); + if (oldValue == newValue) { + this.session.setAttribute(name, newValue); + } + } + } + catch (IllegalStateException ex) { + // Session invalidated - shouldn't usually happen. + } + } + this.sessionAttributesToUpdate.clear(); + } + } + + /** + * Register the given callback as to be executed after session termination. + * @param name the name of the attribute to register the callback for + * @param callback the callback to be executed for destruction + */ + private void registerSessionDestructionCallback(String name, Runnable callback) { + HttpSession session = getSession(true); + session.setAttribute(DESTRUCTION_CALLBACK_NAME_PREFIX + name, + new DestructionCallbackBindingListener(callback)); + } + + + public String toString() { + return this.request.toString(); + } + + + /** + * Adapter that implements the Servlet 2.3 HttpSessionBindingListener + * interface, wrapping a session destruction callback. + */ + private static class DestructionCallbackBindingListener implements HttpSessionBindingListener, Serializable { + + private final Runnable destructionCallback; + + public DestructionCallbackBindingListener(Runnable destructionCallback) { + this.destructionCallback = destructionCallback; + } + + public void valueBound(HttpSessionBindingEvent event) { + } + + public void valueUnbound(HttpSessionBindingEvent event) { + this.destructionCallback.run(); + } + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/context/request/ServletWebRequest.java b/org.springframework.web/src/main/java/org/springframework/web/context/request/ServletWebRequest.java new file mode 100644 index 0000000000..51d43ff774 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/context/request/ServletWebRequest.java @@ -0,0 +1,166 @@ +/* + * Copyright 2002-2008 the original author or authors. + * + * 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.springframework.web.context.request; + +import java.security.Principal; +import java.util.Locale; +import java.util.Map; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +import org.springframework.util.StringUtils; + +/** + * {@link WebRequest} adapter for an {@link javax.servlet.http.HttpServletRequest}. + * + * @author Juergen Hoeller + * @since 2.0 + */ +public class ServletWebRequest extends ServletRequestAttributes implements NativeWebRequest { + + private static final String HEADER_IF_MODIFIED_SINCE = "If-Modified-Since"; + + private static final String HEADER_LAST_MODIFIED = "Last-Modified"; + + + private HttpServletResponse response; + + private boolean notModified = false; + + + /** + * Create a new ServletWebRequest instance for the given request. + * @param request current HTTP request + */ + public ServletWebRequest(HttpServletRequest request) { + super(request); + } + + /** + * Create a new ServletWebRequest instance for the given request/response pair. + * @param request current HTTP request + * @param response current HTTP response (for automatic last-modified handling) + */ + public ServletWebRequest(HttpServletRequest request, HttpServletResponse response) { + super(request); + this.response = response; + } + + + /** + * Exposes the native {@link HttpServletRequest} that we're wrapping (if any). + */ + public final HttpServletResponse getResponse() { + return this.response; + } + + public Object getNativeRequest() { + return getRequest(); + } + + public Object getNativeResponse() { + return getResponse(); + } + + + public String getParameter(String paramName) { + return getRequest().getParameter(paramName); + } + + public String[] getParameterValues(String paramName) { + return getRequest().getParameterValues(paramName); + } + + public Map getParameterMap() { + return getRequest().getParameterMap(); + } + + public Locale getLocale() { + return getRequest().getLocale(); + } + + public String getContextPath() { + return getRequest().getContextPath(); + } + + public String getRemoteUser() { + return getRequest().getRemoteUser(); + } + + public Principal getUserPrincipal() { + return getRequest().getUserPrincipal(); + } + + public boolean isUserInRole(String role) { + return getRequest().isUserInRole(role); + } + + public boolean isSecure() { + return getRequest().isSecure(); + } + + + public boolean checkNotModified(long lastModifiedTimestamp) { + if (lastModifiedTimestamp >= 0 && !this.notModified && + (this.response == null || !this.response.containsHeader(HEADER_LAST_MODIFIED))) { + long ifModifiedSince = getRequest().getDateHeader(HEADER_IF_MODIFIED_SINCE); + this.notModified = (ifModifiedSince >= (lastModifiedTimestamp / 1000 * 1000)); + if (this.response != null) { + if (this.notModified) { + this.response.setStatus(HttpServletResponse.SC_NOT_MODIFIED); + } + else { + this.response.setDateHeader(HEADER_LAST_MODIFIED, lastModifiedTimestamp); + } + } + } + return this.notModified; + } + + public boolean isNotModified() { + return this.notModified; + } + + + public String getDescription(boolean includeClientInfo) { + HttpServletRequest request = getRequest(); + StringBuffer buffer = new StringBuffer(); + buffer.append("uri=").append(request.getRequestURI()); + if (includeClientInfo) { + String client = request.getRemoteAddr(); + if (StringUtils.hasLength(client)) { + buffer.append(";client=").append(client); + } + HttpSession session = request.getSession(false); + if (session != null) { + buffer.append(";session=").append(session.getId()); + } + String user = request.getRemoteUser(); + if (StringUtils.hasLength(user)) { + buffer.append(";user=").append(user); + } + } + return buffer.toString(); + } + + public String toString() { + return "ServletWebRequest: " + getDescription(true); + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/context/request/SessionScope.java b/org.springframework.web/src/main/java/org/springframework/web/context/request/SessionScope.java new file mode 100644 index 0000000000..0d0c5f60af --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/context/request/SessionScope.java @@ -0,0 +1,101 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.web.context.request; + +import org.springframework.beans.factory.ObjectFactory; + +/** + * Session-backed {@link org.springframework.beans.factory.config.Scope} + * implementation. + * + *

Relies on a thread-bound {@link RequestAttributes} instance, which + * can be exported through {@link RequestContextListener}, + * {@link org.springframework.web.filter.RequestContextFilter} or + * {@link org.springframework.web.servlet.DispatcherServlet}. + * + *

This Scope will also work for Portlet environments, + * through an alternate RequestAttributes implementation + * (as exposed out-of-the-box by Spring's + * {@link org.springframework.web.portlet.DispatcherPortlet}. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @author Rob Harrop + * @since 2.0 + * @see RequestContextHolder#currentRequestAttributes() + * @see RequestAttributes#SCOPE_SESSION + * @see RequestAttributes#SCOPE_GLOBAL_SESSION + * @see RequestContextListener + * @see org.springframework.web.filter.RequestContextFilter + * @see org.springframework.web.servlet.DispatcherServlet + * @see org.springframework.web.portlet.DispatcherPortlet + */ +public class SessionScope extends AbstractRequestAttributesScope { + + private final int scope; + + + /** + * Create a new SessionScope, storing attributes in a locally + * isolated session (or default session, if there is no distinction + * between a global session and a component-specific session). + */ + public SessionScope() { + this.scope = RequestAttributes.SCOPE_SESSION; + } + + /** + * Create a new SessionScope, specifying whether to store attributes + * in the global session, provided that such a distinction is available. + *

This distinction is important for Portlet environments, where there + * are two notions of a session: "portlet scope" and "application scope". + * If this flag is on, objects will be put into the "application scope" session; + * else they will end up in the "portlet scope" session (the typical default). + *

In a Servlet environment, this flag is effectively ignored. + * @param globalSession true in case of the global session as target; + * false in case of a component-specific session as target + * @see org.springframework.web.portlet.context.PortletRequestAttributes + * @see org.springframework.web.context.request.ServletRequestAttributes + */ + public SessionScope(boolean globalSession) { + this.scope = (globalSession ? RequestAttributes.SCOPE_GLOBAL_SESSION : RequestAttributes.SCOPE_SESSION); + } + + + protected int getScope() { + return this.scope; + } + + public String getConversationId() { + return RequestContextHolder.currentRequestAttributes().getSessionId(); + } + + public Object get(String name, ObjectFactory objectFactory) { + Object mutex = RequestContextHolder.currentRequestAttributes().getSessionMutex(); + synchronized (mutex) { + return super.get(name, objectFactory); + } + } + + public Object remove(String name) { + Object mutex = RequestContextHolder.currentRequestAttributes().getSessionMutex(); + synchronized (mutex) { + return super.remove(name); + } + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/context/request/WebRequest.java b/org.springframework.web/src/main/java/org/springframework/web/context/request/WebRequest.java new file mode 100644 index 0000000000..a2529bccad --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/context/request/WebRequest.java @@ -0,0 +1,129 @@ +/* + * Copyright 2002-2008 the original author or authors. + * + * 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.springframework.web.context.request; + +import java.security.Principal; +import java.util.Locale; +import java.util.Map; + +/** + * Generic interface for a web request. Mainly intended for generic web + * request interceptors, giving them access to general request metadata, + * not for actual handling of the request. + * + * @author Juergen Hoeller + * @since 2.0 + * @see WebRequestInterceptor + */ +public interface WebRequest extends RequestAttributes { + + /** + * Return the request parameter of the given name, or null if none. + *

Retrieves the first parameter value in case of a multi-value parameter. + * @see javax.servlet.http.HttpServletRequest#getParameter(String) + */ + String getParameter(String paramName); + + /** + * Return the request parameter values for the given parameter name, + * or null if none. + *

A single-value parameter will be exposed as an array with a single element. + * @see javax.servlet.http.HttpServletRequest#getParameterValues(String) + */ + String[] getParameterValues(String paramName); + + /** + * Return a immutable Map of the request parameters, with parameter names as map keys + * and parameter values as map values. The map values will be of type String array. + *

A single-value parameter will be exposed as an array with a single element. + * @see javax.servlet.http.HttpServletRequest#getParameterMap() + */ + Map getParameterMap(); + + /** + * Return the primary Locale for this request. + * @see javax.servlet.http.HttpServletRequest#getLocale() + */ + Locale getLocale(); + + /** + * Return the context path for this request + * (usually the base path that the current web application is mapped to). + * @see javax.servlet.http.HttpServletRequest#getContextPath() + */ + String getContextPath(); + + /** + * Return the remote user for this request, if any. + * @see javax.servlet.http.HttpServletRequest#getRemoteUser() + */ + String getRemoteUser(); + + /** + * Return the user principal for this request, if any. + * @see javax.servlet.http.HttpServletRequest#getUserPrincipal() + */ + Principal getUserPrincipal(); + + /** + * Determine whether the user is in the given role for this request. + * @see javax.servlet.http.HttpServletRequest#isUserInRole(String) + */ + boolean isUserInRole(String role); + + /** + * Return whether this request has been sent over a secure transport + * mechanism (such as SSL). + * @see javax.servlet.http.HttpServletRequest#isSecure() + */ + boolean isSecure(); + + /** + * Check whether the request qualifies as not modified given the + * supplied last-modified timestamp (as determined by the application). + *

This will also transparently set the appropriate response headers, + * for both the modified case and the not-modified case. + *

Typical usage: + *

+	 * public String myHandleMethod(WebRequest webRequest, Model model) {
+	 *   long lastModified = // application-specific calculation
+	 *   if (request.checkNotModified(lastModified)) {
+	 *     // shortcut exit - no further processing necessary
+	 *     return null;
+	 *   }
+	 *   // further request processing, actually building content
+	 *   model.addAttribute(...);
+	 *   return "myViewName";
+	 * }
+ * @param lastModifiedTimestamp the last-modified timestamp that + * the application determined for the underlying resource + * @return whether the request qualifies as not modified, + * allowing to abort request processing and relying on the response + * telling the client that the content has not been modified + */ + boolean checkNotModified(long lastModifiedTimestamp); + + /** + * Get a short description of this request, + * typically containing request URI and session id. + * @param includeClientInfo whether to include client-specific + * information such as session id and user name + * @return the requested description as String + */ + String getDescription(boolean includeClientInfo); + +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/context/request/WebRequestInterceptor.java b/org.springframework.web/src/main/java/org/springframework/web/context/request/WebRequestInterceptor.java new file mode 100644 index 0000000000..366d5cff23 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/context/request/WebRequestInterceptor.java @@ -0,0 +1,89 @@ +/* + * Copyright 2002-2006 the original author or authors. + * + * 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.springframework.web.context.request; + +import org.springframework.ui.ModelMap; + +/** + * Interface for general web request interception. Allows for being applied + * to Servlet request as well as Portlet request environments, through + * building on the {@link WebRequest} abstraction. + * + *

This interface assumes MVC-style request processing: A handler gets executed, + * exposes a set of model objects, then a view gets rendered based on that model. + * Alternatively, a handler may also process the request completely, with no + * view to be rendered. + * + *

This interface is deliberatly minimalistic to keep the dependencies of + * generic request interceptors as minimal as feasible. + * + *

NOTE: While this interceptor is applied to the entire request processing + * in a Servlet environment, it is by default only applied to the render phase + * in a Portlet environment, preparing and rendering a Portlet view. To apply + * WebRequestInterceptors to the action phase as well, set the HandlerMapping's + * "applyWebRequestInterceptorsToRenderPhaseOnly" flag to "false". Alternatively, + * consider using the Portlet-specific HandlerInterceptor mechanism for such needs. + * + * @author Juergen Hoeller + * @since 2.0 + * @see ServletWebRequest + * @see org.springframework.web.servlet.DispatcherServlet + * @see org.springframework.web.servlet.handler.AbstractHandlerMapping#setInterceptors + * @see org.springframework.web.servlet.HandlerInterceptor + * @see org.springframework.web.portlet.context.PortletWebRequest + * @see org.springframework.web.portlet.DispatcherPortlet + * @see org.springframework.web.portlet.handler.AbstractHandlerMapping#setInterceptors + * @see org.springframework.web.portlet.handler.AbstractHandlerMapping#setApplyWebRequestInterceptorsToRenderPhaseOnly + * @see org.springframework.web.portlet.HandlerInterceptor + */ +public interface WebRequestInterceptor { + + /** + * Intercept the execution of a request handler before its invocation. + *

Allows for preparing context resources (such as a Hibernate Session) + * and expose them as request attributes or as thread-local objects. + * @param request the current web request + * @throws Exception in case of errors + */ + void preHandle(WebRequest request) throws Exception; + + /** + * Intercept the execution of a request handler after its successful + * invocation, right before view rendering (if any). + *

Allows for modifying context resources after successful handler + * execution (for example, flushing a Hibernate Session). + * @param request the current web request + * @param model the map of model objects that will be exposed to the view + * (may be null). Can be used to analyze the exposed model + * and/or to add further model attributes, if desired. + * @throws Exception in case of errors + */ + void postHandle(WebRequest request, ModelMap model) throws Exception; + + /** + * Callback after completion of request processing, that is, after rendering + * the view. Will be called on any outcome of handler execution, thus allows + * for proper resource cleanup. + *

Note: Will only be called if this interceptor's preHandle + * method has successfully completed! + * @param request the current web request + * @param ex exception thrown on handler execution, if any + * @throws Exception in case of errors + */ + void afterCompletion(WebRequest request, Exception ex) throws Exception; + +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/context/request/package.html b/org.springframework.web/src/main/java/org/springframework/web/context/request/package.html new file mode 100644 index 0000000000..c901ee221c --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/context/request/package.html @@ -0,0 +1,8 @@ + + + +Support for generic request context holding, in particular for +scoping of application objects per HTTP request or HTTP session. + + + diff --git a/org.springframework.web/src/main/java/org/springframework/web/context/support/AbstractRefreshableWebApplicationContext.java b/org.springframework.web/src/main/java/org/springframework/web/context/support/AbstractRefreshableWebApplicationContext.java new file mode 100644 index 0000000000..fcb815c0a4 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/context/support/AbstractRefreshableWebApplicationContext.java @@ -0,0 +1,173 @@ +/* + * Copyright 2002-2008 the original author or authors. + * + * 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.springframework.web.context.support; + +import javax.servlet.ServletConfig; +import javax.servlet.ServletContext; + +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.context.support.AbstractRefreshableConfigApplicationContext; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.ResourcePatternResolver; +import org.springframework.ui.context.Theme; +import org.springframework.ui.context.ThemeSource; +import org.springframework.ui.context.support.UiApplicationContextUtils; +import org.springframework.web.context.ConfigurableWebApplicationContext; +import org.springframework.web.context.ServletConfigAware; +import org.springframework.web.context.ServletContextAware; + +/** + * {@link org.springframework.context.support.AbstractRefreshableApplicationContext} + * subclass which implements the + * {@link org.springframework.web.context.ConfigurableWebApplicationContext} + * interface for web environments. Provides a "configLocations" property, + * to be populated through the ConfigurableWebApplicationContext interface + * on web application startup. + * + *

This class is as easy to subclass as AbstractRefreshableApplicationContext: + * All you need to implements is the {@link #loadBeanDefinitions} method; + * see the superclass javadoc for details. Note that implementations are supposed + * to load bean definitions from the files specified by the locations returned + * by the {@link #getConfigLocations} method. + * + *

Interprets resource paths as servlet context resources, i.e. as paths beneath + * the web application root. Absolute paths, e.g. for files outside the web app root, + * can be accessed via "file:" URLs, as implemented by + * {@link org.springframework.core.io.DefaultResourceLoader}. + * + *

In addition to the special beans detected by + * {@link org.springframework.context.support.AbstractApplicationContext}, + * this class detects a bean of type {@link org.springframework.ui.context.ThemeSource} + * in the context, under the special bean name "themeSource". + * + *

This is the web context to be subclassed for a different bean definition format. + * Such a context implementation can be specified as "contextClass" context-param + * for {@link org.springframework.web.context.ContextLoader} or as "contextClass" + * init-param for {@link org.springframework.web.servlet.FrameworkServlet}, + * replacing the default {@link XmlWebApplicationContext}. It will then automatically + * receive the "contextConfigLocation" context-param or init-param, respectively. + * + *

Note that WebApplicationContext implementations are generally supposed + * to configure themselves based on the configuration received through the + * {@link ConfigurableWebApplicationContext} interface. In contrast, a standalone + * application context might allow for configuration in custom startup code + * (for example, {@link org.springframework.context.support.GenericApplicationContext}). + * + * @author Juergen Hoeller + * @since 1.1.3 + * @see #loadBeanDefinitions + * @see org.springframework.web.context.ConfigurableWebApplicationContext#setConfigLocations + * @see org.springframework.ui.context.ThemeSource + * @see XmlWebApplicationContext + */ +public abstract class AbstractRefreshableWebApplicationContext extends AbstractRefreshableConfigApplicationContext + implements ConfigurableWebApplicationContext, ThemeSource { + + /** Servlet context that this context runs in */ + private ServletContext servletContext; + + /** Servlet config that this context runs in, if any */ + private ServletConfig servletConfig; + + /** Namespace of this context, or null if root */ + private String namespace; + + /** the ThemeSource for this ApplicationContext */ + private ThemeSource themeSource; + + + public AbstractRefreshableWebApplicationContext() { + setDisplayName("Root WebApplicationContext"); + } + + + public void setServletContext(ServletContext servletContext) { + this.servletContext = servletContext; + } + + public ServletContext getServletContext() { + return this.servletContext; + } + + public void setServletConfig(ServletConfig servletConfig) { + this.servletConfig = servletConfig; + if (servletConfig != null && this.servletContext == null) { + this.servletContext = servletConfig.getServletContext(); + } + } + + public ServletConfig getServletConfig() { + return this.servletConfig; + } + + public void setNamespace(String namespace) { + this.namespace = namespace; + if (namespace != null) { + setDisplayName("WebApplicationContext for namespace '" + namespace + "'"); + } + } + + public String getNamespace() { + return this.namespace; + } + + public String[] getConfigLocations() { + return super.getConfigLocations(); + } + + + /** + * Register request/session scopes, a {@link ServletContextAwareProcessor}, etc. + */ + protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) { + beanFactory.addBeanPostProcessor(new ServletContextAwareProcessor(this.servletContext, this.servletConfig)); + beanFactory.ignoreDependencyInterface(ServletContextAware.class); + beanFactory.ignoreDependencyInterface(ServletConfigAware.class); + beanFactory.registerResolvableDependency(ServletContext.class, this.servletContext); + beanFactory.registerResolvableDependency(ServletConfig.class, this.servletConfig); + + WebApplicationContextUtils.registerWebApplicationScopes(beanFactory); + } + + /** + * This implementation supports file paths beneath the root of the ServletContext. + * @see ServletContextResource + */ + protected Resource getResourceByPath(String path) { + return new ServletContextResource(this.servletContext, path); + } + + /** + * This implementation supports pattern matching in unexpanded WARs too. + * @see ServletContextResourcePatternResolver + */ + protected ResourcePatternResolver getResourcePatternResolver() { + return new ServletContextResourcePatternResolver(this); + } + + /** + * Initialize the theme capability. + */ + protected void onRefresh() { + this.themeSource = UiApplicationContextUtils.initThemeSource(this); + } + + public Theme getTheme(String themeName) { + return this.themeSource.getTheme(themeName); + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/context/support/ContextExposingHttpServletRequest.java b/org.springframework.web/src/main/java/org/springframework/web/context/support/ContextExposingHttpServletRequest.java new file mode 100644 index 0000000000..dd653aa4d8 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/context/support/ContextExposingHttpServletRequest.java @@ -0,0 +1,99 @@ +/* + * Copyright 2002-2008 the original author or authors. + * + * 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.springframework.web.context.support; + +import java.util.HashSet; +import java.util.Set; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; + +import org.springframework.util.Assert; +import org.springframework.web.context.WebApplicationContext; + +/** + * HttpServletRequest decorator that makes all Spring beans in a + * given WebApplicationContext accessible as request attributes, + * through lazy checking once an attribute gets accessed. + * + * @author Juergen Hoeller + * @since 2.5 + */ +public class ContextExposingHttpServletRequest extends HttpServletRequestWrapper { + + private final WebApplicationContext webApplicationContext; + + private final Set exposedContextBeanNames; + + private Set explicitAttributes; + + + /** + * Create a new ContextExposingHttpServletRequest for the given request. + * @param originalRequest the original HttpServletRequest + * @param context the WebApplicationContext that this request runs in + */ + public ContextExposingHttpServletRequest(HttpServletRequest originalRequest, WebApplicationContext context) { + this(originalRequest, context, null); + } + + /** + * Create a new ContextExposingHttpServletRequest for the given request. + * @param originalRequest the original HttpServletRequest + * @param context the WebApplicationContext that this request runs in + * @param exposedContextBeanNames the names of beans in the context which + * are supposed to be exposed (if this is non-null, only the beans in this + * Set are eligible for exposure as attributes) + */ + public ContextExposingHttpServletRequest( + HttpServletRequest originalRequest, WebApplicationContext context, Set exposedContextBeanNames) { + + super(originalRequest); + Assert.notNull(context, "WebApplicationContext must not be null"); + this.webApplicationContext = context; + this.exposedContextBeanNames = exposedContextBeanNames; + } + + + /** + * Return the WebApplicationContext that this request runs in. + */ + public final WebApplicationContext getWebApplicationContext() { + return this.webApplicationContext; + } + + + public Object getAttribute(String name) { + if ((this.explicitAttributes == null || !this.explicitAttributes.contains(name)) && + (this.exposedContextBeanNames == null || this.exposedContextBeanNames.contains(name)) && + this.webApplicationContext.containsBean(name)) { + return this.webApplicationContext.getBean(name); + } + else { + return super.getAttribute(name); + } + } + + public void setAttribute(String name, Object value) { + super.setAttribute(name, value); + if (this.explicitAttributes == null) { + this.explicitAttributes = new HashSet(8); + } + this.explicitAttributes.add(name); + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/context/support/GenericWebApplicationContext.java b/org.springframework.web/src/main/java/org/springframework/web/context/support/GenericWebApplicationContext.java new file mode 100644 index 0000000000..846b5e6e72 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/context/support/GenericWebApplicationContext.java @@ -0,0 +1,137 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.web.context.support; + +import javax.servlet.ServletContext; + +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.context.support.GenericApplicationContext; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.ResourcePatternResolver; +import org.springframework.ui.context.Theme; +import org.springframework.ui.context.ThemeSource; +import org.springframework.ui.context.support.UiApplicationContextUtils; +import org.springframework.web.context.ServletContextAware; +import org.springframework.web.context.WebApplicationContext; + +/** + * Subclass of {@link GenericApplicationContext}, suitable for web environments. + * + *

Implements the {@link WebApplicationContext} interface, but not + * {@link org.springframework.web.context.ConfigurableWebApplicationContext}, + * as it is not intended for declarative setup in web.xml. Instead, + * it is designed for programmatic setup, for example for building nested contexts. + * + *

If you intend to implement a WebApplicationContext that reads bean definitions + * from configuration files, consider deriving from AbstractRefreshableWebApplicationContext, + * reading the bean definitions in an implementation of the loadBeanDefinitions + * method. + * + *

Interprets resource paths as servlet context resources, i.e. as paths beneath + * the web application root. Absolute paths, e.g. for files outside the web app root, + * can be accessed via "file:" URLs, as implemented by AbstractApplicationContext. + * + *

In addition to the special beans detected by + * {@link org.springframework.context.support.AbstractApplicationContext}, + * this class detects a ThemeSource bean in the context, with the name "themeSource". + * + * @author Juergen Hoeller + * @since 1.2 + */ +public class GenericWebApplicationContext extends GenericApplicationContext + implements WebApplicationContext, ThemeSource { + + private ServletContext servletContext; + + private ThemeSource themeSource; + + + /** + * Create a new GenericWebApplicationContext. + * @see #setServletContext + * @see #registerBeanDefinition + * @see #refresh + */ + public GenericWebApplicationContext() { + super(); + } + + /** + * Create a new GenericWebApplicationContext with the given DefaultListableBeanFactory. + * @param beanFactory the DefaultListableBeanFactory instance to use for this context + * @see #setServletContext + * @see #registerBeanDefinition + * @see #refresh + */ + public GenericWebApplicationContext(DefaultListableBeanFactory beanFactory) { + super(beanFactory); + } + + + /** + * Set the ServletContext that this WebApplicationContext runs in. + */ + public void setServletContext(ServletContext servletContext) { + this.servletContext = servletContext; + } + + public ServletContext getServletContext() { + return this.servletContext; + } + + + /** + * Register ServletContextAwareProcessor. + * @see ServletContextAwareProcessor + */ + protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) { + beanFactory.addBeanPostProcessor(new ServletContextAwareProcessor(this.servletContext)); + beanFactory.ignoreDependencyInterface(ServletContextAware.class); + beanFactory.registerResolvableDependency(ServletContext.class, this.servletContext); + + WebApplicationContextUtils.registerWebApplicationScopes(beanFactory); + } + + /** + * This implementation supports file paths beneath the root of the ServletContext. + * @see ServletContextResource + */ + protected Resource getResourceByPath(String path) { + return new ServletContextResource(this.servletContext, path); + } + + /** + * This implementation supports pattern matching in unexpanded WARs too. + * @see ServletContextResourcePatternResolver + */ + protected ResourcePatternResolver getResourcePatternResolver() { + return new ServletContextResourcePatternResolver(this); + } + + /** + * Initialize the theme capability. + */ + protected void onRefresh() { + this.themeSource = UiApplicationContextUtils.initThemeSource(this); + } + + public Theme getTheme(String themeName) { + return this.themeSource.getTheme(themeName); + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/context/support/HttpRequestHandlerServlet.java b/org.springframework.web/src/main/java/org/springframework/web/context/support/HttpRequestHandlerServlet.java new file mode 100644 index 0000000000..a50b9955fe --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/context/support/HttpRequestHandlerServlet.java @@ -0,0 +1,79 @@ +/* + * Copyright 2002-2008 the original author or authors. + * + * 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.springframework.web.context.support; + +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.context.i18n.LocaleContextHolder; +import org.springframework.util.StringUtils; +import org.springframework.web.HttpRequestHandler; +import org.springframework.web.HttpRequestMethodNotSupportedException; +import org.springframework.web.context.WebApplicationContext; + +/** + * Simple HttpServlet that delegates to an {@link HttpRequestHandler} bean defined + * in Spring's root web application context. The target bean name must match the + * HttpRequestHandlerServlet servlet-name as defined in web.xml. + * + *

This can for example be used to expose a single Spring remote exporter, + * such as {@link org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter} + * or {@link org.springframework.remoting.caucho.HessianServiceExporter}, + * per HttpRequestHandlerServlet definition. This is a minimal alternative + * to defining remote exporters as beans in a DispatcherServlet context + * (with advanced mapping and interception facilities being available there). + * + * @author Juergen Hoeller + * @since 2.0 + * @see org.springframework.web.HttpRequestHandler + * @see org.springframework.web.servlet.DispatcherServlet + */ +public class HttpRequestHandlerServlet extends HttpServlet { + + private HttpRequestHandler target; + + + public void init() throws ServletException { + WebApplicationContext wac = WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext()); + this.target = (HttpRequestHandler) wac.getBean(getServletName(), HttpRequestHandler.class); + } + + + protected void service(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + + LocaleContextHolder.setLocale(request.getLocale()); + try { + this.target.handleRequest(request, response); + } + catch (HttpRequestMethodNotSupportedException ex) { + String[] supportedMethods = ((HttpRequestMethodNotSupportedException) ex).getSupportedMethods(); + if (supportedMethods != null) { + response.setHeader("Allow", StringUtils.arrayToDelimitedString(supportedMethods, ", ")); + } + response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, ex.getMessage()); + } + finally { + LocaleContextHolder.resetLocaleContext(); + } + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/context/support/PerformanceMonitorListener.java b/org.springframework.web/src/main/java/org/springframework/web/context/support/PerformanceMonitorListener.java new file mode 100644 index 0000000000..f1c9401693 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/context/support/PerformanceMonitorListener.java @@ -0,0 +1,57 @@ +/* + * Copyright 2002-2008 the original author or authors. + * + * 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.springframework.web.context.support; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.context.ApplicationEvent; +import org.springframework.context.ApplicationListener; +import org.springframework.util.ResponseTimeMonitorImpl; + +/** + * Listener that logs the response times of web requests. + * To be registered as bean in a WebApplicationContext. + * + *

Logs performance statistics using Commons Logging at "trace" level. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @since January 21, 2001 + * @see RequestHandledEvent + * @deprecated as of Spring 2.5, to be removed in Spring 3.0. + * Use a custom ApplicationListener specific to your needs instead. + */ +public class PerformanceMonitorListener implements ApplicationListener { + + protected final Log logger = LogFactory.getLog(getClass()); + + protected final ResponseTimeMonitorImpl responseTimeMonitor = new ResponseTimeMonitorImpl(); + + + public void onApplicationEvent(ApplicationEvent event) { + if (event instanceof RequestHandledEvent) { + RequestHandledEvent rhe = (RequestHandledEvent) event; + this.responseTimeMonitor.recordResponseTime(rhe.getProcessingTimeMillis()); + if (logger.isTraceEnabled()) { + logger.trace("PerformanceMonitorListener: last=[" + rhe.getProcessingTimeMillis() + "ms]; " + + this.responseTimeMonitor + "; " + rhe.getShortDescription()); + } + } + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/context/support/RequestHandledEvent.java b/org.springframework.web/src/main/java/org/springframework/web/context/support/RequestHandledEvent.java new file mode 100644 index 0000000000..5b8e356c8e --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/context/support/RequestHandledEvent.java @@ -0,0 +1,157 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.web.context.support; + +import org.springframework.context.ApplicationEvent; + +/** + * Event raised when a request is handled within an ApplicationContext. + * + *

Supported by Spring's own FrameworkServlet (through a specific + * ServletRequestHandledEvent subclass), but can also be raised by any + * other web component. Used, for example, by Spring's out-of-the-box + * PerformanceMonitorListener. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @since January 17, 2001 + * @see ServletRequestHandledEvent + * @see PerformanceMonitorListener + * @see org.springframework.web.servlet.FrameworkServlet + * @see org.springframework.context.ApplicationContext#publishEvent + */ +public class RequestHandledEvent extends ApplicationEvent { + + /** Session id that applied to the request, if any */ + private String sessionId; + + /** Usually the UserPrincipal */ + private String userName; + + /** Request processing time */ + private final long processingTimeMillis; + + /** Cause of failure, if any */ + private Throwable failureCause; + + + /** + * Create a new RequestHandledEvent with session information. + * @param source the component that published the event + * @param sessionId the id of the HTTP session, if any + * @param userName the name of the user that was associated with the + * request, if any (usually the UserPrincipal) + * @param processingTimeMillis the processing time of the request in milliseconds + */ + public RequestHandledEvent(Object source, String sessionId, String userName, long processingTimeMillis) { + super(source); + this.sessionId = sessionId; + this.userName = userName; + this.processingTimeMillis = processingTimeMillis; + } + + /** + * Create a new RequestHandledEvent with session information. + * @param source the component that published the event + * @param sessionId the id of the HTTP session, if any + * @param userName the name of the user that was associated with the + * request, if any (usually the UserPrincipal) + * @param processingTimeMillis the processing time of the request in milliseconds + * @param failureCause the cause of failure, if any + */ + public RequestHandledEvent( + Object source, String sessionId, String userName, long processingTimeMillis, Throwable failureCause) { + + this(source, sessionId, userName, processingTimeMillis); + this.failureCause = failureCause; + } + + + /** + * Return the processing time of the request in milliseconds. + */ + public long getProcessingTimeMillis() { + return this.processingTimeMillis; + } + + /** + * Return the id of the HTTP session, if any. + */ + public String getSessionId() { + return this.sessionId; + } + + /** + * Return the name of the user that was associated with the request + * (usually the UserPrincipal). + * @see javax.servlet.http.HttpServletRequest#getUserPrincipal() + */ + public String getUserName() { + return this.userName; + } + + /** + * Return whether the request failed. + */ + public boolean wasFailure() { + return (this.failureCause != null); + } + + /** + * Return the cause of failure, if any. + */ + public Throwable getFailureCause() { + return this.failureCause; + } + + + /** + * Return a short description of this event, only involving + * the most important context data. + */ + public String getShortDescription() { + StringBuffer sb = new StringBuffer(); + sb.append("session=[").append(this.sessionId).append("]; "); + sb.append("user=[").append(this.userName).append("]; "); + return sb.toString(); + } + + /** + * Return a full description of this event, involving + * all available context data. + */ + public String getDescription() { + StringBuffer sb = new StringBuffer(); + sb.append("session=[").append(this.sessionId).append("]; "); + sb.append("user=[").append(this.userName).append("]; "); + sb.append("time=[").append(this.processingTimeMillis).append("ms]; "); + sb.append("status=["); + if (!wasFailure()) { + sb.append("OK"); + } + else { + sb.append("failed: ").append(this.failureCause); + } + sb.append(']'); + return sb.toString(); + } + + public String toString() { + return ("RequestHandledEvent: " + getDescription()); + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/context/support/ServletContextAttributeExporter.java b/org.springframework.web/src/main/java/org/springframework/web/context/support/ServletContextAttributeExporter.java new file mode 100644 index 0000000000..1649a0249e --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/context/support/ServletContextAttributeExporter.java @@ -0,0 +1,84 @@ +/* + * Copyright 2002-2005 the original author or authors. + * + * 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.springframework.web.context.support; + +import java.util.Iterator; +import java.util.Map; + +import javax.servlet.ServletContext; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.web.context.ServletContextAware; + +/** + * Exporter that takes Spring-defined objects and exposes them as + * ServletContext attributes. Usually, bean references will be used + * to export Spring-defined beans as ServletContext attributes. + * + *

Useful to make Spring-defined beans available to code that is + * not aware of Spring at all, but rather just of the Servlet API. + * Client code can then use plain ServletContext attribute lookups + * to access those objects, despite them being defined in a Spring + * application context. + * + *

Alternatively, consider using the WebApplicationContextUtils + * class to access Spring-defined beans via the WebApplicationContext + * interface. This makes client code aware of Spring API, of course. + * + * @author Juergen Hoeller + * @since 1.1.4 + * @see javax.servlet.ServletContext#getAttribute + * @see WebApplicationContextUtils#getWebApplicationContext + */ +public class ServletContextAttributeExporter implements ServletContextAware { + + protected final Log logger = LogFactory.getLog(getClass()); + + private Map attributes; + + /** + * Set the ServletContext attributes to expose as key-value pairs. + * Each key will be considered a ServletContext attributes key, + * and each value will be used as corresponding attribute value. + *

Usually, you will use bean references for the values, + * to export Spring-defined beans as ServletContext attributes. + * Of course, it is also possible to define plain values to export. + * @param attributes Map with String keys and Object values + */ + public void setAttributes(Map attributes) { + this.attributes = attributes; + } + + public void setServletContext(ServletContext servletContext) { + for (Iterator it = this.attributes.entrySet().iterator(); it.hasNext();) { + Map.Entry entry = (Map.Entry) it.next(); + String attributeName = (String) entry.getKey(); + if (logger.isWarnEnabled()) { + if (servletContext.getAttribute(attributeName) != null) { + logger.warn("Overwriting existing ServletContext attribute with name '" + attributeName + "'"); + } + } + servletContext.setAttribute(attributeName, entry.getValue()); + if (logger.isInfoEnabled()) { + logger.info("Exported ServletContext attribute with name '" + attributeName + "'"); + } + } + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/context/support/ServletContextAttributeFactoryBean.java b/org.springframework.web/src/main/java/org/springframework/web/context/support/ServletContextAttributeFactoryBean.java new file mode 100644 index 0000000000..55ed97f3ee --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/context/support/ServletContextAttributeFactoryBean.java @@ -0,0 +1,76 @@ +/* + * Copyright 2002-2005 the original author or authors. + * + * 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.springframework.web.context.support; + +import javax.servlet.ServletContext; + +import org.springframework.beans.factory.FactoryBean; +import org.springframework.web.context.ServletContextAware; + +/** + * FactoryBean that fetches a specific, existing ServletContext attribute. + * Exposes that ServletContext attribute when used as bean reference, + * effectively making it available as named Spring bean instance. + * + *

Intended to link in ServletContext attributes that exist before + * the startup of the Spring application context. Typically, such + * attributes will have been put there by third-party web frameworks. + * In a purely Spring-based web application, no such linking in of + * ServletContext attrutes will be necessary. + * + * @author Juergen Hoeller + * @since 1.1.4 + * @see ServletContextParameterFactoryBean + */ +public class ServletContextAttributeFactoryBean implements FactoryBean, ServletContextAware { + + private String attributeName; + + private Object attribute; + + + /** + * Set the name of the ServletContext attribute to expose. + */ + public void setAttributeName(String attributeName) { + this.attributeName = attributeName; + } + + public void setServletContext(ServletContext servletContext) { + if (this.attributeName == null) { + throw new IllegalArgumentException("attributeName is required"); + } + this.attribute = servletContext.getAttribute(this.attributeName); + if (this.attribute == null) { + throw new IllegalStateException("No ServletContext attribute '" + this.attributeName + "' found"); + } + } + + + public Object getObject() throws Exception { + return this.attribute; + } + + public Class getObjectType() { + return (this.attribute != null ? this.attribute.getClass() : null); + } + + public boolean isSingleton() { + return true; + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/context/support/ServletContextAwareProcessor.java b/org.springframework.web/src/main/java/org/springframework/web/context/support/ServletContextAwareProcessor.java new file mode 100644 index 0000000000..3e19b43f57 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/context/support/ServletContextAwareProcessor.java @@ -0,0 +1,87 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.web.context.support; + +import javax.servlet.ServletConfig; +import javax.servlet.ServletContext; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.web.context.ServletConfigAware; +import org.springframework.web.context.ServletContextAware; + +/** + * {@link org.springframework.beans.factory.config.BeanPostProcessor} + * implementation that passes the ServletContext to beans that implement + * the {@link ServletContextAware} interface. + * + *

Web application contexts will automatically register this with their + * underlying bean factory. Applications do not use this directly. + * + * @author Juergen Hoeller + * @since 12.03.2004 + * @see org.springframework.web.context.ServletContextAware + * @see org.springframework.web.context.support.XmlWebApplicationContext#postProcessBeanFactory + */ +public class ServletContextAwareProcessor implements BeanPostProcessor { + + private ServletContext servletContext; + + private ServletConfig servletConfig; + + + /** + * Create a new ServletContextAwareProcessor for the given context. + */ + public ServletContextAwareProcessor(ServletContext servletContext) { + this(servletContext, null); + } + + /** + * Create a new ServletContextAwareProcessor for the given config. + */ + public ServletContextAwareProcessor(ServletConfig servletConfig) { + this(null, servletConfig); + } + + /** + * Create a new ServletContextAwareProcessor for the given context and config. + */ + public ServletContextAwareProcessor(ServletContext servletContext, ServletConfig servletConfig) { + this.servletContext = servletContext; + this.servletConfig = servletConfig; + if (servletContext == null && servletConfig != null) { + this.servletContext = servletConfig.getServletContext(); + } + } + + + public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { + if (this.servletContext != null && bean instanceof ServletContextAware) { + ((ServletContextAware) bean).setServletContext(this.servletContext); + } + if (this.servletConfig != null && bean instanceof ServletConfigAware) { + ((ServletConfigAware) bean).setServletConfig(this.servletConfig); + } + return bean; + } + + public Object postProcessAfterInitialization(Object bean, String beanName) { + return bean; + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/context/support/ServletContextFactoryBean.java b/org.springframework.web/src/main/java/org/springframework/web/context/support/ServletContextFactoryBean.java new file mode 100644 index 0000000000..46d47276ee --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/context/support/ServletContextFactoryBean.java @@ -0,0 +1,65 @@ +/* + * Copyright 2002-2006 the original author or authors. + * + * 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.springframework.web.context.support; + +import javax.servlet.ServletContext; + +import org.springframework.beans.factory.FactoryBean; +import org.springframework.web.context.ServletContextAware; + +/** + * Simple FactoryBean that exposes the ServletContext for bean references. + * Can be used as alternative to implementing the ServletContextAware + * callback interface. Allows for passing the ServletContext reference + * to a constructor argument or any custom bean property. + * + *

Note that there's a special FactoryBean for exposing a specific + * ServletContext attribute, named ServletContextAttributeFactoryBean. + * So if all you need from the ServletContext is access to a specific + * attribute, ServletContextAttributeFactoryBean allows you to expose + * a constructor argument or bean property of the attribute type, + * which is a preferable to a dependency on the full ServletContext. + * + * @author Juergen Hoeller + * @since 1.1.4 + * @see javax.servlet.ServletContext + * @see org.springframework.web.context.ServletContextAware + * @see ServletContextAttributeFactoryBean + */ +public class ServletContextFactoryBean implements FactoryBean, ServletContextAware { + + private ServletContext servletContext; + + + public void setServletContext(ServletContext servletContext) { + this.servletContext = servletContext; + } + + + public Object getObject() { + return this.servletContext; + } + + public Class getObjectType() { + return (this.servletContext != null ? this.servletContext.getClass() : ServletContext.class); + } + + public boolean isSingleton() { + return true; + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/context/support/ServletContextParameterFactoryBean.java b/org.springframework.web/src/main/java/org/springframework/web/context/support/ServletContextParameterFactoryBean.java new file mode 100644 index 0000000000..ec6cb6e49b --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/context/support/ServletContextParameterFactoryBean.java @@ -0,0 +1,71 @@ +/* + * Copyright 2002-2005 the original author or authors. + * + * 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.springframework.web.context.support; + +import javax.servlet.ServletContext; + +import org.springframework.beans.factory.FactoryBean; +import org.springframework.web.context.ServletContextAware; + +/** + * FactoryBean that retrieves a specific ServletContext init parameter + * (that is, a "context-param" defined in web.xml). + * Exposes that ServletContext init parameter when used as bean reference, + * effectively making it available as named Spring bean instance. + * + * @author Juergen Hoeller + * @since 1.2.4 + * @see ServletContextAttributeFactoryBean + */ +public class ServletContextParameterFactoryBean implements FactoryBean, ServletContextAware { + + private String initParamName; + + private String paramValue; + + + /** + * Set the name of the ServletContext init parameter to expose. + */ + public void setInitParamName(String initParamName) { + this.initParamName = initParamName; + } + + public void setServletContext(ServletContext servletContext) { + if (this.initParamName == null) { + throw new IllegalArgumentException("initParamName is required"); + } + this.paramValue = servletContext.getInitParameter(this.initParamName); + if (this.paramValue == null) { + throw new IllegalStateException("No ServletContext init parameter '" + this.initParamName + "' found"); + } + } + + + public Object getObject() throws Exception { + return this.paramValue; + } + + public Class getObjectType() { + return String.class; + } + + public boolean isSingleton() { + return true; + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/context/support/ServletContextPropertyPlaceholderConfigurer.java b/org.springframework.web/src/main/java/org/springframework/web/context/support/ServletContextPropertyPlaceholderConfigurer.java new file mode 100644 index 0000000000..069bad13eb --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/context/support/ServletContextPropertyPlaceholderConfigurer.java @@ -0,0 +1,158 @@ +/* + * Copyright 2002-2005 the original author or authors. + * + * 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.springframework.web.context.support; + +import java.util.Properties; + +import javax.servlet.ServletContext; + +import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer; +import org.springframework.web.context.ServletContextAware; + +/** + * Subclass of PropertyPlaceholderConfigurer that resolves placeholders as + * ServletContext init parameters (that is, web.xml context-param + * entries). + * + *

Can be combined with "locations" and/or "properties" values in addition + * to web.xml context-params. Alternatively, can be defined without local + * properties, to resolve all placeholders as web.xml context-params + * (or JVM system properties). + * + *

If a placeholder could not be resolved against the provided local + * properties within the application, this configurer will fall back to + * ServletContext parameters. Can also be configured to let ServletContext + * init parameters override local properties (contextOverride=true). + * + *

Optionally supports searching for ServletContext attributes: If turned + * on, an otherwise unresolvable placeholder will matched against the corresponding + * ServletContext attribute, using its stringified value if found. This can be + * used to feed dynamic values into Spring's placeholder resolution. + * + *

If not running within a WebApplicationContext (or any other context that + * is able to satisfy the ServletContextAware callback), this class will behave + * like the default PropertyPlaceholderConfigurer. This allows for keeping + * ServletContextPropertyPlaceholderConfigurer definitions in test suites. + * + * @author Juergen Hoeller + * @since 1.1.4 + * @see #setLocations + * @see #setProperties + * @see #setSystemPropertiesModeName + * @see #setContextOverride + * @see #setSearchContextAttributes + * @see javax.servlet.ServletContext#getInitParameter(String) + * @see javax.servlet.ServletContext#getAttribute(String) + */ +public class ServletContextPropertyPlaceholderConfigurer extends PropertyPlaceholderConfigurer + implements ServletContextAware { + + private boolean contextOverride = false; + + private boolean searchContextAttributes = false; + + private ServletContext servletContext; + + + /** + * Set whether ServletContext init parameters (and optionally also ServletContext + * attributes) should override local properties within the application. + * Default is "false": ServletContext settings serve as fallback. + *

Note that system properties will still override ServletContext settings, + * if the system properties mode is set to "SYSTEM_PROPERTIES_MODE_OVERRIDE". + * @see #setSearchContextAttributes + * @see #setSystemPropertiesModeName + * @see #SYSTEM_PROPERTIES_MODE_OVERRIDE + */ + public void setContextOverride(boolean contextOverride) { + this.contextOverride = contextOverride; + } + + /** + * Set whether to search for matching a ServletContext attribute before + * checking a ServletContext init parameter. Default is "false": only + * checking init parameters. + *

If turned on, the configurer will look for a ServletContext attribute with + * the same name as the placeholder, and use its stringified value if found. + * Exposure of such ServletContext attributes can be used to dynamically override + * init parameters defined in web.xml, for example in a custom + * context listener. + * @see javax.servlet.ServletContext#getInitParameter(String) + * @see javax.servlet.ServletContext#getAttribute(String) + */ + public void setSearchContextAttributes(boolean searchContextAttributes) { + this.searchContextAttributes = searchContextAttributes; + } + + /** + * Set the ServletContext to resolve placeholders against. + * Will be auto-populated when running in a WebApplicationContext. + *

If not set, this configurer will simply not resolve placeholders + * against the ServletContext: It will effectively behave like a plain + * PropertyPlaceholderConfigurer in such a scenario. + */ + public void setServletContext(ServletContext servletContext) { + this.servletContext = servletContext; + } + + + protected String resolvePlaceholder(String placeholder, Properties props) { + String value = null; + if (this.contextOverride && this.servletContext != null) { + value = resolvePlaceholder(placeholder, this.servletContext, this.searchContextAttributes); + } + if (value == null) { + value = super.resolvePlaceholder(placeholder, props); + } + if (value == null && this.servletContext != null) { + value = resolvePlaceholder(placeholder, this.servletContext, this.searchContextAttributes); + } + return value; + } + + /** + * Resolves the given placeholder using the init parameters + * and optionally also the attributes of the given ServletContext. + *

Default implementation checks ServletContext attributes before + * init parameters. Can be overridden to customize this behavior, + * potentially also applying specific naming patterns for parameters + * and/or attributes (instead of using the exact placeholder name). + * @param placeholder the placeholder to resolve + * @param servletContext the ServletContext to check + * @param searchContextAttributes whether to search for a matching + * ServletContext attribute + * @return the resolved value, of null if none + * @see javax.servlet.ServletContext#getInitParameter(String) + * @see javax.servlet.ServletContext#getAttribute(String) + */ + protected String resolvePlaceholder( + String placeholder, ServletContext servletContext, boolean searchContextAttributes) { + + String value = null; + if (searchContextAttributes) { + Object attrValue = servletContext.getAttribute(placeholder); + if (attrValue != null) { + value = attrValue.toString(); + } + } + if (value == null) { + value = servletContext.getInitParameter(placeholder); + } + return value; + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/context/support/ServletContextResource.java b/org.springframework.web/src/main/java/org/springframework/web/context/support/ServletContextResource.java new file mode 100644 index 0000000000..e0d5612e69 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/context/support/ServletContextResource.java @@ -0,0 +1,200 @@ +/* + * Copyright 2002-2008 the original author or authors. + * + * 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.springframework.web.context.support; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; + +import javax.servlet.ServletContext; + +import org.springframework.core.io.AbstractResource; +import org.springframework.core.io.ContextResource; +import org.springframework.core.io.Resource; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; +import org.springframework.web.util.WebUtils; + +/** + * {@link org.springframework.core.io.Resource} implementation for + * {@link javax.servlet.ServletContext} resources, interpreting + * relative paths within the web application root directory. + * + *

Always supports stream access and URL access, but only allows + * java.io.File access when the web application archive + * is expanded. + * + * @author Juergen Hoeller + * @since 28.12.2003 + * @see javax.servlet.ServletContext#getResourceAsStream + * @see javax.servlet.ServletContext#getResource + * @see javax.servlet.ServletContext#getRealPath + */ +public class ServletContextResource extends AbstractResource implements ContextResource { + + private final ServletContext servletContext; + + private final String path; + + + /** + * Create a new ServletContextResource. + *

The Servlet spec requires that resource paths start with a slash, + * even if many containers accept paths without leading slash too. + * Consequently, the given path will be prepended with a slash if it + * doesn't already start with one. + * @param servletContext the ServletContext to load from + * @param path the path of the resource + */ + public ServletContextResource(ServletContext servletContext, String path) { + // check ServletContext + Assert.notNull(servletContext, "Cannot resolve ServletContextResource without ServletContext"); + this.servletContext = servletContext; + + // check path + Assert.notNull(path, "Path is required"); + String pathToUse = StringUtils.cleanPath(path); + if (!pathToUse.startsWith("/")) { + pathToUse = "/" + pathToUse; + } + this.path = pathToUse; + } + + /** + * Return the ServletContext for this resource. + */ + public final ServletContext getServletContext() { + return this.servletContext; + } + + /** + * Return the path for this resource. + */ + public final String getPath() { + return this.path; + } + + + /** + * This implementation checks ServletContext.getResource. + * @see javax.servlet.ServletContext#getResource(String) + */ + public boolean exists() { + try { + URL url = this.servletContext.getResource(this.path); + return (url != null); + } + catch (MalformedURLException ex) { + return false; + } + } + + /** + * This implementation delegates to ServletContext.getResourceAsStream, + * but throws a FileNotFoundException if no resource found. + * @see javax.servlet.ServletContext#getResourceAsStream(String) + */ + public InputStream getInputStream() throws IOException { + InputStream is = this.servletContext.getResourceAsStream(this.path); + if (is == null) { + throw new FileNotFoundException("Could not open " + getDescription()); + } + return is; + } + + /** + * This implementation delegates to ServletContext.getResource, + * but throws a FileNotFoundException if no resource found. + * @see javax.servlet.ServletContext#getResource(String) + */ + public URL getURL() throws IOException { + URL url = this.servletContext.getResource(this.path); + if (url == null) { + throw new FileNotFoundException( + getDescription() + " cannot be resolved to URL because it does not exist"); + } + return url; + } + + /** + * This implementation delegates to ServletContext.getRealPath, + * but throws a FileNotFoundException if not found or not resolvable. + * @see javax.servlet.ServletContext#getRealPath(String) + */ + public File getFile() throws IOException { + String realPath = WebUtils.getRealPath(this.servletContext, this.path); + return new File(realPath); + } + + /** + * This implementation creates a ServletContextResource, applying the given path + * relative to the path of the underlying file of this resource descriptor. + * @see org.springframework.util.StringUtils#applyRelativePath(String, String) + */ + public Resource createRelative(String relativePath) { + String pathToUse = StringUtils.applyRelativePath(this.path, relativePath); + return new ServletContextResource(this.servletContext, pathToUse); + } + + /** + * This implementation returns the name of the file that this ServletContext + * resource refers to. + * @see org.springframework.util.StringUtils#getFilename(String) + */ + public String getFilename() { + return StringUtils.getFilename(this.path); + } + + /** + * This implementation returns a description that includes the ServletContext + * resource location. + */ + public String getDescription() { + return "ServletContext resource [" + this.path + "]"; + } + + public String getPathWithinContext() { + return this.path; + } + + + /** + * This implementation compares the underlying ServletContext resource locations. + */ + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj instanceof ServletContextResource) { + ServletContextResource otherRes = (ServletContextResource) obj; + return (this.servletContext.equals(otherRes.servletContext) && this.path.equals(otherRes.path)); + } + return false; + } + + /** + * This implementation returns the hash code of the underlying + * ServletContext resource location. + */ + public int hashCode() { + return this.path.hashCode(); + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/context/support/ServletContextResourceLoader.java b/org.springframework.web/src/main/java/org/springframework/web/context/support/ServletContextResourceLoader.java new file mode 100644 index 0000000000..6d25067a50 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/context/support/ServletContextResourceLoader.java @@ -0,0 +1,61 @@ +/* + * Copyright 2002-2005 the original author or authors. + * + * 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.springframework.web.context.support; + +import javax.servlet.ServletContext; + +import org.springframework.core.io.DefaultResourceLoader; +import org.springframework.core.io.Resource; + +/** + * ResourceLoader implementation that resolves paths as ServletContext + * resources, for use outside a WebApplicationContext (for example, + * in a HttpServletBean or GenericFilterBean subclass). + * + *

Within a WebApplicationContext, resource paths are automatically + * resolved as ServletContext resources by the context implementation. + * + * @author Juergen Hoeller + * @since 1.0.2 + * @see #getResourceByPath + * @see ServletContextResource + * @see org.springframework.web.context.WebApplicationContext + * @see org.springframework.web.servlet.HttpServletBean + * @see org.springframework.web.filter.GenericFilterBean + */ +public class ServletContextResourceLoader extends DefaultResourceLoader { + + private final ServletContext servletContext; + + + /** + * Create a new ServletContextResourceLoader. + * @param servletContext the ServletContext to load resources with + */ + public ServletContextResourceLoader(ServletContext servletContext) { + this.servletContext = servletContext; + } + + /** + * This implementation supports file paths beneath the root of the web application. + * @see ServletContextResource + */ + protected Resource getResourceByPath(String path) { + return new ServletContextResource(this.servletContext, path); + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/context/support/ServletContextResourcePatternResolver.java b/org.springframework.web/src/main/java/org/springframework/web/context/support/ServletContextResourcePatternResolver.java new file mode 100644 index 0000000000..8ec9aa76fa --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/context/support/ServletContextResourcePatternResolver.java @@ -0,0 +1,126 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.web.context.support; + +import java.io.IOException; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.Set; + +import javax.servlet.ServletContext; + +import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; +import org.springframework.util.StringUtils; + +/** + * ServletContext-aware subclass of {@link PathMatchingResourcePatternResolver}, + * able to find matching resources below the web application root directory + * via Servlet 2.3's ServletContext.getResourcePaths. + * Falls back to the superclass' file system checking for other resources. + * + * @author Juergen Hoeller + * @since 1.1.2 + */ +public class ServletContextResourcePatternResolver extends PathMatchingResourcePatternResolver { + + /** + * Create a new ServletContextResourcePatternResolver. + * @param servletContext the ServletContext to load resources with + * @see ServletContextResourceLoader#ServletContextResourceLoader(javax.servlet.ServletContext) + */ + public ServletContextResourcePatternResolver(ServletContext servletContext) { + super(new ServletContextResourceLoader(servletContext)); + } + + /** + * Create a new ServletContextResourcePatternResolver. + * @param resourceLoader the ResourceLoader to load root directories and + * actual resources with + */ + public ServletContextResourcePatternResolver(ResourceLoader resourceLoader) { + super(resourceLoader); + } + + + /** + * Overridden version which checks for ServletContextResource + * and uses ServletContext.getResourcePaths to find + * matching resources below the web application root directory. + * In case of other resources, delegates to the superclass version. + * @see #doRetrieveMatchingServletContextResources + * @see ServletContextResource + * @see javax.servlet.ServletContext#getResourcePaths + */ + protected Set doFindPathMatchingFileResources(Resource rootDirResource, String subPattern) throws IOException { + if (rootDirResource instanceof ServletContextResource) { + ServletContextResource scResource = (ServletContextResource) rootDirResource; + ServletContext sc = scResource.getServletContext(); + String fullPattern = scResource.getPath() + subPattern; + Set result = new LinkedHashSet(8); + doRetrieveMatchingServletContextResources(sc, fullPattern, scResource.getPath(), result); + return result; + } + else { + return super.doFindPathMatchingFileResources(rootDirResource, subPattern); + } + } + + /** + * Recursively retrieve ServletContextResources that match the given pattern, + * adding them to the given result set. + * @param servletContext the ServletContext to work on + * @param fullPattern the pattern to match against, + * with preprended root directory path + * @param dir the current directory + * @param result the Set of matching Resources to add to + * @throws IOException if directory contents could not be retrieved + * @see ServletContextResource + * @see javax.servlet.ServletContext#getResourcePaths + */ + protected void doRetrieveMatchingServletContextResources( + ServletContext servletContext, String fullPattern, String dir, Set result) throws IOException { + + Set candidates = servletContext.getResourcePaths(dir); + if (candidates != null) { + boolean dirDepthNotFixed = (fullPattern.indexOf("**") != -1); + for (Iterator it = candidates.iterator(); it.hasNext();) { + String currPath = (String) it.next(); + if (!currPath.startsWith(dir)) { + // Returned resource path does not start with relative directory: + // assuming absolute path returned -> strip absolute path. + int dirIndex = currPath.indexOf(dir); + if (dirIndex != -1) { + currPath = currPath.substring(dirIndex); + } + } + if (currPath.endsWith("/") && + (dirDepthNotFixed || + StringUtils.countOccurrencesOf(currPath, "/") <= StringUtils.countOccurrencesOf(fullPattern, "/"))) { + // Search subdirectories recursively: ServletContext.getResourcePaths + // only returns entries for one directory level. + doRetrieveMatchingServletContextResources(servletContext, fullPattern, currPath, result); + } + if (getPathMatcher().match(fullPattern, currPath)) { + result.add(new ServletContextResource(servletContext, currPath)); + } + } + } + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/context/support/ServletRequestHandledEvent.java b/org.springframework.web/src/main/java/org/springframework/web/context/support/ServletRequestHandledEvent.java new file mode 100644 index 0000000000..a6384fc842 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/context/support/ServletRequestHandledEvent.java @@ -0,0 +1,142 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.web.context.support; + +/** + * Servlet-specific subclass of RequestHandledEvent, + * adding servlet-specific context information. + * + * @author Juergen Hoeller + * @since 2.0 + * @see org.springframework.web.servlet.FrameworkServlet + * @see org.springframework.context.ApplicationContext#publishEvent + */ +public class ServletRequestHandledEvent extends RequestHandledEvent { + + /** URL that the triggered the request */ + private final String requestUrl; + + /** IP address that the request came from */ + private final String clientAddress; + + /** Usually GET or POST */ + private final String method; + + /** Name of the servlet that handled the request */ + private final String servletName; + + + /** + * Create a new ServletRequestHandledEvent. + * @param source the component that published the event + * @param requestUrl the URL of the request + * @param clientAddress the IP address that the request came from + * @param method the HTTP method of the request (usually GET or POST) + * @param servletName the name of the servlet that handled the request + * @param sessionId the id of the HTTP session, if any + * @param userName the name of the user that was associated with the + * request, if any (usually the UserPrincipal) + * @param processingTimeMillis the processing time of the request in milliseconds + */ + public ServletRequestHandledEvent(Object source, String requestUrl, + String clientAddress, String method, String servletName, + String sessionId, String userName, long processingTimeMillis) { + + super(source, sessionId, userName, processingTimeMillis); + this.requestUrl = requestUrl; + this.clientAddress = clientAddress; + this.method = method; + this.servletName = servletName; + } + + /** + * Create a new ServletRequestHandledEvent. + * @param source the component that published the event + * @param requestUrl the URL of the request + * @param clientAddress the IP address that the request came from + * @param method the HTTP method of the request (usually GET or POST) + * @param servletName the name of the servlet that handled the request + * @param sessionId the id of the HTTP session, if any + * @param userName the name of the user that was associated with the + * request, if any (usually the UserPrincipal) + * @param processingTimeMillis the processing time of the request in milliseconds + * @param failureCause the cause of failure, if any + */ + public ServletRequestHandledEvent(Object source, String requestUrl, + String clientAddress, String method, String servletName, String sessionId, + String userName, long processingTimeMillis, Throwable failureCause) { + + super(source, sessionId, userName, processingTimeMillis, failureCause); + this.requestUrl = requestUrl; + this.clientAddress = clientAddress; + this.method = method; + this.servletName = servletName; + } + + + /** + * Return the URL of the request. + */ + public String getRequestUrl() { + return this.requestUrl; + } + + /** + * Return the IP address that the request came from. + */ + public String getClientAddress() { + return this.clientAddress; + } + + /** + * Return the HTTP method of the request (usually GET or POST). + */ + public String getMethod() { + return this.method; + } + + /** + * Return the name of the servlet that handled the request. + */ + public String getServletName() { + return this.servletName; + } + + + public String getShortDescription() { + StringBuffer sb = new StringBuffer(); + sb.append("url=[").append(getRequestUrl()).append("]; "); + sb.append("client=[").append(getClientAddress()).append("]; "); + sb.append(super.getShortDescription()); + return sb.toString(); + } + + public String getDescription() { + StringBuffer sb = new StringBuffer(); + sb.append("url=[").append(getRequestUrl()).append("]; "); + sb.append("client=[").append(getClientAddress()).append("]; "); + sb.append("method=[").append(getMethod()).append("]; "); + sb.append("servlet=[").append(getServletName()).append("]; "); + sb.append(super.getDescription()); + return sb.toString(); + } + + public String toString() { + return "ServletRequestHandledEvent: " + getDescription(); + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/context/support/SpringBeanAutowiringSupport.java b/org.springframework.web/src/main/java/org/springframework/web/context/support/SpringBeanAutowiringSupport.java new file mode 100644 index 0000000000..b8536ffdb2 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/context/support/SpringBeanAutowiringSupport.java @@ -0,0 +1,94 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.web.context.support; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; +import org.springframework.web.context.ContextLoader; +import org.springframework.web.context.WebApplicationContext; + +/** + * Convenient base class for self-autowiring classes that gets constructed + * within a Spring-based web application. Resolves @Autowired + * annotations in the endpoint class against beans in the current Spring + * root web application context (as determined by the current thread's + * context ClassLoader, which needs to be the web application's ClassLoader). + * Can alternatively be used as a delegate instead of as a base class. + * + *

A typical usage of this base class is a JAX-WS endpoint class: + * Such a Spring-based JAX-WS endpoint implementation will follow the + * standard JAX-WS contract for endpoint classes but will be 'thin' + * in that it delegates the actual work to one or more Spring-managed + * service beans - typically obtained using @Autowired. + * The lifecycle of such an endpoint instance will be managed by the + * JAX-WS runtime, hence the need for this base class to provide + * @Autowired processing based on the current Spring context. + * + *

NOTE: If there is an explicit way to access the ServletContext, + * prefer such a way over using this class. The {@link WebApplicationContextUtils} + * class allows for easy access to the Spring root web application context + * based on the ServletContext. + * + * @author Juergen Hoeller + * @since 2.5.1 + * @see WebApplicationObjectSupport + */ +public abstract class SpringBeanAutowiringSupport { + + private static final Log logger = LogFactory.getLog(SpringBeanAutowiringSupport.class); + + + /** + * This constructor performs injection on this instance, + * based on the current web application context. + *

Intended for use as a base class. + * @see #processInjectionBasedOnCurrentContext + */ + public SpringBeanAutowiringSupport() { + processInjectionBasedOnCurrentContext(this); + } + + + /** + * Process @Autowired injection for the given target object, + * based on the current web application context. + *

Intended for use as a delegate. + * @param target the target object to process + * @see org.springframework.web.context.ContextLoader#getCurrentWebApplicationContext() + */ + public static void processInjectionBasedOnCurrentContext(Object target) { + Assert.notNull(target, "Target object must not be null"); + WebApplicationContext cc = ContextLoader.getCurrentWebApplicationContext(); + if (cc != null) { + AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); + bpp.setBeanFactory(cc.getAutowireCapableBeanFactory()); + bpp.processInjection(target); + } + else { + if (logger.isDebugEnabled()) { + logger.debug("Current WebApplicationContext is not available for processing of " + + ClassUtils.getShortName(target.getClass()) + ": " + + "Make sure this class gets constructed in a Spring web application. Proceeding without injection."); + } + } + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/context/support/StaticWebApplicationContext.java b/org.springframework.web/src/main/java/org/springframework/web/context/support/StaticWebApplicationContext.java new file mode 100644 index 0000000000..e03eb44196 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/context/support/StaticWebApplicationContext.java @@ -0,0 +1,172 @@ +/* + * Copyright 2002-2008 the original author or authors. + * + * 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.springframework.web.context.support; + +import javax.servlet.ServletConfig; +import javax.servlet.ServletContext; + +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.context.support.StaticApplicationContext; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.ResourcePatternResolver; +import org.springframework.ui.context.Theme; +import org.springframework.ui.context.ThemeSource; +import org.springframework.ui.context.support.UiApplicationContextUtils; +import org.springframework.web.context.ConfigurableWebApplicationContext; +import org.springframework.web.context.ServletConfigAware; +import org.springframework.web.context.ServletContextAware; +import org.springframework.web.context.request.RequestScope; +import org.springframework.web.context.request.SessionScope; + +/** + * Static {@link org.springframework.web.context.WebApplicationContext} + * implementation for testing. Not intended for use in production applications. + * + *

Implements the {@link org.springframework.web.context.ConfigurableWebApplicationContext} + * interface to allow for direct replacement of an {@link XmlWebApplicationContext}, + * despite not actually supporting external configuration files. + * + *

Interprets resource paths as servlet context resources, i.e. as paths beneath + * the web application root. Absolute paths, e.g. for files outside the web app root, + * can be accessed via "file:" URLs, as implemented by + * {@link org.springframework.core.io.DefaultResourceLoader}. + * + *

In addition to the special beans detected by + * {@link org.springframework.context.support.AbstractApplicationContext}, + * this class detects a bean of type {@link org.springframework.ui.context.ThemeSource} + * in the context, under the special bean name "themeSource". + * + * @author Rod Johnson + * @author Juergen Hoeller + * @see org.springframework.ui.context.ThemeSource + */ +public class StaticWebApplicationContext extends StaticApplicationContext + implements ConfigurableWebApplicationContext, ThemeSource { + + private ServletContext servletContext; + + private ServletConfig servletConfig; + + private String namespace; + + private ThemeSource themeSource; + + + public StaticWebApplicationContext() { + setDisplayName("Root WebApplicationContext"); + } + + + /** + * Set the ServletContext that this WebApplicationContext runs in. + */ + public void setServletContext(ServletContext servletContext) { + this.servletContext = servletContext; + } + + public ServletContext getServletContext() { + return this.servletContext; + } + + public void setServletConfig(ServletConfig servletConfig) { + this.servletConfig = servletConfig; + if (servletConfig != null && this.servletContext == null) { + this.servletContext = servletConfig.getServletContext(); + } + } + + public ServletConfig getServletConfig() { + return this.servletConfig; + } + + public void setNamespace(String namespace) { + this.namespace = namespace; + if (namespace != null) { + setDisplayName("WebApplicationContext for namespace '" + namespace + "'"); + } + } + + public String getNamespace() { + return this.namespace; + } + + /** + * The {@link StaticWebApplicationContext} class does not support this method. + * @throws UnsupportedOperationException always + */ + public void setConfigLocation(String configLocation) { + if (configLocation != null) { + throw new UnsupportedOperationException("StaticWebApplicationContext does not support config locations"); + } + } + + /** + * The {@link StaticWebApplicationContext} class does not support this method. + * @throws UnsupportedOperationException always + */ + public void setConfigLocations(String[] configLocations) { + if (configLocations != null) { + throw new UnsupportedOperationException("StaticWebApplicationContext does not support config locations"); + } + } + + public String[] getConfigLocations() { + return null; + } + + + /** + * Register request/session scopes, a {@link ServletContextAwareProcessor}, etc. + */ + protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) { + beanFactory.registerScope(SCOPE_REQUEST, new RequestScope()); + beanFactory.registerScope(SCOPE_SESSION, new SessionScope(false)); + beanFactory.registerScope(SCOPE_GLOBAL_SESSION, new SessionScope(true)); + + beanFactory.addBeanPostProcessor(new ServletContextAwareProcessor(this.servletContext, this.servletConfig)); + beanFactory.ignoreDependencyInterface(ServletContextAware.class); + beanFactory.ignoreDependencyInterface(ServletConfigAware.class); + } + + /** + * This implementation supports file paths beneath the root of the ServletContext. + * @see ServletContextResource + */ + protected Resource getResourceByPath(String path) { + return new ServletContextResource(this.servletContext, path); + } + + /** + * This implementation supports pattern matching in unexpanded WARs too. + * @see ServletContextResourcePatternResolver + */ + protected ResourcePatternResolver getResourcePatternResolver() { + return new ServletContextResourcePatternResolver(this); + } + + /** + * Initialize the theme capability. + */ + protected void onRefresh() { + this.themeSource = UiApplicationContextUtils.initThemeSource(this); + } + + public Theme getTheme(String themeName) { + return this.themeSource.getTheme(themeName); + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/context/support/WebApplicationContextUtils.java b/org.springframework.web/src/main/java/org/springframework/web/context/support/WebApplicationContextUtils.java new file mode 100644 index 0000000000..48f1d136af --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/context/support/WebApplicationContextUtils.java @@ -0,0 +1,149 @@ +/* + * Copyright 2002-2008 the original author or authors. + * + * 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.springframework.web.context.support; + +import javax.servlet.ServletContext; +import javax.servlet.ServletRequest; +import javax.servlet.http.HttpSession; + +import org.springframework.beans.factory.ObjectFactory; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.util.Assert; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.context.request.RequestAttributes; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.RequestScope; +import org.springframework.web.context.request.ServletRequestAttributes; +import org.springframework.web.context.request.SessionScope; + +/** + * Convenience methods for retrieving the root + * {@link org.springframework.web.context.WebApplicationContext} for a given + * ServletContext. This is e.g. useful for accessing a Spring + * context from within custom web views or Struts actions. + * + *

Note that there are more convenient ways of accessing the root context for + * many web frameworks, either part of Spring or available as external library. + * This helper class is just the most generic way to access the root context. + * + * @author Juergen Hoeller + * @see org.springframework.web.context.ContextLoader + * @see org.springframework.web.servlet.FrameworkServlet + * @see org.springframework.web.servlet.DispatcherServlet + * @see org.springframework.web.struts.ActionSupport + * @see org.springframework.web.struts.DelegatingActionProxy + * @see org.springframework.web.jsf.FacesContextUtils + * @see org.springframework.web.jsf.DelegatingVariableResolver + */ +public abstract class WebApplicationContextUtils { + + /** + * Find the root WebApplicationContext for this web application, which is + * typically loaded via {@link org.springframework.web.context.ContextLoaderListener} or + * {@link org.springframework.web.context.ContextLoaderServlet}. + *

Will rethrow an exception that happened on root context startup, + * to differentiate between a failed context startup and no context at all. + * @param sc ServletContext to find the web application context for + * @return the root WebApplicationContext for this web app + * @throws IllegalStateException if the root WebApplicationContext could not be found + * @see org.springframework.web.context.WebApplicationContext#ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + */ + public static WebApplicationContext getRequiredWebApplicationContext(ServletContext sc) + throws IllegalStateException { + + WebApplicationContext wac = getWebApplicationContext(sc); + if (wac == null) { + throw new IllegalStateException("No WebApplicationContext found: no ContextLoaderListener registered?"); + } + return wac; + } + + /** + * Find the root WebApplicationContext for this web application, which is + * typically loaded via {@link org.springframework.web.context.ContextLoaderListener} or + * {@link org.springframework.web.context.ContextLoaderServlet}. + *

Will rethrow an exception that happened on root context startup, + * to differentiate between a failed context startup and no context at all. + * @param sc ServletContext to find the web application context for + * @return the root WebApplicationContext for this web app, or null if none + * @see org.springframework.web.context.WebApplicationContext#ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + */ + public static WebApplicationContext getWebApplicationContext(ServletContext sc) { + return getWebApplicationContext(sc, WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE); + } + + /** + * Find a custom WebApplicationContext for this web application. + * @param sc ServletContext to find the web application context for + * @param attrName the name of the ServletContext attribute to look for + * @return the desired WebApplicationContext for this web app, or null if none + */ + public static WebApplicationContext getWebApplicationContext(ServletContext sc, String attrName) { + Assert.notNull(sc, "ServletContext must not be null"); + Object attr = sc.getAttribute(attrName); + if (attr == null) { + return null; + } + if (attr instanceof RuntimeException) { + throw (RuntimeException) attr; + } + if (attr instanceof Error) { + throw (Error) attr; + } + if (attr instanceof Exception) { + IllegalStateException ex = new IllegalStateException(); + ex.initCause((Exception) attr); + throw ex; + } + if (!(attr instanceof WebApplicationContext)) { + throw new IllegalStateException("Context attribute is not of type WebApplicationContext: " + attr); + } + return (WebApplicationContext) attr; + } + + + /** + * Register web-specific scopes with the given BeanFactory, + * as used by the WebApplicationContext. + * @param beanFactory the BeanFactory to configure + */ + public static void registerWebApplicationScopes(ConfigurableListableBeanFactory beanFactory) { + beanFactory.registerScope(WebApplicationContext.SCOPE_REQUEST, new RequestScope()); + beanFactory.registerScope(WebApplicationContext.SCOPE_SESSION, new SessionScope(false)); + beanFactory.registerScope(WebApplicationContext.SCOPE_GLOBAL_SESSION, new SessionScope(true)); + + beanFactory.registerResolvableDependency(ServletRequest.class, new ObjectFactory() { + public Object getObject() { + RequestAttributes requestAttr = RequestContextHolder.currentRequestAttributes(); + if (!(requestAttr instanceof ServletRequestAttributes)) { + throw new IllegalStateException("Current request is not a servlet request"); + } + return ((ServletRequestAttributes) requestAttr).getRequest(); + } + }); + beanFactory.registerResolvableDependency(HttpSession.class, new ObjectFactory() { + public Object getObject() { + RequestAttributes requestAttr = RequestContextHolder.currentRequestAttributes(); + if (!(requestAttr instanceof ServletRequestAttributes)) { + throw new IllegalStateException("Current request is not a servlet request"); + } + return ((ServletRequestAttributes) requestAttr).getRequest().getSession(); + } + }); + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/context/support/WebApplicationObjectSupport.java b/org.springframework.web/src/main/java/org/springframework/web/context/support/WebApplicationObjectSupport.java new file mode 100644 index 0000000000..d544335739 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/context/support/WebApplicationObjectSupport.java @@ -0,0 +1,140 @@ +/* + * Copyright 2002-2008 the original author or authors. + * + * 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.springframework.web.context.support; + +import java.io.File; + +import javax.servlet.ServletContext; + +import org.springframework.context.ApplicationContext; +import org.springframework.context.support.ApplicationObjectSupport; +import org.springframework.web.context.ServletContextAware; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.util.WebUtils; + +/** + * Convenient superclass for application objects running in a WebApplicationContext. + * Provides getWebApplicationContext(), getServletContext(), + * and getTempDir() methods. + * + * @author Juergen Hoeller + * @since 28.08.2003 + * @see SpringBeanAutowiringSupport + */ +public abstract class WebApplicationObjectSupport extends ApplicationObjectSupport + implements ServletContextAware { + + private ServletContext servletContext; + + + public final void setServletContext(ServletContext servletContext) { + this.servletContext = servletContext; + if (servletContext != null) { + initServletContext(servletContext); + } + } + + /** + * Overrides the base class behavior to enforce running in an ApplicationContext. + * All accessors will throw IllegalStateException if not running in a context. + * @see #getApplicationContext() + * @see #getMessageSourceAccessor() + * @see #getWebApplicationContext() + * @see #getServletContext() + * @see #getTempDir() + */ + protected boolean isContextRequired() { + return true; + } + + /** + * Calls {@link #initServletContext(javax.servlet.ServletContext)} if the + * given ApplicationContext is a {@link WebApplicationContext}. + */ + protected void initApplicationContext(ApplicationContext context) { + super.initApplicationContext(context); + if (context instanceof WebApplicationContext) { + ServletContext servletContext = ((WebApplicationContext) context).getServletContext(); + if (servletContext != null) { + initServletContext(servletContext); + } + } + } + + /** + * Subclasses may override this for custom initialization based + * on the ServletContext that this application object runs in. + *

The default implementation is empty. Called by + * {@link #initApplicationContext(org.springframework.context.ApplicationContext)} + * as well as {@link #setServletContext(javax.servlet.ServletContext)}. + * @param servletContext the ServletContext that this application object runs in + * (never null) + */ + protected void initServletContext(ServletContext servletContext) { + } + + /** + * Return the current application context as WebApplicationContext. + *

NOTE: Only use this if you actually need to access + * WebApplicationContext-specific functionality. Preferably use + * getApplicationContext() or getServletContext() + * else, to be able to run in non-WebApplicationContext environments as well. + * @throws IllegalStateException if not running in a WebApplicationContext + * @see #getApplicationContext() + */ + protected final WebApplicationContext getWebApplicationContext() throws IllegalStateException { + ApplicationContext ctx = getApplicationContext(); + if (ctx instanceof WebApplicationContext) { + return (WebApplicationContext) getApplicationContext(); + } + else if (isContextRequired()) { + throw new IllegalStateException("WebApplicationObjectSupport instance [" + this + + "] does not run in a WebApplicationContext but in: " + ctx); + } + else { + return null; + } + } + + /** + * Return the current ServletContext. + * @throws IllegalStateException if not running within a ServletContext + */ + protected final ServletContext getServletContext() throws IllegalStateException { + if (this.servletContext != null) { + return this.servletContext; + } + ServletContext servletContext = getWebApplicationContext().getServletContext(); + if (servletContext == null && isContextRequired()) { + throw new IllegalStateException("WebApplicationObjectSupport instance [" + this + + "] does not run within a ServletContext. Make sure the object is fully configured!"); + } + return servletContext; + } + + /** + * Return the temporary directory for the current web application, + * as provided by the servlet container. + * @return the File representing the temporary directory + * @throws IllegalStateException if not running within a ServletContext + * @see org.springframework.web.util.WebUtils#getTempDir(javax.servlet.ServletContext) + */ + protected final File getTempDir() throws IllegalStateException { + return WebUtils.getTempDir(getServletContext()); + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/context/support/XmlWebApplicationContext.java b/org.springframework.web/src/main/java/org/springframework/web/context/support/XmlWebApplicationContext.java new file mode 100644 index 0000000000..6d85039b71 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/context/support/XmlWebApplicationContext.java @@ -0,0 +1,143 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.web.context.support; + +import java.io.IOException; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.beans.factory.xml.ResourceEntityResolver; +import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; + +/** + * {@link org.springframework.web.context.WebApplicationContext} implementation + * which takes its configuration from XML documents, understood by an + * {@link org.springframework.beans.factory.xml.XmlBeanDefinitionReader}. + * This is essentially the equivalent of + * {@link org.springframework.context.support.AbstractXmlApplicationContext} + * for a web environment. + * + *

By default, the configuration will be taken from "/WEB-INF/applicationContext.xml" + * for the root context, and "/WEB-INF/test-servlet.xml" for a context with the namespace + * "test-servlet" (like for a DispatcherServlet instance with the servlet-name "test"). + * + *

The config location defaults can be overridden via the "contextConfigLocation" + * context-param of {@link org.springframework.web.context.ContextLoader} and servlet + * init-param of {@link org.springframework.web.servlet.FrameworkServlet}. Config locations + * can either denote concrete files like "/WEB-INF/context.xml" or Ant-style patterns + * like "/WEB-INF/*-context.xml" (see {@link org.springframework.util.PathMatcher} + * javadoc for pattern details). + * + *

Note: In case of multiple config locations, later bean definitions will + * override ones defined in earlier loaded files. This can be leveraged to + * deliberately override certain bean definitions via an extra XML file. + * + *

For a WebApplicationContext that reads in a different bean definition format, + * create an analogous subclass of {@link AbstractRefreshableWebApplicationContext}. + * Such a context implementation can be specified as "contextClass" context-param + * for ContextLoader or "contextClass" init-param for FrameworkServlet. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @see #setNamespace + * @see #setConfigLocations + * @see org.springframework.beans.factory.xml.XmlBeanDefinitionReader + * @see org.springframework.web.context.ContextLoader#initWebApplicationContext + * @see org.springframework.web.servlet.FrameworkServlet#initWebApplicationContext + */ +public class XmlWebApplicationContext extends AbstractRefreshableWebApplicationContext { + + /** Default config location for the root context */ + public static final String DEFAULT_CONFIG_LOCATION = "/WEB-INF/applicationContext.xml"; + + /** Default prefix for building a config location for a namespace */ + public static final String DEFAULT_CONFIG_LOCATION_PREFIX = "/WEB-INF/"; + + /** Default suffix for building a config location for a namespace */ + public static final String DEFAULT_CONFIG_LOCATION_SUFFIX = ".xml"; + + + /** + * Loads the bean definitions via an XmlBeanDefinitionReader. + * @see org.springframework.beans.factory.xml.XmlBeanDefinitionReader + * @see #initBeanDefinitionReader + * @see #loadBeanDefinitions + */ + protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws IOException { + // Create a new XmlBeanDefinitionReader for the given BeanFactory. + XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory); + + // Configure the bean definition reader with this context's + // resource loading environment. + beanDefinitionReader.setResourceLoader(this); + beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this)); + + // Allow a subclass to provide custom initialization of the reader, + // then proceed with actually loading the bean definitions. + initBeanDefinitionReader(beanDefinitionReader); + loadBeanDefinitions(beanDefinitionReader); + } + + /** + * Initialize the bean definition reader used for loading the bean + * definitions of this context. Default implementation is empty. + *

Can be overridden in subclasses, e.g. for turning off XML validation + * or using a different XmlBeanDefinitionParser implementation. + * @param beanDefinitionReader the bean definition reader used by this context + * @see org.springframework.beans.factory.xml.XmlBeanDefinitionReader#setValidationMode + * @see org.springframework.beans.factory.xml.XmlBeanDefinitionReader#setDocumentReaderClass + */ + protected void initBeanDefinitionReader(XmlBeanDefinitionReader beanDefinitionReader) { + } + + /** + * Load the bean definitions with the given XmlBeanDefinitionReader. + *

The lifecycle of the bean factory is handled by the refreshBeanFactory method; + * therefore this method is just supposed to load and/or register bean definitions. + *

Delegates to a ResourcePatternResolver for resolving location patterns + * into Resource instances. + * @throws org.springframework.beans.BeansException in case of bean registration errors + * @throws java.io.IOException if the required XML document isn't found + * @see #refreshBeanFactory + * @see #getConfigLocations + * @see #getResources + * @see #getResourcePatternResolver + */ + protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException { + String[] configLocations = getConfigLocations(); + if (configLocations != null) { + for (int i = 0; i < configLocations.length; i++) { + reader.loadBeanDefinitions(configLocations[i]); + } + } + } + + /** + * The default location for the root context is "/WEB-INF/applicationContext.xml", + * and "/WEB-INF/test-servlet.xml" for a context with the namespace "test-servlet" + * (like for a DispatcherServlet instance with the servlet-name "test"). + */ + protected String[] getDefaultConfigLocations() { + if (getNamespace() != null) { + return new String[] {DEFAULT_CONFIG_LOCATION_PREFIX + getNamespace() + DEFAULT_CONFIG_LOCATION_SUFFIX}; + } + else { + return new String[] {DEFAULT_CONFIG_LOCATION}; + } + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/context/support/package.html b/org.springframework.web/src/main/java/org/springframework/web/context/support/package.html new file mode 100644 index 0000000000..6545916284 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/context/support/package.html @@ -0,0 +1,8 @@ + + + +Classes supporting the org.springframework.web.context package, +such as WebApplicationContext implementations and various utility classes. + + + diff --git a/org.springframework.web/src/main/java/org/springframework/web/filter/AbstractRequestLoggingFilter.java b/org.springframework.web/src/main/java/org/springframework/web/filter/AbstractRequestLoggingFilter.java new file mode 100644 index 0000000000..e801ed4485 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/filter/AbstractRequestLoggingFilter.java @@ -0,0 +1,231 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.web.filter; + +import java.io.IOException; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +import org.springframework.util.StringUtils; + +/** + * Base class for Filters that perform logging operations before and after a + * request is processed. + * + *

Subclasses should override the beforeRequest(HttpServletRequest, String) + * and afterRequest(HttpServletRequest, String) methods to perform the actual + * logging around the request. + * + *

Subclasses are passed the message to write to the log in the beforeRequest + * and afterRequest methods. By default, only the URI of the request is logged. + * However, setting the includeQueryString property to true will + * cause the query string of the request to be included also. + * + *

Prefixes and suffixes for the before and after messages can be configured + * using the beforeMessagePrefix, afterMessagePrefix, + * beforeMessageSuffix and afterMessageSuffix properties, + * + * @author Rob Harrop + * @author Juergen Hoeller + * @since 1.2.5 + * @see #beforeRequest + * @see #afterRequest + */ +public abstract class AbstractRequestLoggingFilter extends OncePerRequestFilter { + + public static final String DEFAULT_BEFORE_MESSAGE_PREFIX = "Before request ["; + + public static final String DEFAULT_BEFORE_MESSAGE_SUFFIX = "]"; + + public static final String DEFAULT_AFTER_MESSAGE_PREFIX = "After request ["; + + public static final String DEFAULT_AFTER_MESSAGE_SUFFIX = "]"; + + + private boolean includeQueryString = false; + + private boolean includeClientInfo = false; + + private String beforeMessagePrefix = DEFAULT_BEFORE_MESSAGE_PREFIX; + + private String beforeMessageSuffix = DEFAULT_BEFORE_MESSAGE_SUFFIX; + + private String afterMessagePrefix = DEFAULT_AFTER_MESSAGE_PREFIX; + + private String afterMessageSuffix = DEFAULT_AFTER_MESSAGE_SUFFIX; + + + /** + * Set whether or not the query string should be included in the log message. + *

Should be configured using an <init-param> for parameter + * name "includeQueryString" in the filter definition in web.xml. + */ + public void setIncludeQueryString(boolean includeQueryString) { + this.includeQueryString = includeQueryString; + } + + /** + * Return whether or not the query string should be included in the log message. + */ + protected boolean isIncludeQueryString() { + return this.includeQueryString; + } + + /** + * Set whether or not the client address and session id should be included + * in the log message. + *

Should be configured using an <init-param> for parameter + * name "includeClientInfo" in the filter definition in web.xml. + */ + public void setIncludeClientInfo(boolean includeClientInfo) { + this.includeClientInfo = includeClientInfo; + } + + /** + * Return whether or not the client address and session id should be included + * in the log message. + */ + protected boolean isIncludeClientInfo() { + return this.includeClientInfo; + } + + /** + * Set the value that should be prepended to the log message written + * before a request is processed. + */ + public void setBeforeMessagePrefix(String beforeMessagePrefix) { + this.beforeMessagePrefix = beforeMessagePrefix; + } + + /** + * Set the value that should be apppended to the log message written + * before a request is processed. + */ + public void setBeforeMessageSuffix(String beforeMessageSuffix) { + this.beforeMessageSuffix = beforeMessageSuffix; + } + + /** + * Set the value that should be prepended to the log message written + * after a request is processed. + */ + public void setAfterMessagePrefix(String afterMessagePrefix) { + this.afterMessagePrefix = afterMessagePrefix; + } + + /** + * Set the value that should be appended to the log message written + * after a request is processed. + */ + public void setAfterMessageSuffix(String afterMessageSuffix) { + this.afterMessageSuffix = afterMessageSuffix; + } + + + /** + * Forwards the request to the next filter in the chain and delegates + * down to the subclasses to perform the actual request logging both + * before and after the request is processed. + * @see #beforeRequest + * @see #afterRequest + */ + protected void doFilterInternal( + HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) + throws ServletException, IOException { + + beforeRequest(request, getBeforeMessage(request)); + try { + filterChain.doFilter(request, response); + } + finally { + afterRequest(request, getAfterMessage(request)); + } + } + + + /** + * Get the message to write to the log before the request. + * @see #createMessage + */ + private String getBeforeMessage(HttpServletRequest request) { + return createMessage(request, this.beforeMessagePrefix, this.beforeMessageSuffix); + } + + /** + * Get the message to write to the log after the request. + * @see #createMessage + */ + private String getAfterMessage(HttpServletRequest request) { + return createMessage(request, this.afterMessagePrefix, this.afterMessageSuffix); + } + + /** + * Create a log message for the given request, prefix and suffix. + *

If includeQueryString is true then + * the inner part of the log message will take the form + * request_uri?query_string otherwise the message will + * simply be of the form request_uri. + *

The final message is composed of the inner part as described + * and the supplied prefix and suffix. + */ + protected String createMessage(HttpServletRequest request, String prefix, String suffix) { + StringBuffer buffer = new StringBuffer(); + buffer.append(prefix); + buffer.append("uri=").append(request.getRequestURI()); + if (isIncludeQueryString()) { + buffer.append('?').append(request.getQueryString()); + } + if (isIncludeClientInfo()) { + String client = request.getRemoteAddr(); + if (StringUtils.hasLength(client)) { + buffer.append(";client=").append(client); + } + HttpSession session = request.getSession(false); + if (session != null) { + buffer.append(";session=").append(session.getId()); + } + String user = request.getRemoteUser(); + if (user != null) { + buffer.append(";user=").append(user); + } + } + buffer.append(suffix); + return buffer.toString(); + } + + + /** + * Concrete subclasses should implement this method to write a log message + * before the request is processed. + * @param request current HTTP request + * @param message the message to log + */ + protected abstract void beforeRequest(HttpServletRequest request, String message); + + /** + * Concrete subclasses should implement this method to write a log message + * after the request is processed. + * @param request current HTTP request + * @param message the message to log + */ + protected abstract void afterRequest(HttpServletRequest request, String message); + +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/filter/CharacterEncodingFilter.java b/org.springframework.web/src/main/java/org/springframework/web/filter/CharacterEncodingFilter.java new file mode 100644 index 0000000000..14cf070134 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/filter/CharacterEncodingFilter.java @@ -0,0 +1,99 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.web.filter; + +import java.io.IOException; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.util.ClassUtils; + +/** + * Servlet 2.3/2.4 Filter that allows one to specify a character encoding for + * requests. This is useful because current browsers typically do not set a + * character encoding even if specified in the HTML page or form. + * + *

This filter can either apply its encoding if the request does not + * already specify an encoding, or enforce this filter's encoding in any case + * ("forceEncoding"="true"). In the latter case, the encoding will also be + * applied as default response encoding on Servlet 2.4+ containers (although + * this will usually be overridden by a full content type set in the view). + * + * @author Juergen Hoeller + * @since 15.03.2004 + * @see #setEncoding + * @see #setForceEncoding + * @see javax.servlet.http.HttpServletRequest#setCharacterEncoding + * @see javax.servlet.http.HttpServletResponse#setCharacterEncoding + */ +public class CharacterEncodingFilter extends OncePerRequestFilter { + + // Determine whether the Servlet 2.4 HttpServletResponse.setCharacterEncoding(String) + // method is available, for use in the "doFilterInternal" implementation. + private final static boolean responseSetCharacterEncodingAvailable = ClassUtils.hasMethod( + HttpServletResponse.class, "setCharacterEncoding", new Class[] {String.class}); + + + private String encoding; + + private boolean forceEncoding = false; + + + /** + * Set the encoding to use for requests. This encoding will be passed into a + * {@link javax.servlet.http.HttpServletRequest#setCharacterEncoding} call. + *

Whether this encoding will override existing request encodings + * (and whether it will be applied as default response encoding as well) + * depends on the {@link #setForceEncoding "forceEncoding"} flag. + */ + public void setEncoding(String encoding) { + this.encoding = encoding; + } + + /** + * Set whether the configured {@link #setEncoding encoding} of this filter + * is supposed to override existing request and response encodings. + *

Default is "false", i.e. do not modify the encoding if + * {@link javax.servlet.http.HttpServletRequest#getCharacterEncoding()} + * returns a non-null value. Switch this to "true" to enforce the specified + * encoding in any case, applying it as default response encoding as well. + *

Note that the response encoding will only be set on Servlet 2.4+ + * containers, since Servlet 2.3 did not provide a facility for setting + * a default response encoding. + */ + public void setForceEncoding(boolean forceEncoding) { + this.forceEncoding = forceEncoding; + } + + + protected void doFilterInternal( + HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) + throws ServletException, IOException { + + if (this.encoding != null && (this.forceEncoding || request.getCharacterEncoding() == null)) { + request.setCharacterEncoding(this.encoding); + if (this.forceEncoding && responseSetCharacterEncodingAvailable) { + response.setCharacterEncoding(this.encoding); + } + } + filterChain.doFilter(request, response); + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/filter/CommonsRequestLoggingFilter.java b/org.springframework.web/src/main/java/org/springframework/web/filter/CommonsRequestLoggingFilter.java new file mode 100644 index 0000000000..32fc88cd39 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/filter/CommonsRequestLoggingFilter.java @@ -0,0 +1,54 @@ +/* + * Copyright 2002-2005 the original author or authors. + * + * 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.springframework.web.filter; + +import javax.servlet.http.HttpServletRequest; + +/** + * Simple request logging filter that writes the request URI + * (and optionally the query string) to the Commons Log. + * + * @author Rob Harrop + * @since 1.2.5 + * @see #setIncludeQueryString + * @see #setBeforeMessagePrefix + * @see #setBeforeMessageSuffix + * @see #setAfterMessagePrefix + * @see #setAfterMessageSuffix + * @see org.apache.commons.logging.Log#debug(Object) + */ +public class CommonsRequestLoggingFilter extends AbstractRequestLoggingFilter { + + /** + * Writes a log message before the request is processed. + */ + protected void beforeRequest(HttpServletRequest request, String message) { + if (logger.isDebugEnabled()) { + logger.debug(message); + } + } + + /** + * Writes a log message after the request is processed. + */ + protected void afterRequest(HttpServletRequest request, String message) { + if (logger.isDebugEnabled()) { + logger.debug(message); + } + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/filter/DelegatingFilterProxy.java b/org.springframework.web/src/main/java/org/springframework/web/filter/DelegatingFilterProxy.java new file mode 100644 index 0000000000..ff64b2c54a --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/filter/DelegatingFilterProxy.java @@ -0,0 +1,252 @@ +/* + * Copyright 2002-2008 the original author or authors. + * + * 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.springframework.web.filter; + +import java.io.IOException; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; + +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.context.support.WebApplicationContextUtils; + +/** + * Proxy for a standard Servlet 2.3 Filter, delegating to a Spring-managed + * bean that implements the Filter interface. Supports a "targetBeanName" + * filter init-param in web.xml, specifying the name of the + * target bean in the Spring application context. + * + *

web.xml will usually contain a DelegatingFilterProxy definition, + * with the specified filter-name corresponding to a bean name in + * Spring's root application context. All calls to the filter proxy will then + * be delegated to that bean in the Spring context, which is required to implement + * the standard Servlet 2.3 Filter interface. + * + *

This approach is particularly useful for Filter implementation with complex + * setup needs, allowing to apply the full Spring bean definition machinery to + * Filter instances. Alternatively, consider standard Filter setup in combination + * with looking up service beans from the Spring root application context. + * + *

NOTE: The lifecycle methods defined by the Servlet Filter interface + * will by default not be delegated to the target bean, relying on the + * Spring application context to manage the lifecycle of that bean. Specifying + * the "targetFilterLifecycle" filter init-param as "true" will enforce invocation + * of the Filter.init and Filter.destroy lifecycle methods + * on the target bean, letting the servlet container manage the filter lifecycle. + * + *

This class is inspired by Acegi Security's FilterToBeanProxy class, + * written by Ben Alex. + * + * @author Juergen Hoeller + * @author Sam Brannen + * @since 1.2 + * @see #setTargetBeanName + * @see #setTargetFilterLifecycle + * @see javax.servlet.Filter#doFilter + * @see javax.servlet.Filter#init + * @see javax.servlet.Filter#destroy + */ +public class DelegatingFilterProxy extends GenericFilterBean { + + private String contextAttribute; + + private String targetBeanName; + + private boolean targetFilterLifecycle = false; + + private Filter delegate; + + private final Object delegateMonitor = new Object(); + + + /** + * Set the name of the ServletContext attribute which should be used to retrieve the + * {@link WebApplicationContext} from which to load the delegate {@link Filter} bean. + */ + public void setContextAttribute(String contextAttribute) { + this.contextAttribute = contextAttribute; + } + + /** + * Return the name of the ServletContext attribute which should be used to retrieve the + * {@link WebApplicationContext} from which to load the delegate {@link Filter} bean. + */ + public String getContextAttribute() { + return this.contextAttribute; + } + + /** + * Set the name of the target bean in the Spring application context. + * The target bean must implement the standard Servlet 2.3 Filter interface. + *

By default, the filter-name as specified for the + * DelegatingFilterProxy in web.xml will be used. + */ + public void setTargetBeanName(String targetBeanName) { + this.targetBeanName = targetBeanName; + } + + /** + * Return the name of the target bean in the Spring application context. + */ + protected String getTargetBeanName() { + return this.targetBeanName; + } + + /** + * Set whether to invoke the Filter.init and + * Filter.destroy lifecycle methods on the target bean. + *

Default is "false"; target beans usually rely on the Spring application + * context for managing their lifecycle. Setting this flag to "true" means + * that the servlet container will control the lifecycle of the target + * Filter, with this proxy delegating the corresponding calls. + */ + public void setTargetFilterLifecycle(boolean targetFilterLifecycle) { + this.targetFilterLifecycle = targetFilterLifecycle; + } + + /** + * Return whether to invoke the Filter.init and + * Filter.destroy lifecycle methods on the target bean. + */ + protected boolean isTargetFilterLifecycle() { + return this.targetFilterLifecycle; + } + + + protected void initFilterBean() throws ServletException { + // If no target bean name specified, use filter name. + if (this.targetBeanName == null) { + this.targetBeanName = getFilterName(); + } + + // Fetch Spring root application context and initialize the delegate early, + // if possible. If the root application context will be started after this + // filter proxy, we'll have to resort to lazy initialization. + synchronized (this.delegateMonitor) { + WebApplicationContext wac = findWebApplicationContext(); + if (wac != null) { + this.delegate = initDelegate(wac); + } + } + } + + public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) + throws ServletException, IOException { + + // Lazily initialize the delegate if necessary. + Filter delegateToUse = null; + synchronized (this.delegateMonitor) { + if (this.delegate == null) { + WebApplicationContext wac = findWebApplicationContext(); + if (wac == null) { + throw new IllegalStateException("No WebApplicationContext found: no ContextLoaderListener registered?"); + } + this.delegate = initDelegate(wac); + } + delegateToUse = this.delegate; + } + + // Let the delegate perform the actual doFilter operation. + invokeDelegate(delegateToUse, request, response, filterChain); + } + + public void destroy() { + Filter delegateToUse = null; + synchronized (this.delegateMonitor) { + delegateToUse = this.delegate; + } + if (delegateToUse != null) { + destroyDelegate(delegateToUse); + } + } + + + /** + * Retrieve a WebApplicationContext from the ServletContext + * attribute with the {@link #setContextAttribute configured name}. The + * WebApplicationContext must have already been loaded and stored in the + * ServletContext before this filter gets initialized (or invoked). + *

Subclasses may override this method to provide a different + * WebApplicationContext retrieval strategy. + * @return the WebApplicationContext for this proxy, or null if not found + * @see #getContextAttribute() + */ + protected WebApplicationContext findWebApplicationContext() { + String attrName = getContextAttribute(); + if (attrName != null) { + return WebApplicationContextUtils.getWebApplicationContext(getServletContext(), attrName); + } + else { + return WebApplicationContextUtils.getWebApplicationContext(getServletContext()); + } + } + + /** + * Initialize the Filter delegate, defined as bean the given Spring + * application context. + *

Default implementation fetches the bean from the application context + * and calls the standard Filter.init method on it, passing + * in the FilterConfig of this Filter proxy. + * @param wac the root application context + * @return the initialized delegate Filter + * @throws ServletException if thrown by the Filter + * @see #getTargetBeanName() + * @see #isTargetFilterLifecycle() + * @see #getFilterConfig() + * @see javax.servlet.Filter#init(javax.servlet.FilterConfig) + */ + protected Filter initDelegate(WebApplicationContext wac) throws ServletException { + Filter delegate = (Filter) wac.getBean(getTargetBeanName(), Filter.class); + if (isTargetFilterLifecycle()) { + delegate.init(getFilterConfig()); + } + return delegate; + } + + /** + * Actually invoke the delegate Filter with the given request and response. + * @param delegate the delegate Filter + * @param request the current HTTP request + * @param response the current HTTP response + * @param filterChain the current FilterChain + * @throws ServletException if thrown by the Filter + * @throws IOException if thrown by the Filter + */ + protected void invokeDelegate( + Filter delegate, ServletRequest request, ServletResponse response, FilterChain filterChain) + throws ServletException, IOException { + + delegate.doFilter(request, response, filterChain); + } + + /** + * Destroy the Filter delegate. + * Default implementation simply calls Filter.destroy on it. + * @param delegate the Filter delegate (never null) + * @see #isTargetFilterLifecycle() + * @see javax.servlet.Filter#destroy() + */ + protected void destroyDelegate(Filter delegate) { + if (isTargetFilterLifecycle()) { + delegate.destroy(); + } + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/filter/GenericFilterBean.java b/org.springframework.web/src/main/java/org/springframework/web/filter/GenericFilterBean.java new file mode 100644 index 0000000000..d4e1fa8c06 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/filter/GenericFilterBean.java @@ -0,0 +1,304 @@ +/* + * Copyright 2002-2008 the original author or authors. + * + * 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.springframework.web.filter; + +import java.util.Enumeration; +import java.util.HashSet; +import java.util.Set; + +import javax.servlet.Filter; +import javax.servlet.FilterConfig; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.beans.BeanWrapper; +import org.springframework.beans.BeansException; +import org.springframework.beans.MutablePropertyValues; +import org.springframework.beans.PropertyAccessorFactory; +import org.springframework.beans.PropertyValue; +import org.springframework.beans.PropertyValues; +import org.springframework.beans.factory.BeanNameAware; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceEditor; +import org.springframework.core.io.ResourceLoader; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; +import org.springframework.web.context.ServletContextAware; +import org.springframework.web.context.support.ServletContextResourceLoader; +import org.springframework.web.util.NestedServletException; + +/** + * Simple base implementation of {@link javax.servlet.Filter} which treats + * its config parameters (init-param entries within the + * filter tag in web.xml) as bean properties. + * + *

A handy superclass for any type of filter. Type conversion of config + * parameters is automatic, with the corresponding setter method getting + * invoked with the converted value. It is also possible for subclasses to + * specify required properties. Parameters without matching bean property + * setter will simply be ignored. + * + *

This filter leaves actual filtering to subclasses, which have to + * implement the {@link javax.servlet.Filter#doFilter} method. + * + *

This generic filter base class has no dependency on the Spring + * {@link org.springframework.context.ApplicationContext} concept. + * Filters usually don't load their own context but rather access service + * beans from the Spring root application context, accessible via the + * filter's {@link #getServletContext() ServletContext} (see + * {@link org.springframework.web.context.support.WebApplicationContextUtils}). + * + * @author Juergen Hoeller + * @since 06.12.2003 + * @see #addRequiredProperty + * @see #initFilterBean + * @see #doFilter + */ +public abstract class GenericFilterBean implements + Filter, BeanNameAware, ServletContextAware, InitializingBean, DisposableBean { + + /** Logger available to subclasses */ + protected final Log logger = LogFactory.getLog(getClass()); + + /** + * Set of required properties (Strings) that must be supplied as + * config parameters to this filter. + */ + private final Set requiredProperties = new HashSet(); + + /* The FilterConfig of this filter */ + private FilterConfig filterConfig; + + + private String beanName; + + private ServletContext servletContext; + + + /** + * Stores the bean name as defined in the Spring bean factory. + *

Only relevant in case of initialization as bean, to have a name as + * fallback to the filter name usually provided by a FilterConfig instance. + * @see org.springframework.beans.factory.BeanNameAware + * @see #getFilterName() + */ + public final void setBeanName(String beanName) { + this.beanName = beanName; + } + + /** + * Stores the ServletContext that the bean factory runs in. + *

Only relevant in case of initialization as bean, to have a ServletContext + * as fallback to the context usually provided by a FilterConfig instance. + * @see org.springframework.web.context.ServletContextAware + * @see #getServletContext() + */ + public final void setServletContext(ServletContext servletContext) { + this.servletContext = servletContext; + } + + /** + * Calls the initFilterBean() method that might + * contain custom initialization of a subclass. + *

Only relevant in case of initialization as bean, where the + * standard init(FilterConfig) method won't be called. + * @see #initFilterBean() + * @see #init(javax.servlet.FilterConfig) + */ + public void afterPropertiesSet() throws ServletException { + initFilterBean(); + } + + + /** + * Subclasses can invoke this method to specify that this property + * (which must match a JavaBean property they expose) is mandatory, + * and must be supplied as a config parameter. This should be called + * from the constructor of a subclass. + *

This method is only relevant in case of traditional initialization + * driven by a FilterConfig instance. + * @param property name of the required property + */ + protected final void addRequiredProperty(String property) { + this.requiredProperties.add(property); + } + + /** + * Standard way of initializing this filter. + * Map config parameters onto bean properties of this filter, and + * invoke subclass initialization. + * @param filterConfig the configuration for this filter + * @throws ServletException if bean properties are invalid (or required + * properties are missing), or if subclass initialization fails. + * @see #initFilterBean + */ + public final void init(FilterConfig filterConfig) throws ServletException { + Assert.notNull(filterConfig, "FilterConfig must not be null"); + if (logger.isDebugEnabled()) { + logger.debug("Initializing filter '" + filterConfig.getFilterName() + "'"); + } + + this.filterConfig = filterConfig; + + // Set bean properties from init parameters. + try { + PropertyValues pvs = new FilterConfigPropertyValues(filterConfig, this.requiredProperties); + BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this); + ResourceLoader resourceLoader = new ServletContextResourceLoader(filterConfig.getServletContext()); + bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader)); + initBeanWrapper(bw); + bw.setPropertyValues(pvs, true); + } + catch (BeansException ex) { + String msg = "Failed to set bean properties on filter '" + + filterConfig.getFilterName() + "': " + ex.getMessage(); + logger.error(msg, ex); + throw new NestedServletException(msg, ex); + } + + // Let subclasses do whatever initialization they like. + initFilterBean(); + + if (logger.isDebugEnabled()) { + logger.debug("Filter '" + filterConfig.getFilterName() + "' configured successfully"); + } + } + + /** + * Initialize the BeanWrapper for this GenericFilterBean, + * possibly with custom editors. + *

This default implementation is empty. + * @param bw the BeanWrapper to initialize + * @throws BeansException if thrown by BeanWrapper methods + * @see org.springframework.beans.BeanWrapper#registerCustomEditor + */ + protected void initBeanWrapper(BeanWrapper bw) throws BeansException { + } + + + /** + * Make the FilterConfig of this filter available, if any. + * Analogous to GenericServlet's getServletConfig(). + *

Public to resemble the getFilterConfig() method + * of the Servlet Filter version that shipped with WebLogic 6.1. + * @return the FilterConfig instance, or null if none available + * @see javax.servlet.GenericServlet#getServletConfig() + */ + public final FilterConfig getFilterConfig() { + return this.filterConfig; + } + + /** + * Make the name of this filter available to subclasses. + * Analogous to GenericServlet's getServletName(). + *

Takes the FilterConfig's filter name by default. + * If initialized as bean in a Spring application context, + * it falls back to the bean name as defined in the bean factory. + * @return the filter name, or null if none available + * @see javax.servlet.GenericServlet#getServletName() + * @see javax.servlet.FilterConfig#getFilterName() + * @see #setBeanName + */ + protected final String getFilterName() { + return (this.filterConfig != null ? this.filterConfig.getFilterName() : this.beanName); + } + + /** + * Make the ServletContext of this filter available to subclasses. + * Analogous to GenericServlet's getServletContext(). + *

Takes the FilterConfig's ServletContext by default. + * If initialized as bean in a Spring application context, + * it falls back to the ServletContext that the bean factory runs in. + * @return the ServletContext instance, or null if none available + * @see javax.servlet.GenericServlet#getServletContext() + * @see javax.servlet.FilterConfig#getServletContext() + * @see #setServletContext + */ + protected final ServletContext getServletContext() { + return (this.filterConfig != null ? this.filterConfig.getServletContext() : this.servletContext); + } + + + /** + * Subclasses may override this to perform custom initialization. + * All bean properties of this filter will have been set before this + * method is invoked. + *

Note: This method will be called from standard filter initialization + * as well as filter bean initialization in a Spring application context. + * Filter name and ServletContext will be available in both cases. + *

This default implementation is empty. + * @throws ServletException if subclass initialization fails + * @see #getFilterName() + * @see #getServletContext() + */ + protected void initFilterBean() throws ServletException { + } + + /** + * Subclasses may override this to perform custom filter shutdown. + *

Note: This method will be called from standard filter destruction + * as well as filter bean destruction in a Spring application context. + *

This default implementation is empty. + */ + public void destroy() { + } + + + /** + * PropertyValues implementation created from FilterConfig init parameters. + */ + private static class FilterConfigPropertyValues extends MutablePropertyValues { + + /** + * Create new FilterConfigPropertyValues. + * @param config FilterConfig we'll use to take PropertyValues from + * @param requiredProperties set of property names we need, where + * we can't accept default values + * @throws ServletException if any required properties are missing + */ + public FilterConfigPropertyValues(FilterConfig config, Set requiredProperties) + throws ServletException { + + Set missingProps = (requiredProperties != null && !requiredProperties.isEmpty()) ? + new HashSet(requiredProperties) : null; + + Enumeration en = config.getInitParameterNames(); + while (en.hasMoreElements()) { + String property = (String) en.nextElement(); + Object value = config.getInitParameter(property); + addPropertyValue(new PropertyValue(property, value)); + if (missingProps != null) { + missingProps.remove(property); + } + } + + // Fail if we are still missing properties. + if (missingProps != null && missingProps.size() > 0) { + throw new ServletException( + "Initialization from FilterConfig for filter '" + config.getFilterName() + + "' failed; the following required properties were missing: " + + StringUtils.collectionToDelimitedString(missingProps, ", ")); + } + } + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/filter/Log4jNestedDiagnosticContextFilter.java b/org.springframework.web/src/main/java/org/springframework/web/filter/Log4jNestedDiagnosticContextFilter.java new file mode 100644 index 0000000000..85453393d4 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/filter/Log4jNestedDiagnosticContextFilter.java @@ -0,0 +1,82 @@ +/* + * Copyright 2002-2008 the original author or authors. + * + * 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.springframework.web.filter; + +import javax.servlet.http.HttpServletRequest; + +import org.apache.log4j.Logger; +import org.apache.log4j.NDC; + +/** + * Request logging filter that adds the request log message to the Log4J + * nested diagnostic context (NDC) before the request is processed, + * removing it again after the request is processed. + * + * @author Juergen Hoeller + * @author Rob Harrop + * @since 1.2.5 + * @see #setIncludeQueryString + * @see #setBeforeMessagePrefix + * @see #setBeforeMessageSuffix + * @see #setAfterMessagePrefix + * @see #setAfterMessageSuffix + * @see org.apache.log4j.NDC#push(String) + * @see org.apache.log4j.NDC#pop() + */ +public class Log4jNestedDiagnosticContextFilter extends AbstractRequestLoggingFilter { + + /** Logger available to subclasses */ + protected final Logger log4jLogger = Logger.getLogger(getClass()); + + + /** + * Logs the before-request message through Log4J and + * adds a message the Log4J NDC before the request is processed. + */ + protected void beforeRequest(HttpServletRequest request, String message) { + if (log4jLogger.isDebugEnabled()) { + log4jLogger.debug(message); + } + NDC.push(getNestedDiagnosticContextMessage(request)); + } + + /** + * Determine the message to be pushed onto the Log4J nested diagnostic context. + *

Default is a plain request log message without prefix or suffix. + * @param request current HTTP request + * @return the message to be pushed onto the Log4J NDC + * @see #createMessage + */ + protected String getNestedDiagnosticContextMessage(HttpServletRequest request) { + return createMessage(request, "", ""); + } + + /** + * Removes the log message from the Log4J NDC after the request is processed + * and logs the after-request message through Log4J. + */ + protected void afterRequest(HttpServletRequest request, String message) { + NDC.pop(); + if (NDC.getDepth() == 0) { + NDC.remove(); + } + if (log4jLogger.isDebugEnabled()) { + log4jLogger.debug(message); + } + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/filter/OncePerRequestFilter.java b/org.springframework.web/src/main/java/org/springframework/web/filter/OncePerRequestFilter.java new file mode 100644 index 0000000000..2f53f6be1b --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/filter/OncePerRequestFilter.java @@ -0,0 +1,125 @@ +/* + * Copyright 2002-2008 the original author or authors. + * + * 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.springframework.web.filter; + +import java.io.IOException; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * Filter base class that guarantees to be just executed once per request, + * on any servlet container. It provides a {@link #doFilterInternal} + * method with HttpServletRequest and HttpServletResponse arguments. + * + *

The {@link #getAlreadyFilteredAttributeName} method determines how + * to identify that a request is already filtered. The default implementation + * is based on the configured name of the concrete filter instance. + * + * @author Juergen Hoeller + * @since 06.12.2003 + */ +public abstract class OncePerRequestFilter extends GenericFilterBean { + + /** + * Suffix that gets appended to the filter name for the + * "already filtered" request attribute. + * @see #getAlreadyFilteredAttributeName + */ + public static final String ALREADY_FILTERED_SUFFIX = ".FILTERED"; + + + /** + * This doFilter implementation stores a request attribute for + * "already filtered", proceeding without filtering again if the + * attribute is already there. + * @see #getAlreadyFilteredAttributeName + * @see #shouldNotFilter + * @see #doFilterInternal + */ + public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) + throws ServletException, IOException { + + if (!(request instanceof HttpServletRequest) || !(response instanceof HttpServletResponse)) { + throw new ServletException("OncePerRequestFilter just supports HTTP requests"); + } + HttpServletRequest httpRequest = (HttpServletRequest) request; + HttpServletResponse httpResponse = (HttpServletResponse) response; + + String alreadyFilteredAttributeName = getAlreadyFilteredAttributeName(); + if (request.getAttribute(alreadyFilteredAttributeName) != null || shouldNotFilter(httpRequest)) { + // Proceed without invoking this filter... + filterChain.doFilter(request, response); + } + else { + // Do invoke this filter... + request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE); + try { + doFilterInternal(httpRequest, httpResponse, filterChain); + } + finally { + // Remove the "already filtered" request attribute for this request. + request.removeAttribute(alreadyFilteredAttributeName); + } + } + } + + /** + * Return the name of the request attribute that identifies that a request + * is already filtered. + *

Default implementation takes the configured name of the concrete filter + * instance and appends ".FILTERED". If the filter is not fully initialized, + * it falls back to its class name. + * @see #getFilterName + * @see #ALREADY_FILTERED_SUFFIX + */ + protected String getAlreadyFilteredAttributeName() { + String name = getFilterName(); + if (name == null) { + name = getClass().getName(); + } + return name + ALREADY_FILTERED_SUFFIX; + } + + /** + * Can be overridden in subclasses for custom filtering control, + * returning true to avoid filtering of the given request. + *

The default implementation always returns false. + * @param request current HTTP request + * @return whether the given request should not be filtered + * @throws ServletException in case of errors + */ + protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException { + return false; + } + + + /** + * Same contract as for doFilter, but guaranteed to be + * just invoked once per request. Provides HttpServletRequest and + * HttpServletResponse arguments instead of the default ServletRequest + * and ServletResponse ones. + */ + protected abstract void doFilterInternal( + HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) + throws ServletException, IOException; + +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/filter/RequestContextFilter.java b/org.springframework.web/src/main/java/org/springframework/web/filter/RequestContextFilter.java new file mode 100644 index 0000000000..ce2a31dd12 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/filter/RequestContextFilter.java @@ -0,0 +1,95 @@ +/* + * Copyright 2002-2008 the original author or authors. + * + * 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.springframework.web.filter; + +import java.io.IOException; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.context.i18n.LocaleContextHolder; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +/** + * Servlet 2.3 Filter that exposes the request to the current thread, + * through both {@link org.springframework.context.i18n.LocaleContextHolder} and + * {@link RequestContextHolder}. To be registered as filter in web.xml. + * + *

Alternatively, Spring's {@link org.springframework.web.context.request.RequestContextListener} + * and Spring's {@link org.springframework.web.servlet.DispatcherServlet} also expose + * the same request context to the current thread. + * + *

This filter is mainly for use with third-party servlets, e.g. the JSF FacesServlet. + * Within Spring's own web support, DispatcherServlet's processing is perfectly sufficient. + * + * @author Juergen Hoeller + * @author Rod Johnson + * @since 2.0 + * @see org.springframework.context.i18n.LocaleContextHolder + * @see org.springframework.web.context.request.RequestContextHolder + * @see org.springframework.web.context.request.RequestContextListener + * @see org.springframework.web.servlet.DispatcherServlet + */ +public class RequestContextFilter extends OncePerRequestFilter { + + private boolean threadContextInheritable = false; + + + /** + * Set whether to expose the LocaleContext and RequestAttributes as inheritable + * for child threads (using an {@link java.lang.InheritableThreadLocal}). + *

Default is "false", to avoid side effects on spawned background threads. + * Switch this to "true" to enable inheritance for custom child threads which + * are spawned during request processing and only used for this request + * (that is, ending after their initial task, without reuse of the thread). + *

WARNING: Do not use inheritance for child threads if you are + * accessing a thread pool which is configured to potentially add new threads + * on demand (e.g. a JDK {@link java.util.concurrent.ThreadPoolExecutor}), + * since this will expose the inherited context to such a pooled thread. + */ + public void setThreadContextInheritable(boolean threadContextInheritable) { + this.threadContextInheritable = threadContextInheritable; + } + + + protected void doFilterInternal( + HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) + throws ServletException, IOException { + + ServletRequestAttributes attributes = new ServletRequestAttributes(request); + LocaleContextHolder.setLocale(request.getLocale(), this.threadContextInheritable); + RequestContextHolder.setRequestAttributes(attributes, this.threadContextInheritable); + if (logger.isDebugEnabled()) { + logger.debug("Bound request context to thread: " + request); + } + try { + filterChain.doFilter(request, response); + } + finally { + RequestContextHolder.resetRequestAttributes(); + LocaleContextHolder.resetLocaleContext(); + attributes.requestCompleted(); + if (logger.isDebugEnabled()) { + logger.debug("Cleared thread-bound request context: " + request); + } + } + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/filter/ServletContextRequestLoggingFilter.java b/org.springframework.web/src/main/java/org/springframework/web/filter/ServletContextRequestLoggingFilter.java new file mode 100644 index 0000000000..fef384d473 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/filter/ServletContextRequestLoggingFilter.java @@ -0,0 +1,50 @@ +/* + * Copyright 2002-2005 the original author or authors. + * + * 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.springframework.web.filter; + +import javax.servlet.http.HttpServletRequest; + +/** + * Simple request logging filter that writes the request URI + * (and optionally the query string) to the ServletContext log. + * + * @author Juergen Hoeller + * @since 1.2.5 + * @see #setIncludeQueryString + * @see #setBeforeMessagePrefix + * @see #setBeforeMessageSuffix + * @see #setAfterMessagePrefix + * @see #setAfterMessageSuffix + * @see javax.servlet.ServletContext#log(String) + */ +public class ServletContextRequestLoggingFilter extends AbstractRequestLoggingFilter { + + /** + * Writes a log message before the request is processed. + */ + protected void beforeRequest(HttpServletRequest request, String message) { + getServletContext().log(message); + } + + /** + * Writes a log message after the request is processed. + */ + protected void afterRequest(HttpServletRequest request, String message) { + getServletContext().log(message); + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/filter/package.html b/org.springframework.web/src/main/java/org/springframework/web/filter/package.html new file mode 100644 index 0000000000..19eb29db9a --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/filter/package.html @@ -0,0 +1,7 @@ + + + +Provides generic filter base classes allowing for bean-style configuration. + + + diff --git a/org.springframework.web/src/main/java/org/springframework/web/jsf/DecoratingNavigationHandler.java b/org.springframework.web/src/main/java/org/springframework/web/jsf/DecoratingNavigationHandler.java new file mode 100644 index 0000000000..1baf8adaf6 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/jsf/DecoratingNavigationHandler.java @@ -0,0 +1,151 @@ +/* + * Copyright 2002-2006 the original author or authors. + * + * 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.springframework.web.jsf; + +import javax.faces.application.NavigationHandler; +import javax.faces.context.FacesContext; + +/** + * Base class for JSF NavigationHandler implementations that want + * to be capable of decorating an original NavigationHandler. + * + *

Supports the standard JSF style of decoration (through a constructor argument) + * as well as an overloaded handleNavigation method with explicit + * NavigationHandler argument (passing in the original NavigationHandler). Subclasses + * are forced to implement this overloaded handleNavigation method. + * Standard JSF invocations will automatically delegate to the overloaded method, + * with the constructor-injected NavigationHandler as argument. + * + * @author Juergen Hoeller + * @since 1.2.7 + * @see DelegatingNavigationHandlerProxy + */ +public abstract class DecoratingNavigationHandler extends NavigationHandler { + + private NavigationHandler decoratedNavigationHandler; + + + /** + * Create a DecoratingNavigationHandler without fixed original NavigationHandler. + */ + protected DecoratingNavigationHandler() { + } + + /** + * Create a DecoratingNavigationHandler with fixed original NavigationHandler. + * @param originalNavigationHandler the original NavigationHandler to decorate + */ + protected DecoratingNavigationHandler(NavigationHandler originalNavigationHandler) { + this.decoratedNavigationHandler = originalNavigationHandler; + } + + /** + * Return the fixed original NavigationHandler decorated by this handler, if any + * (that is, if passed in through the constructor). + */ + public final NavigationHandler getDecoratedNavigationHandler() { + return decoratedNavigationHandler; + } + + + /** + * This implementation of the standard JSF handleNavigation method + * delegates to the overloaded variant, passing in constructor-injected + * NavigationHandler as argument. + * @see #handleNavigation(javax.faces.context.FacesContext, String, String, javax.faces.application.NavigationHandler) + */ + public final void handleNavigation(FacesContext facesContext, String fromAction, String outcome) { + handleNavigation(facesContext, fromAction, outcome, this.decoratedNavigationHandler); + } + + /** + * Special handleNavigation variant with explicit NavigationHandler + * argument. Either called directly, by code with an explicit original handler, + * or called from the standard handleNavigation method, as + * plain JSF-defined NavigationHandler. + *

Implementations should invoke callNextHandlerInChain to + * delegate to the next handler in the chain. This will always call the most + * appropriate next handler (see callNextHandlerInChain javadoc). + * Alternatively, the decorated NavigationHandler or the passed-in original + * NavigationHandler can also be called directly; however, this is not as + * flexible in terms of reacting to potential positions in the chain. + * @param facesContext the current JSF context + * @param fromAction the action binding expression that was evaluated to retrieve the + * specified outcome, or null if the outcome was acquired by some other means + * @param outcome the logical outcome returned by a previous invoked application action + * (which may be null) + * @param originalNavigationHandler the original NavigationHandler, + * or null if none + * @see #callNextHandlerInChain + */ + public abstract void handleNavigation( + FacesContext facesContext, String fromAction, String outcome, NavigationHandler originalNavigationHandler); + + + /** + * Method to be called by subclasses when intending to delegate to the next + * handler in the NavigationHandler chain. Will always call the most + * appropriate next handler, either the decorated NavigationHandler passed + * in as constructor argument or the original NavigationHandler as passed + * into this method - according to the position of this instance in the chain. + *

Will call the decorated NavigationHandler specified as constructor + * argument, if any. In case of a DecoratingNavigationHandler as target, the + * original NavigationHandler as passed into this method will be passed on to + * the next element in the chain: This ensures propagation of the original + * handler that the last element in the handler chain might delegate back to. + * In case of a standard NavigationHandler as target, the original handler + * will simply not get passed on; no delegating back to the original is + * possible further down the chain in that scenario. + *

If no decorated NavigationHandler specified as constructor argument, + * this instance is the last element in the chain. Hence, this method will + * call the original NavigationHandler as passed into this method. If no + * original NavigantionHandler has been passed in (for example if this + * instance is the last element in a chain with standard NavigationHandlers + * as earlier elements), this method corresponds to a no-op. + * @param facesContext the current JSF context + * @param fromAction the action binding expression that was evaluated to retrieve the + * specified outcome, or null if the outcome was acquired by some other means + * @param outcome the logical outcome returned by a previous invoked application action + * (which may be null) + * @param originalNavigationHandler the original NavigationHandler, + * or null if none + */ + protected final void callNextHandlerInChain( + FacesContext facesContext, String fromAction, String outcome, NavigationHandler originalNavigationHandler) { + + NavigationHandler decoratedNavigationHandler = getDecoratedNavigationHandler(); + + if (decoratedNavigationHandler instanceof DecoratingNavigationHandler) { + // DecoratingNavigationHandler specified through constructor argument: + // Call it with original NavigationHandler passed in. + DecoratingNavigationHandler decHandler = (DecoratingNavigationHandler) decoratedNavigationHandler; + decHandler.handleNavigation(facesContext, fromAction, outcome, originalNavigationHandler); + } + else if (decoratedNavigationHandler != null) { + // Standard NavigationHandler specified through constructor argument: + // Call it through standard API, without original NavigationHandler passed in. + // The called handler will not be able to redirect to the original handler. + decoratedNavigationHandler.handleNavigation(facesContext, fromAction, outcome); + } + else if (originalNavigationHandler != null) { + // No NavigationHandler specified through constructor argument: + // Call original handler, marking the end of this chain. + originalNavigationHandler.handleNavigation(facesContext, fromAction, outcome); + } + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/jsf/DelegatingNavigationHandlerProxy.java b/org.springframework.web/src/main/java/org/springframework/web/jsf/DelegatingNavigationHandlerProxy.java new file mode 100644 index 0000000000..b984ab091f --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/jsf/DelegatingNavigationHandlerProxy.java @@ -0,0 +1,167 @@ +/* + * Copyright 2002-2006 the original author or authors. + * + * 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.springframework.web.jsf; + +import javax.faces.application.NavigationHandler; +import javax.faces.context.FacesContext; + +import org.springframework.beans.factory.BeanFactory; +import org.springframework.web.context.WebApplicationContext; + +/** + * JSF NavigationHandler implementation that delegates to a NavigationHandler + * bean obtained from the Spring root WebApplicationContext. + * + *

Configure this handler proxy in your faces-config.xml file + * as follows: + * + *

+ * <application>
+ *   ...
+ *   <navigation-handler>
+ * 	   org.springframework.web.jsf.DelegatingNavigationHandlerProxy
+ *   </navigation-handler>
+ *   ...
+ * </application>
+ * + * By default, the Spring ApplicationContext will be searched for the NavigationHandler + * under the bean name "jsfNavigationHandler". In the simplest case, this is a plain + * Spring bean definition like the following. However, all of Spring's bean configuration + * power can be applied to such a bean, in particular all flavors of dependency injection. + * + *
+ * <bean name="jsfNavigationHandler" class="mypackage.MyNavigationHandler">
+ *   <property name="myProperty" ref="myOtherBean"/>
+ * </bean>
+ * + * The target NavigationHandler bean will typically extend the standard JSF + * NavigationHandler class. However, note that decorating the original + * NavigationHandler (the JSF provider's default handler) is not supported + * in such a scenario, since we can't inject the original handler in standard + * JSF style (that is, as constructor argument). + * + *

For decorating the original NavigationHandler, make sure that your + * target bean extends Spring's DecoratingNavigationHandler class. This + * allows to pass in the original handler as method argument, which this proxy + * automatically detects. Note that a DecoratingNavigationHandler subclass + * will still work as standard JSF NavigationHandler as well! + * + *

This proxy may be subclassed to change the bean name used to search for the + * navigation handler, change the strategy used to obtain the target handler, + * or change the strategy used to access the ApplicationContext (normally obtained + * via {@link FacesContextUtils#getWebApplicationContext(FacesContext)}). + * + * @author Juergen Hoeller + * @author Colin Sampaleanu + * @since 1.2.7 + * @see DecoratingNavigationHandler + */ +public class DelegatingNavigationHandlerProxy extends NavigationHandler { + + /** + * Default name of the target bean in the Spring application context: + * "jsfNavigationHandler" + */ + public final static String DEFAULT_TARGET_BEAN_NAME = "jsfNavigationHandler"; + + private NavigationHandler originalNavigationHandler; + + + /** + * Create a new DelegatingNavigationHandlerProxy. + */ + public DelegatingNavigationHandlerProxy() { + } + + /** + * Create a new DelegatingNavigationHandlerProxy. + * @param originalNavigationHandler the original NavigationHandler + */ + public DelegatingNavigationHandlerProxy(NavigationHandler originalNavigationHandler) { + this.originalNavigationHandler = originalNavigationHandler; + } + + + /** + * Handle the navigation request implied by the specified parameters, + * through delegating to the target bean in the Spring application context. + *

The target bean needs to extend the JSF NavigationHandler class. + * If it extends Spring's DecoratingNavigationHandler, the overloaded + * handleNavigation method with the original NavigationHandler + * as argument will be used. Else, the standard handleNavigation + * method will be called. + */ + public void handleNavigation(FacesContext facesContext, String fromAction, String outcome) { + NavigationHandler handler = getDelegate(facesContext); + if (handler instanceof DecoratingNavigationHandler) { + ((DecoratingNavigationHandler) handler).handleNavigation( + facesContext, fromAction, outcome, this.originalNavigationHandler); + } + else { + handler.handleNavigation(facesContext, fromAction, outcome); + } + } + + /** + * Return the target NavigationHandler to delegate to. + *

By default, a bean with the name "jsfNavigationHandler" is obtained + * from the Spring root WebApplicationContext, for every invocation. + * @param facesContext the current JSF context + * @return the target NavigationHandler to delegate to + * @see #getTargetBeanName + * @see #getBeanFactory + */ + protected NavigationHandler getDelegate(FacesContext facesContext) { + String targetBeanName = getTargetBeanName(facesContext); + return (NavigationHandler) getBeanFactory(facesContext).getBean(targetBeanName, NavigationHandler.class); + } + + /** + * Return the name of the target NavigationHandler bean in the BeanFactory. + * Default is "jsfNavigationHandler". + * @param facesContext the current JSF context + * @return the name of the target bean + */ + protected String getTargetBeanName(FacesContext facesContext) { + return DEFAULT_TARGET_BEAN_NAME; + } + + /** + * Retrieve the Spring BeanFactory to delegate bean name resolution to. + *

Default implementation delegates to getWebApplicationContext. + * Can be overridden to provide an arbitrary BeanFactory reference to resolve + * against; usually, this will be a full Spring ApplicationContext. + * @param facesContext the current JSF context + * @return the Spring BeanFactory (never null) + * @see #getWebApplicationContext + */ + protected BeanFactory getBeanFactory(FacesContext facesContext) { + return getWebApplicationContext(facesContext); + } + + /** + * Retrieve the web application context to delegate bean name resolution to. + *

Default implementation delegates to FacesContextUtils. + * @param facesContext the current JSF context + * @return the Spring web application context (never null) + * @see FacesContextUtils#getRequiredWebApplicationContext + */ + protected WebApplicationContext getWebApplicationContext(FacesContext facesContext) { + return FacesContextUtils.getRequiredWebApplicationContext(facesContext); + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/jsf/DelegatingPhaseListenerMulticaster.java b/org.springframework.web/src/main/java/org/springframework/web/jsf/DelegatingPhaseListenerMulticaster.java new file mode 100644 index 0000000000..36a6e704f1 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/jsf/DelegatingPhaseListenerMulticaster.java @@ -0,0 +1,125 @@ +/* + * Copyright 2002-2008 the original author or authors. + * + * 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.springframework.web.jsf; + +import java.util.Collection; +import java.util.Iterator; + +import javax.faces.context.FacesContext; +import javax.faces.event.PhaseEvent; +import javax.faces.event.PhaseId; +import javax.faces.event.PhaseListener; + +import org.springframework.beans.factory.BeanFactoryUtils; +import org.springframework.beans.factory.ListableBeanFactory; +import org.springframework.web.context.WebApplicationContext; + +/** + * JSF PhaseListener implementation that delegates to one or more Spring-managed + * PhaseListener beans coming from the Spring root WebApplicationContext. + * + *

Configure this listener multicaster in your faces-config.xml file + * as follows: + * + *

+ * <application>
+ *   ...
+ *   <phase-listener>
+ *     org.springframework.web.jsf.DelegatingPhaseListenerMulticaster
+ *   </phase-listener>
+ *   ...
+ * </application>
+ * + * The multicaster will delegate all beforePhase and afterPhase + * events to all target PhaseListener beans. By default, those will simply be obtained + * by type: All beans in the Spring root WebApplicationContext that implement the + * PhaseListener interface will be fetched and invoked. + * + *

Note: This multicaster's getPhaseId() method will always return + * ANY_PHASE. The phase id exposed by the target listener beans + * will be ignored; all events will be propagated to all listeners. + * + *

This multicaster may be subclassed to change the strategy used to obtain + * the listener beans, or to change the strategy used to access the ApplicationContext + * (normally obtained via {@link FacesContextUtils#getWebApplicationContext(FacesContext)}). + * + * @author Juergen Hoeller + * @author Colin Sampaleanu + * @since 1.2.7 + */ +public class DelegatingPhaseListenerMulticaster implements PhaseListener { + + public PhaseId getPhaseId() { + return PhaseId.ANY_PHASE; + } + + public void beforePhase(PhaseEvent event) { + Collection listeners = getDelegates(event.getFacesContext()); + Iterator it = listeners.iterator(); + while (it.hasNext()) { + PhaseListener listener = (PhaseListener) it.next(); + listener.beforePhase(event); + } + } + + public void afterPhase(PhaseEvent event) { + Collection listeners = getDelegates(event.getFacesContext()); + Iterator it = listeners.iterator(); + while (it.hasNext()) { + PhaseListener listener = (PhaseListener) it.next(); + listener.afterPhase(event); + } + } + + + /** + * Obtain the delegate PhaseListener beans from the Spring root WebApplicationContext. + * @param facesContext the current JSF context + * @return a Collection of PhaseListener objects + * @see #getBeanFactory + * @see org.springframework.beans.factory.ListableBeanFactory#getBeansOfType(Class) + */ + protected Collection getDelegates(FacesContext facesContext) { + ListableBeanFactory bf = getBeanFactory(facesContext); + return BeanFactoryUtils.beansOfTypeIncludingAncestors(bf, PhaseListener.class, true, false).values(); + } + + /** + * Retrieve the Spring BeanFactory to delegate bean name resolution to. + *

The default implementation delegates to getWebApplicationContext. + * Can be overridden to provide an arbitrary ListableBeanFactory reference to + * resolve against; usually, this will be a full Spring ApplicationContext. + * @param facesContext the current JSF context + * @return the Spring ListableBeanFactory (never null) + * @see #getWebApplicationContext + */ + protected ListableBeanFactory getBeanFactory(FacesContext facesContext) { + return getWebApplicationContext(facesContext); + } + + /** + * Retrieve the web application context to delegate bean name resolution to. + *

The default implementation delegates to FacesContextUtils. + * @param facesContext the current JSF context + * @return the Spring web application context (never null) + * @see FacesContextUtils#getRequiredWebApplicationContext + */ + protected WebApplicationContext getWebApplicationContext(FacesContext facesContext) { + return FacesContextUtils.getRequiredWebApplicationContext(facesContext); + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/jsf/DelegatingVariableResolver.java b/org.springframework.web/src/main/java/org/springframework/web/jsf/DelegatingVariableResolver.java new file mode 100644 index 0000000000..c2dc1acf78 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/jsf/DelegatingVariableResolver.java @@ -0,0 +1,169 @@ +/* + * Copyright 2002-2008 the original author or authors. + * + * 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.springframework.web.jsf; + +import javax.faces.context.FacesContext; +import javax.faces.el.EvaluationException; +import javax.faces.el.VariableResolver; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.beans.factory.BeanFactory; +import org.springframework.util.Assert; +import org.springframework.web.context.WebApplicationContext; + +/** + * JSF 1.1 VariableResolver that first delegates to the + * original resolver of the underlying JSF implementation (for resolving + * managed-bean objects as defined in faces-config.xml + * as well as well-known implicit EL attributes), then to the Spring + * root WebApplicationContext (for resolving Spring beans). + * + *

Configure this resolver in your faces-config.xml file as follows: + * + *

+ * <application>
+ *   ...
+ *   <variable-resolver>org.springframework.web.jsf.DelegatingVariableResolver</variable-resolver>
+ * </application>
+ * + * All your JSF expressions can then implicitly refer to the names of + * Spring-managed service layer beans, for example in property values of + * JSF-managed beans: + * + *
+ * <managed-bean>
+ *   <managed-bean-name>myJsfManagedBean</managed-bean-name>
+ *   <managed-bean-class>example.MyJsfManagedBean</managed-bean-class>
+ *   <managed-bean-scope>session</managed-bean-scope>
+ *   <managed-property>
+ *     <property-name>mySpringManagedBusinessObject</property-name>
+ *     <value>#{mySpringManagedBusinessObject}</value>
+ *   </managed-property>
+ * </managed-bean>
+ * + * with "mySpringManagedBusinessObject" defined as Spring bean in + * applicationContext.xml: + * + *
+ * <bean id="mySpringManagedBusinessObject" class="example.MySpringManagedBusinessObject">
+ *   ...
+ * </bean>
+ * + * @author Juergen Hoeller + * @since 1.1 + * @see WebApplicationContextVariableResolver + * @see FacesContextUtils#getRequiredWebApplicationContext + */ +public class DelegatingVariableResolver extends VariableResolver { + + /** Logger available to subclasses */ + protected final Log logger = LogFactory.getLog(getClass()); + + protected final VariableResolver originalVariableResolver; + + + /** + * Create a new DelegatingVariableResolver, using the given original VariableResolver. + *

A JSF implementation will automatically pass its original resolver into the + * constructor of a configured resolver, provided that there is a corresponding + * constructor argument. + * @param originalVariableResolver the original VariableResolver + */ + public DelegatingVariableResolver(VariableResolver originalVariableResolver) { + Assert.notNull(originalVariableResolver, "Original JSF VariableResolver must not be null"); + this.originalVariableResolver = originalVariableResolver; + } + + /** + * Return the original JSF VariableResolver that this resolver delegates to. + * Used to resolve standard JSF-managed beans. + */ + protected final VariableResolver getOriginalVariableResolver() { + return this.originalVariableResolver; + } + + + /** + * Delegate to the original VariableResolver first, then try to + * resolve the variable as Spring bean in the root WebApplicationContext. + */ + public Object resolveVariable(FacesContext facesContext, String name) throws EvaluationException { + Object value = resolveOriginal(facesContext, name); + if (value != null) { + return value; + } + Object bean = resolveSpringBean(facesContext, name); + if (bean != null) { + return bean; + } + return null; + } + + /** + * Resolve the attribute via the original JSF VariableResolver. + */ + protected Object resolveOriginal(FacesContext facesContext, String name) { + Object value = getOriginalVariableResolver().resolveVariable(facesContext, name); + if (value != null && logger.isTraceEnabled()) { + logger.trace("Successfully resolved variable '" + name + "' via original VariableResolver"); + } + return value; + } + + /** + * Resolve the attribute as a Spring bean in the ApplicationContext. + */ + protected Object resolveSpringBean(FacesContext facesContext, String name) { + BeanFactory bf = getBeanFactory(facesContext); + if (bf.containsBean(name)) { + if (logger.isTraceEnabled()) { + logger.trace("Successfully resolved variable '" + name + "' in Spring BeanFactory"); + } + return bf.getBean(name); + } + else { + return null; + } + } + + /** + * Retrieve the Spring BeanFactory to delegate bean name resolution to. + *

The default implementation delegates to getWebApplicationContext. + * Can be overridden to provide an arbitrary BeanFactory reference to resolve + * against; usually, this will be a full Spring ApplicationContext. + * @param facesContext the current JSF context + * @return the Spring BeanFactory (never null) + * @see #getWebApplicationContext + */ + protected BeanFactory getBeanFactory(FacesContext facesContext) { + return getWebApplicationContext(facesContext); + } + + /** + * Retrieve the web application context to delegate bean name resolution to. + *

The default implementation delegates to FacesContextUtils. + * @param facesContext the current JSF context + * @return the Spring web application context (never null) + * @see FacesContextUtils#getRequiredWebApplicationContext + */ + protected WebApplicationContext getWebApplicationContext(FacesContext facesContext) { + return FacesContextUtils.getRequiredWebApplicationContext(facesContext); + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/jsf/FacesContextUtils.java b/org.springframework.web/src/main/java/org/springframework/web/jsf/FacesContextUtils.java new file mode 100644 index 0000000000..e28efaeb57 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/jsf/FacesContextUtils.java @@ -0,0 +1,118 @@ +/* + * Copyright 2002-2006 the original author or authors. + * + * 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.springframework.web.jsf; + +import javax.faces.context.ExternalContext; +import javax.faces.context.FacesContext; + +import org.springframework.util.Assert; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.util.WebUtils; + +/** + * Convenience methods to retrieve the root WebApplicationContext for a given + * FacesContext. This is e.g. useful for accessing a Spring context from + * custom JSF code. + * + *

Analogous to Spring's WebApplicationContextUtils for the ServletContext. + * + * @author Juergen Hoeller + * @since 1.1 + * @see org.springframework.web.context.ContextLoader + * @see org.springframework.web.context.support.WebApplicationContextUtils + */ +public abstract class FacesContextUtils { + + /** + * Find the root WebApplicationContext for this web app, which is + * typically loaded via ContextLoaderListener or ContextLoaderServlet. + *

Will rethrow an exception that happened on root context startup, + * to differentiate between a failed context startup and no context at all. + * @param fc the FacesContext to find the web application context for + * @return the root WebApplicationContext for this web app, or null if none + * @see org.springframework.web.context.WebApplicationContext#ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + */ + public static WebApplicationContext getWebApplicationContext(FacesContext fc) { + Assert.notNull(fc, "FacesContext must not be null"); + Object attr = fc.getExternalContext().getApplicationMap().get( + WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE); + if (attr == null) { + return null; + } + if (attr instanceof RuntimeException) { + throw (RuntimeException) attr; + } + if (attr instanceof Error) { + throw (Error) attr; + } + if (!(attr instanceof WebApplicationContext)) { + throw new IllegalStateException("Root context attribute is not of type WebApplicationContext: " + attr); + } + return (WebApplicationContext) attr; + } + + /** + * Find the root WebApplicationContext for this web app, which is + * typically loaded via ContextLoaderListener or ContextLoaderServlet. + *

Will rethrow an exception that happened on root context startup, + * to differentiate between a failed context startup and no context at all. + * @param fc the FacesContext to find the web application context for + * @return the root WebApplicationContext for this web app + * @throws IllegalStateException if the root WebApplicationContext could not be found + * @see org.springframework.web.context.WebApplicationContext#ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + */ + public static WebApplicationContext getRequiredWebApplicationContext(FacesContext fc) + throws IllegalStateException { + + WebApplicationContext wac = getWebApplicationContext(fc); + if (wac == null) { + throw new IllegalStateException("No WebApplicationContext found: no ContextLoaderListener registered?"); + } + return wac; + } + + /** + * Return the best available mutex for the given session: + * that is, an object to synchronize on for the given session. + *

Returns the session mutex attribute if available; usually, + * this means that the HttpSessionMutexListener needs to be defined + * in web.xml. Falls back to the Session reference itself + * if no mutex attribute found. + *

The session mutex is guaranteed to be the same object during + * the entire lifetime of the session, available under the key defined + * by the SESSION_MUTEX_ATTRIBUTE constant. It serves as a + * safe reference to synchronize on for locking on the current session. + *

In many cases, the Session reference itself is a safe mutex + * as well, since it will always be the same object reference for the + * same active logical session. However, this is not guaranteed across + * different servlet containers; the only 100% safe way is a session mutex. + * @param fc the FacesContext to find the session mutex for + * @return the mutex object (never null) + * @see org.springframework.web.util.WebUtils#SESSION_MUTEX_ATTRIBUTE + * @see org.springframework.web.util.HttpSessionMutexListener + */ + public static Object getSessionMutex(FacesContext fc) { + Assert.notNull(fc, "FacesContext must not be null"); + ExternalContext ec = fc.getExternalContext(); + Object mutex = ec.getSessionMap().get(WebUtils.SESSION_MUTEX_ATTRIBUTE); + if (mutex == null) { + mutex = ec.getSession(true); + } + return mutex; + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/jsf/SpringBeanVariableResolver.java b/org.springframework.web/src/main/java/org/springframework/web/jsf/SpringBeanVariableResolver.java new file mode 100644 index 0000000000..c51a38e485 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/jsf/SpringBeanVariableResolver.java @@ -0,0 +1,53 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.web.jsf; + +import javax.faces.context.FacesContext; +import javax.faces.el.EvaluationException; +import javax.faces.el.VariableResolver; + +/** + * This is a subclass of the JSF 1.1 {@link DelegatingVariableResolver}, + * letting Spring bean definitions override other attributes of the same name. + * + *

The main purpose of this class is to provide behavior that is analogous + * to the JSF 1.2 {@link org.springframework.web.jsf.el.SpringBeanFacesELResolver}. + * + * @author Juergen Hoeller + * @since 2.5 + * @see WebApplicationContextVariableResolver + * @see FacesContextUtils#getRequiredWebApplicationContext + */ +public class SpringBeanVariableResolver extends DelegatingVariableResolver { + + public SpringBeanVariableResolver(VariableResolver originalVariableResolver) { + super(originalVariableResolver); + } + + public Object resolveVariable(FacesContext facesContext, String name) throws EvaluationException { + Object bean = resolveSpringBean(facesContext, name); + if (bean != null) { + return bean; + } + Object value = resolveOriginal(facesContext, name); + if (value != null) { + return value; + } + return null; + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/jsf/WebApplicationContextVariableResolver.java b/org.springframework.web/src/main/java/org/springframework/web/jsf/WebApplicationContextVariableResolver.java new file mode 100644 index 0000000000..3c8fd8d2ff --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/jsf/WebApplicationContextVariableResolver.java @@ -0,0 +1,113 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.web.jsf; + +import javax.faces.context.FacesContext; +import javax.faces.el.EvaluationException; +import javax.faces.el.VariableResolver; + +import org.springframework.util.Assert; +import org.springframework.web.context.WebApplicationContext; + +/** + * Special JSF 1.1 VariableResolver that exposes the Spring + * WebApplicationContext instance under a variable named + * "webApplicationContext". + * + *

In contrast to {@link DelegatingVariableResolver}, this VariableResolver + * does not resolve JSF variable names as Spring bean names. It rather + * exposes Spring's root WebApplicationContext itself under a special name. + * JSF-managed beans can then use Spring's WebApplicationContext API to retrieve + * Spring-managed beans, access resources, etc. + * + *

Configure this resolver in your faces-config.xml file as follows: + * + *

+ * <application>
+ *   ...
+ *   <variable-resolver>org.springframework.web.jsf.WebApplicationContextVariableResolver</variable-resolver>
+ * </application>
+ * + * @author Colin Sampaleanu + * @author Juergen Hoeller + * @since 1.2.5 + * @see DelegatingVariableResolver + * @see FacesContextUtils#getWebApplicationContext + */ +public class WebApplicationContextVariableResolver extends VariableResolver { + + /** + * Name of the exposed WebApplicationContext variable: "webApplicationContext". + */ + public static final String WEB_APPLICATION_CONTEXT_VARIABLE_NAME = "webApplicationContext"; + + + protected final VariableResolver originalVariableResolver; + + + /** + * Create a new WebApplicationContextVariableResolver, using the given + * original VariableResolver. + *

A JSF implementation will automatically pass its original resolver into the + * constructor of a configured resolver, provided that there is a corresponding + * constructor argument. + * @param originalVariableResolver the original VariableResolver + */ + public WebApplicationContextVariableResolver(VariableResolver originalVariableResolver) { + Assert.notNull(originalVariableResolver, "Original JSF VariableResolver must not be null"); + this.originalVariableResolver = originalVariableResolver; + } + + /** + * Return the original JSF VariableResolver that this resolver delegates to. + * Used to resolve standard JSF-managed beans. + */ + protected final VariableResolver getOriginalVariableResolver() { + return this.originalVariableResolver; + } + + + /** + * Check for the special "webApplicationContext" variable first, + * then delegate to the original VariableResolver. + *

If no WebApplicationContext is available, all requests + * will be delegated to the original VariableResolver. + */ + public Object resolveVariable(FacesContext context, String name) throws EvaluationException { + Object value = null; + if (WEB_APPLICATION_CONTEXT_VARIABLE_NAME.equals(name)) { + value = getWebApplicationContext(context); + } + if (value == null) { + value = getOriginalVariableResolver().resolveVariable(context, name); + } + return value; + } + + /** + * Retrieve the WebApplicationContext reference to expose. + *

The default implementation delegates to FacesContextUtils, + * returning null if no WebApplicationContext found. + * @param facesContext the current JSF context + * @return the Spring web application context + * @see FacesContextUtils#getWebApplicationContext + */ + protected WebApplicationContext getWebApplicationContext(FacesContext facesContext) { + return FacesContextUtils.getWebApplicationContext(facesContext); + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/jsf/el/SpringBeanFacesELResolver.java b/org.springframework.web/src/main/java/org/springframework/web/jsf/el/SpringBeanFacesELResolver.java new file mode 100644 index 0000000000..2a7a4de262 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/jsf/el/SpringBeanFacesELResolver.java @@ -0,0 +1,93 @@ +/* + * Copyright 2002-2008 the original author or authors. + * + * 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.springframework.web.jsf.el; + +import javax.el.ELContext; +import javax.faces.context.FacesContext; + +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.access.el.SpringBeanELResolver; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.jsf.FacesContextUtils; + +/** + * JSF 1.2 ELResolver that delegates to the Spring root + * WebApplicationContext, resolving name references to + * Spring-defined beans. + * + *

Configure this resolver in your faces-config.xml file as follows: + * + *

+ * <application>
+ *   ...
+ *   <el-resolver>org.springframework.web.jsf.el.SpringBeanFacesELResolver</el-resolver>
+ * </application>
+ * + * All your JSF expressions can then implicitly refer to the names of + * Spring-managed service layer beans, for example in property values of + * JSF-managed beans: + * + *
+ * <managed-bean>
+ *   <managed-bean-name>myJsfManagedBean</managed-bean-name>
+ *   <managed-bean-class>example.MyJsfManagedBean</managed-bean-class>
+ *   <managed-bean-scope>session</managed-bean-scope>
+ *   <managed-property>
+ *     <property-name>mySpringManagedBusinessObject</property-name>
+ *     <value>#{mySpringManagedBusinessObject}</value>
+ *   </managed-property>
+ * </managed-bean>
+ * + * with "mySpringManagedBusinessObject" defined as Spring bean in + * applicationContext.xml: + * + *
+ * <bean id="mySpringManagedBusinessObject" class="example.MySpringManagedBusinessObject">
+ *   ...
+ * </bean>
+ * + * @author Juergen Hoeller + * @since 2.5 + * @see WebApplicationContextFacesELResolver + * @see org.springframework.web.jsf.FacesContextUtils#getRequiredWebApplicationContext + */ +public class SpringBeanFacesELResolver extends SpringBeanELResolver { + + /** + * This implementation delegates to {@link #getWebApplicationContext}. + * Can be overridden to provide an arbitrary BeanFactory reference to resolve + * against; usually, this will be a full Spring ApplicationContext. + * @param elContext the current JSF ELContext + * @return the Spring BeanFactory (never null) + */ + protected BeanFactory getBeanFactory(ELContext elContext) { + return getWebApplicationContext(elContext); + } + + /** + * Retrieve the web application context to delegate bean name resolution to. + *

The default implementation delegates to FacesContextUtils. + * @param elContext the current JSF ELContext + * @return the Spring web application context (never null) + * @see org.springframework.web.jsf.FacesContextUtils#getRequiredWebApplicationContext + */ + protected WebApplicationContext getWebApplicationContext(ELContext elContext) { + FacesContext facesContext = FacesContext.getCurrentInstance(); + return FacesContextUtils.getRequiredWebApplicationContext(facesContext); + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/jsf/el/WebApplicationContextFacesELResolver.java b/org.springframework.web/src/main/java/org/springframework/web/jsf/el/WebApplicationContextFacesELResolver.java new file mode 100644 index 0000000000..018448705b --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/jsf/el/WebApplicationContextFacesELResolver.java @@ -0,0 +1,175 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.web.jsf.el; + +import java.beans.FeatureDescriptor; +import java.util.Iterator; + +import javax.el.ELContext; +import javax.el.ELException; +import javax.el.ELResolver; +import javax.faces.context.FacesContext; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.beans.BeansException; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.jsf.FacesContextUtils; + +/** + * Special JSF 1.2 ELResolver that exposes the Spring + * WebApplicationContext instance under a variable named + * "webApplicationContext". + * + *

In contrast to {@link SpringBeanFacesELResolver}, this ELResolver variant + * does not resolve JSF variable names as Spring bean names. It rather + * exposes Spring's root WebApplicationContext itself under a special name, + * and is able to resolve "webApplicationContext.mySpringManagedBusinessObject" + * dereferences to Spring-defined beans in that application context. + * + *

Configure this resolver in your faces-config.xml file as follows: + * + *

+ * <application>
+ *   ...
+ *   <el-resolver>org.springframework.web.jsf.el.WebApplicationContextFacesELResolver</el-resolver>
+ * </application>
+ * + * @author Juergen Hoeller + * @since 2.5 + * @see SpringBeanFacesELResolver + * @see org.springframework.web.jsf.FacesContextUtils#getWebApplicationContext + */ +public class WebApplicationContextFacesELResolver extends ELResolver { + + /** + * Name of the exposed WebApplicationContext variable: "webApplicationContext". + */ + public static final String WEB_APPLICATION_CONTEXT_VARIABLE_NAME = "webApplicationContext"; + + + /** Logger available to subclasses */ + protected final Log logger = LogFactory.getLog(getClass()); + + + public Object getValue(ELContext elContext, Object base, Object property) throws ELException { + if (base != null) { + if (base instanceof WebApplicationContext) { + WebApplicationContext wac = (WebApplicationContext) base; + String beanName = property.toString(); + if (logger.isTraceEnabled()) { + logger.trace("Attempting to resolve property '" + beanName + "' in root WebApplicationContext"); + } + if (wac.containsBean(beanName)) { + if (logger.isDebugEnabled()) { + logger.debug("Successfully resolved property '" + beanName + "' in root WebApplicationContext"); + } + elContext.setPropertyResolved(true); + try { + return wac.getBean(beanName); + } + catch (BeansException ex) { + throw new ELException(ex); + } + } + else { + // Mimic standard JSF/JSP behavior when base is a Map by returning null. + return null; + } + } + } + else { + if (WEB_APPLICATION_CONTEXT_VARIABLE_NAME.equals(property)) { + elContext.setPropertyResolved(true); + return getWebApplicationContext(elContext); + } + } + + return null; + } + + public Class getType(ELContext elContext, Object base, Object property) throws ELException { + if (base != null) { + if (base instanceof WebApplicationContext) { + WebApplicationContext wac = (WebApplicationContext) base; + String beanName = property.toString(); + if (logger.isDebugEnabled()) { + logger.debug("Attempting to resolve property '" + beanName + "' in root WebApplicationContext"); + } + if (wac.containsBean(beanName)) { + if (logger.isDebugEnabled()) { + logger.debug("Successfully resolved property '" + beanName + "' in root WebApplicationContext"); + } + elContext.setPropertyResolved(true); + try { + return wac.getType(beanName); + } + catch (BeansException ex) { + throw new ELException(ex); + } + } + else { + // Mimic standard JSF/JSP behavior when base is a Map by returning null. + return null; + } + } + } + else { + if (WEB_APPLICATION_CONTEXT_VARIABLE_NAME.equals(property)) { + elContext.setPropertyResolved(true); + return WebApplicationContext.class; + } + } + + return null; + } + + public void setValue(ELContext elContext, Object base, Object property, Object value) throws ELException { + } + + public boolean isReadOnly(ELContext elContext, Object base, Object property) throws ELException { + if (base instanceof WebApplicationContext) { + elContext.setPropertyResolved(true); + return false; + } + return false; + } + + public Iterator getFeatureDescriptors(ELContext elContext, Object base) { + return null; + } + + public Class getCommonPropertyType(ELContext elContext, Object base) { + return Object.class; + } + + + /** + * Retrieve the WebApplicationContext reference to expose. + *

The default implementation delegates to FacesContextUtils, + * returning null if no WebApplicationContext found. + * @param elContext the current JSF ELContext + * @return the Spring web application context + * @see org.springframework.web.jsf.FacesContextUtils#getWebApplicationContext + */ + protected WebApplicationContext getWebApplicationContext(ELContext elContext) { + FacesContext facesContext = FacesContext.getCurrentInstance(); + return FacesContextUtils.getRequiredWebApplicationContext(facesContext); + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/jsf/el/package.html b/org.springframework.web/src/main/java/org/springframework/web/jsf/el/package.html new file mode 100644 index 0000000000..c5cca972c1 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/jsf/el/package.html @@ -0,0 +1,11 @@ + + + +Support classes for integrating a JSF 1.2 web tier with a Spring middle tier +which is hosted in a Spring root WebApplicationContext. + +

Supports JSF 1.2's ELResolver mechanism, providing closer integration +than JSF 1.1's VariableResolver mechanism allowed for. + + + diff --git a/org.springframework.web/src/main/java/org/springframework/web/jsf/package.html b/org.springframework.web/src/main/java/org/springframework/web/jsf/package.html new file mode 100644 index 0000000000..b63d0525a9 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/jsf/package.html @@ -0,0 +1,11 @@ + + + +Support classes for integrating a JSF web tier with a Spring middle tier +which is hosted in a Spring root WebApplicationContext. + +

Supports easy access to beans in the Spring root WebApplicationContext +from JSF EL expressions, for example in property values of JSF-managed beans. + + + diff --git a/org.springframework.web/src/main/java/org/springframework/web/package.html b/org.springframework.web/src/main/java/org/springframework/web/package.html new file mode 100644 index 0000000000..662e973141 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/package.html @@ -0,0 +1,8 @@ + + + +Common, generic interfaces that define minimal boundary points +between Spring's web infrastructure and other framework modules. + + + diff --git a/org.springframework.web/src/main/java/org/springframework/web/util/CookieGenerator.java b/org.springframework.web/src/main/java/org/springframework/web/util/CookieGenerator.java new file mode 100644 index 0000000000..7ba3c47afc --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/util/CookieGenerator.java @@ -0,0 +1,204 @@ +/* + * Copyright 2002-2006 the original author or authors. + * + * 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.springframework.web.util; + +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Helper class for cookie generation, carrying cookie descriptor settings + * as bean properties and being able to add and remove cookie to/from a + * given response. + * + *

Can serve as base class for components that generate specific cookies, + * like CookieLocaleResolcer and CookieThemeResolver. + * + * @author Juergen Hoeller + * @since 1.1.4 + * @see #addCookie + * @see #removeCookie + * @see org.springframework.web.servlet.i18n.CookieLocaleResolver + * @see org.springframework.web.servlet.theme.CookieThemeResolver + */ +public class CookieGenerator { + + /** + * Default path that cookies will be visible to: "/", i.e. the entire server. + */ + public static final String DEFAULT_COOKIE_PATH = "/"; + + /** + * Default maximum age of cookies: maximum integer value, i.e. forever. + */ + public static final int DEFAULT_COOKIE_MAX_AGE = Integer.MAX_VALUE; + + + protected final Log logger = LogFactory.getLog(getClass()); + + private String cookieName; + + private String cookieDomain; + + private String cookiePath = DEFAULT_COOKIE_PATH; + + private int cookieMaxAge = DEFAULT_COOKIE_MAX_AGE; + + private boolean cookieSecure = false; + + + /** + * Use the given name for cookies created by this generator. + */ + public void setCookieName(String cookieName) { + this.cookieName = cookieName; + } + + /** + * Return the given name for cookies created by this generator. + */ + public String getCookieName() { + return cookieName; + } + + /** + * Use the given domain for cookies created by this generator. + * The cookie is only visible to servers in this domain. + */ + public void setCookieDomain(String cookieDomain) { + this.cookieDomain = cookieDomain; + } + + /** + * Return the domain for cookies created by this generator, if any. + */ + public String getCookieDomain() { + return cookieDomain; + } + + /** + * Use the given path for cookies created by this generator. + * The cookie is only visible to URLs in this path and below. + */ + public void setCookiePath(String cookiePath) { + this.cookiePath = cookiePath; + } + + /** + * Return the path for cookies created by this generator. + */ + public String getCookiePath() { + return cookiePath; + } + + /** + * Use the given maximum age (in seconds) for cookies created by this generator. + * Useful special value: -1 ... not persistent, deleted when client shuts down + */ + public void setCookieMaxAge(int cookieMaxAge) { + this.cookieMaxAge = cookieMaxAge; + } + + /** + * Return the maximum age for cookies created by this generator. + */ + public int getCookieMaxAge() { + return cookieMaxAge; + } + + /** + * Set whether the cookie should only be sent using a secure protocol, + * such as HTTPS (SSL). This is an indication to the receiving browser, + * not processed by the HTTP server itself. Default is "false". + */ + public void setCookieSecure(boolean cookieSecure) { + this.cookieSecure = cookieSecure; + } + + /** + * Return whether the cookie should only be sent using a secure protocol, + * such as HTTPS (SSL). + */ + public boolean isCookieSecure() { + return cookieSecure; + } + + + /** + * Add a cookie with the given value to the response, + * using the cookie descriptor settings of this generator. + *

Delegates to createCookie for cookie creation. + * @param response the HTTP response to add the cookie to + * @param cookieValue the value of the cookie to add + * @see #setCookieName + * @see #setCookieDomain + * @see #setCookiePath + * @see #setCookieMaxAge + * @see #createCookie + */ + public void addCookie(HttpServletResponse response, String cookieValue) { + Cookie cookie = createCookie(cookieValue); + cookie.setMaxAge(getCookieMaxAge()); + if (isCookieSecure()) { + cookie.setSecure(true); + } + response.addCookie(cookie); + if (logger.isDebugEnabled()) { + logger.debug("Added cookie with name [" + getCookieName() + "] and value [" + cookieValue + "]"); + } + } + + /** + * Remove the cookie that this generator describes from the response. + * Will generate a cookie with empty value and max age 0. + *

Delegates to createCookie for cookie creation. + * @param response the HTTP response to remove the cookie from + * @see #setCookieName + * @see #setCookieDomain + * @see #setCookiePath + * @see #createCookie + */ + public void removeCookie(HttpServletResponse response) { + Cookie cookie = createCookie(""); + cookie.setMaxAge(0); + response.addCookie(cookie); + if (logger.isDebugEnabled()) { + logger.debug("Removed cookie with name [" + getCookieName() + "]"); + } + } + + /** + * Create a cookie with the given value, using the cookie descriptor + * settings of this generator (except for "cookieMaxAge"). + * @param cookieValue the value of the cookie to crate + * @return the cookie + * @see #setCookieName + * @see #setCookieDomain + * @see #setCookiePath + */ + protected Cookie createCookie(String cookieValue) { + Cookie cookie = new Cookie(getCookieName(), cookieValue); + if (getCookieDomain() != null) { + cookie.setDomain(getCookieDomain()); + } + cookie.setPath(getCookiePath()); + return cookie; + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/util/ExpressionEvaluationUtils.java b/org.springframework.web/src/main/java/org/springframework/web/util/ExpressionEvaluationUtils.java new file mode 100644 index 0000000000..1f01f15849 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/util/ExpressionEvaluationUtils.java @@ -0,0 +1,456 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.web.util; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import javax.servlet.ServletContext; +import javax.servlet.jsp.JspException; +import javax.servlet.jsp.PageContext; +import javax.servlet.jsp.el.ELException; +import javax.servlet.jsp.el.Expression; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.taglibs.standard.lang.support.ExpressionEvaluatorManager; + +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; + +/** + * Convenience methods for transparent access to JSP 2.0's built-in + * {@link javax.servlet.jsp.el.ExpressionEvaluator} or the standalone + * {@link org.apache.taglibs.standard.lang.support.ExpressionEvaluatorManager} + * of Jakarta's JSTL implementation. + * + *

Automatically detects JSP 2.0 or Jakarta JSTL, preferring the JSP 2.0 + * mechanism if available. Also detects the JSP 2.0 API being present but + * the runtime JSP engine not actually supporting JSP 2.0, falling back to + * the Jakarta JSTL in this case. Throws an exception when encountering actual + * EL expressions if neither JSP 2.0 nor the Jakarta JSTL is available. + * + *

In the case of JSP 2.0, this class will by default use standard + * evaluate calls. If your application server happens to be + * inefficient in that respect, consider setting Spring's "cacheJspExpressions" + * context-param in web.xml to "true", which will use + * parseExpression calls with cached Expression objects instead. + * + *

The evaluation methods check if the value contains "${" before + * invoking the EL evaluator, treating the value as "normal" expression + * (i.e. a literal String value) else. + * + *

Note: The evaluation methods do not have a runtime dependency on + * JSP 2.0 or on Jakarta's JSTL implementation, as long as they are not + * asked to process actual EL expressions. This allows for using EL-aware + * tags with Java-based JSP expressions instead, for example. + * + * @author Juergen Hoeller + * @author Alef Arendsen + * @since 11.07.2003 + * @see javax.servlet.jsp.el.ExpressionEvaluator#evaluate + * @see javax.servlet.jsp.el.ExpressionEvaluator#parseExpression + * @see org.apache.taglibs.standard.lang.support.ExpressionEvaluatorManager + */ +public abstract class ExpressionEvaluationUtils { + + /** + * JSP 2.0 expression cache parameter at the servlet context level + * (i.e. a context-param in web.xml): "cacheJspExpressions". + */ + public static final String EXPRESSION_CACHE_CONTEXT_PARAM = "cacheJspExpressions"; + + public static final String EXPRESSION_PREFIX = "${"; + + public static final String EXPRESSION_SUFFIX = "}"; + + + private static final String EXPRESSION_CACHE_FLAG_CONTEXT_ATTR = + ExpressionEvaluationUtils.class.getName() + ".CACHE_JSP_EXPRESSIONS"; + + private static final String EXPRESSION_CACHE_MAP_CONTEXT_ATTR = + ExpressionEvaluationUtils.class.getName() + ".JSP_EXPRESSION_CACHE"; + + private static final String JSP_20_CLASS_NAME = + "javax.servlet.jsp.el.ExpressionEvaluator"; + + private static final String JAKARTA_JSTL_CLASS_NAME = + "org.apache.taglibs.standard.lang.support.ExpressionEvaluatorManager"; + + + private static final Log logger = LogFactory.getLog(ExpressionEvaluationUtils.class); + + private static ExpressionEvaluationHelper helper; + + + static { + ClassLoader cl = ExpressionEvaluationUtils.class.getClassLoader(); + if (ClassUtils.isPresent(JSP_20_CLASS_NAME, cl)) { + logger.debug("Found JSP 2.0 ExpressionEvaluator"); + if (ClassUtils.isPresent(JAKARTA_JSTL_CLASS_NAME, cl)) { + logger.debug("Found Jakarta JSTL ExpressionEvaluatorManager"); + helper = new Jsp20ExpressionEvaluationHelper(new JakartaExpressionEvaluationHelper()); + } + else { + helper = new Jsp20ExpressionEvaluationHelper(new NoExpressionEvaluationHelper()); + } + } + else if (ClassUtils.isPresent(JAKARTA_JSTL_CLASS_NAME, cl)) { + logger.debug("Found Jakarta JSTL ExpressionEvaluatorManager"); + helper = new JakartaExpressionEvaluationHelper(); + } + else { + logger.debug("JSP expression evaluation not available"); + helper = new NoExpressionEvaluationHelper(); + } + } + + + /** + * Check if the given expression value is an EL expression. + * @param value the expression to check + * @return true if the expression is an EL expression, + * false otherwise + */ + public static boolean isExpressionLanguage(String value) { + return (value != null && value.indexOf(EXPRESSION_PREFIX) != -1); + } + + /** + * Evaluate the given expression (be it EL or a literal String value) + * to an Object of a given type, + * @param attrName name of the attribute (typically a JSP tag attribute) + * @param attrValue value of the attribute + * @param resultClass class that the result should have (String, Integer, Boolean) + * @param pageContext current JSP PageContext + * @return the result of the evaluation + * @throws JspException in case of parsing errors, also in case of type mismatch + * if the passed-in literal value is not an EL expression and not assignable to + * the result class + */ + public static Object evaluate(String attrName, String attrValue, Class resultClass, PageContext pageContext) + throws JspException { + + if (isExpressionLanguage(attrValue)) { + return doEvaluate(attrName, attrValue, resultClass, pageContext); + } + else if (attrValue != null && resultClass != null && !resultClass.isInstance(attrValue)) { + throw new JspException("Attribute value \"" + attrValue + "\" is neither a JSP EL expression nor " + + "assignable to result class [" + resultClass.getName() + "]"); + } + else { + return attrValue; + } + } + + /** + * Evaluate the given expression (be it EL or a literal String value) to an Object. + * @param attrName name of the attribute (typically a JSP tag attribute) + * @param attrValue value of the attribute + * @param pageContext current JSP PageContext + * @return the result of the evaluation + * @throws JspException in case of parsing errors + */ + public static Object evaluate(String attrName, String attrValue, PageContext pageContext) + throws JspException { + + if (isExpressionLanguage(attrValue)) { + return doEvaluate(attrName, attrValue, Object.class, pageContext); + } + else { + return attrValue; + } + } + + /** + * Evaluate the given expression (be it EL or a literal String value) to a String. + * @param attrName name of the attribute (typically a JSP tag attribute) + * @param attrValue value of the attribute + * @param pageContext current JSP PageContext + * @return the result of the evaluation + * @throws JspException in case of parsing errors + */ + public static String evaluateString(String attrName, String attrValue, PageContext pageContext) + throws JspException { + + if (isExpressionLanguage(attrValue)) { + return (String) doEvaluate(attrName, attrValue, String.class, pageContext); + } + else { + return attrValue; + } + } + + /** + * Evaluate the given expression (be it EL or a literal String value) to an integer. + * @param attrName name of the attribute (typically a JSP tag attribute) + * @param attrValue value of the attribute + * @param pageContext current JSP PageContext + * @return the result of the evaluation + * @throws JspException in case of parsing errors + */ + public static int evaluateInteger(String attrName, String attrValue, PageContext pageContext) + throws JspException { + + if (isExpressionLanguage(attrValue)) { + return ((Integer) doEvaluate(attrName, attrValue, Integer.class, pageContext)).intValue(); + } + else { + return Integer.parseInt(attrValue); + } + } + + /** + * Evaluate the given expression (be it EL or a literal String value) to a boolean. + * @param attrName name of the attribute (typically a JSP tag attribute) + * @param attrValue value of the attribute + * @param pageContext current JSP PageContext + * @return the result of the evaluation + * @throws JspException in case of parsing errors + */ + public static boolean evaluateBoolean(String attrName, String attrValue, PageContext pageContext) + throws JspException { + + if (isExpressionLanguage(attrValue)) { + return ((Boolean) doEvaluate(attrName, attrValue, Boolean.class, pageContext)).booleanValue(); + } + else { + return Boolean.valueOf(attrValue).booleanValue(); + } + } + + + /** + * Actually evaluate the given expression (be it EL or a literal String value) + * to an Object of a given type. Supports concatenated expressions, + * for example: "${var1}text${var2}" + * @param attrName name of the attribute + * @param attrValue value of the attribute + * @param resultClass class that the result should have + * @param pageContext current JSP PageContext + * @return the result of the evaluation + * @throws JspException in case of parsing errors + */ + private static Object doEvaluate(String attrName, String attrValue, Class resultClass, PageContext pageContext) + throws JspException { + + Assert.notNull(attrValue, "Attribute value must not be null"); + Assert.notNull(resultClass, "Result class must not be null"); + Assert.notNull(pageContext, "PageContext must not be null"); + + if (resultClass.isAssignableFrom(String.class)) { + StringBuffer resultValue = null; + int exprPrefixIndex = -1; + int exprSuffixIndex = 0; + do { + exprPrefixIndex = attrValue.indexOf(EXPRESSION_PREFIX, exprSuffixIndex); + if (exprPrefixIndex != -1) { + int prevExprSuffixIndex = exprSuffixIndex; + exprSuffixIndex = attrValue.indexOf(EXPRESSION_SUFFIX, exprPrefixIndex + EXPRESSION_PREFIX.length()); + String expr = null; + if (exprSuffixIndex != -1) { + exprSuffixIndex += EXPRESSION_SUFFIX.length(); + expr = attrValue.substring(exprPrefixIndex, exprSuffixIndex); + } + else { + expr = attrValue.substring(exprPrefixIndex); + } + if (expr.length() == attrValue.length()) { + // A single expression without static prefix or suffix -> + // parse it with the specified result class rather than String. + return helper.evaluate(attrName, attrValue, resultClass, pageContext); + } + else { + // We actually need to concatenate partial expressions into a String. + if (resultValue == null) { + resultValue = new StringBuffer(); + } + resultValue.append(attrValue.substring(prevExprSuffixIndex, exprPrefixIndex)); + resultValue.append(helper.evaluate(attrName, expr, String.class, pageContext)); + } + } + else { + if (resultValue == null) { + resultValue = new StringBuffer(); + } + resultValue.append(attrValue.substring(exprSuffixIndex)); + } + } + while (exprPrefixIndex != -1 && exprSuffixIndex != -1); + return resultValue.toString(); + } + + else { + return helper.evaluate(attrName, attrValue, resultClass, pageContext); + } + } + + /** + * Determine whether JSP 2.0 expressions are supposed to be cached + * and return the corresponding cache Map, or null if + * caching is not enabled. + * @param pageContext current JSP PageContext + * @return the cache Map, or null if caching is disabled + */ + private static Map getJspExpressionCache(PageContext pageContext) { + ServletContext servletContext = pageContext.getServletContext(); + Map cacheMap = (Map) servletContext.getAttribute(EXPRESSION_CACHE_MAP_CONTEXT_ATTR); + if (cacheMap == null) { + Boolean cacheFlag = (Boolean) servletContext.getAttribute(EXPRESSION_CACHE_FLAG_CONTEXT_ATTR); + if (cacheFlag == null) { + cacheFlag = Boolean.valueOf(servletContext.getInitParameter(EXPRESSION_CACHE_CONTEXT_PARAM)); + servletContext.setAttribute(EXPRESSION_CACHE_FLAG_CONTEXT_ATTR, cacheFlag); + } + if (cacheFlag.booleanValue()) { + cacheMap = Collections.synchronizedMap(new HashMap()); + servletContext.setAttribute(EXPRESSION_CACHE_MAP_CONTEXT_ATTR, cacheMap); + } + } + return cacheMap; + } + + + /** + * Internal interface for evaluating a JSP EL expression. + */ + private static interface ExpressionEvaluationHelper { + + public Object evaluate(String attrName, String attrValue, Class resultClass, PageContext pageContext) + throws JspException; + } + + + /** + * Fallback ExpressionEvaluationHelper: + * always throws an exception in case of an actual EL expression. + */ + private static class NoExpressionEvaluationHelper implements ExpressionEvaluationHelper { + + public Object evaluate(String attrName, String attrValue, Class resultClass, PageContext pageContext) + throws JspException { + + throw new JspException( + "Neither JSP 2.0 nor Jakarta JSTL available - cannot parse JSP EL expression \"" + attrValue + "\""); + } + } + + + /** + * Actual invocation of the Jakarta ExpressionEvaluatorManager. + * In separate inner class to avoid runtime dependency on Jakarta's + * JSTL implementation, for evaluation of non-EL expressions. + */ + private static class JakartaExpressionEvaluationHelper implements ExpressionEvaluationHelper { + + public Object evaluate(String attrName, String attrValue, Class resultClass, PageContext pageContext) + throws JspException { + + return ExpressionEvaluatorManager.evaluate(attrName, attrValue, resultClass, pageContext); + } + } + + + /** + * Actual invocation of the JSP 2.0 ExpressionEvaluator. + * In separate inner class to avoid runtime dependency on JSP 2.0, + * for evaluation of non-EL expressions. + */ + private static class Jsp20ExpressionEvaluationHelper implements ExpressionEvaluationHelper { + + private final ExpressionEvaluationHelper fallback; + + private boolean fallbackNecessary = false; + + public Jsp20ExpressionEvaluationHelper(ExpressionEvaluationHelper fallback) { + this.fallback = fallback; + } + + public Object evaluate(String attrName, String attrValue, Class resultClass, PageContext pageContext) + throws JspException { + + if (isFallbackNecessary()) { + return this.fallback.evaluate(attrName, attrValue, resultClass, pageContext); + } + + try { + Map expressionCache = getJspExpressionCache(pageContext); + if (expressionCache != null) { + // We are supposed to explicitly create and cache JSP Expression objects. + ExpressionCacheKey cacheKey = new ExpressionCacheKey(attrValue, resultClass); + Expression expr = (Expression) expressionCache.get(cacheKey); + if (expr == null) { + expr = pageContext.getExpressionEvaluator().parseExpression(attrValue, resultClass, null); + expressionCache.put(cacheKey, expr); + } + return expr.evaluate(pageContext.getVariableResolver()); + } + else { + // We're simply calling the JSP 2.0 evaluate method straight away. + return pageContext.getExpressionEvaluator().evaluate( + attrValue, resultClass, pageContext.getVariableResolver(), null); + } + } + catch (ELException ex) { + throw new JspException("Parsing of JSP EL expression \"" + attrValue + "\" failed", ex); + } + catch (LinkageError err) { + logger.debug("JSP 2.0 ExpressionEvaluator API present but not implemented - using fallback", err); + setFallbackNecessary(); + return this.fallback.evaluate(attrName, attrValue, resultClass, pageContext); + } + } + + private synchronized boolean isFallbackNecessary() { + return this.fallbackNecessary; + } + + private synchronized void setFallbackNecessary() { + this.fallbackNecessary = true; + } + } + + + /** + * Cache key class for JSP 2.0 Expression objects. + */ + private static class ExpressionCacheKey { + + private final String value; + private final Class resultClass; + private final int hashCode; + + public ExpressionCacheKey(String value, Class resultClass) { + this.value = value; + this.resultClass = resultClass; + this.hashCode = this.value.hashCode() * 29 + this.resultClass.hashCode(); + } + + public boolean equals(Object obj) { + if (!(obj instanceof ExpressionCacheKey)) { + return false; + } + ExpressionCacheKey other = (ExpressionCacheKey) obj; + return (this.value.equals(other.value) && this.resultClass.equals(other.resultClass)); + } + + public int hashCode() { + return this.hashCode; + } + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/util/HtmlCharacterEntityDecoder.java b/org.springframework.web/src/main/java/org/springframework/web/util/HtmlCharacterEntityDecoder.java new file mode 100644 index 0000000000..5f1e478735 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/util/HtmlCharacterEntityDecoder.java @@ -0,0 +1,153 @@ +/* + * Copyright 2002-2005 the original author or authors. + * + * 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.springframework.web.util; + +/** + * Helper for decoding HTML Strings by replacing character + * entity references with the referred character. + * + * @author Juergen Hoeller + * @author Martin Kersten + * @since 1.2.1 + */ +class HtmlCharacterEntityDecoder { + + private static final int MAX_REFERENCE_SIZE = 10; + + + private final HtmlCharacterEntityReferences characterEntityReferences; + + private final String originalMessage; + + private final StringBuffer decodedMessage; + + private int currentPosition = 0; + + private int nextPotentialReferencePosition = -1; + + private int nextSemicolonPosition = -2; + + + public HtmlCharacterEntityDecoder(HtmlCharacterEntityReferences characterEntityReferences, String original) { + this.characterEntityReferences = characterEntityReferences; + this.originalMessage = original; + this.decodedMessage = new StringBuffer(originalMessage.length()); + } + + public String decode() { + while (currentPosition < originalMessage.length()) { + findNextPotentialReference(currentPosition); + copyCharactersTillPotentialReference(); + processPossibleReference(); + } + return decodedMessage.toString(); + } + + private void findNextPotentialReference(int startPosition) { + nextPotentialReferencePosition = Math.max(startPosition, nextSemicolonPosition - MAX_REFERENCE_SIZE); + + do { + nextPotentialReferencePosition = + originalMessage.indexOf('&', nextPotentialReferencePosition); + + if (nextSemicolonPosition != -1 && + nextSemicolonPosition < nextPotentialReferencePosition) + nextSemicolonPosition = originalMessage.indexOf(';', nextPotentialReferencePosition + 1); + + boolean isPotentialReference = + nextPotentialReferencePosition != -1 + && nextSemicolonPosition != -1 + && nextPotentialReferencePosition - nextSemicolonPosition < MAX_REFERENCE_SIZE; + + if (isPotentialReference) { + break; + } + if (nextPotentialReferencePosition == -1) { + break; + } + if (nextSemicolonPosition == -1) { + nextPotentialReferencePosition = -1; + break; + } + + nextPotentialReferencePosition = nextPotentialReferencePosition + 1; + } + while (nextPotentialReferencePosition != -1); + } + + + private void copyCharactersTillPotentialReference() { + if (nextPotentialReferencePosition != currentPosition) { + int skipUntilIndex = nextPotentialReferencePosition != -1 ? + nextPotentialReferencePosition : originalMessage.length(); + if (skipUntilIndex - currentPosition > 3) { + decodedMessage.append(originalMessage.substring(currentPosition, skipUntilIndex)); + currentPosition = skipUntilIndex; + } + else { + while (currentPosition < skipUntilIndex) + decodedMessage.append(originalMessage.charAt(currentPosition++)); + } + } + } + + private void processPossibleReference() { + if (nextPotentialReferencePosition != -1) { + boolean isNumberedReference = originalMessage.charAt(currentPosition + 1) == '#'; + boolean wasProcessable = isNumberedReference ? processNumberedReference() : processNamedReference(); + if (wasProcessable) { + currentPosition = nextSemicolonPosition + 1; + } + else { + char currentChar = originalMessage.charAt(currentPosition); + decodedMessage.append(currentChar); + currentPosition++; + } + } + } + + private boolean processNumberedReference() { + boolean isHexNumberedReference = + originalMessage.charAt(nextPotentialReferencePosition + 2) == 'x' || + originalMessage.charAt(nextPotentialReferencePosition + 2) == 'X'; + try { + int value = (!isHexNumberedReference) ? + Integer.parseInt(getReferenceSubstring(2)) : + Integer.parseInt(getReferenceSubstring(3), 16); + decodedMessage.append((char) value); + return true; + } + catch (NumberFormatException ex) { + return false; + } + } + + private boolean processNamedReference() { + String referenceName = getReferenceSubstring(1); + char mappedCharacter = characterEntityReferences.convertToCharacter(referenceName); + if (mappedCharacter != HtmlCharacterEntityReferences.CHAR_NULL) { + decodedMessage.append(mappedCharacter); + return true; + } + return false; + } + + private String getReferenceSubstring(int referenceOffset) { + return originalMessage.substring(nextPotentialReferencePosition + referenceOffset, nextSemicolonPosition); + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/util/HtmlCharacterEntityReferences.java b/org.springframework.web/src/main/java/org/springframework/web/util/HtmlCharacterEntityReferences.java new file mode 100644 index 0000000000..5009a69d34 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/util/HtmlCharacterEntityReferences.java @@ -0,0 +1,138 @@ +/* + * Copyright 2002-2005 the original author or authors. + * + * 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.springframework.web.util; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; + +import org.springframework.util.Assert; + +/** + * Represents a set of character entity references defined by the + * HTML 4.0 standard. + * + *

A complete description of the HTML 4.0 character set can be found + * at http://www.w3.org/TR/html4/charset.html. + * + * @author Juergen Hoeller + * @author Martin Kersten + * @since 1.2.1 + */ +class HtmlCharacterEntityReferences { + + static final char REFERENCE_START = '&'; + + static final String DECIMAL_REFERENCE_START = "&#"; + + static final String HEX_REFERENCE_START = "&#x"; + + static final char REFERENCE_END = ';'; + + static final char CHAR_NULL = (char) -1; + + + private static final String PROPERTIES_FILE = "HtmlCharacterEntityReferences.properties"; + + + private final String[] characterToEntityReferenceMap = new String[3000]; + + private final Map entityReferenceToCharacterMap = new HashMap(252); + + + /** + * Returns a new set of character entity references reflecting the HTML 4.0 character set. + */ + public HtmlCharacterEntityReferences() { + Properties entityReferences = new Properties(); + + // Load refeence definition file. + InputStream is = HtmlCharacterEntityReferences.class.getResourceAsStream(PROPERTIES_FILE); + if (is == null) { + throw new IllegalStateException( + "Cannot find reference definition file [HtmlCharacterEntityReferences.properties] as class path resource"); + } + try { + try { + entityReferences.load(is); + } + finally { + is.close(); + } + } + catch (IOException ex) { + throw new IllegalStateException( + "Failed to parse reference definition file [HtmlCharacterEntityReferences.properties]: " + ex.getMessage()); + } + + // Parse reference definition properites. + Enumeration keys = entityReferences.propertyNames(); + while (keys.hasMoreElements()) { + String key = (String) keys.nextElement(); + int referredChar = Integer.parseInt(key); + Assert.isTrue((referredChar < 1000 || (referredChar >= 8000 && referredChar < 10000)), + "Invalid reference to special HTML entity: " + referredChar); + int index = (referredChar < 1000 ? referredChar : referredChar - 7000); + String reference = entityReferences.getProperty(key); + this.characterToEntityReferenceMap[index] = REFERENCE_START + reference + REFERENCE_END; + this.entityReferenceToCharacterMap.put(reference, new Character((char) referredChar)); + } + } + + /** + * Return the number of supported entity references. + */ + public int getSupportedReferenceCount() { + return this.entityReferenceToCharacterMap.size(); + } + + /** + * Return true if the given character is mapped to a supported entity reference. + */ + public boolean isMappedToReference(char character) { + return (convertToReference(character) != null); + } + + /** + * Return the reference mapped to the given character or null. + */ + public String convertToReference(char character) { + if (character < 1000 || (character >= 8000 && character < 10000)) { + int index = (character < 1000 ? character : character - 7000); + String entityReference = this.characterToEntityReferenceMap[index]; + if (entityReference != null) { + return entityReference; + } + } + return null; + } + + /** + * Return the char mapped to the given entityReference or -1. + */ + public char convertToCharacter(String entityReference) { + Character referredCharacter = (Character) this.entityReferenceToCharacterMap.get(entityReference); + if (referredCharacter != null) { + return referredCharacter.charValue(); + } + return CHAR_NULL; + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/util/HtmlCharacterEntityReferences.properties b/org.springframework.web/src/main/java/org/springframework/web/util/HtmlCharacterEntityReferences.properties new file mode 100644 index 0000000000..f1f00c7517 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/util/HtmlCharacterEntityReferences.properties @@ -0,0 +1,268 @@ +# Character Entity References defined by the HTML 4.0 standard. +# A complete description of the HTML 4.0 character set can be found at: +# http://www.w3.org/TR/html4/charset.html + + +# Character entity references for ISO 8859-1 characters + +160 = nbsp +161 = iexcl +162 = cent +163 = pound +164 = curren +165 = yen +166 = brvbar +167 = sect +168 = uml +169 = copy +170 = ordf +171 = laquo +172 = not +173 = shy +174 = reg +175 = macr +176 = deg +177 = plusmn +178 = sup2 +179 = sup3 +180 = acute +181 = micro +182 = para +183 = middot +184 = cedil +185 = sup1 +186 = ordm +187 = raquo +188 = frac14 +189 = frac12 +190 = frac34 +191 = iquest +192 = Agrave +193 = Aacute +194 = Acirc +195 = Atilde +196 = Auml +197 = Aring +198 = AElig +199 = Ccedil +200 = Egrave +201 = Eacute +202 = Ecirc +203 = Euml +204 = Igrave +205 = Iacute +206 = Icirc +207 = Iuml +208 = ETH +209 = Ntilde +210 = Ograve +211 = Oacute +212 = Ocirc +213 = Otilde +214 = Ouml +215 = times +216 = Oslash +217 = Ugrave +218 = Uacute +219 = Ucirc +220 = Uuml +221 = Yacute +222 = THORN +223 = szlig +224 = agrave +225 = aacute +226 = acirc +227 = atilde +228 = auml +229 = aring +230 = aelig +231 = ccedil +232 = egrave +233 = eacute +234 = ecirc +235 = euml +236 = igrave +237 = iacute +238 = icirc +239 = iuml +240 = eth +241 = ntilde +242 = ograve +243 = oacute +244 = ocirc +245 = otilde +246 = ouml +247 = divide +248 = oslash +249 = ugrave +250 = uacute +251 = ucirc +252 = uuml +253 = yacute +254 = thorn +255 = yuml + + +# Character entity references for symbols, mathematical symbols, and Greek letters + +402 = fnof +913 = Alpha +914 = Beta +915 = Gamma +916 = Delta +917 = Epsilon +918 = Zeta +919 = Eta +920 = Theta +921 = Iota +922 = Kappa +923 = Lambda +924 = Mu +925 = Nu +926 = Xi +927 = Omicron +928 = Pi +929 = Rho +931 = Sigma +932 = Tau +933 = Upsilon +934 = Phi +935 = Chi +936 = Psi +937 = Omega +945 = alpha +946 = beta +947 = gamma +948 = delta +949 = epsilon +950 = zeta +951 = eta +952 = theta +953 = iota +954 = kappa +955 = lambda +956 = mu +957 = nu +958 = xi +959 = omicron +960 = pi +961 = rho +962 = sigmaf +963 = sigma +964 = tau +965 = upsilon +966 = phi +967 = chi +968 = psi +969 = omega +977 = thetasym +978 = upsih +982 = piv +8226 = bull +8230 = hellip +8242 = prime +8243 = Prime +8254 = oline +8260 = frasl +8472 = weierp +8465 = image +8476 = real +8482 = trade +8501 = alefsym +8592 = larr +8593 = uarr +8594 = rarr +8595 = darr +8596 = harr +8629 = crarr +8656 = lArr +8657 = uArr +8658 = rArr +8659 = dArr +8660 = hArr +8704 = forall +8706 = part +8707 = exist +8709 = empty +8711 = nabla +8712 = isin +8713 = notin +8715 = ni +8719 = prod +8721 = sum +8722 = minus +8727 = lowast +8730 = radic +8733 = prop +8734 = infin +8736 = ang +8743 = and +8744 = or +8745 = cap +8746 = cup +8747 = int +8756 = there4 +8764 = sim +8773 = cong +8776 = asymp +8800 = ne +8801 = equiv +8804 = le +8805 = ge +8834 = sub +8835 = sup +8836 = nsub +8838 = sube +8839 = supe +8853 = oplus +8855 = otimes +8869 = perp +8901 = sdot +8968 = lceil +8969 = rceil +8970 = lfloor +8971 = rfloor +9001 = lang +9002 = rang +9674 = loz +9824 = spades +9827 = clubs +9829 = hearts +9830 = diams + + +# Character entity references for markup-significant and internationalization characters + +34 = quot +38 = amp +60 = lt +62 = gt +338 = OElig +339 = oelig +352 = Scaron +353 = scaron +376 = Yuml +710 = circ +732 = tilde +8194 = ensp +8195 = emsp +8201 = thinsp +8204 = zwnj +8205 = zwj +8206 = lrm +8207 = rlm +8211 = ndash +8212 = mdash +8216 = lsquo +8217 = rsquo +8218 = sbquo +8220 = ldquo +8221 = rdquo +8222 = bdquo +8224 = dagger +8225 = Dagger +8240 = permil +8249 = lsaquo +8250 = rsaquo +8364 = euro + diff --git a/org.springframework.web/src/main/java/org/springframework/web/util/HtmlUtils.java b/org.springframework.web/src/main/java/org/springframework/web/util/HtmlUtils.java new file mode 100644 index 0000000000..4a79aab916 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/util/HtmlUtils.java @@ -0,0 +1,165 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.web.util; + +/** + * Utility class for HTML escaping. Escapes and unescapes + * based on the W3C HTML 4.01 recommendation, handling + * character entity references. + * + *

Reference: + * http://www.w3.org/TR/html4/charset.html + * + *

For a comprehensive set of String escaping utilities, + * consider Jakarta Commons Lang and its StringEscapeUtils class. + * We are not using that class here to avoid a runtime dependency + * on Commons Lang just for HTML escaping. Furthermore, Spring's + * HTML escaping is more flexible and 100% HTML 4.0 compliant. + * + * @author Juergen Hoeller + * @author Martin Kersten + * @since 01.03.2003 + * @see org.apache.commons.lang.StringEscapeUtils + */ +public abstract class HtmlUtils { + + /** + * Shared instance of pre-parsed HTML character entity references. + */ + private static final HtmlCharacterEntityReferences characterEntityReferences = + new HtmlCharacterEntityReferences(); + + + /** + * Turn special characters into HTML character references. + * Handles complete character set defined in HTML 4.01 recommendation. + *

Escapes all special characters to their corresponding + * entity reference (e.g. <). + *

Reference: + * + * http://www.w3.org/TR/html4/sgml/entities.html + * + * @param input the (unescaped) input string + * @return the escaped string + */ + public static String htmlEscape(String input) { + if (input == null) { + return null; + } + StringBuffer escaped = new StringBuffer(input.length() * 2); + for (int i = 0; i < input.length(); i++) { + char character = input.charAt(i); + String reference = characterEntityReferences.convertToReference(character); + if (reference != null) { + escaped.append(reference); + } + else { + escaped.append(character); + } + } + return escaped.toString(); + } + + /** + * Turn special characters into HTML character references. + * Handles complete character set defined in HTML 4.01 recommendation. + *

Escapes all special characters to their corresponding numeric + * reference in decimal format (&#Decimal;). + *

Reference: + * + * http://www.w3.org/TR/html4/sgml/entities.html + * + * @param input the (unescaped) input string + * @return the escaped string + */ + public static String htmlEscapeDecimal(String input) { + if (input == null) { + return null; + } + StringBuffer escaped = new StringBuffer(input.length() * 2); + for (int i = 0; i < input.length(); i++) { + char character = input.charAt(i); + if (characterEntityReferences.isMappedToReference(character)) { + escaped.append(HtmlCharacterEntityReferences.DECIMAL_REFERENCE_START); + escaped.append((int) character); + escaped.append(HtmlCharacterEntityReferences.REFERENCE_END); + } + else { + escaped.append(character); + } + } + return escaped.toString(); + } + + /** + * Turn special characters into HTML character references. + * Handles complete character set defined in HTML 4.01 recommendation. + *

Escapes all special characters to their corresponding numeric + * reference in hex format (&#xHex;). + *

Reference: + * + * http://www.w3.org/TR/html4/sgml/entities.html + * + * @param input the (unescaped) input string + * @return the escaped string + */ + public static String htmlEscapeHex(String input) { + if (input == null) { + return null; + } + StringBuffer escaped = new StringBuffer(input.length() * 2); + for (int i = 0; i < input.length(); i++) { + char character = input.charAt(i); + if (characterEntityReferences.isMappedToReference(character)) { + escaped.append(HtmlCharacterEntityReferences.HEX_REFERENCE_START); + escaped.append(Integer.toString((int) character, 16)); + escaped.append(HtmlCharacterEntityReferences.REFERENCE_END); + } + else { + escaped.append(character); + } + } + return escaped.toString(); + } + + /** + * Turn HTML character references into their plain text UNICODE equivalent. + *

Handles complete character set defined in HTML 4.01 recommendation + * and all reference types (decimal, hex, and entity). + *

Correctly converts the following formats: + *

+ * &#Entity; - (Example: &amp;) case sensitive + * &#Decimal; - (Example: &#68;)
+ * &#xHex; - (Example: &#xE5;) case insensitive
+ *
+ * Gracefully handles malformed character references by copying original + * characters as is when encountered.

+ *

Reference: + * + * http://www.w3.org/TR/html4/sgml/entities.html + * + * @param input the (escaped) input string + * @return the unescaped string + */ + public static String htmlUnescape(String input) { + if (input == null) { + return null; + } + return new HtmlCharacterEntityDecoder(characterEntityReferences, input).decode(); + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/util/HttpSessionMutexListener.java b/org.springframework.web/src/main/java/org/springframework/web/util/HttpSessionMutexListener.java new file mode 100644 index 0000000000..40143639a6 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/util/HttpSessionMutexListener.java @@ -0,0 +1,64 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.web.util; + +import java.io.Serializable; + +import javax.servlet.http.HttpSessionEvent; +import javax.servlet.http.HttpSessionListener; + +/** + * Servlet 2.3 HttpSessionListener that automatically exposes the + * session mutex when an HttpSession gets created. + * To be registered as a listener in web.xml. + * + *

The session mutex is guaranteed to be the same object during + * the entire lifetime of the session, available under the key defined + * by the SESSION_MUTEX_ATTRIBUTE constant. It serves as a + * safe reference to synchronize on for locking on the current session. + * + *

In many cases, the HttpSession reference itself is a safe mutex + * as well, since it will always be the same object reference for the + * same active logical session. However, this is not guaranteed across + * different servlet containers; the only 100% safe way is a session mutex. + * + * @author Juergen Hoeller + * @since 1.2.7 + * @see WebUtils#SESSION_MUTEX_ATTRIBUTE + * @see WebUtils#getSessionMutex(javax.servlet.http.HttpSession) + * @see org.springframework.web.servlet.mvc.AbstractController#setSynchronizeOnSession + */ +public class HttpSessionMutexListener implements HttpSessionListener { + + public void sessionCreated(HttpSessionEvent event) { + event.getSession().setAttribute(WebUtils.SESSION_MUTEX_ATTRIBUTE, new Mutex()); + } + + public void sessionDestroyed(HttpSessionEvent event) { + event.getSession().removeAttribute(WebUtils.SESSION_MUTEX_ATTRIBUTE); + } + + + /** + * The mutex to be registered. + * Doesn't need to be anything but a plain Object to synchronize on. + * Should be serializable to allow for HttpSession persistence. + */ + private static class Mutex implements Serializable { + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/util/IntrospectorCleanupListener.java b/org.springframework.web/src/main/java/org/springframework/web/util/IntrospectorCleanupListener.java new file mode 100644 index 0000000000..4ccb0f8b1e --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/util/IntrospectorCleanupListener.java @@ -0,0 +1,83 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.web.util; + +import java.beans.Introspector; + +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; + +import org.springframework.beans.CachedIntrospectionResults; + +/** + * Listener that flushes the JDK's {@link java.beans.Introspector JavaBeans Introspector} + * cache on web app shutdown. Register this listener in your web.xml to + * guarantee proper release of the web application class loader and its loaded classes. + * + *

If the JavaBeans Introspector has been used to analyze application classes, + * the system-level Introspector cache will hold a hard reference to those classes. + * Consequently, those classes and the web application class loader will not be + * garbage-collected on web app shutdown! This listener performs proper cleanup, + * to allow for garbage collection to take effect. + * + *

Unfortunately, the only way to clean up the Introspector is to flush + * the entire cache, as there is no way to specifically determine the + * application's classes referenced there. This will remove cached + * introspection results for all other applications in the server too. + * + *

Note that this listener is not necessary when using Spring's beans + * infrastructure within the application, as Spring's own introspection results + * cache will immediately flush an analyzed class from the JavaBeans Introspector + * cache and only hold a cache within the application's own ClassLoader. + * + * Although Spring itself does not create JDK Introspector leaks, note that this + * listener should nevertheless be used in scenarios where the Spring framework classes + * themselves reside in a 'common' ClassLoader (such as the system ClassLoader). + * In such a scenario, this listener will properly clean up Spring's introspection cache. + * + *

Application classes hardly ever need to use the JavaBeans Introspector + * directly, so are normally not the cause of Introspector resource leaks. + * Rather, many libraries and frameworks do not clean up the Introspector: + * e.g. Struts and Quartz. + * + *

Note that a single such Introspector leak will cause the entire web + * app class loader to not get garbage collected! This has the consequence that + * you will see all the application's static class resources (like singletons) + * around after web app shutdown, which is not the fault of those classes! + * + *

This listener should be registered as the first one in web.xml, + * before any application listeners such as Spring's ContextLoaderListener. + * This allows the listener to take full effect at the right time of the lifecycle. + * + * @author Juergen Hoeller + * @since 1.1 + * @see java.beans.Introspector#flushCaches() + * @see org.springframework.beans.CachedIntrospectionResults#acceptClassLoader + * @see org.springframework.beans.CachedIntrospectionResults#clearClassLoader + */ +public class IntrospectorCleanupListener implements ServletContextListener { + + public void contextInitialized(ServletContextEvent event) { + CachedIntrospectionResults.acceptClassLoader(Thread.currentThread().getContextClassLoader()); + } + + public void contextDestroyed(ServletContextEvent event) { + CachedIntrospectionResults.clearClassLoader(Thread.currentThread().getContextClassLoader()); + Introspector.flushCaches(); + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/util/JavaScriptUtils.java b/org.springframework.web/src/main/java/org/springframework/web/util/JavaScriptUtils.java new file mode 100644 index 0000000000..9b82db0405 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/util/JavaScriptUtils.java @@ -0,0 +1,85 @@ +/* + * Copyright 2002-2006 the original author or authors. + * + * 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.springframework.web.util; + +/** + * Utility class for JavaScript escaping. + * Escapes based on the JavaScript 1.5 recommendation. + * + *

Reference: + * + * Core JavaScript 1.5 Guide + * + * + * @author Juergen Hoeller + * @author Rob Harrop + * @since 1.1.1 + */ +public class JavaScriptUtils { + + /** + * Turn special characters into escaped characters conforming to JavaScript. + * Handles complete character set defined in HTML 4.01 recommendation. + * @param input the input string + * @return the escaped string + */ + public static String javaScriptEscape(String input) { + if (input == null) { + return input; + } + + StringBuffer filtered = new StringBuffer(input.length()); + char prevChar = '\u0000'; + char c; + for (int i = 0; i < input.length(); i++) { + c = input.charAt(i); + if (c == '"') { + filtered.append("\\\""); + } + else if (c == '\'') { + filtered.append("\\'"); + } + else if (c == '\\') { + filtered.append("\\\\"); + } + else if (c == '/') { + filtered.append("\\/"); + } + else if (c == '\t') { + filtered.append("\\t"); + } + else if (c == '\n') { + if (prevChar != '\r') { + filtered.append("\\n"); + } + } + else if (c == '\r') { + filtered.append("\\n"); + } + else if (c == '\f') { + filtered.append("\\f"); + } + else { + filtered.append(c); + } + prevChar = c; + + } + return filtered.toString(); + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/util/Log4jConfigListener.java b/org.springframework.web/src/main/java/org/springframework/web/util/Log4jConfigListener.java new file mode 100644 index 0000000000..95e501f89a --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/util/Log4jConfigListener.java @@ -0,0 +1,54 @@ +/* + * Copyright 2002-2008 the original author or authors. + * + * 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.springframework.web.util; + +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; + +/** + * Bootstrap listener for custom log4j initialization in a web environment. + * Delegates to {@link Log4jWebConfigurer} (see its javadoc for configuration details). + * + * WARNING: Assumes an expanded WAR file, both for loading the configuration + * file and for writing the log files. If you want to keep your WAR unexpanded or + * don't need application-specific log files within the WAR directory, don't use + * log4j setup within the application (thus, don't use Log4jConfigListener or + * Log4jConfigServlet). Instead, use a global, VM-wide log4j setup (for example, + * in JBoss) or JDK 1.4's java.util.logging (which is global too). + * + *

This listener should be registered before ContextLoaderListener in web.xml + * when using custom log4j initialization. + * + * @author Juergen Hoeller + * @since 13.03.2003 + * @see Log4jWebConfigurer + * @see Log4jConfigServlet + * @see org.springframework.web.context.ContextLoaderListener + * @see org.springframework.web.context.ContextLoaderServlet + * @see WebAppRootListener + */ +public class Log4jConfigListener implements ServletContextListener { + + public void contextInitialized(ServletContextEvent event) { + Log4jWebConfigurer.initLogging(event.getServletContext()); + } + + public void contextDestroyed(ServletContextEvent event) { + Log4jWebConfigurer.shutdownLogging(event.getServletContext()); + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/util/Log4jConfigServlet.java b/org.springframework.web/src/main/java/org/springframework/web/util/Log4jConfigServlet.java new file mode 100644 index 0000000000..841fb9e6e5 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/util/Log4jConfigServlet.java @@ -0,0 +1,82 @@ +/* + * Copyright 2002-2008 the original author or authors. + * + * 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.springframework.web.util; + +import java.io.IOException; + +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * Bootstrap servlet for custom log4j initialization in a web environment. + * Delegates to {@link Log4jWebConfigurer} (see its javadoc for configuration details). + * + * WARNING: Assumes an expanded WAR file, both for loading the configuration + * file and for writing the log files. If you want to keep your WAR unexpanded or + * don't need application-specific log files within the WAR directory, don't use + * log4j setup within the application (thus, don't use Log4jConfigListener or + * Log4jConfigServlet). Instead, use a global, VM-wide log4j setup (for example, + * in JBoss) or JDK 1.4's java.util.logging (which is global too). + * + *

Note: This servlet should have a lower load-on-startup value + * in web.xml than ContextLoaderServlet, when using custom log4j + * initialization. + * + *

Note that this class has been deprecated for containers implementing + * Servlet API 2.4 or higher, in favor of {@link Log4jConfigListener}.
+ * According to Servlet 2.4, listeners must be initialized before load-on-startup + * servlets. Many Servlet 2.3 containers already enforce this behavior + * (see ContextLoaderServlet javadocs for details). If you use such a container, + * this servlet can be replaced with Log4jConfigListener. + * + * @author Juergen Hoeller + * @author Darren Davison + * @since 12.08.2003 + * @see Log4jWebConfigurer + * @see Log4jConfigListener + * @see org.springframework.web.context.ContextLoaderServlet + */ +public class Log4jConfigServlet extends HttpServlet { + + public void init() { + Log4jWebConfigurer.initLogging(getServletContext()); + } + + public void destroy() { + Log4jWebConfigurer.shutdownLogging(getServletContext()); + } + + + /** + * This should never even be called since no mapping to this servlet should + * ever be created in web.xml. That's why a correctly invoked Servlet 2.3 + * listener is much more appropriate for initialization work ;-) + */ + public void service(HttpServletRequest request, HttpServletResponse response) throws IOException { + getServletContext().log( + "Attempt to call service method on Log4jConfigServlet as [" + + request.getRequestURI() + "] was ignored"); + response.sendError(HttpServletResponse.SC_BAD_REQUEST); + } + + public String getServletInfo() { + return "Log4jConfigServlet for Servlet API 2.3 " + + "(deprecated in favor of Log4jConfigListener for Servlet API 2.4)"; + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/util/Log4jWebConfigurer.java b/org.springframework.web/src/main/java/org/springframework/web/util/Log4jWebConfigurer.java new file mode 100644 index 0000000000..ac37e6c099 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/util/Log4jWebConfigurer.java @@ -0,0 +1,190 @@ +/* + * Copyright 2002-2008 the original author or authors. + * + * 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.springframework.web.util; + +import java.io.FileNotFoundException; + +import javax.servlet.ServletContext; + +import org.springframework.util.Log4jConfigurer; +import org.springframework.util.ResourceUtils; +import org.springframework.util.SystemPropertyUtils; + +/** + * Convenience class that performs custom log4j initialization for web environments, + * allowing for log file paths within the web application, with the option to + * perform automatic refresh checks (for runtime changes in logging configuration). + * + *

WARNING: Assumes an expanded WAR file, both for loading the configuration + * file and for writing the log files. If you want to keep your WAR unexpanded or + * don't need application-specific log files within the WAR directory, don't use + * log4j setup within the application (thus, don't use Log4jConfigListener or + * Log4jConfigServlet). Instead, use a global, VM-wide log4j setup (for example, + * in JBoss) or JDK 1.4's java.util.logging (which is global too). + * + *

Supports three init parameters at the servlet context level (that is, + * context-param entries in web.xml): + * + *

+ * + *

Note: initLogging should be called before any other Spring activity + * (when using log4j), for proper initialization before any Spring logging attempts. + * + *

Log4j's watchdog thread will asynchronously check whether the timestamp + * of the config file has changed, using the given interval between checks. + * A refresh interval of 1000 milliseconds (one second), which allows to + * do on-demand log level changes with immediate effect, is not unfeasible. + + *

WARNING: Log4j's watchdog thread does not terminate until VM shutdown; + * in particular, it does not terminate on LogManager shutdown. Therefore, it is + * recommended to not use config file refreshing in a production J2EE + * environment; the watchdog thread would not stop on application shutdown there. + * + *

By default, this configurer automatically sets the web app root system property, + * for "${key}" substitutions within log file locations in the log4j config file, + * allowing for log file paths relative to the web application root directory. + * The default system property key is "webapp.root", to be used in a log4j config + * file like as follows: + * + *

log4j.appender.myfile.File=${webapp.root}/WEB-INF/demo.log + * + *

Alternatively, specify a unique context-param "webAppRootKey" per web application. + * For example, with "webAppRootKey = "demo.root": + * + *

log4j.appender.myfile.File=${demo.root}/WEB-INF/demo.log + * + *

WARNING: Some containers (like Tomcat) do not keep system properties + * separate per web app. You have to use unique "webAppRootKey" context-params per web + * app then, to avoid clashes. Other containers like Resin do isolate each web app's + * system properties: Here you can use the default key (i.e. no "webAppRootKey" + * context-param at all) without worrying. + * + * @author Juergen Hoeller + * @since 12.08.2003 + * @see org.springframework.util.Log4jConfigurer + * @see Log4jConfigListener + * @see Log4jConfigServlet + */ +public abstract class Log4jWebConfigurer { + + /** Parameter specifying the location of the log4j config file */ + public static final String CONFIG_LOCATION_PARAM = "log4jConfigLocation"; + + /** Parameter specifying the refresh interval for checking the log4j config file */ + public static final String REFRESH_INTERVAL_PARAM = "log4jRefreshInterval"; + + /** Parameter specifying whether to expose the web app root system property */ + public static final String EXPOSE_WEB_APP_ROOT_PARAM = "log4jExposeWebAppRoot"; + + + /** + * Initialize log4j, including setting the web app root system property. + * @param servletContext the current ServletContext + * @see WebUtils#setWebAppRootSystemProperty + */ + public static void initLogging(ServletContext servletContext) { + // Expose the web app root system property. + if (exposeWebAppRoot(servletContext)) { + WebUtils.setWebAppRootSystemProperty(servletContext); + } + + // Only perform custom log4j initialization in case of a config file. + String location = servletContext.getInitParameter(CONFIG_LOCATION_PARAM); + if (location != null) { + // Perform actual log4j initialization; else rely on log4j's default initialization. + try { + // Return a URL (e.g. "classpath:" or "file:") as-is; + // consider a plain file path as relative to the web application root directory. + if (!ResourceUtils.isUrl(location)) { + // Resolve system property placeholders before resolving real path. + location = SystemPropertyUtils.resolvePlaceholders(location); + location = WebUtils.getRealPath(servletContext, location); + } + + // Write log message to server log. + servletContext.log("Initializing log4j from [" + location + "]"); + + // Check whether refresh interval was specified. + String intervalString = servletContext.getInitParameter(REFRESH_INTERVAL_PARAM); + if (intervalString != null) { + // Initialize with refresh interval, i.e. with log4j's watchdog thread, + // checking the file in the background. + try { + long refreshInterval = Long.parseLong(intervalString); + Log4jConfigurer.initLogging(location, refreshInterval); + } + catch (NumberFormatException ex) { + throw new IllegalArgumentException("Invalid 'log4jRefreshInterval' parameter: " + ex.getMessage()); + } + } + else { + // Initialize without refresh check, i.e. without log4j's watchdog thread. + Log4jConfigurer.initLogging(location); + } + } + catch (FileNotFoundException ex) { + throw new IllegalArgumentException("Invalid 'log4jConfigLocation' parameter: " + ex.getMessage()); + } + } + } + + /** + * Shut down log4j, properly releasing all file locks + * and resetting the web app root system property. + * @param servletContext the current ServletContext + * @see WebUtils#removeWebAppRootSystemProperty + */ + public static void shutdownLogging(ServletContext servletContext) { + servletContext.log("Shutting down log4j"); + try { + Log4jConfigurer.shutdownLogging(); + } + finally { + // Remove the web app root system property. + if (exposeWebAppRoot(servletContext)) { + WebUtils.removeWebAppRootSystemProperty(servletContext); + } + } + } + + /** + * Return whether to expose the web app root system property, + * checking the corresponding ServletContext init parameter. + * @see #EXPOSE_WEB_APP_ROOT_PARAM + */ + private static boolean exposeWebAppRoot(ServletContext servletContext) { + String exposeWebAppRootParam = servletContext.getInitParameter(EXPOSE_WEB_APP_ROOT_PARAM); + return (exposeWebAppRootParam == null || Boolean.valueOf(exposeWebAppRootParam).booleanValue()); + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/util/NestedServletException.java b/org.springframework.web/src/main/java/org/springframework/web/util/NestedServletException.java new file mode 100644 index 0000000000..32f1dca4ac --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/util/NestedServletException.java @@ -0,0 +1,78 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.web.util; + +import javax.servlet.ServletException; + +import org.springframework.core.NestedExceptionUtils; + +/** + * Subclass of ServletException that properly handles a root cause in terms + * of message and stacktrace, just like NestedChecked/RuntimeException does. + * Note that the plain ServletException doesn't expose its root cause at all, + * neither in the exception message nor in printed stack traces! + * + *

The similarity between this class and the NestedChecked/RuntimeException + * class is unavoidable, as this class needs to derive from ServletException + * and cannot derive from NestedCheckedException. + * + * @author Juergen Hoeller + * @since 1.2.5 + * @see #getMessage + * @see #printStackTrace + * @see org.springframework.core.NestedCheckedException + * @see org.springframework.core.NestedRuntimeException + */ +public class NestedServletException extends ServletException { + + /** Use serialVersionUID from Spring 1.2 for interoperability */ + private static final long serialVersionUID = -5292377985529381145L; + + + /** + * Construct a NestedServletException with the specified detail message. + * @param msg the detail message + */ + public NestedServletException(String msg) { + super(msg); + } + + /** + * Construct a NestedServletException with the specified detail message + * and nested exception. + * @param msg the detail message + * @param cause the nested exception + */ + public NestedServletException(String msg, Throwable cause) { + super(msg, cause); + // Set JDK 1.4 exception chain cause if not done by ServletException class already + // (this differs between Servlet API versions). + if (getCause() == null) { + initCause(cause); + } + } + + + /** + * Return the detail message, including the message from the nested exception + * if there is one. + */ + public String getMessage() { + return NestedExceptionUtils.buildMessage(super.getMessage(), getCause()); + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/util/TagUtils.java b/org.springframework.web/src/main/java/org/springframework/web/util/TagUtils.java new file mode 100644 index 0000000000..8c000e83f0 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/util/TagUtils.java @@ -0,0 +1,136 @@ +/* + * Copyright 2002-2006 the original author or authors. + * + * 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.springframework.web.util; + +import javax.servlet.jsp.PageContext; +import javax.servlet.jsp.tagext.Tag; + +import org.springframework.util.Assert; + +/** + * Utility class for tag library related code, exposing functionality + * such as translating {@link String Strings} to web scopes. + * + *

+ *

+ * + * @author Alef Arendsen + * @author Rob Harrop + * @author Juergen Hoeller + * @author Rick Evans + */ +public abstract class TagUtils { + + /** Constant identifying the page scope */ + public static final String SCOPE_PAGE = "page"; + + /** Constant identifying the request scope */ + public static final String SCOPE_REQUEST = "request"; + + /** Constant identifying the session scope */ + public static final String SCOPE_SESSION = "session"; + + /** Constant identifying the application scope */ + public static final String SCOPE_APPLICATION = "application"; + + + /** + * Determines the scope for a given input String. + *

If the String does not match 'request', 'session', + * 'page' or 'application', the method will return {@link PageContext#PAGE_SCOPE}. + * @param scope the String to inspect + * @return the scope found, or {@link PageContext#PAGE_SCOPE} if no scope matched + * @throws IllegalArgumentException if the supplied scope is null + */ + public static int getScope(String scope) { + Assert.notNull(scope, "Scope to search for cannot be null"); + if (scope.equals(SCOPE_REQUEST)) { + return PageContext.REQUEST_SCOPE; + } + else if (scope.equals(SCOPE_SESSION)) { + return PageContext.SESSION_SCOPE; + } + else if (scope.equals(SCOPE_APPLICATION)) { + return PageContext.APPLICATION_SCOPE; + } + else { + return PageContext.PAGE_SCOPE; + } + } + + /** + * Determine whether the supplied {@link Tag} has any ancestor tag + * of the supplied type. + * @param tag the tag whose ancestors are to be checked + * @param ancestorTagClass the ancestor {@link Class} being searched for + * @return true if the supplied {@link Tag} has any ancestor tag + * of the supplied type + * @throws IllegalArgumentException if either of the supplied arguments is null; + * or if the supplied ancestorTagClass is not type-assignable to + * the {@link Tag} class + */ + public static boolean hasAncestorOfType(Tag tag, Class ancestorTagClass) { + Assert.notNull(tag, "Tag cannot be null"); + Assert.notNull(ancestorTagClass, "Ancestor tag class cannot be null"); + if (!Tag.class.isAssignableFrom(ancestorTagClass)) { + throw new IllegalArgumentException( + "Class '" + ancestorTagClass.getName() + "' is not a valid Tag type"); + } + Tag ancestor = tag.getParent(); + while (ancestor != null) { + if (ancestorTagClass.isAssignableFrom(ancestor.getClass())) { + return true; + } + ancestor = ancestor.getParent(); + } + return false; + } + + /** + * Determine whether the supplied {@link Tag} has any ancestor tag + * of the supplied type, throwing an {@link IllegalStateException} + * if not. + * @param tag the tag whose ancestors are to be checked + * @param ancestorTagClass the ancestor {@link Class} being searched for + * @param tagName the name of the tag; for example 'option' + * @param ancestorTagName the name of the ancestor tag; for example 'select' + * @throws IllegalStateException if the supplied tag does not + * have a tag of the supplied parentTagClass as an ancestor + * @throws IllegalArgumentException if any of the supplied arguments is null, + * or in the case of the {@link String}-typed arguments, is composed wholly + * of whitespace; or if the supplied ancestorTagClass is not + * type-assignable to the {@link Tag} class + * @see #hasAncestorOfType(javax.servlet.jsp.tagext.Tag, Class) + */ + public static void assertHasAncestorOfType(Tag tag, Class ancestorTagClass, String tagName, String ancestorTagName) { + Assert.hasText(tagName, "'tagName' must not be empty"); + Assert.hasText(ancestorTagName, "'ancestorTagName' must not be empty"); + if (!TagUtils.hasAncestorOfType(tag, ancestorTagClass)) { + throw new IllegalStateException("The '" + tagName + "' tag can only be used inside a valid '" + ancestorTagName + "' tag."); + } + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/util/UrlPathHelper.java b/org.springframework.web/src/main/java/org/springframework/web/util/UrlPathHelper.java new file mode 100644 index 0000000000..59a3917919 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/util/UrlPathHelper.java @@ -0,0 +1,357 @@ +/* + * Copyright 2002-2008 the original author or authors. + * + * 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.springframework.web.util; + +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; + +import javax.servlet.http.HttpServletRequest; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.util.StringUtils; + +/** + * Helper class for URL path matching. Provides support for URL paths in + * RequestDispatcher includes and support for consistent URL decoding. + * + *

Used by {@link org.springframework.web.servlet.handler.AbstractUrlHandlerMapping}, + * {@link org.springframework.web.servlet.mvc.multiaction.AbstractUrlMethodNameResolver} + * and {@link org.springframework.web.servlet.support.RequestContext} for path matching + * and/or URI determination. + * + * @author Juergen Hoeller + * @author Rob Harrop + * @since 14.01.2004 + */ +public class UrlPathHelper { + + /** + * @deprecated as of Spring 2.0, in favor of WebUtils.INCLUDE_REQUEST_URI_ATTRIBUTE + * @see org.springframework.web.util.WebUtils#INCLUDE_REQUEST_URI_ATTRIBUTE + */ + public static final String INCLUDE_URI_REQUEST_ATTRIBUTE = WebUtils.INCLUDE_REQUEST_URI_ATTRIBUTE; + + /** + * @deprecated as of Spring 2.0, in favor of WebUtils.INCLUDE_CONTEXT_PATH_ATTRIBUTE + * @see org.springframework.web.util.WebUtils#INCLUDE_CONTEXT_PATH_ATTRIBUTE + */ + public static final String INCLUDE_CONTEXT_PATH_REQUEST_ATTRIBUTE = WebUtils.INCLUDE_CONTEXT_PATH_ATTRIBUTE; + + /** + * @deprecated as of Spring 2.0, in favor of WebUtils.INCLUDE_SERVLET_PATH_ATTRIBUTE + * @see org.springframework.web.util.WebUtils#INCLUDE_SERVLET_PATH_ATTRIBUTE + */ + public static final String INCLUDE_SERVLET_PATH_REQUEST_ATTRIBUTE = WebUtils.INCLUDE_SERVLET_PATH_ATTRIBUTE; + + + private final Log logger = LogFactory.getLog(getClass()); + + private boolean alwaysUseFullPath = false; + + private boolean urlDecode = true; + + private String defaultEncoding = WebUtils.DEFAULT_CHARACTER_ENCODING; + + + /** + * Set if URL lookup should always use full path within current servlet + * context. Else, the path within the current servlet mapping is used + * if applicable (i.e. in the case of a ".../*" servlet mapping in web.xml). + * Default is "false". + */ + public void setAlwaysUseFullPath(boolean alwaysUseFullPath) { + this.alwaysUseFullPath = alwaysUseFullPath; + } + + /** + * Set if context path and request URI should be URL-decoded. + * Both are returned undecoded by the Servlet API, + * in contrast to the servlet path. + *

Uses either the request encoding or the default encoding according + * to the Servlet spec (ISO-8859-1). + *

Default is "true", as of Spring 2.5. + * @see #getServletPath + * @see #getContextPath + * @see #getRequestUri + * @see WebUtils#DEFAULT_CHARACTER_ENCODING + * @see javax.servlet.ServletRequest#getCharacterEncoding() + * @see java.net.URLDecoder#decode(String, String) + */ + public void setUrlDecode(boolean urlDecode) { + this.urlDecode = urlDecode; + } + + /** + * Set the default character encoding to use for URL decoding. + * Default is ISO-8859-1, according to the Servlet spec. + *

If the request specifies a character encoding itself, the request + * encoding will override this setting. This also allows for generically + * overriding the character encoding in a filter that invokes the + * ServletRequest.setCharacterEncoding method. + * @param defaultEncoding the character encoding to use + * @see #determineEncoding + * @see javax.servlet.ServletRequest#getCharacterEncoding() + * @see javax.servlet.ServletRequest#setCharacterEncoding(String) + * @see WebUtils#DEFAULT_CHARACTER_ENCODING + */ + public void setDefaultEncoding(String defaultEncoding) { + this.defaultEncoding = defaultEncoding; + } + + /** + * Return the default character encoding to use for URL decoding. + */ + protected String getDefaultEncoding() { + return this.defaultEncoding; + } + + + /** + * Return the mapping lookup path for the given request, within the current + * servlet mapping if applicable, else within the web application. + *

Detects include request URL if called within a RequestDispatcher include. + * @param request current HTTP request + * @return the lookup path + * @see #getPathWithinApplication + * @see #getPathWithinServletMapping + */ + public String getLookupPathForRequest(HttpServletRequest request) { + // Always use full path within current servlet context? + if (this.alwaysUseFullPath) { + return getPathWithinApplication(request); + } + // Else, use path within current servlet mapping if applicable + String rest = getPathWithinServletMapping(request); + if (!"".equals(rest)) { + return rest; + } + else { + return getPathWithinApplication(request); + } + } + + /** + * Return the path within the servlet mapping for the given request, + * i.e. the part of the request's URL beyond the part that called the servlet, + * or "" if the whole URL has been used to identify the servlet. + *

Detects include request URL if called within a RequestDispatcher include. + *

E.g.: servlet mapping = "/test/*"; request URI = "/test/a" -> "/a". + *

E.g.: servlet mapping = "/test"; request URI = "/test" -> "". + *

E.g.: servlet mapping = "/*.test"; request URI = "/a.test" -> "". + * @param request current HTTP request + * @return the path within the servlet mapping, or "" + */ + public String getPathWithinServletMapping(HttpServletRequest request) { + String pathWithinApp = getPathWithinApplication(request); + String servletPath = getServletPath(request); + if (pathWithinApp.startsWith(servletPath)) { + // Normal case: URI contains servlet path. + return pathWithinApp.substring(servletPath.length()); + } + else { + // Special case: URI is different from servlet path. + // Can happen e.g. with index page: URI="/", servletPath="/index.html" + // Use servlet path in this case, as it indicates the actual target path. + return servletPath; + } + } + + /** + * Return the path within the web application for the given request. + *

Detects include request URL if called within a RequestDispatcher include. + * @param request current HTTP request + * @return the path within the web application + */ + public String getPathWithinApplication(HttpServletRequest request) { + String contextPath = getContextPath(request); + String requestUri = getRequestUri(request); + if (StringUtils.startsWithIgnoreCase(requestUri, contextPath)) { + // Normal case: URI contains context path. + String path = requestUri.substring(contextPath.length()); + return (StringUtils.hasText(path) ? path : "/"); + } + else { + // Special case: rather unusual. + return requestUri; + } + } + + + /** + * Return the request URI for the given request, detecting an include request + * URL if called within a RequestDispatcher include. + *

As the value returned by request.getRequestURI() is not + * decoded by the servlet container, this method will decode it. + *

The URI that the web container resolves should be correct, but some + * containers like JBoss/Jetty incorrectly include ";" strings like ";jsessionid" + * in the URI. This method cuts off such incorrect appendices. + * @param request current HTTP request + * @return the request URI + */ + public String getRequestUri(HttpServletRequest request) { + String uri = (String) request.getAttribute(WebUtils.INCLUDE_REQUEST_URI_ATTRIBUTE); + if (uri == null) { + uri = request.getRequestURI(); + } + return decodeAndCleanUriString(request, uri); + } + + /** + * Return the context path for the given request, detecting an include request + * URL if called within a RequestDispatcher include. + *

As the value returned by request.getContextPath() is not + * decoded by the servlet container, this method will decode it. + * @param request current HTTP request + * @return the context path + */ + public String getContextPath(HttpServletRequest request) { + String contextPath = (String) request.getAttribute(WebUtils.INCLUDE_CONTEXT_PATH_ATTRIBUTE); + if (contextPath == null) { + contextPath = request.getContextPath(); + } + if ("/".equals(contextPath)) { + // Invalid case, but happens for includes on Jetty: silently adapt it. + contextPath = ""; + } + return decodeRequestString(request, contextPath); + } + + /** + * Return the servlet path for the given request, regarding an include request + * URL if called within a RequestDispatcher include. + *

As the value returned by request.getServletPath() is already + * decoded by the servlet container, this method will not attempt to decode it. + * @param request current HTTP request + * @return the servlet path + */ + public String getServletPath(HttpServletRequest request) { + String servletPath = (String) request.getAttribute(WebUtils.INCLUDE_SERVLET_PATH_ATTRIBUTE); + if (servletPath == null) { + servletPath = request.getServletPath(); + } + return servletPath; + } + + + /** + * Return the request URI for root of the given request. If this is a forwarded request, + * correctly resolves to the request URI of the original request. + *

Relies on the Servlet 2.4 'forward' attributes. These attributes may be set by + * other components when running in a Servlet 2.3 environment. + */ + public String getOriginatingRequestUri(HttpServletRequest request) { + String uri = (String) request.getAttribute(WebUtils.FORWARD_REQUEST_URI_ATTRIBUTE); + if (uri == null) { + uri = request.getRequestURI(); + } + return decodeAndCleanUriString(request, uri); + } + + /** + * Return the context path for the given request, detecting an include request + * URL if called within a RequestDispatcher include. + *

As the value returned by request.getContextPath() is not + * decoded by the servlet container, this method will decode it. + *

Relies on the Servlet 2.4 'forward' attributes. These attributes may be set by + * other components when running in a Servlet 2.3 environment. + * @param request current HTTP request + * @return the context path + */ + public String getOriginatingContextPath(HttpServletRequest request) { + String contextPath = (String) request.getAttribute(WebUtils.FORWARD_CONTEXT_PATH_ATTRIBUTE); + if (contextPath == null) { + contextPath = request.getContextPath(); + } + return decodeRequestString(request, contextPath); + } + + /** + * Return the request URI for root of the given request. If this is a forwarded request, + * correctly resolves to the request URI of the original request. + *

Relies on the Servlet 2.4 'forward' attributes. These attributes may be set by + * other components when running in a Servlet 2.3 environment. + * @param request current HTTP request + * @return the query string + */ + public String getOriginatingQueryString(HttpServletRequest request) { + String queryString = (String) request.getAttribute(WebUtils.FORWARD_QUERY_STRING_ATTRIBUTE); + if (queryString == null) { + queryString = request.getQueryString(); + } + return queryString; + } + + + /** + * Decode the supplied URI string and strips any extraneous portion after a ';'. + */ + private String decodeAndCleanUriString(HttpServletRequest request, String uri) { + uri = decodeRequestString(request, uri); + int semicolonIndex = uri.indexOf(';'); + return (semicolonIndex != -1 ? uri.substring(0, semicolonIndex) : uri); + } + + /** + * Decode the given source string with a URLDecoder. The encoding will be taken + * from the request, falling back to the default "ISO-8859-1". + *

The default implementation uses URLDecoder.decode(input, enc). + * @param request current HTTP request + * @param source the String to decode + * @return the decoded String + * @see WebUtils#DEFAULT_CHARACTER_ENCODING + * @see javax.servlet.ServletRequest#getCharacterEncoding + * @see java.net.URLDecoder#decode(String, String) + * @see java.net.URLDecoder#decode(String) + */ + public String decodeRequestString(HttpServletRequest request, String source) { + if (this.urlDecode) { + String enc = determineEncoding(request); + try { + return URLDecoder.decode(source, enc); + } + catch (UnsupportedEncodingException ex) { + if (logger.isWarnEnabled()) { + logger.warn("Could not decode request string [" + source + "] with encoding '" + enc + + "': falling back to platform default encoding; exception message: " + ex.getMessage()); + } + return URLDecoder.decode(source); + } + } + return source; + } + + /** + * Determine the encoding for the given request. + * Can be overridden in subclasses. + *

The default implementation checks the request encoding, + * falling back to the default encoding specified for this resolver. + * @param request current HTTP request + * @return the encoding for the request (never null) + * @see javax.servlet.ServletRequest#getCharacterEncoding() + * @see #setDefaultEncoding + */ + protected String determineEncoding(HttpServletRequest request) { + String enc = request.getCharacterEncoding(); + if (enc == null) { + enc = getDefaultEncoding(); + } + return enc; + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/util/WebAppRootListener.java b/org.springframework.web/src/main/java/org/springframework/web/util/WebAppRootListener.java new file mode 100644 index 0000000000..a352f8e9cd --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/util/WebAppRootListener.java @@ -0,0 +1,63 @@ +/* + * Copyright 2002-2008 the original author or authors. + * + * 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.springframework.web.util; + +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; + +/** + * Listener that sets a system property to the web application root directory. + * The key of the system property can be defined with the "webAppRootKey" init + * parameter at the servlet context level (i.e. context-param in web.xml), + * the default key is "webapp.root". + * + *

Can be used for toolkits that support substition with system properties + * (i.e. System.getProperty values), like log4j's "${key}" syntax within log + * file locations. + * + *

Note: This listener should be placed before ContextLoaderListener in web.xml, + * at least when used for log4j. Log4jConfigListener sets the system property + * implicitly, so there's no need for this listener in addition to it. + * + *

WARNING: Some containers, e.g. Tomcat, do NOT keep system properties separate + * per web app. You have to use unique "webAppRootKey" context-params per web app + * then, to avoid clashes. Other containers like Resin do isolate each web app's + * system properties: Here you can use the default key (i.e. no "webAppRootKey" + * context-param at all) without worrying. + * + *

WARNING: The WAR file containing the web application needs to be expanded + * to allow for setting the web app root system property. This is by default not + * the case when a WAR file gets deployed to WebLogic, for example. Do not use + * this listener in such an environment! + * + * @author Juergen Hoeller + * @since 18.04.2003 + * @see WebUtils#setWebAppRootSystemProperty + * @see Log4jConfigListener + * @see java.lang.System#getProperty + */ +public class WebAppRootListener implements ServletContextListener { + + public void contextInitialized(ServletContextEvent event) { + WebUtils.setWebAppRootSystemProperty(event.getServletContext()); + } + + public void contextDestroyed(ServletContextEvent event) { + WebUtils.removeWebAppRootSystemProperty(event.getServletContext()); + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/util/WebUtils.java b/org.springframework.web/src/main/java/org/springframework/web/util/WebUtils.java new file mode 100644 index 0000000000..971362bdbf --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/util/WebUtils.java @@ -0,0 +1,679 @@ +/* + * Copyright 2002-2008 the original author or authors. + * + * 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.springframework.web.util; + +import java.io.File; +import java.io.FileNotFoundException; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.Map; +import java.util.TreeMap; + +import javax.servlet.ServletContext; +import javax.servlet.ServletRequest; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +/** + * Miscellaneous utilities for web applications. + * Used by various framework classes. + * + * @author Rod Johnson + * @author Juergen Hoeller + */ +public abstract class WebUtils { + + /** + * Standard Servlet 2.3+ spec request attributes for include URI and paths. + *

If included via a RequestDispatcher, the current resource will see the + * originating request. Its own URI and paths are exposed as request attributes. + */ + public static final String INCLUDE_REQUEST_URI_ATTRIBUTE = "javax.servlet.include.request_uri"; + public static final String INCLUDE_CONTEXT_PATH_ATTRIBUTE = "javax.servlet.include.context_path"; + public static final String INCLUDE_SERVLET_PATH_ATTRIBUTE = "javax.servlet.include.servlet_path"; + public static final String INCLUDE_PATH_INFO_ATTRIBUTE = "javax.servlet.include.path_info"; + public static final String INCLUDE_QUERY_STRING_ATTRIBUTE = "javax.servlet.include.query_string"; + + /** + * Standard Servlet 2.4+ spec request attributes for forward URI and paths. + *

If forwarded to via a RequestDispatcher, the current resource will see its + * own URI and paths. The originating URI and paths are exposed as request attributes. + */ + public static final String FORWARD_REQUEST_URI_ATTRIBUTE = "javax.servlet.forward.request_uri"; + public static final String FORWARD_CONTEXT_PATH_ATTRIBUTE = "javax.servlet.forward.context_path"; + public static final String FORWARD_SERVLET_PATH_ATTRIBUTE = "javax.servlet.forward.servlet_path"; + public static final String FORWARD_PATH_INFO_ATTRIBUTE = "javax.servlet.forward.path_info"; + public static final String FORWARD_QUERY_STRING_ATTRIBUTE = "javax.servlet.forward.query_string"; + + /** + * Standard Servlet 2.3+ spec request attributes for error pages. + *

To be exposed to JSPs that are marked as error pages, when forwarding + * to them directly rather than through the servlet container's error page + * resolution mechanism. + */ + public static final String ERROR_STATUS_CODE_ATTRIBUTE = "javax.servlet.error.status_code"; + public static final String ERROR_EXCEPTION_TYPE_ATTRIBUTE = "javax.servlet.error.exception_type"; + public static final String ERROR_MESSAGE_ATTRIBUTE = "javax.servlet.error.message"; + public static final String ERROR_EXCEPTION_ATTRIBUTE = "javax.servlet.error.exception"; + public static final String ERROR_REQUEST_URI_ATTRIBUTE = "javax.servlet.error.request_uri"; + public static final String ERROR_SERVLET_NAME_ATTRIBUTE = "javax.servlet.error.servlet_name"; + + + /** + * Prefix of the charset clause in a content type String: ";charset=" + */ + public static final String CONTENT_TYPE_CHARSET_PREFIX = ";charset="; + + /** + * Default character encoding to use when request.getCharacterEncoding + * returns null, according to the Servlet spec. + * @see ServletRequest#getCharacterEncoding + */ + public static final String DEFAULT_CHARACTER_ENCODING = "ISO-8859-1"; + + /** + * Standard Servlet spec context attribute that specifies a temporary + * directory for the current web application, of type java.io.File. + */ + public static final String TEMP_DIR_CONTEXT_ATTRIBUTE = "javax.servlet.context.tempdir"; + + /** + * HTML escape parameter at the servlet context level + * (i.e. a context-param in web.xml): "defaultHtmlEscape". + */ + public static final String HTML_ESCAPE_CONTEXT_PARAM = "defaultHtmlEscape"; + + /** + * Web app root key parameter at the servlet context level + * (i.e. a context-param in web.xml): "webAppRootKey". + */ + public static final String WEB_APP_ROOT_KEY_PARAM = "webAppRootKey"; + + /** Default web app root key: "webapp.root" */ + public static final String DEFAULT_WEB_APP_ROOT_KEY = "webapp.root"; + + /** Name suffixes in case of image buttons */ + public static final String[] SUBMIT_IMAGE_SUFFIXES = {".x", ".y"}; + + /** Key for the mutex session attribute */ + public static final String SESSION_MUTEX_ATTRIBUTE = WebUtils.class.getName() + ".MUTEX"; + + + /** + * Set a system property to the web application root directory. + * The key of the system property can be defined with the "webAppRootKey" + * context-param in web.xml. Default is "webapp.root". + *

Can be used for tools that support substition with System.getProperty + * values, like log4j's "${key}" syntax within log file locations. + * @param servletContext the servlet context of the web application + * @throws IllegalStateException if the system property is already set, + * or if the WAR file is not expanded + * @see #WEB_APP_ROOT_KEY_PARAM + * @see #DEFAULT_WEB_APP_ROOT_KEY + * @see WebAppRootListener + * @see Log4jWebConfigurer + */ + public static void setWebAppRootSystemProperty(ServletContext servletContext) throws IllegalStateException { + Assert.notNull(servletContext, "ServletContext must not be null"); + String root = servletContext.getRealPath("/"); + if (root == null) { + throw new IllegalStateException( + "Cannot set web app root system property when WAR file is not expanded"); + } + String param = servletContext.getInitParameter(WEB_APP_ROOT_KEY_PARAM); + String key = (param != null ? param : DEFAULT_WEB_APP_ROOT_KEY); + String oldValue = System.getProperty(key); + if (oldValue != null && !StringUtils.pathEquals(oldValue, root)) { + throw new IllegalStateException( + "Web app root system property already set to different value: '" + + key + "' = [" + oldValue + "] instead of [" + root + "] - " + + "Choose unique values for the 'webAppRootKey' context-param in your web.xml files!"); + } + System.setProperty(key, root); + servletContext.log("Set web app root system property: '" + key + "' = [" + root + "]"); + } + + /** + * Remove the system property that points to the web app root directory. + * To be called on shutdown of the web application. + * @param servletContext the servlet context of the web application + * @see #setWebAppRootSystemProperty + */ + public static void removeWebAppRootSystemProperty(ServletContext servletContext) { + Assert.notNull(servletContext, "ServletContext must not be null"); + String param = servletContext.getInitParameter(WEB_APP_ROOT_KEY_PARAM); + String key = (param != null ? param : DEFAULT_WEB_APP_ROOT_KEY); + System.getProperties().remove(key); + } + + /** + * Return whether default HTML escaping is enabled for the web application, + * i.e. the value of the "defaultHtmlEscape" context-param in web.xml + * (if any). Falls back to false in case of no explicit default given. + * @param servletContext the servlet context of the web application + * @return whether default HTML escaping is enabled (default is false) + */ + public static boolean isDefaultHtmlEscape(ServletContext servletContext) { + if (servletContext == null) { + return false; + } + String param = servletContext.getInitParameter(HTML_ESCAPE_CONTEXT_PARAM); + return Boolean.valueOf(param).booleanValue(); + } + + /** + * Return whether default HTML escaping is enabled for the web application, + * i.e. the value of the "defaultHtmlEscape" context-param in web.xml + * (if any). + *

This method differentiates between no param specified at all and + * an actual boolean value specified, allowing to have a context-specific + * default in case of no setting at the global level. + * @param servletContext the servlet context of the web application + * @return whether default HTML escaping is enabled (null = no explicit default) + */ + public static Boolean getDefaultHtmlEscape(ServletContext servletContext) { + if (servletContext == null) { + return null; + } + Assert.notNull(servletContext, "ServletContext must not be null"); + String param = servletContext.getInitParameter(HTML_ESCAPE_CONTEXT_PARAM); + return (StringUtils.hasText(param)? Boolean.valueOf(param) : null); + } + + /** + * Return the temporary directory for the current web application, + * as provided by the servlet container. + * @param servletContext the servlet context of the web application + * @return the File representing the temporary directory + */ + public static File getTempDir(ServletContext servletContext) { + Assert.notNull(servletContext, "ServletContext must not be null"); + return (File) servletContext.getAttribute(TEMP_DIR_CONTEXT_ATTRIBUTE); + } + + /** + * Return the real path of the given path within the web application, + * as provided by the servlet container. + *

Prepends a slash if the path does not already start with a slash, + * and throws a FileNotFoundException if the path cannot be resolved to + * a resource (in contrast to ServletContext's getRealPath, + * which returns null). + * @param servletContext the servlet context of the web application + * @param path the path within the web application + * @return the corresponding real path + * @throws FileNotFoundException if the path cannot be resolved to a resource + * @see javax.servlet.ServletContext#getRealPath + */ + public static String getRealPath(ServletContext servletContext, String path) throws FileNotFoundException { + Assert.notNull(servletContext, "ServletContext must not be null"); + // Interpret location as relative to the web application root directory. + if (!path.startsWith("/")) { + path = "/" + path; + } + String realPath = servletContext.getRealPath(path); + if (realPath == null) { + throw new FileNotFoundException( + "ServletContext resource [" + path + "] cannot be resolved to absolute file path - " + + "web application archive not expanded?"); + } + return realPath; + } + + + /** + * Determine the session id of the given request, if any. + * @param request current HTTP request + * @return the session id, or null if none + */ + public static String getSessionId(HttpServletRequest request) { + Assert.notNull(request, "Request must not be null"); + HttpSession session = request.getSession(false); + return (session != null ? session.getId() : null); + } + + /** + * Check the given request for a session attribute of the given name. + * Returns null if there is no session or if the session has no such attribute. + * Does not create a new session if none has existed before! + * @param request current HTTP request + * @param name the name of the session attribute + * @return the value of the session attribute, or null if not found + */ + public static Object getSessionAttribute(HttpServletRequest request, String name) { + Assert.notNull(request, "Request must not be null"); + HttpSession session = request.getSession(false); + return (session != null ? session.getAttribute(name) : null); + } + + /** + * Check the given request for a session attribute of the given name. + * Throws an exception if there is no session or if the session has no such + * attribute. Does not create a new session if none has existed before! + * @param request current HTTP request + * @param name the name of the session attribute + * @return the value of the session attribute, or null if not found + * @throws IllegalStateException if the session attribute could not be found + */ + public static Object getRequiredSessionAttribute(HttpServletRequest request, String name) + throws IllegalStateException { + + Object attr = getSessionAttribute(request, name); + if (attr == null) { + throw new IllegalStateException("No session attribute '" + name + "' found"); + } + return attr; + } + + /** + * Set the session attribute with the given name to the given value. + * Removes the session attribute if value is null, if a session existed at all. + * Does not create a new session if not necessary! + * @param request current HTTP request + * @param name the name of the session attribute + * @param value the value of the session attribute + */ + public static void setSessionAttribute(HttpServletRequest request, String name, Object value) { + Assert.notNull(request, "Request must not be null"); + if (value != null) { + request.getSession().setAttribute(name, value); + } + else { + HttpSession session = request.getSession(false); + if (session != null) { + session.removeAttribute(name); + } + } + } + + /** + * Get the specified session attribute, creating and setting a new attribute if + * no existing found. The given class needs to have a public no-arg constructor. + * Useful for on-demand state objects in a web tier, like shopping carts. + * @param session current HTTP session + * @param name the name of the session attribute + * @param clazz the class to instantiate for a new attribute + * @return the value of the session attribute, newly created if not found + * @throws IllegalArgumentException if the session attribute could not be instantiated + */ + public static Object getOrCreateSessionAttribute(HttpSession session, String name, Class clazz) + throws IllegalArgumentException { + + Assert.notNull(session, "Session must not be null"); + Object sessionObject = session.getAttribute(name); + if (sessionObject == null) { + try { + sessionObject = clazz.newInstance(); + } + catch (InstantiationException ex) { + throw new IllegalArgumentException( + "Could not instantiate class [" + clazz.getName() + + "] for session attribute '" + name + "': " + ex.getMessage()); + } + catch (IllegalAccessException ex) { + throw new IllegalArgumentException( + "Could not access default constructor of class [" + clazz.getName() + + "] for session attribute '" + name + "': " + ex.getMessage()); + } + session.setAttribute(name, sessionObject); + } + return sessionObject; + } + + /** + * Return the best available mutex for the given session: + * that is, an object to synchronize on for the given session. + *

Returns the session mutex attribute if available; usually, + * this means that the HttpSessionMutexListener needs to be defined + * in web.xml. Falls back to the HttpSession itself + * if no mutex attribute found. + *

The session mutex is guaranteed to be the same object during + * the entire lifetime of the session, available under the key defined + * by the SESSION_MUTEX_ATTRIBUTE constant. It serves as a + * safe reference to synchronize on for locking on the current session. + *

In many cases, the HttpSession reference itself is a safe mutex + * as well, since it will always be the same object reference for the + * same active logical session. However, this is not guaranteed across + * different servlet containers; the only 100% safe way is a session mutex. + * @param session the HttpSession to find a mutex for + * @return the mutex object (never null) + * @see #SESSION_MUTEX_ATTRIBUTE + * @see HttpSessionMutexListener + */ + public static Object getSessionMutex(HttpSession session) { + Assert.notNull(session, "Session must not be null"); + Object mutex = session.getAttribute(SESSION_MUTEX_ATTRIBUTE); + if (mutex == null) { + mutex = session; + } + return mutex; + } + + + /** + * Determine whether the given request is an include request, + * that is, not a top-level HTTP request coming in from the outside. + *

Checks the presence of the "javax.servlet.include.request_uri" + * request attribute. Could check any request attribute that is only + * present in an include request. + * @param request current servlet request + * @return whether the given request is an include request + */ + public static boolean isIncludeRequest(ServletRequest request) { + return (request.getAttribute(INCLUDE_REQUEST_URI_ATTRIBUTE) != null); + } + + /** + * Expose the current request URI and paths as {@link javax.servlet.http.HttpServletRequest} + * attributes under the keys defined in the Servlet 2.4 specification, + * for containers that implement 2.3 or an earlier version of the Servlet API: + * javax.servlet.forward.request_uri, + * javax.servlet.forward.context_path, + * javax.servlet.forward.servlet_path, + * javax.servlet.forward.path_info, + * javax.servlet.forward.query_string. + *

Does not override values if already present, to not cause conflicts + * with the attributes exposed by Servlet 2.4+ containers themselves. + * @param request current servlet request + */ + public static void exposeForwardRequestAttributes(HttpServletRequest request) { + exposeRequestAttributeIfNotPresent(request, FORWARD_REQUEST_URI_ATTRIBUTE, request.getRequestURI()); + exposeRequestAttributeIfNotPresent(request, FORWARD_CONTEXT_PATH_ATTRIBUTE, request.getContextPath()); + exposeRequestAttributeIfNotPresent(request, FORWARD_SERVLET_PATH_ATTRIBUTE, request.getServletPath()); + exposeRequestAttributeIfNotPresent(request, FORWARD_PATH_INFO_ATTRIBUTE, request.getPathInfo()); + exposeRequestAttributeIfNotPresent(request, FORWARD_QUERY_STRING_ATTRIBUTE, request.getQueryString()); + } + + /** + * Expose the Servlet spec's error attributes as {@link javax.servlet.http.HttpServletRequest} + * attributes under the keys defined in the Servlet 2.3 specification, for error pages that + * are rendered directly rather than through the Servlet container's error page resolution: + * javax.servlet.error.status_code, + * javax.servlet.error.exception_type, + * javax.servlet.error.message, + * javax.servlet.error.exception, + * javax.servlet.error.request_uri, + * javax.servlet.error.servlet_name. + *

Does not override values if already present, to respect attribute values + * that have been exposed explicitly before. + *

Exposes status code 200 by default. Set the "javax.servlet.error.status_code" + * attribute explicitly (before or after) in order to expose a different status code. + * @param request current servlet request + * @param ex the exception encountered + * @param servletName the name of the offending servlet + */ + public static void exposeErrorRequestAttributes(HttpServletRequest request, Throwable ex, String servletName) { + exposeRequestAttributeIfNotPresent(request, ERROR_STATUS_CODE_ATTRIBUTE, new Integer(HttpServletResponse.SC_OK)); + exposeRequestAttributeIfNotPresent(request, ERROR_EXCEPTION_TYPE_ATTRIBUTE, ex.getClass()); + exposeRequestAttributeIfNotPresent(request, ERROR_MESSAGE_ATTRIBUTE, ex.getMessage()); + exposeRequestAttributeIfNotPresent(request, ERROR_EXCEPTION_ATTRIBUTE, ex); + exposeRequestAttributeIfNotPresent(request, ERROR_REQUEST_URI_ATTRIBUTE, request.getRequestURI()); + exposeRequestAttributeIfNotPresent(request, ERROR_SERVLET_NAME_ATTRIBUTE, servletName); + } + + /** + * Expose the specified request attribute if not already present. + * @param request current servlet request + * @param name the name of the attribute + * @param value the suggested value of the attribute + */ + private static void exposeRequestAttributeIfNotPresent(ServletRequest request, String name, Object value) { + if (request.getAttribute(name) == null) { + request.setAttribute(name, value); + } + } + + /** + * Clear the Servlet spec's error attributes as {@link javax.servlet.http.HttpServletRequest} + * attributes under the keys defined in the Servlet 2.3 specification: + * javax.servlet.error.status_code, + * javax.servlet.error.exception_type, + * javax.servlet.error.message, + * javax.servlet.error.exception, + * javax.servlet.error.request_uri, + * javax.servlet.error.servlet_name. + * @param request current servlet request + */ + public static void clearErrorRequestAttributes(HttpServletRequest request) { + request.removeAttribute(ERROR_STATUS_CODE_ATTRIBUTE); + request.removeAttribute(ERROR_EXCEPTION_TYPE_ATTRIBUTE); + request.removeAttribute(ERROR_MESSAGE_ATTRIBUTE); + request.removeAttribute(ERROR_EXCEPTION_ATTRIBUTE); + request.removeAttribute(ERROR_REQUEST_URI_ATTRIBUTE); + request.removeAttribute(ERROR_SERVLET_NAME_ATTRIBUTE); + } + + /** + * Expose the given Map as request attributes, using the keys as attribute names + * and the values as corresponding attribute values. Keys need to be Strings. + * @param request current HTTP request + * @param attributes the attributes Map + */ + public static void exposeRequestAttributes(ServletRequest request, Map attributes) { + Assert.notNull(request, "Request must not be null"); + Iterator it = attributes.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry entry = (Map.Entry) it.next(); + if (!(entry.getKey() instanceof String)) { + throw new IllegalArgumentException( + "Invalid key [" + entry.getKey() + "] in attributes Map - only Strings allowed as attribute keys"); + } + request.setAttribute((String) entry.getKey(), entry.getValue()); + } + } + + /** + * Retrieve the first cookie with the given name. Note that multiple + * cookies can have the same name but different paths or domains. + * @param request current servlet request + * @param name cookie name + * @return the first cookie with the given name, or null if none is found + */ + public static Cookie getCookie(HttpServletRequest request, String name) { + Assert.notNull(request, "Request must not be null"); + Cookie cookies[] = request.getCookies(); + if (cookies != null) { + for (int i = 0; i < cookies.length; i++) { + if (name.equals(cookies[i].getName())) { + return cookies[i]; + } + } + } + return null; + } + + + /** + * Check if a specific input type="submit" parameter was sent in the request, + * either via a button (directly with name) or via an image (name + ".x" or + * name + ".y"). + * @param request current HTTP request + * @param name name of the parameter + * @return if the parameter was sent + * @see #SUBMIT_IMAGE_SUFFIXES + */ + public static boolean hasSubmitParameter(ServletRequest request, String name) { + Assert.notNull(request, "Request must not be null"); + if (request.getParameter(name) != null) { + return true; + } + for (int i = 0; i < SUBMIT_IMAGE_SUFFIXES.length; i++) { + String suffix = SUBMIT_IMAGE_SUFFIXES[i]; + if (request.getParameter(name + suffix) != null) { + return true; + } + } + return false; + } + + /** + * Obtain a named parameter from the given request parameters. + *

See {@link #findParameterValue(java.util.Map, String)} + * for a description of the lookup algorithm. + * @param request current HTTP request + * @param name the logical name of the request parameter + * @return the value of the parameter, or null + * if the parameter does not exist in given request + */ + public static String findParameterValue(ServletRequest request, String name) { + return findParameterValue(request.getParameterMap(), name); + } + + /** + * Obtain a named parameter from the given request parameters. + *

This method will try to obtain a parameter value using the + * following algorithm: + *

    + *
  1. Try to get the parameter value using just the given logical name. + * This handles parameters of the form logicalName = value. For normal + * parameters, e.g. submitted using a hidden HTML form field, this will return + * the requested value.
  2. + *
  3. Try to obtain the parameter value from the parameter name, where the + * parameter name in the request is of the form logicalName_value = xyz + * with "_" being the configured delimiter. This deals with parameter values + * submitted using an HTML form submit button.
  4. + *
  5. If the value obtained in the previous step has a ".x" or ".y" suffix, + * remove that. This handles cases where the value was submitted using an + * HTML form image button. In this case the parameter in the request would + * actually be of the form logicalName_value.x = 123.
  6. + *
+ * @param parameters the available parameter map + * @param name the logical name of the request parameter + * @return the value of the parameter, or null + * if the parameter does not exist in given request + */ + public static String findParameterValue(Map parameters, String name) { + // First try to get it as a normal name=value parameter + String value = (String) parameters.get(name); + if (value != null) { + return value; + } + // If no value yet, try to get it as a name_value=xyz parameter + String prefix = name + "_"; + Iterator paramNames = parameters.keySet().iterator(); + while (paramNames.hasNext()) { + String paramName = (String) paramNames.next(); + if (paramName.startsWith(prefix)) { + // Support images buttons, which would submit parameters as name_value.x=123 + for (int i = 0; i < SUBMIT_IMAGE_SUFFIXES.length; i++) { + String suffix = SUBMIT_IMAGE_SUFFIXES[i]; + if (paramName.endsWith(suffix)) { + return paramName.substring(prefix.length(), paramName.length() - suffix.length()); + } + } + return paramName.substring(prefix.length()); + } + } + // We couldn't find the parameter value... + return null; + } + + /** + * Return a map containing all parameters with the given prefix. + * Maps single values to String and multiple values to String array. + *

For example, with a prefix of "spring_", "spring_param1" and + * "spring_param2" result in a Map with "param1" and "param2" as keys. + * @param request HTTP request in which to look for parameters + * @param prefix the beginning of parameter names + * (if this is null or the empty string, all parameters will match) + * @return map containing request parameters without the prefix, + * containing either a String or a String array as values + * @see javax.servlet.ServletRequest#getParameterNames + * @see javax.servlet.ServletRequest#getParameterValues + * @see javax.servlet.ServletRequest#getParameterMap + */ + public static Map getParametersStartingWith(ServletRequest request, String prefix) { + Assert.notNull(request, "Request must not be null"); + Enumeration paramNames = request.getParameterNames(); + Map params = new TreeMap(); + if (prefix == null) { + prefix = ""; + } + while (paramNames != null && paramNames.hasMoreElements()) { + String paramName = (String) paramNames.nextElement(); + if ("".equals(prefix) || paramName.startsWith(prefix)) { + String unprefixed = paramName.substring(prefix.length()); + String[] values = request.getParameterValues(paramName); + if (values == null || values.length == 0) { + // Do nothing, no values found at all. + } + else if (values.length > 1) { + params.put(unprefixed, values); + } + else { + params.put(unprefixed, values[0]); + } + } + } + return params; + } + + /** + * Return the target page specified in the request. + * @param request current servlet request + * @param paramPrefix the parameter prefix to check for + * (e.g. "_target" for parameters like "_target1" or "_target2") + * @param currentPage the current page, to be returned as fallback + * if no target page specified + * @return the page specified in the request, or current page if not found + */ + public static int getTargetPage(ServletRequest request, String paramPrefix, int currentPage) { + Enumeration paramNames = request.getParameterNames(); + while (paramNames.hasMoreElements()) { + String paramName = (String) paramNames.nextElement(); + if (paramName.startsWith(paramPrefix)) { + for (int i = 0; i < WebUtils.SUBMIT_IMAGE_SUFFIXES.length; i++) { + String suffix = WebUtils.SUBMIT_IMAGE_SUFFIXES[i]; + if (paramName.endsWith(suffix)) { + paramName = paramName.substring(0, paramName.length() - suffix.length()); + } + } + return Integer.parseInt(paramName.substring(paramPrefix.length())); + } + } + return currentPage; + } + + + /** + * Extract the URL filename from the given request URL path. + * Correctly resolves nested paths such as "/products/view.html" as well. + * @param urlPath the request URL path (e.g. "/index.html") + * @return the extracted URI filename (e.g. "index") + */ + public static String extractFilenameFromUrlPath(String urlPath) { + int end = urlPath.indexOf(';'); + if (end == -1) { + end = urlPath.indexOf('?'); + if (end == -1) { + end = urlPath.length(); + } + } + int begin = urlPath.lastIndexOf('/', end) + 1; + String filename = urlPath.substring(begin, end); + int dotIndex = filename.lastIndexOf('.'); + if (dotIndex != -1) { + filename = filename.substring(0, dotIndex); + } + return filename; + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/util/package.html b/org.springframework.web/src/main/java/org/springframework/web/util/package.html new file mode 100644 index 0000000000..5a5c49ba6e --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/util/package.html @@ -0,0 +1,8 @@ + + + +Miscellaneous web utility classes, such as HTML escaping, +log4j initialization, and cookie handling. + + + diff --git a/org.springframework.web/src/main/java/overview.html b/org.springframework.web/src/main/java/overview.html new file mode 100644 index 0000000000..1eb7a2e8c1 --- /dev/null +++ b/org.springframework.web/src/main/java/overview.html @@ -0,0 +1,7 @@ + + +

+The Spring Data Binding framework, an internal library used by Spring Web Flow. +

+ + \ No newline at end of file diff --git a/org.springframework.web/src/test/resources/log4j.xml b/org.springframework.web/src/test/resources/log4j.xml new file mode 100644 index 0000000000..767b96d620 --- /dev/null +++ b/org.springframework.web/src/test/resources/log4j.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/org.springframework.web/template.mf b/org.springframework.web/template.mf new file mode 100644 index 0000000000..6173054df7 --- /dev/null +++ b/org.springframework.web/template.mf @@ -0,0 +1,74 @@ +Bundle-SymbolicName: org.springframework.orm +Bundle-Name: Spring ORM +Bundle-Vendor: SpringSource +Bundle-ManifestVersion: 2 +Import-Package: + com.ibatis.sqlmap.engine.transaction.external;version="[2.3.0.677, 3.0.0)";resolution:=optional, + oracle.toplink.essentials.expressions;version="[2.0.0.b41-beta2, 3.0.0)";resolution:=optional, + org.eclipse.persistence.expressions;version="[1.0.0, 2.0.0)";resolution:=optional +Import-Template: + com.ibatis.*;version="[2.3.0.677, 3.0.0)";resolution:=optional, + javax.jdo.*;version="[2.0.0, 3.0.0)";resolution:=optional, + javax.persistence.*;version="[1.0.0, 2.0.0)";resolution:=optional, + javax.servlet.*;version="[2.4.0, 3.0.0)";resolution:=optional, + javax.transaction.*;version="[1.0.1, 2.0.0)";resolution:=optional, + oracle.toplink.essentials.*;version="[2.0.0.b41-beta2, 3.0.0)";resolution:=optional, + oracle.toplink.exceptions;version="[10.1.3, 11.0.0)";resolution:=optional, + oracle.toplink.expressions;version="[10.1.3, 11.0.0)";resolution:=optional, + oracle.toplink.internal.databaseaccess;version="[10.1.3, 11.0.0)";resolution:=optional, + oracle.toplink.jndi;version="[10.1.3, 11.0.0)";resolution:=optional, + oracle.toplink.logging;version="[10.1.3, 11.0.0)";resolution:=optional, + oracle.toplink.publicinterface;version="[10.1.3, 11.0.0)";resolution:=optional, + oracle.toplink.queryframework;version="[10.1.3, 11.0.0)";resolution:=optional, + oracle.toplink.sessionbroker;version="[10.1.3, 11.0.0)";resolution:=optional, + oracle.toplink.sessions;version="[10.1.3, 11.0.0)";resolution:=optional, + oracle.toplink.threetier;version="[10.1.3, 11.0.0)";resolution:=optional, + oracle.toplink.tools.*;version="[10.1.3, 11.0.0)";resolution:=optional, + org.aopalliance.*;version="[1.0.0, 2.0.0)", + org.apache.commons.logging.*;version="[1.1.1, 2.0.0)", + org.apache.openjpa.persistence.*;version="[1.0.2, 2.0.0)";resolution:=optional, + org.eclipse.persistence.*;version="[1.0.0, 2.0.0)";resolution:=optional, + org.hibernate;version="[3.2.6.ga, 3.3.0)";resolution:=optional, + org.hibernate.cache;version="[3.2.6.ga, 3.3.0)";resolution:=optional, + org.hibernate.cfg;version="[3.2.6.ga, 3.3.0)";resolution:=optional, + org.hibernate.classic;version="[3.2.6.ga, 3.3.0)";resolution:=optional, + org.hibernate.connection;version="[3.2.6.ga, 3.3.0)";resolution:=optional, + org.hibernate.context;version="[3.2.6.ga, 3.3.0)";resolution:=optional, + org.hibernate.criterion;version="[3.2.6.ga, 3.3.0)";resolution:=optional, + org.hibernate.dialect;version="[3.2.6.ga, 3.3.0)";resolution:=optional, + org.hibernate.ejb;version="[3.3.0.ga, 3.4.0)";resolution:=optional, + org.hibernate.engine;version="[3.2.6.ga, 3.3.0)";resolution:=optional, + org.hibernate.event.*;version="[3.2.6.ga, 3.3.0)";resolution:=optional, + org.hibernate.exception;version="[3.2.6.ga, 3.3.0)";resolution:=optional, + org.hibernate.impl;version="[3.2.6.ga, 3.3.0)";resolution:=optional, + org.hibernate.jdbc;version="[3.2.6.ga, 3.3.0)";resolution:=optional, + org.hibernate.persister.entity;version="[3.2.6.ga, 3.3.0)";resolution:=optional, + org.hibernate.tool.hbm2ddl;version="[3.2.6.ga, 3.3.0)";resolution:=optional, + org.hibernate.transaction;version="[3.2.6.ga, 3.3.0)";resolution:=optional, + org.hibernate.type;version="[3.2.6.ga, 3.3.0)";resolution:=optional, + org.hibernate.usertype;version="[3.2.6.ga, 3.3.0)";resolution:=optional, + org.hibernate.util;version="[3.2.6.ga, 3.3.0)";resolution:=optional, + org.springframework.aop.*;version="[2.5.5.A, 2.5.5.A]", + org.springframework.beans.*;version="[2.5.5.A, 2.5.5.A]", + org.springframework.context.*;version="[2.5.5.A, 2.5.5.A]", + org.springframework.core.*;version="[2.5.5.A, 2.5.5.A]", + org.springframework.dao.*;version="[2.5.5.A, 2.5.5.A]", + org.springframework.instrument.classloading.*;version="[2.5.5.A, 2.5.5.A]";resolution:=optional, + org.springframework.jdbc.*;version="[2.5.5.A, 2.5.5.A]", + org.springframework.jndi.*;version="[2.5.5.A, 2.5.5.A]";resolution:=optional, + org.springframework.transaction.*;version="[2.5.5.A, 2.5.5.A]", + org.springframework.util.*;version="[2.5.5.A, 2.5.5.A]", + org.springframework.web.*;version="[2.5.5.A, 2.5.5.A]";resolution:=optional, + org.springframework.ui.*;version="[2.5.5.A, 2.5.5.A]";resolution:=optional +Unversioned-Imports: + javax.naming.*, + javax.sql.*, + javax.xml.parsers.*, + org.w3c.dom.*, + org.xml.sax +Ignored-Existing-Headers: + Bnd-LastModified, + DynamicImport-Package, + Import-Package, + Export-Package, + Tool -- GitLab