//
// Copyright (c)1998-2011 Pearson Education, Inc. or its affiliate(s).
// All rights reserved.
//
package openadk.library.tools.mapping;
import openadk.library.*;
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.ri.model.NodePointer;
/**
* A Rule class to evaluate XPath-like queries as defined by the
* <code>SIFDTD.lookupByXPath</code> method
*
* @author Eric Petersen
* @version 1.0
*/
public class XPathRule extends Rule
{
private String fDef = null;
private ElementDef fTargetDef;
/**
* For outbound mappings that contain an expression containing the '=' sign,
* this field contains the index of that sign for easy parsing
*/
private int fValueIndex = -1;
private CompiledExpression fExpression;
/**
* Constructor
* @param definition An XPath-like query (e.g. "@RefId",
* "StudentAddress/Address[@Type='H','M']/Street/Line1") to evaluate
* against the SIFDataObject passed to the <code>evaluate</code> method
*/
public XPathRule( String definition )
{
fDef = definition;
}
/**
* Produces a duplicate of this Rule object<p>
* @param newParent The FieldMapping to copy this rule to
*
* @return A "deep copy" of this Rule object
*/
public Rule copy( FieldMapping newParent )
{
XPathRule clone = new XPathRule( fDef );
if( newParent.fNode != null ) {
XMLUtils.setText( newParent.fNode, fDef );
}
return clone;
}
/**
* Render this XPathRule as an XML DOM Node
* @param parent The XML node to write to
*/
public void toXML( org.w3c.dom.Node parent )
{
if( parent.getNodeType() == org.w3c.dom.Node.ELEMENT_NODE ){
XMLUtils.setText( parent, fDef );
}
else if( parent.getNodeType() == org.w3c.dom.Node.TEXT_NODE ) {
parent.setNodeValue( fDef );
}
}
/**
* Render this Rule as an XML element
* @return The XPath expression this rule represents
*/
public String toString()
{
return fDef;
}
/**
* @return The XPath expression this rule represents
*/
public String getXPath()
{
return fDef;
}
/**
*
* @return
*/
public String getValueExpression()
{
if( fValueIndex == -1 ){
return null;
}
return fDef.substring( fValueIndex );
}
public synchronized String getPathExpression()
{
if( fExpression == null ){
compile();
}
if( fValueIndex == -1 ){
return fDef;
}
return fDef.substring( 0, fValueIndex - 1 );
}
/* (non-Javadoc)
* @see openadk.library.tools.mapping.Rule#evaluate(openadk.library.tools.xpath.SIFXPathContext, openadk.library.SIFVersion)
*/
@Override
public synchronized SIFSimpleType evaluate(SIFXPathContext xpathContext, SIFVersion version)
throws ADKSchemaException
{
SIFSimpleType retval = null;
// TODO: This could be done in the constructor, but the ADK outbound mapping
// syntax sometimes cannot be compiled because it uses proprietary syntax
// Therefore, compile the expression the first time it is used for a mapping
if( fExpression == null ) {
compile();
}
Object value = fExpression.getValue( xpathContext );
if( value == null ){
return null;
} else if( value instanceof Element )
{
return ((Element)value).getSIFValue();
} else {
return new SIFString(value.toString() );
}
}
/**
* Builds out the path specified by this XPath rule and returns the final Element in the path
* @param context
* @param version
* @return The newly-created element. If the Element already exists, however, the return value will
* be null
*/
public synchronized NodePointer createPath( SIFXPathContext context, SIFVersion version )
{
if( fExpression == null ){
compile();
}
String expressionAsString =fExpression.toString();
// modified to support StudentAddressListSurrogate (next 4 lines)
NodePointer np = (NodePointer) fExpression.getPointer(context, expressionAsString);
// This is a bit of a hack due to unfamiliarity with the API. There may be a more appropriate way
// to determine that the given expression needs the opportunity to create multiple matching tags.
// but for now, we're just checking if adk:x is in there, and then another method down the chain
// does the real evaluation.
if (np != null && np.getValue() != null && !expressionAsString.contains("and adk:x()]")) {
return np;
}
NodePointer new_np = (NodePointer) fExpression.createPath( context );
return new_np;
}
private void compile() {
// If there is a value assignment in the rule, chop it off
String sqp = fDef;
int lastEqualsSign = fDef.lastIndexOf( "=" );
if( lastEqualsSign > -1 ){
int lastBracket = fDef.lastIndexOf( "]" );
if( lastBracket < lastEqualsSign ){
sqp = fDef.substring( 0, lastEqualsSign );
fValueIndex = lastEqualsSign + 1;
}
}
String jXPath = SIFXPathContext.convertLegacyXPath( sqp );
fExpression = SIFXPathContext.compile( jXPath );
}
/**
* Looks up the ElementDef that this XPathRule points to by XPath
* @param parent The parent object metadata object, representing the root of the path
* @return The ElementDef that this XPathRule points to
*/
public ElementDef lookupTargetDef( ElementDef parent ) {
if( fTargetDef == null ){
fTargetDef = ADK.DTD().lookupElementDefBySQP( parent, getPathExpression() );
}
return fTargetDef;
}
}