[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