/*
* #!
* Ontopia Webed
* #-
* Copyright (C) 2001 - 2013 The Ontopia Project
* #-
* 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 net.ontopia.topicmaps.webed.impl.utils;
import java.io.File;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import javax.servlet.ServletContext;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.jsp.JspTagException;
import javax.servlet.jsp.PageContext;
import net.ontopia.topicmaps.core.TMObjectIF;
import net.ontopia.topicmaps.core.TopicMapIF;
import net.ontopia.topicmaps.entry.TopicMapReferenceIF;
import net.ontopia.topicmaps.entry.TopicMapRepositoryIF;
import net.ontopia.topicmaps.nav2.core.NavigatorApplicationIF;
import net.ontopia.topicmaps.nav2.core.NavigatorPageIF;
import net.ontopia.topicmaps.nav2.core.UserIF;
import net.ontopia.topicmaps.nav2.impl.framework.InteractionELSupport;
import net.ontopia.topicmaps.nav2.utils.ContextUtils;
import net.ontopia.topicmaps.nav2.utils.FrameworkUtils;
import net.ontopia.topicmaps.nav2.utils.NavigatorUtils;
import net.ontopia.topicmaps.query.parser.AntlrWrapException;
import net.ontopia.topicmaps.query.parser.ParseContextIF;
import net.ontopia.topicmaps.query.parser.QName;
import net.ontopia.topicmaps.schema.core.SchemaSyntaxException;
import net.ontopia.topicmaps.schema.impl.osl.OSLSchema;
import net.ontopia.topicmaps.schema.impl.osl.OSLSchemaReader;
import net.ontopia.topicmaps.webed.impl.basic.ActionDataSet;
import net.ontopia.topicmaps.webed.impl.basic.ActionInGroup;
import net.ontopia.topicmaps.webed.impl.basic.ActionRegistryIF;
import net.ontopia.topicmaps.webed.impl.basic.Constants;
import net.ontopia.topicmaps.webed.taglibs.form.FormTag;
import net.ontopia.utils.OntopiaRuntimeException;
import net.ontopia.utils.StringUtils;
import net.ontopia.topicmaps.impl.utils.TMRevitalizer;
import net.ontopia.topicmaps.impl.utils.TMRevitalizerIF;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;
import org.apache.velocity.app.VelocityEngine;
import org.xml.sax.Locator;
/**
* INTERNAL: Utilities used by the tag classes.
*/
public final class TagUtils {
// initialization of logging facility
private static Logger log = LoggerFactory.getLogger(TagUtils.class.getName());
/**
* This variable is used to assign unique IDs to requests and
* actions. Do NOT access it directly, to avoid synchronization
* issues like bug #2021.
*/
private static long counter = 0;
/**
* The default location where the velocity templates are stored in.
*/
protected final static String VELOCITY_TEMPLATE_PATH = "/WEB-INF/templates/";
/**
* Gets the name of the action group as an attribute value (residing
* in the page scope).
*/
public static String getActionGroup(PageContext pageContext) {
return (String) pageContext.getAttribute(Constants.RA_ACTIONGROUP,
PageContext.REQUEST_SCOPE);
}
/**
* Sets the name of the action group (an attribute is set in the
* page scope).
*/
public static void setActionGroup(PageContext pageContext,
String actionGroup) {
pageContext.setAttribute(Constants.RA_ACTIONGROUP, actionGroup,
PageContext.REQUEST_SCOPE);
}
/**
* Gets the topic map object from the given <code>obj_name</code> by
* requesting the context manager retrieved by the given
* <code>pageContext</code>.
*/
public static TMObjectIF getTMObject(PageContext pageContext,
String obj_name)
throws JspTagException {
Object obj = ContextUtils.getSingleValue(obj_name, pageContext);
if (obj == null)
return null;
if (!(obj instanceof TMObjectIF))
throw new JspTagException("Object with name '" + obj_name + "' " +
"cannot be casted to TMObjectIF.");
return ((TMObjectIF) obj);
}
public static Map getSchemaRegistry(ServletContext servletContext) {
Map schemas = (Map)servletContext.getAttribute(Constants.AA_SCHEMAS);
if (schemas != null)
return schemas;
// Read in schemas for the topicmaps and provide them to the app context
String schemasRootDir = servletContext.getInitParameter(Constants.SCTXT_SCHEMAS_ROOTDIR);
if (schemasRootDir != null)
schemasRootDir = servletContext.getRealPath(schemasRootDir);
schemas = new HashMap();
if (schemasRootDir == null) {
servletContext.setAttribute(Constants.AA_SCHEMAS, schemas);
log.debug("No schema directory configured; registry empty");
return schemas;
}
log.debug("Reading schemas from " + schemasRootDir);
TopicMapRepositoryIF repository = NavigatorUtils.getTopicMapRepository(servletContext);
Collection refkeys = repository.getReferenceKeys();
Iterator iter = refkeys.iterator();
while (iter.hasNext()) {
String refkey = (String) iter.next();
TopicMapReferenceIF reference = repository.getReferenceByKey(refkey);
String tmid = reference.getId();
try {
OSLSchemaReader reader = new OSLSchemaReader(new File(schemasRootDir, tmid + ".osl"));
OSLSchema schema = (OSLSchema) reader.read();
schemas.put(tmid, schema);
log.info("Loaded schema for " + tmid);
} catch (java.io.IOException e) {
log.info("Warning: " + e.getMessage());
} catch (SchemaSyntaxException e) {
log.error("Schema syntax error: " + e.getMessage());
Locator loc = e.getErrorLocation();
log.error("Location: " + loc.getSystemId() + ":" +
loc.getLineNumber() + ":" + loc.getColumnNumber() + ".");
}
}
servletContext.setAttribute(Constants.AA_SCHEMAS, schemas);
return schemas;
}
public static synchronized NamedLockManager getNamedLockManager(ServletContext servletContext) {
String identifier = servletContext.getInitParameter("lockmanager");
if (identifier == null) identifier = "";
return LockManagers.getLockManager(identifier);
}
public static ActionRegistryIF getActionRegistry(ServletRequest request)
throws JspTagException {
ServletContext servletContext = ((HttpServletRequest)request).getSession().getServletContext();
ActionRegistryIF registry = (ActionRegistryIF)servletContext
.getAttribute(Constants.AA_REGISTRY);
if (registry != null)
return registry;
// Read in Action Configuration and set it to application context
String cfgpath = servletContext.getInitParameter(Constants.SCTXT_CONFIG_PATH);
if (cfgpath == null)
cfgpath = "classpath:actions.xml";
log.debug("Start reading action configuration from " + cfgpath);
String str_delay = servletContext.getInitParameter(Constants.SCTXT_RELOAD_DELAY);
long delay = 6000; // every 6 seconds by default
if (str_delay != null) {
try {
delay = Long.parseLong(str_delay) * 1000; // value in milliseconds
} catch (NumberFormatException e) {
delay = -1;
log.warn("Warning: Falling back to no config re-reading, " +e);
}
}
String ctxtPath = ((HttpServletRequest)request).getContextPath();
String realpath = servletContext.getRealPath("");
ActionConfigurator aconf =
new ActionConfigurator(ctxtPath, realpath, cfgpath, delay);
ActionConfigRegistrator registrator =
new ActionConfigRegistrator(servletContext);
//!aconf.addObserver(registrator);
//!aconf.readAndWatchRegistry();
// HACK to make loading config files from classpath work
aconf.readRegistryConfiguration();
registry = aconf.getRegistry();
registrator.configurationChanged(registry);
log.debug("Setup action configuration for the web editor and assigned it to application context.");
return registry;
}
/**
* INTERNAL: Utility for attaching an ID to an action.
*/
public static String getActionID(PageContext pageContext,
String action_name, String group_name,
Set value)
throws JspTagException {
return registerData(pageContext, action_name, group_name, (List) null, value);
}
/**
* INTERNAL: Evaluates a string of space-separated variable names as a list
* of collections, and returns it.
*/
public static List evaluateParameterList(PageContext pageContext,
String params)
throws JspTagException {
if (params != null && !params.equals(""))
return getMultipleValuesAsList(params, pageContext);
else
return Collections.EMPTY_LIST;
}
/**
* INTERNAL: Returns the values retrieved from the given variable
* names or qnames in the order given.
*
* @param params - variable names or qnames, separated by whitespaces.
*/
private static List getMultipleValuesAsList(String params,
PageContext pageContext)
throws JspTagException {
log.debug("getMultipleValuesAsList");
// find parsecontext
NavigatorPageIF ctxt = (NavigatorPageIF)
pageContext.getAttribute(NavigatorApplicationIF.CONTEXT_KEY,
PageContext.REQUEST_SCOPE);
ParseContextIF pctxt = (ParseContextIF) ctxt.getDeclarationContext();
// Replace sequences of special characters like \n and \t with single space.
// Needed since StringUtils.split() treats special characters as tokens.
String paramsNormalized = StringUtils.normalizeWhitespace(params.trim());
// get the values
String[] names = StringUtils.split(paramsNormalized);
List varlist = new ArrayList(names.length);
for (int i = 0; i < names.length; i++) {
Collection values;
if (names[i].indexOf(':') != -1) {
// it's a qname
try {
values = Collections.singleton(pctxt.getObject(new QName(names[i])));
} catch (AntlrWrapException e) {
throw new JspTagException(e.getException().getMessage() +
" (in action parameter list)");
}
} else
// it's a variable name
values = InteractionELSupport.extendedGetValue(names[i], pageContext);
varlist.add(values);
}
return varlist;
}
/**
* INTERNAL: Creates the field name used by a particular action and
* registers the data used by the action in the user session.
*/
public static String registerData(PageContext pageContext,
String action_name, String group_name,
String params, Set value)
throws JspTagException {
return registerData(pageContext, action_name, group_name, params, null,
value);
}
/**
* INTERNAL: Creates the field name used by a particular action and
* registers the data used by the action in the user session.
*/
public static String registerData(PageContext pageContext,
String action_name, String group_name,
String params, Set value,
boolean run_if_no_changes)
throws JspTagException {
List paramlist = evaluateParameterList(pageContext, params);
return registerData(pageContext, action_name, group_name, paramlist, null,
value, false, run_if_no_changes);
}
/**
* INTERNAL: Creates the field name used by a particular action and
* registers the data used by the action in the user session.
*/
public static String registerData(PageContext pageContext,
String action_name, String group_name,
String params, List sub_actions, Set value)
throws JspTagException {
List paramlist = evaluateParameterList(pageContext, params);
return registerData(pageContext, action_name, group_name, paramlist,
sub_actions, value);
}
/**
* INTERNAL: Creates the field name used by a particular action and
* registers the data used by the action in the user session.
*/
public static String registerData(PageContext pageContext,
String action_name, String group_name,
List paramlist, Set value)
throws JspTagException {
return registerData(pageContext, action_name, group_name, paramlist, null,
value);
}
/**
* INTERNAL: Creates the field name used by a particular action and
* registers the data used by the action in the user session.
*/
public static String registerData(PageContext pageContext,
String action_name, String group_name,
List paramlist, List sub_actions, Set value)
throws JspTagException {
return registerData(pageContext, action_name, group_name, paramlist, sub_actions,
value, false, true);
}
/**
* INTERNAL: Creates the field name used by a particular action and registers
* the data used by the action in the user session.
*
* @param pageContext the context of the page being rendered
* @param action_name the name of the action
* @param group_name the name of the action group
* @param paramlist the parameters to the action (list of collections)
* @param sub_actions the sub-actions of this action, if any
* @param value the current value of the form control
* @param create_ads_if_not_exists whether to create ActionDataSet
* if not found
* @return the name of the form field that will trigger the action
*/
private static String registerData(PageContext pageContext,
String action_name, String group_name, List paramlist, List sub_actions,
Set value, boolean create_ads_if_not_exists, boolean run_if_no_changes)
throws JspTagException {
// retrieve action
ActionRegistryIF registry = getActionRegistry(pageContext);
ActionInGroup action = ActionUtils.getAction(registry, group_name,
action_name);
if (action == null)
throw new JspTagException("Unknown action '" + action_name + "' in group"
+ " '" + group_name + "', please check configuration.");
// compute name
String name = action.getName() + getNextCounterId();
// register action with field name
ActionData data = new ActionData(action, TagUtils.serializeParameters(paramlist), value,
sub_actions, name);
data.setRunIfNoChanges(run_if_no_changes);
ActionDataSet ads = getActionDataSet(pageContext, create_ads_if_not_exists);
ads.addActionData(data);
log.info("Attached action data to field " + name);
return name;
}
/**
* INTERNAL: Creates an ActionData wrapper for the given action and
* parameters.
*
* @param params A whitespace-separated list of navigator variable names
*/
public static ActionData makeActionData(PageContext pageContext,
String action_name, String group_name,
String params)
throws JspTagException {
ActionRegistryIF registry = getActionRegistry(pageContext);
ActionInGroup action = ActionUtils.getAction(registry, group_name, action_name);
if (action == null)
throw new JspTagException("Unknown action '" + action_name + "' in group" +
" '" + group_name + "', please check configuration.");
List paramlist = evaluateParameterList(pageContext, params);
return new ActionData(action, TagUtils.serializeParameters(paramlist));
}
/**
* INTERNAL: Creates a new request ID, guaranteed to be unique
* throughout the lifetime of the web application (that is, until
* server restart).
*/
public static String createRequestId() {
return "rid" + Long.toString(getNextCounterId());
}
/**
* INTERNAL: Produces a new unique ID and advances the internal
* counter. Created as the easiest way to synchronize access to the
* internal counter.
*/
private static synchronized long getNextCounterId() {
return counter++;
}
/**
* INTERNAL: Retrieves the action data set for the current form.
*/
public static ActionDataSet getActionDataSet(PageContext pageContext) {
return getActionDataSet(pageContext, false);
}
public static ActionDataSet createActionDataSet(PageContext pageContext) {
return getActionDataSet(pageContext, true);
}
private static ActionDataSet getActionDataSet(PageContext pageContext,
boolean create) {
UserIF user = FrameworkUtils.getUser(pageContext);
String requestId = TagUtils.getRequestId(pageContext);
if (requestId == null)
throw new OntopiaRuntimeException("No request id assigned. Binding action "
+ "outside <webed:form> tag?");
ActionDataSet ads = (ActionDataSet) user.getWorkingBundle(requestId);
if (ads == null && create) {
ads = new ActionDataSet(requestId);
log.debug("Adding ActionDataSet to request id: " + requestId);
user.addWorkingBundle(requestId, ads);
} else if (ads == null && !create)
throw new OntopiaRuntimeException("No action data set. Binding action "
+ "outside <webed:form> tag?");
return ads;
}
// -----------------------------------------------------------------------
// Velocity related helper methods
// -----------------------------------------------------------------------
public static VelocityContext getVelocityContext(PageContext pageContext) {
return new VelocityContext();
}
protected static VelocityEngine getVelocityEngine(ServletContext scontext) {
VelocityEngine vengine = (VelocityEngine)scontext.getAttribute(Constants.SCTXT_VELOCITY_ENGINE);
if (vengine == null) {
// create a new velocity engine
vengine = new VelocityEngine();
// --- try to get properties from an own file
String relPath = scontext.getInitParameter(Constants.SCTXT_VELOPROPS_PATH);
if (relPath != null) {
String velocityPropPath = scontext.getRealPath(relPath);
log.info("Initialising velocity from property file: " + velocityPropPath);
// load in properties
Properties props = new Properties();
try {
props.load( new java.io.FileInputStream(velocityPropPath) );
} catch (java.io.IOException ioe) {
throw new OntopiaRuntimeException(ioe);
}
// pre-cat the real directory
String path = props.getProperty(Velocity.FILE_RESOURCE_LOADER_PATH, null);
if (path != null) {
path = scontext.getRealPath(path);
props.setProperty(Velocity.FILE_RESOURCE_LOADER_PATH, path);
} else {
// no directory set, use default loader (classpath) for default templates
props.setProperty("resource.loader", "class");
props.setProperty("class.resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
props.setProperty("class.resource.loader.cache", "true");
}
try {
vengine.init(props);
} catch (Exception e) {
throw new OntopiaRuntimeException(e);
}
} else {
log.info("Initializing velocity with default properties.");
// use class resource loaders
Properties props = new Properties();
props.setProperty("resource.loader", "class");
props.setProperty("class.resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
props.setProperty("class.resource.loader.cache", "true");
// use log4j logging system
props.setProperty("runtime.log.system", "org.apache.velocity.runtime.log.SimpleLog4JLogSystem" );
props.setProperty("runtime.log.logsystem.log4j.category", "net.ontopia.velocity");
props.setProperty("runtime.log", "velocity.log");
try {
vengine.init(props);
} catch (Exception e) {
throw new OntopiaRuntimeException(e);
}
}
// add velocity engine to servlet context
scontext.setAttribute(Constants.SCTXT_VELOCITY_ENGINE, vengine);
}
return vengine;
}
public static void processWithVelocity(PageContext pageContext, String template_file,
Writer writer, VelocityContext vc) {
try {
VelocityEngine vengine = getVelocityEngine(pageContext.getServletContext());
vengine.mergeTemplate(template_file, org.apache.velocity.runtime.RuntimeSingleton.getString(Velocity.INPUT_ENCODING, Velocity.ENCODING_DEFAULT), vc, writer);
} catch (Exception e) {
throw new OntopiaRuntimeException(e);
}
}
/**
* Gets the action registry object from application scope.
*/
public static ActionRegistryIF getActionRegistry(PageContext pageContext)
throws JspTagException {
return getActionRegistry(pageContext.getRequest());
}
public static String getRequestId(PageContext pageContext) {
return (String)pageContext.getAttribute(FormTag.REQUEST_ID_ATTRIBUTE_NAME,
PageContext.REQUEST_SCOPE);
}
/**
* INTERNAL: Returns true if the form is read-only.
* @param request The current request object
* @return a boolean object indicating whether the form is read-only or not
*/
public static boolean isFormReadOnly(ServletRequest request) {
Boolean value = (Boolean) request.getAttribute(Constants.OKS_FORM_READONLY);
return value != null && value.booleanValue();
}
public static LockResult getReadOnlyLock(HttpServletRequest request) {
LockResult retVal = (LockResult)request.getAttribute(Constants.LOCK_RESULT);
if (retVal == null)
log.warn("TagUtils.getReadOnlyLock returning null.");
return retVal;
}
public static boolean isComponentReadOnly(ServletRequest request,
boolean componentIsReadOnly) {
return componentIsReadOnly || isFormReadOnly(request);
}
public static boolean isComponentReadOnly(PageContext pageContext,
String compReadOnlyAttr) {
ServletRequest request = pageContext.getRequest();
if (compReadOnlyAttr == null)
return isFormReadOnly(request);
else if (compReadOnlyAttr.equals("false"))
return false;
else if (isFormReadOnly(request))
return true;
else
return InteractionELSupport.getBooleanValue(compReadOnlyAttr, false,
pageContext);
}
public static FormTag getCurrentFormTag(ServletRequest request) {
return (FormTag) request.getAttribute("OKS_FORM");
}
public static void setCurrentFormTag(ServletRequest request, FormTag tag) {
request.setAttribute("OKS_FORM", tag);
}
// -- parameter serialization and deserialization
public static List serializeParameters(List parameters) {
return parameters;
}
public static List deserializeParameters(List parameters, TopicMapIF topicmap) {
// revitalize parameters
TMRevitalizerIF revitalizer = new TMRevitalizer(topicmap);
return (List)revitalizer.revitalize(parameters);
}
}