// // Copyright (c)1998-2011 Pearson Education, Inc. or its affiliate(s). // All rights reserved. // package openadk.library.tools.queries; import java.util.*; import openadk.library.*; /** * The abstract base class for query formatters, which format SIF_Query queries * in another form such as an SQL WHERE clause. The way in which a query is * formatted is determined by the subclass implementation. A subclass must * implement these methods: * <p> * * <ul> * <li>getOpenBrace</li> * <li>getCloseBrace</li> * <li>getOperator</li> * <li>renderField</li> * <li>renderValue</li> * </ul> * <p> * * @author Edustructures LLC * @version ADK 1.0 */ public abstract class QueryFormatter { /** * Constructs a QueryFormatter */ public QueryFormatter() { } /** * Builds a query string given a dictionary of field definitions and a Query * instance. This method evaluates the conditions of that Query to produce * a textual query string in the format determined by the implementation.<p> * * The dictionary should contain application-defined field values that map * to <code>ElementDef</code> key elements. Whenever a SIF element or attribute * is found in the Query, the corresponding application-defined field is * used in its place.<p> * * A special convention allows agents to define an in-line translation table for * replacing SIF element/attribute values with values defined in the table. If * a field is expressed in the form "field-name{value1=cons1;value2=cons2;..}", * the comma-delimited list of values within the curly braces is applied to the * value of the SIF_Value element in the SIF_Query, such that "value1" is * represented as "cons1". For example, the acceptable values for * LibraryPatronStatus/@SifRefIdType attribute are "StudentPersonal" and * "StaffPersonal". If in your application you represent these values as numeric * types - say, 1 and 2, respectively - you could create the following in-line * translation table to instruct the QueryFormatter to substitute * "StudentPersonal" with "1" and "StaffPersonal" with "2": * * "CircRecord.PatronType{StudentPersonal=1;StaffPersonal=2}" * * @param query An ADK Query object, usually obtained during the processing * of a SIF_Request by a <i>Publisher</i> message handler * @param table A dictionary that maps <code>SIFDTD</code> ElementDef constants to * application-defined field values */ public String format( Query query, Map table ) throws QueryFormatterException { return format( query, table, true ); } public String format( Query query, Map table, boolean explicit ) throws QueryFormatterException { StringBuffer str = new StringBuffer(); ConditionGroup[] grp = query.getConditions(); for( int c = 0; c < grp.length; c++ ) { str.append( getOpenBrace() ); evaluateConditionGroup( query, grp[c], str, table, explicit ); str.append( getCloseBrace() ); if( c != grp.length - 1 ) str.append( getOperator( query.getRootConditionGroup().getOperator() ) ); } return str.toString(); } protected void evaluateConditionGroup( Query query, ConditionGroup grp, StringBuffer str, Map table, boolean explicit ) throws QueryFormatterException { Condition[] conds = grp.getConditions(); if( conds.length != 0 ) { for( int i = 0; i < conds.length; i++ ) { evaluateCondition( query, conds[i], str, table, explicit ); if( i != conds.length - 1 ) str.append( getOperator( grp.getOperator() ) ); } } else { ConditionGroup[] groups = grp.getGroups(); for( int i = 0; i < groups.length; i++ ) { str.append( getOpenBrace() ); evaluateConditionGroup( query, groups[i], str, table, explicit ); str.append( getCloseBrace() ); if( i != groups.length - 1 ) str.append( getOperator( grp.getOperator() ) ); } } } protected void evaluateCondition( Query query, Condition cond, StringBuffer str, Map table, boolean explicit ) throws QueryFormatterException { String path = cond.getXPath(); Object o = table.get( path ); if( o == null ){ ElementDef field = cond.getField(); if( field != null ){ o = table.get( field ); } } if( o == null ){ if( explicit ){ throw new QueryFormatterException( "QueryFormatter was not provided with an application-defined field value for " + query.getObjectTag() + "/" + cond.getXPath() + "; cannot format the SIF_Query" ); } else { // Render a default operation that will always return true // as a placeholder str.append( "1=1" ); return; } } if ( o instanceof QueryField ) { str.append( ((QueryField) o).render( this, query, cond ) ); } else { str.append( renderField( cond.getField(), o ) ); str.append( getOperator( cond.getOperator() ) ); str.append( renderValue( cond.getValue(), o ) ); } } /** * Return the text that should be inserted for an opening brace */ public abstract String getOpenBrace(); /** * Return the text that should be inserted for a closing brace */ public abstract String getCloseBrace(); /** * Return the text that should be inserted for the particular comparison operator, such as "Equals" */ public abstract String getOperator( ComparisonOperators op ); /** * Return the text that should be inserted for the particular grouping operator, such as "AND" */ public abstract String getOperator( GroupOperators op ); /** * Return the text for a field name * @param field The field name * @param def The corresponding field definition from the Map passed to * the <code>format</code> method * @return The implementation returns the field name in whatever form is * appropriate to the implementation, using the supplied <i>def</i> * Object if necessary to obtain additional field information. */ public abstract String renderField( ElementDef field, Object def ) throws QueryFormatterException; /** * Return the text for a field value * @param field The field value * @param def The corresponding field definition from the Map passed to * the <code>format</code> method * @return The implementation returns the field value in whatever form is * appropriate to the implementation, using the supplied <i>def</i> * Object if necessary to obtain additional field information */ public abstract String renderValue( String value, Object def ) throws QueryFormatterException; /** * Extracts a field name from a string in the form "field-name{...}" */ protected String extractFieldName( String def ) { int i = def.indexOf('{'); if( i == -1 ) return def; return def.substring(0,i); } /** * Applies the value substitutions defined for a field as described in the * class comments. For example, if the source string passed to this method * has the value "Blue" and the field mapping definition has the value * "Color{Red=0;Green=1;Blue=2}", a value of "2" will be returned. * <p> * * @param src The value to process * @param def The application-defined field mapping to apply */ protected String doValueSubstitution( String src, String def ) { int i = def.indexOf( '{' ); if( i == -1 ) return src; int end = def.indexOf( '}', i+1 ); if( end == -1 ) return src; String trans = def.substring(i+1,end); StringTokenizer tok = new StringTokenizer( trans,";" ); while( tok.hasMoreTokens() ) { String s = tok.nextToken(); i = s.indexOf('='); if( i != -1 ) { if( i == 0 ) return s.substring(1); String cmp = s.substring(0,i); if( cmp.equals( src ) ) return s.substring(i+1); } } return src; } }