package cu.ftpd.user.userbases.changetracking;
import cu.ftpd.logging.Logging;
import cu.ftpd.user.userbases.local.LocalUserbase;
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
import java.io.File;
import java.net.URI;
/**
* @author markus@jevring.net
*/
public class ChangeReceiver {
private final ChangeApplicator changeApplicator;
private final ChangeParser changeParser = new ChangeParser();
/**
* Indicates who this connection is with. This is used when filtering who to propagate the changes to.
* Obviously, we don't want to propagate changes back to the sender.
*/
private final String peer;
private final String myName;
private final URI broker;
private final ChangeTracker changeTracker;
private Connection connection;
private Session session;
private MessageConsumer consumer;
// todo: who runs allotment when we propagate changes?
// also, what do we do with allotment changes?
public ChangeReceiver(URI broker, String peer, final String myName, ChangeTracker changeTracker, ChangeApplicator changeApplicator) {
this.peer = peer;
this.myName = myName;
this.broker = broker;
this.changeTracker = changeTracker;
this.changeApplicator = changeApplicator;
}
public void start() throws JMSException {
// todo: introduce username/password for the connection and the broker and the connection.
// Here we can't use JNDI, as we'll be using the broker of the broker.
ConnectionFactory connectionFactory = new ActiveMQConnectionFactory(broker);
connection = connectionFactory.createConnection();
connection.setExceptionListener(new ExceptionListener() {
@Override
public void onException(JMSException exception) {
Logging.getErrorLog().reportException(myName + ": " + exception.getMessage(), exception);
}
});
session = connection.createSession(false, Session.CLIENT_ACKNOWLEDGE);
final Queue queue = session.createQueue("cu.mq.userbase." + myName);
consumer = session.createConsumer(queue);
consumer.setMessageListener(new MessageListener() {
@Override
public void onMessage(Message message) {
if (message instanceof TextMessage) {
String text = null;
try {
text = ((TextMessage) message).getText();
Change change = changeParser.parse(text, peer);
apply(change);
changeTracker.addChange(change); // propagate the change
// todo: use a ReentrantReadWriteLock for the userbase initialization.
// the normal updaters take read locks, but when someone needs to get a copy of the userbase,
// the sender can take a write lock to lock everybody out until it is done.
} catch (JMSException e) {
Logging.getErrorLog().reportException("Could not get body of userbase message", e);
} catch (RuntimeException e) {
// just in case something happens, we don't want to break everything
Logging.getErrorLog().reportException("Could not apply change: " + text, e);
}
try {
message.acknowledge(); // ideally we'd like some way of rolling back a change if we can't acknowledge because the host goes down
} catch (JMSException e) {
Logging.getErrorLog().reportException("Failed to acknowledge message regarding change. Possible userbase drift. Change was: " + text, e);
}
}
}
});
connection.start();
}
private void apply(Change change) {
System.out.println("Received change: " + change);
changeApplicator.apply(change);
}
public void stop() throws JMSException {
consumer.close();
session.close();
connection.stop();
}
// todo: make a standalone server that does only this... (like the existing RMI server)
public static void main(String[] args) {
try {
final AsynchronousMessageQueueChangeTracker changeTracker = new AsynchronousMessageQueueChangeTracker(URI.create("tcp://localhost:1800"), "master");
final LocalUserbase userbase = new LocalUserbase(new File("c:/users/captain/desktop/receiver-data"), false, changeTracker);
ChangeApplicator changeApplicator = new ChangeApplicator(userbase);
changeTracker.addPeer("cuftpd", URI.create("failover:tcp://localhost:1700"), changeApplicator);
} catch (Exception e) {
e.printStackTrace();
}
}
}