//$Header: /cvsroot-fuse/mec-as2/39/mendelson/comm/as2/send/DirPollManager.java,v 1.1 2012/04/18 14:10:35 heller Exp $ package de.mendelson.comm.as2.send; import de.mendelson.comm.as2.clientserver.message.RefreshClientMessageOverviewList; import de.mendelson.util.security.cert.CertificateManager; import de.mendelson.comm.as2.message.AS2Message; import de.mendelson.comm.as2.message.store.MessageStoreHandler; import de.mendelson.comm.as2.notification.Notification; import de.mendelson.comm.as2.partner.Partner; import de.mendelson.comm.as2.partner.PartnerAccessDB; import de.mendelson.comm.as2.preferences.PreferencesAS2; import de.mendelson.comm.as2.sendorder.SendOrderSender; import de.mendelson.comm.as2.server.AS2Server; import de.mendelson.util.FileFilterRegexpMatch; import de.mendelson.util.MecResourceBundle; import de.mendelson.util.clientserver.ClientServer; import java.io.File; import java.sql.Connection; import java.util.ArrayList; import java.util.Arrays; import java.util.Hashtable; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.MissingResourceException; import java.util.ResourceBundle; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; 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. */ /** * Manager that polls the outbox directories of the partners, creates messages * and sends them * * @author S.Heller * @version $Revision: 1.1 $ */ public class DirPollManager { private Logger logger = Logger.getLogger(AS2Server.SERVER_LOGGER_NAME); private PreferencesAS2 preferences = new PreferencesAS2(); private CertificateManager certificateManager; /** * Stores all poll threads key: partner DB id, value: pollThread */ private Map<String, DirPollThread> mapPollThread = new Hashtable<String, DirPollThread>(); /** * Localize the GUI */ private MecResourceBundle rb = null; //DB connection private Connection configConnection; private Connection runtimeConnection; private ClientServer clientserver; public DirPollManager(CertificateManager certificateManager, Connection configConnection, Connection runtimeConnection, ClientServer clientserver) { this.configConnection = configConnection; this.runtimeConnection = runtimeConnection; this.clientserver = clientserver; //Load default resourcebundle try { this.rb = (MecResourceBundle) ResourceBundle.getBundle( ResourceBundleDirPollManager.class.getName()); } //load up resourcebundle catch (MissingResourceException e) { throw new RuntimeException("Oops..resource bundle " + e.getClassName() + " not found."); } this.certificateManager = certificateManager; this.logger.info(this.rb.getResourceString("manager.started")); } /** * Indicates that the partner configuration has been changed: This should * stop now unued tasks and start other */ public void partnerConfigurationChanged() { Partner[] partner = null; Partner[] localStations = null; PartnerAccessDB access = new PartnerAccessDB(this.configConnection, this.runtimeConnection); partner = access.getPartner(); localStations = access.getLocalStations(); if (partner == null) { this.logger.severe("partnerConfigurationChanged: Unable to load partner"); return; } for (Partner sender : localStations) { for (Partner receiver : partner) { String id = sender.getDBId() + "_" + receiver.getDBId(); //add partner task if it does not exist so far if (!this.mapPollThread.containsKey(id) && !receiver.isLocalStation()) { this.addPartnerPollThread(sender, receiver); } else if (this.mapPollThread.containsKey(id)) { DirPollThread thread = (DirPollThread) this.mapPollThread.get(id); if (!receiver.isLocalStation()) { //task exists: update its information thread.setRelationShip(sender, receiver); } else { //its a local station now: stop the task and remove it thread.requestStop(); this.mapPollThread.remove(id); } } } } //still running task that is not in the configuration any more: stop and remove List<String> idList = new ArrayList<String>(); Iterator iterator = this.mapPollThread.keySet().iterator(); while (iterator.hasNext()) { idList.add((String) iterator.next()); } for (String id : idList) { boolean idFound = false; for (Partner sender : localStations) { for (Partner receiver : partner) { String relationShipId = sender.getDBId() + "_" + receiver.getDBId(); if (id.equals(relationShipId)) { idFound = true; } } } //old still running taks, has been deleted in the config: stop and remove if (!idFound) { DirPollThread thread = this.mapPollThread.get(id); thread.requestStop(); this.mapPollThread.remove(id); } } } /** * Adds a new partner to the poll thread list * */ private void addPartnerPollThread(Partner localStation, Partner partner) { DirPollThread thread = new DirPollThread(this.configConnection, this.runtimeConnection); thread.setRelationShip(localStation, partner); this.mapPollThread.put(localStation.getDBId() + "_" + partner.getDBId(), thread); Executors.newSingleThreadExecutor().submit(thread); String pollIgnoreList = partner.getPollIgnoreListAsString(); if (pollIgnoreList == null) { pollIgnoreList = "--"; } logger.info(rb.getResourceString("poll.started", new Object[]{ localStation.getName(), partner.getName(), pollIgnoreList, partner.getPollInterval() })); } /** * Worker class that listens on the queue and performs a http send if a send * order has been found */ public class DirPollThread implements Runnable { /** * Polls all 10s by default */ private long pollInterval = TimeUnit.SECONDS.toMillis(10); private boolean stopRequested = false; private Partner receiver = null; private Partner sender = null; private Connection configConnection; private Connection runtimeConnection; public DirPollThread(Connection configConnection, Connection runtimeConnection) { this.configConnection = configConnection; this.runtimeConnection = runtimeConnection; } /** * Asks the thread to stiop */ public void requestStop() { logger.info(rb.getResourceString("poll.stopped", new Object[]{this.sender.getName(), this.receiver.getName()})); this.stopRequested = true; } /** * Extracts the right directory to poll for the passed partner */ public synchronized void setRelationShip(Partner newSender, Partner newReceiver) { //partner renamed, this results in a new poll directory if ((this.receiver != null && this.sender != null)) { if (!this.receiver.getName().equals(newReceiver.getName()) || !this.sender.getName().equals(newSender.getName())) { logger.info(rb.getResourceString("poll.stopped", new Object[]{ this.sender.getName(), this.receiver.getName() })); String pollIgnoreList = newReceiver.getPollIgnoreListAsString(); if (pollIgnoreList == null) { pollIgnoreList = "--"; } logger.info(rb.getResourceString("poll.started", new Object[]{ newSender.getName(), newReceiver.getName(), pollIgnoreList, newReceiver.getPollInterval() })); } } this.receiver = newReceiver; this.sender = newSender; this.pollInterval = newReceiver.getPollInterval() * TimeUnit.SECONDS.toMillis(1); } /** * Runs this thread */ @Override public void run() { //allow to process 3 files threaded ExecutorService fixedTheadExecutor = Executors.newFixedThreadPool(3); while (!stopRequested) { try { Thread.sleep(this.pollInterval); } catch (InterruptedException e) { //nop } StringBuilder outboxDirName = new StringBuilder(); outboxDirName.append(new File(preferences.get(PreferencesAS2.DIR_MSG)).getAbsolutePath()); outboxDirName.append(File.separator); outboxDirName.append(MessageStoreHandler.convertToValidFilename(this.receiver.getName())); outboxDirName.append(File.separator); outboxDirName.append("outbox"); outboxDirName.append(File.separator); outboxDirName.append(MessageStoreHandler.convertToValidFilename(this.sender.getName())); outboxDirName.append(File.separator); File outboxDir = new File(outboxDirName.toString()); if (!outboxDir.exists()) { outboxDir.mkdirs(); } FileFilterRegexpMatch fileFilter = new FileFilterRegexpMatch(); if (this.receiver.getPollIgnoreList() != null) { for (String ignoreEntry : this.receiver.getPollIgnoreList()) { fileFilter.addNonMatchingPattern(ignoreEntry); } } File[] files = outboxDir.listFiles(fileFilter); Arrays.sort(files, new ComparatorFiledateOldestFirst()); List<Callable<Boolean>> tasks = new ArrayList<Callable<Boolean>>(); int fileCounter = 0; for (File file : files) { if (file.isDirectory()) { continue; } if (!file.canWrite()) { logger.warning(rb.getResourceString("warning.ro", file.getAbsolutePath())); continue; } if (!this.renameIsPossible(file)) { logger.warning(rb.getResourceString("warning.notcomplete", file.getAbsolutePath())); continue; } final File finalFile = file; Callable<Boolean> singleTask = new Callable<Boolean>() { @Override public Boolean call() { processFile(finalFile); return (Boolean.TRUE); } }; tasks.add(singleTask); fileCounter++; //take a defined max number of files per poll process only if (fileCounter == this.receiver.getMaxPollFiles()) { break; } } //wait for all threads to be finished try { fixedTheadExecutor.invokeAll(tasks); } catch (InterruptedException e) { //nop } } } /** * Checks if the passed file could be renamed. If this is not possible, * the file is still used as stream target and should not be touched * (works actually only on windows but does not lead to problems for * other OS) * * @param file * @return */ private boolean renameIsPossible(File file) { File newFile = new File(file.getAbsolutePath() + "x"); boolean renamePossible = file.renameTo(newFile); boolean renameBackPossible = newFile.renameTo(file); return (renamePossible && renameBackPossible); } /** * Processes a single, found file */ private void processFile(File file) { try { logger.fine(rb.getResourceString("processing.file", new Object[]{ file.getName(), this.sender.getName(), this.receiver.getName() })); SendOrderSender orderSender = new SendOrderSender(this.configConnection, this.runtimeConnection); AS2Message message = orderSender.send(certificateManager, this.sender, this.receiver, file); clientserver.broadcastToClients(new RefreshClientMessageOverviewList()); boolean deleted = file.delete(); if (deleted) { logger.log(Level.INFO, rb.getResourceString("messagefile.deleted", new Object[]{ message.getAS2Info().getMessageId(), file.getName(),}), message.getAS2Info()); } } catch (Throwable e) { String message = rb.getResourceString("processing.file.error", new Object[]{file.getName(), this.sender, this.receiver, e.getMessage()}); logger.severe(message); Exception exception = new Exception(message, e); Notification.systemFailure(this.configConnection, this.runtimeConnection, exception); } } } }