[java-shib-shared] branch main updated: IDP-2015 - Review web.xml for improvements via code or annotations

Scott Cantor cantor.2 at osu.edu
Tue Oct 4 17:24:11 UTC 2022


This is an automated email from the git hooks/post-receive script.

scantor pushed a commit to branch main
in repository java-shib-shared.

View the commit online:
http://git.shibboleth.net/view/?p=java-shib-shared.git;a=commit;h=f1c55dc5aacc7d68fd651591b6b28c06e41d401d

The following commit(s) were added to refs/heads/main by this push:
     new f1c55dc5 IDP-2015 - Review web.xml for improvements via code or annotations
f1c55dc5 is described below

commit f1c55dc5aacc7d68fd651591b6b28c06e41d401d
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Tue Oct 4 13:24:08 2022 -0400

    IDP-2015 - Review web.xml for improvements via code or annotations
    
    https://shibboleth.atlassian.net/browse/IDP-2015
    
    Add a delegating servlet proxy, modeled on Spring's filter proxy.
---
 .../servlet/impl/DelegatingServletProxy.java       | 340 +++++++++++++++++++++
 1 file changed, 340 insertions(+)

diff --git a/shib-networking-spring/src/main/java/net/shibboleth/shared/spring/servlet/impl/DelegatingServletProxy.java b/shib-networking-spring/src/main/java/net/shibboleth/shared/spring/servlet/impl/DelegatingServletProxy.java
new file mode 100644
index 00000000..ef8c664c
--- /dev/null
+++ b/shib-networking-spring/src/main/java/net/shibboleth/shared/spring/servlet/impl/DelegatingServletProxy.java
@@ -0,0 +1,340 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.shared.spring.servlet.impl;
+
+import java.io.IOException;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import jakarta.servlet.GenericServlet;
+import jakarta.servlet.Servlet;
+import jakarta.servlet.ServletContext;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.ServletRequest;
+import jakarta.servlet.ServletResponse;
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.StringSupport;
+
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.web.context.WebApplicationContext;
+import org.springframework.web.context.support.WebApplicationContextUtils;
+import org.springframework.web.filter.DelegatingFilterProxy;
+
+/**
+ * Proxy for a standard Servlet, delegating to a Spring-managed bean that
+ * implements the Servlet interface. Supports a "targetBeanName" init-param
+ * in {@code web.xml}, specifying the name of the target bean in the Spring
+ * application context.
+ *
+ * <p>This class was originally inspired by Spring's {@link DelegatingFilterProxy}
+ * class and has the same general behavior.</p>
+ */
+public class DelegatingServletProxy extends GenericServlet {
+
+	private static final long serialVersionUID = 5470308159110258401L;
+
+    @Nullable @NotEmpty private String contextAttribute;
+
+	@Nullable private WebApplicationContext webApplicationContext;
+
+	@Nullable @NotEmpty private String targetBeanName;
+
+	private boolean targetFilterLifecycle;
+
+	@Nullable private volatile Servlet delegate;
+
+	@Nonnull private Object delegateMonitor;
+
+
+	/**
+	 * Constructor for traditional use in {@code web.xml}.
+	 */
+	public DelegatingServletProxy() {
+	    delegateMonitor = new Object();
+	}
+
+	/**
+	 * Constructor using the given {@link Servlet} delegate.
+	 * 
+	 * <p>Bypasses entirely the need for interacting with a Spring application context,
+	 * specifying the {@linkplain #setTargetBeanName target bean name}, etc.</p>
+	 * 
+	 * <p>For use with instance-based registration of filters.</p>
+	 * 
+	 * @param del the {@code HttpServlet} instance that this proxy will delegate to and
+	 * manage the lifecycle for
+	 */
+	public DelegatingServletProxy(@Nonnull final Servlet del) {
+	    delegateMonitor = new Object();
+		delegate = Constraint.isNotNull(del, "Delegate servlet cannot be null");
+	}
+
+	/**
+	 * Constructor that will retrieve the named target
+	 * bean from the Spring {@code WebApplicationContext} found in the {@code ServletContext}
+	 * (either the 'root' application context or the context named by
+	 * {@link #setContextAttribute}).
+	 * 
+	 * <p>For use with instance-based registration of filters.</p>
+	 * 
+	 * <p>The target bean must implement the standard {@link Servlet} interface.</p>
+	 * 
+	 * @param targetBeanName name of the target servlet bean to look up in the Spring
+	 * application context
+	 */
+	public DelegatingServletProxy(@Nonnull @NotEmpty final String targetBeanName) {
+		this(targetBeanName, null);
+	}
+
+	/**
+	 * Constructor that will retrieve the named target bean from the given Spring {@code WebApplicationContext}.
+	 * 
+	 * <p>For use with instance-based registration of filters.</p>
+	 * 
+	 * <p>The target bean must implement the standard {@link Servlet} interface.</p>
+	 * 
+	 * <p>The given {@code WebApplicationContext} may or may not be refreshed when passed
+	 * in. If it has not, and if the context implements {@link ConfigurableApplicationContext},
+	 * a {@link ConfigurableApplicationContext#refresh() refresh()} will be attempted before
+	 * retrieving the named target bean.</p>
+	 * 
+	 * @param targetBeanName name of the target filter bean in the Spring application context
+	 * @param wac the application context from which the target filter will be retrieved;
+	 *     if {@code null}, an application context will be looked up from {@code ServletContext}
+	 *     as a fallback
+	 */
+	public DelegatingServletProxy(@Nonnull @NotEmpty final String targetBeanName, @Nullable final WebApplicationContext wac) {
+	    delegateMonitor = new Object();
+	    
+		setTargetBeanName(targetBeanName);
+		webApplicationContext = wac;
+	}
+
+	/**
+	 * Set the name of the ServletContext attribute which should be used to retrieve the
+	 * {@link WebApplicationContext} from which to load the delegate {@link Servlet} bean.
+	 * 
+	 * @param name of attribute
+	 */
+	public void setContextAttribute(@Nullable @NotEmpty String name) {
+		contextAttribute = StringSupport.trimOrNull(name);
+	}
+
+	/**
+	 * Return the name of the ServletContext attribute which should be used to retrieve the
+	 * {@link WebApplicationContext} from which to load the delegate {@link Servlet} bean.
+	 * 
+	 * @return name of attribute
+	 */
+	@Nullable @NotEmpty public String getContextAttribute() {
+		return contextAttribute;
+	}
+
+	/**
+	 * Set the name of the target bean in the Spring application context.
+	 * 
+	 * <p>The target bean must implement the standard {@link Servlet} interface.</p>
+	 * 
+	 * <p>By default, the {@code servlet-name} as specified for the
+	 * DelegatingFilterProxy in {@code web.xml} will be used.</p>
+	 * 
+	 * @param name bean name
+	 */
+	public void setTargetBeanName(@Nullable @NotEmpty String name) {
+		targetBeanName = StringSupport.trimOrNull(name);
+	}
+
+	/**
+	 * Return the name of the target bean in the Spring application context.
+	 * 
+	 * @return bean name
+	 */
+	@Nullable @NotEmpty protected String getTargetBeanName() {
+		return targetBeanName;
+	}
+
+	/**
+	 * Set whether to invoke the {@link Servlet#init(jakarta.servlet.ServletConfig)} and
+	 * {@link Servlet#destroy()} lifecycle methods on the target bean.
+	 * 
+	 * <p>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
+	 * bean, with this proxy delegating the corresponding calls.</p>
+	 * 
+	 * @param flag flag to set
+	 */
+	public void setTargetFilterLifecycle(final boolean flag) {
+		targetFilterLifecycle = flag;
+	}
+
+	/**
+	 * Return whether to invoke the Servlet lifecycle methods on the target bean.
+	 * 
+	 * @return whether to invoke the Servlet lifecycle methods on the target bean
+	 */
+	protected boolean isTargetFilterLifecycle() {
+		return targetFilterLifecycle;
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public void init() throws ServletException {
+		synchronized (delegateMonitor) {
+			if (delegate == null) {
+				// If no target bean name specified, use servlet name.
+				if (targetBeanName == null) {
+					targetBeanName = getServletName();
+				}
+				
+				// 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.
+				final WebApplicationContext wac = findWebApplicationContext();
+				if (wac != null) {
+					delegate = initDelegate(wac);
+				}
+			}
+		}
+	}
+
+    /** {@inheritDoc} */
+	@Override
+	public void service(ServletRequest request, ServletResponse response)
+			throws ServletException, IOException {
+
+		// Lazily initialize the delegate if necessary.
+		Servlet delegateToUse = delegate;
+		if (delegateToUse == null) {
+			synchronized (delegateMonitor) {
+				delegateToUse = delegate;
+				if (delegateToUse == null) {
+					final WebApplicationContext wac = findWebApplicationContext();
+					if (wac == null) {
+						throw new IllegalStateException("No WebApplicationContext found: " +
+								"no ContextLoaderListener or DispatcherServlet registered?");
+					}
+					delegateToUse = initDelegate(wac);
+				}
+				delegate = delegateToUse;
+			}
+		}
+
+		// Let the delegate perform the actual service operation.
+		invokeDelegate(delegateToUse, request, response);
+	}
+
+    /** {@inheritDoc} */
+	@Override
+	public void destroy() {
+		final Servlet delegateToUse = delegate;
+		if (delegateToUse != null) {
+			destroyDelegate(delegateToUse);
+		}
+	}
+
+
+	/**
+	 * Return the {@link WebApplicationContext} passed in at construction time, if available.
+	 * 
+	 * <p>Otherwise, attempt to retrieve a {@link WebApplicationContext} from the
+	 * {@link ServletContext} attribute with the {@linkplain #setContextAttribute
+	 * configured name} if set. Otherwise look up a {@link WebApplicationContext} under
+	 * the well-known "root" application context attribute.</p>
+	 * 
+	 * <p>The {@link WebApplicationContext} must have already been loaded and stored in the
+	 * {@link ServletContext} before this filter gets initialized (or invoked).</p>
+	 * 
+	 * <p>Subclasses may override this method to provide a different
+	 * {@link WebApplicationContext} retrieval strategy.</p>
+	 * 
+	 * @return the {@link WebApplicationContext} for this proxy, or {@code null} if not found
+	 */
+	@Nullable protected WebApplicationContext findWebApplicationContext() {
+		if (webApplicationContext != null) {
+			// The user has injected a context at construction time -> use it...
+			if (webApplicationContext instanceof ConfigurableApplicationContext cac && !cac.isActive()) {
+				// The context has not yet been refreshed -> do so before returning it...
+				cac.refresh();
+			}
+			return webApplicationContext;
+		}
+		final String attrName = getContextAttribute();
+		if (attrName != null) {
+			return WebApplicationContextUtils.getWebApplicationContext(getServletContext(), attrName);
+		}
+		else {
+			return WebApplicationContextUtils.findWebApplicationContext(getServletContext());
+		}
+	}
+
+	/**
+	 * Initialize the Servlet delegate, defined as a bean in the given Spring application context.
+	 * 
+	 * <p>The default implementation fetches the bean from the application context
+	 * and calls the standard {@code Servlet.init} method on it, passing
+	 * in the ServletConfig of this Servlet proxy.</p>
+	 * 
+	 * @param wac the root application context
+	 * 
+	 * @return the initialized delegate Filter
+	 * 
+	 * @throws ServletException if thrown by the servlet or if no bean name can be found
+	 */
+	protected Servlet initDelegate(@Nonnull final WebApplicationContext wac) throws ServletException {
+		final String targetBeanName = getTargetBeanName();
+		if (targetBeanName == null) {
+		    throw new ServletException("No target bean name set.");
+		}
+		final Servlet delegate = wac.getBean(targetBeanName, Servlet.class);
+		if (isTargetFilterLifecycle()) {
+			delegate.init(getServletConfig());
+		}
+		return delegate;
+	}
+
+	/**
+	 * Actually invoke the delegate Servlet with the given request and response.
+	 * 
+	 * @param delegate the delegate Servlet
+	 * @param request the current request
+	 * @param response the current response
+	 * 
+	 * @throws ServletException if thrown by the Servlet
+	 * @throws IOException if thrown by the Servlet
+	 */
+	protected void invokeDelegate(@Nonnull final Servlet delegate, @Nonnull final ServletRequest request,
+	        @Nonnull final ServletResponse response) throws ServletException, IOException {
+
+		delegate.service(request, response);
+	}
+
+	/**
+	 * Destroy the Servlet delegate.
+	 * 
+	 * @param delegate the Servlet delegate
+	 */
+	protected void destroyDelegate(@Nonnull final Servlet delegate) {
+		if (isTargetFilterLifecycle()) {
+			delegate.destroy();
+		}
+	}
+
+}
\ No newline at end of file

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.


More information about the commits mailing list