/* * Copyright 2017 OmniFaces * * 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.omnifaces.cdi.viewscope; import static java.lang.String.format; import static org.omnifaces.cdi.viewscope.ViewScopeManager.DEFAULT_MAX_ACTIVE_VIEW_SCOPES; import static org.omnifaces.cdi.viewscope.ViewScopeManager.PARAM_NAME_MAX_ACTIVE_VIEW_SCOPES; import static org.omnifaces.cdi.viewscope.ViewScopeManager.PARAM_NAME_MOJARRA_NUMBER_OF_VIEWS; import static org.omnifaces.cdi.viewscope.ViewScopeManager.PARAM_NAME_MYFACES_NUMBER_OF_VIEWS; import static org.omnifaces.util.Faces.getInitParameter; import static org.omnifaces.util.Faces.getViewAttribute; import static org.omnifaces.util.Faces.setViewAttribute; import java.io.Serializable; import java.util.UUID; import java.util.concurrent.ConcurrentMap; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import javax.enterprise.context.SessionScoped; import org.omnifaces.cdi.BeanStorage; import org.omnifaces.cdi.ViewScoped; import org.omnifaces.util.concurrentlinkedhashmap.ConcurrentLinkedHashMap; import org.omnifaces.util.concurrentlinkedhashmap.EvictionListener; /** * Stores view scoped bean instances in a LRU map in HTTP session. * * @author Bauke Scholtz * @see ViewScoped * @see ViewScopeManager * @since 2.6 */ @SessionScoped public class ViewScopeStorageInSession implements ViewScopeStorage, Serializable { // Private constants ---------------------------------------------------------------------------------------------- private static final long serialVersionUID = 42L; private static final String[] PARAM_NAMES_MAX_ACTIVE_VIEW_SCOPES = { PARAM_NAME_MAX_ACTIVE_VIEW_SCOPES, PARAM_NAME_MOJARRA_NUMBER_OF_VIEWS, PARAM_NAME_MYFACES_NUMBER_OF_VIEWS }; private static final String ERROR_MAX_ACTIVE_VIEW_SCOPES = "The '%s' init param must be a number." + " Encountered an invalid value of '%s'."; // Static variables ----------------------------------------------------------------------------------------------- private static volatile Integer maxActiveViewScopes; // Variables ------------------------------------------------------------------------------------------------------ private ConcurrentMap<UUID, BeanStorage> activeViewScopes; // Actions -------------------------------------------------------------------------------------------------------- /** * Create a new LRU map of active view scopes with maximum weighted capacity depending on several context params. * See javadoc of {@link ViewScoped} for details. */ @PostConstruct public void postConstructSession() { activeViewScopes = new ConcurrentLinkedHashMap.Builder<UUID, BeanStorage>() .maximumWeightedCapacity(getMaxActiveViewScopes()) .listener(new BeanStorageEvictionListener()) .build(); } @Override public UUID getBeanStorageId() { UUID beanStorageId = getViewAttribute(getClass().getName()); return (beanStorageId != null && activeViewScopes.containsKey(beanStorageId)) ? beanStorageId : null; } @Override public BeanStorage getBeanStorage(UUID beanStorageId) { return activeViewScopes.get(beanStorageId); } @Override public void setBeanStorage(UUID beanStorageId, BeanStorage beanStorage) { activeViewScopes.put(beanStorageId, beanStorage); setViewAttribute(getClass().getName(), beanStorageId); } /** * Destroys all beans associated with given bean storage identifier. * @param beanStorageId The bean storage identifier. */ public void destroyBeans(UUID beanStorageId) { BeanStorage storage = activeViewScopes.remove(beanStorageId); if (storage != null) { storage.destroyBeans(); } } /** * This method is invoked during session destroy, in that case destroy all beans in all active view scopes. */ @PreDestroy public void preDestroySession() { for (BeanStorage storage : activeViewScopes.values()) { storage.destroyBeans(); } } // Helpers -------------------------------------------------------------------------------------------------------- /** * Returns the max active view scopes depending on available context params. This will be calculated lazily once * and re-returned everytime; the faces context is namely not available during class' initialization/construction, * but only during a post construct. */ private static int getMaxActiveViewScopes() { if (maxActiveViewScopes != null) { return maxActiveViewScopes; } for (String name : PARAM_NAMES_MAX_ACTIVE_VIEW_SCOPES) { String value = getInitParameter(name); if (value != null) { try { maxActiveViewScopes = Integer.valueOf(value); return maxActiveViewScopes; } catch (NumberFormatException e) { throw new IllegalArgumentException(format(ERROR_MAX_ACTIVE_VIEW_SCOPES, name, value), e); } } } maxActiveViewScopes = DEFAULT_MAX_ACTIVE_VIEW_SCOPES; return maxActiveViewScopes; } // Nested classes ------------------------------------------------------------------------------------------------- /** * Listener for {@link ConcurrentLinkedHashMap} which will be invoked when an entry is evicted. It will in turn * invoke {@link BeanStorage#destroyBeans()}. */ private static final class BeanStorageEvictionListener implements EvictionListener<UUID, BeanStorage>, Serializable { private static final long serialVersionUID = 42L; @Override public void onEviction(UUID id, BeanStorage storage) { storage.destroyBeans(); } } }