com.sun.faban.harness.webclient.XFormServlet.java Source code

Java tutorial

Introduction

Here is the source code for com.sun.faban.harness.webclient.XFormServlet.java

Source

/* 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;
        }
    }
}