/*
* #!
* 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.taglibs.form;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.StringTokenizer;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspTagException;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.tagext.BodyContent;
import javax.servlet.jsp.tagext.BodyTagSupport;
import javax.servlet.jsp.tagext.TagSupport;
import net.ontopia.topicmaps.core.TopicMapIF;
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.FrameworkUtils;
import net.ontopia.topicmaps.webed.impl.basic.ActionDataSet;
import net.ontopia.topicmaps.webed.impl.basic.ActionForwardPageIF;
import net.ontopia.topicmaps.webed.impl.basic.ActionGroupIF;
import net.ontopia.topicmaps.webed.impl.basic.ActionRegistryIF;
import net.ontopia.topicmaps.webed.impl.basic.Constants;
import net.ontopia.topicmaps.webed.impl.utils.ActionData;
import net.ontopia.topicmaps.webed.impl.utils.LockResult;
import net.ontopia.topicmaps.webed.impl.utils.NamedLockManager;
import net.ontopia.topicmaps.webed.impl.utils.TagUtils;
import net.ontopia.utils.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.velocity.VelocityContext;
/**
* INTERNAL: Custom tag that represents an (HTML) input form holding different
* elements for modification of topic map object values.
*/
public class FormTag extends BodyTagSupport {
// initialization of logging facility
private static final String CATEGORY_NAME = FormTag.class.getName();
private static Logger logger = LoggerFactory.getLogger(CATEGORY_NAME);
/**
* The default location where the velocity template can be retrieved from.
*/
protected final static String TEMPLATE_FILE = "form.vm";
/**
* The attribute key under which the associated bean giving access to this
* form is stored.
*/
protected final static String NAME = Constants.FORM_EDIT_NAME;
/**
* The id of the tag. This value will be used as the value of an ID attribute
* in the generated output.
*/
protected String idattr;
protected String klass;
/**
* State of the form that specifies whether it is read-only or not.
*/
protected String readonly;
/**
* The action group this form uses, important for the contained input fields
* and buttons.
*/
protected String actiongroup;
/**
* (Optional) Name of variable which should be tried to lock, because of the
* modifications that may be involved in this form.
*/
protected String lockVarname;
protected Boolean nested;
/**
* If a action URI is specified directly the default process servlet ACTION is
* overwritten.
*
* @see net.ontopia.topicmaps.webed.impl.basic.Constants#PROCESS_SERVLET
*/
protected String action_uri;
/**
* If target is specified it will override the default TARGET.
*/
protected String target;
/**
* The name of the field to receive focus, if any.
*/
protected String focus; // currently unused (not set)
protected String enctype;
protected String requestId;
private Collection validationRules;
private boolean outputSubmitFunc = false;
public static final String REQUEST_ID_ATTRIBUTE_NAME = "FormTag."
+ Constants.RP_REQUEST_ID;
public int doStartTag() throws JspException {
NavigatorPageIF contextTag = FrameworkUtils.getContextTag(pageContext);
if (contextTag == null)
throw new JspTagException("<webed:form> must be nested"
+ " within a <tolog:context> tag, but no"
+ " <tolog:context> was found.");
if (TagSupport.findAncestorWithClass(this, FormTag.class) != null)
throw new JspTagException("<webed:form> cannot be nested");
HttpServletRequest request = (HttpServletRequest) pageContext.getRequest();
boolean readonly =
InteractionELSupport.getBooleanValue(this.readonly, false, pageContext);
request.setAttribute(Constants.OKS_FORM_READONLY, new Boolean(readonly));
// put the name of the action group to page scope
// to allow child tags to access this information
TagUtils.setActionGroup(pageContext, actiongroup);
TagUtils.setCurrentFormTag(request, this);
requestId = TagUtils.createRequestId();
// -- try to lock variable
UserIF user = FrameworkUtils.getUser(pageContext);
if (lockVarname != null) {
ActionRegistryIF registry = TagUtils.getActionRegistry(pageContext);
if (registry == null)
throw new JspException("No action registry! Check actions.xml for " +
"errors; see log for details.");
Collection lockColl = InteractionELSupport
.extendedGetValue(lockVarname, pageContext);
NamedLockManager lockMan = TagUtils
.getNamedLockManager(pageContext.getServletContext());
LockResult lockResult = lockMan.attemptToLock(user, lockColl,
lockVarname, pageContext.getSession());
lockVarname = lockResult.getName();
Collection unlockable = lockResult.getUnlockable();
request.setAttribute(Constants.LOCK_RESULT, lockResult);
if (!unlockable.isEmpty()) {
logger.warn("Unable to lock contents of variable '" + lockVarname
+ "'." + unlockable);
// forward to error page if variable is locked
ActionGroupIF ag = registry.getActionGroup(actiongroup);
ActionForwardPageIF forwardPage = ag.getLockedForwardPage();
if (forwardPage != null && forwardPage.getURL() != null) {
String fwd_url = forwardPage.getURL();
logger.info("Forward to lock error page: " + fwd_url);
try {
((HttpServletResponse) pageContext.getResponse())
.sendRedirect(fwd_url);
} catch (IOException ioe) {
logger.error("Problem occurred while forwarding: "
+ ioe.getMessage());
throw new JspException("I/O-Problem while forwarding to '"
+ fwd_url + "': " + ioe);
}
return SKIP_PAGE;
} else {
logger
.warn("No forward page found for lock situation. Setting form to be read-only");
request.setAttribute(Constants.OKS_FORM_READONLY, Boolean.TRUE);
}
} else {
logger.info("Locked contents of variable '" + lockVarname + "'.");
}
}
// register a new action data set
pageContext.setAttribute(FormTag.REQUEST_ID_ATTRIBUTE_NAME, requestId,
PageContext.REQUEST_SCOPE);
TagUtils.createActionDataSet(pageContext);
return EVAL_BODY_BUFFERED;
}
/**
* Renders the input form element with it's content.
*/
public int doAfterBody() throws JspException {
HttpServletRequest request = (HttpServletRequest) pageContext.getRequest();
TagUtils.setCurrentFormTag(request, null);
VelocityContext vc = TagUtils.getVelocityContext(pageContext);
// attributes of the form element
vc.put("name", NAME);
String id = idattr;
if (id == null)
id = requestId;
// Use requestId by default, to make sure there always is an id.
vc.put("idattr", id);
// -- class
if (klass != null) vc.put("class", klass);
validationRules = getFieldValidationRules();
if (!validationRules.isEmpty()) {
vc.put("performFieldValidation", Boolean.TRUE);
vc.put("validationRules", validationRules);
vc.put("onsubmit", "return validate('" + id + "');");
} else vc.put("onsubmit", "return true;");
vc.put("outputSubmitFunc", getOutputSubmitFunc());
// reset the outputSubmitFunc variable
setOutputSubmitFunc(false);
// -- action
String context_name = request.getContextPath();
String default_action = context_name + "/" + Constants.PROCESS_SERVLET;
vc.put("action", (action_uri != null) ? action_uri : default_action);
ActionRegistryIF registry = TagUtils.getActionRegistry(pageContext);
// -- target (only in the case of a multi framed web app)
if (registry == null)
throw new JspException(
"No action registry available! Check actions.xml for errors; see log for details.");
vc.put("target", target);
// -- enctype
if (enctype != null)
vc.put("enctype", enctype);
// -- nested
if (nested != null)
vc.put("nested", nested);
if (lockVarname != null)
vc.put(Constants.RP_LOCKVAR, lockVarname);
NavigatorPageIF contextTag = FrameworkUtils.getContextTag(pageContext);
// add hidden parameter value pairs to identify the request
String topicmap_id = request.getParameter(Constants.RP_TOPICMAP_ID);
if (topicmap_id == null && contextTag != null) {
// if not set try to retrieve it from the nav context
NavigatorApplicationIF navApp = contextTag.getNavigatorApplication();
TopicMapIF tm = contextTag.getTopicMap();
if (tm != null)
topicmap_id = navApp.getTopicMapRefId(tm);
}
vc.put(Constants.RP_TOPICMAP_ID, topicmap_id);
vc.put(Constants.RP_TOPIC_ID, request
.getParameter(Constants.RP_TOPIC_ID));
vc.put(Constants.RP_ASSOC_ID, request
.getParameter(Constants.RP_ASSOC_ID));
vc.put(Constants.RP_ACTIONGROUP, actiongroup);
vc.put(Constants.RP_REQUEST_ID, requestId);
// FIXME: Do we really need this line? Probably not, since each control
// should now itself be responisible for determining whether it should be
// readonly. Hence the individual control can overrule the form setting.
// vc.put("readonly", new Boolean(TagUtils.isFormReadOnly(request)));
// content inside the form element
BodyContent body = getBodyContent();
vc.put("content", body.getString());
// render JavaScript to set the input focus (if required)
if (focus != null) {
String focus_elem = focus;
StringBuilder focus_ref = new StringBuilder("[");
if (focus_elem.indexOf('[') > 0) {
StringTokenizer st = new StringTokenizer(focus_elem, "[");
if (st.countTokens() == 2) {
focus_elem = st.nextToken();
focus_ref.append(st.nextToken());
}
}
vc.put("focus_elem", focus_elem);
if (focus_ref.length() > 1)
vc.put("focus_ref", focus_ref.toString());
else
vc.put("focus_ref", "");
}
// all variables are now set, proceed with outputting
TagUtils.processWithVelocity(pageContext, TEMPLATE_FILE, getBodyContent().getEnclosingWriter(), vc);
// clear read-only state
request.removeAttribute(Constants.OKS_FORM_READONLY);
return SKIP_BODY;
}
private Boolean getOutputSubmitFunc() {
// Make sure we only output the functiononce , even if we have multiple forms
// within a page.
Boolean value = (Boolean) pageContext.getRequest().getAttribute("OKS_FORM_OUTPUT_SUBMIT_FUNC");
if (value == null && outputSubmitFunc) {
pageContext.getRequest().setAttribute("OKS_FORM_OUTPUT_SUBMIT_FUNC", Boolean.FALSE);
return Boolean.TRUE;
}
return Boolean.FALSE;
}
private Collection getFieldValidationRules() {
ArrayList rules = new ArrayList();
UserIF user = FrameworkUtils.getUser(pageContext);
String requestId = TagUtils.getRequestId(pageContext);
if (requestId != null) {
ActionDataSet ads = (ActionDataSet) user.getWorkingBundle(requestId);
for (Iterator iter = ads.getAllActionData().iterator(); iter.hasNext();) {
ActionData data = (ActionData) iter.next();
if (data.getMatchExpression() != null) {
rules.add(new ValidationRule(data));
}
}
}
return rules;
}
/**
* Releases any acquired resources.
*/
public void release() {
super.release();
idattr = null;
readonly = null;
actiongroup = null;
action_uri = null;
target = null;
lockVarname = null;
enctype = null;
outputSubmitFunc = false;
}
// ------------------------------------------------------------
// tag attribute accessors
// ------------------------------------------------------------
public void setId(String idattr) {
this.idattr = idattr;
}
public void setReadonly(String readonly) {
this.readonly = readonly;
}
/**
* Sets the class attribute of the tag. This value will be used as
* the value of the 'class' attribute in the generated output.
*/
public void setClass(String klass) {
this.klass = klass;
}
public void setActiongroup(String actiongroup) {
this.actiongroup = actiongroup;
}
public String getActiongroup() {
return actiongroup;
}
public void setActionURI(String action_uri) {
this.action_uri = action_uri;
}
public void setTarget(String target) {
this.target = target;
}
public void setLock(String lock_varname) {
this.lockVarname = lock_varname;
}
public void setNested(String nested) {
this.nested = Boolean.valueOf(nested);
}
public void setEnctype(String enctype) {
this.enctype = enctype;
}
protected void setOutputSubmitFunc(boolean b) {
outputSubmitFunc = b;
}
/**
* INTERNAL: Internal class encapsulating validation rules
*/
public static final class ValidationRule {
private String pattern;
private String fieldValue;
private String fieldName;
public ValidationRule(ActionData data) {
fieldName = data.getFieldName();
Object value = data.getValue().iterator().next();
if (value instanceof String)
fieldValue = (String) value;
else
fieldValue = null;
pattern = data.getMatchExpression();
}
public String getFieldName() {
return fieldName;
}
public String getFieldValue() {
return fieldValue;
}
public String getPattern() {
return pattern;
}
public String getEscapedPattern() {
return StringUtils.replace(StringUtils.replace(pattern, '\\', "\\\\"), '"',
"\\\"");
}
}
}