package org.marketcetera.modules.remote.receiver; import java.util.EnumSet; import org.apache.log4j.Level; import org.apache.log4j.LogManager; import org.apache.log4j.Logger; import org.marketcetera.event.LogEvent; import org.marketcetera.event.LogEventLevel; import org.marketcetera.module.DataFlowID; import org.marketcetera.module.DataReceiver; import org.marketcetera.module.Module; import org.marketcetera.module.ModuleException; import org.marketcetera.module.ModuleURN; import org.marketcetera.module.ReceiveDataException; import org.marketcetera.util.log.I18NBoundMessage1P; import org.marketcetera.util.log.I18NMessage0P; import org.marketcetera.util.misc.ClassVersion; import org.marketcetera.util.spring.SpringUtils; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.context.support.StaticApplicationContext; import org.springframework.jms.JmsException; import org.springframework.jms.core.JmsTemplate; /* $License$ */ /** * A module that can receive any kind of data and emit it on a * remotely accessible messaging topic so that the data can be received * by {@link org.marketcetera.modules.remote.receiver remote recipients}. * <br/> * <table> * <tr><th>Capabilities</th><td>Data Receiver</td></tr> * <tr><th>Stops data flows</th><td>No</td></tr> * <tr><th>Emits data flow errors</th><td>Yes, when it's unable to * serialize or send the object over JMS</td></tr> * <tr><th>Start Operation</th><td>Starts the embedded JMS broker and * connects to it.</td></tr> * <tr><th>Stop Operation</th><td>Stops the embedded broker.</td></tr> * <tr><th>Management Interface</th><td>{@link ReceiverModuleMXBean}</td></tr> * <tr><th>Factory</th><td>{@link ReceiverFactory}</td></tr> * </table> * <br/> * <b>Remoting Mechanism</b> * <p> * The module embeds a JMS message broker to transmit the messages to its * clients. The message broker is configured with a single topic on which * all the messages are broadcast. * The broker is started when the module is started and stopped when * the module is stopped. * <br/> * <b>Received Object Handling</b> * <p> * The module will transmit all the received objects serialized as * {@link javax.jms.ObjectMessage}. If the received object is not * serializable, the module generates an error receiving that object * which is handled and logged by the module framework. The module * ignores null objects. * <br/> * <b>Authentication</b> * <p> * The remote clients of this module need to authenticate themselves to * the message broker in order to connect. This module depends on the * {@link org.marketcetera.client client} module for authentication. * The client module needs to be connected to the server for the * authentication to succeed and the credentials supplied must be the same * as used by the client to connect to the server. * <p> * Do note that this module programmatically sets up the JAAS * {@link javax.security.auth.login.Configuration} to make it easy to use. * However, such a setup may conflict with other JAAS clients within the same * JVM. If that happens, you can set the variable * {@link #setSkipJAASConfiguration(boolean)} to false and make sure that * you setup the JAAS configuration yourself so that this module can work. * Here's the JAAS configuration needed for authentication to work. * <pre> * remoting-amq-domain { * org.marketcetera.modulews.remote.receiver.ClientLoginModule required; * }; * </pre> * * @author anshul@marketcetera.com * @version $Id: ReceiverModule.java 16154 2012-07-14 16:34:05Z colin $ * @since 1.5.0 */ @ClassVersion("$Id: ReceiverModule.java 16154 2012-07-14 16:34:05Z colin $") public class ReceiverModule extends Module implements DataReceiver, ReceiverModuleMXBean { /** * Creates an instance. * * @param inURN the module URN. */ ReceiverModule(ModuleURN inURN) { super(inURN, true); //Set the log level to the default value setLogLevel(LogEventLevel.WARN); } @Override protected void preStart() throws ModuleException { //Check if the broker URL is supplied String url = getURL(); if(url == null || url.trim().isEmpty()) { //If no URL specified do not perform remoting. Messages.NO_URL_SPECIFIED_LOG.info(this); return; } if (!mSkipJAASConfiguration) { //Setup the JAAS configuration JaasConfiguration.setup(); mDoneJaasConfiguration = true; } //Create spring contexts to initialize the broker and messaging topic try { StaticApplicationContext parent = new StaticApplicationContext(); SpringUtils.addStringBean(parent, "brokerURI", url); //$NON-NLS-1$ SpringUtils.addStringBean(parent, "userName", //$NON-NLS-1$ ClientLoginHelper.getUserName()); SpringUtils.addStringBean(parent, "password", //$NON-NLS-1$ ClientLoginHelper.getPassword()); parent.refresh(); mContext = new ClassPathXmlApplicationContext(new String[]{ "remoting_server.xml"}, parent); //$NON-NLS-1$ mContext.start(); mSender = (JmsTemplate) mContext.getBean("sender", //$NON-NLS-1$ JmsTemplate.class); Messages.RECIEVER_REMOTING_CONFIGURED.info(this, url); } catch(Exception e) { throw new ModuleException(e, Messages.ERROR_STARTING_MODULE); } } @Override protected void preStop() throws ModuleException { //Stop & destroy the broker. if (mContext != null) { try { mContext.close(); mContext = null; } catch (Exception e) { Messages.ERROR_STOPPING_MODULE_LOG.warn(this, e); } } mSender = null; } @Override public void receiveData(DataFlowID inFlowID, Object inData) throws ReceiveDataException { if (mSender != null && inData != null) { if(inData instanceof LogEvent) { //Skip the log event if its level is below the current log level if(((LogEvent)inData).getLevel().ordinal() < mLogLevel.ordinal()) { return; } } try { mSender.convertAndSend(inData); } catch (JmsException e) { throw new ReceiveDataException(e, new I18NBoundMessage1P(Messages.ERROR_WHEN_TRANSMITTING, String.valueOf(inData))); } } } @Override public String getURL() { return mURL; } @Override public void setURL(String inURL) { failIfStarted(Messages.ILLEGAL_STATE_SET_URL); mURL = inURL; } @Override public LogEventLevel getLogLevel() { return mLogLevel; } @Override public void setLogLevel(LogEventLevel inLevel) { if(inLevel == null) { throw new IllegalArgumentException( Messages.NULL_LEVEL_VALUE.getText( EnumSet.allOf(LogEventLevel.class))); } getLogger().setLevel(Level.toLevel(inLevel.name(), null)); mLogLevel = inLevel; } @Override public boolean isSkipJAASConfiguration() { return mSkipJAASConfiguration; } @Override public void setSkipJAASConfiguration(boolean inSkipJAASConfiguration) { if(mDoneJaasConfiguration) { throw new IllegalArgumentException( Messages.ILLEGAL_STATE_SET_SKIP_JAAS.getText()); } mSkipJAASConfiguration = inSkipJAASConfiguration; } /** * Gets the log4j logger that corresponds to the system user messages * logger category. * * @return the system user messages logger. */ private Logger getLogger() { return LogManager.getLogger(org.marketcetera.core.Messages.USER_MSG_CATEGORY); } /** * Verifies if the module is not started. * * @param inMessage the message to use when the module is started. * * @throws IllegalStateException if the module is started. */ private void failIfStarted(I18NMessage0P inMessage) { if(getState().isStarted()) { throw new IllegalStateException(inMessage.getText()); } } private volatile String mURL; private volatile ClassPathXmlApplicationContext mContext; private volatile JmsTemplate mSender; private volatile LogEventLevel mLogLevel; private volatile boolean mSkipJAASConfiguration = false; private volatile boolean mDoneJaasConfiguration; }