// $Id $ // (C) cantamen/Dirk Hillbrecht 2013. Released under the LGPL 2.0 or higher. // Version: 2013-10-23-02 // For more information, contact info@cantamen.de or dh@cantamen.de. See http://www.cantamen.de package ENTER-PACKAGE-HERE; import java.io.*; import java.net.HttpURLConnection; import java.net.URL; import java.security.*; import java.security.cert.*; import java.security.cert.Certificate; import javax.net.ssl.*; import javax.xml.parsers.DocumentBuilderFactory; import org.w3c.dom.*; /** Connector to IBAN conversion service from Theano GmbH/IBAN-BIC.com. * * This is a Java-6-SE-only class which connects to the IBAN conversion service of Theano GmbH as described on www.iban-bic.com. * It hides all the implementation of the connection behind some nice Java classes which do the dirty work. *
* Use this by creating one instance of the connector per thread and then calling generateIBAN() or another of the top level public
* methods. The methods run synchronously and return either one of the defined result object (e.g. IBANReply) or throw an
* IBANConverterException. Assuming the existance of some "BankAccountData" data type, a typical usage could be like this:
*
*
IBANConverterServiceConnector connector=new IBANConverterServiceConnector("username","secret");
for (BankAccountData accountdata:getSomeBankAccountData(...whatever...)) {
try {
IBANConverterServiceConnector.IBANReply ibanreply=connector.generateIBAN(accountdata.getCountryCode(),accountdata.getBankCode(),accountdata.getAccountNumber());
if (ibanreply.isPassed() && ibanreply.getResultCode()==0)
accountdata.setIBAN(ibanreply.getIBAN());
else {
accountdata.setProbableIBAN(ibanreply.getIBAN());
accountdata.setIBANResultCode(ibanreply.getResultCode());
}
}
catch (IBANConverterException ice) {
accountdata.setIBANConversionProblem(ice);
}
}
*
* The class contains some static helper classes for result and exception transport. It is fully self-contained with no other external references but the Java 6 SE
* runtime environment.
*
* Internally, IBANConverterServiceConnector connects to the service via its HTTP-based interface. It passes the information through URL parameters and receives * the reply as XML string in the HTTP query response. That one is converted into a DOM tree from where the reply data is extracted and stored in the reply class. * The class does not use any SOAP or other frameworks above the W3C DOM system. * * @author (C) cantamen/Dirk Hillbrecht 2013. Released under the LGPL 2.0 or higher. For more information, contact info@cantamen.de or dh@cantamen.de. See http://www.cantamen.de * @version $Id $ */ public class IBANConverterServiceConnector { // ************************************************** // *** Public interface and public helper classes *** // ************************************************** /** Global flag to set this into debug mode. * * If set to true, the whole connector does not query the conversion service over the web, but simply returns a precompiled answer. * Set this to true during development and debugging so that you do not call the service everytime you run your tests. In production environments, * this should always be set to false. */ private static final boolean DEBUG=false; /** Instantiate a service connector for the IBAN conversion. * * Converters are single-threaded! It takes the username/password information which are used for all ongoing queries. * * @param usernamex Username to use for the IBAN conversion service * @param passwordx Passwort for authenticating to the IBAN conversion service */ public IBANConverterServiceConnector(String usernamex,String passwordx) { username=usernamex; password=passwordx; /* - This is for password hashing which does not seem to work on Java try { md5 = MessageDigest.getInstance("MD5"); } catch (NoSuchAlgorithmException e) { throw new IllegalStateException("error.nomd5digesterfound",e); } */ dbf=DocumentBuilderFactory.newInstance(); dbf.setValidating(false); dbf.setIgnoringComments(false); dbf.setIgnoringElementContentWhitespace(true); dbf.setNamespaceAware(true); // dbf.setCoalescing(true); // dbf.setExpandEntityReferences(true); } /** Special exception class for signalling problems with the IBAN conversion. * * This exception is thrown if something goes severely wrong with the IBAN conversion. It is only thrown if the service is cannot be connected * or returns syntactically unexpected replies. If the IBAN was incontructible from the given data, no exception is thrown but instead a reply * is returned which describes the problems. *
* This exceptions is derived from RuntimeException so that you might more or less "ignore" it while coding. You should not do, however, as such
* behaviour will always bite you and your project when you do not expect it.
*/
public static class IBANConverterException extends RuntimeException {
public IBANConverterException(String s) { super(s); }
public IBANConverterException(String s,Throwable c) { super(s,c); }
}
/** Reply class for IBAN query results.
*
* This class is returned by generateIBAN() and friends. It contains the - probably - generated IBAN and other values as they are returned by the
* IBAN conversion web service. An instance of this class is immutable.
*/
public static class IBANReply {
private final String iban;
private final boolean passed;
private final int resultcode;
private final int balance;
public IBANReply(String ibanx,boolean passedx,int resultcodex,int balancex) {
iban=ibanx;passed=passedx;resultcode=resultcodex;balance=balancex;
}
/** Return the IBAN as created by the service. */
public String getIBAN() { return iban; }
/** Return whether the IBAN generation passed or failed ("result" value of the IBAN creation service) */
public boolean isPassed() { return passed; }
/** Return the result code as generated by the IBAN creation service. */
public int getResultCode() { return resultcode; }
/** Returns the account balance information from the IBAN creation service. */
public int getBalance() { return balance; }
/** Return a string representation. */
@Override
public String toString() { return "{IBANReply, passed: "+passed+", result code: "+resultcode+", IBAN: "+iban+", balance: "+balance+"}"; }
}
/** Builder for the IBANReply class. */
private static class IBANReplyBuilder {
private String iban=""; public IBANReplyBuilder setIBAN(String ibanx) { iban=ibanx; return this; }
private boolean passed=false; public IBANReplyBuilder setResult(String resultstring) { passed="passed".equalsIgnoreCase(resultstring); return this; }
private int resultcode=-1; public IBANReplyBuilder setResultCode(String resultcodestring) { resultcode=Integer.parseInt(resultcodestring); return this; }
private int balance=-1; public IBANReplyBuilder setBalance(String balancestring) { balance=Integer.parseInt(balancestring); return this; }
public IBANReply build() { return new IBANReply(iban,passed,resultcode,balance); }
}
/** Convert a legacy German bankcode/account pair to an IBAN.
*
* This method gets a german bank account at a German bank and returns the IBAN for this account. The result also contains result and error codes of
* the conversion.
*
* @param bankcode Bank code of the account to convert
* @param account Account number
* @returns IBANReply instance with the generated IBAN or the appropriate error information if no IBAN was created.
*/
public IBANReply generateGermanIBAN(String bankcode,String account) throws IBANConverterException {
return generateIBAN("DE",bankcode,account);
}
/** Convert a legacy bankcode/account pair to an IBAN.
*
* This method gets a bank account at a bank in the country given by the country code and returns the IBAN for this account.
* The result also contains result and error codes of the conversion.
*
* @param countrycode 2-character ISO country code of the account's and bank's country
* @param bankcode Bank code of the account to convert
* @param account Account number
* @returns IBANReply instance with the generated IBAN or the appropriate error information if no IBAN was created.
*/
public IBANReply generateIBAN(String countrycode,String bankcode,String account) throws IBANConverterException {
try {
Document reply;
if (DEBUG) {
System.err.println("IBAN converter in DEBUG MODE!!!");
reply=dbf.newDocumentBuilder().parse(new ByteArrayInputStream(debugresult.getBytes()));
}
else {
URL url = new URL(generateCalculateIBANURL(countrycode,bankcode,account));
HttpsURLConnection con = (HttpsURLConnection)url.openConnection();
try {
// Here we set the special socket factory for accessing the IBAN service
con.setSSLSocketFactory(ibanrechnersslcontext.getSocketFactory());
if (con.getResponseCode()!=HttpURLConnection.HTTP_OK)
throw new IllegalStateException("error.httpnotok|"+con.getResponseCode()+"|"+con.getResponseMessage());
// We have to remove any leading empty lines from the body as this confuses the DOM parser. So, eat up characters until "<" appears.
PushbackInputStream pbis=new PushbackInputStream(con.getInputStream());
int ch;
while ((ch=pbis.read())=='\n'){}
pbis.unread(ch);
reply=dbf.newDocumentBuilder().parse(pbis);
}
finally {
con.disconnect();
}
}
if (reply==null)
throw new IllegalStateException("error.noreply");
Node result=reply.getFirstChild();
if (result==null)
throw new IllegalStateException("error.noresultnode");
if (!"result".equals(result.getNodeName()))
throw new IllegalStateException("error.resultnodename|"+result.getNodeName());
IBANReplyBuilder builder=new IBANReplyBuilder();
NodeList nl=result.getChildNodes();
for (int i=0;i