//$Header: /cvsroot-fuse/mec-as2/39/mendelson/comm/as2/sendorder/SendOrderReceiver.java,v 1.1 2012/04/18 14:10:38 heller Exp $ package de.mendelson.comm.as2.sendorder; import de.mendelson.comm.as2.clientserver.message.RefreshClientMessageOverviewList; import de.mendelson.comm.as2.message.AS2MDNInfo; import de.mendelson.comm.as2.message.AS2Message; import de.mendelson.comm.as2.message.AS2MessageInfo; import de.mendelson.comm.as2.message.ExecuteShellCommand; import de.mendelson.comm.as2.message.MessageAccessDB; import de.mendelson.comm.as2.message.store.MessageStoreHandler; import de.mendelson.comm.as2.preferences.PreferencesAS2; import de.mendelson.comm.as2.send.HttpConnectionParameter; import de.mendelson.comm.as2.send.MessageHttpUploader; import de.mendelson.comm.as2.send.NoConnectionException; import de.mendelson.comm.as2.server.AS2Server; import de.mendelson.util.MecResourceBundle; import de.mendelson.util.clientserver.ClientServer; import java.sql.Connection; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.List; import java.util.MissingResourceException; import java.util.Properties; import java.util.ResourceBundle; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; /* * Copyright (C) mendelson-e-commerce GmbH Berlin Germany * * This software is subject to the license agreement set forth in the license. * Please read and agree to all terms before using this software. Other product * and brand names are trademarks of their respective owners. */ /** * Receiver class that enqueues send orders * * @author S.Heller * @version $Revision: 1.1 $ */ public class SendOrderReceiver implements Runnable { private Logger logger = Logger.getLogger(AS2Server.SERVER_LOGGER_NAME); private MecResourceBundle rb; private Connection configConnection; private Connection runtimeConnection; private SendOrderAccessDB sendOrderAccess; private final int MAX_RETRY_COUNT = 10; private final long RETRY_WAIT_TIME = TimeUnit.SECONDS.toMillis(30); /** * Thread will stop if this is no longer set */ private boolean runPermission = true; /** * Needed for refresh */ private ClientServer clientserver = null; /** * Server preferences */ private PreferencesAS2 preferences = new PreferencesAS2(); /** * Handles messages storage */ private MessageStoreHandler messageStoreHandler; private MessageAccessDB messageAccess; public SendOrderReceiver(Connection configConnection, Connection runtimeConnection, ClientServer clientserver) { //Load default resourcebundle try { this.rb = (MecResourceBundle) ResourceBundle.getBundle( ResourceBundleSendOrderReceiver.class.getName()); } //load up resourcebundle catch (MissingResourceException e) { throw new RuntimeException("Oops..resource bundle " + e.getClassName() + " not found."); } this.configConnection = configConnection; this.runtimeConnection = runtimeConnection; this.sendOrderAccess = new SendOrderAccessDB(this.configConnection, this.runtimeConnection); this.messageAccess = new MessageAccessDB(this.configConnection, this.runtimeConnection); this.messageStoreHandler = new MessageStoreHandler(this.configConnection, this.runtimeConnection); this.clientserver = clientserver; } /** * Stops the listener */ public void stopReceiver() { this.runPermission = false; } @Override public void run() { //Max number of outbound connections. All other connection attempts are scheduled in a queue ExecutorService fixedTheadExecutor = Executors.newFixedThreadPool(5); //listen until stop is requested while (this.runPermission) { //listen on the queue for new awaiting send orders List<SendOrder> waitingOrders = this.sendOrderAccess.getNext(5); for (SendOrder order : waitingOrders) { final SendOrder finalOrder = order; fixedTheadExecutor.execute(new Runnable() { @Override public void run() { processOrder(finalOrder); } }); } //wait some time before looking for new messages try { Thread.sleep(TimeUnit.MILLISECONDS.toMillis(250)); } catch (InterruptedException e) { //NOP } } } private void processOrder(SendOrder order) { try { boolean processingAllowed = true; //before performing the send there has to be checked if the send process is still valid. The orders //are queued, between scheduling and processing the orders the transmission time could expire //or the user could cancel it if (order.getMessage().isMDN()) { //if the MDN state is on failure then the related transmission is on failure state, too - //checking this makes no sense here AS2MDNInfo mdnInfo = (AS2MDNInfo) order.getMessage().getAS2Info(); AS2MessageInfo relatedMessageInfo = messageAccess.getLastMessageEntry(mdnInfo.getRelatedMessageId()); if (relatedMessageInfo == null) { processingAllowed = false; } } else { AS2MessageInfo messageInfo = (AS2MessageInfo) order.getMessage().getAS2Info(); if (messageInfo.getMessageType() == AS2Message.MESSAGETYPE_AS2) { //update the message info from the database messageInfo = messageAccess.getLastMessageEntry(messageInfo.getMessageId()); if (messageInfo == null || messageInfo.getState() == AS2Message.STATE_STOPPED) { processingAllowed = false; } } else if (messageInfo.getMessageType() == AS2Message.MESSAGETYPE_CEM) { processingAllowed = true; } } if (processingAllowed) { MessageHttpUploader messageUploader = new MessageHttpUploader(); messageUploader.setLogger(this.logger); messageUploader.setAbstractServer(this.clientserver); messageUploader.setDBConnection(this.configConnection, this.runtimeConnection); //configure the connection parameters HttpConnectionParameter connectionParameter = new HttpConnectionParameter(); connectionParameter.setConnectionTimeoutMillis(this.preferences.getInt(PreferencesAS2.HTTP_SEND_TIMEOUT)); connectionParameter.setHttpProtocolVersion(order.getReceiver().getHttpProtocolVersion()); connectionParameter.setProxy(messageUploader.createProxyObjectFromPreferences()); connectionParameter.setUseExpectContinue(true); Properties requestHeader = messageUploader.upload(connectionParameter, order.getMessage(), order.getSender(), order.getReceiver()); //set error or finish state, remember that this send order could be //also an MDN if async MDN is requested if (order.getMessage().isMDN()) { AS2MDNInfo mdnInfo = (AS2MDNInfo) order.getMessage().getAS2Info(); if (mdnInfo.getState() == AS2Message.STATE_FINISHED) { AS2MessageInfo relatedMessageInfo = messageAccess.getLastMessageEntry(mdnInfo.getRelatedMessageId()); this.messageStoreHandler.movePayloadToInbox(relatedMessageInfo.getMessageType(), mdnInfo.getRelatedMessageId(), order.getSender(), order.getReceiver()); //execute a shell command after send SUCCESS ExecuteShellCommand executeCommand = new ExecuteShellCommand(this.configConnection, this.runtimeConnection); //switch sender and receiver because its the MDN sender that is requested, not the message sender executeCommand.executeShellCommandOnReceipt(order.getReceiver(), order.getSender(), relatedMessageInfo); } //set the transaction state to the MDN state messageAccess.setMessageState(mdnInfo.getRelatedMessageId(), mdnInfo.getState()); } else { //its a AS2 message that has been sent AS2MessageInfo messageInfo = (AS2MessageInfo) order.getMessage().getAS2Info(); messageAccess.updateFilenames(messageInfo); messageAccess.setMessageSendDate(messageInfo); if (!messageInfo.requestsSyncMDN()) { long endTime = System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(preferences.getInt(PreferencesAS2.ASYNC_MDN_TIMEOUT)); DateFormat format = SimpleDateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.MEDIUM); logger.log(Level.INFO, rb.getResourceString("async.mdn.wait", new Object[]{ order.getMessage().getAS2Info().getMessageId(), format.format(endTime) }), messageInfo); } } } //even if a processing was not possible: delete the sendorder this.sendOrderAccess.delete(order.getDbId()); this.clientserver.broadcastToClients(new RefreshClientMessageOverviewList()); } catch (NoConnectionException e) { int retryCount = order.incRetryCount(); //to many retries: cancel the transaction if (retryCount > this.MAX_RETRY_COUNT) { logger.log(Level.SEVERE, e.getMessage(), order.getMessage().getAS2Info()); logger.log(Level.SEVERE, rb.getResourceString("max.retry.reached", new Object[]{ order.getMessage().getAS2Info().getMessageId(),}), order.getMessage().getAS2Info()); this.processUploadError(order); } else { logger.log(Level.WARNING, e.getMessage(), order.getMessage().getAS2Info()); logger.log(Level.WARNING, rb.getResourceString("retry", new Object[]{ order.getMessage().getAS2Info().getMessageId(), String.valueOf((this.RETRY_WAIT_TIME / 1000)), String.valueOf(retryCount), String.valueOf(this.MAX_RETRY_COUNT) }), order.getMessage().getAS2Info()); this.sendOrderToRetry(order); } } catch (Exception e) { e.printStackTrace(); logger.log(Level.SEVERE, e.getMessage(), order.getMessage().getAS2Info()); this.processUploadError(order); } } /** * Update the order in the queue - with a new nextexecution time */ private void sendOrderToRetry(SendOrder order) { SendOrderSender sender = null; sender = new SendOrderSender(this.configConnection, this.runtimeConnection); sender.resend(order, System.currentTimeMillis() + this.RETRY_WAIT_TIME); } /** * The upload process of the data failed. Set the message state, execute the * command, .. */ private void processUploadError(SendOrder order) { try { //stores this.messageStoreHandler.storeSentErrorMessage( order.getMessage(), order.getSender(), order.getReceiver()); if (!order.getMessage().isMDN()) { //message upload failure messageAccess.setMessageState(order.getMessage().getAS2Info().getMessageId(), AS2Message.STATE_STOPPED); //its important to set the state in the message info, too. An event exec is not performed //for pending messages order.getMessage().getAS2Info().setState(AS2Message.STATE_STOPPED); messageAccess.updateFilenames((AS2MessageInfo) order.getMessage().getAS2Info()); //execute a shell command after send ERROR if this is configured (sync MDN, async MDN) ExecuteShellCommand executor = new ExecuteShellCommand(this.configConnection, this.runtimeConnection); executor.executeShellCommandOnSend((AS2MessageInfo) order.getMessage().getAS2Info(), null); //write status file this.messageStoreHandler.writeOutboundStatusFile((AS2MessageInfo) order.getMessage().getAS2Info()); } else { //MDN send failure, e.g. wrong URL for async MDN in message messageAccess.setMessageState(((AS2MDNInfo) order.getMessage().getAS2Info()).getRelatedMessageId(), AS2Message.STATE_STOPPED); } this.sendOrderAccess.delete(order.getDbId()); this.clientserver.broadcastToClients(new RefreshClientMessageOverviewList()); } catch (Exception ee) { ee.printStackTrace(); logger.log(Level.SEVERE, "SendOrderReceiver.processUploadError(): " + ee.getMessage(), order.getMessage().getAS2Info()); this.messageAccess.setMessageState(order.getMessage().getAS2Info().getMessageId(), AS2Message.STATE_STOPPED); } } }