org.wapama.web.EditorHandler.java Source code

Java tutorial

Introduction

Here is the source code for org.wapama.web.EditorHandler.java

Source

/***************************************
 * Copyright (c) 2008-2010
 * Philipp Berger 2009
 * Intalio, Inc 2010
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
****************************************/

package org.wapama.web;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.WeakHashMap;

import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.Namespace;
import org.jdom.input.SAXBuilder;
import org.jdom.output.Format;
import org.jdom.output.XMLOutputter;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.mozilla.javascript.EvaluatorException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.wapama.web.plugin.IDiagramPlugin;
import org.wapama.web.plugin.IDiagramPluginService;
import org.wapama.web.plugin.impl.PluginServiceImpl;
import org.wapama.web.preference.IDiagramPreference;
import org.wapama.web.preference.IDiagramPreferenceService;
import org.wapama.web.profile.IDiagramProfile;
import org.wapama.web.profile.IDiagramProfileService;
import org.wapama.web.profile.impl.ProfileServiceImpl;

import com.yahoo.platform.yui.compressor.JavaScriptCompressor;

/**
 * Servlet to load plugin and Wapama stencilset
 */
public class EditorHandler extends HttpServlet {

    private static final long serialVersionUID = -7439613152623067053L;

    /**
     * da logger
     */
    private static final Logger _logger = LoggerFactory.getLogger(EditorHandler.class);

    public static IDiagramPreferenceService PREFERENCE_FACTORY = new IDiagramPreferenceService() {

        public IDiagramPreference createPreference(HttpServletRequest req) {
            //later we could read the parameters from the URL as well.
            return new IDiagramPreference() {

                public boolean isAutoSaveEnabled() {
                    return true;
                }

                public int getAutosaveInterval() {
                    return 120000;
                }
            };
        }
    };

    /**
     * The base path under which the application will be made available at runtime.
     * This constant should be used throughout the application.
     */
    public static final String wapama_path = "/designer/";

    /**
     * The designer DEV flag.
     * When set, the logging will be enabled at the javascript level
     */
    public static final String DEV = "designer.dev";

    /**
     * The profile service, a global registry to get the
     * profiles.
     */
    private IDiagramProfileService _profileService = null;

    /**
     * The plugin service, a global registry for all plugins.
     */
    private IDiagramPluginService _pluginService = null;

    private List<String> _envFiles = new ArrayList<String>();

    private Map<String, List<IDiagramPlugin>> _pluginfiles = new HashMap<String, List<IDiagramPlugin>>();

    private Map<String, List<IDiagramPlugin>> _uncompressedPlugins = new WeakHashMap<String, List<IDiagramPlugin>>();

    /**
     * editor.html document.
     */
    private Document _doc = null;

    public void init(ServletConfig config) throws ServletException {
        super.init(config);
        _profileService = ProfileServiceImpl.INSTANCE;
        _profileService.init(config.getServletContext());
        _pluginService = PluginServiceImpl.getInstance(config.getServletContext());

        String editor_file = config.getServletContext().getRealPath("/editor.html");
        try {
            _doc = readDocument(editor_file);
        } catch (Exception e) {
            throw new ServletException("Error while parsing editor.html", e);
        }
        if (_doc == null) {
            _logger.error("Invalid editor.html, " + "could not be read as a document.");
            throw new ServletException("Invalid editor.html, " + "could not be read as a document.");
        }

        Element root = _doc.getRootElement();
        Element head = root.getChild("head", root.getNamespace());
        if (head == null) {
            _logger.error("Invalid editor.html. No html or head tag");
            throw new ServletException("Invalid editor.html. " + "No html or head tag");
        }

        try {
            initEnvFiles(getServletContext());
        } catch (IOException e) {
            throw new ServletException(e);
        }
    }

    /**
     * Initiate the compression of the environment.
     * @param context
     * @throws IOException
     */
    private void initEnvFiles(ServletContext context) throws IOException {
        // only do it the first time the servlet starts
        try {
            JSONObject obj = new JSONObject(readEnvFiles(context));

            JSONArray array = obj.getJSONArray("files");
            for (int i = 0; i < array.length(); i++) {
                _envFiles.add(array.getString(i));
            }
        } catch (JSONException e) {
            _logger.error("invalid js_files.json");
            _logger.error(e.getMessage(), e);
            throw new RuntimeException("Error initializing the " + "environment of the editor");
        }

        // generate script to setup the languages
        //_envFiles.add("i18n/translation_ja.js");
        if (System.getProperty(DEV) == null) {
            StringWriter sw = new StringWriter();
            for (String file : _envFiles) {
                sw.append("/* ").append(file).append(" */\n");
                InputStream input = new FileInputStream(new File(getServletContext().getRealPath(file)));
                try {
                    JavaScriptCompressor compressor = new JavaScriptCompressor(new InputStreamReader(input), null);
                    compressor.compress(sw, -1, false, false, false, false);
                } catch (EvaluatorException e) {
                    _logger.error(e.getMessage(), e);
                } catch (IOException e) {
                    _logger.error(e.getMessage(), e);
                } finally {
                    try {
                        input.close();
                    } catch (IOException e) {
                    }
                }

                sw.append("\n");
            }
            try {
                FileWriter w = new FileWriter(context.getRealPath("jsc/env_combined.js"));
                w.write(sw.toString());
                w.close();
            } catch (IOException e) {
                _logger.error(e.getMessage(), e);
            }
        } else {
            if (_logger.isInfoEnabled()) {
                _logger.info("The diagram editor is running in development mode. "
                        + "Javascript will be served uncompressed");
            }
        }
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        Document doc = (Document) _doc.clone();
        String profileName = request.getParameter("profile");
        IDiagramProfile profile = _profileService.findProfile(request, profileName);
        if (profile == null) {
            _logger.error("No profile with the name " + profileName + " was registered");
            throw new IllegalArgumentException("No profile with the name " + profileName + " was registered");
        }

        //output env javascript files
        if (System.getProperty(DEV) != null) {
            for (String jsFile : _envFiles) {
                addScript(doc, wapama_path + jsFile, true);
            }
        } else {
            addScript(doc, wapama_path + "jsc/env_combined.js", true);
        }

        // get language from cookie
        String lang = null;
        Cookie[] cookies = request.getCookies();
        for (Cookie cookie : cookies) {
            if ("locale".equals(cookie.getName())) {
                lang = cookie.getValue().trim();
                break;
            }
        }

        // i18n message resource file name
        String i18nJsFile = null;

        if ("de_DE".equals(lang)) {
            i18nJsFile = "i18n/translation_de.js";
        } else if ("ru".equals(lang)) {
            i18nJsFile = "i18n/translation_ru.js";
        } else if ("es".equals(lang)) {
            i18nJsFile = "i18n/translation_es.js";
        } else if ("ja_JP".equals(lang)) {
            i18nJsFile = "i18n/translation_ja.js";
        } else {
            i18nJsFile = "i18n/translation_en_us.js";
        }
        // generate script to setup the languages
        addScript(doc, wapama_path + i18nJsFile, true);

        // generate script tags for plugins.
        // they are located after the initialization script.

        if (_pluginfiles.get(profileName) == null) {
            List<IDiagramPlugin> compressed = new ArrayList<IDiagramPlugin>();
            List<IDiagramPlugin> uncompressed = new ArrayList<IDiagramPlugin>();
            _pluginfiles.put(profileName, compressed);
            _uncompressedPlugins.put(profileName, uncompressed);
            for (String pluginName : profile.getPlugins()) {
                IDiagramPlugin plugin = _pluginService.findPlugin(request, pluginName);
                if (plugin == null) {
                    _logger.warn("Could not find the plugin " + pluginName + " requested by the profile "
                            + profile.getName());
                    continue;
                }
                if (plugin.isCompressable()) {
                    compressed.add(plugin);
                } else {
                    uncompressed.add(plugin);
                }
            }

            if (System.getProperty(DEV) == null) {
                // let's call the compression routine
                String rs = compressJS(_pluginfiles.get(profileName), getServletContext());
                try {
                    FileWriter w = new FileWriter(
                            getServletContext().getRealPath("jsc/plugins_" + profileName + ".js"));
                    w.write(rs.toString());
                    w.close();
                } catch (Exception e) {
                    _logger.error(e.getMessage(), e);
                }
            }
        }

        if (System.getProperty(DEV) != null) {
            for (IDiagramPlugin jsFile : _pluginfiles.get(profileName)) {
                addScript(doc, wapama_path + "plugin/" + jsFile.getName() + ".js", true);
            }
        } else {
            addScript(doc, wapama_path + "jsc/plugins_" + profileName + ".js", false);
        }

        for (IDiagramPlugin uncompressed : _uncompressedPlugins.get(profileName)) {
            addScript(doc, wapama_path + "plugin/" + uncompressed.getName() + ".js", false);
        }

        // send the updated editor.html to client 
        if (!isIE(request)) {
            response.setContentType("application/xhtml+xml");
        }
        XMLOutputter outputter = new XMLOutputter();
        Format format = Format.getPrettyFormat();
        format.setExpandEmptyElements(true);
        outputter.setFormat(format);
        String html = outputter.outputString(doc);
        StringTokenizer tokenizer = new StringTokenizer(html, "@", true);
        StringBuilder resultHtml = new StringBuilder();
        boolean tokenFound = false;
        boolean replacementMade = false;

        IDiagramPreference pref = PREFERENCE_FACTORY.createPreference(request);
        int autoSaveInt = pref.getAutosaveInterval();
        boolean autoSaveOn = pref.isAutoSaveEnabled();

        while (tokenizer.hasMoreTokens()) {
            String elt = tokenizer.nextToken();
            if ("title".equals(elt)) {
                resultHtml.append(profile.getTitle());
                replacementMade = true;
            } else if ("stencilset".equals(elt)) {
                resultHtml.append(profile.getStencilSet());
                replacementMade = true;
            } else if ("debug".equals(elt)) {
                resultHtml.append(System.getProperty(DEV) != null);
                replacementMade = true;
            } else if ("autosaveinterval".equals(elt)) {
                resultHtml.append(autoSaveInt);
                replacementMade = true;
            } else if ("autosavedefault".equals(elt)) {
                resultHtml.append(autoSaveOn);
                replacementMade = true;
            } else if ("profileplugins".equals(elt)) {
                StringBuilder plugins = new StringBuilder();
                boolean commaNeeded = false;
                for (String ext : profile.getPlugins()) {
                    if (commaNeeded) {
                        plugins.append(",");
                    } else {
                        commaNeeded = true;
                    }
                    plugins.append("\"").append(ext).append("\"");
                }
                resultHtml.append(plugins.toString());
                replacementMade = true;
            } else if ("ssextensions".equals(elt)) {
                StringBuilder ssexts = new StringBuilder();
                boolean commaNeeded = false;
                for (String ext : profile.getStencilSetExtensions()) {
                    if (commaNeeded) {
                        ssexts.append(",");
                    } else {
                        commaNeeded = true;
                    }
                    ssexts.append("\"").append(ext).append("\"");
                }
                resultHtml.append(ssexts.toString());
                replacementMade = true;
            } else if ("@".equals(elt)) {
                if (replacementMade) {
                    tokenFound = false;
                    replacementMade = false;
                } else {
                    tokenFound = true;
                }
            } else {
                if (tokenFound) {
                    tokenFound = false;
                    resultHtml.append("@");
                }
                resultHtml.append(elt);
            }
        }

        response.getWriter().write(resultHtml.toString());
    }

    /**
     * Reads the document from the file at the given path
     * @param path the path to the file
     * @return a document
     * @throws JDOMException
     * @throws IOException
     */
    private static Document readDocument(String path) throws JDOMException, IOException {
        SAXBuilder builder = new SAXBuilder(false);

        // no DTD validation
        builder.setValidation(false);
        builder.setFeature("http://xml.org/sax/features/validation", false);
        builder.setFeature("http://apache.org/xml/features/nonvalidating/load-dtd-grammar", false);
        builder.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);

        Document anotherDocument = builder.build(new File(path));
        return anotherDocument;
    }

    /**
     * Adds a script to the head.
     * @param doc the document to use.
     * @param src the location of the script
     */
    private void addScript(Document doc, String src, boolean isCore) {
        Namespace nm = doc.getRootElement().getNamespace();
        Element script = new Element("script", nm);
        // set the attributes
        script.setAttribute("src", src);
        script.setAttribute("type", "text/javascript");
        //add an empty text node in them
        script.addContent("");
        // put it to the right place
        Element head = doc.getRootElement().getChild("head", nm);

        if (isCore) {
            // then place it first.
            //insert before the last script tag.
            head.addContent(head.getContentSize() - 2, script);
        } else {
            head.addContent(script);
        }

        return;
    }

    /**
     * @return read the files to be placed as core scripts
     * from a configuration file in a json file.
     * @throws IOException 
     */
    private static String readEnvFiles(ServletContext context) throws IOException {
        FileInputStream core_scripts = new FileInputStream(context.getRealPath("/js/js_files.json"));
        try {
            ByteArrayOutputStream stream = new ByteArrayOutputStream();
            byte[] buffer = new byte[4096];
            int read;
            while ((read = core_scripts.read(buffer)) != -1) {
                stream.write(buffer, 0, read);
            }
            return stream.toString();
        } finally {
            try {
                core_scripts.close();
            } catch (IOException e) {
                _logger.error(e.getMessage(), e);
            }
        }
    }

    /**
     * Compress a list of js files into one combined string
     * @param a list of js files
     * @return a string that contains all the compressed data
     * @throws EvaluatorException
     * @throws IOException
     */
    private static String compressJS(Collection<IDiagramPlugin> plugins, ServletContext context) {
        StringWriter sw = new StringWriter();
        for (IDiagramPlugin plugin : plugins) {
            sw.append("/* ").append(plugin.getName()).append(" */\n");
            InputStream input = plugin.getContents();
            try {
                JavaScriptCompressor compressor = new JavaScriptCompressor(new InputStreamReader(input), null);
                compressor.compress(sw, -1, false, false, false, false);
            } catch (EvaluatorException e) {
                _logger.error(e.getMessage(), e);
            } catch (IOException e) {
                _logger.error(e.getMessage(), e);
            } finally {
                try {
                    input.close();
                } catch (IOException e) {
                }
            }

            sw.append("\n");
        }
        return sw.toString();
    }

    /**
     * Determine whether the browser is IE
     * @param request
     * @return true: IE browser false: others browsers
     */
    private static boolean isIE(HttpServletRequest request) {
        return request.getHeader("USER-AGENT").toLowerCase().indexOf("msie") > 0;
    }
}