/*
* 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: XForm.java 3009 2009-03-20 12:46:39Z drmlipp $
*
* $Log$
* Revision 1.2 2006/12/12 10:14:23 drmlipp
* New taglib for XForms display.
*
* Revision 1.1.2.8 2006/12/11 13:30:19 drmlipp
* Continued with result delivery.
*
* Revision 1.1.2.7 2006/12/10 21:53:03 mlipp
* Insert instance in first existing model.
*
* Revision 1.1.2.6 2006/12/08 22:01:57 mlipp
* Minor improvements and adapted to Chiba update.
*
* Revision 1.1.2.5 2006/12/08 15:23:12 drmlipp
* Added submit handling to XForms JSF component.
*
* Revision 1.1.2.4 2006/12/08 08:51:43 drmlipp
* Moved generation of xforms:instance node to XForm.
*
* Revision 1.1.2.3 2006/12/07 23:17:13 mlipp
* Restructured XForm component's properties.
*
* Revision 1.1.2.2 2006/11/28 13:08:42 drmlipp
* Started Ajax response handling.
*
* 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.6 2006/11/03 15:37:07 drmlipp
* Made component a naming container.
*
* Revision 1.1.2.5 2006/10/31 16:07:17 drmlipp
* Seeing the form...
*
* Revision 1.1.2.4 2006/10/30 09:41:33 drmlipp
* Added form attribute.
*
* 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:11 drmlipp
* Component for linking stylesheet added.
*
*/
package de.danet.an.util.jsf.xftaglib;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import javax.faces.FacesException;
import javax.faces.component.ActionSource;
import javax.faces.component.NamingContainer;
import javax.faces.component.UICommand;
import javax.faces.context.FacesContext;
import javax.faces.el.ValueBinding;
import javax.portlet.PortletSession;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionBindingEvent;
import javax.servlet.http.HttpSessionBindingListener;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
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 org.chiba.xml.xforms.ChibaBean;
import org.chiba.xml.xforms.config.Config;
import org.chiba.xml.xforms.config.XFormsConfigException;
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.xml.sax.InputSource;
import org.xml.sax.SAXException;
import de.danet.an.util.XMLUtil;
/**
* This class provides a component that causes a given XForm
* to be included in the page.
*
* @author Michael Lipp
*
*/
public class XForm extends UICommand
implements NamingContainer, ActionSource {
private static final org.apache.commons.logging.Log logger
= org.apache.commons.logging.LogFactory.getLog(XForm.class);
public static String COMPONENT_FAMILY = "de.danet.bts.jsf.taglib";
public static String COMPONENT_TYPE = "de.danet.bts.XForm";
private static String CHIBA_BEANS_ATTR_PREFIX = "CHIBA_BEAN_";
/** ChibaBeans are not serializable and may therefore not be put
* in the session context. We therefore store a key into this cache
* instead */
private static Map chibaBeansCache = null;
private Object form;
private InstanceDataModel value;
private String submitAction = null;
private Node updatedInstanceData = null;
/* (non-Javadoc)
* @see javax.faces.component.UIComponent#getFamily()
*/
public String getFamily() {
return COMPONENT_FAMILY;
}
public Object saveState(FacesContext context) {
Object[] values = new Object[3];
values[0] = super.saveState(context);
values[1] = form;
values[2] = value;
return ((Object) (values));
}
public void restoreState(FacesContext context, Object state) {
Object[] values = (Object[])state;
super.restoreState(context, values[0]);
form = values[1];
value = (InstanceDataModel)values[2];
}
public void setForm(Object value) {
form = (Object)value;
}
public Object getForm() {
if (form != null) {
return form;
}
ValueBinding vb = getValueBinding("form");
return vb != null ? (Object)vb.getValue(getFacesContext()) : null;
}
public void setValue(Object value) {
value = (InstanceDataModel)value;
}
public Object getValue() {
if (value != null) {
return value;
}
ValueBinding vb = getValueBinding("value");
return vb != null
? (InstanceDataModel)vb.getValue(getFacesContext()) : null;
}
/**
* This class provides a session binding listener that removes a
* ChibaBean from the global cache when it is no longer referenced
* from the session.
*
* @author Michael Lipp
*/
public static class ChibaBeanKeyHolder
implements HttpSessionBindingListener, Serializable {
private String key;
public ChibaBeanKeyHolder (String key) {
this.key = key;
}
/* (non-Javadoc)
* @see javax.servlet.http.HttpSessionBindingListener#valueBound
*/
public void valueBound(HttpSessionBindingEvent arg0) {
}
/* (non-Javadoc)
* @see javax.servlet.http.HttpSessionBindingListener#valueUnbound
*/
public void valueUnbound(HttpSessionBindingEvent arg0) {
ChibaBean chiba = (ChibaBean)chibaBeansCache.remove(key);
try {
chiba.shutdown();
} catch (XFormsException e) {
logger.debug(e.getMessage(), e);
}
}
}
public ChibaBean chiba() {
if (chibaBeansCache == null) {
chibaBeansCache = new HashMap ();
InputStream is
= XForm.class.getResourceAsStream("chiba-config.xml");
try {
Config.getInstance(is);
} catch (XFormsConfigException e) {
throw (IllegalStateException)
(new IllegalStateException (e.getMessage())).initCause(e);
}
}
String key = null;
Object sessionObject
= FacesContext.getCurrentInstance()
.getExternalContext().getSession(true);
if (sessionObject instanceof PortletSession) {
key = ((PortletSession)sessionObject).getId();
} else {
key = ((HttpSession)sessionObject).getId();
}
key += "/" + getId() + "/" + ((InstanceDataModel)getValue()).getId();
if (chibaBeansCache.containsKey(key)) {
return (ChibaBean)chibaBeansCache.get(key);
}
ChibaBean chiba = setupChibaBean();
chibaBeansCache.put(key, chiba);
if (sessionObject instanceof PortletSession) {
((PortletSession)sessionObject).setAttribute
(CHIBA_BEANS_ATTR_PREFIX + getId(),
new ChibaBeanKeyHolder (key));
} else if (sessionObject instanceof HttpSession) {
((HttpSession)sessionObject).setAttribute
(CHIBA_BEANS_ATTR_PREFIX + getId(),
new ChibaBeanKeyHolder (key));
}
return chiba;
}
/**
* Setup a new ChibaBean for the given form and instance data.
* @return
*/
private ChibaBean setupChibaBean() {
Element form = setupBasicForm();
insertInstanceData (form);
if (logger.isDebugEnabled()) {
try {
StringWriter sw = new StringWriter();
Transformer t;
t = TransformerFactory.newInstance().newTransformer();
t.transform(new DOMSource(form), new StreamResult(sw));
logger.debug ("Initializing Chiba with:\n" + sw.toString());
} catch (TransformerConfigurationException e) {
logger.debug(e.getMessage(), e);
} catch (TransformerFactoryConfigurationError e) {
logger.debug(e.getMessage(), e);
} catch (TransformerException e) {
logger.debug(e.getMessage(), e);
}
}
ChibaBean chiba = new ChibaBean ();
try {
chiba.setXMLContainer (form);
chiba.init();
} catch (XFormsException e) {
throw new FacesException(e.getMessage(), e);
}
return chiba;
}
/**
* Setup the basic form.
* @return the form propertiy if given as W3C DOM node, else the
* parsed form definition string.
*/
private Element setupBasicForm() {
Element form = null;
Object formProperty = getForm ();
if (formProperty instanceof Document) {
form = ((Document)formProperty).getDocumentElement();
} else if (formProperty instanceof Element) {
form = (Element)formProperty;
} else if (formProperty instanceof String) {
DocumentBuilder db;
try {
db = DocumentBuilderFactory.newInstance().newDocumentBuilder();
form = db.parse
(new InputSource(new StringReader((String)formProperty)))
.getDocumentElement();
} catch (ParserConfigurationException e) {
throw (IllegalStateException)
new IllegalStateException (e.getMessage()).initCause(e);
} catch (SAXException e) {
throw (IllegalArgumentException)
new IllegalArgumentException (e.getMessage()).initCause(e);
} catch (IOException e) {
throw (IllegalStateException)
new IllegalStateException (e.getMessage()).initCause(e);
}
} else {
throw new FacesException
("Form attribute must be of type String or W3C node.");
}
return form;
}
/**
* Insert the instance data into the basic form.
* @param form the basic form
*/
void insertInstanceData (Element form) {
// get xhtml:head element
NodeList headEls
= form.getElementsByTagNameNS(XMLUtil.XMLNS_XHTML, "head");
if (headEls.getLength() != 1) {
throw new IllegalArgumentException
("The form must include exactly one xhtml:head element.");
}
Element head = (Element)headEls.item(0);
// find or insert xforms:model
Element model = null;
NodeList modelEls
= head.getElementsByTagNameNS(XMLUtil.XMLNS_XFORMS, "model");
if (modelEls.getLength() > 0) {
model = (Element)modelEls.item(0);
} else {
model = form.getOwnerDocument()
.createElementNS(XMLUtil.XMLNS_XFORMS, "xforms:model");
head.appendChild(model);
}
// insert instance data in model
InstanceDataModel idm = (InstanceDataModel)getValue();
Element instanceData = (Element)
form.getOwnerDocument().importNode(idm.getInstanceData(), true);
Element instance = form.getOwnerDocument()
.createElementNS(XMLUtil.XMLNS_XFORMS, "instance");
instance.setAttributeNS(XMLUtil.XMLNS_NS, "xmlns", "");
instance.appendChild(instanceData);
model.insertBefore(instance, model.getFirstChild());
// add submission
Element submission = form.getOwnerDocument()
.createElementNS(XMLUtil.XMLNS_XFORMS, "submission");
submission.setAttribute("id", "action");
submission.setAttribute("action", "invoke:application");
submission.setAttribute("method", "");
submission.setAttribute("replace", "none");
model.insertBefore(submission, instance.getNextSibling());
// find existing bindings
NodeList bindEls = model
.getElementsByTagNameNS(XMLUtil.XMLNS_XFORMS, "bind");
Set knownBinds = new HashSet ();
for (int i = 0; i < bindEls.getLength(); i++) {
knownBinds.add (((Element)bindEls.item(i)).getAttribute("id"));
}
// add provided bindings with lower precedence
if (idm.getBindings() != null) {
for (Element child = (Element)idm.getBindings().getFirstChild();
child != null; child = (Element)child.getNextSibling()) {
String id = child.getAttribute("id");
if (!knownBinds.contains(id)) {
Node bind = form.getOwnerDocument().importNode(child, true);
model.insertBefore(bind, submission.getNextSibling());
}
}
}
}
/**
* @param updatedInstanceData The updatedInstanceData to set.
*/
public void setUpdatedInstanceData
(String submitAction, Node updatedInstanceData) {
this.submitAction = submitAction;
this.updatedInstanceData = updatedInstanceData;
}
/* (non-Javadoc)
* @see javax.faces.component.UIComponentBase#processUpdates
*/
public void processUpdates(FacesContext context) {
super.processUpdates(context);
((InstanceDataModel)getValue()).setSubmitAction(submitAction);
((InstanceDataModel)getValue()).setInstanceData(updatedInstanceData);
}
}