Java tutorial
/* * Copyright (c) 2012. betterFORM Project - http://www.betterform.de * Licensed under the terms of BSD License */ package de.betterform.xml.xforms.model.bind; import de.betterform.xml.dom.DOMUtil; import de.betterform.xml.events.DefaultAction; import de.betterform.xml.events.XFormsEventNames; import de.betterform.xml.events.XMLEvent; import de.betterform.xml.ns.NamespaceConstants; import de.betterform.xml.xforms.Initializer; import de.betterform.xml.xforms.XFormsConstants; import de.betterform.xml.xforms.XFormsElement; import de.betterform.xml.xforms.exception.XFormsBindingException; import de.betterform.xml.xforms.exception.XFormsComputeException; import de.betterform.xml.xforms.exception.XFormsException; import de.betterform.xml.xforms.model.Instance; import de.betterform.xml.xforms.model.Model; import de.betterform.xml.xforms.model.ModelItem; import de.betterform.xml.xpath.XPathReferenceFinder; import de.betterform.xml.xpath.impl.saxon.XPathCache; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.w3c.dom.events.Event; import java.util.*; /** * Implementation of XForms Model Bind Element. * * @version $Id: Bind.java 3479 2008-08-19 10:44:53Z joern $ */ public class Bind extends XFormsElement implements Binding, DefaultAction { private static Log LOGGER = LogFactory.getLog(Bind.class); private String locationPath; private String instanceId; private String type; private String readonly; private String required; private String relevant; private String calculate; private String constraint; private List constraints; private String p3ptype; private Map<String, String> customMIPs; private XPathReferenceFinder referenceFinder; private Set readonlyReferences; private Set requiredReferences; private Set relevantReferences; private Set calculateReferences; private Set constraintReferences; private HashMap<String, Set> customMIPReferences; protected List nodeset; private static final short TYPE = 0; private static final short READONLY = 1; private static final short REQUIRED = 2; private static final short RELEVANT = 3; private static final short CONSTRAINT = 4; private static final short CALCULATE = 5; private static final String COMBINE_NOT_SUPPORTED = null; private static final String COMBINE_ALL = "and"; private static final String COMBINE_ONE = "or"; private static final String TYPE_COMBINE = COMBINE_ALL; private static final String CONSTRAINT_COMBINE = COMBINE_ALL; private static final String RELEVANT_COMBINE = COMBINE_ALL; private static final String REQUIRED_COMBINE = COMBINE_ONE; private static final String READONLY_COMBINE = COMBINE_ONE; /** * Creates a new Bind object. * * @param element the DOM Element annotated by this object * @param model the parent Model object */ public Bind(Element element, Model model) { super(element, model); this.constraints = new ArrayList(); // register with model getModel().addBindElement(this); } /** * Updates the childEvaluationContext * * @throws XFormsException in case an XPathException happens during evaluation */ public void updateXPathContext() throws XFormsException { List inscopeContext = evalInScopeContext(); // if(LOGGER.isDebugEnabled()){ // NodeWrapper info = (NodeWrapper) parentNodeset.get(0); // LOGGER.debug(this + " bound to Node:" + info.getUnderlyingNode()); // LOGGER.debug("in scope xpath context for " + this + " = " + // BindingResolver.getExpressionPath(this,repeatItemId)); // } final String relativeExpr = getBindingExpression(); try { if (relativeExpr != null) { if (this.nodeset == null) { this.nodeset = new ArrayList(); } else { // When rebuild is called we should clear the list before adding all the nodes again. this.nodeset.clear(); } for (int i = 0; i < inscopeContext.size(); ++i) { this.nodeset.addAll(XPathCache.getInstance().evaluate(inscopeContext, i + 1, relativeExpr, getPrefixMapping(), xpathFunctionContext)); } } else { this.nodeset = inscopeContext; } } catch (XFormsException e) { throw new XFormsComputeException(e.getMessage(), this.target, null); } } // implementation of 'de.betterform.xml.xforms.model.bind.Binding' public List getNodeset() { return this.nodeset; } public int getPosition() { return 1; } /** * Returns the binding expression. * * @return the binding expression. */ public String getBindingExpression() { if (BindingUtil.hasRef(this.element)) { return getXFormsAttribute(REF_ATTRIBUTE); } return getXFormsAttribute(NODESET_ATTRIBUTE); } /** * Returns the id of the binding element. * * @return the id of the binding element. */ public String getBindingId() { return this.id; } /** * Returns the enclosing element. * * @return the enclosing element. */ public Binding getEnclosingBinding() { Element parentElement = (Element) this.element.getParentNode(); if (parentElement.getLocalName().equals(XFormsConstants.MODEL)) { return null; } return (Binding) parentElement.getUserData(""); } /** * Returns the location path. * * @return the location path. */ public String getLocationPath() { return this.locationPath; } /** * Returns the model id of the binding element. * * @return the model id of the binding element. */ public String getModelId() { return this.model.getId(); } public boolean hasBindingExpression() { if (getBindingExpression() != null) return true; else return false; } // convenience /** * Returns the instance id of the binding element. * * @return the instance id of the binding element. */ public String getInstanceId() { return this.instanceId; } // bind members /** * Returns the <code>type</code> attribute. * * @return the <code>type</code> attribute. */ public String getDatatype() { return this.type; } /** * Returns the <code>readonly</code> attribute. * * @return the <code>readonly</code> attribute. */ public String getReadonly() { return this.readonly; } /** * Returns the <code>required</code> attribute. * * @return the <code>required</code> attribute. */ public String getRequired() { return this.required; } /** * Returns the <code>relevant</code> attribute. * * @return the <code>relevant</code> attribute. */ public String getRelevant() { return this.relevant; } /** * Returns the <code>calculate</code> attribute. * * @return the <code>calculate</code> attribute. */ public String getCalculate() { return this.calculate; } /** * Returns the <code>constraint</code> attribute. * * @return the <code>constraint</code> attribute. */ public String getConstraint() { return this.constraint; } public List getConstraints() { return this.constraints; } public Map<String, String> getCustomMIPs() { return this.customMIPs; } /** * Returns the <code>p3ptype</code> attribute. * * @return the <code>p3ptype</code> attribute. * @deprecated deprecated without replacement */ public String getP3PType() { return this.p3ptype; } /** * Assigns an XPath reference finder. * * @param referenceFinder the XPath reference finder. */ public void setReferenceFinder(XPathReferenceFinder referenceFinder) { this.referenceFinder = referenceFinder; } /** * Returns the set of all node names referenced by the <code>readonly</code> expression. * * @return the set of all node names referenced by the <code>readonly</code> expression. */ public Set getReadonlyReferences() { return this.readonlyReferences; } /** * Returns the set of all node names referenced by the <code>required</code> expression. * * @return the set of all node names referenced by the <code>required</code> expression. */ public Set getRequiredReferences() { return this.requiredReferences; } /** * Returns the set of all node names referenced by the <code>relevant</code> expression. * * @return the set of all node names referenced by the <code>relevant</code> expression. */ public Set getRelevantReferences() { return this.relevantReferences; } /** * Returns the set of all node names referenced by the <code>calculate</code> expression. * * @return the set of all node names referenced by the <code>calculate</code> expression. */ public Set getCalculateReferences() { return this.calculateReferences; } /** * Returns the set of all node names referenced by the <code>constraint</code> expression. * * @return the set of all node names referenced by the <code>constraint</code> expression. */ public Set getConstraintReferences() { return this.constraintReferences; } public Set getCustomMIPReferences(String customMIP) { return this.customMIPReferences.get(customMIP); } public Map<String, Set> getCustomMIPsReferences() { return this.customMIPReferences; } // implementation of 'de.betterform.xml.events.DefaultAction' /** * Performs the implementation specific default action for this event. * * @param event the event. */ public void performDefault(Event event) { if (event.getType().equals(XFormsEventNames.BINDING_EXCEPTION)) { getLogger().error(this + " binding exception: " + ((XMLEvent) event).getContextInfo()); return; } } // lifecycle methods /** * Performs element init. * * @throws XFormsException if any error occurred during init. */ public void init() throws XFormsException { initializeDefaultAction(); initializeBindingContext(); initializeModelItems(); Initializer.initializeBindElements(getModel(), getElement(), this.referenceFinder); } /** * Performs element disposal. * * @throws XFormsException if any error occurred during disposal. */ public void dispose() throws XFormsException { if (getLogger().isDebugEnabled()) { getLogger().debug(this + " dispose"); } disposeDefaultAction(); } // lifecycle template methods /** * Initializes the default action. */ protected void initializeDefaultAction() { this.container.getXMLEventService().registerDefaultAction(this.target, XFormsEventNames.BINDING_EXCEPTION, this); } /** * Initializes the binding context. * * @throws XFormsException if any error occured during binding context init. */ protected void initializeBindingContext() throws XFormsException { // resolve location path and instance id this.locationPath = this.container.getBindingResolver().resolve(this); this.instanceId = this.model.computeInstanceId(this.locationPath); if (this.instanceId == null) { throw new XFormsBindingException("wrong instance id", this.target, this.locationPath); } // get type attributes //todo:support combination this.p3ptype = getXFormsAttribute(P3PTYPE_ATTRIBUTE); try { // this.type = getXFormsAttribute(TYPE_ATTRIBUTE); this.type = getMIP(TYPE); // get model item attributes and analyze path structure this.readonly = getMIP(READONLY); if (this.readonly != null) { this.readonlyReferences = this.referenceFinder.getReferences(this.readonly, getPrefixMapping(), this.container); } this.required = getMIP(REQUIRED); if (this.required != null) { this.requiredReferences = this.referenceFinder.getReferences(this.required, getPrefixMapping(), this.container); } this.relevant = getMIP(RELEVANT); if (this.relevant != null) { this.relevantReferences = this.referenceFinder.getReferences(this.relevant, getPrefixMapping(), this.container); } this.calculate = getMIP(CALCULATE); if (this.calculate != null) { this.calculateReferences = this.referenceFinder.getReferences(this.calculate, getPrefixMapping(), this.container); } registerConstraints(); this.constraint = getMIP(CONSTRAINT); if (this.constraint != null) { this.constraintReferences = this.referenceFinder.getReferences(this.constraint, getPrefixMapping(), this.container); } this.customMIPs = getCustomMIPAttributes(); if (!this.customMIPs.isEmpty()) { this.customMIPReferences = new HashMap<String, Set>(); for (String key : this.customMIPs.keySet()) { this.customMIPReferences.put(key, this.referenceFinder.getReferences(this.customMIPs.get(key), getPrefixMapping(), this.container)); } } updateXPathContext(); } catch (XFormsComputeException e) { throw e; } catch (XFormsException e) { throw new XFormsComputeException(e.getMessage(), this.target, null); } } /** * Initializes all bound model items. * * @throws XFormsException if any error occured during model item init. */ protected void initializeModelItems() throws XFormsException { Instance instance = getModel().getInstance(getInstanceId()); List nodeset = getNodeset(); if (nodeset != null && nodeset.size() > 0) { Iterator iterator = instance.iterateModelItems(nodeset, false); if (iterator != null) { ModelItem modelItem; while (iterator.hasNext()) { modelItem = (ModelItem) iterator.next(); if (LOGGER.isDebugEnabled()) { LOGGER.debug(this + " init: model item for " + DOMUtil.getCanonicalPath((Node) modelItem.getNode())); } // 4.2.1 - 4.b applying model item properties to each node initializeModelItemProperties(modelItem); } } } } /** * Initializes the model item properties of the specified model item. * * @param item the model item. * @throws XFormsException if any error occured during model item properties init. */ protected void initializeModelItemProperties(ModelItem item) throws XFormsException { DeclarationView declaration = item.getDeclarationView(); if (this.type != null) { if (declaration.getDatatype() != null) { throw new XFormsBindingException("property 'type' already present at model item", this.target, this.id); } if (!this.model.getValidator().isSupported(this.type)) { throw new XFormsBindingException("datatype '" + this.type + "' is not supported", this.target, this.id); } if (!this.model.getValidator().isKnown(this.type)) { throw new XFormsBindingException("datatype '" + this.type + "' is unknown", this.target, this.id); } declaration.setDatatype(this.type); } if (this.readonly != null) { if (declaration.getReadonly() != null) { this.readonly = declaration.getReadonly() + " " + COMBINE_ONE + " " + this.readonly; // throw new XFormsBindingException("property 'readonly' already present at model item", this.target, this.id); } this.readonlyReferences = this.referenceFinder.getReferences(this.readonly, getPrefixMapping(), this.container); declaration.setReadonly(this.readonly); } if (this.required != null) { if (declaration.getRequired() != null) { this.required = declaration.getRequired() + " " + COMBINE_ONE + " " + this.required; // throw new XFormsBindingException("property 'required' already present at model item", this.target, this.id); } this.requiredReferences = this.referenceFinder.getReferences(this.required, getPrefixMapping(), this.container); declaration.setRequired(this.required); } if (this.relevant != null) { if (declaration.getRelevant() != null) { this.relevant = declaration.getRelevant() + " " + COMBINE_ONE + " " + this.relevant; // throw new XFormsBindingException("property 'relevant' already present at model item", this.target, this.id); } this.relevantReferences = this.referenceFinder.getReferences(this.relevant, getPrefixMapping(), this.container); declaration.setRelevant(this.relevant); } if (this.calculate != null) { if (declaration.getCalculate() != null) { throw new XFormsBindingException("property 'calculate' already present at model item", this.target, this.id); } declaration.setCalculate(this.calculate); } //should be: declaration.addConstraint(this. // if(this.constraints.size() != 0){ // // } if (this.constraint != null) { if (declaration.getConstraint() != null) { /* TODO ADAPT ME TO LIST ME*/ this.constraint = declaration.getConstraint() + " " + COMBINE_ALL + " " + this.constraint; } /* TODO REMOVE ME*/ declaration.setConstraint(this.constraint); declaration.setConstraints(this.constraints); } if (this.p3ptype != null) { if (declaration.getP3PType() != null) { throw new XFormsBindingException("property 'p3ptype' already present at model item", this.target, this.id); } declaration.setP3PType(this.p3ptype); } updateXPathContext(); } /** * Disposes the default action. */ protected void disposeDefaultAction() { this.container.getXMLEventService().deregisterDefaultAction(this.target, XFormsEventNames.BINDING_EXCEPTION, this); } /** * Returns the logger object. * * @return the logger object. */ protected Log getLogger() { return LOGGER; } private void registerConstraints() { String s = getXFormsAttribute("constraint"); if (s != null) { this.constraints.add(new ConstraintAttribute(this.element)); } NodeList nl = this.element.getElementsByTagNameNS(NamespaceConstants.BETTERFORM_NS, "constraint"); int len = nl.getLength(); Element e; String id; for (int i = 0; i < len; i++) { e = (Element) nl.item(i); id = this.container.generateId(); e.setAttribute("id", id); this.constraints.add(new ConstraintElement(e, this.model)); } } private String getMIP(short MIPType) { String s = null; switch (MIPType) { case TYPE: return getValueForMip("type", null); case READONLY: return getValueForMip("readonly", "or"); case REQUIRED: return getValueForMip("required", "or"); case RELEVANT: return getValueForMip("relevant", "or"); case CALCULATE: return getValueForMip("calculate", null); case CONSTRAINT: return getValueForMip("constraint", "and"); default: return null; } } private String getValueForMip(String mip, String combine) { Element e; int len = 0; NodeList nl = null; // calculate and type cannot be combined if (combine == null) { String s = getXFormsAttribute(mip); if (s != null) { return s; } if (LOGGER.isWarnEnabled()) { if (this.element.getElementsByTagNameNS(NamespaceConstants.BETTERFORM_NS, mip).getLength() != 0) { LOGGER.warn("<bf:" + mip + "> is not supported. Use @" + mip + " on bind element instead"); } } } else { StringBuffer buf = new StringBuffer(""); //check for existence of standard xforms mip attribute String s = getXFormsAttribute(mip); if (s != null) { buf.append(s); } nl = this.element.getElementsByTagNameNS(NamespaceConstants.BETTERFORM_NS, mip); len = nl.getLength(); for (int i = 0; i < len; i++) { e = (Element) nl.item(i); if (s != null) { buf.append(" ").append(combine).append(" "); } // buf.append(e.getAttribute(XFormsConstants.VALUE_ATTRIBUTE)); buf.append(getMIPAttributeOrElement(e)); if (i < len - 1) { buf.append(" ").append(combine).append(" "); } } if (buf.length() != 0) { return buf.toString(); } } return null; } private String getMIPAttributeOrElement(Element e) { String mipValue = e.getAttribute(XFormsConstants.VALUE_ATTRIBUTE); if (mipValue != "") { //value attribute takes precedence if present return mipValue; } else { Element valueElem = DOMUtil.findFirstChildNS(e, NamespaceConstants.BETTERFORM_NS, "value"); return DOMUtil.getElementValue(valueElem).trim(); } } } // end of class