/*
* This software is distributed under the terms of the FSF
* Gnu Lesser General Public License (see lgpl.txt).
*
* This program is distributed WITHOUT ANY WARRANTY. See the
* GNU General Public License for more details.
*/
package com.scooterframework.web.controller;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import com.scooterframework.admin.Constants;
import com.scooterframework.common.util.Converters;
import com.scooterframework.common.util.CurrentThreadCache;
import com.scooterframework.common.util.Message;
import com.scooterframework.common.util.StringUtil;
/**
* <p>
* ActionContext class holds context data in different scopes.
* </p>
*
* <p>
* There are six supported scopes: thread, parameter, request, session, context
* and global. The first scope supports data held in current thread, the next
* four scopes map to the corresponding scopes in Servlet and JSP for web
* environment. The global scope provides a place for the whole application to
* access data that can be used for all users in a static way.
* </p>
*
* @author (Fei) John Chen
*/
public abstract class ActionContext {
private static final String LOCALE_KEY = Constants.CURRENT_LOCALE;
//key to the flash message
public static final String KEY_FLASH_MESSAGE = "KEY_FLASH_MESSAGE";
/**
* String constant for thread scope.
*/
public static final String SCOPE_THREAD = "thread";
/**
* String constant for parameter scope.
*/
public static final String SCOPE_PARAMETER = "parameter";
/**
* String constant for request scope.
*/
public static final String SCOPE_REQUEST = "request";
/**
* String constant for session scope.
*/
public static final String SCOPE_SESSION = "session";
/**
* String constant for context scope.
*/
public static final String SCOPE_CONTEXT = "context";
/**
* String constant for global scope.
*/
public static final String SCOPE_GLOBAL = "global";
/**
* Global data map.
*/
private static Map<String, Object> globalData = new HashMap<String, Object>();
public ActionContext() {
}
/**
* <p>Gets data in parameter scope as a map.</p>
*
* <p>Return guaranteed: An empty map will be returned if there is no data.</p>
*
* @return Map
*/
public abstract Map<String, Object> getParameterDataAsMap();
/**
* <p>Gets data in request scope as a map.</p>
*
* <p>Return guaranteed: An empty map will be returned if there is no data.</p>
*
* @return Map
*/
public abstract Map<String, Object> getRequestDataAsMap();
/**
* <p>Gets data in both parameter scope and request scope as a map.</p>
*
* <p>Return guaranteed: An empty map will be returned if there is no data.</p>
*
* @return Map
*/
public Map<String, Object> getAllRequestDataAsMap() {
Map<String, Object> pm = getParameterDataAsMap();
Map<String, Object> rm = getRequestDataAsMap();
if (pm == null) pm = new HashMap<String, Object>();
if (rm != null) pm.putAll(rm);
return pm;
}
/**
* <p>Gets data in parameter scope as a map. Only those keys with a specific
* keyPrefix will be processed.</p>
*
* <p>Return guaranteed: An empty map will be returned if there is no data.</p>
*
* @return Map
*/
public Map<String, Object> getParameterDataAsMap(String keyPrefix) {
Map<String, Object> m = getParameterDataAsMap();
if (keyPrefix == null || "".equals(keyPrefix)) return m;
Map<String, Object> m2 = new HashMap<String, Object>();
for (Map.Entry<String, Object> entry : m.entrySet()) {
String key = entry.getKey();
if (key != null && key.startsWith(keyPrefix)) {
String key2 = key.substring(keyPrefix.length());
m2.put(key2, entry.getValue());
}
}
return m2;
}
/**
* <p>Gets data in request scope as a map. Only those keys with a specific
* keyPrefix will be processed.</p>
*
* <p>Return guaranteed: An empty map will be returned if there is no data.</p>
*
* @return Map
*/
public Map<String, Object> getRequestDataAsMap(String keyPrefix) {
Map<String, Object> m = getRequestDataAsMap();
if (keyPrefix == null || "".equals(keyPrefix)) return m;
Map<String, Object> m2 = new HashMap<String, Object>();
for (Map.Entry<String, Object> entry : m.entrySet()) {
String key = entry.getKey();
if (key != null && key.startsWith(keyPrefix)) {
String key2 = key.substring(keyPrefix.length());
m2.put(key2, m.get(key));
}
}
return m2;
}
/**
* <p>Gets data in both parameter scope and request scope as a map. Only those
* keys with a specific keyPrefix will be processed.</p>
*
* <p>Return guaranteed: An empty map will be returned if there is no data.</p>
*
* @return Map
*/
public Map<String, Object> getAllRequestDataAsMap(String keyPrefix) {
Map<String, Object> pm = getParameterDataAsMap(keyPrefix);
Map<String, Object> rm = getRequestDataAsMap(keyPrefix);
if (pm == null) pm = new HashMap<String, Object>();
if (rm != null) pm.putAll(rm);
return pm;
}
/**
* <p>Gets data in session scope as a map.</p>
*
* <p>Return guaranteed: An empty map will be returned if there is no data.</p>
*
* @return Map
*/
public abstract Map<String, Object> getSessionDataAsMap();
/**
* Gets data in context scope as a map.
*
* @return Map
*/
public abstract Map<String, Object> getContextDataAsMap();
/**
* Gets data in global scope as a map.
*
* @return Map
*/
public static Map<String, Object> getGlobalDataAsMap() {
return globalData;
}
/**
* <p>Gets data represented by the key from the thread scope.</p>
*
* <p>Note: The result of this method is sensitive to the case of key string.</p>
*
* @param key
* @return Object
*/
public static Object getFromThreadData(String key) {
return CurrentThreadCache.get(key);
}
/**
* <p>Gets data represented by the key from the parameter scope.</p>
*
* <p>Note: The result of this method is sensitive to the case of key string.</p>
*
* @param key
* @return Object
*/
public abstract Object getFromParameterData(String key);
/**
* <p>Gets data represented by the key from the parameter scope. Ignore the
* case of the key string.</p>
*
* <p>Note: The result of this method is sensitive to the case of key string.</p>
*
* @param key
* @return Object
*/
public Object getFromParameterDataIgnoreCase(String key) {
if (key == null) return null;
Object tmp = null;
Map<String, Object> m = getParameterDataAsMap();
if (m == null) return null;
for (Map.Entry<String, Object> entry : m.entrySet()) {
String name = entry.getKey();
if (name.equalsIgnoreCase(key)) {
tmp = entry.getValue();
break;
}
}
return tmp;
}
/**
* <p>Gets data represented by the key from the request scope.</p>
*
* <p>Note: The result of this method is sensitive to the case of key string.</p>
*
* @param key
* @return Object
*/
public abstract Object getFromRequestData(String key);
/**
* <p>Gets data represented by the key from the session scope.</p>
*
* <p>Note: The result of this method is sensitive to the case of key string.</p>
*
* @param key
* @return Object
*/
public abstract Object getFromSessionData(String key);
/**
* <p>Gets data represented by the key from the context scope.</p>
*
* <p>Note: The result of this method is sensitive to the case of key string.</p>
*
* @param key
* @return Object
*/
public abstract Object getFromContextData(String key);
/**
* <p>Gets data represented by the key from the global scope.</p>
*
* <p>Note: The result of this method is sensitive to the case of key string.</p>
*
* @param key
* @return Object
*/
public static Object getFromGlobalData(String key) {
return getGlobalDataAsMap().get(key);
}
/**
* Gets the first message represented by the type from the flash.
*
* @param type
* @return String
*/
public String getFirstFlashMessage(String type) {
FlashMessage fm = (FlashMessage)getFromRequestData(KEY_FLASH_MESSAGE);
return (fm != null)?fm.getFirst(type):"";
}
/**
* Gets the latest message represented by the type from the flash.
*
* @param type
* @return String
*/
public String getLatestFlashMessage(String type) {
FlashMessage fm = (FlashMessage)getFromRequestData(KEY_FLASH_MESSAGE);
return (fm != null)?fm.getLast(type):"";
}
/**
* Gets all messages represented by the type from the flash.
*
* @param type flash message type
* @return list of messages
*/
public List<Message> getAllFlashMessages(String type) {
FlashMessage fm = (FlashMessage)getFromRequestData(KEY_FLASH_MESSAGE);
return (fm != null)?fm.getAll(type):null;
}
/**
* <p>Gets data represented by the key from the first scope it is found in
* parameter scope and request scope.</p>
*
* <p>Note: The result of this method is sensitive to the case of key string.</p>
*
* @param key
* @return Object
*/
public Object getFromAllRequestData(String key) {
Object o = getFromParameterData(key);
if (o == null) {
o = getFromRequestData(key);
}
return o;
}
/**
* <p>Gets data represented by the key from the first scope it is found.</p>
*
* <p>
* There are six supported scopes: thread, parameter, request, session, context
* and global. The first scope supports data held in current thread, the next
* four scopes map to the corresponding scopes in Servlet and JSP for web
* environment. The global scope provides a place for the whole application to
* access data that can be used for all users in a static way.
* </p>
*
* <p>Note: The result of this method is sensitive to the case of key string.<p>
*
* @param key
* @return Object
*/
public Object get(String key) {
Object o = getFromThreadData(key);
if (o == null) {
o = getFromParameterData(key);
if (o == null) {
o = getFromRequestData(key);
if (o == null) {
o = getFromSessionData(key);
if (o == null) {
o = getFromContextData(key);
if (o == null) {
o = getFromGlobalData(key);
}
}
}
}
}
return o;
}
/**
* <p>Gets data represented by the key from the specific scope.</p>
*
* <p>
* There are six supported scopes: thread, parameter, request, session, context
* and global. The first scope supports data held in current thread, the next
* four scopes map to the corresponding scopes in Servlet and JSP for web
* environment. The global scope provides a place for the whole application to
* access data that can be used for all users in a static way.
* </p>
*
* <p>Note: The result of this method is sensitive to the case of key string.</p>
*
* @param key
* @return Object
*/
public Object get(String key, String scope) {
Object o = null;
if (SCOPE_THREAD.equals(scope)) {
o = getFromThreadData(key);
}
else if (SCOPE_PARAMETER.equals(scope)) {
o = getFromParameterData(key);
}
else if (SCOPE_REQUEST.equals(scope)) {
o = getFromRequestData(key);
}
else if (SCOPE_SESSION.equals(scope)) {
o = getFromSessionData(key);
}
else if (SCOPE_CONTEXT.equals(scope)) {
o = getFromContextData(key);
}
else if (SCOPE_GLOBAL.equals(scope)) {
o = getFromGlobalData(key);
}
else {
throw new IllegalArgumentException("Undefined scope: " + scope + ".");
}
return o;
}
/**
* <p>Removes data represented by the key from all scopes except the
* parameter scope. There is no servlet API that can remove data from
* parameter scope.</p>
*
* <p>Note: The result of this method is sensitive to the case of key string.</p>
*
* @param key
*/
public void remove(String key) {
removeFromThreadData(key);
removeFromRequestData(key);
removeFromSessionData(key);
removeFromContextData(key);
removeFromGlobalData(key);
}
/**
* <p>Removes data represented by the key from thread scope.</p>
*
* <p>Note: The result of this method is sensitive to the case of key string.</p>
*
* @param key
*/
public static void removeFromThreadData(String key) {
CurrentThreadCache.clear(key);
}
/**
* <p>Removes data represented by the key from request scope.</p>
*
* <p>Note: The result of this method is sensitive to the case of key string.</p>
*
* @param key
*/
public abstract void removeFromRequestData(String key);
/**
* <p>Removes data represented by the key from session scope.</p>
*
* <p>Note: The result of this method is sensitive to the case of key string.</p>
*
* @param key
*/
public abstract void removeFromSessionData(String key);
/**
* <p>Removes data represented by the key from context scope.</p>
*
* <p>Note: The result of this method is sensitive to the case of key string.</p>
*
* @param key
*/
public abstract void removeFromContextData(String key);
/**
* <p>Removes data represented by the key from global scope.</p>
*
* <p>Note: The result of this method is sensitive to the case of key string.</p>
*
* @param key
*/
public static void removeFromGlobalData(String key) {
getGlobalDataAsMap().remove(key);
}
/**
* Removes all data represented by the key from session scope.
*/
public abstract void removeAllSessionData();
/**
* Stores the object represented by the key to thread scope.
*
* @param key String
* @param value Object
*/
public static void storeToThread(String key, Object value) {
CurrentThreadCache.set(key, value);
}
/**
* Stores the object represented by the key to request scope.
*
* @param key String
* @param value Object
*/
public abstract void storeToRequest(String key, Object value);
/**
* Stores the object represented by the key to session scope.
*
* @param key String
* @param value Object
*/
public abstract void storeToSession(String key, Object value);
/**
* Stores the object represented by the key to context scope.
*
* @param key String
* @param object Object
*/
public abstract void storeToContext(String key, Object object);
/**
* Stores the object represented by the key to global scope.
*
* @param key String
* @param value Object
*/
public static void storeToGlobal(String key, Object value) {
getGlobalDataAsMap().put(key, value);
}
/**
* Sets message for a type.
*/
public void setFlashMessage(String type, String message) {
setFlashMessage(type, new Message(message));
}
/**
* Sets message for a type.
*/
public void setFlashMessage(String type, Message message) {
FlashMessage fm = (FlashMessage)getFromSessionData(KEY_FLASH_MESSAGE);
if (fm == null) {
fm = new FlashMessage();
storeToSession(KEY_FLASH_MESSAGE, fm);
}
fm.addMessage(type, message);
//set to request too
FlashMessage fm2 = (FlashMessage)getFromRequestData(KEY_FLASH_MESSAGE);
if (fm2 == null) {
storeToRequest(KEY_FLASH_MESSAGE, fm);
}
else {
fm2.addMessage(type, message);
}
}
/**
* Removes FlashMessage data from session and stores it in the
* incoming request.
*/
public void resetFlashMessage() {
FlashMessage fm = (FlashMessage)getFromSessionData(KEY_FLASH_MESSAGE);
if (fm != null) {
storeToRequest(KEY_FLASH_MESSAGE, fm);
removeFromSessionData(KEY_FLASH_MESSAGE);
}
}
/**
* Returns errors as Map.
*
* @return Map of error
*/
public Map<String, Object> getErrorAsMap() {
return errors;
}
/**
* Sets a map of error to error map.
*
* @param errors Map
*/
public void setErrors(Map<String, Object> errors) {
if (errors != null) this.errors.putAll(errors);
}
/**
* Gets an error object represented by a key to error map.
*
* @param key The key to the error object.
* @return error Object
*/
public Object getError(String key) {
return errors.get(key);
}
/**
* Sets an error object represented by a key to error map.
*
* @param error An error object.
*/
public void setError(String key, Object error) {
errors.put(key, error);
}
/**
* Errors map.
*/
protected Map<String, Object> errors = new HashMap<String, Object>();
/**
* <p>Retrieves a map of primary key and values.</p>
*
* <p>The result map is restricted to those keys in the input with a specific
* prefix.</p>
*
* @param keyPrefix a prefix string.
* @param pkNames an array of primary key names.
* @return map Map of primary key data.
*/
public Map<String, Object> retrievePrimaryKeyDataMapFromRequest(String keyPrefix, String[] pkNames) {
if (pkNames == null || pkNames.length == 0) return new HashMap<String, Object>();
if (keyPrefix == null) keyPrefix = "";
Map<String, Object> hm = new HashMap<String, Object>();
Map<String, Object> dataMap = getAllRequestDataAsMap(keyPrefix);
if (dataMap == null) return hm;
for (Map.Entry<String, Object> entry : dataMap.entrySet()) {
String key = entry.getKey();
if (key != null && StringUtil.isStringInArray(key, pkNames, true)) hm.put(key.toLowerCase(), entry.getValue());
}
return hm;
}
/**
* Retrieves a map of primary key and values.
*
* @param pkNames an array of primary key names.
* @return map Map of primary key data.
*/
public Map<String, Object> retrievePrimaryKeyDataMapFromRequest(String[] pkNames) {
return retrievePrimaryKeyDataMapFromRequest("", pkNames);
}
/**
* Checks if the value of a field is required.
*
* @param name name of a request parameter.
* @return true if required
*/
public boolean isRequiredField(String name) {
boolean require = false;
String value = (String)get(name);
if ("on".equalsIgnoreCase(value) || "true".equalsIgnoreCase(value)) require = true;
return require;
}
/**
* <p>Returns Locale object from the first scope it is found. If no locale
* object is found, <tt>Locale.getDefault()</tt> is returned. </p>
*
* <p>
* There are six supported scopes: thread, parameter, request, session, context
* and global. The first scope supports data held in current thread, the next
* four scopes map to the corresponding scopes in Servlet and JSP for web
* environment. The global scope provides a place for the whole application to
* access data that can be used for all users in a static way.
* </p>
*
* @return Locale for the scope
*/
public Locale getLocale() {
Locale l = (Locale)get(LOCALE_KEY);
if (l == null) {
l = Locale.getDefault();
}
return l;
}
/**
* Returns Locale of a scope. If no locale object is found, <tt>Locale.getDefault()</tt>
* is returned.
*
* @param scope the specific scope
* @return Locale for the scope
*/
public Locale getLocale(String scope) {
return (Locale)get(LOCALE_KEY, scope);
}
/**
* Stores locale object in a scope. The scope is either <tt>request</tt> or
* <tt>session</tt> or <tt>context</tt> or <tt>global</tt>.
*
* @param locale
* @param scope
*/
public void setLocale(Locale locale, String scope) {
if (SCOPE_REQUEST.equals(scope)) {
storeToRequest(LOCALE_KEY, locale);
}
else if (SCOPE_SESSION.equals(scope)) {
storeToSession(LOCALE_KEY, locale);
}
else if (SCOPE_CONTEXT.equals(scope)) {
storeToContext(LOCALE_KEY, locale);
}
else if (SCOPE_GLOBAL.equals(scope)) {
storeToGlobal(LOCALE_KEY, locale);
}
else {
throw new IllegalArgumentException("Unsupported scope value for storing locale object: " + scope + ".");
}
}
/**
* Sets locale object to be used by a single user request.
* @param locale
*/
public void setRequestLocale(Locale locale) {
setLocale(locale, SCOPE_REQUEST);
}
/**
* Sets locale object to be used by a single user session.
*
* @param locale
*/
public void setSessionLocale(Locale locale) {
setLocale(locale, SCOPE_SESSION);
}
/**
* Sets locale object to be used by all users.
*
* @param locale
*/
public static void setGlobalLocale(Locale locale) {
storeToGlobal(LOCALE_KEY, locale);
}
/**
* Gets a locale object that can be used by all users.
*/
public static Locale getGlobalLocale() {
return (Locale)getFromGlobalData(LOCALE_KEY);
}
/**
* Returns an item from a list of items.
*
* Examples:
* <pre>
* class=<%=W.cycle("odd, even")%>
* -- use "red" class for odd rows and "blue" class for even rows.
* </pre>
*
* @param items list of items to be cycled
* @return an item value
*/
public String cycle(String items) {
return cycle(items, items);
}
/**
* Returns an item from a list of items. The <tt>items</tt> string may
* contain several items separated by comma.
*
* @param items list of items to be cycled
* @param name the cycle name
* @return an item value
*/
public String cycle(String items, String name) {
if (items == null || "".equals(items)) return "";
Cycle c = (Cycle)getCycleFromCycleMap(name);
if (c == null) {
c = new Cycle(items);
setCycleToCycleMap(name, c);
}
//in case the same cycle name is used somewhere else but with a
//different list of items to cycle through which may cause something
//like IndexOutOfBound exception
if (!items.equals(c.cycleStrings)) {
c.setItems(items);
}
return c.getValue();
}
/**
* Returns current item in the named cycle.
*
* @param name name of the cycle
* @return current item in the named cycle.
*/
public String currentCycle(String name) {
Cycle c = (Cycle)getCycleFromCycleMap(name);
return (c != null)?c.getCurrentValue():"";
}
/**
* Resets the cycle
*
* @param name cycle's name
*/
public void resetCycle(String name) {
Cycle c = (Cycle)getCycleFromCycleMap(name);
if (c != null) c.resetPointer();
}
/**
* Returns a named cycle from cycle map.
*
* @param name
* @return cycle object
*/
abstract protected Object getCycleFromCycleMap(String name);
/**
* Sets a named cycle in cycle map.
*
* @param name
* @param cycle
*/
abstract protected void setCycleToCycleMap(String name, Object cycle);
private static class Cycle {
public Cycle(String items) {
cycleStrings = items;
setItems(items);
currentIndex = 0;
}
void setItems(String items) {
itemsList = Converters.convertStringToList(items);
totalSize = itemsList.size();
}
String getValue() {
int pointer = getPointer();
if (pointer == -1) return "";
latestItem = itemsList.get(pointer);
return latestItem;
}
String getCurrentValue() {
return latestItem;
}
private int getPointer() {
if (totalSize == 0) return -1; //nothing to cycle
int pointer = currentIndex;
//move to next item
incrementPointer(currentIndex);
return pointer;
}
void resetPointer() {
currentIndex = 0;
}
private void incrementPointer(int index) {
currentIndex = index + 1;
if (currentIndex >= totalSize) {
resetPointer();
}
}
private String cycleStrings = "";
private List<String> itemsList = null;
private int currentIndex = 0;
private String latestItem = "";
private int totalSize = 0;
}
}