// // Copyright (c)1998-2011 Pearson Education, Inc. or its affiliate(s). // All rights reserved. // package openadk.library.tools.mapping; import java.io.FileInputStream; import java.io.FileReader; import java.io.FileWriter; import java.io.PrintWriter; import java.io.StringWriter; import java.util.Arrays; import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.ListIterator; import java.util.Map; import java.util.Properties; import java.util.StringTokenizer; import java.util.Vector; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import openadk.library.ADK; import openadk.library.ADKSchemaException; import openadk.library.ADKTypeParseException; import openadk.library.DefaultValueBuilder; import openadk.library.Element; import openadk.library.ElementDef; import openadk.library.SIFDataObject; import openadk.library.SIFElement; import openadk.library.SIFFormatter; import openadk.library.SIFMessageInfo; import openadk.library.SIFParser; import openadk.library.SIFSimpleType; import openadk.library.SIFString; import openadk.library.SIFTypeConverter; import openadk.library.SIFTypeConverters; import openadk.library.SIFVersion; import openadk.library.SIFWriter; import openadk.library.ValueBuilder; import openadk.library.impl.BuildOptions; import openadk.library.impl.ProfilerUtils; import openadk.library.tools.cfg.ADKConfigException; import openadk.library.tools.xpath.SIFXPathContext; import openadk.util.XMLUtils; import org.apache.commons.jxpath.CompiledExpression; import org.apache.commons.jxpath.JXPathContext; import org.apache.commons.jxpath.Pointer; import org.apache.commons.jxpath.Variables; import org.apache.commons.jxpath.ri.model.NodePointer; import org.apache.xml.serialize.OutputFormat; import org.apache.xml.serialize.XMLSerializer; import org.w3c.dom.Attr; import org.w3c.dom.Document; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; /** * Manages a set of mapping rules that define how to tranform a set of * application field values into SIF Data Objects. The Mappings class is a * powerful facility for programmatically transforming sets of data into * SIFDataObject instances based on XPath-like mapping rules. These rules can be * read from any DOM Document or configuration file read and written by the * AgentConfig class, which makes them easily customizable by the end-user. * <p> * * Mappings are arranged into a selection hierarchy so that different sets of * rules can be created for individual zones, agent SourceIds, and versions of * SIF. Rules at lower levels in the hierarchy are inherited from higher levels. * Thus, you could create a default set of rules describing how to produce * StudentPersonal objects for all zones, and extend or override these rules in * a set of child rules that are specific to an individual zone. When selecting * the Mappings object for the SIF Message being processed, the * <code>select</code> method would choose the zone-specific set of rules over * the default rules, but would inherit any default field mappings not * explicitly overridden. This technique is very useful when the mapping rules * of a SIF Agent differ from zone-to-zone or from one version of SIF to the * next. * <p> * * To use the Mappings class, * <p> * * <ul> * <li> * Create a Mappings instance and populate it with Mappings children. The * easiest way to accomplish this is to call the <code>read</code> method to * read an XML configuration file or the <code>populate</code> method to * populate a Mappings instance from a DOM <i>Document</i>. Refer to the ADK * Developer Guide for more information. <br/> * <br/> * </li> * <li> * When your agent needs to map local application field values to SIF Data * Object elements and attributes, call the <code>select</code> method to select * the appropriate Mappings instance based on ZoneId, SourceId, and SIF Version. * The <code>select</code> method returns a Mappings instance. <br/> * <br/> * </li> * <li> * Call the <code>map</code> method to produce a SIFDataObject from a set of * field values prepared by your application (i.e. when preparing outbound * messages), or to populate your data from a SIFDataObject (i.e. when * processing inbound messages). <br/> * <br/> * </li> * <li> * The set of field values being mapped to or from SIF is maintained by an * instance of a class implementing the <code>FieldAdaptor</code> interface. The * ADK provides a default set of classes that implement this interface and that * can map data to a Map (as strings or as native data types), as well as to a * Java <code>ResultSet</code> object. <br/> * <br/> * </li> * <li> * The ADK allows for custom data mapping implementations to be created. To * create your own custom implementation, create a class that implements the * <code>FieldAdaptor</code> interface.</li> * </ul> * * <b>Overview</b> * <p> * * Each Mappings object is comprised of one or more of the following: * <p> * * <ul> * <li> * <code>ObjectMappings</code> that defines how to map fields of the application * to a SIF Data Object's elements and attributes. Create one ObjectMapping per * SIF Data Object type (e.g. for "StudentPersonal", "BusInfo", etc.) An * <code>ObjectMapping</code> instance is in turn comprised of one or more * <code>FieldMapping</code> objects, each of which is comprised of a * <code>Rule</code>. Two Rule implementations are currently defined: * <code>XPathRule</code>, which uses an XPath-like query string to describe how * to interpret or build a SIF element or attribute; and * <code>OtherIdRule</code>, which describes how to interpret or build * <code><OtherId></code> values. <br/> * <br/> * </li> * <li> * <code>ValueSets</code> that define a simple mapping from codes and constants * used in the application to equivalent codes and constants used by SIF. A * <code>ValueSet</code> could be used, for example, tofield define a mapping * table for Grade Levels, Ethnicity Codes, English Proficiency Codes, etc.</li> * <li> * User-defined properties comprised of a name and value pair.</li> * </ul> * * <b>Mappings Hierarchy</b> * <p> * * Mappings objects form a hierarchy comprised of a <i>root</i> Mappings object * -- which is always present and does not have an ID -- and one or more child * Mappings of the root. The root serves as a container for all other Mappings * objects. Each child of the root container must be assigned a unique ID. * Mappings may also be assigned an optional ZoneId filter, SourceId filter, an * SIF Version filter. * <p> * * The unique ID is used to select a Mappings object at runtime when multiple * groups of Mappings are present. If you only have one Mappings object, it is * recommended that it be given an ID of "default". However, agents that both * provide and consume objects will usually define two groups of Mappings * objects: one used in the translation of incoming messages and another used in * the production of outgoing messages. In this case it is recommended that two * Mappings objects be defined, one with an ID of "incoming" or similar and the * other with an ID of "outgoing" or similar. * <p> * * When Mappings are nested, child Mappings inherit the filters, ObjectMappings, * ValueSets, and properties of the parent. * <p> * * <b>SourceId Filter</b> * <p> * * When a SourceId filter is assigned to a Mappings object, the * <code>select</code> method will exclude any Mappings object that does not * include the SourceId passed to that method. If you have tested your agent * with several Student Information Systems, for example, you may wish to define * the unique mapping rules for each SIS in a separate Mappings object where the * SourceId filter includes the SourceId of the SIS agent. * <p> * * <b>ZoneId Filter</b> * <p> * * When a ZoneId filter is assigned to a Mappings object, the * <code>select</code> method will exclude any Mappings instance that does not * include the ZoneId passed to that method. The ZoneId filter can be used to * customize mappings on a zone-by-zone basis. * <p> * * <b>SIFVersion Filter</b> * <p> * * When a SIFVersion filter is assigned to a Mappings object, the * <code>select</code> method will exclude any Mappings instance that does not * include the SIF Version passed to that method. Such a filter can be used to * customize mappings for a specific version of SIF. This is often necessary if * the tag names of elements or attributes have changed from one version of SIF * to the next. In this case, the XPath-like rules that you include in a * Mappings object must reflect the element and attribute names of each version * of SIF. * <p> * * <b>Mappings Rules</b> * <p> * * Mapping <i>rules</i> are comprised of one or more ObjectMapping and * FieldMapping objects that define how to map a field of the local * application's database to a SIF Data Object element or attribute. When * multiple mappings are defined for a field, the first one that evaluates true * is selected. The <code>map</code> method that accepts a * <code>SIFElement</code> object evaluates the rules of a Mappings object * against a SIF Data Object instance to produce a set of of values. Each entry * in the <code>FieldAdaptor</code> is a key/value pair where the key is the * name of the local application field and the value is the value from the SIF * Data Object that mapped to that field. Thus, an agent can call the * <code>map</code> method to obtain a table of values for each SIF Data Object * it is processing. The Mappings class can also be used to produce a SIF Data * Object instance from a <code>FieldAdaptor</code> prepared by the agent. Call * the <code>map</code> method that accepts a <code>FieldAdaptor</code> instance * to return a <code>SIFElement</code> object. * <p> * * Mappings are comprised of a hierarchy of ObjectMapping and FieldMapping * object. The ObjectMapping class encapsulates a SIF Data Object type such as * StudentPersonal and BusInfo. Each ObjectMapping contains one or more * FieldMapping objects to define a field-level mapping. Mapping rules are * specified in an XPath-like format relative to the SIF Data Object type named * in the ObjectMapping. * <p> * <p> * * <b>XML Configuration</b> * <p> * * The <code>populate</code> method constructs a Mappings hierarchy from a * parsed DOM Document. The populate method can only be called on the root * Mappings container. Consult the Mappings.dtd file in the ADK's Extras * directory for the expected schema. * <p> * <p> * * <b>Notes for ADK 1.x users</b> * <p> * * In the 1.x version of the ADK, all of the overloads of the <code>map</code> * method expected a <code>java.util.Map</code> to be passed in. ADK 2.0 * supports mapping to other storage types of data, and as such, the * <code>java.util.Map</code> parameter was replaced with a * {@link openadk.library.tools.mapping.FieldAdaptor}. * <p> * * The ADK does provides the * {@link openadk.library.tools.mapping.StringMapAdaptor} * implementation class that matches the mappings behavior of the 1.x version of * the ADK. To convert your ADK 1.x code to use the ADK 2.x * <code>Mappings</code> class, create an instance of * <code>StringMapAdaptor</code> to wrap your <code>Map</code> instance and pass * that to the <code>Mappings.Map(...)</code) method. The default implementation * of <code>StringMapAdaptor</code> formats data from SIF using SIF 1.x formats * and stores them in the Map as a text value. * <p> * * Here is an example... * <p> * * <pre> * // ADK 1.x code... * HashMap values; * SIFDataObject sdo; * Mappings.Map(values, sdo); * * // ADK 2.x equivalent.... * HashMap values; * SIFDataObject sdo; * StringMapAdaptor fieldAdaptor = new StringMapAdaptor(values); * Mappings.Map(fieldAdaptor, sdo); * </pre> * * * * @author Eric Petersen * @version ADK 1.0 */ public class Mappings { // These are not declared final so vendors can change them at runtime if // needed private static String XML_MAPPINGS = "mappings", XML_OBJECT = "object", XML_VALUESET = "valueset"; static String XML_FIELD = "field"; static String XML_LIST = "list"; static String XML_LISTEND = "/list"; /** * The parent Mappings object */ protected Mappings fParent; /** * Child Mappings objects keyed by ID */ protected HashMap<String, Mappings> fChildren = null; /** * Optional DOM Node associated with this Mappings */ protected Node fNode; /** * The ID of this Mappings object */ protected String fId; /** * SourceId Filter. A list of SourceIds that define the SIF Agents to which * this mapping applies. When null, this Mappings object applies to all * SourceIds. */ protected String[] fSourceIds; /** * ZoneId Filter. A list of ZoneIds that define the Zones to which this * mapping applies. When null, this Mappings object applies to all ZoneIds. */ protected String[] fZoneIds; /** * SIFVersion Filter. A list of SIFVersion instances that define the * versions of SIF to which this mapping applies. When null, this Mappings * object applies to all versions of SIF. */ protected SIFVersion[] fSifVersions; /** * ObjectMappings. Each entry is an ObjectMapping keyed by object name. */ protected HashMap<String, ObjectMapping> fObjRules = null; /** * ValueSetMappings. Each entry is a ValueSet keyed by a unique string ID. */ protected HashMap<String, ValueSet> fValueSets = null; /** * User-defined properties for this Mappings object */ protected HashMap<String, String> fProps = null; /** * Constructs the root-level Mappings container */ public Mappings() { this(null, null, null, null, null); } /** * Constructs a child Mappings object with no filters. * <p> * * @param parent * The parent Mappings object * @param id * A unique string ID for this Mappings object */ public Mappings(Mappings parent, String id) { this(parent, id, null, null, null); } /** * Constructs a child Mappings object with optional filters. * <p> * * @param parent * The root Mappings object * @param id * A unique identifier for this Mappings object * @param sourceIdFilter * A comma-delimited list of SourceIds or null if no SourceId * filter should be applied to this Mappings object * @param zoneIdFilter * A comma-delimited list of ZoneIds or null if no SourceId * filter should be applied to this Mappings object * @param sifVersionFilter * A comma-delimited list of SIF Versions, in the form "1.0r1", * or null if no SIF Version filter should be applied to this * Mappings object */ public Mappings(Mappings parent, String id, String sourceIdFilter, String zoneIdFilter, String sifVersionFilter) { fParent = parent; fId = id; setSourceIdFilter(sourceIdFilter); setZoneIdFilter(zoneIdFilter); setSIFVersionFilter(sifVersionFilter); } /** * Creates a copy of this Mappings object and adds the copy as a child of * the specified parent. Note the root Mappings container cannot be copied. * If this method is called on the root an exception is raised. * <p> * * This method performs a "deep copy", such that a copy is made of each * child Mappings, ObjectMapping, FieldMapping, ValueSet, and Property * instance. * <p> * * @param newParent * The parent Mappings instance * @return A "deep copy" of this root Mappings object * * @exception ADKMappingException * thrown if this method is not called on the root Mappings * container */ public Mappings copy(Mappings newParent) throws ADKMappingException { if (isRoot()) throw new ADKMappingException( "Mappings.copy cannot be called on the root Mappings container", null); // Create a new Mappings instance Mappings m = new Mappings(newParent, fId); // Copy the DOM Node if there is one if (fNode != null && newParent.fNode != null) { m.fNode = newParent.fNode.getOwnerDocument().importNode(fNode, false); } newParent.addChild(m); // Copy the filters m.setSourceIdFilter(getSourceIdFilterString()); m.setZoneIdFilter(getZoneIdFilterString()); m.setSIFVersionFilter(getSIFVersionFilterString()); // Copy all Mappings children if (fChildren != null) { for (Iterator it = fChildren.keySet().iterator(); it.hasNext();) { Mappings ch = fChildren.get(it.next()); m.addChild(ch.copy(m)); } } // Copy all ObjectMapping children if (fObjRules != null) { for (Iterator it = fObjRules.keySet().iterator(); it.hasNext();) { ObjectMapping ch = fObjRules.get(it.next()); ObjectMapping copy = ch.copy(m); m.addRules(copy, false); // if( m.fNode != null ) // m.fNode.appendChild( copy.fNode ); } } // Copy fValueSets if (fValueSets != null) { for (Iterator it = fValueSets.keySet().iterator(); it.hasNext();) { String key = (String) it.next(); ValueSet vs = fValueSets.get(key); m.addValueSet(vs.copy(m)); } } // Copy properties if (fProps != null) { for (Iterator it = fProps.keySet().iterator(); it.hasNext();) { String key = (String) it.next(); String val = fProps.get(key); m.setProperty(key, val); } } return m; } /** * Sets the optional DOM Node associated with this Mappings instance. The * DOM Node is set when a Mappings object is populated from a DOM Document. * * @param node * The DOM Node associated with this Mappings instance */ public void setNode(Node node) { fNode = node; } /** * Gets the optional DOM Node associated with this Mappings instance. The * DOM Node is set when a Mappings object is populated from a DOM Document. * * @return The DOM Node associated with this Mappings instance */ public Node getNode() { return fNode; } /** * Gets the unique ID of this Mappings instance * * @return The unique ID of this Mappings instance */ public String getId() { return fId; } /** * Gets the parent Mappings instance * * @return The parent Mappings instance or NULL if this is the top-level * Mappings instance */ public Mappings getParent() { return fParent; } /** * Determines if this is the root Mappings container. Applications may call * the <code>select</code> and <code>map</code> methods on the root * container only. * * @return true if this is the root Mappings container, otherwise false */ public boolean isRoot() { return fParent == null; } /** * Gets the root Mappings container. * * @return The root Mappings container instance */ public Mappings getRoot() { Mappings parent = this; while (parent.fParent != null) parent = parent.fParent; return parent; } /** * Adds a Mappings child * * @param m * The child Mappings instance to add to this parent */ public void addChild(Mappings m) { if (fChildren == null) fChildren = new HashMap<String, Mappings>(); fChildren.put(m.getId(), m); // If there is a DOM Node associated with this Mappings, and the new // child // also has a Node, attach it if (fNode != null && m.fNode != null) fNode.appendChild(m.fNode); } /** * Create a Mappings child * * @param id * The ID of the new Mappings instance * @return The newly-created child Mappings instance with the specified ID * @throws ADKMappingException * If the child cannot be created */ public Mappings createChild(String id) throws ADKMappingException { Mappings m = new Mappings(this, id); try { if (fNode != null) { m.fNode = m.toDOM(fNode.getOwnerDocument()); } } catch (SAXException se) { throw new ADKMappingException(se.getMessage(), null, se); } addChild(m); return m; } /** * Removes a Mappings child * * @param m * The child Mappings instance to remove */ public void removeChild(Mappings m) { if (fChildren != null) fChildren.remove(m.getId()); // If there is a DOM Node associated with this Mappings, and the new // child // also has a Node, detatch it if (fNode != null && m.fNode != null) fNode.removeChild(m.fNode); } /** * Return an array of all Mappings children * * @return An array of all Mappings children */ public Mappings[] getChildren() { Mappings[] arr = new Mappings[fChildren == null ? 0 : fChildren.size()]; if (fChildren != null) { int i = 0; for (Iterator it = fChildren.values().iterator(); it.hasNext();) { arr[i++] = (Mappings) it.next(); } } return arr; } /** * Count the number of Mappings children * * @return The number of Mappings children */ public int getChildCount() { return fChildren == null ? 0 : fChildren.size(); } /** * Sets a SourceId filter on this Mappings object. * <p> * * @param filter * A comma-delimited list of SourceIds that define the agents * this Mappings object applies to. If the filter includes an * asterisk, the Mappings object will have no SourceId filter * regardless of whether any other SourceIds are specified in the * <i>filter</i> string. */ public void setSourceIdFilter(String filter) { if (filter != null) { StringTokenizer tok = new StringTokenizer(filter, ","); fSourceIds = new String[tok.countTokens()]; for (int i = 0; i < fSourceIds.length; i++) { fSourceIds[i] = (String) tok.nextElement(); if (fSourceIds[i].equals("*")) { fSourceIds = null; return; } } } else fSourceIds = null; if (fNode != null) XMLUtils.setAttribute(fNode, "sourceId", filter); } /** * Sets a ZoneId filter on this Mappings object. * <p> * * @param filter * A comma-delimited list of ZoneIds that define the zones this * Mappings object applies to. If the filter includes an * asterisk, the Mappings object will have no ZoneId filter * regardless of whether any other ZoneIds are specified in the * <i>filter</i> string. */ public void setZoneIdFilter(String filter) { if (filter != null) { StringTokenizer tok = new StringTokenizer(filter, ","); fZoneIds = new String[tok.countTokens()]; for (int i = 0; i < fZoneIds.length; i++) { fZoneIds[i] = (String) tok.nextElement(); if (fZoneIds[i].equals("*")) { fZoneIds = null; return; } } } else fZoneIds = null; if (fNode != null) XMLUtils.setAttribute(fNode, "zoneId", filter); } /** * Sets a SIFVersion filter on this Mappings object. * <p> * * @param filter * A comma-delimited list of SIFVersion strings, in the form * "1.0r1", that define the zones this Mappings object applies * to. If the filter includes an asterisk, the Mappings object * will have no SIFVersion filter regardless of whether any other * SIFVersions are specified in the <i>filter</i> string. */ public void setSIFVersionFilter(String filter) { if (filter != null) { StringTokenizer tok = new StringTokenizer(filter, ","); fSifVersions = new SIFVersion[tok.countTokens()]; for (int i = 0; i < fSifVersions.length; i++) { String verStr = (String) tok.nextElement(); if (verStr.equals("*")) { fSifVersions = null; return; } fSifVersions[i] = SIFVersion.parse(verStr); } } else fSifVersions = null; if (fNode != null) XMLUtils.setAttribute(fNode, "sifVersion", filter); } /** * Set a user-defined property. * <p> * * @param name * The property name * @param value * The property value */ public void setProperty(String name, String value) { if (fProps == null) fProps = new HashMap<String, String>(); if (name != null) fProps.put(name, value); if (fNode != null) { List<Node> v = XMLUtils.getElementsByTagName(fNode, "property", false); // Look for existing property with this name, update its value for (int i = 0; i < v.size(); i++) { Node n = v.get(i); if (XMLUtils.getAttribute(n, "name").equals(name)) { XMLUtils.setAttribute(n, "value", value); return; } } // No existing property found, add a new one Node n = fNode.getOwnerDocument().createElement("property"); XMLUtils.setAttribute(n, "name", name); XMLUtils.setAttribute(n, "value", value); fNode.appendChild(n); } } /** * Gets a user-defined property. * <p> * * @param name * The property name * @param defaultValue * The value to return if the property is not defined * * @return The value of the property */ public String getProperty(String name, String defaultValue) { if (fProps == null || name == null) return defaultValue; String s = fProps.get(name); return s != null ? s : defaultValue; } /** * Determines if a user-defined property is currently set on this Mappings * instance. * <p> * * @param name * The property name * * @return true if the property is set; otherwise false */ public boolean hasProperty(String name) { return fProps != null && fProps.containsKey(name); } /** * Gets the names of all user-defined properties currently set on this * Mappings instance. * <p> * * @return An array of property names */ public String[] getPropertyNames() { String[] names = new String[fProps != null ? fProps.size() : 0]; if (fProps != null) { int i = 0; for (Iterator it = fProps.keySet().iterator(); it.hasNext();) { names[i++] = (String) it.next(); } } return names; } /** * Populate the Mappings hierarchy from an XML Document. * <p> * * This method can only be called on the root Mappings object or an * exception is raised. It reads all <mapping> elements from the * Document to build the Mappings hierarchy. * <p> * * @param doc * A DOM <i>Document</i> that defines one or more * <mappings> elements from which to populate the Mappings * hierarchy. Note the root Mappings object is a virtual object * and therefore not represented by a Node in this Document. * * @param parent * A DOM <i>Node</i> in the source document that should be * considered the parent Node of this Mappings object. This * parameter is required because the root Mappings object is a * virtual object that is not represented in the DOM graph, so * some other node usually serves as its parent (e.g. the * <agent> node in an AgentConfig configuration file) * * @exception ADKConfigException * is thrown if a required element or attribute is missing or * has an invalid value * * @exception ADKMappingException * is thrown if this method is called on a Mappings instance * other than the root Mappings container */ public void populate(Document doc, Node parent) throws ADKConfigException, ADKMappingException { if (fParent != null) throw new ADKMappingException( "Mappings.populate can only be called on the root Mappings container", null); fNode = parent; populate(doc, this); } protected void populate(Node node, Mappings parent) throws ADKConfigException, ADKMappingException { NodeList nl = node.getChildNodes(); for (int i = 0; i < nl.getLength(); i++) { Node n = nl.item(i); if (n.getNodeType() == Node.ELEMENT_NODE) { if (n.getNodeName().equalsIgnoreCase(XML_MAPPINGS)) { // Get the ID String id = getAttribute(n, "id"); // Create a new child Mappings object Mappings mappings = new Mappings(parent, id); mappings.setNode(n); if (parent.fChildren == null) parent.fChildren = new HashMap<String, Mappings>(); parent.fChildren.put(mappings.getId(), mappings); // Set a SIFVersion filter if present String ver = getAttribute(n, "sifVersion"); if (ver != null && ver.trim().length() > 0) mappings.setSIFVersionFilter(ver); // Set a ZoneId filter if present String zoneIds = getAttribute(n, "zoneId"); if (zoneIds != null && zoneIds.trim().length() > 0) mappings.setZoneIdFilter(zoneIds); // Set a SourceId filter if present String sourceIds = getAttribute(n, "sourceId"); if (sourceIds != null && sourceIds.trim().length() > 0) mappings.setSourceIdFilter(sourceIds); // Populate the Mappings object with rules populate(n, mappings); } else if (n.getNodeName().equalsIgnoreCase(XML_OBJECT)) { if (n.getParentNode().getNodeName().equals(XML_MAPPINGS)) { String obj = getAttribute(n, XML_OBJECT); if (obj == null) throw new ADKConfigException( "<object> element must have an object attribute"); ObjectMapping om = new ObjectMapping(obj); om.setNode(n); parent.addRules(om, false); populateObject(n, om); NamedNodeMap nnm = n.getAttributes(); for (int nnmIdx = 0; nnmIdx < nnm.getLength(); nnmIdx++) { Attr attr = (Attr)nnm.item(nnmIdx); if (attr.getNodeName().startsWith("xmlns:")) { om.setNamespaceURIForPrefix(attr.getNodeName().substring(6), attr.getNodeValue(), false); } } } } else if (n.getNodeName().equalsIgnoreCase("property")) { if (n.getParentNode().getNodeName().equals(XML_MAPPINGS)) { parent.setProperty(getAttribute(n, "name"), getAttribute(n, "value")); } } else if (n.getNodeName().equalsIgnoreCase(XML_VALUESET)) { if (n.getParentNode().getNodeName().equals(XML_MAPPINGS)) { ValueSet set = new ValueSet(getAttribute(n, "id"), getAttribute(n, "title"), n); parent.addValueSet(set); populateValueSet(n, set); } } else populate(n, parent); } } } /** * Parse the <code><field></code> children of an * <code><object></code> element. * <p> * * @param element * A DOM <i>Node</i> encapsulating an <code><object></code> * element * @param parent * The parent ObjectMapping instance */ protected void populateObject(Node element, ObjectMapping parent) throws ADKConfigException, ADKMappingException { NodeList nl = element.getChildNodes(); for (int i = 0; i < nl.getLength(); i++) { Node n = nl.item(i); if (n.getNodeType() == Node.ELEMENT_NODE) { // JEN - List Element if (n.getNodeName().equalsIgnoreCase(XML_FIELD)) { FieldMapping fm = FieldMapping.fromXML(parent, (org.w3c.dom.Element) n); parent.addRule(fm, false); } else if (n.getNodeName().equalsIgnoreCase(XML_LIST)) { parent.addListingMapping(populateList(n, parent)); } } } } protected ListMapping populateList(Node node, ObjectMapping parent) throws ADKConfigException, ADKMappingException { // <List node NamedNodeMap nnm = node.getAttributes(); String listObjectName = ""; Node n = null; if ((n = nnm.getNamedItem("listObjectName")) != null) listObjectName = n.getNodeValue(); String child = ""; if ((n = nnm.getNamedItem("child")) != null) child = n.getNodeValue(); String xpath = ""; if ((n = nnm.getNamedItem("xPath")) != null) xpath = n.getNodeValue(); ListMapping listMapping = new ListMapping(listObjectName, child, xpath); if ((n = nnm.getNamedItem("reference")) != null) listMapping.setReference(n.getNodeValue()); node = node.getFirstChild(); while (node != null) { if (node.getNodeType() == Node.ELEMENT_NODE) { if (node.getNodeName().equalsIgnoreCase(XML_LIST)) { listMapping.addMapping(populateList(node, parent)); } else { FieldMapping fieldMapping = FieldMapping.fromXML(parent, (org.w3c.dom.Element) node); listMapping.addMapping(fieldMapping); } } node = node.getNextSibling(); } return listMapping; } /** * Parse the <code><value></code> children of a * <code><valueset></code> element. * <p> * * @param element * A DOM <i>Node</i> encapsulating a * <code><valueset></code> element * @param parent * The parent ValueSet instance */ protected void populateValueSet(Node element, ValueSet parent) throws ADKConfigException, ADKMappingException { NodeList nl = element.getChildNodes(); for (int i = 0; i < nl.getLength(); i++) { Node n = nl.item(i); if (n.getNodeType() == Node.ELEMENT_NODE) { if (n.getNodeName().equalsIgnoreCase("value")) { String name = getAttribute(n, "name"); if (name == null) throw new ADKConfigException( "<value> name= attribute is required"); String title = getAttribute(n, "title"); String sifValue = XMLUtils.getText(n); parent.define(name, sifValue, title, n); String def = XMLUtils.getAttribute(n, "default"); if (def != null) { String ifNull = XMLUtils.getAttribute(n, "ifnull"); boolean renderIfDefault = false; if (ifNull != null) { // cool phrase! renderIfDefault = ifNull .equalsIgnoreCase("default"); } boolean inboundDefault = false; boolean outboundDefault = false; if (def.equalsIgnoreCase("both") || def.equalsIgnoreCase("true")) { inboundDefault = true; outboundDefault = true; } if (!inboundDefault && def.equalsIgnoreCase("inbound")) { inboundDefault = true; } if (!outboundDefault && def.equalsIgnoreCase("outbound")) { outboundDefault = true; } if (inboundDefault) { parent.setAppDefault(name, renderIfDefault); } if (outboundDefault) { parent.setSifDefault(sifValue, renderIfDefault); } } } } } } /** * Gets the Mappings object with the specified ID. * <p> * * Unlike the <code>select</code> methods, <code>getMappings</code> should * be called when the agent knows the specific Mappings object it is going * to use to perform a mapping operation. Conversely, the * <code>select</code> methods select the most appropriate Mappings object * based on the ZoneId, SourceId, and SIFVersion values passed to those * methods. * <p> * * @param id * The unique string identifier of the Mappings object to return. * * @return The Mappings instance with the specified ID, or <code>null</code> * if no match was found. * * @exception ADKMappingException * thrown if this method is not called on the root Mappings * container */ public Mappings getMappings(String id) throws ADKMappingException { Mappings[] ch = getChildren(); for (int i = 0; i < ch.length; i++) { if (ch[i].fId != null && ch[i].fId.equals(id)) return ch[i]; } return null; } /** * Selects the appropriate Mappings object to use for <code>map</code> * operations. Call this method on the root Mappings object to obtain a * Mappings instance, then call its <code>map</code> method to perform a * mapping operation. * <p> * * The selection process is as follows: * * <ul> * <li> * All children of this Mappings are evaluated as a group in a <b>flat * list</b>. The group is ordered by "restrictiveness". The more filters * defined for a Mappings the more restrictive it is considered. Thus, a * Mappings that defines a ZoneId, SourceId, and SIFVersion filter will be * evaluated before a less-restrictive Mappings that defines only a SourceId * filter. Because children inherit the filters of their parent, a child can * never be less restrictive than its parent. Two or more Mappings with * equal restrictiveness are evaluated in natural order, so care should be * taken to not organize child Mappings objects with equal filters or the * first one present will always be selected. <br/> * <br/> * </li> * <li> * The ordered Mappings are evaluated sequentially. For each, the ZoneId, * SourceId, and Version are compared against any filters in effect for the * Mappings instance. Any Mappings that does not pass the criteria is * eliminated from the list of candidates. <br/> * <br/> * </li> * <li> * If no child Mappings pass the selection process, the Mappings on which * this method is called is returned (i.e. self is returned) This ensures * that this method always returns a non-null Mappings instance to the * caller. <br/> * <br/> * </li> * </ul> * * @param zoneId * Restricts the selection of a Mappings object to only those * that allow this ZoneId. This parameter may be null. * @param sourceId * Restricts the selection of a Mappings object to only those * that allow this SourceId. This parameter may be null. * @param version * Restricts the selection of a Mappings object to only those * that allow this version of SIF. This parameter may be null. * * @return The first Mappings child instance that matches the criteria or * null if no match was found * @throws ADKMappingException * If this method is called on Mappings instance other than * those directly under the Root instance */ public Mappings select(String zoneId, String sourceId, SIFVersion version) throws ADKMappingException { if (fParent == null) throw new ADKMappingException( "Mappings.select cannot be called on the root-level Mappings container; it can only be called on one of the root's children", null); if (fParent != getRoot()) throw new ADKMappingException( "Mappings.select can only be called on a top-level Mappings object (i.e. a child of the root Mappings container)", null); // Optimization: If no children, return self if (getChildCount() == 0) return this; // Group all of the child Mappings, if any, in a flat list List<Mappings> v = new Vector<Mappings>(); v.add(this); groupDescendents(this, v); // Order all Mappings by "restrictiveness" Candidate[] candidates = new Candidate[v.size()]; for (int i = 0; i < candidates.length; i++) candidates[i] = new Candidate(v.get(i)); Arrays.sort(candidates, new Comparator<Candidate>() { public int compare(Candidate c1, Candidate c2) { if (c1.restrictiveness < c2.restrictiveness) return 1; else if (c1.restrictiveness > c2.restrictiveness) return -1; return 0; } public boolean equals(Object o) { return o.equals(this); } }); Mappings selection = this; int eval = 0, highScore = 0; for (int i = 0; i < candidates.length; i++) { int score = 0; if (zoneId != null) { eval = candidates[i].fMapping.allowsZoneId(zoneId); if (eval == -1) continue; score += eval; } if (sourceId != null) { eval = candidates[i].fMapping.allowsSourceId(sourceId); if (eval == -1) continue; score += eval; } if (version != null) { eval = candidates[i].fMapping.allowsVersion(version); if (eval == -1) continue; score += eval; } if (score > highScore) { highScore = score; selection = candidates[i].fMapping; } } return selection; } /** * Selects an appropriate MappingsContext object to use for an inbound * <code>map</code> operation. Call this method on the root Mappings object * to obtain a MappingsContext instance, then call its <code>map</code> * method to perform an inbound mapping operation. * * @param elementDef * The ElementDef of the Element being mapped to * @param version * The SIFVersion to use when evaluating mappings XPaths. * @param zoneId * The ID of the zone that this mappings operation is being * performed on * @param sourceId * The Source ID of the destination agent that this mappings is * being performed for * * @return a MappingsContext that can be used for evaluating mappings * @throws ADKMappingException */ public MappingsContext selectInbound(ElementDef elementDef, SIFVersion version, String zoneId, String sourceId) throws ADKMappingException { return selectContext(MappingsDirection.INBOUND, elementDef, version, zoneId, sourceId); } /** * Selects an appropriate MappingsContext object to use for an inbound * <code>map</code> operation. Call this method on the root Mappings object * to obtain a MappingsContext instance, then call its <code>map</code> * method to perform an inbound mapping operation. * * @param elementDef * The ElementDef of the Element being mapped to * @param message * The SIF Message that is being mapped * @return a MappingsContext that can be used for evaluating mappings * @throws ADKMappingException */ public MappingsContext selectInbound(ElementDef elementDef, SIFMessageInfo message) throws ADKMappingException { return selectContext(MappingsDirection.INBOUND, elementDef, message.getSIFVersion(), message.getZone().getZoneId(), message.getSourceId()); } /** * Selects an appropriate MappingsContext object to use for an outbound * <code>map</code> operation. Call this method on the root Mappings object * to obtain a MappingsContext instance, then call its <code>map</code> * method to perform an outbound mapping operation. * * @param elementDef * The ElementDef of the Element being mapped to * @param version * The SIFVersion to use when evaluating mappings XPaths. * @param zoneId * The ID of the zone that this mappings operation is being * performed on * @param sourceId * The Source ID of the destination agent that this mappings is * being performed for * * @return a MappingsContext that can be used for evaluating mappings * @throws ADKMappingException */ public MappingsContext selectOutbound(ElementDef elementDef, SIFVersion version, String zoneId, String sourceId) throws ADKMappingException { return selectContext(MappingsDirection.OUTBOUND, elementDef, version, zoneId, sourceId); } /** * Selects an appropriate MappingsContext object to use for an outbound * <code>map</code> operation. Call this method on the root Mappings object * to obtain a MappingsContext instance, then call its <code>map</code> * method to perform an outbound mapping operation. * * @param elementDef * The ElementDef of the Element being mapped to * @param message * The SIF Message that is being used to determine targets for * this mapping * @return a MappingsContext that can be used for evaluating mappings * @throws ADKMappingException */ public MappingsContext selectOutbound(ElementDef elementDef, SIFMessageInfo message) throws ADKMappingException { return selectContext(MappingsDirection.OUTBOUND, elementDef, message.getLatestSIFRequestVersion(), message.getZone() .getZoneId(), message.getSourceId()); } /** * Selects an appropriate MappingsContext object to use for <code>map</code> * operations. Call this method on the root Mappings object to obtain a * MappingsContext instance, then call its <code>map</code> method to * perform a mapping operation. * * @param direction * The MappingsDirection that this mapping will use * @param elementDef * The ElementDef of the Element being mapped to * @param version * The SIFVersion to use when evaluating mappings XPaths. * @param zoneId * The ID of the zone that this mappings operation is being * performed on * @param sourceId * The Source ID of the destination agent that this mappings is * being performed for * @return a MappingsContext that can be used for evaluating mappings * @throws ADKMappingException */ private MappingsContext selectContext(MappingsDirection direction, ElementDef elementDef, SIFVersion version, String zoneId, String sourceId) throws ADKMappingException { // Select the mappings instance Mappings m = select(zoneId, sourceId, version); // Create a mappings context, that retains the filters and metadata // associated // with this mappings operation MappingsContext mc = MappingsContext.create(m, direction, version, elementDef); return mc; } /** * Recursively adds all children of the Mappings to the supplied Vector */ private void groupDescendents(Mappings m, List<Mappings> v) { if (m.fChildren != null) { for (Iterator it = m.fChildren.values().iterator(); it.hasNext();) { Mappings ch = (Mappings) it.next(); v.add(ch); groupDescendents(ch, v); } } } /** * Produce a table of field values from a SIF Data Object using an inbound * mapping operation. * <p> * * This <code>map</code> method populates the supplied data adaptor with * element or attribute values from the SIFDataObject by evaluating all * field rules defined by this Mappings object for the associated SIF Data * Object type. * <p> * * This method is intended to obtain element and attribute values from a * SIFDataObject <i>consumed</i> by the agent when processing SIF Events or * SIF Responses. In contrast, the other form of the <code>map</code> method * is intended to populate a new SIFDataObject instance when an agent is * <i>publishing</i> objects to a zone. * <p> * * * @param dataObject * The SIFDataObject from which to retrieve element and attribute * values from when performing the mapping operation. * * @param results * A FieldAdaptor to receive the results of the mapping, * * @exception ADKMappingException * thrown if an error occurs while evaluating a field rule */ public void mapInbound(SIFDataObject dataObject, FieldAdaptor results) throws ADKMappingException { mapInbound(dataObject, results, ADK.getSIFVersion()); } /** * Produce a table of field values from a SIF Data Object using an inbound * mapping operation. * <p> * * This form of the <code>map</code> method allows the client to specify * whether it is performing an inbound or outbound mapping operation. * Currently, the direction flag is used to invoke automatic ValueSet * translations on fields that have a <i>ValueSet</i> attribute. * <p> * * @param data * The SIFDataObject from which to retrieve element and attribute * values from when performing the mapping operation. * * @param results * A <code>FieldAdaptor</code> instance that stores fields as the * result of the mapping operation. * * @param version * The SIFVersion associated with the mapping operation. For * inbound SIF_Event and SIF_Response messages, this value should * be obtained by calling <code>getSIFVersion</code> on the * <i>SIFMessageInfo</i> parameter passed to the message handler. * For inbound SIF_Request messages, it should be obtained by * calling the <code>SIFMessageInfo.getSIFRequestVersion</code> * method. For outbound messages, this value should be obtained * by calling <code>ADK.getSIFVersion</code> to get the version * of SIF the class framework was initialized with. Note when * this parameter is <code>null</code>, no SIF Version filtering * will be applied to field mapping rules. * * @exception ADKMappingException * thrown if an error occurs while evaluating a field rule * * @since ADK 1.5 */ public void mapInbound(SIFDataObject data, FieldAdaptor results, SIFVersion version) throws ADKMappingException { ObjectMapping om = getRules( data.getElementDef().tag(data.getSIFVersion()), true); if (om != null) { SIFXPathContext xpathContext = SIFXPathContext.newSIFContext(data, version); for (Map.Entry<String, String> prefixMapping : om.getAllNamespaceURIs(true).entrySet()) { xpathContext.registerNamespace(prefixMapping.getKey(), prefixMapping.getValue()); } if (results instanceof Variables) { xpathContext.setVariables((Variables) results); } List<Mapping> list = getRulesList(version, om, MappingsDirection.INBOUND); mapInbound(xpathContext, results, data, list, version); } } /** * Do an internal inbound mapping operation * * @param xpathContext * A the SIFXPathContext to use * @param results * The FieldAdaptor to store the mapped fields into * @param fields * An iterable collection of FieldMappings. IMPORTANT! The * MappingsFilter needs to be already evaluated for this * collection. This method does not evaluate the filter * @param version * The SIFVersion to use * @throws ADKMappingException */ void mapInbound(SIFXPathContext xpathContext, FieldAdaptor results, SIFElement inboundObject, List<Mapping> fields, SIFVersion version) throws ADKMappingException { // if (BuildOptions.PROFILED) { // ProfilerUtils // .profileStart( // String.valueOf(openadk.profiler.api.OIDs.ADK_INBOUND_TRANSFORMATIONS), // inboundObject.getElementDef(), null); // } try { mapInboundInternal(xpathContext, results, fields, version); } finally { if (BuildOptions.PROFILED) ProfilerUtils.profileStop(); } } /** * @param xpathContext * @param results * @param fields * @param version * @throws ADKMappingException */ private void mapInboundInternal(SIFXPathContext xpathContext, FieldAdaptor results, List<Mapping> fields, SIFVersion version) throws ADKMappingException { Mapping lastRule = null; try { for (Mapping mapping : fields) { lastRule = mapping; // JEN ListMapping if (mapping instanceof FieldMapping) { FieldMapping rule = (FieldMapping) mapping; SIFSimpleType val = mapInboundRule(rule, results, xpathContext, version); if (val != null) results.setSIFValue(rule.getFieldName(), val, rule); } else if (mapping instanceof ListMapping) { if (results instanceof ComplexFieldAdaptor) { ComplexFieldAdaptor complexFieldAdaptor = (ComplexFieldAdaptor) results; // Create Initial list mapInboundList((ListMapping) mapping, complexFieldAdaptor, xpathContext, version); } else { ADK.getLog() .warn("List Mapping [" + mapping.toString() + "] defined, but " + "a ComplexFieldAdaptor was not used to perform the mapping"); } } } } catch (Exception e) { if (lastRule != null) { throw new ADKMappingException("Unable to evaluate field rule: " + lastRule.toString() + " : " + e.getMessage(), null, e); } throw new ADKMappingException(e.toString(), null, e); } } private void mapInboundList(ListMapping listMapping, ComplexFieldAdaptor adaptor, SIFXPathContext context, SIFVersion version) throws ADKSchemaException, ADKMappingException { // Use XPath to get a list of matchable nodes CompiledExpression ce = SIFXPathContext.compile(listMapping.getXPath()); Iterator iterator = ce.iteratePointers(context); if (iterator != null && iterator.hasNext()) { IterableFieldAdaptor ifa = adaptor.addChildRelationship(listMapping .getChild()); while (iterator.hasNext()) { Pointer p = (Pointer) iterator.next(); SIFXPathContext rowContext = (SIFXPathContext) SIFXPathContext .newContext(context, p.getNode()); FieldAdaptor row = ifa.addRow(); List<Mapping> listRules = getListMappingRules(listMapping, version, MappingsDirection.OUTBOUND); if (listRules != null) { mapInboundInternal(rowContext, row, listRules, version); } } } /* * * * SIFList listElements = xpathRule.evaluate(xpathContext); * * if (listElements != null) { // String name = baseName + "/" + * listMapping.getXPath(); String name = listMapping.getXPath(); * * IterableFieldAdaptor table = null; if (parent == null) table = * adaptor.addChildRelationship(name); else table = * parent.addChildRelationship(name); * * for (int i = 0; i < listElements.size(); ++ i) { ComplexFieldAdaptor * row = (ComplexFieldAdaptor) table.addRow(); //SIFElement element = * listElements.get(i); //elementName = * element.getElementDef().getClassName(); * * // Create new XPathContext based on this list SIFXPathContext * currentContext = SIFXPathContext.newSIFContext(listElements.get(i)); * * for (Mapping mapping : listMapping.getMappings()) { if (mapping * instanceof ListMapping) { mapInboundList((ListMapping)mapping, * adaptor, currentContext, version, name, row); } else { FieldMapping * fieldMapping = (FieldMapping) mapping; SIFSimpleType value = * mapInboundRule(fieldMapping, adaptor, currentContext, version); if * (value != null) row.setSIFValue( fieldMapping.getKey(), value, null * ); } } } } */ } private SIFSimpleType mapInboundRule(FieldMapping rule, FieldAdaptor adaptor, SIFXPathContext xpathContext, SIFVersion version) throws ADKSchemaException { SIFSimpleType retval = null; if (!adaptor.hasField(rule.getFieldName())) { retval = rule.evaluate(xpathContext, version, true); if (retval != null) { if (rule.getValueSetID() != null && retval instanceof SIFString) { String currentValue = retval.toString(); // Perform automatic ValueSet translation // TT 199. Perform a more detailed valueset translation. // If there is a default value set, use it if there is // no match found in the value set ValueSet vs = getValueSet(rule.getValueSetID(), true); if (vs != null) { currentValue = vs.translateReverse(currentValue, rule.getDefaultValue()); } retval = new SIFString(currentValue); } } } return retval; } /** * Populate a SIFDataObject from values in the supplied HashMap by * evaluating all field rules for the associated SIF Data Object type. For * each key in the <code>FieldAdaptor</code>, the corresponding field rule * is evaluated to assign the <code>FieldAdaptor</code> value to the * appropriate element or attribute in the SIFDataObject. If a field is not * represented in the <code>FieldAdaptor</code>, its associated rule will * not be evaluated. * <p> * * This method is intended to populate a new SIFDataObject instance when an * agent is <i>publishing</i> objects to a zone. The other form of the * <code>map</code> method is intended to obtain element and attribute * values from a SIFDataObject <i>consumed</i> by the agent when processing * SIF Events or SIF Responses. * <p> * * To use this method, * <p> * * <ol> * <li> * Create a <code>FieldAdaptor</code> and populate it with all known field * values. The key of each entry must be the local application-defined name * of the field -- the same field name used in field rules -- and the value * is the string value to assign to the corresponding element or attribute * in the SIFDataObject when a field rule matches.</li> * <li> * Create a SIFDataObject instance of the appropriate type (e.g. a * openadk.library.student.StudentPersonal instance if the * mapping will be applied to an incoming <StudentPersonal> message).</li> * <li> * Call this <code>map</code> method to apply all field values in the * <code>FieldAdaptor</code> to corresponding elements and/or attributes in * the SIFDataObject. The method first looks up the ObjectMapping instance * corresponding to the SIF Data Object type. If no ObjectMapping has been * defined for the object type, no action is taken and the method returns * successfully without exception. Otherwise, all field rules defined by the * ObjectMapping are evaluated in order.</li> * </ol> * * @param adaptor * A <code>FieldAdaptor</code> instance which returns field * values used to populate individual elements within the * supplied <code>SIFDataObject</code>. * * @param dataObject * The SIFDataObject to assign field values to * * @exception ADKMappingException * thrown if an error occurs while evaluating a field rule */ public void mapOutbound(FieldAdaptor adaptor, SIFDataObject dataObject) throws ADKMappingException { mapOutbound(adaptor, dataObject, new DefaultValueBuilder(adaptor)); } /** * Populate a SIFDataObject from values in the supplied * <code>FieldAdaptor</code>. * <p> * * This form of the <code>map</code> method that accepts a custom * <code>ValueBuilder</code> implementation to evaluate value expressions in * XPath-like query strings. The <code>map</code> method uses the * DefaultValueBuilder class as its built-in implementation, but you can * supply your own by calling this method instead. * <p> * * @param adaptor * A <code>FieldAdaptor</code> instance which returns field * values used to populate individual elements within the * supplied <code>SIFDataObject</code>. * * @param dataObject * The SIFDataObject to assign field values to * * @param valueBuilder * A custom ValueBuilder implementation to evaluate value * expressions in XPath-like query strings * * @exception ADKMappingException * thrown if an error occurs while evaluating a field rule */ public void mapOutbound(FieldAdaptor adaptor, SIFDataObject dataObject, ValueBuilder valueBuilder) throws ADKMappingException { mapOutbound(adaptor, dataObject, valueBuilder, ADK.getSIFVersion()); } /** * Populate a SIFDataObject from values in the supplied * <code>FieldAdaptor</code>. * <p> * * This form of the <code>map</code> method allows the client to specify the * SIFVersion that is in effect for this mapping operation * * @param adaptor * A <code>FieldAdaptor</code> instance which returns field * values used to populate individual elements within the * supplied <code>SIFDataObject</code>. * @param dataObject * The SIFDataObject to assign field values to * @param version * The SIFVersion that should be used when evaluating this * mappings operation. The SIFVersion influences the XPaths that * are used to set elements in the data object * * @throws ADKMappingException */ public void mapOutbound(FieldAdaptor adaptor, SIFDataObject dataObject, SIFVersion version) throws ADKMappingException { mapOutbound(adaptor, dataObject, new DefaultValueBuilder(adaptor, ADK .DTD().getFormatter(version)), version); } /** * Populate a SIFDataObject from values in the supplied * <code>FieldAdaptor</code>. * <p> * * This form of the <code>map</code> method allows the caller to specify * whether it is performing an inbound or outbound mapping operation, as * well as the version of SIF associated with the SIF Data Object that's * being mapped. These values are used to filter field mapping rules. The * direction flag is also used to invoke automatic ValueSet translations on * fields that have a <i>ValueSet</i> attribute. * <p> * * @param adaptor * A <code>FieldAdaptor</code> instance which returns field * values used to populate individual elements within the * supplied <code>SIFDataObject</code>. * @param element * The SIFDataObject to assign field values to * @param valueBuilder * A custom ValueBuilder implementation to evaluate value * expressions in XPath-like query strings * @param version * The SIFVersion associated with the mapping operation. For * inbound SIF_Event and SIF_Response messages, this value should * be obtained by calling <code>getSIFVersion</code> on the * <i>SIFMessageInfo</i> parameter passed to the message handler. * For inbound SIF_Request messages, it should be obtained by * calling the <code>SIFMessageInfo.getSIFRequestVersion</code> * method. For outbound messages, this value should be obtained * by calling <code>ADK.getSIFVersion</code> to get the version * of SIF the class framework was initialized with. Note when * this parameter is <code>null</code>, no SIF Version filtering * will be applied to field mapping rules. * * @exception ADKMappingException * thrown if an error occurs while evaluating a field rule * * @since ADK 1.5 */ public void mapOutbound(FieldAdaptor adaptor, SIFElement element, ValueBuilder valueBuilder, SIFVersion version) throws ADKMappingException { ObjectMapping om = getRules(element.getElementDef().tag(version), true); if (om != null) { SIFXPathContext xpathContext = SIFXPathContext.newSIFContext( element, version); for (Map.Entry<String, String> prefixMapping : om.getAllNamespaceURIs(true).entrySet()) { xpathContext.registerNamespace(prefixMapping.getKey(), prefixMapping.getValue()); } if (adaptor instanceof Variables) { xpathContext.setVariables((Variables) adaptor); } List<Mapping> list = getRulesList(version, om, MappingsDirection.OUTBOUND); mapOutbound(xpathContext, adaptor, element, list, valueBuilder, version); } } private List<Mapping> getRulesList(SIFVersion version, ObjectMapping om, MappingsDirection direction) { List<Mapping> list = om.getAllRulesList(true); // Remove any items that should be filtered out ListIterator<Mapping> it = list.listIterator(); while (it.hasNext()) { Mapping fm = it.next(); MappingsFilter filt = fm.getFilter(); // Filter out this rule? if (filt != null) { if (!filt.evalDirection(direction) || !filt.evalVersion(version)) { it.remove(); } } } return list; } void mapOutbound(SIFXPathContext context, FieldAdaptor adaptor, SIFElement dataObject, List<Mapping> fields, ValueBuilder valueBuilder, SIFVersion version) throws ADKMappingException { // if (BuildOptions.PROFILED) { // ProfilerUtils // .profileStart( // String.valueOf(openadk.profiler.api.OIDs.ADK_OUTBOUND_TRANSFORMATIONS), // dataObject.getElementDef(), null); // } try { mapOutboundInternal(context, adaptor, dataObject, fields, valueBuilder, version); } finally { if (BuildOptions.PROFILED) ProfilerUtils.profileStop(); } } /** * @param context * @param adaptor * @param dataObject * @param fields * @param valueBuilder * @param version * @throws ADKMappingException */ private void mapOutboundInternal(SIFXPathContext context, FieldAdaptor adaptor, SIFElement dataObject, List<Mapping> fields, ValueBuilder valueBuilder, SIFVersion version) throws ADKMappingException { SIFFormatter textFormatter = ADK.getTextFormatter(); Mapping lastRule = null; try { for (Mapping mapping : fields) { lastRule = mapping; if (mapping instanceof FieldMapping) { FieldMapping fm = (FieldMapping) mapping; SIFSimpleType mappedValue = mapOutBoundRule(fm, context, adaptor, dataObject, valueBuilder, version); if (mappedValue != null) { XPathRule rule = (XPathRule) fm.getRule(); // At this point, mappedValue should not be null. We are // committed // to building out the path and setting the value. NodePointer pointer = null; // JEN Fix Element/Element path create error (i.e. // AlertMessage/AlertMessage) /* * if * (dataObject.getElementDef().name().equals(fm.getRule * ().toString())) { * dataObject.setField(dataObject.getElementDef(), * mappedValue); } else { */ pointer = rule.createPath(context, version); /* } */ // If the element/attribute does not have a value, // assign one. // If it does have a value, it was already assigned by // the XPath // rule in the lookupByXPath method above and should not // be // changed. // if (pointer != null) { Object pointedValue = pointer.getValue(); if (pointedValue instanceof Element) { Element pointedElement = (Element) pointer .getValue(); SIFSimpleType elementValue = pointedElement .getSIFValue(); if (elementValue == null || elementValue.getValue() == null) { if (mappedValue != null && mappedValue instanceof SIFString) { // Now that we have the actual element, // we may need to create convert the // data if we were unable to resolve the // TypeConverter above. This only // happens // in cases involving surrogates where // the rule.lookupTargetDef( // dataObject.getElementDef() ); // fails to find the target ElementDef SIFTypeConverter converter = pointedElement.getElementDef().getTypeConverter(); if (converter != null && converter.getDataType() != mappedValue.getDataType()) { mappedValue = converter.parse( textFormatter,mappedValue.toString()); } } // This check for null should really not be // necessary, // however keepingit in for now if (mappedValue != null) { pointedElement.setSIFValue(mappedValue); } } } else if ( pointer.getNode() instanceof org.w3c.dom.Node){ if ( mappedValue != null ) { org.w3c.dom.Node node = (org.w3c.dom.Node)pointer.getNode(); node.setTextContent(mappedValue.toString()); } } } } } else if (mapping instanceof ListMapping) { System.out.println("Mapping is " + mapping); ListMapping listMapping = (ListMapping) mapping; if (adaptor instanceof ComplexFieldAdaptor) mapOutBoundList(context, (ComplexFieldAdaptor) adaptor, listMapping, valueBuilder, version); } } } catch (Exception e) { if (lastRule != null) { throw new ADKMappingException("Unable to evaluate field rule: " + lastRule.toString() + " : " + e.getMessage(), null, e); } throw new ADKMappingException(e.toString(), null, e); } } /* * Start of Andys Pseudo code * * private void mapOutBoundList(SIFXPathContext context, ComplexFieldAdaptor * adaptor, SIFElement dataObject, ListMapping listMapping ,ValueBuilder * valueBuilder,SIFVersion version ) throws ADKMappingException { * * IterableFieldAdaptor rowList = * adaptor.getChildRelationship(listMapping.getChild()); if (rowList != * null) { for (FieldAdaptor row : rowList) { NodePointer pointer = * listRule.createPath(context, version); } } * * } */ private void mapOutBoundList(SIFXPathContext context, ComplexFieldAdaptor adaptor, ListMapping listMapping, ValueBuilder valueBuilder, SIFVersion version) throws ADKMappingException { IterableFieldAdaptor rowList = adaptor.getChildRelationship(listMapping .getChild()); if (rowList != null) { for (FieldAdaptor row : rowList) { // for each row, execute the Xpath on the parent, which returns // individual rows // TODO: The following addition to the XPath forces the // SIFXPathContext class to // Create additional child rows each time. This should be // cleaned up using a more // elegant manner for readability. We also may need to support // xPath predicate // conditions in the List/@XPath attribute in mappings String fixedUp = listMapping.getXPath() + "[adk:x() and adk:x()]"; Pointer p = context.createPath(fixedUp); if (!(p.getNode() instanceof SIFElement)) { throw new IllegalStateException( context.getContextBean().getClass().getName() + "/" + listMapping.getXPath() + ": Does not result in a complex type, which is required for list mappings."); } JXPathContext jContext = SIFXPathContext.newContext(context, p.getNode()); SIFXPathContext rowContext = (SIFXPathContext) jContext; // Determine if this list mapping references an external node or // an embedded list of fields List<Mapping> listRules = getListMappingRules(listMapping, version, MappingsDirection.OUTBOUND); if (listRules != null) { mapOutbound(rowContext, row, (SIFElement) p.getNode(), listRules, valueBuilder, version); } } } } /** * @param listMapping * @param version * @param direction * @return */ private List<Mapping> getListMappingRules(ListMapping listMapping, SIFVersion version, MappingsDirection direction) { List<Mapping> listFields = null; String reference = listMapping.getReference(); if (reference != null && reference.length() > 0) { ObjectMapping om = this.getRules(reference, true); if (om != null) { listFields = this.getRulesList(version, om, direction); } } else { listFields = listMapping.getMappings(); } return listFields; } private SIFSimpleType mapOutBoundRule(FieldMapping fm, SIFXPathContext context, FieldAdaptor adaptor, SIFElement dataObject, ValueBuilder valueBuilder, SIFVersion version) throws ADKMappingException, ADKTypeParseException { SIFSimpleType mappedValue = null; SIFFormatter textFormatter = ADK.getTextFormatter(); String fieldName = fm.getAlias(); if (fieldName == null) { fieldName = fm.getFieldName(); } if (fieldName == null || fieldName.length() == 0) { throw new ADKMappingException("Mapping rule for " + dataObject.getElementDef().name() + "[" + fm.getFieldName() + "] must specify a field name", null); } if (adaptor.hasField(fieldName) || fm.hasDefaultValue()) { // // For outbound mapping operations, only process // XPathRules. All other rule types, like OtherIdRule, // are only intended to be used for inbound mappings. // if (fm.getRule() instanceof XPathRule) { XPathRule rule = (XPathRule) fm.getRule(); // Lookup or create the element/attribute referenced by the rule String ruledef = rule.getXPath(); if (ruledef == null || ruledef.trim().length() == 0) { throw new ADKMappingException( "Mapping rule for " + dataObject.getElementDef().name() + "[\"" + fieldName + "\"] must specify a path to an element or attribute", null); } // TT 199 If the FieldMapping has an "ifnull" value of // "suppress", // don't render a result // Determine if this element should be created before attempting // to create it // If the value resolves to null and the IFNULL_SUPPRESS flag is // set, the element // should not be created. That's why we have to look up the // ElementDef first SIFTypeConverter typeConverter = null; ElementDef def = rule.lookupTargetDef(dataObject .getElementDef()); if (def != null) { typeConverter = def.getTypeConverter(); } if (typeConverter == null) { typeConverter = SIFTypeConverters.STRING; // TODO: Perhaps the following exception should be thrown // when // in STRICT mode // throw new ADKMappingException( "Element {" + def.name() + // "} from rule \"" + ruledef + // "\" does not have a data type definition.", null ); } // Perform a valueset translation, if applicable if (fm.getValueSetID() != null) { Object objectValue = adaptor.getValue(fieldName); if (objectValue != null) { String textValue = objectValue.toString(); // Perform automatic ValueSet translation ValueSet vs = getValueSet(fm.getValueSetID(), true); if (vs != null) { // TT 199. Perform a more detailed valueset translation. // If there is a default value for this field, use it if // there is // no match found in the value set textValue = vs.translate(textValue, fm.getDefaultValue()); } mappedValue = typeConverter.getSIFSimpleType(textValue); } } else { try { mappedValue = adaptor.getSIFValue(fieldName, typeConverter, fm); } catch (NumberFormatException nfe) { // TT 2998 Add support for ignoring type parse exceptions // on Outbound data. If it fails to parse, ignore if ((ADK.debug & ADK.DBG_MESSAGING_DETAILED) > 0) { // TODO: This decision should really be based upon the // adk.messaging.strictTypeParsing property. However, // mappings // don't currently have access to ADK Properties. // Therefore, this // needs to be set somewhere on the Mappings class String identifier = dataObject.getXmlId(); ADK.getLog().warn( "Unable to parse value for outbound mapping for element" + identifier == null ? "" : "(" + identifier + ")" + ". Ignored error: " + nfe.getMessage(), nfe); } return null; } } boolean usedDefault = false; if (mappedValue == null) { // If the FieldMapping has a Default value, use that, unless // it is explicitly suppressed if (fm.getNullBehavior() != FieldMapping.IFNULL_SUPPRESS && fm.hasDefaultValue()) { mappedValue = fm.getDefaultValue(typeConverter, textFormatter); usedDefault = true; } else { return null; } } if (!usedDefault) { String valueExpression = rule.getValueExpression(); if (valueExpression != null) { // This XPath rule has a value assignment expression at // the end of it String value = valueBuilder.evaluate(valueExpression); mappedValue = typeConverter.parse(textFormatter, value); } } // If we have a null value to assign at this point, move on to // the next rule if (mappedValue == null) { return null; } else if (mappedValue.getValue() == null && fm.getNullBehavior() == FieldMapping.IFNULL_SUPPRESS) { return null; } } } return mappedValue; } /** * Returns the SourceId filters in effect for this Mappings instance. A * Mappings instances always inherits the filters of its ancestry. * <p> * * @return An array of SourceIds or null if this Mappings object applies to * all SIF Agents * @see #setSourceIdFilter */ public String[] getSourceIdFilter() { if (fParent == getRoot()) return fSourceIds; HashMap<String, Object> m = new HashMap<String, Object>(); Mappings parent = this; while (parent.fParent != null) { String[] list = parent.fSourceIds; if (list != null) { for (int i = 0; i < list.length; i++) m.put(list[i], null); } parent = parent.fParent; } int i = 0; String[] arr = new String[m.size()]; for (Iterator it = m.keySet().iterator(); it.hasNext();) arr[i++] = (String) it.next(); return arr; } /** * Returns the SourceId filters in effect for this Mappings instance as a * comma-delimited string. * <p> * * @return A comma-delimited string of the SourceId in the filter */ public String getSourceIdFilterString() { StringBuffer b = new StringBuffer(); if (fSourceIds != null) { for (int i = 0; i < fSourceIds.length; i++) { if (b.length() > 0) b.append(","); b.append(fSourceIds[i]); } } return b.toString(); } /** * Returns the Zone ID filters in effect for this Mappings instance as a * comma-delimited string. * <p> * * @return A comma-delimited string of the SourceId in the filter */ public String getZoneIdFilterString() { StringBuffer b = new StringBuffer(); if (fZoneIds != null) { for (int i = 0; i < fZoneIds.length; i++) { if (b.length() > 0) b.append(","); b.append(fZoneIds[i]); } } return b.toString(); } /** * Returns the SIF Version filters in effect for this Mappings instance as a * comma-delimited string. * <p> * * @return A comma-delimited string of the SourceId in the filter */ public String getSIFVersionFilterString() { StringBuffer b = new StringBuffer(); if (fSifVersions != null) { for (int i = 0; i < fSifVersions.length; i++) { if (b.length() > 0) b.append(","); b.append(fSifVersions[i].toString()); } } return b.toString(); } /** * Returns the ZoneId filters in effect for this Mappings instance. A * Mappings instances always inherits the filters of its ancestry. * <p> * * @return An array of ZoneIds or null if this Mappings object applies to * all zones * @see #setZoneIdFilter */ public String[] getZoneIdFilter() { if (fParent == getRoot()) return fZoneIds; HashMap<String, Object> m = new HashMap<String, Object>(); Mappings parent = this; while (parent.fParent != null) { String[] list = parent.fZoneIds; if (list != null) { for (int i = 0; i < list.length; i++) m.put(list[i], null); } parent = parent.fParent; } int i = 0; String[] arr = new String[m.size()]; for (Iterator it = m.keySet().iterator(); it.hasNext();) arr[i++] = (String) it.next(); return arr; } /** * Returns the SIFVersion filters in effect for this Mappings instance. A * Mappings instances always inherits the filters of its ancestry. * <p> * * @return An array of SIFVersion objects or null if this Mappings object * applies to all versions of SIF * @see #setSIFVersionFilter */ public SIFVersion[] getSIFVersionFilter() { if (fParent == getRoot()) return fSifVersions; HashMap<SIFVersion, Object> m = new HashMap<SIFVersion, Object>(); Mappings parent = this; while (parent.fParent != null) { SIFVersion[] list = parent.fSifVersions; if (list != null) { for (int i = 0; i < list.length; i++) m.put(list[i], null); } parent = parent.fParent; } int i = 0; SIFVersion[] arr = new SIFVersion[m.size()]; for (Iterator it = m.keySet().iterator(); it.hasNext();) arr[i++] = (SIFVersion) it.next(); return arr; } /** * Determines if this nested Mappings instance allows the specified * SourceId. * <p> * * This method is only called on nested children of a Mappings object, never * on a top-level Mappings object because top-level parents always allow all * ZoneIds, SourceIds, and SIF Versions. * <p> * * @param sourceId * An agent's SourceId * * @return true if the SourceId is permitted by the SourceId filter in * effect and can therefore be considered for selection by the * <code>select</code> method; false if the SourceId is not included * in the SourceId filter and should therefore not be considered for * selection */ public int allowsSourceId(String sourceId) { // Allows all SourceIds? if (fSourceIds == null || fSourceIds.length == 0) return 0; for (int i = 0; i < fSourceIds.length; i++) { if (fSourceIds[i].equals(sourceId)) return 1; } return -1; } /** * Determines if this nested Mappings instance allows the specified ZoneId. * <p> * * This method is only called on nested children of a Mappings object, never * on a top-level Mappings object because top-level parents always allow all * ZoneIds, SourceIds, and SIF Versions. * <p> * * @param zoneId * A ZoneId * * @return true if the ZoneId is permitted by the ZoneId filter in effect * and can therefore be considered for selection by the * <code>select</code> method; false if the ZoneId is not included * in the ZoneId filter and should therefore not be considered for * selection */ public int allowsZoneId(String zoneId) { // Allows all zones? if (fZoneIds == null || fZoneIds.length == 0) return 0; for (int i = 0; i < fZoneIds.length; i++) { if (fZoneIds[i].equals(zoneId)) return 1; } return -1; } /** * Determines if this nested Mappings instance allows the specified version * of SIF * <p> * * This method is only called on nested children of a Mappings object, never * on a top-level Mappings object because top-level parents always allow all * ZoneIds, SourceIds, and SIF Versions. * <p> * * @param version * A SIFVersion instance describing a version of SIF * * @return true if the version is permitted by the SIFVersion filter in * effect and can therefore be considered for selection by the * <code>select</code> method; false if the version is not included * in the SIFVersion filter and should therefore not be considered * for selection */ public int allowsVersion(SIFVersion version) { // Allows all versions? if (fSifVersions == null || fSifVersions.length == 0) return 0; for (int i = 0; i < fSifVersions.length; i++) { if (fSifVersions[i].equals(version)) return 1; } return -1; } /** * Add a ValueSet to this Mappings instance * * @param vset * The ValueSet instance to add to this Mappings instance */ public void addValueSet(ValueSet vset) { if (fValueSets == null) fValueSets = new HashMap<String, ValueSet>(); fValueSets.put(vset.fId, vset); if (vset.fNode == null && fNode != null) { // Create a new <valueset> org.w3c.dom.Element element = fNode.getOwnerDocument() .createElement(XML_VALUESET); vset.fNode = element; fNode.appendChild(element); vset.toXml(element); } } /** * Remove a ValueSet from this Mappings instance. * <p> * * If the ValueSet has a DOM Node attached to it, it is removed from its * parent Node and dereferenced. * <p> * * @param vset * The ValueSet to remove */ public void removeValueSet(ValueSet vset) { if (fValueSets != null) { if (vset.fNode != null) { // Remove the ValueSet's node from its parent's DOM Node vset.fNode.getParentNode().removeChild(vset.fNode); // Remove all children and dereference the entrys' Nodes ValueSetEntry[] entries = vset.getEntries(); for (int i = 0; i < entries.length; i++) { if (entries[i].node != null && entries[i].node.getParentNode() != null) { entries[i].node.getParentNode().removeChild( entries[i].node); } entries[i].node = null; } } // Remove from the lookup table fValueSets.remove(vset.fId); // Dereference the node vset.fNode = null; } } /** * Remove a ValueSet from this Mappings instance. * <p> * * If a ValueSet with the specified ID is found, it is removed from this * Mappings and returned; otherwise no action is taken. If the ValueSet has * a DOM Node attached to it, it is removed from its parent Node and * dereferenced. * <p> * * @param id * The ID of the ValueSet to remove * @return The ValueSet that was removed from this Mappings instance or NULL */ public ValueSet removeValueSet(String id) { ValueSet vs = getValueSet(id, false); if (vs != null) { removeValueSet(vs); } return vs; } /** * Gets a ValueSet by ID. * <p> * * @param id * The unique ID of the ValueSet * @param inherit * true to inherit the ValueSet from the Mappings ancestry if not * found as a child of this Mappings object; false to return null * if no ValueSet is found * @return The ValueSet that was found or Null */ public ValueSet getValueSet(String id, boolean inherit) { if (!inherit) return fValueSets != null ? (ValueSet) fValueSets.get(id) : null; Mappings m = this; ValueSet vs = null; while (m != null && vs == null) { if (m.fValueSets != null) vs = m.fValueSets.get(id); m = m.getParent(); } return vs; } /** * Gets all ValueSets for this Mappings instance. * <p> * * @param inherit * true to include ValueSets from the Mappings ancestry in the * returned array, false to include only ValueSets defined by * this Mappings object * @return The ValueSets that are defined by this Mappings instance */ public ValueSet[] getValueSets(boolean inherit) { HashMap<String, ValueSet> results = new HashMap<String, ValueSet>(); Mappings m = this; do { if (m.fValueSets != null) { for (Iterator it = m.fValueSets.values().iterator(); it .hasNext();) { ValueSet vs = (ValueSet) it.next(); if (vs != null && !results.containsKey(vs.fId)) results.put(vs.fId, vs); } } m = m.getParent(); } while (m != null && inherit); int i = 0; ValueSet[] arr = new ValueSet[results.size()]; for (Iterator it = results.values().iterator(); it.hasNext();) arr[i++] = (ValueSet) it.next(); return arr; } /** * Add an ObjectMapping definition to this Mappings instance * * @param om * The ObjectMapping to add to this Mappings instance */ public void addRules(ObjectMapping om) { addRules(om, true); } /** * Add an ObjectMapping definition to this Mappings instance */ protected void addRules(ObjectMapping om, boolean buildDomTree) { if (om.fParent != null) throw new IllegalStateException( "ObjectMapping is already a child of a Mappings instance"); om.fParent = this; if (fObjRules == null) { fObjRules = new HashMap<String, ObjectMapping>(); } fObjRules.put(om.getObjectType(), om); if (om.fNode == null && buildDomTree && fNode != null) { om.fNode = fNode.getOwnerDocument().createElement(XML_OBJECT); if (om.getObjectType() != null) XMLUtils.setAttribute(om.fNode, "object", om.getObjectType()); fNode.appendChild(om.fNode); } } /** * Remove an ObjectMapping definition from this Mappings instance * * @param om * The ObjectMapping definition to remove */ public void removeRules(ObjectMapping om) { if (fObjRules != null) { if (fNode != null && om.fNode != null) { fNode.removeChild(om.fNode); } fObjRules.remove(om.getObjectType()); } } /** * Gets all ObjectMappings defined for this Mappings instance, including * those inherited by its parents. * <p> * * @return An array of all ObjectMappings */ public ObjectMapping[] getObjectMappings() { return getObjectMappings(true); } /** * Gets all ObjectMappings defined for this Mappings instance, optionally * including those inherited by its parents. * <p> * * @param inherit * True if ObjectMappings defined by parents should be included * as well. * @return An array of all ObjectMappings */ public ObjectMapping[] getObjectMappings(boolean inherit) { HashMap<String, ObjectMapping> set = new HashMap<String, ObjectMapping>(); Mappings m = this; while (m != null) { if (m.fObjRules != null) { for (Iterator it = m.fObjRules.values().iterator(); it .hasNext();) { ObjectMapping om = (ObjectMapping) it.next(); if (!set.containsKey(om.getObjectType())) set.put(om.getObjectType(), om); } } if (inherit) m = m.fParent; else m = null; } int i = 0; ObjectMapping[] arr = new ObjectMapping[set.size()]; for (Iterator it = set.values().iterator(); it.hasNext();) { arr[i++] = (ObjectMapping) it.next(); } return arr; } /** * Gets the ObjectMapping defined for the specified object type from this * Mappings instance, optionally including those inherited by its parents. * <p> * * @param objectType * The object type, such as "StudentPersoanal * @param inherit * True if an ObjectMapping from the parent Mappings instance can * be returned * @return An ObjectMapping instance */ public ObjectMapping getObjectMapping(String objectType, boolean inherit) { if (!inherit) { return fObjRules == null ? null : (ObjectMapping) fObjRules .get(objectType); } Mappings m = this; while (m != null) { if (m.fObjRules != null) { if (m.fObjRules.containsKey(objectType)) { return m.fObjRules.get(objectType); } } m = m.fParent; } return null; } /** * Return an ObjectMapping definition for a given object type. * * @param object * A SIF Data Object (e.g. "StudentPersonal") * @param inherit * True to inherit the ObjectMapping from the ancestry if this * Mappings instance does not define it * @return an ObjectMapping definition for the given object type. */ public ObjectMapping getRules(SIFDataObject object, boolean inherit) { return object != null ? getRules(object.getElementDef().name(), inherit) : null; } /** * Returns the ObjectMapping for a given object type. * <p> * * @param objType * The name of a SIF Data Object (e.g. "StudentPersonal") * @param inherit * True to inherit the ObjectMapping from the ancestry if this * Mappings instance does not define it * @return The ObjectMappints instance for the given object type */ public ObjectMapping getRules(String objType, boolean inherit) { Mappings m = this; while (m != null) { if (m.fObjRules != null) { for (Iterator it = m.fObjRules.values().iterator(); it .hasNext();) { ObjectMapping om = (ObjectMapping) it.next(); if (om.getObjectType().equals(objType)) return om; } } if (inherit) m = m.fParent; else m = null; } return null; } /** * Gets a named attribute of a Node * <p> * * @param node * The node to search * @param attr * The name of the attribute to get */ private String getAttribute(Node node, String attr) { if (node == null) return null; NamedNodeMap map = node.getAttributes(); if (map != null) { Node an = map.getNamedItem(attr); if (an != null) return an.getNodeValue(); } return null; } /** * Stores all configuration information from this Mappings instance to the * specified DOM document * * @param doc * @return The root DOM node created by this method * @throws SAXException */ public Node toDOM(Document doc) throws SAXException { return toDOM(doc, false); } /** * Produces a DOM Node graph of this Mappings object.</p> * * NOTE: This method creates the node but does not append it to the document * that is passed in. It is the responsibility of the caller to add the node * where appropriate * * @param doc * The parent document * @param renderAsRuntimeMappings * true to inherit object and field rules so this Mappings is * rendered with dynamic content as it would be evaluated at * runtime. This can be useful for diagnostics (i.e. displaying * the current state of a Mappings object), but not for rendering * to a configuration file. * @return A DOM Node graph representing this Mappings object * @throws SAXException */ public Node toDOM(Document doc, boolean renderAsRuntimeMappings) throws SAXException { if (doc == null) return null; Node mappingsNode = doc.createElement(XML_MAPPINGS); XMLUtils.setAttribute(mappingsNode, "id", fId == null ? "" : fId); if (fSifVersions != null && fSifVersions.length > 0) { StringBuffer buf = new StringBuffer(); for (int i = 0; i < fSifVersions.length; i++) { if (i != 0) buf.append(","); buf.append(fSifVersions[i]); } XMLUtils.setAttribute(mappingsNode, "sifVersion", buf.toString()); } if (fSourceIds != null && fSourceIds.length > 0) { StringBuffer buf = new StringBuffer(); for (int i = 0; i < fSourceIds.length; i++) { if (i != 0) buf.append(","); buf.append(fSourceIds[i]); } XMLUtils.setAttribute(mappingsNode, "sourceId", buf.toString()); } if (fZoneIds != null && fZoneIds.length > 0) { StringBuffer buf = new StringBuffer(); for (int i = 0; i < fZoneIds.length; i++) { if (i != 0) buf.append(","); buf.append(fZoneIds[i]); } XMLUtils.setAttribute(mappingsNode, "zoneId", buf.toString()); } // Write out each <object>... ObjectMapping[] objects = getObjectMappings(renderAsRuntimeMappings); for (int i = 0; i < objects.length; i++) { Node objNode = doc.createElement(XML_OBJECT); mappingsNode.appendChild(objNode); XMLUtils.setAttribute(objNode, XML_OBJECT, objects[i].getObjectType()); for(Mapping mapping : objects[i].getAllRulesList(renderAsRuntimeMappings)) { if(mapping instanceof FieldMapping) { // Write out each <field>... org.w3c.dom.Element fieldNode = doc.createElement(XML_FIELD); objNode.appendChild(fieldNode); mapping.toXML(fieldNode); } else if(mapping instanceof ListMapping) { // Write out each <list>... org.w3c.dom.Element listNode = doc.createElement(XML_LIST); objNode.appendChild(listNode); mapping.toXML(listNode); } } } // Write out each <valueset>... ValueSet[] valuesets = this.getValueSets(renderAsRuntimeMappings); for (int i = 0; i < valuesets.length; i++) { org.w3c.dom.Element valuesetNode = doc.createElement(XML_VALUESET); valuesets[i].toXml(valuesetNode); mappingsNode.appendChild(valuesetNode); } // Copy any children mappings Mappings[] children = this.getChildren(); for (int a = 0; a < children.length; a++) { Node childNode = children[a].toDOM(doc); mappingsNode.appendChild(childNode); } return mappingsNode; } /** * Returns the Mappings as a string in XML form * * @return A string representing the XML form of this Mappings instance */ public String toXML() { return toXML(false); } /** * Returns the Mappings as a string in XML form. * <p> * * @param renderAsRuntimeMappings * true to inherit object and field rules so this Mappings is * rendered with dynamic content as it would be evaluated at * runtime. This can be useful for diagnostics (i.e. displaying * the current state of a Mappings object), but not for rendering * to a configuration file. * @return A string representing the XML form of this Mappings instance */ public String toXML(boolean renderAsRuntimeMappings) { try { DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); DocumentBuilder db = dbf.newDocumentBuilder(); Document dom = db.newDocument(); Node n = this.toDOM(dom, renderAsRuntimeMappings); dom.appendChild(n); // Populate OutputFormat format = new OutputFormat(dom); format.setLineWidth(65); format.setIndenting(true); format.setIndent(4); format.setLineSeparator("\r\n"); StringWriter sw = new StringWriter(); XMLSerializer serializer = new XMLSerializer(sw, format); serializer.serialize(dom); return sw.toString(); } catch (Exception ex) { return "<error>" + ex + "</error>"; } } class Candidate { Mappings fMapping; int restrictiveness; Candidate(Mappings candidateMapping) { this.fMapping = candidateMapping; // The "restrictiveness" score is based on a count of all filters String[] f = fMapping.getZoneIdFilter(); if (f != null) restrictiveness += f.length; f = fMapping.getSourceIdFilter(); if (f != null) restrictiveness += f.length; SIFVersion[] v = fMapping.getSIFVersionFilter(); if (v != null) restrictiveness += v.length; } } /** * Displays the Mappings defined by a configuration file * * Usage: Mappings config.xml [[/zone=zoneId /sifVersion=version * /sourceId=sourceId] /v] * * When the /zone, /sifVersion, or /sourceId parameters are specified, the * Mappings.select method is called to select the Mappings object that most * closely matches the Zone(s), SIF Version, and SourceId value(s). All * three options must be specified. When these options are not present, all * Mappings are written to System.out * * When the /v option is specified, the configuration file is parsed with a * validating parser. * * @param args * The command-line arguments, as documented, used by this method */ public static void main(String[] args) { try { if (args.length == 0 || args[0].equals("/?") || args[0].equalsIgnoreCase("/h")) { _printUsage(); return; } String id = null; String srcId = null; String zoneId = null; String version = null; String toSIF = null; String fromSIF = null; String file = null; boolean validate = false; boolean toTable = false; try { for (int i = 1; i < args.length; i++) { if (args[i].startsWith("/") && args[i].length() >= 2) { switch (Character.toUpperCase(args[i].charAt(1))) { case 'X': validate = true; break; case 'I': id = args[i].substring(3); break; case 'Z': zoneId = args[i].substring(3); break; case 'S': srcId = args[i].substring(3); break; case 'V': version = args[i].substring(3); break; case 'T': toSIF = args[i].substring(7); file = args[++i]; break; case 'F': fromSIF = args[++i]; break; case 'W': toTable = true; break; case 'H': case '?': _printUsage(); return; default: System.out.println(args[i] + " not a recognized option"); } } } } catch (Throwable thr) { System.out.println(thr); _printUsage(); return; } // Initialize the ADK ADK.initialize(); // Parse the configuration file openadk.library.tools.cfg.AgentConfig config = new openadk.library.tools.cfg.AgentConfig(); config.read(args[0], validate); // Select the Mappings object with the specified ID Mappings m = config.getMappings(); if (m == null) { System.out .println("Configuration file does not define a <mappings> element"); return; } m = m.getMappings(id == null ? "Default" : id); if (m == null) { System.out.println("Mappings[" + id + "] not found"); return; } SIFVersion sifVersion = version == null ? null : SIFVersion .parse(version); // Select the best Mappings match based on the SourceId, ZoneId, and // SifVersion m = m.select(zoneId, srcId, sifVersion); if (toSIF != null) { // The "/toSIF:" option was used... Properties props = new Properties(); FileInputStream in = new FileInputStream(file); props.load(in); in.close(); // Create a new instance of the named SIFDataObject SIFDataObject sdo = ADK.DTD().createSIFDataObject( ADK.DTD().lookupElementDef(toSIF)); // Apply the Mappings StringMapAdaptor propsMapper = new StringMapAdaptor(props); m.mapOutbound(propsMapper, sdo); // Render the SIFDataObject to the console SIFWriter out = new SIFWriter(System.out); out.write(sdo); out.close(); } else if (fromSIF != null) { // The "/fromSIF" option was used... SIFParser p = SIFParser.newInstance(); FileReader in = new FileReader(fromSIF); SIFDataObject sdo = (SIFDataObject) p.parse(in, null); in.close(); // Apply the Mappings HashMap<String, String> map = new HashMap<String, String>(); StringMapAdaptor table = new StringMapAdaptor(map); m.mapInbound(sdo, table); // Display the table for (Iterator<String> it = map.keySet().iterator(); it .hasNext();) { String name = it.next(); String value = map.get(name); System.out.println(name + "=" + value); } } if (toTable = true) { PrintWriter pw = new PrintWriter(new FileWriter( "AgentMappings.html")); pw.println("<html>\n\t<head>\n\t\t<title>Agent Mappings for " + config.getSourceId() + "</title>"); pw.println("\t\t<style type='text/css'>"); pw.println("\t\t\ttable{border: thin solid black;border-collapse: collapse;}"); pw.println("\t\t\tth{background-color: black;color:white;padding-left:7px;padding-right:7px;padding-top:3px;padding-bottom:1px;}"); pw.println("\t\t\ttd{border: thin solid black;padding-left:5px;padding-right:5px;}"); pw.println("\t\t</style>"); pw.println("\t</head>"); pw.println("\t<body>"); pw.println("<h1>Agent Mappings for " + config.getSourceId() + "</h1>"); for (ObjectMapping om : m.getObjectMappings()) { pw.println("\t<h3>" + om.getObjectType() + "</h3>"); pw.println("\t<table>"); pw.println("\t\t<tr>"); pw.println("\t\t\t<th>Field Code</th>"); pw.println("\t\t\t<th>" + om.getObjectType() + " Element/Attribute" + "</th>"); pw.println("\t\t\t<th>SIF Version</th>"); pw.println("\t\t</tr>"); for (FieldMapping rule : om.getRules(true)) { pw.println("\t\t<tr>"); String fieldName = rule.getFieldName(); if (rule.getAlias() != null) { fieldName = rule.getAlias(); } pw.println("\t\t\t<td>" + fieldName + "</td>"); pw.println("\t\t\t<td>" + rule.getRule().toString() + "</td>"); String renderVersion = "All"; MappingsFilter filter = rule.getFilter(); if (filter != null && filter.hasVersionFilter()) { String filterVersion = filter.fVersion; if (filterVersion.startsWith("=")) { renderVersion = filterVersion.substring(1); } else if (filterVersion.startsWith("+")) { renderVersion = ">=" + filterVersion.substring(1); } else if (filterVersion.startsWith("-")) { renderVersion = "<=" + filterVersion.substring(1); } else { renderVersion = version; } } pw.println("\t\t\t<td>" + renderVersion + "</td>"); pw.println("\t\t</tr>"); } pw.println("\t</table>"); } pw.println("\t</body>"); pw.println("</html>"); pw.flush(); pw.close(); } else { // Render the Mappings to System.out System.out.println("Mappings[" + (m.getId() == null ? "" : m.getId()) + "]:"); System.out.println(m.toXML(true)); System.out.println(); } } catch (Exception ex) { System.out.println(ex); } } private static void _printUsage() { System.out .println("\nThe Mappings class can be run as a program to display the Mappings that would"); System.out .println("be selected at runtime given a set of ZoneId, SourceId, and SIF Version "); System.out .println("values. It can also be used to map a table of values to a SIFDataObject by"); System.out .println("using the /toSIF option, or a SIFDataObject to a table of values by using the"); System.out .println("/fromSIF option. Use this program to test Mappings configurations."); System.out.println(); System.out .println("Usage: Mappings config.xml /i:MappingsId /z:ZoneId /v:SifVersion /s:SourceId [/x] [/toSIF:StudentPersonal table.properties] [/fromSIF:sdo.xml]"); System.out.println(); System.out .println("Example: The following example selects the Mappings object from the agent.cfg"); System.out .println("configuration file that is the best match when the ZoneId is 'MyZone', the SIF"); System.out .println("Version is 1.0r1, and the SourceId is 'MyAgent'. The contents of the Mappings"); System.out.println("object is printed to the console:"); System.out.println(); System.out .println("Mappings agent.cfg /i:Default /z:MyZone /v:1.0r1 /s:MyAgent"); System.out.println(); System.out .println("Example: The following example reads a list of name=value pairs from a file"); System.out .println("named values.prop to produce a <StudentPersonal> object."); System.out.println(); System.out .println("Mappings agent.cfg /i:Default /toSIF:StudentPersonal values.prop"); System.out.println(); System.out .println("Example: The following example reads a <StudentPersonal> object from a file"); System.out .println("named sdo.xml to produce a table of name/value pairs. The /s option is"); System.out .println("used to select the Mappings object defined for the 'MyAgent' SourceId."); System.out.println(); System.out .println("Mappings agent.cfg /i:Default /s:MyAgent /fromSIF sdo.xml"); System.out.println(); System.out .println("Example: The following creates a simple table, listing the"); System.out.println("mappings configuration in HTML format"); System.out.println(); System.out.println("Mappings agent.cfg /W"); System.out.println(); } }