/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF 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 org.apache.myfaces.extensions.cdi.javaee.jsf.impl.util;
import org.apache.myfaces.extensions.cdi.core.api.manager.BeanManagerProvider;
import static org.apache.myfaces.extensions.cdi.core.api.manager.BeanManagerProvider.getInstance;
import org.apache.myfaces.extensions.cdi.core.api.resolver.ConfigResolver;
import org.apache.myfaces.extensions.cdi.core.api.scope.conversation.ConversationGroup;
import org.apache.myfaces.extensions.cdi.core.api.scope.conversation.ViewAccessScoped;
import org.apache.myfaces.extensions.cdi.core.api.scope.conversation.WindowContext;
import org.apache.myfaces.extensions.cdi.core.api.scope.conversation.WindowScoped;
import org.apache.myfaces.extensions.cdi.core.api.tools.annotate.DefaultAnnotation;
import org.apache.myfaces.extensions.cdi.core.impl.scope.conversation.spi.WindowContextManager;
import org.apache.myfaces.extensions.cdi.core.impl.utils.CodiUtils;
import org.apache.myfaces.extensions.cdi.javaee.jsf.api.qualifier.Jsf;
import org.apache.myfaces.extensions.cdi.javaee.jsf.impl.scope.conversation.WindowContextIdHolderComponent;
import org.apache.myfaces.extensions.cdi.javaee.jsf.impl.scope.conversation.spi.JsfAwareWindowContextConfig;
import org.apache.myfaces.extensions.cdi.javaee.jsf.impl.scope.conversation.spi.WindowHandler;
import org.apache.myfaces.extensions.cdi.javaee.jsf.impl.scope.conversation.spi.EditableWindowContextManager;
import org.apache.myfaces.extensions.cdi.javaee.jsf.impl.scope.conversation.spi.EditableWindowContext;
import javax.enterprise.inject.spi.Bean;
import javax.enterprise.inject.spi.BeanManager;
import javax.faces.component.UIComponent;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Collection;
import java.util.Collections;
/**
* internal! utils
* @author Gerhard Petracek
*/
public class ConversationUtils
{
public static final String EXISTING_WINDOW_ID_SET_KEY =
WindowContext.class.getName() + ":EXISTING_WINDOW_ID_LIST";
private static final ViewAccessScoped VIEW_ACCESS_SCOPED = DefaultAnnotation.of(ViewAccessScoped.class);
private static final Jsf JSF_QUALIFIER = DefaultAnnotation.of(Jsf.class);
private static final String OLD_VIEW_ID_KEY = "oldViewId";
private static final String NEW_VIEW_ID_KEY = "newViewId";
/**
* @return the descriptor of a custom
* {@link org.apache.myfaces.extensions.cdi.core.impl.scope.conversation.spi.WindowContextManager}
* with the qualifier {@link org.apache.myfaces.extensions.cdi.javaee.jsf.api.qualifier.Jsf} or
* the descriptor of the default implementation provided by this module
*/
public static Bean<WindowContextManager> resolveConversationManagerBean()
{
BeanManager beanManager = BeanManagerProvider.getInstance().getBeanManager();
Set<?> conversationManagerBeans = beanManager.getBeans(WindowContextManager.class, JSF_QUALIFIER);
if (conversationManagerBeans.isEmpty())
{
conversationManagerBeans = getDefaultConversationManager(beanManager);
}
if (conversationManagerBeans.size() != 1)
{
throw new IllegalStateException(conversationManagerBeans.size() + " conversation-managers were found");
}
//noinspection unchecked
return (Bean<WindowContextManager>) conversationManagerBeans.iterator().next();
}
public static Class convertViewAccessScope(Bean<?> bean, Class conversationGroup, Set<Annotation> qualifiers)
{
//workaround to keep the existing api
if(ViewAccessScoped.class.isAssignableFrom(conversationGroup))
{
//TODO maybe we have to add a real qualifier instead
qualifiers.add(VIEW_ACCESS_SCOPED);
conversationGroup = bean.getBeanClass();
}
return conversationGroup;
}
public static Class getConversationGroup(Bean<?> bean)
{
Set<Class<? extends Annotation>> stereotypes = bean.getStereotypes();
if(stereotypes.contains(WindowScoped.class))
{
return WindowScoped.class;
}
if(stereotypes.contains(ViewAccessScoped.class))
{
return ViewAccessScoped.class;
}
ConversationGroup conversationGroupAnnotation = findConversationGroupAnnotation(bean);
if(conversationGroupAnnotation == null)
{
return bean.getBeanClass();
}
Class groupClass = conversationGroupAnnotation.value();
if(WindowScoped.class.isAssignableFrom(groupClass))
{
return WindowScoped.class;
}
if(ViewAccessScoped.class.isAssignableFrom(groupClass))
{
return ViewAccessScoped.class;
}
return groupClass;
}
private static ConversationGroup findConversationGroupAnnotation(Bean<?> bean)
{
Set<Annotation> qualifiers = bean.getQualifiers();
for(Annotation qualifier : qualifiers)
{
if(ConversationGroup.class.isAssignableFrom(qualifier.annotationType()))
{
return (ConversationGroup)qualifier;
}
}
return null;
}
/**
* @param beanManager current {@link javax.enterprise.inject.spi.BeanManager}
* @return the descriptor of the default
* {@link org.apache.myfaces.extensions.cdi.core.impl.scope.conversation.spi.WindowContextManager}
*/
private static Set<Bean<?>> getDefaultConversationManager(BeanManager beanManager)
{
return beanManager.getBeans(WindowContextManager.class);
}
//TODO
public static String resolveWindowContextId(WindowHandler windowHandler,
boolean requestParameterSupported,
boolean allowUnknownWindowIds)
{
FacesContext facesContext = FacesContext.getCurrentInstance();
ExternalContext externalContext = facesContext.getExternalContext();
Map<String, String> requestParameterMap = externalContext.getRequestParameterMap();
Map<String, Object> requestMap = externalContext.getRequestMap();
//try to find id in request map
String id = tryToFindWindowIdInRequestMap(requestMap);
if(id == null && windowHandler != null)
{
id = windowHandler.restoreWindowId(facesContext.getExternalContext());
}
if(id == null)
{
id = tryToRestoreWindowIdFromRequestParameterMap(requestParameterSupported, requestParameterMap);
}
if(id != null && !cacheWindowId(externalContext, id, allowUnknownWindowIds))
{
id = null;
}
if (id != null)
{
return id;
}
//try to restore id from component
WindowContextIdHolderComponent windowContextIdHolder = getWindowContextIdHolderComponent(facesContext);
if (windowContextIdHolder != null)
{
//TODO cache for request
id = windowContextIdHolder.getWindowContextId();
if(id != null && !cacheWindowId(externalContext, id, allowUnknownWindowIds))
{
id = null;
}
if(id != null)
{
requestMap.put(WindowContextManager.WINDOW_CONTEXT_ID_PARAMETER_KEY,
windowContextIdHolder.getWindowContextId());
}
}
return id;
}
private static String tryToRestoreWindowIdFromRequestParameterMap(
boolean requestParameterSupported, Map<String, String> requestParameterMap)
{
//try to restore get-request parameter
String idViaGetRequest = null;
if (requestParameterSupported)
{
idViaGetRequest = requestParameterMap.get(WindowContextManager.WINDOW_CONTEXT_ID_PARAMETER_KEY);
}
return idViaGetRequest;
}
/**
* @param externalContext externalContext
* @param id windowId
* @param allowUnknownWindowIds true to force the usage of the given id
* @return false if the id doesn't exist in the storage (e.g. in case of bookmarks)
*/
public static boolean cacheWindowId(ExternalContext externalContext, String id, boolean allowUnknownWindowIds)
{
Map<String, Object> sessionMap = externalContext.getSessionMap();
Set<String> existingWindowIdSet = (Set)sessionMap.get(EXISTING_WINDOW_ID_SET_KEY);
if(existingWindowIdSet == null)
{
existingWindowIdSet = new HashSet<String>();
sessionMap.put(EXISTING_WINDOW_ID_SET_KEY, existingWindowIdSet);
}
if(!allowUnknownWindowIds && !existingWindowIdSet.contains(id))
{
return false;
}
//TODO check if it should be replace with the RequestCache
Map<String, Object> requestMap = externalContext.getRequestMap();
requestMap.put(WindowContextManager.WINDOW_CONTEXT_ID_PARAMETER_KEY, id);
return true;
}
private static String tryToFindWindowIdInRequestMap(Map<String, Object> requestMap)
{
return (String) requestMap.get(WindowContextManager.WINDOW_CONTEXT_ID_PARAMETER_KEY);
}
public static void storeCurrentViewIdAsOldViewId(FacesContext facesContext
/*TODO add window context as parameter and test it in combination with redirects*/)
{
storeCurrentViewIdAsOldViewId(facesContext, getWindowContextManager());
}
public static void storeCurrentViewIdAsOldViewId(
FacesContext facesContext, WindowContextManager windowContextManager)
{
String oldViewId = facesContext.getViewRoot().getViewId();
windowContextManager.getCurrentWindowContext().setAttribute(OLD_VIEW_ID_KEY, oldViewId);
}
public static void storeCurrentViewIdAsNewViewId(FacesContext facesContext)
{
storeCurrentViewIdAsNewViewId(facesContext, getWindowContextManager().getCurrentWindowContext());
}
public static void storeCurrentViewIdAsNewViewId(FacesContext facesContext, WindowContext windowContext)
{
String newViewId = facesContext.getViewRoot().getViewId();
windowContext.setAttribute(NEW_VIEW_ID_KEY, newViewId);
}
public static String getOldViewId()
{
return getWindowContextManager().getCurrentWindowContext().getAttribute(OLD_VIEW_ID_KEY, String.class);
}
public static String getNewViewId()
{
return getWindowContextManager().getCurrentWindowContext().getAttribute(NEW_VIEW_ID_KEY, String.class);
}
public static WindowContextIdHolderComponent getWindowContextIdHolderComponent(FacesContext facesContext)
{
List<UIComponent> uiComponents = facesContext.getViewRoot().getChildren();
for (UIComponent uiComponent : uiComponents)
{
if (uiComponent instanceof WindowContextIdHolderComponent)
{
return ((WindowContextIdHolderComponent) uiComponent);
}
}
return null;
}
public static void addWindowContextIdHolderComponent()
{
FacesContext facesContext = FacesContext.getCurrentInstance();
List<UIComponent> uiComponents = facesContext.getViewRoot().getChildren();
for (UIComponent uiComponent : uiComponents)
{
if (uiComponent instanceof WindowContextIdHolderComponent)
{
//in this case we have the same view-root
return;
}
}
facesContext.getViewRoot().getChildren().add(createComponentWithCurrentWindowContextId());
}
private static WindowContextIdHolderComponent createComponentWithCurrentWindowContextId()
{
Bean<WindowContextManager> conversationManagerBean = resolveConversationManagerBean();
WindowContextManager conversationManager = CodiUtils.getOrCreateScopedInstanceOfBean(conversationManagerBean);
return new WindowContextIdHolderComponent(conversationManager.getCurrentWindowContext().getId());
}
public static void sendRedirect(ExternalContext externalContext,
String url,
WindowHandler windowHandler) throws IOException
{
storeCurrentViewIdAsOldViewId(FacesContext.getCurrentInstance());
RequestCache.resetCache();
if(windowHandler != null)
{
windowHandler.sendRedirect(externalContext, url, false);
}
else
{
//TODO log warning in case of project stage dev.
externalContext.redirect(url);
}
}
public static JsfAwareWindowContextConfig getJsfAwareWindowContextConfig()
{
Set<Bean<?>> configResolvers = getInstance().getBeanManager().getBeans(ConfigResolver.class);
//TODO
ConfigResolver configResolver = (ConfigResolver) CodiUtils
.getOrCreateScopedInstanceOfBean(configResolvers.iterator().next());
return configResolver.resolve(JsfAwareWindowContextConfig.class);
}
public static WindowHandler getWindowHandler()
{
return getJsfAwareWindowContextConfig().getWindowHandler();
}
public static WindowContextManager getWindowContextManager()
{
return RequestCache.getWindowContextManager();
}
public static boolean removeExistingWindowId(ExternalContext externalContext, String windowContextId)
{
return getEditableWindowIdSet(externalContext).remove(windowContextId);
}
public static Set<String> getExistingWindowIdSet(ExternalContext externalContext)
{
Set<String> existingWindowIdSet = getEditableWindowIdSet(externalContext);
return Collections.unmodifiableSet(existingWindowIdSet);
}
public static void storeCreatedWindowContextId(ExternalContext externalContext, String windowContextId)
{
getEditableWindowIdSet(externalContext).add(windowContextId);
}
private static Set<String> getEditableWindowIdSet(ExternalContext externalContext)
{
Map<String, Object> sessionMap = externalContext.getSessionMap();
@SuppressWarnings({"unchecked"})
Set<String> existingWindowIdSet = (Set)sessionMap.get(EXISTING_WINDOW_ID_SET_KEY);
if(existingWindowIdSet == null)
{
existingWindowIdSet = new HashSet<String>();
sessionMap.put(EXISTING_WINDOW_ID_SET_KEY, existingWindowIdSet);
}
return existingWindowIdSet;
}
public static boolean cleanupInactiveWindowContexts(EditableWindowContextManager windowContextManager)
{
Collection<EditableWindowContext> windowContexts = windowContextManager.getWindowContexts();
int count = windowContexts.size();
for (EditableWindowContext windowContext : windowContexts)
{
if(isEligibleForCleanup(windowContext))
{
windowContextManager.removeWindowContext(windowContext);
}
}
return windowContexts.size() < count;
}
private static boolean isEligibleForCleanup(EditableWindowContext editableWindowContext)
{
return !editableWindowContext.isActive() || editableWindowContext.getConversations().isEmpty();
}
}