// // Copyright (c)1998-2011 Pearson Education, Inc. or its affiliate(s). // All rights reserved. // package openadk.examples.tinysis; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.sql.Statement; import java.util.Map; import openadk.examples.tinysis.data.ResultSetAdapter; import openadk.library.ADK; import openadk.library.ADKException; import openadk.library.DataObjectOutputStream; import openadk.library.DefaultValueBuilder; import openadk.library.MessageInfo; import openadk.library.Publisher; import openadk.library.Query; import openadk.library.SIFDataObject; import openadk.library.SIFErrorCategory; import openadk.library.SIFErrorCodes; import openadk.library.SIFException; import openadk.library.SIFMessageInfo; import openadk.library.Zone; import openadk.library.tools.mapping.ADKMappingException; import openadk.library.tools.mapping.FieldAdaptor; import openadk.library.tools.mapping.Mappings; import openadk.library.tools.mapping.MappingsContext; import openadk.library.tools.queries.QueryFormatterException; import openadk.library.tools.queries.SQLDialect; import openadk.library.tools.queries.SQLQueryFormatter; /** * * @author ADK Examples * */ public class DataObjectProvider implements Publisher { private String fTableName; private Mappings fMappings; public DataObjectProvider( String tableName, Mappings rootMappings ){ fTableName = tableName; fMappings = rootMappings; } /* (non-Javadoc) * @see openadk.library.Publisher#onRequest(openadk.library.DataObjectOutputStream, openadk.library.Query, openadk.library.Zone, openadk.library.MessageInfo) */ public void onRequest(DataObjectOutputStream out, Query query, Zone zone, MessageInfo info) throws ADKException { SIFMessageInfo smi = (SIFMessageInfo)info; System.out.println( "Received a request for " + query.getObjectTag() + " from agent \"" + smi.getSourceId() + "\" in zone " + zone.getZoneId() ); MappingsContext mappingsContext = fMappings.selectOutbound( query.getObjectType(), smi.getLatestSIFRequestVersion(), zone.getZoneId(), smi.getSourceId()); // Configure the output stream to automatically filter out any // objects that don't match the query conditions // NOTE: This is in addition to the filtering done by the SQL query below out.setAutoFilter( query ); // Read all objects from the database to populate a HashMap of // field/value pairs. The field names can be whatever we choose as long // as they match the field names used in the <mappings> section of the // agent.cfg configuration file. Each time a record is read, convert it // to the specified SIF data object using the Mappings class and stream it to // the supplied output stream. String sql = convertQueryToSql( smi, query, mappingsContext ); Connection conn = getConnection(zone); try { int count = 0; ResultSet rs = getData( conn, sql, zone ); ResultSetAdapter rsa = new ResultSetAdapter( rs ); mappingsContext.setValueBuilder( new DefaultValueBuilder( rsa ) ); while( rsa.next() ) { count++; SIFDataObject sdo = ADK.DTD().createSIFDataObject( query.getObjectType() ); sdo.setSIFVersion( smi.getLatestSIFRequestVersion() ); onMappingObject( smi, query, mappingsContext, rsa, sdo); out.write( sdo ); } rs.close(); System.out.println( "- Returned " + count + " records from the database in response" ); } catch( Exception ex ) { System.out.println( "Error returning a SIF_Error response: " + ex.toString() ); throw new SIFException( SIFErrorCategory.REQUEST_RESPONSE, SIFErrorCodes.REQRSP_GENERIC_ERROR_1, "An error occurred while querying the database for students", zone, ex ); } finally { if( conn != null ) { try { conn.close(); } catch( Exception ignored ) { System.out.println( ignored.toString() ); } } } } /** * Perform a mappings operation on the given object. This method allows * subclasses to override behavior and change values before or after * mapping occurs * @param smi * @param q * @param mappingsContext * @param oma * @param sdo * @throws ADKMappingException */ protected void onMappingObject(SIFMessageInfo smi, Query q, MappingsContext mappingsContext, FieldAdaptor oma, SIFDataObject sdo) throws ADKMappingException { mappingsContext.map( sdo, oma ); } /** * Adds fields to the SQLQueryFormatter so that it can render the SQL. This method * allows subclasses to override behavior and to add additional SQLFields, representing * calculated values. A good example of this is queries on StudentSchoolEnrollment/TimeFrame, * which can be calculated based on EntryDate and ExitDate * @param smi * @param query * @param context * @param formatter */ protected void onAddQueryFormatterFields( SIFMessageInfo smi, Query query, MappingsContext context, SQLQueryFormatter formatter ) { formatter.addFields( context, SQLDialect.DEFAULT ); } private String convertQueryToSql( SIFMessageInfo smi, Query query, MappingsContext mappingsContext) throws QueryFormatterException { StringBuilder sql = new StringBuilder(); sql.append( "Select * FROM " ); sql.append( fTableName ); // TODO: Commented out for now.... if( query.hasConditions() ){ SQLQueryFormatter sqlFormatter = new SQLQueryFormatter(); onAddQueryFormatterFields( smi, query, mappingsContext, sqlFormatter ); sql.append( " WHERE " ); sql.append( sqlFormatter.format( query, false ) ); } return sql.toString(); } /** * Gets a connection from the database. If a connection cannot be obtained, * a SIFException is thrown with the retry flag set to True to signal to the * ZIS to retry the message later. * @param zone * @return * @throws SIFException */ private Connection getConnection(Zone zone) throws SIFException { Connection conn = null; try { // Get a Connection conn = DriverManager.getConnection( zone.getProperties().getProperty( "jdbc.url" )); conn.setAutoCommit( true ); } catch( SQLException sqlEx ){ SIFException sifEx = new SIFException( SIFErrorCategory.SYSTEM, SIFErrorCodes.SYS_GENERIC_ERROR_1, "Unable to connect to application database", zone, sqlEx ); // Notify the ZIS to re-send the message later sifEx.setRetry( true ); throw sifEx; } return conn; } /** * Gets a ResultSet from the database. If an exception occurs, the appropriate * SIFException is thrown * @param conn An open connection to the database * @param sql The SQL to run to get data * @return A ResultSet of data * @throws SIFException */ private ResultSet getData( Connection conn, String sql, Zone zone ) throws SIFException { try { // Query the database for all students Statement s = conn.createStatement(); return s.executeQuery( sql ); } catch( SQLException sqlEx ){ SIFException sifEx = new SIFException( SIFErrorCategory.SYSTEM, SIFErrorCodes.SYS_GENERIC_ERROR_1, "Error executing query", zone, sqlEx ); throw sifEx; } } /** * Gets the current row of data from the ResultSet and stores all fields * in a Map. The Map is used by the ADK to retrieve data for Mapping * operations. * @param resultSet The ResultSet object to get a row of data from * @return The current row of data, contained in a Map */ private void getDataValues( ResultSet resultSet, Map<String,Object> values ) throws SQLException { ResultSetMetaData rsmd = resultSet.getMetaData(); for( int a = 1; a < ( rsmd.getColumnCount() + 1 ); a++ ) { try{ String fieldName = rsmd.getColumnName( a ); Object value = resultSet.getObject( a ); values.put( fieldName, value ); } catch( SQLException sqlEx ){ System.out.println( "Error on field # " + a + sqlEx ); } } } }