/* ==================================================================== * SNOCS Notification Framework * ==================================================================== */ package OpenRate.adapter.cassandra; import OpenRate.CommonConfig; import OpenRate.adapter.AbstractTransactionalOutputAdapter; import OpenRate.configurationmanager.ClientManager; import OpenRate.exception.InitializationException; import OpenRate.exception.ProcessingException; import OpenRate.logging.LogUtil; import OpenRate.record.IRecord; import OpenRate.record.KeyValuePairRecord; import OpenRate.utils.PropertyUtils; import java.util.Collection; import org.apache.cassandra.thrift.Cassandra; import org.apache.thrift.protocol.TBinaryProtocol; import org.apache.thrift.protocol.TProtocol; import org.apache.thrift.transport.TFramedTransport; import org.apache.thrift.transport.TSocket; import org.apache.thrift.transport.TTransport; import org.apache.thrift.transport.TTransportException; /** * <p> * ActiveMQ Output Adapter.<br> * * This module writes records into a Cassandra key space. */ public abstract class AbstractCassandraOutputAdapter extends AbstractTransactionalOutputAdapter { /** * The name of the queue we are scanning. */ protected String cassandraIPAddr = null; /** * The name of the host where the queue is. */ protected int cassandraPort = 0; /** * The port where the queue is. */ protected String cassandraUserName = null; /** * The name of the queue we are scanning. */ protected String cassandraPassword = null; // List of Services that this Client supports private static final String SERVICE_CASSANDRA_IP_ADDR = "CassandraIPAddress"; private static final String SERVICE_CASSANDRA_PORT = "CassandraPort"; private static final String SERVICE_CASSANDRA_USER_NAME = "CassandraUserName"; private static final String SERVICE_CASSANDRA_PASSWORD = "CassandraPassword"; // This tells us if we should look for new work or continue with something // that is going on at the moment private boolean OutputStreamOpen = false; // The Cassandra transport private TTransport tr; // The Casasandra client private Cassandra.Client client; /** * Initialise the module. Called during pipeline creation. Initialise the * Logger, and load the SQL statements. * * @param PipelineName The name of the pipeline this module is in * @param ModuleName The module symbolic name of this module * @throws OpenRate.exception.InitializationException */ @Override public void init(String PipelineName, String ModuleName) throws InitializationException { String ConfigHelper; super.init(PipelineName, ModuleName); registerClientManager(); // Register ourself with the client manager setSymbolicName(ModuleName); // get the IP address of the SMSC ConfigHelper = initCassandraIPAddress(); processControlEvent(SERVICE_CASSANDRA_IP_ADDR, true, ConfigHelper); // get the port for the SMSC ConfigHelper = initCassandraPort(); processControlEvent(SERVICE_CASSANDRA_PORT, true, ConfigHelper); // get the user name ConfigHelper = initCassandraUserName(); processControlEvent(SERVICE_CASSANDRA_USER_NAME, true, ConfigHelper); // get the user name ConfigHelper = initCassandraPassword(); processControlEvent(SERVICE_CASSANDRA_PASSWORD, true, ConfigHelper); // initialise the valid queue producer try { initCassandraConnection(); } catch (Exception ex) { throw new InitializationException("Error opening Cassandra connection <" + ex.getMessage() + ">", getSymbolicName()); } } /** * Prepare good records for writing to the defined output stream. * * @param r The current record we are working on * @return The prepared record * @throws ProcessingException */ @Override public IRecord prepValidRecord(IRecord r) throws ProcessingException { try { // We perform the sending here procValidRecord(r); } catch (ProcessingException pe) { // Pass the exception up String Message = "Processing exception preparing valid record in module <" + getSymbolicName() + ">. Message <" + pe.getMessage() + ">. Aborting transaction."; getPipeLog().fatal(Message); getExceptionHandler().reportException(new ProcessingException(pe, getSymbolicName())); setTransactionAbort(getTransactionNumber()); } catch (Exception ex) { // Not good. Abort the transaction String Message = "Unexpected Exception preparing valid record in module <" + getSymbolicName() + ">. Message <" + ex.getMessage() + ">. Aborting transaction."; getPipeLog().fatal(Message); getExceptionHandler().reportException(new ProcessingException(Message, ex, getSymbolicName())); setTransactionAbort(getTransactionNumber()); } return r; } /** * Prepare bad records for writing to the defined output stream. * * @param r The current record we are working on * @return The prepared record * @throws ProcessingException */ @Override public IRecord prepErrorRecord(IRecord r) throws ProcessingException { try { // We perform the sending here procErrorRecord(r); } catch (ProcessingException pe) { // Pass the exception up String Message = "Processing exception preparing valid record in module <" + getSymbolicName() + ">. Message <" + pe.getMessage() + ">. Aborting transaction."; getPipeLog().fatal(Message); getExceptionHandler().reportException(new ProcessingException(pe, getSymbolicName())); setTransactionAbort(getTransactionNumber()); } catch (Exception ex) { // Not good. Abort the transaction String Message = "Unexpected Exception preparing valid record in module <" + getSymbolicName() + ">. Message <" + ex.getMessage() + ">. Aborting transaction."; getPipeLog().fatal(Message); getExceptionHandler().reportException(new ProcessingException(Message, ex, getSymbolicName())); setTransactionAbort(getTransactionNumber()); } return r; } /** * This is called when a data record is encountered. You should do any normal * processing here. Note that the result is a collection for the case that we * have to re-expand after a record compression input adapter has done * compression on the input stream. * * @param r The record we are working on * @return The collection of processed records * @throws ProcessingException */ public abstract Collection<KeyValuePairRecord> procValidRecord(IRecord r) throws ProcessingException; /** * This is called when a data record with errors is encountered. You should do * any processing here that you have to do for error records, e.g. statistics, * special handling, even error correction! * * @param r The record we are working on * @return The collection of processed records * @throws ProcessingException */ public abstract Collection<KeyValuePairRecord> procErrorRecord(IRecord r) throws ProcessingException; // ----------------------------------------------------------------------------- // ------------------ Custom connection management functions ------------------- // ----------------------------------------------------------------------------- /* * closeStream() is called by the pipeline when no more information comes * down it. We must perform a transaction state change here to FLUSHED */ @Override public void closeStream(int TransactionNumber) { if (OutputStreamOpen) { setTransactionFlushed(TransactionNumber); OutputStreamOpen = false; } } // ----------------------------------------------------------------------------- // ------------- Start of inherited IEventInterface functions ------------------ // ----------------------------------------------------------------------------- /** * processControlEvent is the event processing hook for the External Control * Interface (ECI). This allows interaction with the external world, for * example turning the dumping on and off. * * @param Command The command that we are to work on * @param Init True if the pipeline is currently being constructed * @param Parameter The parameter value for the command * @return The result message of the operation */ @Override public String processControlEvent(String Command, boolean Init, String Parameter) { int ResultCode = -1; if (Command.equalsIgnoreCase(SERVICE_CASSANDRA_IP_ADDR)) { if (Init) { cassandraIPAddr = Parameter; ResultCode = 0; } else { if (Parameter.equals("")) { return cassandraIPAddr; } else { return CommonConfig.NON_DYNAMIC_PARAM; } } } if (Command.equalsIgnoreCase(SERVICE_CASSANDRA_PORT)) { if (Init) { cassandraPort = Integer.parseInt(Parameter); ResultCode = 0; } else { if (Parameter.equals("")) { return String.valueOf(cassandraPort); } else { return CommonConfig.NON_DYNAMIC_PARAM; } } } if (Command.equalsIgnoreCase(SERVICE_CASSANDRA_USER_NAME)) { if (Init) { cassandraUserName = Parameter; ResultCode = 0; } else { if (Parameter.equals("")) { return cassandraUserName; } else { return CommonConfig.NON_DYNAMIC_PARAM; } } } if (Command.equalsIgnoreCase(SERVICE_CASSANDRA_PASSWORD)) { if (Init) { cassandraPassword = Parameter; ResultCode = 0; } else { if (Parameter.equals("")) { return cassandraPassword; } else { return CommonConfig.NON_DYNAMIC_PARAM; } } } if (ResultCode == 0) { getPipeLog().debug(LogUtil.LogECIPipeCommand(getSymbolicName(), getPipeName(), Command, Parameter)); return "OK"; } else { // This is not our event, pass it up the stack return super.processControlEvent(Command, Init, Parameter); } } /** * registerClientManager registers this class as a client of the ECI listener * and publishes the commands that the plug in understands. The listener is * responsible for delivering only these commands to the plug in. * * @throws OpenRate.exception.InitializationException */ @Override public void registerClientManager() throws InitializationException { // Set the client reference and the base services first super.registerClientManager(); //Register services for this Client ClientManager.getClientManager().registerClientService(getSymbolicName(), SERVICE_CASSANDRA_IP_ADDR, ClientManager.PARAM_MANDATORY); ClientManager.getClientManager().registerClientService(getSymbolicName(), SERVICE_CASSANDRA_PORT, ClientManager.PARAM_MANDATORY); ClientManager.getClientManager().registerClientService(getSymbolicName(), SERVICE_CASSANDRA_USER_NAME, ClientManager.PARAM_MANDATORY); ClientManager.getClientManager().registerClientService(getSymbolicName(), SERVICE_CASSANDRA_PASSWORD, ClientManager.PARAM_MANDATORY); } // ----------------------------------------------------------------------------- // --------------- Start of transactional layer functions ---------------------- // ----------------------------------------------------------------------------- /** * When a transaction is started, the transactional layer calls this method to * see if we have any reason to stop the transaction being started, and to do * any preparation work that may be necessary before we start. * * @param transactionNumber The transaction to start * @return */ @Override public int startTransaction(int transactionNumber) { // We do not have any reason to inhibit the transaction start, so return // the OK flag return 0; } /** * Perform any processing that needs to be done when we are flushing the * transaction; * * @param transactionNumber The transaction to flush * @return */ @Override public int flushTransaction(int transactionNumber) { // close the input stream closeStream(transactionNumber); return 0; } /** * Perform any processing that needs to be done when we are committing the * transaction; * * @param transactionNumber The transaction to commit */ @Override public void commitTransaction(int transactionNumber) { // Nothing } /** * Perform any processing that needs to be done when we are rolling back the * transaction; * * @param transactionNumber The transaction to rollback */ @Override public void rollbackTransaction(int transactionNumber) { // Nothing } /** * Close Transaction is the trigger to clean up transaction related * information such as variables, status etc. * * Close down the statements we opened. Because the commit and rollback * statements are optional, we check if they have been defined before we ry to * close them. * * @param transactionNumber The transaction we are working on */ @Override public void closeTransaction(int transactionNumber) { // Nothing } // ----------------------------------------------------------------------------- // --------------- Start of custom initialisation functions --------------------- // ----------------------------------------------------------------------------- /** * The initValidQueueName gets the name of the valid output queue. * * @return The query string * @throws OpenRate.exception.InitializationException */ public String initCassandraIPAddress() throws InitializationException { String configHelper; // Get the init statement from the properties configHelper = PropertyUtils.getPropertyUtils().getBatchOutputAdapterPropertyValueDef(getPipeName(), getSymbolicName(), SERVICE_CASSANDRA_IP_ADDR, "None"); if ((configHelper == null) || configHelper.equalsIgnoreCase("None")) { String Message = "Output <" + getSymbolicName() + "> - config parameter <" + SERVICE_CASSANDRA_IP_ADDR + "> not found"; getPipeLog().error(Message); throw new InitializationException(Message, getSymbolicName()); } return configHelper; } /** * The initValidQueueName gets the name of the valid output queue. * * @return The query string * @throws OpenRate.exception.InitializationException */ public String initCassandraPort() throws InitializationException { String configHelper; // Get the init statement from the properties configHelper = PropertyUtils.getPropertyUtils().getBatchOutputAdapterPropertyValueDef(getPipeName(), getSymbolicName(), SERVICE_CASSANDRA_PORT, "None"); if ((configHelper == null) || configHelper.equalsIgnoreCase("None")) { message = "Output <" + getSymbolicName() + "> - config parameter <" + SERVICE_CASSANDRA_PORT + "> not found"; getPipeLog().error(message); throw new InitializationException(message, getSymbolicName()); } // check it is numeric try { cassandraPort = Integer.parseInt(configHelper); } catch (NumberFormatException ex) { message = "Output <" + getSymbolicName() + "> - config parameter <" + SERVICE_CASSANDRA_PORT + "> not numeric"; getPipeLog().error(message); throw new InitializationException(message, getSymbolicName()); } return configHelper; } /** * The initSMSCUserName gets the user name for logging into the SMSC with. * * @return The query string * @throws OpenRate.exception.InitializationException */ public String initCassandraUserName() throws InitializationException { String configHelper; // Get the init statement from the properties configHelper = PropertyUtils.getPropertyUtils().getBatchOutputAdapterPropertyValueDef(getPipeName(), getSymbolicName(), SERVICE_CASSANDRA_USER_NAME, "None"); if ((configHelper == null) || configHelper.equalsIgnoreCase("None")) { String Message = "Output <" + getSymbolicName() + "> - config parameter <" + SERVICE_CASSANDRA_USER_NAME + "> not found"; getPipeLog().error(Message); throw new InitializationException(Message, getSymbolicName()); } return configHelper; } /** * The initSMSCPassword gets the password for logging into the SMSC with. * * @return The query string * @throws OpenRate.exception.InitializationException */ public String initCassandraPassword() throws InitializationException { String configHelper; // Get the init statement from the properties configHelper = PropertyUtils.getPropertyUtils().getBatchOutputAdapterPropertyValueDef(getPipeName(), getSymbolicName(), SERVICE_CASSANDRA_PASSWORD, "None"); if ((configHelper == null) || configHelper.equalsIgnoreCase("None")) { String Message = "Output <" + getSymbolicName() + "> - config parameter <" + SERVICE_CASSANDRA_USER_NAME + "> not found"; getPipeLog().error(Message); throw new InitializationException(Message, getSymbolicName()); } return configHelper; } /** * Tries to connect to Cassandra. * * @throws OpenRate.exception.InitializationException */ public void initCassandraConnection() throws InitializationException { tr = new TFramedTransport(new TSocket(cassandraIPAddr, cassandraPort)); TProtocol proto = new TBinaryProtocol(tr); client = new Cassandra.Client(proto); try { tr.open(); } catch (TTransportException ex) { message = "Transport exception opening Cassandra transport"; throw new InitializationException(message, ex, getSymbolicName()); } } /** * Tries to connect to Cassandra. * * @throws OpenRate.exception.ProcessingException */ public void openCassandraConnection() throws ProcessingException { tr = new TFramedTransport(new TSocket(cassandraIPAddr, 9160)); TProtocol proto = new TBinaryProtocol(tr); client = new Cassandra.Client(proto); try { tr.open(); } catch (TTransportException ex) { message = "Transport exception opening Cassandra transport"; throw new ProcessingException(message, ex, getSymbolicName()); } } /** * Tries to connect to Cassandra. */ public void closeCassandraConnection() { tr.close(); } /** * @return the client */ public Cassandra.Client getClient() { return client; } /** * @param client the client to set */ public void setClient(Cassandra.Client client) { this.client = client; } }