All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.vaadin.spring.internal.UIScopeImpl Maven / Gradle / Ivy

There is a newer version: 24.7.0
Show newest version
/*
 * Copyright 2015 The original 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 com.vaadin.spring.internal;

import java.io.Serializable;
import java.util.HashSet;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.Scope;
import org.springframework.util.Assert;

import com.vaadin.server.ClientConnector;
import com.vaadin.server.ServiceDestroyEvent;
import com.vaadin.server.ServiceDestroyListener;
import com.vaadin.server.SessionDestroyEvent;
import com.vaadin.server.SessionDestroyListener;
import com.vaadin.server.VaadinSession;
import com.vaadin.ui.UI;
import com.vaadin.util.CurrentInstance;

/**
 * Implementation of Spring's
 * {@link org.springframework.beans.factory.config.Scope} that binds the UIs and
 * dependent beans to the current {@link com.vaadin.server.VaadinSession} (as
 * opposed to the current Servlet session). Registered by default as the scope "
 * {@value #VAADIN_UI_SCOPE_NAME}".
 *
 * @author Petter Holmström ([email protected])
 * @see com.vaadin.spring.annotation.UIScope
 */
public class UIScopeImpl implements Scope, BeanFactoryPostProcessor {

    public static final String VAADIN_UI_SCOPE_NAME = "vaadin-ui";
    private static final Logger LOGGER = LoggerFactory
            .getLogger(UIScopeImpl.class);

    private static BeanStoreRetrievalStrategy beanStoreRetrievalStrategy = new VaadinSessionBeanStoreRetrievalStrategy();

    /**
     * Sets the {@link BeanStoreRetrievalStrategy} to use.
     */
    public static synchronized void setBeanStoreRetrievalStrategy(
            BeanStoreRetrievalStrategy beanStoreRetrievalStrategy) {
        if (beanStoreRetrievalStrategy == null) {
            beanStoreRetrievalStrategy = new VaadinSessionBeanStoreRetrievalStrategy();
        }
        UIScopeImpl.beanStoreRetrievalStrategy = beanStoreRetrievalStrategy;
    }

    /**
     * Returns the {@link BeanStoreRetrievalStrategy} to use. By default,
     * {@link com.vaadin.spring.internal.UIScopeImpl.VaadinSessionBeanStoreRetrievalStrategy}
     * is used.
     */
    public static synchronized BeanStoreRetrievalStrategy getBeanStoreRetrievalStrategy() {
        return beanStoreRetrievalStrategy;
    }

    @Override
    public Object get(String s, ObjectFactory objectFactory) {
        return getBeanStore().get(s, objectFactory);
    }

    @Override
    public Object remove(String s) {
        return getBeanStore().remove(s);
    }

    @Override
    public void registerDestructionCallback(String s, Runnable runnable) {
        getBeanStore().registerDestructionCallback(s, runnable);
    }

    @Override
    public Object resolveContextualObject(String s) {
        return null;
    }

    @Override
    public String getConversationId() {
        return getBeanStoreRetrievalStrategy().getConversationId();
    }

    private BeanStore getBeanStore() {
        return getBeanStoreRetrievalStrategy().getBeanStore();
    }

    @Override
    public void postProcessBeanFactory(
            ConfigurableListableBeanFactory configurableListableBeanFactory)
            throws BeansException {
        LOGGER.debug("Registering Vaadin UI scope with bean factory [{}]",
                configurableListableBeanFactory);
        configurableListableBeanFactory.registerScope(VAADIN_UI_SCOPE_NAME,
                this);
    }

    /**
     * Implementation of {@link BeanStoreRetrievalStrategy} that stores the
     * {@link BeanStore} in the current {@link com.vaadin.server.VaadinSession}.
     */
    public static class VaadinSessionBeanStoreRetrievalStrategy implements
            BeanStoreRetrievalStrategy {

        private VaadinSession getVaadinSession() {
            VaadinSession current = VaadinSession.getCurrent();
            if (current == null) {
                throw new IllegalStateException(
                        "No VaadinSession bound to current thread");
            }
            if (current.getState() != VaadinSession.State.OPEN) {
                throw new IllegalStateException(
                        "Current VaadinSession is not open");
            }
            return current;
        }

        private UIStore getUIStore() {
            final VaadinSession session = getVaadinSession();
            session.lock();
            try {
                UIStore uiStore = session.getAttribute(UIStore.class);
                if (uiStore == null) {
                    uiStore = new UIStore(session);
                }
                return uiStore;
            } finally {
                session.unlock();
            }
        }

        private UIID getUIID() {
            final UI currentUI = UI.getCurrent();
            if (currentUI != null && currentUI.getUIId() != -1) {
                return new UIID(currentUI);
            } else {
                UIID currentIdentifier = CurrentInstance.get(UIID.class);
                Assert.notNull(
                        currentIdentifier,
                        String.format("Found no valid %s instance!",
                                UIID.class.getName()));
                return currentIdentifier;
            }
        }

        @Override
        public BeanStore getBeanStore() {
            return getUIStore().getBeanStore(getUIID());
        }

        @Override
        public String getConversationId() {
            return getVaadinSession().getSession().getId() + getUIID();
        }
    }

    static class UIStore implements SessionDestroyListener,
            ServiceDestroyListener, Serializable {

        private static final long serialVersionUID = -2964924681534104416L;

        private static final Logger LOGGER = LoggerFactory
                .getLogger(UIStore.class);

        private final Map beanStoreMap = new ConcurrentHashMap();
        private final VaadinSession session;
        private final String sessionId;

        // for testing only
        UIStore() {
            // just to keep the compiler happy when the UIStore is mocked
            sessionId = null;
            session = null;
        }

        UIStore(VaadinSession session) {
            sessionId = session.getSession().getId();
            this.session = session;
            this.session.getService().addSessionDestroyListener(this);
            this.session.getService().addServiceDestroyListener(this);
            this.session.setAttribute(UIStore.class, this);
        }

        BeanStore getBeanStore(final UIID uiid) {
            BeanStore beanStore = beanStoreMap.get(uiid);
            if (beanStore == null) {
                beanStore = new UIBeanStore(session, uiid,
                        new BeanStore.DestructionCallback() {
                            private static final long serialVersionUID = 2829542503960939412L;

                            @Override
                            public void beanStoreDestroyed(BeanStore beanStore) {
                                removeBeanStore(uiid);
                            }

                        });
                LOGGER.trace("Added [{}] to [{}]", beanStore, this);
                beanStoreMap.put(uiid, beanStore);
            }
            return beanStore;
        }

        void removeBeanStore(UIID uiid) {
            BeanStore removed = beanStoreMap.remove(uiid);
            if (removed != null) {
                LOGGER.trace("Removed [{}] from [{}]", removed, this);
            }
        }

        void destroy() {
            LOGGER.trace("Destroying [{}]", this);
            session.setAttribute(BeanStore.class, null);
            session.getService().removeSessionDestroyListener(this);
            session.getService().removeServiceDestroyListener(this);
            for (BeanStore beanStore : new HashSet(
                    beanStoreMap.values())) {
                beanStore.destroy();
            }
            Assert.isTrue(beanStoreMap.isEmpty(),
                    "beanStore should have been emptied by the destruction callbacks");
        }

        @Override
        public void serviceDestroy(ServiceDestroyEvent event) {
            LOGGER.debug("Vaadin service has been destroyed, destroying [{}]",
                    this);
            destroy();
        }

        @Override
        public void sessionDestroy(SessionDestroyEvent event) {
            if (event.getSession().equals(session)) {
                LOGGER.debug(
                        "Vaadin session has been destroyed, destroying [{}]",
                        this);
                destroy();
            }
        }

        @Override
        public String toString() {
            return String.format("%s[id=%x, sessionId=%s]", getClass()
                    .getSimpleName(), System.identityHashCode(this), sessionId);
        }
    }

    static class UIBeanStore extends SessionLockingBeanStore implements
            ClientConnector.DetachListener {

        private static final long serialVersionUID = 8775528615443492292L;

        UIBeanStore(VaadinSession session, UIID uuid,
                DestructionCallback destructionCallback) {
            super(session, uuid.toString(), destructionCallback);
        }

        @Override
        protected Object create(String s, ObjectFactory objectFactory) {
            Object bean = super.create(s, objectFactory);
            if (bean instanceof UI) {
                ((UI) bean).addDetachListener(this);
            }
            return bean;
        }

        @Override
        public void detach(ClientConnector.DetachEvent event) {
            LOGGER.debug("UI [{}] has been detached, destroying [{}]",
                    event.getSource(), this);
            destroy();
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy