001/*
002 * Copyright 2002-2019 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.web.context.support;
018
019import java.util.LinkedHashMap;
020import java.util.Map;
021
022import javax.servlet.ServletContext;
023
024import org.springframework.beans.factory.DisposableBean;
025import org.springframework.beans.factory.ObjectFactory;
026import org.springframework.beans.factory.config.Scope;
027import org.springframework.lang.Nullable;
028import org.springframework.util.Assert;
029
030/**
031 * {@link Scope} wrapper for a ServletContext, i.e. for global web application attributes.
032 *
033 * <p>This differs from traditional Spring singletons in that it exposes attributes in the
034 * ServletContext. Those attributes will get destroyed whenever the entire application
035 * shuts down, which might be earlier or later than the shutdown of the containing Spring
036 * ApplicationContext.
037 *
038 * <p>The associated destruction mechanism relies on a
039 * {@link org.springframework.web.context.ContextCleanupListener} being registered in
040 * {@code web.xml}. Note that {@link org.springframework.web.context.ContextLoaderListener}
041 * includes ContextCleanupListener's functionality.
042 *
043 * <p>This scope is registered as default scope with key
044 * {@link org.springframework.web.context.WebApplicationContext#SCOPE_APPLICATION "application"}.
045 *
046 * @author Juergen Hoeller
047 * @since 3.0
048 * @see org.springframework.web.context.ContextCleanupListener
049 */
050public class ServletContextScope implements Scope, DisposableBean {
051
052        private final ServletContext servletContext;
053
054        private final Map<String, Runnable> destructionCallbacks = new LinkedHashMap<>();
055
056
057        /**
058         * Create a new Scope wrapper for the given ServletContext.
059         * @param servletContext the ServletContext to wrap
060         */
061        public ServletContextScope(ServletContext servletContext) {
062                Assert.notNull(servletContext, "ServletContext must not be null");
063                this.servletContext = servletContext;
064        }
065
066
067        @Override
068        public Object get(String name, ObjectFactory<?> objectFactory) {
069                Object scopedObject = this.servletContext.getAttribute(name);
070                if (scopedObject == null) {
071                        scopedObject = objectFactory.getObject();
072                        this.servletContext.setAttribute(name, scopedObject);
073                }
074                return scopedObject;
075        }
076
077        @Override
078        @Nullable
079        public Object remove(String name) {
080                Object scopedObject = this.servletContext.getAttribute(name);
081                if (scopedObject != null) {
082                        synchronized (this.destructionCallbacks) {
083                                this.destructionCallbacks.remove(name);
084                        }
085                        this.servletContext.removeAttribute(name);
086                        return scopedObject;
087                }
088                else {
089                        return null;
090                }
091        }
092
093        @Override
094        public void registerDestructionCallback(String name, Runnable callback) {
095                synchronized (this.destructionCallbacks) {
096                        this.destructionCallbacks.put(name, callback);
097                }
098        }
099
100        @Override
101        @Nullable
102        public Object resolveContextualObject(String key) {
103                return null;
104        }
105
106        @Override
107        @Nullable
108        public String getConversationId() {
109                return null;
110        }
111
112
113        /**
114         * Invoke all registered destruction callbacks.
115         * To be called on ServletContext shutdown.
116         * @see org.springframework.web.context.ContextCleanupListener
117         */
118        @Override
119        public void destroy() {
120                synchronized (this.destructionCallbacks) {
121                        for (Runnable runnable : this.destructionCallbacks.values()) {
122                                runnable.run();
123                        }
124                        this.destructionCallbacks.clear();
125                }
126        }
127
128}