/*
* This file is part of the WfMOpen project.
* Copyright (C) 2001-2005 Danet GmbH (www.danet.de), BU BTS.
* All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* $Id: XFormRenderer.java 3009 2009-03-20 12:46:39Z drmlipp $
*
* $Log$
* Revision 1.7 2006/12/19 22:38:36 mlipp
* Fixed newline handling.
*
* Revision 1.6 2006/12/19 15:23:46 drmlipp
* Implemented multiple selects.
*
* Revision 1.5 2006/12/15 15:58:02 drmlipp
* Continued.
*
* Revision 1.4 2006/12/13 12:49:33 drmlipp
* Simplified.
*
* Revision 1.3 2006/12/12 14:22:08 drmlipp
* Merged XForms client from branch.
*
* Revision 1.2 2006/12/12 10:14:23 drmlipp
* New taglib for XForms display.
*
* Revision 1.1.2.24 2006/12/11 13:30:19 drmlipp
* Continued with result delivery.
*
* Revision 1.1.2.23 2006/12/08 22:01:57 mlipp
* Minor improvements and adapted to Chiba update.
*
* Revision 1.1.2.22 2006/12/08 15:23:12 drmlipp
* Added submit handling to XForms JSF component.
*
* Revision 1.1.2.21 2006/12/02 22:23:35 mlipp
* Improved.
*
* Revision 1.1.2.20 2006/12/01 16:52:07 drmlipp
* Continued.
*
* Revision 1.1.2.19 2006/11/30 22:02:44 mlipp
* Continued.
*
* Revision 1.1.2.18 2006/11/29 22:03:37 mlipp
* Continued.
*
* Revision 1.1.2.17 2006/11/28 16:44:31 drmlipp
* Started JS based rendering.
*
* Revision 1.1.2.16 2006/11/28 13:08:42 drmlipp
* Started Ajax response handling.
*
* Revision 1.1.2.15 2006/11/27 16:57:33 drmlipp
* Outputting event.
*
* Revision 1.1.2.14 2006/11/27 14:28:33 drmlipp
* Continuing.
*
* Revision 1.1.2.13 2006/11/26 21:16:15 mlipp
* Getting Ajax call back to component.
*
* Revision 1.1.2.12 2006/11/25 22:45:56 mlipp
* Got request through to JSF.
*
* Revision 1.1.2.11 2006/11/24 16:15:42 drmlipp
* Continuing Ajax support.
*
* Revision 1.1.2.10 2006/11/23 23:00:53 mlipp
* Started AJAX handling.
*
* Revision 1.1.2.9 2006/11/16 21:23:21 mlipp
* Internationalized calendar dialog.
*
* Revision 1.1.2.8 2006/11/13 22:50:09 mlipp
* Continuing date/time handling.
*
* Revision 1.1.2.7 2006/11/10 22:29:48 mlipp
* Continuing date handling.
*
* Revision 1.1.2.6 2006/11/10 15:26:29 drmlipp
* Removed unnecessary statement.
*
* Revision 1.1.2.5 2006/11/09 23:16:15 mlipp
* Started response processing.
*
* Revision 1.1.2.4 2006/11/08 12:47:02 drmlipp
* Started using stylesheet.
*
* Revision 1.1.2.3 2006/11/07 16:33:01 drmlipp
* Continuing...
*
* Revision 1.1.2.2 2006/11/06 22:40:22 mlipp
* Continuing...
*
* Revision 1.1.2.1 2006/11/06 16:51:09 drmlipp
* Separated JSF taglib from util jar and XForm taglib from util taglib.
*
* Revision 1.1.2.7 2006/11/05 22:12:06 mlipp
* Added calendar library.
*
* Revision 1.1.2.6 2006/11/03 15:37:07 drmlipp
* Made component a naming container.
*
* Revision 1.1.2.5 2006/11/02 22:07:17 mlipp
* Added client id generation.
*
* Revision 1.1.2.4 2006/11/01 22:28:19 mlipp
* Continuing.
*
* Revision 1.1.2.3 2006/10/28 21:56:12 mlipp
* Output via ChibaBean.
*
* Revision 1.1.2.2 2006/10/27 15:25:53 drmlipp
* Continuing.
*
* Revision 1.1.2.1 2006/10/26 21:01:01 mlipp
* Started XForm tag.
*
* Revision 1.2 2006/09/29 12:32:09 drmlipp
* Consistently using WfMOpen as projct name now.
*
* Revision 1.1 2006/09/14 13:34:12 drmlipp
* Component for linking stylesheet added.
*
*/
package de.danet.an.util.jsf.xftaglib;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.ResourceBundle;
import javax.faces.application.FacesMessage;
import javax.faces.component.NamingContainer;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.faces.event.ActionEvent;
import javax.faces.render.Renderer;
import javax.faces.webapp.FacesServlet;
import javax.portlet.RenderResponse;
import javax.servlet.http.HttpServletResponse;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Templates;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.TransformerFactoryConfigurationError;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import org.apache.myfaces.renderkit.html.util.AddResource;
import org.apache.myfaces.renderkit.html.util.AddResourceFactory;
import org.apache.myfaces.shared_tomahawk.renderkit.html.HTML;
import org.apache.myfaces.shared_tomahawk.renderkit.html.util.JavascriptUtils;
import org.chiba.xml.events.ChibaEventNames;
import org.chiba.xml.events.DOMEventNames;
import org.chiba.xml.events.XFormsEventNames;
import org.chiba.xml.xforms.ChibaBean;
import org.chiba.xml.xforms.exception.XFormsException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.events.Event;
import org.w3c.dom.events.EventListener;
import org.w3c.dom.events.EventTarget;
import de.danet.an.util.XMLUtil;
import de.danet.an.util.jsf.JSFUtil;
import de.danet.an.util.jsf.MyFacesAjaxServlet;
import de.danet.an.util.jsf.taglib.JSLoading;
import de.danet.an.util.jsf.taglib.MyFacesResourceHandler;
/**
* This class provides a renderer for the HtmlLinkCssStylesheet component.
*
* @author Michael Lipp
*/
public class XFormRenderer extends Renderer {
private static final org.apache.commons.logging.Log logger
= org.apache.commons.logging.LogFactory.getLog(XFormRenderer.class);
protected static String L10N_MSGS
= XFormRenderer.class.getPackage().getName() + ".L10n";
private static final String DATA_PREFIX = "d_";
private static final String TRIGGER_PREFIX = "t_";
private static final String REMOVE_UPLOAD_PREFIX = "ru_";
private static Templates defTemplatesCache = null;
private static Map monthsNamesCache = new HashMap();
private static Map abbrevMonthsNamesCache = new HashMap ();
private Templates defTemplates () {
if (defTemplatesCache == null) {
try {
InputStream is = XFormRenderer.class
.getResourceAsStream("initialFormData.xsl");
defTemplatesCache = TransformerFactory.newInstance()
.newTemplates(new StreamSource(is));
} catch (TransformerConfigurationException e) {
logger.error (e.getMessage(), e);
} catch (TransformerFactoryConfigurationError e) {
logger.error (e.getMessage(), e);
}
}
return defTemplatesCache;
}
private String[] monthsNames(Locale locale) {
String[] res = (String[])monthsNamesCache.get(locale);
if (res != null) {
return res;
}
initMonthsNames(locale);
return (String[])monthsNamesCache.get(locale);
}
private String[] abbrevMonthsNames(Locale locale) {
String[] res = (String[])abbrevMonthsNamesCache.get(locale);
if (res != null) {
return res;
}
initMonthsNames(locale);
return (String[])abbrevMonthsNamesCache.get(locale);
}
private void initMonthsNames(Locale locale) {
Calendar cal = Calendar.getInstance(locale);
cal.set(Calendar.DATE, 1);
int months = cal.getActualMaximum(Calendar.MONTH)
- cal.getActualMinimum(Calendar.MONTH) + 1;
int minMonth = cal.getActualMinimum(Calendar.MONTH);
String[] monthsNames = new String[months];
String[] abbrevMonthsNames = new String[months];
SimpleDateFormat dfl = new SimpleDateFormat("MMMMM", locale);
SimpleDateFormat dfs = new SimpleDateFormat("MMM", locale);
for (int i = 0; i < months; i++) {
cal.set(Calendar.MONTH, minMonth + i);
monthsNames[i] = dfl.format(cal.getTime());
abbrevMonthsNames[i] = dfs.format(cal.getTime());
}
monthsNamesCache.put(locale, monthsNames);
abbrevMonthsNamesCache.put(locale, abbrevMonthsNames);
}
private String join (String[] strings, String quote, String sep) {
if (quote == null) {
quote = "";
}
StringBuffer res = new StringBuffer ();
for (int i = 0; i < strings.length; i++) {
if (i > 0) {
res.append(sep);
}
res.append(quote);
res.append(strings[i]);
res.append(quote);
}
return res.toString();
}
/* (non-Javadoc)
* @see javax.faces.render.Renderer#encodeBegin
*/
public void encodeBegin(FacesContext context, UIComponent comp)
throws IOException {
super.encodeBegin(context, comp);
ResponseWriter writer = context.getResponseWriter();
writer.startElement(HTML.DIV_ELEM, null);
writer.writeAttribute(HTML.ID_ATTR, comp.getClientId(context), null);
writer.writeAttribute(HTML.CLASS_ATTR, "portlet-font jxcp", null);
writer.endElement(HTML.DIV_ELEM);
}
/* (non-Javadoc)
* @see javax.faces.render.Renderer#encodeEnd(javax.faces.context.FacesContext, javax.faces.component.UIComponent)
*/
public void encodeEnd(FacesContext context, UIComponent component)
throws IOException {
super.encodeEnd(context, component);
XForm comp = (XForm)component;
ChibaBean chiba = comp.chiba();
if (logger.isDebugEnabled()) {
try {
logger.debug ("Chiba document:\n" + XMLUtil.w3cDomToString
(chiba.getXMLContainer().getDocumentElement()));
} catch (XFormsException e) {
logger.debug (e.getMessage());
}
}
Object response = context.getExternalContext().getResponse();
if (response instanceof RenderResponse) {
try {
Element form = chiba.getXMLContainer().getDocumentElement();
NodeList titles
= form.getElementsByTagNameNS(XMLUtil.XMLNS_XHTML, "title");
if (titles.getLength() > 0) {
String title = XMLUtil
.getFirstLevelTextContent((Element)titles.item(0));
((RenderResponse)response).setTitle(title);
}
} catch (XFormsException e) {
logger.debug (e.getMessage());
}
}
// Ensure availability of JS
JSLoading.ensureInclusion(context, JSLoading.UTIL);
JSLoading.ensureInclusion(context, JSLoading.ANCHOR_POSITION);
JSLoading.ensureInclusion(context, JSLoading.DATE);
JSLoading.ensureInclusion(context, JSLoading.POPUP_WINDOW);
JSLoading.ensureInclusion(context, JSLoading.CALENDAR_POPUP);
AddResource addResource = AddResourceFactory.getInstance(context);
if (!context.getExternalContext().getRequestMap()
.containsKey(XFormRenderer.class.getName() + "_SCRIPTS")) {
addResource.addJavaScriptAtPosition
(context, AddResource.HEADER_BEGIN,
new MyFacesResourceHandler(XFormRenderer.class, "helpers.js"));
addResource.addJavaScriptAtPosition
(context, AddResource.HEADER_BEGIN,
new MyFacesResourceHandler
(XFormRenderer.class, "xforms2html.js"));
context.getExternalContext().getRequestMap()
.put(XFormRenderer.class.getName() + "_SCRIPTS", Boolean.TRUE);
}
// Add styles
addResource.addStyleSheet
(context, AddResource.HEADER_BEGIN,
new MyFacesResourceHandler(XFormRenderer.class, "xforms.css"));
// Write individual JS
ResponseWriter writer = context.getResponseWriter();
writer.startElement(HTML.SCRIPT_ELEM, null);
writer.writeAttribute
(HTML.SCRIPT_TYPE_ATTR, HTML.SCRIPT_TYPE_TEXT_JAVASCRIPT, null);
String jsPrefix = JavascriptUtils
.getValidJavascriptName(comp.getClientId(context),false);
writer.write("window." + jsPrefix + "baseData={\n");
writer.write("jsPrefix: \"" + jsPrefix + "\",\n");
writer.write("elementIdPrefix: \""
+ comp.getClientId(context)
+ NamingContainer.SEPARATOR_CHAR + "\",\n");
String urlPrefix = addResource.getResourceUri
(context, new MyFacesResourceHandler(JSLoading.class, ""));
writer.write("resourcePrefix: \"" + urlPrefix + "\",\n");
writer.write("dataPrefix: \"" + DATA_PREFIX + "\",\n");
writer.write("triggerPrefix: \"" + TRIGGER_PREFIX + "\",\n");
writer.write("removeUploadPrefix: \"" + REMOVE_UPLOAD_PREFIX + "\",\n");
Locale locale = context.getViewRoot().getLocale();
ResourceBundle l10n = ResourceBundle.getBundle
("de.danet.an.util.jsf.xftaglib.L10n",
locale, XFormRenderer.class.getClassLoader());
writer.write("dateFormat: \"" + l10n.getString("dateFormat") + "\",\n");
writer.write
("dateTimeFormat: \"" + l10n.getString("dateTimeFormat") + "\",\n");
writer.write("monthsNames: new Array("
+ join(monthsNames(locale), "\"", ",") + "),\n");
writer.write("abbrevMonthsNames: new Array("
+ join(abbrevMonthsNames(locale), "\"", ",") + "),\n");
writer.write("todayLabel: \"" + l10n.getString("todayLabel") + "\",\n");
String path
= MyFacesAjaxServlet.getAjaxActionUrl(context, component);
if (path.indexOf("?") < 0) {
path = path + "?";
} else {
path = path + "&";
}
writer.write("ajaxBaseUrl: \"" + path + "\"\n");
writer.write("};\n");
String guiData = null;
try {
Transformer t;
t = defTemplates().newTransformer();
StringWriter sw = new StringWriter();
t.transform(new DOMSource(chiba.getXMLContainer()),
new StreamResult(sw));
guiData = sw.toString();
} catch (TransformerConfigurationException e) {
logger.error (e.getMessage(), e);
} catch (TransformerFactoryConfigurationError e) {
logger.error (e.getMessage(), e);
} catch (TransformerException e) {
logger.error (e.getMessage(), e);
} catch (XFormsException e) {
logger.error (e.getMessage(), e);
}
guiData = guiData.replaceAll("\\r", "");
guiData = guiData.replaceAll("\\\\", "\\\\\\\\");
guiData = guiData.replaceAll("\"", "\\\\\"");
guiData = guiData.replaceAll("\\n", "\\\\n");
writer.write
("(new JxcpXFormBuilder(window." + jsPrefix + "baseData))"
+ ".buildUI(document.getElementById(\""
+ comp.getClientId(context) + "\"), \"" + guiData + "\");");
writer.endElement(HTML.SCRIPT_ELEM);
}
/* (non-Javadoc)
* @see javax.faces.render.Renderer#decode
*/
public void decode(FacesContext context, UIComponent component) {
super.decode(context, component);
String clientId = component.getClientId(context);
ChibaBean chiba = ((XForm)component).chiba();
Map paramMap = context.getExternalContext().getRequestParameterMap();
String ajaxCallTo
= (String)paramMap.get(MyFacesAjaxServlet.AJAX_CALL_TO);
if (ajaxCallTo != null && ajaxCallTo.equals(clientId)) {
handleAjaxRequest (context, component, paramMap);
return;
}
String clientBaseId = clientId + NamingContainer.SEPARATOR_CHAR;
String trigger = null;
Map paramValuesMap = context
.getExternalContext().getRequestParameterValuesMap();
try {
for (Iterator i = paramValuesMap.entrySet().iterator();
i.hasNext();) {
Map.Entry entry = (Map.Entry)i.next();
String key = (String)entry.getKey();
if (!key.startsWith(clientBaseId)) {
continue;
}
String controlId = key.substring(clientBaseId.length());
if (controlId.startsWith(DATA_PREFIX)) {
String[] values = (String[])entry.getValue();
String value = values[0];
for (int j = 1; j < values.length; j++) {
value += " " + values[j];
}
chiba.updateControlValue
(controlId.substring(DATA_PREFIX.length()), value);
continue;
}
if (controlId.startsWith(TRIGGER_PREFIX)) {
trigger = controlId.substring(TRIGGER_PREFIX.length());
continue;
}
}
if (trigger != null) {
handleTrigger(component, chiba, trigger);
}
} catch (XFormsException e) {
logger.warn(e.getMessage(), e);
}
}
/**
* Used to get result from InvokeSubmissionHandler.
*/
static ThreadLocal submitAction = new ThreadLocal();
static ThreadLocal submitResult = new ThreadLocal();
/**
* @param component
* @param chiba
* @param trigger
* @throws XFormsException
*/
private void handleTrigger
(UIComponent component, ChibaBean chiba, String trigger)
throws XFormsException {
final List events = new ArrayList ();
EventListener listener = new EventListener () {
public void handleEvent(Event evt) {
if (logger.isDebugEnabled()) {
logger.debug
("Event of type \"" + evt.getType()
+ "\" targeted at:\n"
+ XMLUtil.w3cDomToString((Element)evt.getTarget()));
}
events.add(evt);
}
};
EventTarget doc = null;
try {
doc = (EventTarget)
chiba.getXMLContainer().getDocumentElement();
doc.addEventListener
(XFormsEventNames.SUBMIT_ERROR, listener, true);
submitAction.set(null);
submitResult.set(null);
chiba.dispatch(trigger, DOMEventNames.ACTIVATE);
} finally {
if (doc != null) {
doc.removeEventListener
(XFormsEventNames.SUBMIT_ERROR, listener, true);
}
}
if (events.size() > 0) {
JSFUtil.addMessage
(FacesMessage.SEVERITY_ERROR, L10N_MSGS, "submitError", null);
}
if (submitResult.get() != null) {
((XForm)component).queueEvent(new ActionEvent(component));
((XForm)component).setUpdatedInstanceData
((String)submitAction.get(), (Node)submitResult.get());
submitAction.set(null);
submitResult.set(null);
}
}
private void handleAjaxRequest
(FacesContext context, UIComponent component, Map paramMap) {
ChibaBean chiba = ((XForm)component).chiba();
String controlId = (String)paramMap.get("controlId");
String value = (String)paramMap.get("value");
final List events = new ArrayList ();
EventListener listener = new EventListener () {
public void handleEvent(Event evt) {
if (logger.isDebugEnabled()) {
logger.debug
("Event of type \"" + evt.getType()
+ "\" targeted at:\n"
+ XMLUtil.w3cDomToString((Element)evt.getTarget()));
}
events.add(evt);
}
};
EventTarget doc = null;
try {
doc = (EventTarget)
chiba.getXMLContainer().getDocumentElement();
doc.addEventListener
(ChibaEventNames.STATE_CHANGED, listener, true);
doc.addEventListener
(ChibaEventNames.PROTOTYPE_CLONED, listener, true);
doc.addEventListener
(ChibaEventNames.ID_GENERATED, listener, true);
doc.addEventListener
(ChibaEventNames.ITEM_INSERTED, listener, true);
doc.addEventListener
(ChibaEventNames.ITEM_DELETED, listener, true);
doc.addEventListener
(ChibaEventNames.INDEX_CHANGED, listener, true);
doc.addEventListener
(ChibaEventNames.SWITCH_TOGGLED, listener, true);
chiba.updateControlValue (controlId, value);
} catch (XFormsException e) {
logger.warn(e.getMessage(), e);
} finally {
if (doc != null) {
doc.removeEventListener
(ChibaEventNames.STATE_CHANGED, listener, true);
doc.removeEventListener
(ChibaEventNames.PROTOTYPE_CLONED, listener, true);
doc.removeEventListener
(ChibaEventNames.ID_GENERATED, listener, true);
doc.removeEventListener
(ChibaEventNames.ITEM_INSERTED, listener, true);
doc.removeEventListener
(ChibaEventNames.ITEM_DELETED, listener, true);
doc.removeEventListener
(ChibaEventNames.INDEX_CHANGED, listener, true);
doc.removeEventListener
(ChibaEventNames.SWITCH_TOGGLED, listener, true);
}
}
HttpServletResponse response
= (HttpServletResponse)context.getExternalContext().getResponse();
response.setContentType("text/xml; charset=UTF-8");
try {
Document resDoc = DocumentBuilderFactory.newInstance()
.newDocumentBuilder().newDocument();
Element eventsNode = resDoc.createElement("events");
resDoc.appendChild(eventsNode);
for (Iterator i = events.iterator(); i.hasNext();) {
Event evt = (Event)i.next();
Element eventNode = resDoc.createElement("event");
eventNode.setAttribute("type", evt.getType());
if (evt.getTarget() instanceof Node) {
eventNode.appendChild
(resDoc.importNode((Node)evt.getTarget(), true));
}
eventsNode.appendChild(eventNode);
}
// Transform to text
Transformer t = TransformerFactory.newInstance().newTransformer();
StringWriter sw = new StringWriter();
t.transform(new DOMSource(resDoc), new StreamResult(sw));
// Send as response
response.getWriter().write(sw.toString());
response.getWriter().close ();
} catch (IOException e) {
logger.error (e.getMessage(), e);
} catch (ParserConfigurationException e) {
logger.error (e.getMessage(), e);
} catch (TransformerException e) {
logger.error (e.getMessage(), e);
}
context.responseComplete();
}
}