// // Copyright (c)1998-2011 Pearson Education, Inc. or its affiliate(s). // All rights reserved. // package openadk.library.tools.queries; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.Map.Entry; import openadk.library.*; import openadk.library.tools.mapping.*; /** * An implementation of a QueryFormatter that formats SIF_Query conditions as a * string suitable for inclusion in an SQL <code>WHERE</code> clause.<p> * * When preparing a dictionary to be passed to the <code>SQLQueryFormatter.format</code> * method, the caller must map <code>SIFDTD</code> ElementDef constants to instances * of the <code>SQLField</code> class. The constructor to that class requires two * parameters: the application-defined name of the field, and a type code constant * from the <code>java.sql.Types</code> class. The type code is used to properly * render the field value according to its data type (e.g. strings are quoted with * a single quote, numeric fields are rendered as-is, etc.) * <p> * * For example,<p> * * <code> * Map m = new HashMap();<br/> * m.put( SIFDTD.STUDENTPERSONAL_REFID,<br/> *     new SQLField( "Students.Foreign_ID", java.sql.Types.VARCHAR ) );<br/> * m.put( SIFDTD.NAME_LASTNAME,<br/> *     new SQLField( "Students.Last_Name", java.sql.Types.VARCHAR ) );<br/> * m.put( SIFDTD.NAME_FIRSTNAME,<br/> *     new SQLField( "First_Name", java.sql.Types.VARCHAR ) );<br/> * m.put( SIFDTD.DEMOGRAPHICS_CITIZENSHIPSTATUS,<br/> *     new SQLField( "Students.US_Citizen_Bool{04=1;=0}", java.sql.Types.NUMERIC ) );<br/> * </code> * * The above example might result in a string such as "( Students.US_Citizen_Bool = 0 )" * or "( Students.Foreign_ID = '898' ) OR ( Students.Last_Name = 'Cortez' AND First_Name = 'Robert' )"<p> * * @author Edustructures LLC * @version ADK 1.0 */ public class SQLQueryFormatter extends QueryFormatter { private Map<Object, Object> fFields; /** * Constructs a SQLQueryFormatter */ public SQLQueryFormatter() { super(); } /** * Adds an SQLField to use for rendering an SQL Where clause using * the {@link #format(Query)} method. * @param def The ElementDef that is represented by the field * @param field The SQL representation of the field */ public void addField( ElementDef def, SQLField field ) { if( fFields == null ){ fFields = new HashMap<Object, Object>(); } fFields.put( def, field ); } /** * Adds an SQLField to use for rendering an SQL Where clause using * the {@link #format(Query)} method. * @param def The XPath representing the SIF Element being mapped * @param field The SQL representation of the field */ public void addField( String xPath, SQLField field ) { if( fFields == null ){ fFields = new HashMap<Object, Object>(); } fFields.put( xPath, field ); } /** * Adds a QueryField to use for rendering an SQL Where clause using * the {@link #format(Query)} method. * @param def The ElementDef that is represented by the field * @param field The SQL representation of the field */ public void addField( ElementDef def, QueryField field ) { if( fFields == null ){ fFields = new HashMap<Object, Object>(); } fFields.put( def, field ); } /** * Adds a QueryField to use for rendering an SQL Where clause using * the {@link #format(Query)} method. * @param def The xPath representing the SIF Element being mapped * @param field The SQL representation of the field */ public void addField( String xPath, QueryField field ) { if( fFields == null ){ fFields = new HashMap<Object, Object>(); } fFields.put( xPath, field ); } /** * Adds SQLFields to represent each field rule in the specified MappingsContext. * * @param context The MappingsContext containing FieldMappings */ public void addFields( MappingsContext context ){ addFields( context, SQLDialect.DEFAULT ); } /** * Adds SQLFields to represent each field rule in the specified MappingsContext. * * @param context The MappingsContext containing FieldMappings */ public void addFields( MappingsContext context, Dialect dialect ){ for( Mapping amapping : context.getFieldMappings() ){ if (amapping instanceof FieldMapping) { FieldMapping mapping = (FieldMapping)amapping; Rule rule = mapping.getRule(); if( rule instanceof XPathRule ) { // JEN SIFTypeConverter converter = null; XPathRule xRule = (XPathRule)rule; ElementDef targetDef = xRule.lookupTargetDef( context.getObjectDef() ); if( targetDef != null ){ converter = targetDef.getTypeConverter(); } if( converter == null ){ converter = SIFTypeConverters.STRING; } if( mapping.getValueSetID() != null ){ ValueSet vs = context.getMappings().getValueSet( mapping.getValueSetID(), true ); if( vs != null ){ // Create the lookup table for generating the SQL lookup StringBuilder buffer = new StringBuilder(); // e.g. CircRecord.PatronType{StudentPersonal=1;StaffPersonal=2} //buffer.Append( tablePrefix ); buffer.append( mapping.getFieldName() ); buffer.append( '{' ); ValueSetEntry [] entries = vs.getEntries(); for ( int a = 0; a < entries.length; a++ ) { if ( a > 0 ) { buffer.append( ';' ); } buffer.append( entries[ a ].value ); buffer.append( '=' ); buffer.append( entries[ a ].name ); } buffer.append( '}' ); SQLField field = new SQLField( buffer.toString(), converter.getSQLType(), dialect ); addField( xRule.getXPath(), field ); continue; } } SQLField field = new SQLField( mapping.getFieldName(), converter.getSQLType(), dialect ); addField( xRule.getXPath(), field ); } } } } /** * Returns all of the SQLFields that have been mapped for this instance * @return */ public Set<Entry<Object,Object>> getFields() { return fFields.entrySet(); } /** * 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 */ public String format( Query query ) throws QueryFormatterException { return format( query, true ); } public String format( Query query, boolean explicit ) throws QueryFormatterException { if( fFields == null && query.hasConditions() ){ throw new QueryFormatterException( "Agent is not configured to respond to query conditions" ); } return format( query, fFields, explicit ); } /** * Return the text that should be inserted for an opening brace */ public String getOpenBrace() { return "( "; } /** * Return the text that should be inserted for a closing brace */ public String getCloseBrace() { return " )"; } /** * Return the text that should be inserted for the particular comparison operator, such as "Equals" */ public String getOperator( ComparisonOperators op ) { switch( op ) { case EQ: return " = "; case NE: return " != "; case GT: return " > "; case LT: return " < "; case GE: return " >= "; case LE: return " <= "; } return ""; } /** * Return the text that should be inserted for the particular grouping operator, such as "AND" */ public String getOperator( GroupOperators op ) { switch( op ) { case NONE: case AND: return " AND "; case OR: return " OR "; } return ""; } /** * Return the text for a field name * @param field The field * @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 String renderField( ElementDef field, Object def ) throws QueryFormatterException { try { return extractFieldName( ((SQLField)def).Name ); } catch( ClassCastException cce ) { throw new QueryFormatterException( "SQLQueryFormatter requires that the Map passed to the format method consist of SQLField instances (not " + cce.getMessage() + " instances)" ); } } /** * 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 String renderValue( String value, Object def ) throws QueryFormatterException { try { // The elements in the Map are expected to be SQLField instances SQLField f = (SQLField)def; return f.render( doValueSubstitution(value,f.Name) ); } catch( ClassCastException cce ) { throw new QueryFormatterException( "SQLQueryFormatter requires that the Map passed to the format method consist of SQLField instances (not " + cce.getMessage() + " instances)" ); } } }