/*
* 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.scope.conversation;
import org.apache.myfaces.extensions.cdi.core.api.scope.conversation.Conversation;
import org.apache.myfaces.extensions.cdi.core.api.scope.conversation.WindowContext;
import org.apache.myfaces.extensions.cdi.core.api.projectstage.ProjectStage;
import org.apache.myfaces.extensions.cdi.javaee.jsf.impl.scope.conversation.spi.EditableConversation;
import org.apache.myfaces.extensions.cdi.javaee.jsf.impl.util.ConversationUtils;
import org.apache.myfaces.extensions.cdi.javaee.jsf.impl.util.JsfUtils;
import org.apache.myfaces.extensions.cdi.javaee.jsf.impl.util.RequestCache;
import static org.apache.myfaces.extensions.cdi.javaee.jsf.impl.util.ConversationUtils.*;
import static org.apache.myfaces.extensions.cdi.javaee.jsf.impl.util.ExceptionUtils.windowContextNotEditableException;
import org.apache.myfaces.extensions.cdi.javaee.jsf.impl.scope.conversation.spi.EditableWindowContext;
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.EditableWindowContextManager;
import org.apache.myfaces.extensions.cdi.javaee.jsf.impl.scope.conversation.spi.WindowContextFactory;
import org.apache.myfaces.extensions.cdi.javaee.jsf.impl.scope.conversation.spi.WindowContextQuotaHandler;
import org.apache.myfaces.extensions.cdi.javaee.jsf.impl.scope.conversation.spi.WindowHandler;
import javax.enterprise.inject.Typed;
import javax.faces.component.UIComponent;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import java.util.Iterator;
import java.util.Map;
import java.util.Collection;
import java.util.Collections;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author Gerhard Petracek
*/
@Typed()
@SuppressWarnings({"UnusedDeclaration"})
public class DefaultWindowContextManager implements EditableWindowContextManager
{
private static final long serialVersionUID = 2872151847183166424L;
private Map<String, EditableWindowContext> windowContextMap =
new ConcurrentHashMap<String, EditableWindowContext>();
private ProjectStage projectStage;
private JsfAwareWindowContextConfig jsfAwareWindowContextConfig;
private boolean allowUnknownWindowIds = false;
private boolean urlParameterSupported = true;
private WindowHandler windowHandler;
private boolean projectStageDevelopment;
private WindowContextQuotaHandler windowContextQuotaHandler;
//TODO add config + refactor DefaultWindowContextManager
private static final int DEFAULT_WINDOW_KEY_LENGTH = 3;
protected DefaultWindowContextManager(JsfAwareWindowContextConfig jsfAwareWindowContextConfig,
ProjectStage projectStage)
{
this.jsfAwareWindowContextConfig = jsfAwareWindowContextConfig;
this.projectStage = projectStage;
init();
}
protected void init()
{
this.windowHandler = this.jsfAwareWindowContextConfig.getWindowHandler();
this.windowContextQuotaHandler = this.jsfAwareWindowContextConfig.getWindowContextQuotaHandler();
this.allowUnknownWindowIds = this.jsfAwareWindowContextConfig.isUnknownWindowIdsAllowed();
this.urlParameterSupported = this.jsfAwareWindowContextConfig.isUrlParameterSupported();
this.projectStageDevelopment = ProjectStage.Development.equals(this.projectStage);
}
public WindowContext getCurrentWindowContext()
{
WindowContext windowContext = RequestCache.getCurrentWindowContext();
if(windowContext != null)
{
return windowContext;
}
String windowContextId =
resolveWindowContextId(this.windowHandler, this.urlParameterSupported, this.allowUnknownWindowIds);
if (windowContextId == null)
{
windowContextId = createNewWindowContextId();
}
windowContext = getWindowContext(windowContextId);
RequestCache.setCurrentWindowContext(windowContext);
return windowContext;
}
private synchronized String createNewWindowContextId()
{
String windowContextId = this.windowHandler.createWindowId();
ExternalContext externalContext = FacesContext.getCurrentInstance().getExternalContext();
if(this.windowContextQuotaHandler.checkQuota(getNumberOfNextWindowContext()))
{
if(!cleanupInactiveWindowContexts(this))
{
this.windowContextQuotaHandler.handleQuotaViolation();
}
}
if(this.projectStageDevelopment &&
this.windowHandler instanceof DefaultWindowHandler /*only in this case we know all details*/)
{
//it's easier for developers to check the current window context
//after a cleanup of window contexts it isn't reliable
//however - such a cleanup shouldn't occur during development
windowContextId = convertToDevWindowContextId(windowContextId, getNumberOfNextWindowContext());
}
storeCreatedWindowContextId(externalContext, windowContextId);
cacheWindowId(externalContext, windowContextId, this.allowUnknownWindowIds);
return windowContextId;
}
private int getNumberOfNextWindowContext()
{
return this.windowContextMap.size() + 1;
}
public synchronized WindowContext getWindowContext(String windowContextId)
{
EditableWindowContext result = this.windowContextMap.get(windowContextId);
if(result != null && !result.isActive())
{
removeWindowContext(result);
result = null;
}
if (result == null)
{
result = createWindowContext(windowContextId);
this.windowContextMap.put(windowContextId, result);
}
result.touch();
return result;
}
private EditableWindowContext createWindowContext(String windowContextId)
{
WindowContextFactory windowContextFactory = this.jsfAwareWindowContextConfig.getWindowContextFactory();
if(windowContextFactory != null)
{
return windowContextFactory.createWindowContext(windowContextId, this.jsfAwareWindowContextConfig);
}
return new JsfWindowContext(windowContextId, this.jsfAwareWindowContextConfig, this.projectStageDevelopment);
}
public boolean activateWindowContext(String windowContextId)
{
return activateWindowContext(convert(getWindowContext(windowContextId)));
}
public boolean activateWindowContext(EditableWindowContext windowContext)
{
JsfUtils.resetCaches();
FacesContext facesContext = FacesContext.getCurrentInstance();
WindowContextIdHolderComponent windowContextIdHolder =
ConversationUtils.getWindowContextIdHolderComponent(facesContext);
if (windowContextIdHolder != null)
{
windowContextIdHolder.changeWindowContextId(windowContext.getId());
}
return cacheWindowId(facesContext.getExternalContext(), windowContext.getId(), this.allowUnknownWindowIds);
}
public void resetCurrentWindowContext()
{
resetWindowContext(convert(getCurrentWindowContext()));
}
public void resetWindowContext(String windowContextId)
{
resetWindowContext(convert(getWindowContext(windowContextId)));
}
public void resetWindowContext(EditableWindowContext windowContext)
{
JsfUtils.resetCaches();
for (Conversation conversation : windowContext.getConversations().values())
{
conversation.restart();
}
}
public void resetConversations()
{
resetConversations(convert(getCurrentWindowContext()));
}
public void resetConversations(String windowContextId)
{
resetConversations(convert(getWindowContext(windowContextId)));
}
public void resetConversations(EditableWindowContext windowContext)
{
JsfUtils.resetCaches();
for (EditableConversation conversation : windowContext.getConversations().values())
{
conversation.deactivate();
//it isn't possible to deactivate window scoped conversations
if (!conversation.isActive())
{
conversation.restart();
}
}
}
public void removeCurrentWindowContext()
{
removeWindowContext(convert(getCurrentWindowContext()));
}
public void removeWindowContext(String windowContextId)
{
removeWindowContext(convert(getWindowContext(windowContextId)));
}
public void removeWindowContext(EditableWindowContext windowContext)
{
JsfUtils.resetCaches();
this.windowContextMap.remove(windowContext.getId());
FacesContext facesContext = FacesContext.getCurrentInstance();
ExternalContext externalContext = facesContext.getExternalContext();
removeWindowContextIdHolderComponent(facesContext);
//reset existing information
removeExistingWindowId(externalContext, windowContext.getId());
externalContext.getRequestMap().remove(WINDOW_CONTEXT_ID_PARAMETER_KEY);
windowContext.endConversations();
}
private void removeWindowContextIdHolderComponent(FacesContext facesContext)
{
JsfUtils.resetCaches();
Iterator<UIComponent> uiComponents = facesContext.getViewRoot().getChildren().iterator();
UIComponent uiComponent;
while (uiComponents.hasNext())
{
uiComponent = uiComponents.next();
if (uiComponent instanceof WindowContextIdHolderComponent)
{
uiComponents.remove();
return;
}
}
}
private String convertToDevWindowContextId(String windowContextId, int currentWindowContextCount)
{
String devWindowContextId = currentWindowContextCount + windowContextId;
if(devWindowContextId.length() > DEFAULT_WINDOW_KEY_LENGTH + ("" + currentWindowContextCount).length())
{
return windowContextId;
}
return devWindowContextId;
}
public Collection<EditableWindowContext> getWindowContexts()
{
return Collections.unmodifiableCollection(this.windowContextMap.values());
}
public void destroy()
{
for (WindowContext windowContext : this.windowContextMap.values())
{
for (Conversation conversation :
((EditableWindowContext)windowContext).getConversations().values())
{
conversation.end();
}
((EditableWindowContext)windowContext).removeInactiveConversations();
}
}
private EditableWindowContext convert(WindowContext windowContext)
{
if(!(windowContext instanceof EditableWindowContext))
{
throw windowContextNotEditableException(windowContext);
}
return (EditableWindowContext)windowContext;
}
}