//
// Copyright (c)1998-2011 Pearson Education, Inc. or its affiliate(s).
// All rights reserved.
//
package openadk.library;
import java.io.IOException;
import java.io.Serializable;
import java.io.StringWriter;
import java.text.Collator;
import java.util.*;
import openadk.library.impl.ElementDefImpl;
import openadk.library.impl.SIFPrimitives;
import openadk.library.infra.SIF_Condition;
import openadk.library.infra.SIF_ConditionGroup;
import openadk.library.infra.SIF_Conditions;
import openadk.library.infra.SIF_Element;
import openadk.library.infra.SIF_Query;
import openadk.library.infra.SIF_QueryObject;
import openadk.library.infra.SIF_Request;
import openadk.library.tools.xpath.SIFXPathContext;
/**
* Encapsulates a SIF Query.<p>
*
* An instance of this class is passed to the <code>Zone.query</code> and
* <code>Topic.query</code> methods when issuing a SIF Request. A Query object
* defines the following parameters to the request:
*
* <ul>
* <li>The type of SIF Data Object to query for</li>
* <li>Conditions: One or more conditions may be placed on the query to
* select a subset of objects from the responder (when no conditions are
* present the responder returns all objects)</li>
* <li>Field Restrictions: An optional list of elements to include in
* responses to the query (when no field restrictions are present the
* responder returns the full set of elements for each object)</li>
* </ul>
* <p>
*
* To construct a simple Query to query for all objects with no conditions or
* field restrictions, call the constructor that accepts an ElementDef constant
* from the SIFDTD class:
* <p>
*
* <blockquote>
* <code>
* Query myQuery = new Query( SIFDTD.STUDENTPERSONAL );<br/>
* </code>
* </blockquote>
*
* More complex queries can be constructed by specifying conditions and field
* restrictions.<p>
*
* <b>Conditions</b><p>
* A Query may optionally specify one or more conditions to restrict the number
* of objects returned by the responder. (Refer to the SIF Specification for a
* detailed description of how query conditions may be constructed.) When no
* conditions are specified, the responder interprets the query to mean "all
* objects". Note SIF 1.0r2 and earlier limit queries such that only root-level
* attributes may be included in query conditions, and only the equals ("EQ")
* comparison operator may be used. SIF 1.1 and later allow agents to query for
* elements within an object, but responders may return an error if they do not
* support that functionality.
* <p>
*
* Query conditions are encapsulated by the ADK's ConditionGroup class, which is
* used to build SIF_ConditionGroup, SIF_Conditions, and SIF_Condition elements
* when the class framework sends a SIF_Request message to a zone. Every Query
* with conditions has a root ConditionGroup with one or more child ConditionGroups.
* Unless you construct these groups manually, the Query class will automatically
* establish a root ConditionGroup and a single child when the <code>addCondition</code>
* method is called. Use the <code>addCondition</code> method to add conditions
* to a Query. Note the form of Query constructor you call determines how the
* <code>addCondition</code> method works. If you call the default constructor,
* the ADK automatically establishes a root SIF_ConditionGroup with a Type
* attribute of "None", and a single SIF_Conditions child with a Type attribute
* of "And". ("None" will be used if the query has only one condition.)
* SIF_Condition elements are then added to this element whenever the
* <code>addCondition</code> method is called.<p>
*
* For example,<p>
*
* <blockquote>
* <code>
* // Query for a single student by RefId<br/>
* Query query = new Query( SIFDTD.STUDENTPERSONAL );<br/>
* query.addCondition(<br/>
* SIFDTD.STUDENTPERSONAL_REFID, Condition.EQ,<br/>
* "4A37969803F0D00322AF0EB969038483" );<br/>
* </code>
* </blockquote>
*
* If you want to specify the "Or" comparision operator instead of the default
* of "And", call the constructor that accepts a constant from the Condition
* class.<p>
*
* For example,<p>
*
* <blockquote>
* <code>
* // Query for student where the RefId is A, B, or C<br/>
* Query query = new Query( SIFDTD.STUDENTPERSONAL, Condition.OR );<br/>
* <br/>
* query.addCondition(<br/>
* SIFDTD.STUDENTPERSONAL_REFID, Condition.EQ,<br/>
* "4A37969803F0D00322AF0EB969038483" );<br/>
* query.addCondition(<br/>
* SIFDTD.STUDENTPERSONAL_REFID, Condition.EQ,<br/>
* "5A37969803F0D00322AF0EB969038484" );<br/>
* query.addCondition(<br/>
* SIFDTD.STUDENTPERSONAL_REFID, Condition.EQ,<br/>
* "6A37969803F0D00322AF0EB969038485" );<br/>
* </code>
* </blockquote>
*
* The above examples show how to add simple conditions to a Query. To construct
* complex queries with nested groups of conditions, create your own root
* SIF_ConditionGroup object by calling the form of constructor that
* accepts a ConditionGroup instance. You can specify nested ConditionGroup
* children of this root object.
* <p>
*
* For example,<p>
*
* <blockquote>
* <code>
* // Query for student where the Last Name is Jones and the First Name is<br/>
* // Bob, and the graduation year is 2004, 2005, or 2006<br/>
* ConditionGroup root = new ConditionGroup( Condition.AND );<br/>
* ConditionGroup grp1 = new ConditionGroup( Condition.AND );<br/>
* ConditionGroup grp2 = new ConditionGroup( Condition.OR );<br/>
* <br/>
* // For nested elements, you cannot reference a SIFDTD constant. Instead, use<br/>
* // the lookupElementDefBySQL function to lookup an ElementDef constant<br/>
* // given a SIF Query Pattern (SQP)<br/>
* ElementDef lname = ADK.DTD().lookupElementDefBySQP(<br/>
* SIFDTD.STUDENTPERSONAL, "Name/LastName" );</br>
* ElementDef fname = ADK.DTD().lookupElementDefBySQP(<br/>
* SIFDTD.STUDENTPERSONAL, "Name/FirstName" );</br>
* grp1.addCondition( lname, Condition.EQ, "Jones" );<br/>
* grp1.addCondition( fname, Condition.EQ, "Bob" );<br/>
* <br/>
* grp2.addCondition( SIFDTD.STUDENTPERSONAL_GRADYEAR, Condition.EQ, "2004" );<br/>
* grp2.addCondition( SIFDTD.STUDENTPERSONAL_GRADYEAR, Condition.EQ, "2005" );<br/>
* grp2.addCondition( SIFDTD.STUDENTPERSONAL_GRADYEAR, Condition.EQ, "2006" );<br/>
* <br/>
* // Add condition groups to the root group<br/>
* root.addGroup( grp1 );<br/>
* root.addGroup( grp2 );<br/>
* <br/>
* // Query for student with the conditions prepared above by passing the<br/>
* // root ConditionGroup to the constructor<br/>
* Query query = new Query( SIFDTD.STUDENTPERSONAL, root );<br/>
* </code>
* </blockquote>
*
* <b>Field Restrictions</b><p>
* If only a subset of elements and attributes are requested, use the
* <code>setFieldRestrictions</code> method to indicate which elements and
* attributes should be returned to your agent by the responder. For example,
* to request the <StudentPersonal> object with RefId "4A37969803F0D00322AF0EB969038483"
* but to only include the <code>RefId</code> attribute and <code>Name</code>
* and <code>PhoneNumber</code> elements in the response,<p>
*
* <blockquote>
* <code>
*
* // Query for a single student by RefId<br/>
* Query query = new Query( SIFDTD.STUDENTPERSONAL );<br/>
* <br/>
* query.addCondition(<br/>
* SIFDTD.STUDENTPERSONAL_REFID, Condition.EQ,<br/>
* "4A37969803F0D00322AF0EB969038483" );<br/>
*
* query.setFieldRestrictions(<br/>
* new ElementDef[] {<br/>
* SIFDTD.STUDENTPERSONAL_REFID,<br/>
* SIFDTD.STUDENTPERSONAL_NAME,<br/>
* SIFDTD.STUDENTPERSONAL_PHONENUMBER<br/>
* }
* );
* </code>
* </blockquote>
*
* @author Eric Petersen
* @version 1.0
*/
public class Query implements Serializable
{
/** The object to query */
protected transient ElementDef fObjType;
/** The version of SIF associated with the query */
protected SIFVersion[] fVersions = new SIFVersion[0];
/** Root condition groups */
protected ConditionGroup fRoot;
/**
* The SIF Context that this Query applies to
*/
private SIFContext fContext = SIFContext.DEFAULT;
/**
* Fields to include in the result of the query (null = all fields
*/
protected List<ElementRef> fFieldRestrictions;
/**
* The locale-specific collator used for string comparisons when evaluating query conditions
*/
protected Collator fCollator;
/**
* User State
*/
protected Serializable fUserData;
/**
* Constructs a Query object with no initial conditions or field
* restrictions. If conditions are subsequently added to the Query, they
* will be evaluated as a group with the logical AND operator. To specify
* that the logical OR operator be used, call the form of constructor that
* accepts an alternate operator.
* <p>
*
* @param objectType An ElementDef describing the object type to query (e.g.
* <code>ADK.DTD.STUDENTPERSONAL</code>)
*/
public Query( ElementDef objectType )
{
if( !objectType.isObject() )
throw new IllegalArgumentException("\""+objectType.name()+"\" is not a root-level SIF Data Object");
fObjType = objectType;
fRoot = null;
}
/**
* Constructs a Query object with one ConditionGroup where all conditions
* in the group are evaluated using the supplied boolean operator (either
* <code>Condition.AND</code> or <code>Condition.OR</code>). All Conditions
* subsequently added to this Query will be placed into the ConditionGroup
* created by the constructor.<p>
*
* This constructor is provided as a convenience so that callers do
* not have to explicitly create a ConditionGroup for simple queries.<p>
*
* @param objectType An ElementDef describing the object type to query (e.g.
* <code>ADK.DTD.STUDENTPERSONAL</code>)
* @param logicalOp The logical operator that defines how to compare this group
* with other condition groups that comprise the query (e.g. GroupOperators.OR)
*/
public Query( ElementDef objectType, GroupOperators logicalOp )
{
if( !objectType.isObject() )
throw new IllegalArgumentException("\""+objectType.name()+"\" is not a root-level SIF Data Object");
fObjType = objectType;
fRoot = new ConditionGroup( logicalOp );
}
/**
* Constructs a Query object with a ConditionGroup.
* <p>
*
* @param objectType An ElementDef describing the object type to query (e.g.
* <code>ADK.DTD.STUDENTPERSONAL</code>)
* @param conditions A ConditionGroup comprised of one or more query Conditions
*/
public Query( ElementDef objectType, ConditionGroup conditions )
{
if( !objectType.isObject() )
throw new IllegalArgumentException("\""+objectType.name()+"\" is not a root-level SIF Data Object");
fObjType = objectType;
fRoot = conditions;
}
/**
* Constructs a Query object from a SIF_QueryObject.<p>
*
* This constructor is not typically called by agents but is used internally
* by the class framework. The other constructors can be used to safely
* create Query instances to request a specific SIF Data Object. Use the
* <code>addCondition</code> and <code>setFieldRestrictions</code> methods
* to further define the conditions and SIF elements specified by the query.<p>
*
* @param query A SIF_Query object received in a SIF_Request message
* @throws ADKUnknownOperatorException If one of the operators in the SIF_Query is
* unrecognized by the ADK
* @throws ADKSchemaException If the object or elements defined in the query or
* not recognized by the ADK
*/
public Query( SIF_Query query )
throws ADKUnknownOperatorException,
ADKSchemaException
{
SIF_QueryObject qo = query.getSIF_QueryObject();
if( qo == null )
throw new IllegalArgumentException("SIF_Query must have a SIF_QueryObject element");
fObjType = ADK.DTD().lookupElementDef( qo.getObjectName() );
if( fObjType == null ) {
throw new ADKSchemaException( qo.getObjectName() + " is not a recognized SIF Data Object, or the agent is not configured to support this object type" );
}
fRoot = null;
SIF_ConditionGroup cg = query.getSIF_ConditionGroup();
if( cg != null && cg.getSIF_Conditionses() != null )
{
GroupOperators grpOp = GroupOperators.NONE;
try {
grpOp = Condition.parseGroupOperator(cg.getType());
} catch( ADKUnknownOperatorException uoe ) {
grpOp = GroupOperators.NONE;
}
fRoot = new ConditionGroup(grpOp);
SIF_Conditions[] sifConds = cg.getSIF_Conditionses();
if( sifConds.length == 1 )
{
// There is one SIF_ConditionGroup with one SIF_Conditions,
// so just add all of the conditions (no nested groups)
String typ = sifConds[0].getType();
if( typ == null )
throw new ADKSchemaException( "SIF_Conditions/@Type is a required attribute" );
fRoot.fOp = Condition.parseGroupOperator( typ );
SIF_Condition[] clist = sifConds[0].getSIF_Conditions();
populateConditions( query, clist, fRoot );
}
else
{
// There are multiple SIF_Conditions, so add each as a nested
// ConditionGroup of the fRoot
for( int i = 0; i < sifConds.length; i++ )
{
ConditionGroup nested = new ConditionGroup( Condition.parseGroupOperator( sifConds[i].getType() ) );
populateConditions( query, sifConds[i].getSIF_Conditions(), nested );
fRoot.addGroup( nested );
}
}
}
SIFVersion[] reqVersions = null;
// First, try to get the version from the SIF_Request
Element parent = query.getParent();
if( parent != null )
{
if( parent instanceof SIF_Request ){
SIF_Request request = ( SIF_Request )parent;
//There was some kind of bad character or something in this source here, adding comment to force diff
SIFVersion[] versions = request.parseRequestVersions( ADK.getLog() );
if( versions.length > 0 ){
reqVersions = versions;
}
}
}
if( reqVersions == null ){
SIFVersion version = query.effectiveSIFVersion();
if( version != null ){
reqVersions = new SIFVersion[] { version };
}
}
if( reqVersions == null || reqVersions.length == 0 ){
throw new IllegalArgumentException( "SIF_Query is not contained in a SIF_Request that has a SIF_Version element; cannot determine version of SIF to associated with this Query object" );
} else {
fVersions = reqVersions;
}
SIF_Element[] fields = query.getSIF_QueryObject().getSIF_Elements();
if( fields != null && fields.length > 0 )
{
for( int i = 0; i < fields.length; i++ )
{
String xPath = fields[i].getTextValue();
if( xPath == null || xPath.length() == 0 ){
continue;
}
addFieldRestriction( xPath );
}
}
}
private void populateConditions( SIF_Query query, SIF_Condition[] clist, ConditionGroup target )
throws ADKUnknownOperatorException,
ADKSchemaException
{
for( int i = 0; i < clist.length; i++ )
{
String o = clist[i].getSIF_Operator();
ComparisonOperators ops = Condition.parseComparisionOperators( o );
String val = clist[i].getSIF_Value();
String path = clist[i].getSIF_Element();
target.addCondition( fObjType, path, ops, val );
}
}
/**
* Returns the XML representation of this Query in the format required by SIF
* @return a string containing the XML representation as a SIF_Query element. If an error
* occurs during the conversion, an empty string ("") is returned.
*/
public String toXML()
{
return toXML( this.getEffectiveVersion() );
}
/**
* Returns the XML representation of this Query in the format required by SIF
* for the specified version
* @param version The SIF Version to render the Query in. The ADK will attempt to render
* the query path using the proper element or attribute names for the version of SIF
* @return a string containing the XML representation as a SIF_Query element. If an error
* occurs during the conversion, an empty string ("") is returned.
*/
public String toXML( SIFVersion version )
{
// Create a SIF_Query object
SIF_Query sifQ = SIFPrimitives.createSIF_Query( this, version, true );
StringWriter out = null;
SIFWriter w = null;
try
{
out = new StringWriter();
w = new SIFWriter(out,false);
w.write(sifQ);
w.flush();
return out.toString();
}
catch( Exception e )
{
ADK.getLog().warn( "Error creating XML equivalent of Query: " + e, e );
return "";
}
finally
{
try {
if( out != null )
out.close();
if( w != null )
w.close();
} catch( Exception ignored ) {
ADK.getLog().warn( "Error closing writer: " + ignored, ignored );
}
}
}
/**
* Returns the SIF_Query representation of this Query in the format required by SIF
* @return A SIF_Query element.
*/
public SIF_Query toSIF_Query()
{
return toSIF_Query( ADK.getSIFVersion() );
}
/**
* Returns the SIF_Query representation of this Query in the format required by SIF
* for the specified version
* @param version The SIF Version to render the Query in. The ADK will attempt to render
* the query path using the proper element or attribute names for the version of SIF
* @return A SIF_Query element.
*/
public SIF_Query toSIF_Query( SIFVersion version )
{
return SIFPrimitives.createSIF_Query( this, version, true );
}
/**
* Gets the object type being queried<p>
* @return The name of the object passed to the constructor
*/
public ElementDef getObjectType() {
return fObjType;
}
/**
* Gets the tag name of the object type being queried<p>
* @return The tag name of the object passed to the constructor
*/
public String getObjectTag() {
return fObjType.tag( ADK.getLatestSupportedVersion( fVersions ) );
}
/**
* Gets the custom state object associated with this query
* @return The custom data that was set to {@link #setUserData(Serializable)}
*/
public Serializable getUserData()
{
return fUserData;
}
/**
* Allows a custom state object to be associated with a query
* @param userData A custom state object that implements the Serializable interface
*/
public void setUserData( Serializable userData )
{
fUserData = userData;
}
/**
* Add a condition to this query.<p>
*
* This method of adding conditions is convenient for adding conditions involving
* root attributes or elements to a query. If you need to add conditions on deeply
* nested elements, use {@link #addCondition(String, ComparisonOperators, String)}
*
* @param field A constant from the SIFDTD class that identifies an element
* or attribute of the data object (e.g. <code>SIFDTD.STUDENTPERSONAL_REFID</code>).
*
* @param ops The comparison operator. Comparison operator constants are
* defined by the ComparisionOperators enum
* @param value The data that is used to compare to the element or attribute
* @throws IllegalArgumentException if the ElementDef does not represent an immediate
* child of the object being queried.
*/
public void addCondition( ElementDef field, ComparisonOperators ops, String value )
{
// Do some validation to try to prevent invalid query paths from being created
String relativePath = field.getSQPPath( ADK.getSIFVersion() );
ElementDef lookedUp = ADK.DTD().lookupElementDefBySQP( fObjType, relativePath );
if( lookedUp == null ){
throw new IllegalArgumentException( "Invalid path: " + fObjType.name() + "/" + relativePath + " is unable to be resolved" );
}
addCondition( new Condition( fObjType, relativePath, ops, value ) );
}
/**
* Add a condition to this query.
* @param condition The condition to add. This condition is added to the root
* condition group.
* @see #getRootConditionGroup()
* @see #getConditions()
*/
public void addCondition( Condition condition )
{
if( fRoot == null ){
fRoot = new ConditionGroup( GroupOperators.AND );
}
fRoot.addCondition( condition );
}
/**
* Add a condition to this query using a deeply nested path. Using this
* method of adding query condition allows for specifying deeply nested query
* conditions. However, the xpath specified here is specific to the version
* of SIF<p>
*
* To ensure your code works with all versions of SIF, you should use
* {@link #addCondition(ElementDef, ComparisonOperators, String)} whenever possible.
* <p>
*
* @param xPath The Simple XPath to use for this query condition. E.g.
* SIF_ExendedElements/SIF_ExtendedElement[@Name='eyecolor']
*
* @param ops The comparison operator. Comparison operator value from the
* ComparisonOperators enum
*
* @param value The data that is used to compare to the element or attribute
*/
public void addCondition( String xPath, ComparisonOperators ops, String value )
{
addCondition( new Condition( fObjType, xPath, ops, value ) );
}
/**
* Add a condition to this query. This form of the <code>addCondition</code>
* method is intended to be called internally by the ADK when parsing an
* incoming SIF_Query element. To ensure your code works with all versions
* of SIF, you should use the other form of this method that accepts an
* ElementDef constant for the <i>field</i> parameter whenever possible.
* <p>
*
* @param field Identifies an element or attribute of the data object in
* SIF Query Pattern form as described by the SIF 1.0r2 Specification
* (e.g. "@RefId"). With SIF 1.0r2 and earlier, only root-level
* attributes may be specified in a query. Note this string is specific
* to the version of SIF associated with the Query as element and
* attribute names may vary from one version of SIF to the next. The
* version defaults to the version of SIF in effect for the agent or the
* version of SIF associated with the <code>SIF_Query</code> object
* passed to the constructor.
*
* @param ops A value from the ComparisonOperators enum
*
* @param value The data that is used to compare to the element or attribute
*/
public void addCondition( String field, String ops, String value )
{
try {
addCondition(
field, Condition.parseComparisionOperators( ops ), value );
} catch( ADKUnknownOperatorException uoe ) {
addCondition(field, ComparisonOperators.EQ, value );
}
}
/**
* Restricts the query to a specific field (i.e. element or attribute) of
* the data object being requested. If invoked, the results of the query
* will only contain the elements or attributes specified by the fields for
* which this method is called (call this method repeatedly for each field).
* Otherwise, the results will contain a complete object.
*
* @param field A <code>ElementDef</code> object defined by the static
* constants of the <code>SIFDTD</code> class. For example, to restrict
* a query for the StudentPersonal topic to include only the StatePr
* element of the student address, pass <code>SIFDTD.ADDRESS_STATEPR</code>.
* This would cause the query results to include only
* <code>StudentPersonal/Address/StatePr</code> elements.
*/
public synchronized void addFieldRestriction( ElementDef field ) {
if( field == null ){
throw new IllegalArgumentException( "Field cannot be null" );
}
if( fFieldRestrictions == null ) {
fFieldRestrictions = new ArrayList<ElementRef>();
}
fFieldRestrictions.add( new ElementRef( fObjType, field, getEffectiveVersion() ) );
}
/**
* Restricts the query to a specific field (i.e. element or attribute) of
* the data object being requested. If invoked, the results of the query
* will only contain the elements or attributes specified by the fields for
* which this method is called (call this method repeatedly for each field).
* Otherwise, the results will contain a complete object.
*
* @param xPath The relative XPath to the referenced field
*/
public synchronized void addFieldRestriction( String xPath ) {
if( xPath == null || xPath.length() == 0 ){
throw new IllegalArgumentException( "Field cannot be null or zero-length : " + xPath );
}
if( fFieldRestrictions == null ) {
fFieldRestrictions = new ArrayList<ElementRef>();
}
fFieldRestrictions.add( new ElementRef( fObjType, xPath, getEffectiveVersion() ) );
}
/**
* Restricts the query to the specified elements and attributes
* @param fields An array of ElementDef objects identifying one or more
* elements and/or attributes
*/
public void setFieldRestrictions( ElementDef[] fields ) {
if( fFieldRestrictions != null ){
fFieldRestrictions.clear();
}
for( ElementDef def : fields ){
addFieldRestriction( def );
}
}
/**
* Gets the fields to include in the result of the query.<p>
* @return An array of fields that should be included in the results of
* this query, or null if all fields are to be included
*/
public ElementDef[] getFieldRestrictions() {
if( fFieldRestrictions == null ){
return null;
}
ElementDef[] returnValue = new ElementDef[ fFieldRestrictions.size() ];
for( int i = 0; i < returnValue.length; i++ ){
returnValue[i] = fFieldRestrictions.get( i ).getField();
}
return returnValue;
}
/**
* Gets the fields to include in the result of the query.<p>
* @return An array of field references that should be included in the results of
* this query, or null if all fields are to be included
*/
public List<ElementRef> getFieldRestrictionRefs() {
return fFieldRestrictions;
}
/**
* Gets the conditions placed on this query.<p>
* @return An array of ConditionGroup objects in evaluation order. The
* children of the root ConditionGroup are returned. If no conditions
* have been specified, an empty array is returned.
*/
public ConditionGroup[] getConditions()
{
if( fRoot == null )
return new ConditionGroup[0];
ConditionGroup[] groups = fRoot.getGroups();
if( groups != null && groups.length > 0 )
return groups;
// There is a fRoot group -- which means the user must have called
// the default constructor and then called addCondition() to add one
// or more conditions -- but the root group does not itself have any
// nested groups. So, just return the root group...
return new ConditionGroup[] { fRoot };
}
/**
* Gets the root ConditionGroup.<p>
* @return The root ConditionGroup that was established by the constructor.
* If this query has no conditions, null is returned.
*/
public ConditionGroup getRootConditionGroup()
{
return fRoot;
}
/**
* Determines if this Query has any conditions
* @return true if the query has one or more conditions
* @see #getConditions
* @see #addCondition(ElementDef, ComparisonOperators, String)
*/
public boolean hasConditions()
{
return fRoot != null && fRoot.hasConditions();
}
/**
* Determines if this Query has any field restrictions
* @return true if the query specifies a subset of fields to be returned;
* false if the query returns all elements and attributes of each object
* matching the query conditions
* @see #getFieldRestrictions
* @see #addFieldRestriction
* @see #setFieldRestrictions
*/
public boolean hasFieldRestrictions() {
return fFieldRestrictions != null && fFieldRestrictions.size() > 0;
}
/**
* Tests if this Query has a specific element or attribute condition<p>
* @param elementOrAttr The ElementDef constant from the SIFDTD class that
* identifies the specific attribute or element to search for
* @return The Condition object representing the condition. If no
* Condition exists for the element or attribute, null is returned
*/
public Condition hasCondition( ElementDef elementOrAttr )
{
ConditionGroup[] grps = getConditions();
for( int i = 0; i < grps.length; i++ ) {
Condition c = grps[i].hasCondition( elementOrAttr );
if( c != null )
return c;
}
return null;
}
/**
* Tests if this Query has a condition referencing a specific xPath<p>
* @param xPath The Xpath which identifies the specific attribute or element to search for
* @return The Condition object representing the condition. If no
* Condition exists for the element or attribute, null is returned
*/
public Condition hasCondition( String xPath )
{
ConditionGroup[] grps = getConditions();
for( int i = 0; i < grps.length; i++ ) {
Condition c = grps[i].hasCondition( xPath );
if( c != null )
return c;
}
return null;
}
/**
* Sets the value of the SIF_Request/SIF_Version element. By default,
* this value is set to the version of SIF declared for the agent when the
* ADK was initialized.<p>
*
* @param versions The version(s) of SIF the responding agent should use when
* returning SIF_Response messages for this query
*/
public void setSIFVersions( SIFVersion... versions ) {
fVersions = versions;
}
/**
* Sets the value of the SIF_Request/SIF_Version element. By default,
* this value is set to the version of SIF declared for the agent when the
* ADK was initialized.<p>
*
* @return The version of SIF the responding agent should use when
* returning SIF_Response messages for this query
*/
public SIFVersion[] getSIFVersions() {
return fVersions;
}
/**
* From the list of SIFVersions associated with this Query, returns the latest SIFVersion
* supported by the current ADK instance.
* @see ADK#getLatestSupportedVersion(SIFVersion[])
* @return The latest SIFVersion supported by the ADK for this Query
*/
public SIFVersion getEffectiveVersion()
{
return ADK.getLatestSupportedVersion( fVersions );
}
/**
* Sets the root ConditionGroup.<p>
*
* By default a Query is constructed with a ConditionGroup to which
* individual conditions will be added by the <code>addCondition</code>
* methods. You can call this method to prepare a ConditionGroup ahead of
* time and replace the default with your own.<p>
*
* Note calling this method after <code>addCondition</code> will replace
* any conditions previously added to the Query with the conditions in the
* supplied ConditionGroup.<p>
* @param root The root ConditionGroup to use for this query
*/
public void setConditionGroup( ConditionGroup root ) {
fRoot = root;
}
/**
* Evaluate the given the SIFDataObject against the conditions provided in the
* Query. All conditions are evaluated using standard string comparisons using
* the system's locale-specific collator
* @param obj The SIFDataObject to evalaute against this query
* @return TRUE if the SIFDataObject satisfies the conditions in the Query, otherwise FALSE
* @throws ADKSchemaException If the condition contains references to invalid elements
*/
public boolean evaluate( SIFDataObject obj ) throws ADKSchemaException
{
return evaluate( obj, getCollator() );
}
/**
* Evaluate the given the SIFDataObject against the conditions provided in the
* Query. All conditions are evaluated using the provided comparer
* @param obj The SIFDataObject to evalaute against this query
* @param comparer The comparer used to do comparisons
* @return TRUE if the SIFDataObject satisfies the conditions in the Query, otherwise FALSE
* @throws ADKSchemaException If the condition contains references to invalid elements
*/
public boolean evaluate( SIFDataObject obj, Comparator comparer ) throws ADKSchemaException
{
if ( !( obj.getElementDef() == fObjType ) ){
return false;
}
if( fRoot != null ){
SIFXPathContext context = SIFXPathContext.newSIFContext( obj, this.getEffectiveVersion() );
return evaluateConditionGroup( context, fRoot, comparer );
}
return true;
}
/**
* Evaluates a condition group against a SifDataObject to determine if
* they are a match or not
* @param grp
* @param obj
* @return True if the result of evaluating the condition groups is true
* @throws ADKSchemaException If the condition contains references to invalid elements
*/
private boolean evaluateConditionGroup(
SIFXPathContext context,
ConditionGroup grp,
Comparator comparer
) throws ADKSchemaException
{
Condition[] conds = grp.getConditions();
if (conds.length > 0) {
boolean returnOnFirstMatch = grp.getOperator() == GroupOperators.OR ? true
: false;
for (Condition c : conds) {
if ((evaluateCondition(context, c, comparer)) == returnOnFirstMatch) {
// If this is an OR group, return true on the first match
// If this is an AND Group, return false on the first
// failure
return returnOnFirstMatch;
}
}
// None of the conditions matched the returnOnFirstMathValue.
// Therefore,
// return the opposite value
return !returnOnFirstMatch;
} else {
return evaluateConditionGroups(context, grp.getOperator(), grp
.getGroups(), comparer);
}
}
/**
* Evaluates the condition groups and returns True if the Operator is OR and
* at least one of the groups evaluates to TRUE. If the Operator is AND, all
* of the condition groups have to evaluate to TRUE
*
* @param operation
* @param grps
* @param context
* the SIFXPathContext to use for evaluating conditions
* @return true if the object meets all of the query conditions
* @throws ADKSchemaException
* If the condition contains references to invalid elements
*/
private boolean evaluateConditionGroups(
SIFXPathContext context,
GroupOperators operation,
ConditionGroup[] grps,
Comparator comparer ) throws ADKSchemaException
{
boolean isMatch = true;
for ( int c = 0; c < grps.length; c++ )
{
boolean singleMatch = evaluateConditionGroup( context, grps[ c ], comparer );
if ( operation == GroupOperators.OR )
{
if ( singleMatch )
{
// In OR mode, return as soon as we evaluate to True
return true;
}
isMatch |= singleMatch;
}
else
{
isMatch &= singleMatch;
}
// As soon as the evaluation fails, return
if ( !isMatch )
{
return false;
}
}
return isMatch;
}
/**
* Evaluates a single SIF_Condition against an object and returns whether it matches or not
* @param cond
* @param obj
* @return True if the specified condition is true of this object
* @throws ADKSchemaException If the condition contains references to invalid elements
*/
private boolean evaluateCondition(
SIFXPathContext context,
Condition cond,
Comparator comparer ) throws ADKSchemaException
{
// TODO: Add support for comparison using the SIF Data Types
Element def = context.getElementOrAttribute( cond.getXPath() );
String conditionValue = cond.getValue();
String elementValue = null;
if( def != null ){
SIFSimpleType value = def.getSIFValue();
if( value != null ){
// Format the value to string, based on the query version
elementValue = value.toString( this.getEffectiveVersion() );
} else {
// TODO: Not sure if this would ever return a value if the above does not
elementValue = def.getTextValue();
}
}
if ( elementValue == null || conditionValue == null )
{
// Don't use standard comparision because it will fail. If
// one or the other value is null, it cannot be compared, except for
// if the operator is EQ or NOT
boolean bothAreNull = ( elementValue == null && conditionValue == null );
switch( cond.getOperator() )
{
case EQ:
case GE:
case LE:
return bothAreNull;
case NE:
return !bothAreNull;
default:
// For any other operator, the results are indeterminate with
// null values. Return false in this case.
return false;
}
}
int compareLevel = comparer.compare( elementValue, conditionValue );
switch ( cond.getOperator() )
{
case EQ:
return compareLevel == 0;
case NE:
return compareLevel != 0;
case GT:
return compareLevel > 0;
case LT:
return compareLevel < 0;
case GE:
return compareLevel >= 0;
case LE:
return compareLevel <= 0;
}
return false;
}
/**
* Returns the collator that is appropriate for the current locale
* @return The default collator
*/
private Collator getCollator()
{
if( fCollator == null ){
fCollator = Collator.getInstance();
}
return fCollator;
}
/**
* Sets the SIFContext that this query should apply to
* @param context The SIF Context that this query applies to
*/
public void setSIFContext(SIFContext context) {
this.fContext = context;
}
/**
* Gets the SIF Context that this Query applies to
* @return The SIF Context that this query applies to
*/
public SIFContext getSIFContext() {
return fContext;
}
/**
* If SIFElement restrictions are placed on this query, this method
* will take the SIFDataObject and call setChanged(false). It will then
* go through each of the SIFElement restrictions, resolve them, and
* call setChanged(true) on those elements only. This will cause the
* object to be rendered properly using SIFWriter.
* @param sdo
*/
public void setRenderingRestrictionsTo( SIFDataObject sdo ) {
if( sdo == null || fFieldRestrictions == null ){
return;
}
sdo.setChanged( false );
// Go through and only set the filtered items to true
SIFXPathContext context = SIFXPathContext.newSIFContext( sdo );
for( ElementRef ref : fFieldRestrictions ){
String xPath = ref.getXPath();
Element e = context.getElementOrAttribute( xPath );
if( e != null ){
e.setChanged();
}
}
sdo.ensureRootElementRendered();
}
private void writeObject( java.io.ObjectOutputStream out )
throws IOException
{
out.defaultWriteObject();
ElementDefImpl.writeObject(fObjType,out);
}
private void readObject( java.io.ObjectInputStream in )
throws IOException, ClassNotFoundException
{
in.defaultReadObject();
fObjType=ElementDefImpl.readObject(in);
}
}