Title:
- * - *Description:
- * - *Copyright: Copyright (c) 2004
- * - *Company:
- * - * @author not attributable - * @version 1.0 - */ public class Sample { - static SocketDaemon thread; - + + private static final boolean SSL = false; + private static SelfSignedCertificate ssc; + + public static void main(String[] args) { + try { + System.setProperty("com.ceridwen.circulation.SIP.charset", "ISO8859_1"); + + SIPDaemon server; + + // Run netty server + if (SSL) { + ssc = new SelfSignedCertificate(); + server = new SIPDaemon("Sample", "localhost", 12345, ssc.certificate(), ssc.privateKey(), new DummyDriverFactory(), true); + } else { + server = new SIPDaemon("Sample", "localhost", 12345, new DummyDriverFactory(), true); + } + server.start(); + + // Do sample checkout + Sample.checkOut(); + + // Shut down netty server + server.stop(); + } catch (Exception ex) { + Logger.getLogger(Sample.class.getName()).log(Level.SEVERE, null, ex); + } + } + + public static void checkOut() { + /** + * Now try basic client commands + */ + SocketConnection connection; + + if (SSL) { + connection = new SSLSocketConnection(); + ((SSLSocketConnection) connection).setServerCertificateCA(ssc.certificate()); + } else { + connection = new SocketConnection(); + } + connection.setHost("localhost"); + connection.setPort(12345); + connection.setConnectionTimeout(30000); + connection.setIdleTimeout(30000); + connection.setRetryAttempts(2); + connection.setRetryWait(500); + + try { + connection.connect(); + } catch (Exception ex) { + Logger.getLogger(Sample.class.getName()).log(Level.SEVERE, null, ex); + return; + } - public static void startServer() { - /** - * Run simple socket server - */ + /** + * It is necessary to send a SC Status with protocol version 2.0 else server + * will assume 1.0) + */ + SCStatus scStatusRequest = new SCStatus(); + scStatusRequest.setProtocolVersion(ProtocolVersion.VERSION_2_00); - Sample.thread = new SocketDaemon("localhost", 12345, new MessageHandlerDummyImpl()); - Sample.thread.setStrictChecksumChecking(true); - Sample.thread.start(); - } + Message scStatusResponse; - public static void checkOut() { - /** - * Now try basic client commands - */ - Connection connection; - Message request, response; - - connection = new SocketConnection(); - ((SocketConnection) connection).setHost("localhost"); - ((SocketConnection) connection).setPort(12345); - ((SocketConnection) connection).setConnectionTimeout(30000); - ((SocketConnection) connection).setIdleTimeout(30000); - ((SocketConnection) connection).setRetryAttempts(2); - ((SocketConnection) connection).setRetryWait(500); - - try { - connection.connect(); - } catch (Exception e1) { - e1.printStackTrace(); - return; - } - - /** - * It is necessary to send a SC Status with protocol version 2.0 else - * server will assume 1.0) - */ - - request = new SCStatus(); - ((SCStatus) request).setProtocolVersion(ProtocolVersion.VERSION_2_00); - - try { - response = connection.send(request); - } catch (RetriesExceeded e) { - e.printStackTrace(); - return; - } catch (ConnectionFailure e) { - e.printStackTrace(); - return; - } catch (MessageNotUnderstood e) { - e.printStackTrace(); - return; - } catch (ChecksumError e) { - e.printStackTrace(); - return; - } catch (SequenceError e) { - e.printStackTrace(); - return; - } catch (MandatoryFieldOmitted e) { - e.printStackTrace(); - return; - } catch (InvalidFieldLength e) { - e.printStackTrace(); - return; - } - - if (!(response instanceof ACSStatus)) { - System.err.println("Error - Status Request did not return valid response from server."); - return; - } - - /** - * For debugging XML handling code (but could be useful in Cocoon) - */ - response.xmlEncode(System.out); - - /** - * Check if the server can support checkout - */ - if (!((ACSStatus) response).getSupportedMessages().isSet(SupportedMessages.CHECK_OUT)) { - System.out.println("Check out not supported"); - return; - } - - request = new CheckOut(); - - /** - * The code below would be the normal way of creating the request - */ - - ((CheckOut) request).setPatronIdentifier("2000000"); - ((CheckOut) request).setItemIdentifier("300000000"); - ((CheckOut) request).setSCRenewalPolicy(Boolean.TRUE); - ((CheckOut) request).setTransactionDate(new Date()); - - try { - response = connection.send(request); - } catch (RetriesExceeded e) { - e.printStackTrace(); - return; - } catch (ConnectionFailure e) { - e.printStackTrace(); - return; - } catch (MessageNotUnderstood e) { - e.printStackTrace(); - return; - } catch (ChecksumError e) { - e.printStackTrace(); - return; - } catch (SequenceError e) { - e.printStackTrace(); - return; - } catch (MandatoryFieldOmitted e) { - e.printStackTrace(); - return; - } catch (InvalidFieldLength e) { - e.printStackTrace(); - return; - } - - if (!(response instanceof CheckOutResponse)) { - System.err.println("Error - CheckOut Request did not return valid response from server"); - return; - } - response.xmlEncode(System.out); - - // System.out.println(((PatronInformationResponse)response).getPersonalName()); - // System.out.println(((PatronInformationResponse)response).getEMailAddress()); - - connection.disconnect(); + try { + scStatusResponse = connection.send(scStatusRequest); + } catch (RetriesExceeded | MessageNotUnderstood | ChecksumError | SequenceError | MandatoryFieldOmitted | InvalidFieldLength ex) { + Logger.getLogger(Sample.class.getName()).log(Level.SEVERE, null, ex); + return; } - public static void stopServer() { - /** - * Stop simple socket server - */ + if (!(scStatusResponse instanceof ACSStatus)) { + Logger.getLogger(Sample.class.getName()).log(Level.SEVERE, "Error - Status Request did not return valid response from server."); + return; + } - Sample.thread.shutdown(); + /** + * For debugging XML handling code (but could be useful in Cocoon) + */ + scStatusResponse.xmlEncode(System.out); + + /** + * Check if the server can support checkout + */ + if (!((ACSStatus) scStatusResponse).getSupportedMessages().isSet(SupportedMessages.CHECK_OUT)) { + Logger.getLogger(Sample.class.getName()).log(Level.SEVERE, "Check out not supported"); + return; } - public static void main(String[] args) { - System.setProperty("com.ceridwen.circulation.SIP.charset", "ISO8859_1"); + CheckOut checkOutRequest = new CheckOut(); - Sample.startServer(); - Sample.checkOut(); - Sample.stopServer(); + /** + * Now try a checkout request + */ + checkOutRequest.setPatronIdentifier("2000000"); + checkOutRequest.setItemIdentifier("300000000"); + checkOutRequest.setSCRenewalPolicy(Boolean.TRUE); + checkOutRequest.setTransactionDate(new Date()); + Message checkOutResponse; + + try { + checkOutResponse = connection.send(checkOutRequest); + } catch (RetriesExceeded | MessageNotUnderstood | ChecksumError | SequenceError | MandatoryFieldOmitted | InvalidFieldLength ex) { + Logger.getLogger(Sample.class.getName()).log(Level.SEVERE, null, ex); + return; } + + if (!(checkOutResponse instanceof CheckOutResponse)) { + Logger.getLogger(Sample.class.getName()).log(Level.SEVERE, "Error - CheckOut Request did not return valid response from server"); + return; + } + checkOutResponse.xmlEncode(System.out); + + connection.disconnect(); + } + } diff --git a/src/main/java/com/ceridwen/circulation/SIP/samples/netty/DummyDriver.java b/src/main/java/com/ceridwen/circulation/SIP/samples/netty/DummyDriver.java new file mode 100644 index 0000000..c32385b --- /dev/null +++ b/src/main/java/com/ceridwen/circulation/SIP/samples/netty/DummyDriver.java @@ -0,0 +1,150 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package com.ceridwen.circulation.SIP.samples.netty; + +import com.ceridwen.circulation.SIP.messages.ACSStatus; +import com.ceridwen.circulation.SIP.messages.CheckInResponse; +import com.ceridwen.circulation.SIP.messages.CheckOutResponse; +import com.ceridwen.circulation.SIP.messages.EndSessionResponse; +import com.ceridwen.circulation.SIP.messages.FeePaidResponse; +import com.ceridwen.circulation.SIP.messages.HoldResponse; +import com.ceridwen.circulation.SIP.messages.ItemInformationResponse; +import com.ceridwen.circulation.SIP.messages.ItemStatusUpdateResponse; +import com.ceridwen.circulation.SIP.messages.LoginResponse; +import com.ceridwen.circulation.SIP.messages.PatronEnableResponse; +import com.ceridwen.circulation.SIP.messages.PatronInformationResponse; +import com.ceridwen.circulation.SIP.messages.PatronStatusRequest; +import com.ceridwen.circulation.SIP.messages.PatronStatusResponse; +import com.ceridwen.circulation.SIP.messages.RenewAllResponse; +import com.ceridwen.circulation.SIP.messages.RenewResponse; +import com.ceridwen.circulation.SIP.messages.SCStatus; +import com.ceridwen.circulation.SIP.netty.server.driver.AbstractDriver; +import com.ceridwen.circulation.SIP.netty.server.driver.operation.BlockPatronOperation; +import com.ceridwen.circulation.SIP.netty.server.driver.operation.CheckInOperation; +import com.ceridwen.circulation.SIP.netty.server.driver.operation.CheckOutOperation; +import com.ceridwen.circulation.SIP.netty.server.driver.operation.EndPatronSessionOperation; +import com.ceridwen.circulation.SIP.netty.server.driver.operation.FeePaidOperation; +import com.ceridwen.circulation.SIP.netty.server.driver.operation.HoldOperation; +import com.ceridwen.circulation.SIP.netty.server.driver.operation.ItemInformationOperation; +import com.ceridwen.circulation.SIP.netty.server.driver.operation.ItemStatusUpdateOperation; +import com.ceridwen.circulation.SIP.netty.server.driver.operation.LoginOperation; +import com.ceridwen.circulation.SIP.netty.server.driver.operation.PatronEnableOperation; +import com.ceridwen.circulation.SIP.netty.server.driver.operation.PatronInformationOperation; +import com.ceridwen.circulation.SIP.netty.server.driver.operation.PatronStatusOperation; +import com.ceridwen.circulation.SIP.netty.server.driver.operation.RenewAllOperation; +import com.ceridwen.circulation.SIP.netty.server.driver.operation.RenewOperation; + +/** + * + * @author Matthew + */ +public class DummyDriver extends AbstractDriver + implements BlockPatronOperation, + CheckInOperation, + CheckOutOperation, + EndPatronSessionOperation, + FeePaidOperation, + HoldOperation, + ItemInformationOperation, + ItemStatusUpdateOperation, + LoginOperation, + PatronEnableOperation, + PatronInformationOperation, + PatronStatusOperation, + RenewAllOperation, + RenewOperation +{ + + @Override + public ACSStatus Status(ACSStatus status, SCStatus msg) { + status.setACSRenewalPolicy(false); + status.setCheckInOk(true); + status.setCheckOutOk(true); + status.setOfflineOk(false); + status.setStatusUpdateOk(true); + return status; + } + + @Override + public PatronStatusResponse BlockPatron( + com.ceridwen.circulation.SIP.messages.BlockPatron msg) { + return new PatronStatusResponse(); + } + + @Override + public CheckInResponse CheckIn( + com.ceridwen.circulation.SIP.messages.CheckIn msg) { + return new CheckInResponse(); + } + + @Override + public CheckOutResponse CheckOut( + com.ceridwen.circulation.SIP.messages.CheckOut msg) { + return new CheckOutResponse(); + } + + @Override + public EndSessionResponse EndPatronSession( + com.ceridwen.circulation.SIP.messages.EndPatronSession msg) { + return new EndSessionResponse(); + } + + @Override + public FeePaidResponse FeePaid( + com.ceridwen.circulation.SIP.messages.FeePaid msg) { + return new FeePaidResponse(); + } + + @Override + public HoldResponse Hold(com.ceridwen.circulation.SIP.messages.Hold msg) { + return new HoldResponse(); + } + + @Override + public ItemInformationResponse ItemInformation( + com.ceridwen.circulation.SIP.messages.ItemInformation msg) { + return new ItemInformationResponse(); + } + + @Override + public ItemStatusUpdateResponse ItemStatusUpdate( + com.ceridwen.circulation.SIP.messages.ItemStatusUpdate msg) { + return new ItemStatusUpdateResponse(); + } + + @Override + public LoginResponse Login(com.ceridwen.circulation.SIP.messages.Login msg) { + return new LoginResponse(); + } + + @Override + public PatronEnableResponse PatronEnable( + com.ceridwen.circulation.SIP.messages.PatronEnable msg) { + return new PatronEnableResponse(); + } + + @Override + public PatronInformationResponse PatronInformation( + com.ceridwen.circulation.SIP.messages.PatronInformation msg) { + return new PatronInformationResponse(); + } + + @Override + public PatronStatusResponse PatronStatus(PatronStatusRequest msg) { + return new PatronStatusResponse(); + } + + @Override + public RenewResponse Renew(com.ceridwen.circulation.SIP.messages.Renew msg) { + return new RenewResponse(); + } + + @Override + public RenewAllResponse RenewAll( + com.ceridwen.circulation.SIP.messages.RenewAll msg) { + return new RenewAllResponse(); + } +} diff --git a/src/main/java/com/ceridwen/circulation/SIP/samples/netty/DummyDriverFactory.java b/src/main/java/com/ceridwen/circulation/SIP/samples/netty/DummyDriverFactory.java new file mode 100644 index 0000000..2b9bd37 --- /dev/null +++ b/src/main/java/com/ceridwen/circulation/SIP/samples/netty/DummyDriverFactory.java @@ -0,0 +1,22 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package com.ceridwen.circulation.SIP.samples.netty; + +import com.ceridwen.circulation.SIP.netty.server.driver.Driver; +import com.ceridwen.circulation.SIP.netty.server.driver.DriverFactory; + +/** + * + * @author Matthew + */ +public class DummyDriverFactory implements DriverFactory { + + @Override + public Driver getDriver() { + return new DummyDriver(); + } + +} diff --git a/src/main/java/com/ceridwen/circulation/SIP/server/MessageBroker.java b/src/main/java/com/ceridwen/circulation/SIP/server/MessageBroker.java index fded984..7ca9e81 100644 --- a/src/main/java/com/ceridwen/circulation/SIP/server/MessageBroker.java +++ b/src/main/java/com/ceridwen/circulation/SIP/server/MessageBroker.java @@ -29,6 +29,7 @@ import com.ceridwen.circulation.SIP.messages.Message; import com.ceridwen.circulation.SIP.messages.SCResend; +@Deprecated public class MessageBroker { private static Log logger = LogFactory.getLog(MessageBroker.class); diff --git a/src/main/java/com/ceridwen/circulation/SIP/server/MessageHandler.java b/src/main/java/com/ceridwen/circulation/SIP/server/MessageHandler.java index d6044b6..ea33569 100644 --- a/src/main/java/com/ceridwen/circulation/SIP/server/MessageHandler.java +++ b/src/main/java/com/ceridwen/circulation/SIP/server/MessageHandler.java @@ -48,6 +48,7 @@ import com.ceridwen.circulation.SIP.messages.RenewResponse; import com.ceridwen.circulation.SIP.messages.SCStatus; +@Deprecated public interface MessageHandler { public ACSStatus Status(SCStatus msg); diff --git a/src/main/java/com/ceridwen/circulation/SIP/server/MessageHandlerDummyImpl.java b/src/main/java/com/ceridwen/circulation/SIP/server/MessageHandlerDummyImpl.java index f4d62a9..f8aa3f1 100644 --- a/src/main/java/com/ceridwen/circulation/SIP/server/MessageHandlerDummyImpl.java +++ b/src/main/java/com/ceridwen/circulation/SIP/server/MessageHandlerDummyImpl.java @@ -36,6 +36,7 @@ import com.ceridwen.circulation.SIP.messages.SCStatus; import com.ceridwen.circulation.SIP.types.flagfields.SupportedMessages; +@Deprecated public class MessageHandlerDummyImpl implements MessageHandler { @Override diff --git a/src/main/java/com/ceridwen/circulation/SIP/server/SocketDaemon.java b/src/main/java/com/ceridwen/circulation/SIP/server/SocketDaemon.java index 4c903c1..c3ad12c 100644 --- a/src/main/java/com/ceridwen/circulation/SIP/server/SocketDaemon.java +++ b/src/main/java/com/ceridwen/circulation/SIP/server/SocketDaemon.java @@ -31,6 +31,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +@Deprecated public class SocketDaemon extends Thread { private static Log logger = LogFactory.getLog(SocketDaemon.class); diff --git a/src/main/java/com/ceridwen/circulation/SIP/server/SocketServer.java b/src/main/java/com/ceridwen/circulation/SIP/server/SocketServer.java index 5756b11..410b8c6 100644 --- a/src/main/java/com/ceridwen/circulation/SIP/server/SocketServer.java +++ b/src/main/java/com/ceridwen/circulation/SIP/server/SocketServer.java @@ -18,12 +18,13 @@ ******************************************************************************/ package com.ceridwen.circulation.SIP.server; +@Deprecated public class SocketServer { /** * @param args */ - public static void main(String[] args) { + public static void main(String[] args) { SocketDaemon thread = new SocketDaemon("localhost", 12345, new MessageHandlerDummyImpl()); thread.start(); } diff --git a/src/main/java/com/ceridwen/circulation/SIP/server/package-info.java b/src/main/java/com/ceridwen/circulation/SIP/server/package-info.java new file mode 100644 index 0000000..4e885d7 --- /dev/null +++ b/src/main/java/com/ceridwen/circulation/SIP/server/package-info.java @@ -0,0 +1,7 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +@Deprecated +package com.ceridwen.circulation.SIP.server; diff --git a/src/main/java/com/ceridwen/circulation/SIP/transport/Connection.java b/src/main/java/com/ceridwen/circulation/SIP/transport/Connection.java index 077c0f3..a6dc36f 100644 --- a/src/main/java/com/ceridwen/circulation/SIP/transport/Connection.java +++ b/src/main/java/com/ceridwen/circulation/SIP/transport/Connection.java @@ -18,15 +18,6 @@ ******************************************************************************/ package com.ceridwen.circulation.SIP.transport; -/** - *Title: RTSI
- *Description: Real Time Self Issue
- *Copyright:
- - * @author Matthew J. Dovey - * @version 1.0 - */ - import java.util.Timer; import java.util.TimerTask; @@ -207,7 +198,7 @@ public String waitfor(String match) throws ConnectionFailure { return ret; } - public synchronized Message send(Message msg) throws ConnectionFailure, RetriesExceeded, ChecksumError, SequenceError, MessageNotUnderstood, + public synchronized Message send(Message msg) throws RetriesExceeded, ChecksumError, SequenceError, MessageNotUnderstood, MandatoryFieldOmitted, InvalidFieldLength { String request, response = null; Message responseMessage = null; @@ -217,13 +208,12 @@ public synchronized Message send(Message msg) throws ConnectionFailure, RetriesE } try { boolean retry; - boolean understood = false; int retries = 0; do { retry = false; try { if (this.getAddSequenceAndChecksum()) { - request = msg.encode(new Character(this.getNextSequence())); + request = msg.encode(Character.valueOf(this.getNextSequence())); } else { request = msg.encode(null); } @@ -238,28 +228,28 @@ public synchronized Message send(Message msg) throws ConnectionFailure, RetriesE responseMessage = Message.decode(response, null, this.getStrictChecksumChecking()); } if (responseMessage instanceof SCResend) { - throw new ConnectionFailure(); + throw new MessageNotUnderstood(); } - understood = true; - } catch (ConnectionFailure ex) { + } catch (ConnectionFailure | MessageNotUnderstood ex) { try { this.wait(this.getRetryWait()); } catch (Exception ex1) { Connection.log.debug("Thread sleep error", ex1); } - retry = true; retries++; if (retries > this.getRetryAttempts()) { - if (understood) { - throw new RetriesExceeded(); - } else { - throw new MessageNotUnderstood(); - } + if (ex instanceof MessageNotUnderstood) { + throw (MessageNotUnderstood)ex; + } else { + throw new RetriesExceeded(ex); + } + } else { + retry = true; } } } while (retry); if (responseMessage == null) { - throw new ConnectionFailure(); + throw new MessageNotUnderstood(); } return responseMessage; } catch (RetriesExceeded e) { diff --git a/src/main/java/com/ceridwen/circulation/SIP/transport/SSLSocketConnection.java b/src/main/java/com/ceridwen/circulation/SIP/transport/SSLSocketConnection.java new file mode 100644 index 0000000..786cf61 --- /dev/null +++ b/src/main/java/com/ceridwen/circulation/SIP/transport/SSLSocketConnection.java @@ -0,0 +1,146 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package com.ceridwen.circulation.SIP.transport; + + +import java.io.File; +import java.io.FileInputStream; +import java.net.Socket; +import java.nio.file.Files; +import java.security.KeyFactory; +import java.security.KeyStore; +import java.security.SecureRandom; +import java.security.cert.CertificateFactory; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocketFactory; +import java.security.cert.Certificate; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; +import java.util.Base64; + +/** + * + * @author Matthew.Dovey + */ +public class SSLSocketConnection extends SocketConnection { + + + private File clientCertificate; + private File clientPrivateKey; + private String clientPrivateKeyPassword; + private File serverCertificateCA; + + /** + * Get the value of ServerCertificateCA + * + * @return the value of ServerCertificateCA + */ + public File getServerCertificateCA() { + return serverCertificateCA; + } + + /** + * Set the value of ServerCertificateCA + * + * @param ServerCertificateCA new value of ServerCertificateCA + */ + public void setServerCertificateCA(File ServerCertificateCA) { + this.serverCertificateCA = ServerCertificateCA; + } + + + /** + * Get the value of clientCertificate + * + * @return the value of clientCertificate + */ + public File getClientCertificate() { + return clientCertificate; + } + + /** + * Set the value of clientCertificate + * + * @param clientCertificate new value of clientCertificate + */ + public void setClientCertificate(File clientCertificate) { + this.clientCertificate = clientCertificate; + } + + + /** + * Get the value of clientPrivateKey + * + * @return the value of clientPrivateKey + */ + public File getClientPrivateKey() { + return clientPrivateKey; + } + + /** + * Set the value of clientPrivateKey + * + * @param clientPrivateKey new value of clientPrivateKey + */ + public void setClientPrivateKey(File clientPrivateKey) { + this.clientPrivateKey = clientPrivateKey; + } + + public String getClientPrivateKeyPassword() { + return clientPrivateKeyPassword; + } + + public void setClientPrivateKeyPassword(String clientPrivateKeyPassword) { + this.clientPrivateKeyPassword = clientPrivateKeyPassword; + } + + + @Override + protected Socket getSocket() throws Exception { + KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509");; + TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + KeyStore keyStore = KeyStore.getInstance("PKCS12"); + KeyStore trustStore = KeyStore.getInstance("PKCS12"); + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + KeyFactory kf = KeyFactory.getInstance("RSA"); + + if (clientPrivateKey != null && clientCertificate != null) { + keyStore.load(null); + String data = new String(Files.readAllBytes(clientPrivateKey.toPath())); + data = data.replace("-----BEGIN PRIVATE KEY-----\n", ""); + data = data.replace("-----END PRIVATE KEY-----", ""); + data = data.replaceAll("\\s", ""); + if (clientPrivateKeyPassword == null) { + keyStore.setKeyEntry("client", kf.generatePrivate(new PKCS8EncodedKeySpec(Base64.getDecoder().decode(data))), null, cf.generateCertificates(new FileInputStream(clientCertificate)).toArray(new Certificate[]{})); + keyManagerFactory.init(keyStore, null); + } else { + keyStore.setKeyEntry("client", kf.generatePrivate(new PKCS8EncodedKeySpec(Base64.getDecoder().decode(data))), clientPrivateKeyPassword.toCharArray(), cf.generateCertificates(new FileInputStream(clientCertificate)).toArray(new Certificate[]{})); + keyManagerFactory.init(keyStore, clientPrivateKeyPassword.toCharArray()); + } + } else { + keyManagerFactory = null; + } + + if (serverCertificateCA != null) { + trustStore.load(null); + trustStore.setCertificateEntry("ca", cf.generateCertificate(new FileInputStream(serverCertificateCA))); + trustManagerFactory.init(trustStore); + } else { + if (keyManagerFactory == null) { + return SSLContext.getDefault().getSocketFactory().createSocket(); + } else { + trustManagerFactory.init((KeyStore)null); + } + } + SSLContext context = SSLContext.getInstance("TLS"); + context.init(keyManagerFactory == null?null:keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), new SecureRandom()); + SSLSocketFactory sockFact = context.getSocketFactory(); + return sockFact.createSocket(); + } + +} diff --git a/src/main/java/com/ceridwen/circulation/SIP/transport/SocketConnection.java b/src/main/java/com/ceridwen/circulation/SIP/transport/SocketConnection.java index 3060f48..4f12240 100644 --- a/src/main/java/com/ceridwen/circulation/SIP/transport/SocketConnection.java +++ b/src/main/java/com/ceridwen/circulation/SIP/transport/SocketConnection.java @@ -29,6 +29,7 @@ import org.apache.commons.logging.LogFactory; import com.ceridwen.circulation.SIP.exceptions.ConnectionFailure; +import com.ceridwen.circulation.SIP.exceptions.RetriesExceeded; import com.ceridwen.circulation.SIP.messages.Message; public class SocketConnection extends Connection { @@ -38,10 +39,14 @@ public class SocketConnection extends Connection { private BufferedReader in; private BufferedWriter out; + protected Socket getSocket() throws Exception { + return new java.net.Socket(); + } + @Override protected void connect(int retryAttempts) throws Exception { try { - this.socket = new java.net.Socket(); + this.socket = this.getSocket(); this.socket.connect(new InetSocketAddress(this.getHost(), this.getPort()), this.getConnectionTimeout()); this.socket.setSoTimeout(this.getIdleTimeout()); this.out = new BufferedWriter(new OutputStreamWriter(this.socket.getOutputStream(), Message.getCharsetEncoding())); @@ -55,7 +60,7 @@ protected void connect(int retryAttempts) throws Exception { } this.connect(retryAttempts - 1); } else { - throw ex; + throw new RetriesExceeded(ex); } } } diff --git a/src/main/java/com/ceridwen/circulation/SIP/transport/TelnetConnection.java b/src/main/java/com/ceridwen/circulation/SIP/transport/TelnetConnection.java index 399de83..ac26257 100644 --- a/src/main/java/com/ceridwen/circulation/SIP/transport/TelnetConnection.java +++ b/src/main/java/com/ceridwen/circulation/SIP/transport/TelnetConnection.java @@ -28,6 +28,7 @@ import org.apache.commons.net.telnet.TelnetClient; import com.ceridwen.circulation.SIP.exceptions.ConnectionFailure; +import com.ceridwen.circulation.SIP.exceptions.RetriesExceeded; import com.ceridwen.circulation.SIP.messages.Message; import com.ceridwen.util.net.TimeoutSocketFactory; @@ -94,7 +95,7 @@ protected void connect(int retry) throws Exception { } this.connect(retry - 1); } else { - throw e; + throw new RetriesExceeded(e); } } try { @@ -110,7 +111,7 @@ protected void connect(int retry) throws Exception { } this.connect(retry - 1); } else { - throw e; + throw new RetriesExceeded(e); } } } diff --git a/src/main/java/com/ceridwen/circulation/SIP/transport/TestSSLSocketTransport.java b/src/main/java/com/ceridwen/circulation/SIP/transport/TestSSLSocketTransport.java new file mode 100644 index 0000000..42c3702 --- /dev/null +++ b/src/main/java/com/ceridwen/circulation/SIP/transport/TestSSLSocketTransport.java @@ -0,0 +1,175 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package com.ceridwen.circulation.SIP.transport; + +import com.ceridwen.circulation.SIP.exceptions.ChecksumError; +import com.ceridwen.circulation.SIP.exceptions.InvalidFieldLength; +import com.ceridwen.circulation.SIP.exceptions.MandatoryFieldOmitted; +import com.ceridwen.circulation.SIP.exceptions.MessageNotUnderstood; +import com.ceridwen.circulation.SIP.exceptions.RetriesExceeded; +import com.ceridwen.circulation.SIP.exceptions.SequenceError; +import com.ceridwen.circulation.SIP.messages.ACSStatus; +import com.ceridwen.circulation.SIP.messages.CheckOut; +import com.ceridwen.circulation.SIP.messages.CheckOutResponse; +import com.ceridwen.circulation.SIP.messages.Message; +import com.ceridwen.circulation.SIP.messages.SCStatus; +import com.ceridwen.circulation.SIP.netty.server.SIPDaemon; +import com.ceridwen.circulation.SIP.samples.netty.DummyDriverFactory; +import com.ceridwen.circulation.SIP.types.enumerations.ProtocolVersion; +import com.ceridwen.circulation.SIP.types.flagfields.SupportedMessages; +import io.netty.handler.ssl.util.SelfSignedCertificate; +import java.util.Date; +import org.junit.After; +import static org.junit.Assert.fail; +import org.junit.Before; +import org.junit.Test; + +/** + * + * @author Matthew.Dovey + */ +public class TestSSLSocketTransport { + SIPDaemon server; + SelfSignedCertificate ssc; + + @Before + public void setUp() throws Exception { + // Run netty server + ssc = new SelfSignedCertificate(); + server = new SIPDaemon("Sample", "localhost", 12345, ssc.certificate(), ssc.privateKey(), new DummyDriverFactory(), true); + + server.start(); + } + + @After + public void tearDown() throws Exception { + server.stop(); + } + + @Test + public void test() { + /** + * Now try basic client commands + */ + Connection connection; + Message request, response; + + connection = new SSLSocketConnection(); + ((SSLSocketConnection) connection).setServerCertificateCA(ssc.certificate()); + ((SocketConnection) connection).setHost("localhost"); + ((SocketConnection) connection).setPort(12345); + ((SocketConnection) connection).setConnectionTimeout(30000); + ((SocketConnection) connection).setIdleTimeout(30000); + ((SocketConnection) connection).setRetryAttempts(2); + ((SocketConnection) connection).setRetryWait(500); + + try { + connection.connect(); + } catch (Exception e1) { + fail("Connection failed: " + e1.getMessage()); + return; + } + + /** + * It is necessary to send a SC Status with protocol version 2.0 else + * server will assume 1.0) + */ + + request = new SCStatus(); + ((SCStatus) request).setProtocolVersion(ProtocolVersion.VERSION_2_00); + + try { + response = connection.send(request); + } catch (RetriesExceeded e) { + fail("Retries exceeded: " + e.getMessage()); + return; + } catch (MessageNotUnderstood e) { + fail("Message not understood: " + e.getMessage()); + return; + } catch (ChecksumError e) { + fail("Checksum error: " + e.getMessage()); + return; + } catch (SequenceError e) { + fail("Sequence error: " + e.getMessage()); + return; + } catch (MandatoryFieldOmitted e) { + fail("Mandatory Field Omitted: " + e.getMessage()); + return; + } catch (InvalidFieldLength e) { + fail("Invalid field length: " + e.getMessage()); + return; + } + + if (!(response instanceof ACSStatus)) { + fail("Status Request did not return valid response from server."); + return; + } + + + /** + * Check if the server can support checkout + */ + if (!((ACSStatus) response).getSupportedMessages().isSet(SupportedMessages.CHECK_OUT)) { + fail("Check out not supported"); + return; + } + + request = new CheckOut(); + + /** + * The code below would be the normal way of creating the request + */ + + ((CheckOut) request).setPatronIdentifier("2000000"); + ((CheckOut) request).setItemIdentifier("300000000"); + ((CheckOut) request).setSCRenewalPolicy(Boolean.TRUE); + ((CheckOut) request).setTransactionDate(new Date()); + + try { + response = connection.send(request); + } catch (RetriesExceeded e) { + fail("Retries exceeded: " + e.getMessage()); + return; + } catch (MessageNotUnderstood e) { + fail("Message not understood: " + e.getMessage()); + return; + } catch (ChecksumError e) { + fail("Checksum error: " + e.getMessage()); + return; + } catch (SequenceError e) { + fail("Sequence error: " + e.getMessage()); + return; + } catch (MandatoryFieldOmitted e) { + fail("Mandatory Field Omitted: " + e.getMessage()); + return; + } catch (InvalidFieldLength e) { + fail("Invalid field length: " + e.getMessage()); + return; + } + + if (!(response instanceof CheckOutResponse)) { + fail("Error - CheckOut Request did not return valid response from server"); + return; + } + + try { + String testCase = response.encode('1'); + assert(testCase.startsWith("120NUN") && testCase.contains("AA|AB|AH|AJ|AO|AY1AZ")); // strip out components which may change (transaction date and checksum) + } catch (MessageNotUnderstood e) { + fail("Message not understood: " + e.getMessage()); + } catch (MandatoryFieldOmitted e) { + fail("Mandatory Field Omitted: " + e.getMessage()); + } catch (InvalidFieldLength e) { + fail("Invalid field length: " + e.getMessage()); + } + } +} + + + + + + diff --git a/src/main/java/com/ceridwen/circulation/SIP/transport/TestSocketTransport.java b/src/main/java/com/ceridwen/circulation/SIP/transport/TestSocketTransport.java index d25b4ee..3534f6e 100644 --- a/src/main/java/com/ceridwen/circulation/SIP/transport/TestSocketTransport.java +++ b/src/main/java/com/ceridwen/circulation/SIP/transport/TestSocketTransport.java @@ -4,14 +4,11 @@ import java.util.Date; -import junit.framework.Assert; - import org.junit.After; import org.junit.Before; import org.junit.Test; import com.ceridwen.circulation.SIP.exceptions.ChecksumError; -import com.ceridwen.circulation.SIP.exceptions.ConnectionFailure; import com.ceridwen.circulation.SIP.exceptions.InvalidFieldLength; import com.ceridwen.circulation.SIP.exceptions.MandatoryFieldOmitted; import com.ceridwen.circulation.SIP.exceptions.MessageNotUnderstood; @@ -22,33 +19,25 @@ import com.ceridwen.circulation.SIP.messages.CheckOutResponse; import com.ceridwen.circulation.SIP.messages.Message; import com.ceridwen.circulation.SIP.messages.SCStatus; -import com.ceridwen.circulation.SIP.server.MessageHandlerDummyImpl; -import com.ceridwen.circulation.SIP.server.SocketDaemon; +import com.ceridwen.circulation.SIP.netty.server.SIPDaemon; +import com.ceridwen.circulation.SIP.samples.netty.DummyDriverFactory; import com.ceridwen.circulation.SIP.types.enumerations.ProtocolVersion; import com.ceridwen.circulation.SIP.types.flagfields.SupportedMessages; public class TestSocketTransport { - static SocketDaemon thread; - + SIPDaemon server; @Before public void setUp() throws Exception { - /** - * Run simple socket server - */ + // Run netty server + server = new SIPDaemon("Sample", "localhost", 12345, new DummyDriverFactory(), true); - TestSocketTransport.thread = new SocketDaemon("localhost", 12345, new MessageHandlerDummyImpl()); - TestSocketTransport.thread.setStrictChecksumChecking(true); - TestSocketTransport.thread.start(); + server.start(); } @After public void tearDown() throws Exception { - /** - * Stop simple socket server - */ - - TestSocketTransport.thread.shutdown(); + server.stop(); } @Test @@ -85,30 +74,27 @@ public void test() { try { response = connection.send(request); } catch (RetriesExceeded e) { - Assert.fail("Retries exceeded: " + e.getMessage()); - return; - } catch (ConnectionFailure e) { - Assert.fail("Connection failure: " + e.getMessage()); + fail("Retries exceeded: " + e.getMessage()); return; } catch (MessageNotUnderstood e) { - Assert.fail("Message not understood: " + e.getMessage()); + fail("Message not understood: " + e.getMessage()); return; } catch (ChecksumError e) { - Assert.fail("Checksum error: " + e.getMessage()); + fail("Checksum error: " + e.getMessage()); return; } catch (SequenceError e) { - Assert.fail("Sequence error: " + e.getMessage()); + fail("Sequence error: " + e.getMessage()); return; } catch (MandatoryFieldOmitted e) { - Assert.fail("Mandatory Field Omitted: " + e.getMessage()); + fail("Mandatory Field Omitted: " + e.getMessage()); return; } catch (InvalidFieldLength e) { - Assert.fail("Invalid field length: " + e.getMessage()); + fail("Invalid field length: " + e.getMessage()); return; } if (!(response instanceof ACSStatus)) { - fail("Status Request did not return valid response from server."); + fail("Status Request did not return valid response from server."); return; } @@ -137,9 +123,6 @@ public void test() { } catch (RetriesExceeded e) { fail("Retries exceeded: " + e.getMessage()); return; - } catch (ConnectionFailure e) { - fail("Connection failure: " + e.getMessage()); - return; } catch (MessageNotUnderstood e) { fail("Message not understood: " + e.getMessage()); return; @@ -164,16 +147,13 @@ public void test() { try { String testCase = response.encode('1'); - Assert.assertTrue(testCase.startsWith("120NUN") && testCase.contains("AA|AB|AH|AJ|AO|AY1AZ")); // strip out components which may change (transaction date and checksum) + assert(testCase.startsWith("120NUN") && testCase.contains("AA|AB|AH|AJ|AO|AY1AZ")); // strip out components which may change (transaction date and checksum) } catch (MessageNotUnderstood e) { fail("Message not understood: " + e.getMessage()); - return; - } catch (MandatoryFieldOmitted e) { - fail("Mandatory Field Omitted: " + e.getMessage()); - return; + } catch (MandatoryFieldOmitted e) { + fail("Mandatory Field Omitted: " + e.getMessage()); } catch (InvalidFieldLength e) { fail("Invalid field length: " + e.getMessage()); - return; } } } diff --git a/src/test/java/com/ceridwen/circulation/SIP/performance/PerformanceTest.java b/src/test/java/com/ceridwen/circulation/SIP/performance/PerformanceTest.java new file mode 100644 index 0000000..8f1ac5c --- /dev/null +++ b/src/test/java/com/ceridwen/circulation/SIP/performance/PerformanceTest.java @@ -0,0 +1,246 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package com.ceridwen.circulation.SIP.performance; + +import com.ceridwen.circulation.SIP.exceptions.ChecksumError; +import com.ceridwen.circulation.SIP.exceptions.InvalidFieldLength; +import com.ceridwen.circulation.SIP.exceptions.MandatoryFieldOmitted; +import com.ceridwen.circulation.SIP.exceptions.MessageNotUnderstood; +import com.ceridwen.circulation.SIP.exceptions.RetriesExceeded; +import com.ceridwen.circulation.SIP.exceptions.SequenceError; +import com.ceridwen.circulation.SIP.messages.ACSStatus; +import com.ceridwen.circulation.SIP.messages.BlockPatron; +import com.ceridwen.circulation.SIP.messages.CheckIn; +import com.ceridwen.circulation.SIP.messages.CheckOut; +import com.ceridwen.circulation.SIP.messages.EndPatronSession; +import com.ceridwen.circulation.SIP.messages.FeePaid; +import com.ceridwen.circulation.SIP.messages.Hold; +import com.ceridwen.circulation.SIP.messages.ItemInformation; +import com.ceridwen.circulation.SIP.messages.ItemStatusUpdate; +import com.ceridwen.circulation.SIP.messages.Login; +import com.ceridwen.circulation.SIP.messages.Message; +import com.ceridwen.circulation.SIP.messages.PatronEnable; +import com.ceridwen.circulation.SIP.messages.PatronInformation; +import com.ceridwen.circulation.SIP.messages.PatronStatusRequest; +import com.ceridwen.circulation.SIP.messages.Renew; +import com.ceridwen.circulation.SIP.messages.RenewAll; +import com.ceridwen.circulation.SIP.messages.SCStatus; +import com.ceridwen.circulation.SIP.netty.server.SIPDaemon; +import com.ceridwen.circulation.SIP.samples.netty.DummyDriverFactory; +import com.ceridwen.circulation.SIP.transport.SSLSocketConnection; +import com.ceridwen.circulation.SIP.transport.SocketConnection; +import com.ceridwen.circulation.SIP.types.enumerations.ProtocolVersion; +import io.netty.handler.ssl.util.SelfSignedCertificate; +import java.io.File; +import java.util.Scanner; +import java.util.logging.Level; +import java.util.logging.Logger; + +class ThreadCounter { + private int count; + private int error; + private int retries; + + ThreadCounter() { + this.count = 0; + this.error = 0; + this.retries = 0; + } + + synchronized void Enter() { + count++; + } + + synchronized void Leave() { + count--; + } + + synchronized void Error(Exception ex, String type) { + error++; + this.Leave(); + } + + synchronized void Retried() { + retries++; + this.Leave(); + } + + int getCount() { + return count; + } + + int getError() { + return error; + } + + int getRetries() { + return retries; + } +} + +class ClientRunner extends Thread { + File cert = null; + Message msg; + ThreadCounter counter; + + public ClientRunner(Message msg, File cert, ThreadCounter counter) { + this.cert = cert; + this.msg = msg; + this.counter = counter; + } + + @Override + public void run() { + counter.Enter(); + SocketConnection connection; + + if (cert != null) { + connection = new SSLSocketConnection(); + ((SSLSocketConnection) connection).setServerCertificateCA(cert); + } else { + connection = new SocketConnection(); + } + connection.setHost("localhost"); + connection.setPort(12345); + connection.setConnectionTimeout(30000); + connection.setIdleTimeout(30000); + connection.setRetryAttempts(25); + connection.setRetryWait(500); + + try { + connection.connect(); + } catch (RetriesExceeded ex) { + counter.Retried(); + return; + } catch (Exception ex) { + counter.Error(ex, "CONNECT"); + return; + } + + /** + * It is necessary to send a SC Status with protocol version 2.0 else server + * will assume 1.0) + */ + SCStatus scStatusRequest = new SCStatus(); + scStatusRequest.setProtocolVersion(ProtocolVersion.VERSION_2_00); + + Message scStatusResponse; + + try { + scStatusResponse = connection.send(scStatusRequest); + } catch (RetriesExceeded ex) { + counter.Retried(); + return; + } catch (MessageNotUnderstood | ChecksumError | SequenceError | MandatoryFieldOmitted | InvalidFieldLength ex1) { + counter.Error(ex1, "STATUS"); + return; + } + + if (!(scStatusResponse instanceof ACSStatus)) { + counter.Error(new Exception("Invalid Status Response"), "STATUSCHECK"); + return; + } + + Message response; + + try { + response = connection.send(msg); + } catch (RetriesExceeded ex) { + counter.Retried(); + return; + } catch (MessageNotUnderstood | ChecksumError | SequenceError | MandatoryFieldOmitted | InvalidFieldLength ex) { + counter.Error(ex, "MESSAGE"); + return; + } + + connection.disconnect(); + counter.Leave(); + } + + + + +} + + +/** + * + * @author Matthew.Dovey + */ +public class PerformanceTest { + private static final boolean SSL = true; + private static SelfSignedCertificate ssc; + + public static void main(String[] args) { + try { + System.setProperty("com.ceridwen.circulation.SIP.charset", "ISO8859_1"); + + SIPDaemon server; + + // Run netty server + if (SSL) { + ssc = new SelfSignedCertificate(); + server = new SIPDaemon("Sample", "localhost", 12345, ssc.certificate(), ssc.privateKey(), new DummyDriverFactory(), true); + } else { + server = new SIPDaemon("Sample", "localhost", 12345, new DummyDriverFactory(), true); + } + server.start(); + + Scanner sc = new Scanner(System.in); + + while (true) { + System.gc(); + + System.out.println("***READY"); + + boolean pause = true; + while (sc.hasNextLine() && pause) { + if (sc.next().startsWith("go")) { + pause = false; + } + } + + System.out.println("***STARTED"); + + ThreadCounter count = new ThreadCounter(); + + for (int i = 0; i <2000; i++) { + System.out.println("***CLIENT TEST INTERATION: " + i + " - ACTIVE THREADS: " + count.getCount()); + new ClientRunner(new BlockPatron(), ssc == null?null:ssc.certificate(), count).start(); + new ClientRunner(new CheckIn(), ssc == null?null:ssc.certificate(), count).start(); + new ClientRunner(new CheckOut(), ssc == null?null:ssc.certificate(), count).start(); + new ClientRunner(new EndPatronSession(), ssc == null?null:ssc.certificate(), count).start(); + new ClientRunner(new FeePaid(), ssc == null?null:ssc.certificate(), count).start(); + new ClientRunner(new Hold(), ssc == null?null:ssc.certificate(), count).start(); + new ClientRunner(new ItemInformation(), ssc == null?null:ssc.certificate(), count).start(); + new ClientRunner(new ItemStatusUpdate(), ssc == null?null:ssc.certificate(), count).start(); + new ClientRunner(new Login(), ssc == null?null:ssc.certificate(), count).start(); + new ClientRunner(new PatronEnable(), ssc == null?null:ssc.certificate(), count).start(); + new ClientRunner(new PatronInformation(), ssc == null?null:ssc.certificate(), count).start(); + new ClientRunner(new PatronStatusRequest(), ssc == null?null:ssc.certificate(), count).start(); + new ClientRunner(new Renew(), ssc == null?null:ssc.certificate(), count).start(); + new ClientRunner(new RenewAll(), ssc == null?null:ssc.certificate(), count).start(); + } + + System.out.println("***STOPPING"); + while (count.getCount() > 0) { + System.out.println("***ACTIVE THREADS: " + count.getCount()); + Thread.sleep(1); + } + System.out.println("***STOPPED"); + System.out.println("*** THREADS ERRORED: " + count.getError()); + System.out.println("*** THREADS RETRIED: " + count.getRetries()); + } + +// server.stop(); + + + } catch (Exception ex) { + Logger.getLogger(PerformanceTest.class.getName()).log(Level.SEVERE, null, ex); + } + } + +}