diff --git a/pom.xml b/pom.xml index 4958a27b..a26fe1e5 100644 --- a/pom.xml +++ b/pom.xml @@ -83,6 +83,12 @@ + + + src/main/resources + true + + org.codehaus.mojo @@ -139,6 +145,21 @@ maven-surefire-plugin 2.22.1 + + de.ntcomputer + executable-packer-maven-plugin + 1.0.1 + + org.kopi.ebics.client.EbicsClient + + + + + pack-executable-jar + + + + diff --git a/src/main/java/org/kopi/ebics/client/EbicsClient.java b/src/main/java/org/kopi/ebics/client/EbicsClient.java index 48eb0332..f5109905 100644 --- a/src/main/java/org/kopi/ebics/client/EbicsClient.java +++ b/src/main/java/org/kopi/ebics/client/EbicsClient.java @@ -23,9 +23,12 @@ import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; +import java.io.InputStream; import java.io.ObjectInputStream; import java.net.URL; import java.security.GeneralSecurityException; +import java.text.SimpleDateFormat; +import java.time.Instant; import java.util.Arrays; import java.util.Date; import java.util.HashMap; @@ -33,6 +36,8 @@ import java.util.Locale; import java.util.Map; import java.util.Properties; +import java.util.logging.Level; +import java.util.logging.Logger; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.CommandLineParser; @@ -45,6 +50,7 @@ import org.kopi.ebics.exception.NoDownloadDataAvailableException; import org.kopi.ebics.interfaces.Configuration; import org.kopi.ebics.interfaces.EbicsBank; +import org.kopi.ebics.interfaces.EbicsLogger; import org.kopi.ebics.interfaces.EbicsOrderType; import org.kopi.ebics.interfaces.EbicsUser; import org.kopi.ebics.interfaces.InitLetter; @@ -53,6 +59,7 @@ import org.kopi.ebics.io.IOUtils; import org.kopi.ebics.messages.Messages; import org.kopi.ebics.schema.h003.OrderAttributeType; +import org.kopi.ebics.session.CustomOrderType; import org.kopi.ebics.session.DefaultConfiguration; import org.kopi.ebics.session.EbicsSession; import org.kopi.ebics.session.OrderType; @@ -390,6 +397,8 @@ public void sendFile(File file, User user, Product product, EbicsOrderType order configuration.getTransferTraceDirectory(user)); try { + configuration.getLogger().info(messages.getString("upload.request.send", + orderType.getCode())); transferManager.sendFile(IOUtils.getFileContent(file), orderType, orderAttribute); } catch (IOException | EbicsException e) { configuration.getLogger() @@ -416,6 +425,8 @@ public void fetchFile(File file, User user, Product product, EbicsOrderType orde configuration.getTransferTraceDirectory(user)); try { + configuration.getLogger().info(messages.getString("download.request.send", + orderType.getCode())); transferManager.fetchFile(orderType, start, end, file); } catch (NoDownloadDataAvailableException e) { // don't log this exception as an error, caller can decide how to handle @@ -431,6 +442,35 @@ public void fetchFile(File file, EbicsOrderType orderType, Date start, Date end) fetchFile(file, defaultUser, defaultProduct, orderType, false, start, end); } + /** + * Downloads file for order type without throwing NoDataAvailableException. + * + * Downloads file for the order type and logs success or warns about no data. + * + * @param file output file + * @param user EBICS User + * @param product EBICS Product + * @param orderType EBICS Order Type + * @param start Optional Date Range Start Date + * @param end Optional Date Range End Date + * @return true if data was available, false if not + * @throws java.io.IOException + * @throws org.kopi.ebics.exception.EbicsException + */ + public boolean fetchFileIfAvailable(File file, User user, Product product, + EbicsOrderType orderType, Date start, Date end) throws IOException, + EbicsException { + + final EbicsLogger logger = configuration.getLogger(); + try { + fetchFile(file, user, product, orderType, false, start, end); + configuration.getLogger().info(messages.getString("download.file.success", orderType.getCode())); + return true; + } catch (NoDownloadDataAvailableException nodataException) { + configuration.getLogger().warn(messages.getString("download.file.nodata", orderType.getCode())); + return false; + } + } /** * Performs buffers save before quitting the client application. */ @@ -507,6 +547,7 @@ private User createUser(ConfigProperties properties, PasswordCallback pwdHandler private static CommandLine parseArguments(Options options, String[] args) throws ParseException { CommandLineParser parser = new DefaultParser(); options.addOption(null, "help", false, "Print this help text"); + options.addOption(null, "version", false, "Print the version information and exit"); CommandLine line = parser.parse(options, args); if (line.hasOption("help")) { HelpFormatter formatter = new HelpFormatter(); @@ -515,6 +556,20 @@ private static CommandLine parseArguments(Options options, String[] args) throws System.out.println(); System.exit(0); } + if (line.hasOption("version")) { + InputStream streamOrNull = EbicsClient.class.getClassLoader().getResourceAsStream( + "build.properties"); + if (streamOrNull != null) { + try { + Properties properties = new Properties(); + properties.load(streamOrNull); + System.out.println(properties.getProperty("build.name") + " " + + properties.getProperty("build.version")); + } catch (IOException ex) {} + + } + System.exit(0); + } return line; } @@ -611,11 +666,20 @@ public static void main(String[] args) throws Exception { options.addOption("o", "output", true, "output file"); options.addOption("i", "input", true, "input file"); + options.addOption("s", "start", true, "Start date (yyyy-MM-dd)"); + options.addOption("e", "end", true, "End date (yyyy-MM-dd)"); + + options.addOption("d", "download", true, "download custom order type"); + options.addOption("u", "upload", true, "upload custom order type"); + + options.addOption("c", "configuration-directory", true, "Configuration directory"); CommandLine cmd = parseArguments(options, args); - File defaultRootDir = new File(System.getProperty("user.home") + File.separator + "ebics" + String configurationDirectory = cmd.getOptionValue("configuration-directory", + System.getProperty("user.home") + File.separator + "ebics" + File.separator + "client"); + File defaultRootDir = new File(configurationDirectory); File ebicsClientProperties = new File(defaultRootDir, "ebics.txt"); EbicsClient client = createEbicsClient(defaultRootDir, ebicsClientProperties); @@ -642,25 +706,68 @@ public static void main(String[] args) throws Exception { String outputFileValue = cmd.getOptionValue("o"); String inputFileValue = cmd.getOptionValue("i"); - List fetchFileOrders = Arrays.asList(OrderType.STA, OrderType.VMK, - OrderType.C52, OrderType.C53, OrderType.C54, - OrderType.ZDF, OrderType.ZB6, OrderType.PTK, OrderType.HAC, OrderType.Z01); + // Process start and end dates. + // If the end date is specified, start date is required + // If the start date is specified, the end date defaults + // to the current date. + String start = cmd.getOptionValue("s"); + String end = cmd.getOptionValue("e"); + Date startDate = null; + Date endDate = null; + if (start != null) { + final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd"); + startDate = format.parse(start); + endDate = end != null + ? format.parse(end) + : Date.from(Instant.now()); + } else if (end != null) { + throw new EbicsException("Start date required if end date is given"); + } + + boolean allDataWasAvailable = true; - for (EbicsOrderType type : fetchFileOrders) { - if (hasOption(cmd, type)) { - client.fetchFile(getOutputFile(outputFileValue), client.defaultUser, - client.defaultProduct, type, false, null, null); - break; + if (cmd.hasOption("d")) + { + allDataWasAvailable &= client.fetchFileIfAvailable(getOutputFile(outputFileValue), + client.defaultUser, + client.defaultProduct, + new CustomOrderType(cmd.getOptionValue("d")), + startDate, endDate); + } + else + { + List fetchFileOrders = Arrays.asList(OrderType.STA, OrderType.VMK, + OrderType.C52, OrderType.C53, OrderType.C54, + OrderType.ZDF, OrderType.ZB6, OrderType.PTK, OrderType.HAC, OrderType.Z01); + + for (EbicsOrderType type : fetchFileOrders) { + if (hasOption(cmd, type)) { + allDataWasAvailable &= client.fetchFileIfAvailable(getOutputFile(outputFileValue), + client.defaultUser, + client.defaultProduct, + type, + startDate, endDate); + break; + } } } - List sendFileOrders = Arrays.asList(OrderType.XKD, OrderType.FUL, OrderType.XCT, - OrderType.XE2, OrderType.CCT); - for (EbicsOrderType type : sendFileOrders) { - if (hasOption(cmd, type)) { - client.sendFile(new File(inputFileValue), client.defaultUser, - client.defaultProduct, type); - break; + if (cmd.hasOption("u")) + { + client.sendFile(new File(inputFileValue), client.defaultUser, + client.defaultProduct, + new CustomOrderType(cmd.getOptionValue("u"))); + } + else + { + List sendFileOrders = Arrays.asList(OrderType.XKD, OrderType.FUL, OrderType.XCT, + OrderType.XE2, OrderType.CCT); + for (EbicsOrderType type : sendFileOrders) { + if (hasOption(cmd, type)) { + client.sendFile(new File(inputFileValue), client.defaultUser, + client.defaultProduct, type); + break; + } } } @@ -671,6 +778,10 @@ public static void main(String[] args) throws Exception { } } client.quit(); + if (!allDataWasAvailable) + { + System.exit(2); + } } private static File getOutputFile(String outputFileName) { diff --git a/src/main/java/org/kopi/ebics/session/CustomOrderType.java b/src/main/java/org/kopi/ebics/session/CustomOrderType.java new file mode 100644 index 00000000..ce00ba0f --- /dev/null +++ b/src/main/java/org/kopi/ebics/session/CustomOrderType.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2021, Digital Financial, LLC. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.kopi.ebics.session; + +import java.util.Objects; +import org.kopi.ebics.interfaces.EbicsOrderType; + +/** + * + * @author nsushkin + */ +public class CustomOrderType implements EbicsOrderType { + + private final String code; + + public CustomOrderType(String code) { + Objects.requireNonNull(code, "CustomOrderType code must not be null"); + this.code = code.toUpperCase(); + } + + /** + * Get the value of code + * + * @return the value of code + */ + @Override + public String getCode() { + return code; + } + +} diff --git a/src/main/java/org/kopi/ebics/session/DefaultConfiguration.java b/src/main/java/org/kopi/ebics/session/DefaultConfiguration.java index 7e66fbcf..e1debf13 100644 --- a/src/main/java/org/kopi/ebics/session/DefaultConfiguration.java +++ b/src/main/java/org/kopi/ebics/session/DefaultConfiguration.java @@ -41,6 +41,9 @@ /** * A simple client application configuration. * + * Loads client configuration from a property file, but allows properties + * to be overridden from the System properties. + * * @author hachani * */ @@ -60,13 +63,17 @@ public DefaultConfiguration(String rootDir, Properties properties) { } /** - * Returns the corresponding property of the given key + * Returns the corresponding property of the given key. + * + * First, attempts to load property from system properties. If none found, + * loads the property from the bundle. + * * @param key the property key * @return the property value. */ private String getString(String key) { try { - return bundle.getString(key); + return System.getProperty(key, bundle.getString(key)); } catch(MissingResourceException e) { return "!!" + key + "!!"; } @@ -94,7 +101,7 @@ public void init() { //Create users directory IOUtils.createDirectories(getUsersDirectory()); - logger.setLogFile(getLogDirectory() + File.separator + getLogFileName()); + logger.setLogFile(resolveFile(getLogDirectory(), getLogFileName())); ((DefaultEbicsLogger)logger).setFileLoggingEnabled(true); ((DefaultEbicsLogger)logger).setLevel(DefaultEbicsLogger.ALL_LEVEL); serializationManager.setSerializationDirectory(getSerializationDirectory()); @@ -102,6 +109,24 @@ public void init() { letterManager = new DefaultLetterManager(getLocale()); } + /** + * Returns absolute file name or prepends directory name. + * + *

If fileName starts with path separator, just returns the fileName. + * Otherwise, prepends directory name and a path separator. + * + * @param directory directory name + * @param file file name + * @return + */ + public static String resolveFile(String directory, String file) { + if (file != null && file.startsWith(File.separator)) { + return file; + } else { + return directory + File.separator + file; + } + } + @Override public Locale getLocale() { return Locale.FRANCE; @@ -109,7 +134,7 @@ public Locale getLocale() { @Override public String getLogDirectory() { - return rootDir + File.separator + getString("log.dir.name"); + return resolveFile(rootDir, getString("log.dir.name")); } @Override @@ -119,7 +144,7 @@ public String getLogFileName() { @Override public String getConfigurationFile() { - return rootDir + File.separator + getString("conf.file.name"); + return resolveFile(rootDir, getString("conf.file.name")); } @Override @@ -129,22 +154,22 @@ public String getProperty(String key) { @Override public String getKeystoreDirectory(EbicsUser user) { - return getUserDirectory(user) + File.separator + getString("keystore.dir.name"); + return resolveFile(getUserDirectory(user), getString("keystore.dir.name")); } @Override public String getTransferTraceDirectory(EbicsUser user) { - return getUserDirectory(user) + File.separator + getString("traces.dir.name"); + return resolveFile(getUserDirectory(user), getString("traces.dir.name")); } @Override public String getSerializationDirectory() { - return rootDir + File.separator + getString("serialization.dir.name"); + return resolveFile(rootDir, getString("serialization.dir.name")); } @Override public String getSSLTrustedStoreDirectory() { - return rootDir + File.separator + getString("ssltruststore.dir.name"); + return resolveFile(rootDir, getString("ssltruststore.dir.name")); } @Override @@ -154,12 +179,12 @@ public String getSSLKeyStoreDirectory() { @Override public String getSSLBankCertificates() { - return rootDir + File.separator + getString("sslbankcert.dir.name"); + return resolveFile(rootDir, getString("sslbankcert.dir.name")); } @Override public String getUsersDirectory() { - return rootDir + File.separator + getString("users.dir.name"); + return resolveFile(rootDir, getString("users.dir.name")); } @Override @@ -179,12 +204,12 @@ public LetterManager getLetterManager() { @Override public String getLettersDirectory(EbicsUser user) { - return getUserDirectory(user) + File.separator + getString("letters.dir.name"); + return resolveFile(getUserDirectory(user), getString("letters.dir.name")); } @Override public String getUserDirectory(EbicsUser user) { - return getUsersDirectory() + File.separator + user.getUserId(); + return resolveFile(getUsersDirectory(), user.getUserId()); } @Override diff --git a/src/main/java/org/kopi/ebics/xml/DownloadInitializationRequestElement.java b/src/main/java/org/kopi/ebics/xml/DownloadInitializationRequestElement.java index 4bc96c3e..73705d38 100644 --- a/src/main/java/org/kopi/ebics/xml/DownloadInitializationRequestElement.java +++ b/src/main/java/org/kopi/ebics/xml/DownloadInitializationRequestElement.java @@ -125,9 +125,22 @@ public void buildInitialization() throws EbicsException { orderType, fDLOrderParamsType); } else { - StandardOrderParamsType standardOrderParamsType; + StandardOrderParamsType standardOrderParamsType; + standardOrderParamsType = EbicsXmlFactory.createStandardOrderParamsType(); + + if (startRange != null && endRange != null) { + StandardOrderParamsType.DateRange range = StandardOrderParamsType.DateRange.Factory.newInstance(); + Calendar start = Calendar.getInstance(); + Calendar end = Calendar.getInstance(); + start.setTime(startRange); + end.setTime(endRange); + range.setStart(start); + range.setEnd(end); + + standardOrderParamsType.setDateRange(range); + } + - standardOrderParamsType = EbicsXmlFactory.createStandardOrderParamsType(); //FIXME Some banks cannot handle OrderID element in download process. Add parameter in configuration!!! orderDetails = EbicsXmlFactory.createStaticHeaderOrderDetailsType(null,//session.getUser().getPartner().nextOrderId(), OrderAttributeType.DZHNN, diff --git a/src/main/resources/build.properties b/src/main/resources/build.properties new file mode 100644 index 00000000..9b161998 --- /dev/null +++ b/src/main/resources/build.properties @@ -0,0 +1,2 @@ +build.version=${pom.version} +build.name=${pom.name} \ No newline at end of file diff --git a/src/main/resources/org/kopi/ebics/client/messages_en.properties b/src/main/resources/org/kopi/ebics/client/messages_en.properties index 3f92d5b2..ecaf447f 100644 --- a/src/main/resources/org/kopi/ebics/client/messages_en.properties +++ b/src/main/resources/org/kopi/ebics/client/messages_en.properties @@ -23,7 +23,10 @@ app.quit.error = Error while saving users information app.quit.partners = Saving {0} partner information app.quit.users = Saving {0} user information +download.request.send = Downloading file for order type {0} download.file.error = Cannot download the requested file +download.file.success = Data has been retrieved correctly for order type {0} +download.file.nodata = No data available for order type {0} hia.request.send = Sending authentication and encryption certificates for {0} user hia.send.error = The authentication and encryption certificates has not been sent for the {0} user @@ -41,6 +44,7 @@ ini.send.success = The signature certificate has been sent correctly for the { init.configuration = Configuration initialization +upload.request.send = Uploading file for order type {0} upload.file.error = Cannot upload file {0} to the ebics server upload.segment = Uploading segment number {0} diff --git a/src/main/resources/org/kopi/ebics/client/messages_fr.properties b/src/main/resources/org/kopi/ebics/client/messages_fr.properties index 7af2ce38..132dec97 100644 --- a/src/main/resources/org/kopi/ebics/client/messages_fr.properties +++ b/src/main/resources/org/kopi/ebics/client/messages_fr.properties @@ -23,37 +23,41 @@ app.quit.error = Erreur dans la sauvgarde des informations utilisateurs app.quit.partners = Sauvgarde des informations concernant le partenaire {0} app.quit.users = Sauvgarde des informations concernant l''utilisateur {0} -download.file.error = Erreur dans le t\u00E9l\u00E9chargement du fichier +download.request.send = R\u00e9cup\u00e9ration du fichier pour la type de commande {0} +download.file.error = Erreur dans le t\u00e9l\u00e9chargement du fichier +download.file.success = Les donn\u00e9es ont \u00e9t\u00e9 r\u00e9cup\u00e9r\u00e9es avec succ\u00e8s pour le type de commande {0} +download.file.nodata = Pas de donn\u00e9es disponibles pour la type de commande {0} hia.request.send = Envoi des certificats d''authentification et de cryptage pour l''utilisateur {0} -hia.send.error = Les certificats d''authentification et de cryptage n''ont pas \u00E9t\u00E9 envoy\u00E9s pour l''utilisateur {0} -hia.send.success = Les certificats d''authentification et de cryptage ont \u00E9t\u00E9 envoy\u00E9s avec succ\u00E8s pour l''utilisateur {0} +hia.send.error = Les certificats d''authentification et de cryptage n''ont pas \u00e9t\u00e9 envoy\u00e9s pour l''utilisateur {0} +hia.send.success = Les certificats d''authentification et de cryptage ont \u00e9t\u00e9 envoy\u00e9s avec succ\u00e8s pour l''utilisateur {0} -hpb.request.send = R\u00E9cup\u00E9ration des cl\u00E9s publiques de la banque pour l''utilisateur {0} -hpb.send.error = Les cl\u00E9s publiques de la banque ne peuvent pas \u00EAtre r\u00E9cup\u00E9r\u00E9es pour l''utilisateur {0} -hpb.send.success = Les cl\u00E9s publiques de la banque ont \u00E9t\u00E9 r\u00E9cup\u00E9r\u00E9es avec succ\u00E8s pour l''utilisateur {0} +hpb.request.send = R\u00e9cup\u00e9ration des cl\u00e9s publiques de la banque pour l''utilisateur {0} +hpb.send.error = Les cl\u00e9s publiques de la banque ne peuvent pas \u00eatre r\u00e9cup\u00e9r\u00e9es pour l''utilisateur {0} +hpb.send.success = Les cl\u00e9s publiques de la banque ont \u00e9t\u00e9 r\u00e9cup\u00e9r\u00e9es avec succ\u00e8s pour l''utilisateur {0} -http.code.error = Code de retour HTTP erron\u00E9: {0} +http.code.error = Code de retour HTTP erron\u00e9: {0} ini.request.send = Envoie du certificat de signature pour l''utilisateur {0} -ini.send.error = Le certificat de signature ne peut pas \u00EAtre envoy\u00E9 pour l''utilisateur {0} -ini.send.success = Le certificat de signature a \u00E9t\u00E9 envoy\u00E9 avec succ\u00E8s pour l''utilisateur {0} +ini.send.error = Le certificat de signature ne peut pas \u00eatre envoy\u00e9 pour l''utilisateur {0} +ini.send.success = Le certificat de signature a \u00e9t\u00e9 envoy\u00e9 avec succ\u00e8s pour l''utilisateur {0} init.configuration = Initialisation de la configuration -upload.file.error = Le fichier {0} ne peut pas \u00EAtre envoy\u00E9 au server ebics -upload.segment = Envoie du segment num\u00E9ro {0} - -user.already.hia.initialized = Les certificats d''autentification et de cryptage on \u00E9t\u00E9 d\u00E9j\u00E0 envoy\u00E9 pour l''utilisateur {0} -user.already.initialized = Le certificate de signature a \u00E9t\u00E9 d\u00E9j\u00E0 envoy\u00E9 pour l''utilisateur {0} -user.create.directories = Cr\u00E9ation des r\u00E9p\u00E9rtoires n\u00E9cessaires pour l''utilisateur {0} -user.create.error = L''utilisateur ne peut pas \u00EAtre cr\u00E9e -user.create.info = Cr\u00E9ation du nouvel utilisateur {0} -user.create.success = L''utilisateur {0} a \u00E9t\u00E9 cr\u00E9e avec succ\u00E8s -user.load.error = L''utilisateur ne peut pas \u00EAtre charg\u00E9 +upload.request.send = Envoi du fichier pour le type de commande {0} +upload.file.error = Le fichier {0} ne peut pas \u00eatre envoy\u00e9 au server ebics +upload.segment = Envoie du segment num\u00e9ro {0} + +user.already.hia.initialized = Les certificats d''autentification et de cryptage on \u00e9t\u00e9 d\u00e9j\u00e0 envoy\u00e9 pour l''utilisateur {0} +user.already.initialized = Le certificate de signature a \u00e9t\u00e9 d\u00e9j\u00e0 envoy\u00e9 pour l''utilisateur {0} +user.create.directories = Cr\u00e9ation des r\u00e9p\u00e9rtoires n\u00e9cessaires pour l''utilisateur {0} +user.create.error = L''utilisateur ne peut pas \u00eatre cr\u00e9e +user.create.info = Cr\u00e9ation du nouvel utilisateur {0} +user.create.success = L''utilisateur {0} a \u00e9t\u00e9 cr\u00e9e avec succ\u00e8s +user.load.error = L''utilisateur ne peut pas \u00eatre charg\u00e9 user.load.info = Chargement de l''utilisateur {0} -user.load.success = L''utilisateur {0} a \u00E9t\u00E9 charg\u00E9 avec succ\u00E8s +user.load.success = L''utilisateur {0} a \u00e9t\u00e9 charg\u00e9 avec succ\u00e8s spr.request.send = Blockage de l''utilisateur {0} spr.send.error = Erreur dans le bloquage de l''utilisateur {0} -spr.send.success = L''utilisateur {0} est bloqu�. Vous devez effectuez les op�rations d''initialisations � nouveau. \ No newline at end of file +spr.send.success = L''utilisateur {0} est bloqu\u00e9. Vous devez effectuez les op\u00e9rations d''initialisations \u00e0 nouveau. \ No newline at end of file