diff --git a/org.springframework.web/build.xml b/org.springframework.web/build.xml
new file mode 100644
index 0000000000000000000000000000000000000000..ac4c8153a52b1fe8d9b4a8aebf8efbc30a3b3669
--- /dev/null
+++ b/org.springframework.web/build.xml
@@ -0,0 +1,6 @@
+
+
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 0000000000000000000000000000000000000000..6d2d245db2509813bdd100e391884a2fb36ad884 --- /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 0000000000000000000000000000000000000000..a34b5718eabeb2ca498e6efa200a7a0904fd3ba7 --- /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 0000000000000000000000000000000000000000..4f1dc14a6b73557f136e5b099a92ea0c14745d7e --- /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 0000000000000000000000000000000000000000..517d2242d9a0f2b1a91dc8d27036480774b0c13c --- /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 0000000000000000000000000000000000000000..c491dbc1c745a5f67a486553b45cdc3942ecef2b --- /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 0000000000000000000000000000000000000000..7883ff0da62ada91e7fdb2724d2ea0fb1b812575 --- /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 0000000000000000000000000000000000000000..0608a5fe89d9aadd7f5359f75d3751ee7af8bbb6 --- /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 0000000000000000000000000000000000000000..58b5a6cb04dd784d69c0a581b9b4a3d0494e8110
--- /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 0000000000000000000000000000000000000000..751fbc9a7322fd8d31d4b78d72cfbce8ec84bc27 --- /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 0000000000000000000000000000000000000000..86d9bf98a3ec4e7ffb5078c400c6f2e3731ef9ba --- /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 0000000000000000000000000000000000000000..e3776bb29492c79bea9959f02eac7a43e688e326 --- /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 0000000000000000000000000000000000000000..a9dfb8d7723d4f6b76d5d4b2d63e0449fc759699 --- /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 0000000000000000000000000000000000000000..0f023b27be918b6478f6f92da09f099a1560e2e6 --- /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 0000000000000000000000000000000000000000..af6f42d54aee686732153581526f239a59036d24 --- /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 0000000000000000000000000000000000000000..1c8a32031d67147ceb36dbf88e45e19809dd3440
--- /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
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 0000000000000000000000000000000000000000..db048e24e35c5760550ec72bfc9b39e67192a9ac
--- /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 0000000000000000000000000000000000000000..c99028dc850e3c4901d1198788243eb78be8b16b
--- /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 0000000000000000000000000000000000000000..3c62cb6b9ab615ee118970e8db3a32c28d74da13 --- /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 0000000000000000000000000000000000000000..8910bc62aca056e4f6e082059adb157a441b5cb6 --- /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: + *
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 0000000000000000000000000000000000000000..db72c2d04f5e3c23958835b7cbdf531e58d52a9f --- /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 0000000000000000000000000000000000000000..dd18c2d164d1ea687dc577bb8b5fd826e21b6af5 --- /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 0000000000000000000000000000000000000000..3ffc013ad1f250ea73f694f2c331177deed38e69 --- /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 0000000000000000000000000000000000000000..8ba9ac9bc82eadb20c14f7929b24945307a4dbf6 --- /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 0000000000000000000000000000000000000000..34214cef06c1202b6693046e5ae155a6cedbeb84
--- /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 0000000000000000000000000000000000000000..8ef0fe0631d1112ace8975335be83899e479237f --- /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 0000000000000000000000000000000000000000..16349e292959f896787335c53f10be700abda57f
--- /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 0000000000000000000000000000000000000000..3c351552651469dedd6e22ba78aa82552409016a
--- /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 0000000000000000000000000000000000000000..a4e26be5b5dcfb65bd47cf1ef681ce9955f80d8f --- /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 0000000000000000000000000000000000000000..4755de6d17491688dae4830143583f858e090acc --- /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 0000000000000000000000000000000000000000..836d4c76a073325b6823a96640a37154c5deacd6 --- /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 0000000000000000000000000000000000000000..5d86abbb950167386aa5f7b9ebe273f167560e05 --- /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 0000000000000000000000000000000000000000..62722f8b1323875d6d06c8abbad67a11c9b497ba --- /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 0000000000000000000000000000000000000000..36b9a08b5542f16b5ed38ffe2d8ffd2a91079881 --- /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 MapUses 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 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 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 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 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 0000000000000000000000000000000000000000..ab18e3a3fe52c7dbf032e7c29c0c55149a7cd3cc
--- /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 0000000000000000000000000000000000000000..9999c0384e60c29aeaadb88a537a8e013521470b
--- /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 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 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 0000000000000000000000000000000000000000..5f61a3e629084aaaaebc599db7b2bf8379368c17
--- /dev/null
+++ b/org.springframework.web/src/main/java/org/springframework/remoting/jaxws/package.html
@@ -0,0 +1,9 @@
+
+ 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 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 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 Looks for a {@link #CONTEXT_CLASS_PARAM "contextClass"} parameter
+ * at the 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: " The default is Supplying this "parentContextKey" parameter is sufficient when relying
+ * on the default 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 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 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 This listener should be registered after
+ * {@link org.springframework.web.util.Log4jConfigListener}
+ * in This servlet should have a lower Note that this class has been deprecated for containers implementing
+ * Servlet API 2.4 or higher, in favor of {@link ContextLoaderListener}. Servlet 2.3 containers known to work with bootstrap listeners are:
+ * Servlet 2.3 containers known not to work with bootstrap listeners are:
+ * 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 0000000000000000000000000000000000000000..196e3db4346cf52d53bdb5eefa8a0b0495121806
--- /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 Invoked after population of normal bean properties but before an init
+ * callback like InitializingBean's 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 0000000000000000000000000000000000000000..d7ddcd09e9b160660848554693196c57de4d159e
--- /dev/null
+++ b/org.springframework.web/src/main/java/org/springframework/web/context/package.html
@@ -0,0 +1,8 @@
+
+ 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 0000000000000000000000000000000000000000..1b81a41f0a22f1d02244af9035fa91e3760e3501
--- /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 0000000000000000000000000000000000000000..6db824c88a55101322ec2d2cc86348c9af7540ac
--- /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
+ * Default is the request object's 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 0000000000000000000000000000000000000000..262111797047a3258c32d0fb24235f8298ddf9cd
--- /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 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 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 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 0000000000000000000000000000000000000000..34f273f4f20d72a95a6164bc01c069de5df917a5
--- /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 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 0000000000000000000000000000000000000000..4257f850a151527188c33f3c5a0225e521eb14ce
--- /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 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 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 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 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 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:
+ * 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 Note: Will only be called if this interceptor's 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 Implements the {@link WebApplicationContext} interface, but not
+ * {@link org.springframework.web.context.ConfigurableWebApplicationContext},
+ * as it is not intended for declarative setup in 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 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 0000000000000000000000000000000000000000..a50b9955fe7f8d033618b385ddd6fdefeda7e66c
--- /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 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 0000000000000000000000000000000000000000..f1c9401693932b0b74831feaf90a05d015a49239
--- /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 0000000000000000000000000000000000000000..5b8e356c8ed18aa8d0b252a843a635e8d68b9928
--- /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 0000000000000000000000000000000000000000..1649a0249e8af5d3b0b3815163ff4bd3bdcba9be
--- /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 0000000000000000000000000000000000000000..55ed97f3eeb78c6e2f2e49555b35a58a5f60e4ad
--- /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 0000000000000000000000000000000000000000..3e19b43f578306aa0cb7587444af13526693456e
--- /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 0000000000000000000000000000000000000000..46d47276eec6a35e724e8bd5184359b0ecb18f16
--- /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 0000000000000000000000000000000000000000..ec6cb6e49b4b70a8dd2b6b59d56a1ab5de8018ce
--- /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 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 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 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 0000000000000000000000000000000000000000..e0d5612e6935f76752728d59cfcde3433a0181a4
--- /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
+ * 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 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 0000000000000000000000000000000000000000..8ec9aa76fac97a1346f80a791170ee9e0afc602b
--- /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 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 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 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 0000000000000000000000000000000000000000..e03eb44196ffe94400e71982e2a546efcd6e1899
--- /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 0000000000000000000000000000000000000000..48f1d136af202a04f175896afec0bfc30d29c574
--- /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
+ * 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 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 NOTE: Only use this if you actually need to access
+ * WebApplicationContext-specific functionality. Preferably use
+ * 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 0000000000000000000000000000000000000000..6545916284dc352b090dc2638b5124775aae80b6
--- /dev/null
+++ b/org.springframework.web/src/main/java/org/springframework/web/context/support/package.html
@@ -0,0 +1,8 @@
+
+ Subclasses should override the Subclasses are passed the message to write to the log in the Prefixes and suffixes for the before and after messages can be configured
+ * using the Should be configured using an Should be configured using an If 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 0000000000000000000000000000000000000000..14cf07013440d30771b28d4a6fd73120eaeb3f95
--- /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 0000000000000000000000000000000000000000..32fc88cd39acf96782aafde8587baeb023474e1c
--- /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 0000000000000000000000000000000000000000..ff64b2c54a8552dd1ee73abda0a2db0fa2ae2473
--- /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 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 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 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 Subclasses may override this method to provide a different
+ * Default implementation fetches the bean from the application context
+ * and calls the standard 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 Only relevant in case of initialization as bean, where the
+ * standard 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 Public to resemble the 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 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 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 0000000000000000000000000000000000000000..85453393d4c97a957aeb03929363f818102c72d5
--- /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 0000000000000000000000000000000000000000..2f53f6be1b3d6f6736dd60c50330012ea665dcac
--- /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 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 The default implementation always returns 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 0000000000000000000000000000000000000000..fef384d473400c67912aba0dbdee52c01b55e295
--- /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 0000000000000000000000000000000000000000..19eb29db9a2991fe3614c0ccb0f516536f17bc01
--- /dev/null
+++ b/org.springframework.web/src/main/java/org/springframework/web/filter/package.html
@@ -0,0 +1,7 @@
+
+ Supports the standard JSF style of decoration (through a constructor argument)
+ * as well as an overloaded Implementations should invoke 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 Configure this handler proxy in your 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
+ * 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 Default implementation delegates to FacesContextUtils.
+ * @param facesContext the current JSF context
+ * @return the Spring web application context (never Configure this listener multicaster in your Note: This multicaster's 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 The default implementation delegates to FacesContextUtils.
+ * @param facesContext the current JSF context
+ * @return the Spring web application context (never Configure this resolver in your 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 The default implementation delegates to FacesContextUtils.
+ * @param facesContext the current JSF context
+ * @return the Spring web application context (never 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 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 The session mutex is guaranteed to be the same object during
+ * the entire lifetime of the session, available under the key defined
+ * by the 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 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 0000000000000000000000000000000000000000..3c8fd8d2ff24d87ccd7fb17adcd20917ca02944a
--- /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 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 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 Configure this resolver in your The default implementation delegates to FacesContextUtils.
+ * @param elContext the current JSF ELContext
+ * @return the Spring web application context (never 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 The default implementation delegates to FacesContextUtils,
+ * returning 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 0000000000000000000000000000000000000000..b63d0525a94c5c68ef9815be545dbe8bd9100992
--- /dev/null
+++ b/org.springframework.web/src/main/java/org/springframework/web/jsf/package.html
@@ -0,0 +1,11 @@
+
+ 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 0000000000000000000000000000000000000000..662e97314190b0d8898c241c886ce2af7d931af7
--- /dev/null
+++ b/org.springframework.web/src/main/java/org/springframework/web/package.html
@@ -0,0 +1,8 @@
+
+ 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 Delegates to 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
+ * 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 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 = "";
+
+ 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 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 (Hex;).
+ * 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:
+ *
+ * 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 0000000000000000000000000000000000000000..40143639a6d2b8b4acb2ee638edf093e8aaf6965
--- /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 The session mutex is guaranteed to be the same object during
+ * the entire lifetime of the session, available under the key defined
+ * by the 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 0000000000000000000000000000000000000000..4ccb0f8b1efae3d311f28db4cb61ee4ebf70a9e7
--- /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 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 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 0000000000000000000000000000000000000000..95e501f89a2665be5a2f5bac8d6dcf0ad6692bf9
--- /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 This listener should be registered before ContextLoaderListener in Note: This servlet should have a lower Note that this class has been deprecated for containers implementing
+ * Servlet API 2.4 or higher, in favor of {@link Log4jConfigListener}. 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 Supports three init parameters at the servlet context level (that is,
+ * context-param entries in web.xml):
+ *
+ * Note: 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:
+ *
+ * Alternatively, specify a unique context-param "webAppRootKey" per web application.
+ * For example, with "webAppRootKey = "demo.root":
+ *
+ * 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 0000000000000000000000000000000000000000..32f1dca4ac23aa0fee2ae76ac82c7d43c9c6cc0b
--- /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
+ * If the 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 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
+ * 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 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 As the value returned by 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 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 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 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 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 0000000000000000000000000000000000000000..971362bdbf5cac110c6d64f1e9a4fde64228b91c
--- /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 Can be used for tools that support substition with 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 Returns the session mutex attribute if available; usually,
+ * this means that the HttpSessionMutexListener needs to be defined
+ * in The session mutex is guaranteed to be the same object during
+ * the entire lifetime of the session, available under the key defined
+ * by the 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 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:
+ * 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:
+ * 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:
+ * 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 This method will try to obtain a parameter value using the
+ * following algorithm:
+ * 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 0000000000000000000000000000000000000000..5a5c49ba6e5e43c580553f97f328cb32b66e6cb7
--- /dev/null
+++ b/org.springframework.web/src/main/java/org/springframework/web/util/package.html
@@ -0,0 +1,8 @@
+
+
+The Spring Data Binding framework, an internal library used by Spring Web Flow.
+web.xml
, pointing at the target HttpRequestHandler bean
+ * through its servlet-name
which needs to match the target bean name.
+ *
+ * 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 0000000000000000000000000000000000000000..de5d67282058820b2c9920ea169db9ca16afe06f
--- /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 0000000000000000000000000000000000000000..1f056a21ab03b6b608358a6ac69c72135e441647
--- /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 0000000000000000000000000000000000000000..119703c035f47911b4adaafcb022236fef6ef32e
--- /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}.
+ *
+ * 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 0000000000000000000000000000000000000000..4f0c26f220b5fe6df04bbb11ffe2bfe1a1299e9d
--- /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}.
+ *
+ * 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.
+ *
+ * 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.
+ * 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.
+ * 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.
+ * 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.
+ * 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.
+ * 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 0000000000000000000000000000000000000000..6cd24b294afb12aac7fcc2be1893c42d59cc8b7a
--- /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 0000000000000000000000000000000000000000..d754d73ffb04f2fbc4923a606e156be1812d9eb8
--- /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}.
+ *
+ * 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 0000000000000000000000000000000000000000..9359c8849c5d3119bc3bf7437592a66236b70898
--- /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}.
+ *
+ * load-on-startup
value
+ * in web.xml
than any servlets that access the root web
+ * application context.
+ *
+ *
+ * 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.
+ *
+ *
+ *
+ * For working with any of them, ContextLoaderListener is recommended.
+ *
+ *
+ *
+ * If you happen to work with such a server, this servlet has to be used.
+ *
+ * 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 0000000000000000000000000000000000000000..16a6e709a96e1349f7f12457a0ed6c6407c618e2
--- /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.
+ * 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 0000000000000000000000000000000000000000..1a6bd9f6fe2fc9b95be3cd06910dd7aa138ed7a9
--- /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.
+ *
+ * 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 0000000000000000000000000000000000000000..69e5f6042a8f1bc1d0761014470dbb5267e35592
--- /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 0000000000000000000000000000000000000000..2bf99cbe2ad7a5a41571cf3bcd5857d6cce66bfc
--- /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.
+ * 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 0000000000000000000000000000000000000000..f3b0855e21f81c8716bf843bb2d0fff79a8e98e8
--- /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.
+ *
+ * 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.
+ * 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 0000000000000000000000000000000000000000..0943294c4eecf4bc265b9f7395943afb9d8de521
--- /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.
+ *
+ * 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.
+ * web.xml
.
+ *
+ * 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 0000000000000000000000000000000000000000..cf4506d8736f2572950a568758272fff46cc92f7
--- /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.
+ *
+ * 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 0000000000000000000000000000000000000000..51d43ff774880af4362984ee939ab0b6decb0df6
--- /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 0000000000000000000000000000000000000000..0d0c5f60aff88c99457d32b9c8182ab299f91999
--- /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.
+ *
+ * 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.
+ * 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 0000000000000000000000000000000000000000..a2529bccad887868282c5bc8f95829a0b5dd19e0
--- /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.
+ * null
if none.
+ *
+ * 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 0000000000000000000000000000000000000000..366d5cff23e594481a040712315b909c3013b4de
--- /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.
+ *
+ * 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.
+ * 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 0000000000000000000000000000000000000000..c901ee221c636b11c5e94b95cf684001d9698c56
--- /dev/null
+++ b/org.springframework.web/src/main/java/org/springframework/web/context/request/package.html
@@ -0,0 +1,8 @@
+
+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 0000000000000000000000000000000000000000..dd653aa4d86f34cafeb41861d8fca93ec6029865
--- /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 0000000000000000000000000000000000000000..846b5e6e72dd9479f2d35597ac28971233a16730
--- /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.
+ *
+ * web.xml
. Instead,
+ * it is designed for programmatic setup, for example for building nested contexts.
+ *
+ * loadBeanDefinitions
+ * method.
+ *
+ * web.xml
.
+ *
+ * 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 0000000000000000000000000000000000000000..069bad13ebdbb39c8dde2d4b2854cfc0f0a99f7f
--- /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).
+ *
+ * web.xml
context-params
+ * (or JVM system properties).
+ *
+ * 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.
+ * 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.
+ * 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 0000000000000000000000000000000000000000..6d25067a50e4ba72184162f5813fe10b3d0cdd7d
--- /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).
+ *
+ * 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 0000000000000000000000000000000000000000..a6384fc842650364c67e672337e9bb6ac6ac06d4
--- /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 0000000000000000000000000000000000000000..b8536ffdb27a04a6b4bd8cac4959678a2b657cbe
--- /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.
+ *
+ * @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.
+ *
+ * @Autowired
injection for the given target object,
+ * based on the current web application context.
+ * ServletContext
. This is e.g. useful for accessing a Spring
+ * context from within custom web views or Struts actions.
+ *
+ * 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 0000000000000000000000000000000000000000..d544335739bbfcc5fa489a076b8054f5035d21d5
--- /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.
+ * null
)
+ */
+ protected void initServletContext(ServletContext servletContext) {
+ }
+
+ /**
+ * Return the current application context as WebApplicationContext.
+ * 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 0000000000000000000000000000000000000000..6d85039b71d27f70a937dc88025395114b57cab7
--- /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.
+ *
+ * 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 0000000000000000000000000000000000000000..e801ed44856ee826ac492aaf712ebf162cd5c0ed
--- /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 Filter
s that perform logging operations before and after a
+ * request is processed.
+ *
+ * beforeRequest(HttpServletRequest, String)
+ * and afterRequest(HttpServletRequest, String)
methods to perform the actual
+ * logging around the request.
+ *
+ * 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.
+ *
+ * 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.
+ * <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.
+ * <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.
+ * 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
.
+ * 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.
+ *
+ * Filter.init
and Filter.destroy
lifecycle methods
+ * on the target bean, letting the servlet container manage the filter lifecycle.
+ *
+ * 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.
+ * 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).
+ * 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.
+ * 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 0000000000000000000000000000000000000000..d4e1fa8c06a04859fa653482ce5772f478882eb6
--- /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.
+ *
+ * initFilterBean()
method that might
+ * contain custom initialization of a subclass.
+ * 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.
+ * getServletConfig()
.
+ * 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()
.
+ * 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()
.
+ * 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.
+ * 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.
+ * true
to avoid filtering of the given request.
+ * 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 0000000000000000000000000000000000000000..ce2a31dd12b756fbbd7754aa25dd7d17a2e6bdfc
--- /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
.
+ *
+ * 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.
+ * 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.
+ * 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 0000000000000000000000000000000000000000..b984ab091f0c65a4f8c3156a0e1d3e0363180542
--- /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.
+ *
+ * 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).
+ *
+ * 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.
+ * 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.
+ * 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 0000000000000000000000000000000000000000..36a6e704f11fc2dc75c178da17eaccfa4c749c1a
--- /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.
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * 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.
+ * 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 0000000000000000000000000000000000000000..c2dc1acf7819f0ba3e1460de1b2c98562020890a
--- /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).
+ *
+ * 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.
+ * 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.
+ * 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 0000000000000000000000000000000000000000..e28efaeb57828c27ab651699ae720af9c2b1ca7a
--- /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.
+ *
+ * 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.
+ * web.xml
. Falls back to the Session reference itself
+ * if no mutex attribute found.
+ * SESSION_MUTEX_ATTRIBUTE
constant. It serves as a
+ * safe reference to synchronize on for locking on the current session.
+ * 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 0000000000000000000000000000000000000000..c51a38e485fb36a10b211873d4800e96d188e0a3
--- /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.
+ *
+ * VariableResolver
that exposes the Spring
+ * WebApplicationContext
instance under a variable named
+ * "webApplicationContext".
+ *
+ * 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.
+ * 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 0000000000000000000000000000000000000000..2a7a4de2628093b590fbbdd335c19978825a94de
--- /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.
+ *
+ * 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.
+ * 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 0000000000000000000000000000000000000000..018448705b8c69199e4af4ca6e1f259ba9e21ace
--- /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".
+ *
+ * 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 Iteratornull
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 0000000000000000000000000000000000000000..c5cca972c165214dfddf9ed6821ee99f6ec27abd
--- /dev/null
+++ b/org.springframework.web/src/main/java/org/springframework/web/jsf/el/package.html
@@ -0,0 +1,11 @@
+
+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.
+ * 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 0000000000000000000000000000000000000000..1f01f158492c3178c3d4a30955fd69d8ed210103
--- /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.
+ *
+ * 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.
+ *
+ * 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 0000000000000000000000000000000000000000..5f1e4787354fe9d5af8b663202b41336c9347777
--- /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 0000000000000000000000000000000000000000..5009a69d34921bff2f388f793521e97d70dceda1
--- /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.
+ *
+ * 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 0000000000000000000000000000000000000000..f1f00c7517895a18d517af612f9a30c92acfb166
--- /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 0000000000000000000000000000000000000000..4a79aab9162b7d93c01b114631d92806f716663c
--- /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.
+ *
+ * <
).
+ *
+ * &#Entity; - (Example: &) case sensitive
+ * &#Decimal; - (Example: D)
+ * Gracefully handles malformed character references by copying original
+ * characters as is when encountered.
+ * &#xHex; - (Example: å) case insensitive
+ * web.xml
.
+ *
+ * SESSION_MUTEX_ATTRIBUTE
constant. It serves as a
+ * safe reference to synchronize on for locking on the current session.
+ *
+ * web.xml
to
+ * guarantee proper release of the web application class loader and its loaded classes.
+ *
+ * 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 0000000000000000000000000000000000000000..9b82db04056e8dd8e9dcd2d863e81cc9cc226e97
--- /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.
+ *
+ * java.util.logging
(which is global too).
+ *
+ * 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 0000000000000000000000000000000000000000..841fb9e6e5ad85afee65568aecb15a6c4136ecc2
--- /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).
+ *
+ * load-on-startup
value
+ * in web.xml
than ContextLoaderServlet, when using custom log4j
+ * initialization.
+ *
+ *
+ * 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 0000000000000000000000000000000000000000..ac37e6c0996eeefed2ec4bf429fd6f8bee0a29d3
--- /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).
+ *
+ * java.util.logging
(which is global too).
+ *
+ *
+ *
+ *
+ *
+ * Location of the log4j config file; either a "classpath:" location (e.g.
+ * "classpath:myLog4j.properties"), an absolute file URL (e.g. "file:C:/log4j.properties),
+ * or a plain path relative to the web application root directory (e.g.
+ * "/WEB-INF/log4j.properties"). If not specified, default log4j initialization
+ * will apply ("log4j.properties" or "log4j.xml" in the class path; see the
+ * log4j documentation for details).
+ *
+ * Interval between config file refresh checks, in milliseconds. If not specified,
+ * no refresh checks will happen, which avoids starting log4j's watchdog thread.
+ *
+ * Whether the web app root system property should be exposed, allowing for log
+ * file paths relative to the web application root directory. Default is "true";
+ * specify "false" to suppress expose of the web app root system property. See
+ * below for details on how to use this system property in log file locations.
+ * initLogging
should be called before any other Spring activity
+ * (when using log4j), for proper initialization before any Spring logging attempts.
+ *
+ * log4j.appender.myfile.File=${webapp.root}/WEB-INF/demo.log
+ *
+ * log4j.appender.myfile.File=${demo.root}/WEB-INF/demo.log
+ *
+ * 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 0000000000000000000000000000000000000000..8c000e83f0b9b1e1d00a4c210b7b396f18f9e643
--- /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 page
will be transformed to
+ * {@link javax.servlet.jsp.PageContext#PAGE_SCOPE PageContext.PAGE_SCOPE}
+ * request
will be transformed to
+ * {@link javax.servlet.jsp.PageContext#REQUEST_SCOPE PageContext.REQUEST_SCOPE}
+ * session
will be transformed to
+ * {@link javax.servlet.jsp.PageContext#SESSION_SCOPE PageContext.SESSION_SCOPE}
+ * application
will be transformed to
+ * {@link javax.servlet.jsp.PageContext#APPLICATION_SCOPE PageContext.APPLICATION_SCOPE}
+ * String
.
+ * 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 0000000000000000000000000000000000000000..59a3917919d3df2742e1f0098f5890ca8f70bc61
--- /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.
+ *
+ * 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.
+ * 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.
+ * request.getRequestURI()
is not
+ * decoded by the servlet container, this method will decode it.
+ * 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.
+ * 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.
+ * request.getContextPath()
is not
+ * decoded by the servlet container, this method will decode it.
+ * 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.
+ * 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 0000000000000000000000000000000000000000..a352f8e9cd51c28fad9bd74bd6b99db5c296df7a
--- /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".
+ *
+ * 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.
+ *
+ * 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".
+ * 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).
+ * 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.
+ * web.xml
. Falls back to the HttpSession itself
+ * if no mutex attribute found.
+ * SESSION_MUTEX_ATTRIBUTE
constant. It serves as a
+ * safe reference to synchronize on for locking on the current session.
+ * 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.
+ * 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
.
+ * 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
.
+ * 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.
+ * 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.
+ *
+ *
+ * @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.
+ *