/*******************************************************************************
* Copyright 2015 xWic group (http://www.xwic.de)
*
* 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 de.jwic.base;
import java.io.Serializable;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Stack;
import java.util.StringTokenizer;
import java.util.TimeZone;
import org.apache.commons.lang.StringEscapeUtils;
import de.jwic.events.SessionEvent;
import de.jwic.events.SessionListener;
/**
* Holds the configuration data for a specific application session. The lifecycle of
* the SessionContext is managed by the JWicRuntime.
*
* @author Florian Lippisch
*/
public class SessionContext implements IControlContainer, Serializable {
private static final long serialVersionUID = 6009335074727417445L;
final static int AFTER_DESERIALIZATION = 0;
final static int BEFORE_SERIALIZATION = 1;
final static int SESSION_REUSED = 2;
final static int SESSION_STARTED = 3;
final static int SESSION_STOPPED = 4;
public final static int STATE_NORMAL = 0;
public final static int STATE_DESTROYED = 1;
public final static int STATE_STORED = 2;
public final static String PROP_AUTHENTICATION = "authentication";
private Map<String, Control> controls = new HashMap<String, Control>();
private Map<String, Control> layer = new HashMap<String, Control>();
private String strRedirectToURL = null;
private boolean bolDoExit = false;
private String strCallBackURL = null;
private String strExitURL = "";
private String strTopControlID = "";
private Stack<Control> stkTopControls = new Stack<Control>();
private IApplication application = null;
private IApplicationSetup appSetup = null;
private Locale locale = null;
private TimeZone timeZone;
private String dateFormat = null;
private String timeFormat = null;
private DecimalFormat decimalFormat;
private String clientId = null;
private String sessionId = null;
private String themeName = "default";
private Map<String, String[]> initParameter = null;
private List<SessionListener> listeners = null;
private long requestTicket = 0;
private Map<String, Long> layerTickets = new HashMap<String, Long>();
private boolean requireRedraw = true;
private int state = STATE_NORMAL;
private UserAgentInfo userAgent = null;
private MouseEvent mouseEvent = null;
private List<String> scriptQueue = new ArrayList<String>();
private transient IActionController actionController = new DefaultActionController();
/**
* Creates a blank new SessionContext. The contstructor is just visible to
* the package.
* @param timeZone
*/
protected SessionContext(IApplicationSetup appSetup, Locale locale, TimeZone timeZone) {
this.appSetup = appSetup;
this.locale = locale;
this.timeZone = timeZone;
if (locale == null) {
throw new NullPointerException("Locale must not be null.");
}
if (timeZone == null) {
throw new NullPointerException("TimeZone must not be null");
}
if (appSetup == null) {
throw new NullPointerException("ApplicationSetup must not be null");
}
decimalFormat = (DecimalFormat) DecimalFormat.getInstance(locale);
}
/**
* Adds a SessionListener to this SessionContext.
* @param listener
*/
public void addSessionListener(SessionListener listener) {
if (listeners == null) {
listeners = new ArrayList<SessionListener>();
}
listeners.add(listener);
}
/**
* Adds a SessionListener to this SessionContext.
* @param listener
*/
public void removeSessionListener(SessionListener listener) {
if (listeners != null) {
listeners.remove(listener);
}
}
/**
* Fire the specified event/method.
* @param event
* @param method
*/
void fireEvent(SessionEvent event, int method) {
if (method == AFTER_DESERIALIZATION) {
// recreate transient objects.
actionController = new DefaultActionController();
}
event.setSessionContext(this);
if (listeners != null) {
for (Iterator<SessionListener> it = listeners.iterator(); it.hasNext(); ) {
SessionListener listener = it.next();
switch (method) {
case AFTER_DESERIALIZATION:
listener.afterDeserialization(event);
break;
case BEFORE_SERIALIZATION:
listener.beforeSerialization(event);
break;
case SESSION_REUSED:
listener.sessionReused(event);
break;
case SESSION_STARTED :
listener.sessionStarted(event);
break;
case SESSION_STOPPED :
listener.sessionStopped(event);
break;
}
}
}
}
/* (non-Javadoc)
* @see de.jwic.base.IControlContainer#adopt(de.jwic.base.Control)
*/
public void adopt(Control control, String name) {
// notify the old container that the control has been moved (remove it)
IControlContainer oldParent = control.getContainer();
if (oldParent == this) {
return; // nothing to do
}
oldParent.unregisterControl(control);
registerControl(control, name);
}
/**
* Add a new FrameControl to this context. The name can not contain '.', ' ' or '_' characters.
*/
public void registerControl(Control control, String name) {
if (control.getContainer() != null) {
throw new JWicException("The control has already been registerd to a container.");
}
if (name == null) {
int idx = controls.size();
name = "c" + idx;
while (controls.containsKey(name)) {
idx++;
name = "c" + idx;
}
}
control.setSessionContext(this);
control.setContainer(this);
control.setName(name);
control.setControlID(control.getName());
controls.put(control.getName(), control);
}
/* (non-Javadoc)
* @see de.jwic.base.IControlContainer#unregisterControl(de.jwic.base.Control)
*/
public void unregisterControl(Control control) {
if (control.getContainer() == this) {
controls.remove(control.getName());
}
}
/**
* Add a link to a control. A link can be used to render a control
* in a seperate frame.
*
* @param linkID
* @param control
*/
public void addLayer(String layerID, Control control) {
layer.put(layerID, control);
layerTickets.put(layerID, new Long(0));
}
/**
* Stop the frame from redirecting to a previous specified URL.
*/
public void clearRedirect() {
strRedirectToURL = null;
}
/**
* Remove all controls in the context and destroy them.
*
*/
public void destroy() {
application.preDestroy();
if (state != STATE_DESTROYED) {
fireEvent(new SessionEvent(null), SESSION_STOPPED );
state = STATE_DESTROYED;
for (Iterator<Control> it = controls.values().iterator(); it.hasNext(); ) {
Control control = it.next();
it.remove();
control.destroy();
}
}
JWicRuntime.getJWicRuntime().sessionDestroyed(this);
application.postDestroy();
}
/**
* Returns the IApplicationSetup this session is based upon.
* @return
*/
public IApplicationSetup getApplicationSetup() {
return appSetup;
}
/**
* Get the URL that will display the frame/session in the current state.
* @return java.lang.String
*/
public java.lang.String getCallBackURL() {
return strCallBackURL;
}
/**
* Returns the FrameControl specified by its name.
* @return FrameControl
*/
public Control getControl(String ctrlName) {
return controls.get(ctrlName);
}
/**
* Returns the FrameControl specified by a link.
* @return FrameControl
*/
public Control getControlByLayerID(String layerID) {
return layer.get(layerID);
}
/**
* Returns an Iterator for all FrameControl objects.
*/
public Iterator<Control> getControls() {
return controls.values().iterator();
}
/**
* Returns the URL a redirect should be sent to.
*/
public String getRedirectToURL() {
return strRedirectToURL;
}
/**
* Returns the control that is on top of the stack of visible controls.
* @return The top control.
*/
public Control getTopControl() {
if (stkTopControls.empty()) {
return null;
}
return stkTopControls.lastElement();
}
/**
* Returns the id of the control that is currently renderd.
* @return java.lang.String
*/
public String getTopControlID() {
return strTopControlID;
}
/**
* Returns the value for the specified key in the application
* properties.
* @param key
* @param defaultValue
* @return
*/
public String getProperty(String key, String defaultValue) {
String value = appSetup.getProperty(key);
return value == null ? defaultValue : value;
}
/**
* Returns true if the frameset should be left and the data should be purged.
*/
public boolean isDoExit() {
return bolDoExit;
}
/*
* (non-Javadoc)
* @see de.jwic.base.IControlContainer#isRenderingRelevant(de.jwic.base.Control)
*/
public boolean isRenderingRelevant(Control childControl) {
return true;
}
/**
* Remove the control from the stack of pushed top controls.
* @param control
*/
void removeTopControl(Control control) {
if (stkTopControls.remove(control)) {
// control was pushed
if (!stkTopControls.empty()) {
Control fctrl = stkTopControls.lastElement();
strTopControlID = fctrl.getControlID();
} else {
strTopControlID = null;
}
}
}
/**
* Remove the top control from the stack of visible controls.
*
*/
public void popTopControl() {
Control frameControl = getTopControl();
stkTopControls.pop();
if (!stkTopControls.empty()) {
Control fctrl = stkTopControls.lastElement();
strTopControlID = fctrl.getControlID();
if (frameControl instanceof Page && fctrl instanceof Page) {
((Page)frameControl).setClientSettings((Page)fctrl);
}
} else {
strTopControlID = null;
}
setRequireRedraw(true);
}
/**
* Push a control on the stack of visible controls. The top control
* (including it's childs) is rendered only. Use popTopControl() to
* return to the previous control.
* @param frameControl
*/
public void pushTopControl(Control frameControl) {
if (frameControl instanceof Page && getTopControl() instanceof Page) {
((Page)frameControl).setClientSettings((Page)getTopControl());
}
stkTopControls.push(frameControl);
strTopControlID = frameControl.getControlID();
frameControl.setVisible(true);
setRequireRedraw(true);
}
/**
* Sends a redirect to the specified URL with the next page update
* sent to the client. If the isExit flag is true, all data is purged.
*
* @param sURL java.lang.String
* @param isExit boolean
*/
public void redirectTo(String sURL, boolean isExit) {
strRedirectToURL = sURL;
bolDoExit = isExit;
setRequireRedraw(true);
}
/**
* Remove a control from the container.
*/
public void removeControl(String name) {
Control control = controls.get(name);
if (control != null) {
unregisterControl(control);
control.destroy();
}
}
/**
* Remove the specified layer.
* @param layerId
*/
public void removeLayer(String layerId) {
layer.remove(layerId);
}
/**
* Set the URL that will display the frame/session in the current state.
* @param newCallBackURL java.lang.String
*/
public void setCallBackURL(java.lang.String newCallBackURL) {
strCallBackURL = newCallBackURL;
}
/**
* Get a control by its control ID.
* @param string
* @return
*/
public Control getControlById(String ctrlID) throws ControlNotFoundException {
Control control = null;
StringTokenizer stk = new StringTokenizer(ctrlID, ".");
IControlContainer container = this;
while (stk.hasMoreTokens()) {
String name = stk.nextToken();
control = container.getControl(name);
if (stk.hasMoreTokens()) {
if (control == null || !(control instanceof IControlContainer))
throw new ControlNotFoundException(ctrlID);
container = (IControlContainer)control;
}
}
if (control == null)
throw new ControlNotFoundException(ctrlID);
return control;
}
/**
* Returns the URL where the session context redirects to when the application exits.
* @return
*/
public String getExitURL() {
return strExitURL;
}
/**
* Set the URL where the session context redirects to when the application exits.
* @param string
*/
public void setExitURL(String string) {
strExitURL = string;
}
/**
* Exit the session and clean up all controls.
* @throws AgoraException
*/
public void exit() {
if (strExitURL != null && strExitURL.length() != 0)
redirectTo(strExitURL, true);
else {
bolDoExit = true;
}
setRequireRedraw(true);
}
/**
* @return Returns the actionController.
*/
public IActionController getActionController() {
return actionController;
}
/**
* @param actionController The actionController to set.
*/
public void setActionController(IActionController actionController) {
this.actionController = actionController;
}
/**
* @return Returns the locale.
*/
public Locale getLocale() {
return locale;
}
/**
* @return Returns the sessionId.
*/
public String getClientId() {
return clientId;
}
/**
* @param sessionId The sessionId to set.
*/
void setClientId(String clientId) {
this.clientId = clientId;
}
/**
* Returns the value of a parameter that was specified when the application
* has been started.
* @param key
* @return
*/
public String getInitParameter(String key) {
if (initParameter != null) {
String[] str = initParameter.get(key);
if (str != null) {
return str[0];
}
}
return null;
}
/**
* Returns a map of parameters that have been specified when the application
* has been started.
* @return Returns the initParameter.
*/
public Map<String, String[]> getInitParameters() {
return initParameter;
}
/**
* @param initParameter The initParameter to set.
*/
void setInitParameters(Map<String, String[]> initParameter) {
this.initParameter = initParameter;
}
/**
* @return Returns the state.
*/
public int getState() {
return state;
}
/**
* Returns the current request ticket number.
* @return Returns the requestTicket.
*/
public long getRequestTicket() {
return requestTicket;
}
/**
* Returns the ticket number for the specified layer.
* @param layerId
* @return
*/
public long getRequestTicket(String layerId) {
if (layerId == null || layerId.length() == 0) {
return requestTicket;
}
Long l = layerTickets.get(layerId);
if (l == null) {
return 0;
}
return l.longValue();
}
/**
* Validates if the ticket number equals the expected ticket number.
* If the numbers are equal, true is returned and the request ticket
* number is increased by one.
* @param ticket
* @return
*/
public boolean validateTicket(long ticket) {
if (ticket == requestTicket) {
if (requestTicket == Long.MAX_VALUE) {
requestTicket = 0;
} else {
requestTicket++;
}
return true;
}
return false;
}
/**
* Validates if the ticket number of the specified layerId equals the expected ticket number.
* If the numbers are equal, true is returned and the request ticket
* number is increased by one.
* @param ticket
* @return
*/
public boolean validateTicket(long ticket, String layerId) {
if (layerId == null || layerId.length() == 0) {
return validateTicket(ticket);
}
Long lngTicket = layerTickets.get(layerId);
long lTicket = lngTicket == null ? 0 : lngTicket.longValue();
if (ticket == lTicket) {
if (lTicket == Long.MAX_VALUE) {
lTicket = 0;
} else {
lTicket++;
}
layerTickets.put(layerId, new Long(lTicket));
return true;
}
return false;
}
/**
* Returns true if the page content has been changed since the last rendering
* and must be rendered again to reflect the last changes.
* @return Returns the requireRedraw.
*/
public boolean isRequireRedraw() {
return requireRedraw;
}
/**
* Set to true if the page content need to be rendered again to reflect the changes
* of the application since it was last rendered.
* @param requireRedraw The requireRedraw to set.
*/
public void setRequireRedraw(boolean requireRedraw) {
this.requireRedraw = requireRedraw;
}
/**
* @return Returns the application.
*/
public IApplication getApplication() {
return application;
}
/**
* @param application The application to set.
*/
public void setApplication(IApplication application) {
this.application = application;
}
/**
* Set the Locale for this session.
* @param locale
*/
public void setLocale(Locale locale) {
if (locale == null) {
throw new NullPointerException("Locale must not be null");
}
this.locale = locale;
}
/**
* Set the dateformat
* @param dateFormat
*/
public void setDateFormat(String dateFormat) {
this.dateFormat = dateFormat;
}
/**
*
* @return the current dateformat
*/
public String getDateFormat() {
return dateFormat;
}
/**
* @return the timeFormat
*/
public String getTimeFormat() {
return timeFormat;
}
/**
* @param timeFormat the timeFormat to set
*/
public void setTimeFormat(String timeFormat) {
this.timeFormat = timeFormat;
}
/**
*
* @return current decimal format
*/
public DecimalFormat getDecimalFormat() {
return decimalFormat;
}
/**
* Set the decimal format. Default is based on current locale.
* @param decimalFormat
*/
public void setDecimalFormat(DecimalFormat decimalFormat) {
this.decimalFormat = decimalFormat;
}
/**
* @return Returns the sessionId.
*/
public String getSessionId() {
return sessionId;
}
/**
* @param sessionId The sessionId to set.
*/
public void setSessionId(String sessionId) {
this.sessionId = sessionId;
}
/* (non-Javadoc)
* @see de.jwic.base.IControlContainer#getSessionContext()
*/
public SessionContext getSessionContext() {
return this;
}
/**
* Returns Stack of pushed Control's.
* @return
*/
public Stack<Control> getTopControls() {
return stkTopControls;
}
/**
* @return Returns the userAgent.
*/
public UserAgentInfo getUserAgent() {
return userAgent;
}
/**
* @param userAgent The userAgent to set.
*/
void setUserAgent(UserAgentInfo userAgent) {
this.userAgent = userAgent;
}
/**
* @return the timeZone
*/
public TimeZone getTimeZone() {
return timeZone;
}
/**
* @param timeZone the timeZone to set
*/
public void setTimeZone(TimeZone timeZone) {
if (timeZone == null) {
throw new NullPointerException("TimeZone must not be null");
}
this.timeZone = timeZone;
}
/**
* @return the mouseEvent, null if none exists.
*/
public MouseEvent getMouseEvent() {
return mouseEvent;
}
/**
* @param mouseEvent the MouseEvent to set
*/
public void setMouseEvent(MouseEvent mouseEvent) {
this.mouseEvent = mouseEvent;
}
/**
* Returns the list of queued script commands. These are executed with the next regular
* page update.
* @return the scriptQueue
*/
public List<String> getScriptQueue() {
return Collections.unmodifiableList(scriptQueue);
}
/**
* Clear the queue.
*/
public void clearScriptQueue() {
scriptQueue.clear();
}
/**
* Adds a script that is executed after the next page update.
* @param script
*/
public void queueScriptCall(String script) {
scriptQueue.add(script);
}
/**
* Displays a notification message.
* @param message
*/
public void notifyMessage(String message) {
queueScriptCall("JWic.ui.Notify.display('" + StringEscapeUtils.escapeJavaScript(message) + "');");
}
/**
* Displays a notification message with a custom css clazz.
* @param message
*/
public void notifyMessage(String message, String cssClazz) {
queueScriptCall("JWic.ui.Notify.display('" + StringEscapeUtils.escapeJavaScript(message) + "', '" + cssClazz + "');");
}
/**
* Displays a notification message with a custom css clazz and a custom delay.
* @param message
*/
public void notifyMessage(String message, String cssClazz, double duration, double delay) {
queueScriptCall("JWic.ui.Notify.display('" + StringEscapeUtils.escapeJavaScript(message) + "', '" + cssClazz + "', " + duration + ", " + delay + ");");
}
/**
* @return the themeName
*/
public String getThemeName() {
return themeName;
}
/**
* The theme name used by the application. This is typically used to load different
* css files form the themes folder to customize the application.
* @param themeName the themeName to set
*/
public void setThemeName(String themeName) {
this.themeName = themeName;
setRequireRedraw(true);
}
}