/* The contents of this file are subject to the terms
* of the Common Development and Distribution License
* (the License). You may not use this file except in
* compliance with the License.
*
* You can obtain a copy of the License at
* http://www.sun.com/cddl/cddl.html or
* install_dir/legal/LICENSE
* See the License for the specific language governing
* permission and limitations under the License.
*
* When distributing Covered Code, include this CDDL
* Header Notice in each file and include the License file
* at install_dir/legal/LICENSE.
* If applicable, add the following below the CDDL Header,
* with the fields enclosed by brackets [] replaced by
* your own identifying information:
* "Portions Copyrighted [year] [name of copyright owner]"
*
* $Id$
*
* Copyright 2005-2009 Sun Microsystems Inc. All Rights Reserved
*/
package com.sun.faban.harness.webclient;
import org.apache.commons.fileupload.DiskFileUpload;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUpload;
import org.apache.commons.fileupload.FileUploadException;
import org.chiba.adapter.AbstractChibaAdapter;
import org.chiba.adapter.ChibaAdapter;
import org.chiba.adapter.InteractionHandler;
import org.chiba.tools.xslt.StylesheetLoader;
import org.chiba.tools.xslt.UIGenerator;
import org.chiba.tools.xslt.XSLTGenerator;
import org.chiba.xml.xforms.config.Config;
import org.chiba.xml.xforms.events.EventFactory;
import org.chiba.xml.xforms.exception.XFormsException;
import org.chiba.xml.xforms.ui.Repeat;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.*;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLEncoder;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* The XFormServlet is the glue code between Faban and Chiba XForms.
* It serves get requests to initially access the XForm and post requests for
* subsequent interactions.
*/
public class XFormServlet extends HttpServlet {
private static Logger logger = Logger.getLogger(
"com.sun.faban.harness.webclient.XFormServlet");
private String configFile;
private String ctxRoot;
private String uploadDir;
private String xsltDir;
private String errPage;
ServletContext ctx;
/**
* Initializes the servlet.
*
* @throws javax.servlet.ServletException
*/
public void init() throws ServletException {
ctx = getServletContext();
ctxRoot = ctx.getRealPath("");
if (ctxRoot == null)
ctxRoot = ctx.getRealPath(".");
ServletConfig cfg = getServletConfig();
// Location of the config file.
String path = cfg.getInitParameter("configFile");
if (path != null)
configFile = ctx.getRealPath(path);
// Directory to store uploaded files, default to java.io.tmpDir
uploadDir = cfg.getInitParameter("uploadDir");
if (uploadDir == null)
uploadDir = System.getProperty("java.io.tmpdir");
// Make sure nobody uploads to WEB-INF - security breach!
if (uploadDir != null && uploadDir.equalsIgnoreCase("WEB-INF")) {
throw new ServletException("Cannot write directory " + uploadDir);
}
// Directory containing xslt stylesheets
path = cfg.getInitParameter("xsltDir");
if (path != null)
xsltDir = ctx.getRealPath(path);
errPage = cfg.getInitParameter("errorPage");
}
/**
* A get request starts a new form.
*
* @param request The servlet request
* @param response The servlet response
* @throws ServletException Error in request handling
* @throws IOException Error doing other IO
*/
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
HttpSession session = request.getSession(true);
Adapter adapter = null;
String templateFile = (String)
session.getAttribute("faban.submit.template");
String styleSheet = (String)
session.getAttribute("faban.submit.stylesheet");
String srcURL = new File(templateFile).toURI().toString();
logger.finer("benchmark.template: " + srcURL);
session.removeAttribute("faban.submit.template");
session.removeAttribute("faban.submit.stylesheet");
try {
String requestURI = request.getRequestURI();
String formURI = null;
String contextPath = request.getContextPath();
String benchPath = contextPath + "/bm_submit/";
if (requestURI.startsWith(benchPath)) {
int idx = requestURI.indexOf('/', benchPath.length());
String benchName = requestURI.substring(benchPath.length(),
idx);
String formName = requestURI.substring(idx + 1);
formURI = com.sun.faban.harness.common.Config.FABAN_HOME +
"benchmarks/" + benchName + "/META-INF/" + formName;
} else {
StringBuffer buffer = new StringBuffer(request.getScheme());
buffer.append("://");
buffer.append(request.getServerName());
buffer.append(":");
buffer.append(request.getServerPort()) ;
buffer.append(request.getContextPath());
buffer.append(request.getParameter("form"));
formURI = buffer.toString();
}
if (formURI == null) {
throw new IOException("Resource not found: " + formURI);
}
logger.finer("Form URI: " + formURI);
String css = request.getParameter("css");
String actionURL = response.encodeURL(request.getRequestURI());
logger.finer("actionURL: " + actionURL);
// Find the base URL used by Faban. We do not use Config.FABAN_URL
// because this base URL can vary by the interface name the Faban
// master is accessed in this session. Otherwise it is identical.
StringBuffer baseURL = request.getRequestURL();
int uriLength = baseURL.length() - requestURI.length() +
contextPath.length();
baseURL.setLength(++uriLength); // Add the ending slash
adapter = new Adapter();
if (configFile != null && configFile.length() > 0)
adapter.setConfigPath(configFile);
File xsl = null;
if (styleSheet != null)
xsl = new File(styleSheet);
if (xsl != null && xsl.exists()) {
adapter.xslPath = xsl.getParent();
adapter.stylesheet = xsl.getName();
} else {
adapter.xslPath = xsltDir;
adapter.stylesheet = "faban.xsl";
}
adapter.baseURI = baseURL.toString();
adapter.formURI = formURI;
adapter.actionURL = actionURL;
adapter.beanCtx.put("chiba.web.uploadDir", uploadDir);
adapter.beanCtx.put("chiba.useragent", request.getHeader(
"User-Agent"));
adapter.beanCtx.put("chiba.web.request", request);
adapter.beanCtx.put("chiba.web.session", session);
adapter.beanCtx.put("benchmark.template", srcURL);
if (css != null) {
adapter.CSSFile = css;
logger.fine("using css stylesheet: " + css);
}
Map servletMap = new HashMap();
servletMap.put(ChibaAdapter.SESSION_ID, session.getId());
adapter.beanCtx.put(ChibaAdapter.SUBMISSION_RESPONSE, servletMap);
Enumeration params = request.getParameterNames();
while (params.hasMoreElements()) {
String s = (String) params.nextElement();
//store all request-params we don't use in the beanCtx map
if (!(s.equals("form") || s.equals("xslt") ||
s.equals("css") || s.equals("action_url"))) {
String value = request.getParameter(s);
adapter.beanCtx.put(s, value);
logger.finer("added request param '" + s + "' to beanCtx");
}
}
adapter.init();
adapter.execute();
response.setContentType("text/html");
PrintWriter out = response.getWriter();
adapter.generator.setOutput(out);
adapter.buildUI();
session.setAttribute("chiba.adapter", adapter);
out.close();
} catch (Exception e) {
logger.log(Level.SEVERE, "Exception processing XForms", e);
shutdown(adapter, session, e, request, response);
}
}
/**
* A post request deals with form interactions.
*
* @param request The servlet request
* @param response The servlet response
* @throws ServletException Error in request handling
* @throws IOException Error doing other IO
*/
public void doPost(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
HttpSession session = request.getSession(true);
Adapter adapter = null;
try {
adapter = (Adapter) session.getAttribute("chiba.adapter");
if (adapter == null) {
throw new ServletException(Config.getInstance().getErrorMessage(
"session-invalid"));
}
adapter.beanCtx.put("chiba.useragent", request.getHeader(
"User-Agent"));
adapter.beanCtx.put("chiba.web.request", request);
adapter.beanCtx.put("chiba.web.session", session);
//adapter.executeHandler();
adapter.execute();
// Check for redirects
String redirectURI = (String) adapter.beanCtx.get(
ChibaAdapter.LOAD_URI);
if (redirectURI != null) {
String redirectTo = redirectURI;
adapter.shutdown();
response.sendRedirect(response.encodeRedirectURL(redirectTo));
adapter.beanCtx.put(ChibaAdapter.LOAD_URI, null);
return;
}
// Check for forwards
Map forwardMap = (Map) adapter.beanCtx.get(
ChibaAdapter.SUBMISSION_RESPONSE);
InputStream forwardStream = (InputStream) forwardMap.get(
ChibaAdapter.SUBMISSION_RESPONSE_STREAM);
if (forwardStream != null) {
adapter.shutdown();
// fetch response stream
InputStream responseStream = (InputStream) forwardMap.remove(
ChibaAdapter.SUBMISSION_RESPONSE_STREAM);
// copy header info
Iterator iterator = forwardMap.keySet().iterator();
while (iterator.hasNext()) {
String name = iterator.next().toString();
String value = forwardMap.get(name).toString();
response.setHeader(name, value);
}
// copy stream content
byte[] copyBuffer = new byte[8092];
OutputStream out = response.getOutputStream();
int readLength = responseStream.read(copyBuffer);
do {
out.write(copyBuffer, 0, readLength);
readLength = responseStream.read(copyBuffer);
} while (readLength >= 0);
responseStream.close();
out.close();
// remove forward response and terminate
adapter.beanCtx.put(ChibaAdapter.SUBMISSION_RESPONSE, null);
return;
}
// Neither redirects nor forwards, handle it the normal way.
response.setContentType("text/html");
Writer writer = response.getWriter();
adapter.generator.setOutput(writer);
adapter.buildUI();
writer.close();
} catch (Exception e) {
logger.log(Level.SEVERE, "Exception processing XForms", e);
shutdown(adapter, session, e, request, response);
}
}
private void shutdown(Adapter adapter, HttpSession session, Exception e,
HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
// attempt to shutdown processor
if (adapter != null) {
try {
adapter.shutdown();
} catch (XFormsException xe) {
logger.log(Level.WARNING,
"Error shutting down Chiba bean.", xe);
}
}
// store exception
session.setAttribute("chiba.exception", e);
// redirect to error page, if set
if (errPage != null)
response.sendRedirect(response.encodeRedirectURL(
request.getContextPath() + "/" + errPage));
else if (e.getMessage().startsWith(
"could not create document container"))
// This specific message is so misleading, so we'll change it
// to what it really means.
throw new ServletException("XForms xml parsing error. " +
"Please check the XForm for xml errors.", e);
else
throw new ServletException(e);
}
static class Adapter extends AbstractChibaAdapter
implements InteractionHandler {
private HashMap beanCtx = null;
private UIGenerator generator = null;
private String xslPath = null;
private String baseURI = null;
private String formURI = null;
private String actionURL = null;
private String CSSFile = null;
private String stylesheet = null;
private String dataPrefix;
private String selectorPrefix;
private String triggerPrefix;
private String removeUploadPrefix;
private String uploadRoot;
/**
* Creates a new Adapter object.
*/
public Adapter() {
chibaBean = createProcessor();
beanCtx = new HashMap();
chibaBean.setContext(beanCtx);
}
/**
* Initializes the per-session adaptor.
*
* @throws XFormsException An error occurred
*/
public void init() throws XFormsException {
if (formURI != null) {
try {
// A local file can be /... or c:\... but it is
// certainly under the Faban directory.
if (formURI.startsWith(
com.sun.faban.harness.common.Config.FABAN_HOME)) {
FileInputStream stream = new FileInputStream(formURI);
setXForms(stream);
} else {
setXForms(new URI(formURI));
}
} catch (URISyntaxException e) {
throw new XFormsException("URI not well-formed",e);
} catch (FileNotFoundException e) {
throw new XFormsException("File " + formURI +
" not found.", e);
}
chibaBean.setBaseURI(baseURI);
}
if (logger.isLoggable(Level.FINER)) {
logger.finer(toString());
logger.finer("Form URI: " + formURI);
logger.finer("CSS-File: " + CSSFile);
logger.finer("XSLT stylesheet: " + stylesheet);
logger.finer("action URL: " + actionURL);
}
chibaBean.init();
StylesheetLoader stylesLoader = new StylesheetLoader(xslPath);
if (stylesheet != null)
stylesLoader.setStylesheetFile(stylesheet);
if (generator == null)
generator = new XSLTGenerator(stylesLoader);
generator.setParameter("action-url", actionURL);
generator.setParameter("debug-enabled", String.valueOf(
logger.isLoggable(Level.FINE)));
String selectorPrefix1 = Config.getInstance().getProperty(
"chiba.web.selectorPrefix", "s_");
generator.setParameter("selector-prefix", selectorPrefix1);
String removeUploadPrefix1 = Config.getInstance().getProperty(
"chiba.web.removeUploadPrefix", "ru_");
generator.setParameter("remove-upload-prefix", removeUploadPrefix1);
if (CSSFile != null) {
generator.setParameter("css-file", CSSFile);
}
}
/**
* Shuts down the xforms processor.
*
* @throws org.chiba.xml.xforms.exception.XFormsException
*
*/
public void shutdown() throws XFormsException {
if (chibaBean != null)
chibaBean.shutdown();
}
/**
* Handles the request.
*
* @throws XFormsException
*/
public void execute() throws XFormsException {
HttpServletRequest request = (HttpServletRequest) beanCtx.get(
"chiba.web.request");
String contextRoot = request.getSession().getServletContext().
getRealPath("");
if (contextRoot == null) {
contextRoot = request.getSession().getServletContext().
getRealPath(".");
}
String uploadDir = (String) beanCtx.get("chiba.web.uploadDir");
uploadRoot = new File(contextRoot, uploadDir).getAbsolutePath();
String trigger = null;
// Check that we have a file upload request
boolean isMultipart = FileUpload.isMultipartContent(request);
if (logger.isLoggable(Level.FINE)) {
logger.finer("request isMultipart: " + isMultipart);
logger.finer("base URI: " + chibaBean.getBaseURI());
logger.finer("user agent: " + request.getHeader("User-Agent"));
}
if (isMultipart) {
trigger = processMultiPartRequest(request, trigger);
} else {
trigger = processUrlencodedRequest(request, trigger);
}
// finally activate trigger if any
if (trigger != null) {
if (logger.isLoggable(Level.FINE)) {
logger.finer("trigger '" + trigger + "'");
}
chibaBean.dispatch(trigger, EventFactory.DOM_ACTIVATE);
}
}
void buildUI() throws XFormsException {
Config cfg = Config.getInstance();
String dataPrefix = cfg.getProperty("chiba.web.dataPrefix");
String triggerPrefix = cfg.getProperty("chiba.web.triggerPrefix");
String userAgent = (String) beanCtx.get("chiba.useragent");
generator.setParameter("data-prefix", dataPrefix);
generator.setParameter("trigger-prefix", triggerPrefix);
generator.setParameter("user-agent", userAgent);
if (CSSFile != null) {
generator.setParameter("css-file", CSSFile);
}
if (logger.isLoggable(Level.FINE)) {
logger.fine(">>> setting UI generator params...");
logger.fine("data-prefix=" + dataPrefix);
logger.fine("trigger-prefix=" + triggerPrefix);
logger.fine("user-agent=" + userAgent);
if (CSSFile != null) {
logger.fine("css-file=" + CSSFile);
}
logger.fine(">>> setting UI generator params...end");
}
generator.setInputNode(chibaBean.getXMLContainer());
generator.generate();
}
private String processMultiPartRequest(HttpServletRequest request,
String trigger)
throws XFormsException {
DiskFileUpload upload = new DiskFileUpload();
String encoding = request.getCharacterEncoding();
if (encoding == null) {
encoding = "ISO-8859-1";
}
upload.setRepositoryPath(uploadRoot);
if (logger.isLoggable(Level.FINE)) {
logger.fine("root dir for uploads: " + uploadRoot);
}
List items;
try {
items = upload.parseRequest(request);
} catch (FileUploadException e) {
throw new XFormsException(e);
}
Map formFields = new HashMap();
Iterator iter = items.iterator();
while (iter.hasNext()) {
FileItem item = (FileItem) iter.next();
String itemName = item.getName();
String fieldName = item.getFieldName();
String id = fieldName.substring(Config.getInstance().
getProperty("chiba.web.dataPrefix").length());
if (logger.isLoggable(Level.FINE)) {
logger.fine("Multipart item name is: " + itemName
+ " and fieldname is: " + fieldName
+ " and id is: " + id);
logger.fine("Is formfield: " + item.isFormField());
logger.fine("Content: " + item.getString());
}
if (item.isFormField()) {
if (removeUploadPrefix == null) {
try {
removeUploadPrefix = Config.getInstance().
getProperty("chiba.web.removeUploadPrefix",
"ru_");
} catch (Exception e) {
removeUploadPrefix = "ru_";
}
}
if (fieldName.startsWith(removeUploadPrefix)) {
id = fieldName.substring(removeUploadPrefix.length());
chibaBean.updateControlValue(id, "", "", null);
continue;
}
// It's a field name, not a file. Do the same
// as processUrlencodedRequest
String values[] = (String[]) formFields.get(fieldName);
String formFieldValue = null;
try {
formFieldValue = item.getString(encoding);
} catch (UnsupportedEncodingException e1) {
throw new XFormsException(e1.getMessage(), e1);
}
if (values == null) {
formFields.put(fieldName, new String[]{formFieldValue});
} else {
String[] tmp = new String[values.length + 1];
System.arraycopy(values, 0, tmp, 0, values.length);
tmp[values.length] = formFieldValue;
formFields.put(fieldName, tmp);
}
} else {
String uniqueFilename = new File("file" + Integer.
toHexString((int) (Math.random() * 10000)),
new File(itemName).getName()).getPath();
File savedFile = new File(uploadRoot, uniqueFilename);
byte[] data = null;
if (item.getSize() > 0)
if (chibaBean.storesExternalData(id)) {
try {
savedFile.getParentFile().mkdir();
item.write(savedFile);
} catch (Exception e) {
throw new XFormsException(e);
}
try {
data = savedFile.toURI().toString().
getBytes(encoding);
} catch (UnsupportedEncodingException e) {
throw new XFormsException(e);
}
} else {
data = item.get();
}
chibaBean.updateControlValue(id, item.getContentType(),
itemName, data);
}
// handle regular fields
if (formFields.size() > 0)
for (Iterator entries = formFields.entrySet().iterator();
entries.hasNext();) {
Map.Entry entry = (Map.Entry) entries.next();
fieldName = (String) entry.getKey();
String[] values = (String[]) entry.getValue();
handleData(fieldName, values);
handleSelector(fieldName, values[0]);
trigger = handleTrigger(trigger, fieldName);
}
}
return trigger;
}
private String processUrlencodedRequest(HttpServletRequest request,
String trigger)
throws XFormsException {
Map paramMap = request.getParameterMap();
for (Iterator entries = paramMap.entrySet().iterator();
entries.hasNext();) {
Map.Entry entry = (Map.Entry) entries.next();
String paramName = (String) entry.getKey();
String[] values = (String[]) entry.getValue();
if (logger.isLoggable(Level.FINER)) {
logger.finer(this + " parameter-name: " + paramName);
for (int i = 0; i < values.length; i++) {
logger.fine(this + " value: " + values[i]);
}
}
handleData(paramName, values);
handleSelector(paramName, values[0]);
trigger = handleTrigger(trigger, paramName);
}
return trigger;
}
private void handleData(String name, String[] values)
throws XFormsException {
if (name.startsWith(getDataPrefix())) {
String id = name.substring(getDataPrefix().length());
// assemble new control value
String newValue;
if (values.length > 1) {
StringBuffer buffer = new StringBuffer(values[0]);
for (int i = 1; i < values.length; i++) {
buffer.append(" ").append(values[i]);
}
newValue = trim( buffer.toString() );
} else {
newValue = trim( values[0] );
}
chibaBean.updateControlValue(id, newValue);
}
}
private String trim(String value) {
if (value != null && value.length() > 0) {
value = value.replaceAll("\r\n", "\r");
value = value.trim();
}
return value;
}
private void handleSelector(String name, String value)
throws XFormsException {
if (name.startsWith(getSelectorPrefix())) {
int separator = value.lastIndexOf(':');
String id = value.substring(0, separator);
int index = Integer.valueOf(value.substring(separator + 1)).
intValue();
Repeat repeat = (Repeat) chibaBean.lookup(id);
repeat.setIndex(index);
}
}
private String handleTrigger(String trigger, String name) {
if ((trigger == null) && name.startsWith(getTriggerPrefix())) {
String parameter = name;
int x = parameter.lastIndexOf(".x");
int y = parameter.lastIndexOf(".y");
if (x > -1) {
parameter = parameter.substring(0, x);
}
if (y > -1) {
parameter = parameter.substring(0, y);
}
// keep trigger id
trigger = name.substring(getTriggerPrefix().length());
}
return trigger;
}
private final String getTriggerPrefix() {
if (triggerPrefix == null) {
try {
triggerPrefix = Config.getInstance().getProperty(
"chiba.web.triggerPrefix", "t_");
} catch (Exception e) {
triggerPrefix = "t_";
}
}
return triggerPrefix;
}
private final String getDataPrefix() {
if (dataPrefix == null) {
try {
dataPrefix = Config.getInstance().getProperty(
"chiba.web.dataPrefix", "d_");
} catch (Exception e) {
dataPrefix = "d_";
}
}
return dataPrefix;
}
private final String getSelectorPrefix() {
if (selectorPrefix == null) {
try {
selectorPrefix = Config.getInstance().getProperty(
"chiba.web.selectorPrefix",
"s_");
} catch (Exception e) {
selectorPrefix = "s_";
}
}
return selectorPrefix;
}
}
}