Java tutorial
/* * Copyright (C) 2005-2012 Alfresco Software Limited. * * This file is part of Alfresco * * Alfresco is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Alfresco is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Alfresco. If not, see <http://www.gnu.org/licenses/>. */ package org.alfresco.web.forms.xforms; import java.io.Writer; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.ResourceBundle; import java.util.StringTokenizer; import java.util.TreeSet; import javax.faces.context.FacesContext; import org.springframework.extensions.config.ConfigElement; import org.springframework.extensions.config.ConfigService; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.web.app.Application; import org.alfresco.web.app.servlet.FacesHelper; import org.alfresco.web.bean.wcm.AVMBrowseBean; import org.alfresco.web.bean.wcm.AVMUtil; import org.alfresco.web.forms.Form; import org.alfresco.web.forms.FormProcessor; import org.alfresco.util.XMLUtil; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.chiba.xml.ns.NamespaceConstants; import org.chiba.xml.xforms.exception.XFormsException; import org.json.JSONException; import org.json.JSONObject; import org.springframework.web.util.JavaScriptUtils; import org.w3c.dom.Document; import org.w3c.dom.Element; public class XFormsProcessor implements FormProcessor { private static final Log LOGGER = LogFactory.getLog(XFormsProcessor.class); /** * A triple of js variable name, namespace uri, and namespace prefix which * will form javascript variables within alfresco.constants. */ private final static String[][] JS_NAMESPACES = { { "xforms", NamespaceConstants.XFORMS_NS, NamespaceConstants.XFORMS_PREFIX }, { "xhtml", NamespaceConstants.XHTML_NS, NamespaceConstants.XHTML_PREFIX }, { "chiba", NamespaceConstants.CHIBA_NS, NamespaceConstants.CHIBA_PREFIX }, { "alfresco", NamespaceService.ALFRESCO_URI, NamespaceService.ALFRESCO_PREFIX } }; /** Scripts needed to initialize the xforms client. */ private final static String[] JS_SCRIPTS = { "/scripts/tiny_mce/" + (LOGGER.isDebugEnabled() ? "tiny_mce_src.js" : "tiny_mce.js"), "/scripts/ajax/dojo/" + (LOGGER.isDebugEnabled() ? "dojo.js.uncompressed.js" : "dojo.js"), "/scripts/ajax/mootools.v1.11.js", "/scripts/ajax/common.js", "/scripts/ajax/ajax_helper.js", "/scripts/ajax/tiny_mce_wcm_extensions.js", "/scripts/ajax/xforms.js", "/scripts/ajax/file_picker_widget.js", "/scripts/upload_helper.js", "/scripts/ajax/dojo/src/date/serialize.js" }; /** Localized strings needed by the xforms client. */ private final static String[] BUNDLE_KEYS = { "add_content", "cancel", "change", "click_to_edit", "eg", "go_up", "idle", "loading", "path", "select", "upload", "validation_provide_values_for_required_fields", "time_picker_title", "time_picker_button_title", "date_picker_title", "date_picker_button_title", "date_time_picker_title", "date_time_picker_button_title", "increase_week_label", "decrease_week_label", "increase_month_label", "decrease_month_label", "mandatory_parameter", "state_expanded", "state_not_expanded", "select_path_menu", "add_item_title", "move_up_title", "move_down_title", "remove_item_title", "item_title", "accessibility_validation_message", "accessibility_validation_message_with_error_count" }; private static JSONObject widgetConfig = null; public XFormsProcessor() { if (XFormsProcessor.widgetConfig == null) { XFormsProcessor.widgetConfig = XFormsProcessor.loadConfig(); } } public Session process(final Document instanceDataDocument, final String formInstanceDataName, final Form form, final Writer out) throws FormProcessor.ProcessingException { final FacesContext fc = FacesContext.getCurrentInstance(); //make the XFormsBean available for this session final XFormsBean xforms = (XFormsBean) FacesHelper.getManagedBean(fc, XFormsBean.BEAN_NAME); final Session result = xforms.createSession(instanceDataDocument, formInstanceDataName, form); this.process(result, out); return result; } /** * Generates html text which bootstraps the JavaScript code that will * call back into the XFormsBean and get the xform and build the ui. */ public void process(final Session session, final Writer out) throws FormProcessor.ProcessingException { final FacesContext fc = FacesContext.getCurrentInstance(); //make the XFormsBean available for this session final XFormsBean xforms = (XFormsBean) FacesHelper.getManagedBean(fc, XFormsBean.BEAN_NAME); final AVMBrowseBean avmBrowseBean = (AVMBrowseBean) FacesHelper.getManagedBean(fc, AVMBrowseBean.BEAN_NAME); try { xforms.setXFormsSession((XFormsBean.XFormsSession) session); } catch (FormBuilderException fbe) { LOGGER.error(fbe); throw new ProcessingException(fbe); } catch (XFormsException xfe) { LOGGER.error(xfe); throw new ProcessingException(xfe); } final String contextPath = fc.getExternalContext().getRequestContextPath(); final Document result = XMLUtil.newDocument(); final String xformsUIDivId = "alfresco-xforms-ui"; // this div is where the ui will write to final Element div = result.createElement("div"); div.setAttribute("id", xformsUIDivId); result.appendChild(div); Element e = result.createElement("link"); e.setAttribute("rel", "stylesheet"); e.setAttribute("type", "text/css"); e.setAttribute("href", contextPath + "/css/xforms.css"); div.appendChild(e); // a script with config information and globals. e = result.createElement("script"); e.setAttribute("type", "text/javascript"); final StringBuilder js = new StringBuilder("\n"); final String[] jsNamespacesObjects = { "alfresco", "alfresco.constants", "alfresco.xforms", "alfresco.xforms.constants" }; for (final String jsNamespace : jsNamespacesObjects) { js.append(jsNamespace).append(" = typeof ").append(jsNamespace).append(" == 'undefined' ? {} : ") .append(jsNamespace).append(";\n"); } js.append("alfresco.constants.DEBUG = ").append(LOGGER.isDebugEnabled()).append(";\n"); js.append("alfresco.constants.WEBAPP_CONTEXT = '").append(JavaScriptUtils.javaScriptEscape(contextPath)) .append("';\n"); String avmWebApp = avmBrowseBean.getWebapp(); // TODO - need better way to determine WCM vs ECM context js.append("alfresco.constants.AVM_WEBAPP_CONTEXT = '"); if (avmWebApp != null) { js.append(JavaScriptUtils.javaScriptEscape(avmWebApp)); } js.append("';\n"); // TODO - need better way to determine WCM vs ECM context js.append("alfresco.constants.AVM_WEBAPP_URL = '"); if (avmWebApp != null) { //Use preview store because when user upload image it appears in preview, not in main store. String storeName = AVMUtil.getCorrespondingPreviewStoreName(avmBrowseBean.getSandbox()); if (storeName != null) { js.append(JavaScriptUtils.javaScriptEscape( fc.getExternalContext().getRequestContextPath() + "/wcs/api/path/content/avm/" + AVMUtil.buildStoreWebappPath(storeName, avmWebApp).replace(":", ""))); } } js.append("';\n"); js.append("alfresco.constants.AVM_WEBAPP_PREFIX = '"); if (avmWebApp != null) { String storeName = AVMUtil.getCorrespondingPreviewStoreName(avmBrowseBean.getSandbox()); if (storeName != null) { js.append(JavaScriptUtils.javaScriptEscape(fc.getExternalContext().getRequestContextPath() + "/wcs/api/path/content/avm/" + AVMUtil.buildSandboxRootPath(storeName).replace(":", ""))); } } js.append("';\n"); js.append("alfresco.constants.LANGUAGE = '"); String lang = Application.getLanguage(FacesContext.getCurrentInstance()).toString(); // the language can be passed as "en_US" or just "en" if (lang.length() > 4) lang = lang.substring(0, lang.indexOf("_")); js.append(lang); js.append("';\n"); js.append("alfresco.xforms.constants.XFORMS_UI_DIV_ID = '").append(xformsUIDivId).append("';\n"); js.append("alfresco.xforms.constants.FORM_INSTANCE_DATA_NAME = '") .append(JavaScriptUtils.javaScriptEscape(session.getFormInstanceDataName())).append("';\n"); SimpleDateFormat sdf = (SimpleDateFormat) SimpleDateFormat.getDateInstance(DateFormat.SHORT, Application.getLanguage(fc)); js.append("alfresco.xforms.constants.DATE_FORMAT = '").append(sdf.toPattern()).append("';\n"); sdf = (SimpleDateFormat) SimpleDateFormat.getTimeInstance(DateFormat.SHORT, Application.getLanguage(fc)); js.append("alfresco.xforms.constants.TIME_FORMAT = '").append(sdf.toPattern()).append("';\n"); sdf = (SimpleDateFormat) SimpleDateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, Application.getLanguage(fc)); js.append("alfresco.xforms.constants.DATE_TIME_FORMAT = '").append(sdf.toPattern()).append("';\n"); for (String[] ns : JS_NAMESPACES) { js.append("alfresco.xforms.constants.").append(ns[0].toUpperCase()).append("_NS = '").append(ns[1]) .append("';\n"); js.append("alfresco.xforms.constants.").append(ns[0].toUpperCase()).append("_PREFIX = '").append(ns[2]) .append("';\n"); } final ResourceBundle bundle = Application.getBundle(FacesContext.getCurrentInstance()); js.append("alfresco.resources = {\n"); for (String k : BUNDLE_KEYS) { js.append(k).append(": '").append(JavaScriptUtils.javaScriptEscape(bundle.getString(k))).append("'") .append(k.equals(BUNDLE_KEYS[BUNDLE_KEYS.length - 1]) ? "\n};" : ",").append("\n"); } try { js.append("alfresco.xforms.widgetConfig = \n") .append(LOGGER.isDebugEnabled() ? XFormsProcessor.widgetConfig.toString(0) : XFormsProcessor.widgetConfig) .append("\n"); } catch (JSONException jsone) { LOGGER.error(jsone); } e.appendChild(result.createTextNode(js.toString())); div.appendChild(e); // include all our scripts, order is significant for (final String script : JS_SCRIPTS) { if (script == null) { continue; } e = result.createElement("script"); e.setAttribute("type", "text/javascript"); e.setAttribute("src", contextPath + script); e.appendChild(result.createTextNode("\n")); div.appendChild(e); } // output any custom scripts ConfigElement config = Application.getConfigService(fc).getGlobalConfig().getConfigElement("wcm"); if (config != null) { // get the custom scripts to include ConfigElement xformsScriptsConfig = config.getChild("xforms-scripts"); if (xformsScriptsConfig != null) { StringTokenizer t = new StringTokenizer(xformsScriptsConfig.getValue().trim(), ", "); while (t.hasMoreTokens()) { e = result.createElement("script"); e.setAttribute("type", "text/javascript"); e.setAttribute("src", contextPath + t.nextToken()); e.appendChild(result.createTextNode("\n")); div.appendChild(e); } } } XMLUtil.print(result, out); } private static JSONObject loadConfig() { final ConfigService cfgService = Application.getConfigService(FacesContext.getCurrentInstance()); final ConfigElement xformsConfig = cfgService.getGlobalConfig().getConfigElement("wcm").getChild("xforms"); final List<ConfigElement> widgetConfig = xformsConfig.getChildren("widget"); class WidgetConfigElement implements Comparable<WidgetConfigElement> { public final String xformsType; public final String xmlSchemaType; public final String appearance; public final String javascriptClassName; private final Map<String, String> params = new HashMap<String, String>(); public WidgetConfigElement(final String xformsType, final String xmlSchemaType, final String appearance, final String javascriptClassName) { if (xformsType == null) { throw new NullPointerException(); } this.xformsType = xformsType; this.xmlSchemaType = xmlSchemaType; this.appearance = appearance; this.javascriptClassName = javascriptClassName; } public void addParam(final String k, final String v) { this.params.put(k, v); } public Map<String, String> getParams() { return (this.params == null ? (Map<String, String>) Collections.EMPTY_MAP : Collections.unmodifiableMap(this.params)); } public int compareTo(final WidgetConfigElement other) { int result = this.xformsType.compareTo(other.xformsType); if (result != 0) { return result; } result = this.compareAttribute(this.xmlSchemaType, other.xmlSchemaType); if (result != 0) { return result; } result = this.compareAttribute(this.appearance, other.appearance); if (result != 0) { return result; } if (LOGGER.isInfoEnabled()) { LOGGER.info("widget definitions " + this + " and " + other + " may collide"); } return 0; } public String toString() { return (this.getClass().getName() + "{" + "xformsType: " + this.xformsType + ", xmlSchemaType: " + this.xmlSchemaType + ", appearance: " + this.appearance + ", javascriptClassName: " + this.javascriptClassName + ", numParams: " + this.getParams().size() + "}"); } private int compareAttribute(final String s1, final String s2) { return (s1 != null && s2 == null ? 1 : (s1 == null && s2 != null ? -1 : (s1 != null && s2 != null ? s1.compareTo(s2) : 0))); } } final TreeSet<WidgetConfigElement> widgetConfigs = new TreeSet<WidgetConfigElement>(); for (final ConfigElement ce : widgetConfig) { final WidgetConfigElement wce = new WidgetConfigElement(ce.getAttribute("xforms-type"), ce.getAttribute("xml-schema-type"), ce.getAttribute("appearance"), ce.getAttribute("javascript-class-name")); final List<ConfigElement> params = ce.getChildren("param"); for (final ConfigElement p : params) { wce.addParam(p.getAttribute("name"), p.getValue()); } widgetConfigs.add(wce); } try { final JSONObject result = new JSONObject(); for (final WidgetConfigElement wce : widgetConfigs) { if (!result.has(wce.xformsType)) { result.put(wce.xformsType, new JSONObject()); } final JSONObject xformsTypeObject = result.getJSONObject(wce.xformsType); String s = wce.xmlSchemaType == null ? "*" : wce.xmlSchemaType; if (!xformsTypeObject.has(s)) { xformsTypeObject.put(s, new JSONObject()); } final JSONObject schemaTypeObject = xformsTypeObject.getJSONObject(s); s = wce.appearance == null ? "*" : wce.appearance; final JSONObject o = new JSONObject(); schemaTypeObject.put(s, o); o.put("className", wce.javascriptClassName); if (wce.getParams().size() != 0) { o.put("params", new JSONObject(wce.getParams())); } } return result; } catch (JSONException jsone) { LOGGER.error(jsone, jsone); return null; } } }