/* * To change this template, choose Tools | Templates * and open the template in the editor. */ package com.eas.client.threetier.platypus; import com.eas.client.login.AnonymousPlatypusPrincipal; import com.eas.client.login.Credentials; import com.eas.client.login.PlatypusPrincipal; import com.eas.client.threetier.PlatypusConnection; import com.eas.client.threetier.Request; import com.eas.client.threetier.Response; import com.eas.client.threetier.requests.AccessControlExceptionResponse; import com.eas.client.threetier.requests.ExceptionResponse; import com.eas.client.threetier.requests.LogoutRequest; import com.eas.concurrent.PlatypusThreadFactory; import com.eas.script.Scripts; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.InetSocketAddress; import java.net.MalformedURLException; import java.net.Socket; import java.net.URL; import java.net.URLConnection; import java.net.URLStreamHandler; import java.util.Queue; import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.Executor; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; import java.util.logging.Level; import java.util.logging.Logger; import javax.net.ssl.SSLContext; import org.apache.mina.core.buffer.IoBuffer; import org.apache.mina.core.filterchain.IoFilter; import org.apache.mina.core.future.ConnectFuture; import org.apache.mina.core.future.IoFuture; import org.apache.mina.core.future.WriteFuture; import org.apache.mina.core.service.IoHandlerAdapter; import org.apache.mina.core.session.DummySession; import org.apache.mina.core.session.IdleStatus; import org.apache.mina.core.session.IoSession; import org.apache.mina.filter.codec.ProtocolCodecFilter; import org.apache.mina.filter.codec.ProtocolDecoderOutput; import org.apache.mina.filter.codec.ProtocolEncoderOutput; import org.apache.mina.filter.executor.ExecutorFilter; import org.apache.mina.transport.socket.SocketConnector; import org.apache.mina.transport.socket.nio.NioProcessor; import org.apache.mina.transport.socket.nio.NioSocketConnector; /** * * @author pk, mg refactoring */ public class PlatypusPlatypusConnection extends PlatypusConnection { private final String host; private final int port; private final Queue<IoSession> sessions = new ConcurrentLinkedQueue<>(); private final Queue<RequestEnvelope> pending = new ConcurrentLinkedQueue<>(); private String sessionTicket; private final SocketConnector connector; private final Executor networkProcessor; private final SSLContext sslContext; private final int maximumConnections; // sync requests private final RequestEncoder syncEncoder; private final ResponseDecoder syncDecoder; // WARNING!!! syncSocket is intended only for single threaded using private final Socket syncSocket; public PlatypusPlatypusConnection(URL aUrl, Callable<Credentials> aOnCredentials, int aMaximumAuthenticateAttempts, Executor aNetworkProcessor, int aMaximumConnections, boolean aInteractive) throws Exception { super(aUrl, null, aOnCredentials, aMaximumAuthenticateAttempts); host = aUrl.getHost(); port = aUrl.getPort(); networkProcessor = aNetworkProcessor; sslContext = createSSLContext(); maximumConnections = Math.max(1, aMaximumConnections); connector = configureConnector(networkProcessor, sslContext, aInteractive); //syncSocket = sslContext.getSocketFactory().createSocket(); commented out until MINA Sslfilter bugs will be fixed syncSocket = new Socket(); syncEncoder = new RequestEncoder(); syncDecoder = new ResponseDecoder(); } private NioSocketConnector configureConnector(Executor aProcessor, SSLContext sslContext, boolean aInteractive) throws Exception { ThreadPoolExecutor ioProcessorExecutor = new ThreadPoolExecutor(1, 1, 3L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(), new PlatypusThreadFactory("polling-", false)); ioProcessorExecutor.allowCoreThreadTimeOut(true); NioSocketConnector lconnector = new NioSocketConnector(aProcessor, new NioProcessor(ioProcessorExecutor)); lconnector.setDefaultRemoteAddress(new InetSocketAddress(host, port)); //SslFilter sslFilter = new SslFilter(sslContext); commented out until MINA Sslfilter bugs will be fixed //sslFilter.setUseClientMode(true); commented out until MINA Sslfilter bugs will be fixed /** * The sslFilter("encryption") filter should be placed first, according * to MINA architecture, but we have to communicate with a user about * untrusted certificates. All of this should be done in AWT Event * thread and so, we have to place ssqlFilter's work to AWT event * thread. It it would be placed in NioProcessor thread, than we will * get a deadlock. - AWT: write with ssl error -> handshake start and * wait for result from first network communication. - NioProcessor: * perform the handshake and ask a user about untrusted certificate and * wait for AWT dialog processing. */ if (aInteractive) { lconnector.getFilterChain().addLast("executor", new ExecutorFilter(aProcessor)); //lconnector.getFilterChain().addLast("encryption", sslFilter); commented out until MINA Sslfilter bugs will be fixed } else { //lconnector.getFilterChain().addLast("encryption", sslFilter); commented out until MINA Sslfilter bugs will be fixed lconnector.getFilterChain().addLast("executor", new ExecutorFilter(aProcessor)); } lconnector.getFilterChain().addLast("platypusCodec", new ProtocolCodecFilter(new RequestEncoder(), new ResponseDecoder())); lconnector.setHandler(new IoHandlerAdapter() { @Override public void messageReceived(IoSession session, Object aResponse) throws Exception { RequestEnvelope requestEnv = (RequestEnvelope) session.getAttribute(RequestEnvelope.class.getSimpleName()); sessionTicket = requestEnv.ticket; session.removeAttribute(RequestEnvelope.class.getSimpleName()); pendingChanged(session, null); // Report about a response if (requestEnv.onComplete != null) { requestEnv.onComplete.accept((Response) aResponse); } } @Override public void messageSent(IoSession session, Object message) throws Exception { super.messageSent(session, message); } @Override public void sessionIdle(IoSession session, IdleStatus status) throws Exception { super.sessionIdle(session, status); } @Override public void sessionClosed(IoSession session) throws Exception { super.sessionClosed(session); //To change body of generated methods, choose Tools | Templates. } @Override public void exceptionCaught(IoSession session, Throwable cause) throws Exception { Logger.getLogger(PlatypusPlatypusConnection.class.getName()).log(Level.SEVERE, cause.toString()); session.close(true); } }); return lconnector; } @Override public URL getUrl() { try { return new URL("platypus", host, port, "", new URLStreamHandler() { @Override protected URLConnection openConnection(URL u) throws IOException { throw new UnsupportedOperationException("Not supported yet."); } }); } catch (MalformedURLException ex) { Logger.getLogger(PlatypusPlatypusConnection.class.getName()).log(Level.SEVERE, null, ex); return null; } } // Zombie counters... protected volatile int pendingSize; protected volatile int sessionsSize; protected AtomicInteger requestsVersion = new AtomicInteger(); protected void pendingChanged(IoSession aNewSession, RequestEnvelope aRequest) { networkProcessor.execute(() -> { IoSession _newSession = aNewSession; RequestEnvelope _request = aRequest; int rVersion; int rNewVersion; do { rVersion = requestsVersion.get(); rNewVersion = rVersion + 1; if (rNewVersion == Integer.MAX_VALUE) { rNewVersion = 0; } // Some social payload if (_newSession != null) { //if (pendingSize >= sessionsSize) { sessions.offer(_newSession); sessionsSize++; //} _newSession = null; } if (_request != null) { pending.offer(_request); _request = null; pendingSize++; } // end of social payload IoSession session = sessions.poll(); RequestEnvelope request = pending.poll(); if (session != null && request != null) { session.setAttribute(RequestEnvelope.class.getSimpleName(), request); session.write(request); pendingSize--; } else { if (session != null) { sessions.offer(session); } if (request != null) { pending.offer(request); } if (request != null && session == null && sessionsSize < maximumConnections) { ConnectFuture onConnect = connector.connect(); onConnect.addListener((IoFuture future) -> { IoSession created = future.getSession(); pendingChanged(created, null); }); } } } while (!requestsVersion.compareAndSet(rVersion, rNewVersion)); }); } @Override public <R extends Response> void enqueueRequest(Request aRequest, Scripts.Space aSpace, Consumer<R> onSuccess, Consumer<Exception> onFailure) { if (Scripts.getContext() != null) { Scripts.getContext().incAsyncsCount(); } Attempts attemps = new Attempts(); Consumer<Response> responseHandler = (Response response) -> { if (response instanceof ExceptionResponse) { if (onFailure != null) { Exception cause = handleErrorResponse((ExceptionResponse) response, aSpace); onFailure.accept(cause); } } else if (onSuccess != null) { if (aRequest instanceof LogoutRequest) { credentials = null; sessionTicket = null; } onSuccess.accept((R) response); } }; retry(aRequest, aSpace, responseHandler, attemps); } private <R extends Response> void retry(Request aRequest, Scripts.Space aSpace, Consumer<Response> responseHandler, Attempts attemps) { Credentials sentCreds = credentials; Scripts.LocalContext context = Scripts.getContext(); RequestEnvelope requestEnv = new RequestEnvelope(aRequest, credentials != null ? credentials.userName : null, credentials != null ? credentials.password : null, sessionTicket, (Response response) -> { Scripts.setContext(context); try { aSpace.process(() -> { try { if (response instanceof AccessControlExceptionResponse && ((AccessControlExceptionResponse) response).isNotLoggedIn()) { if (attemps.count++ < maximumAuthenticateAttempts) { if (credentials != null && !credentials.equals(sentCreds)) { retry(aRequest, aSpace, responseHandler, attemps); } else { Credentials creds = onCredentials.call(); if (creds != null) { credentials = creds; sessionTicket = null; retry(aRequest, aSpace, responseHandler, attemps); } else { // Credentials are inaccessible, so leave things as is... responseHandler.accept(response); } } } else {// Maximum authentication attempts per request exceeded, so leave things as is... responseHandler.accept(response); } } else { PlatypusPrincipal.setClientSpacePrincipal(credentials != null ? new PlatypusPrincipal(credentials.userName, null, null, this) : new AnonymousPlatypusPrincipal()); responseHandler.accept(response); } } catch (Exception ex) { Logger.getLogger(PlatypusPlatypusConnection.class.getName()).log(Level.SEVERE, null, ex); } }); } finally { Scripts.setContext(null); } }); pendingChanged(null, requestEnv); } protected static class SyncProtocolEncoderOutput implements ProtocolEncoderOutput { protected Object filtered; public SyncProtocolEncoderOutput() { } public Object getFiltered() { return filtered; } @Override public void write(Object encodedMessage) { filtered = encodedMessage; } @Override public void mergeAll() { } @Override public WriteFuture flush() { return null; } }; protected static class SyncProtocolDecoderOutput implements ProtocolDecoderOutput { protected Object filtered; public SyncProtocolDecoderOutput() { } public Object getFiltered() { return filtered; } @Override public void write(Object decodedMessage) { filtered = decodedMessage; } @Override public void flush(IoFilter.NextFilter nextFilter, IoSession session) { } }; @Override public <R extends Response> R executeRequest(Request aRequest) throws Exception { R response = retrySync(aRequest); int authenticateAttempts = 0; while (response instanceof AccessControlExceptionResponse && ((AccessControlExceptionResponse) response).isNotLoggedIn() && authenticateAttempts++ < maximumAuthenticateAttempts && onCredentials != null) { Credentials creds = onCredentials.call(); if (creds != null) { credentials = creds; sessionTicket = null; } response = retrySync(aRequest); } if (response instanceof ExceptionResponse) { throw handleErrorResponse((ExceptionResponse) response, Scripts.getSpace()); } else { if (aRequest instanceof LogoutRequest) { credentials = null; sessionTicket = null; } return (R) response; } } private <R extends Response> R retrySync(Request aRequest) throws Exception { RequestEnvelope requestEnv = new RequestEnvelope(aRequest, null, null, sessionTicket, null); Logger.getLogger(PlatypusPlatypusConnection.class.getName()).log(Level.FINE, "{0} is connecting to {1}:{2}.", new Object[]{Thread.currentThread().getName(), host, port}); if (!syncSocket.isConnected()) { syncSocket.connect(new InetSocketAddress(host, port)); } Logger.getLogger(PlatypusPlatypusConnection.class.getName()).log(Level.FINE, "{0} is connected to {1}:{2}.", new Object[]{Thread.currentThread().getName(), host, port}); SyncProtocolEncoderOutput requestOut = new SyncProtocolEncoderOutput(); syncEncoder.encode(null, requestEnv, requestOut); Object oFiltered = requestOut.getFiltered(); OutputStream os = syncSocket.getOutputStream(); IoBuffer toWrite = (IoBuffer) oFiltered; os.write(toWrite.array()); byte[] readBuffer = new byte[1024 * 16]; ByteArrayOutputStream accumulated = new ByteArrayOutputStream(); InputStream is = syncSocket.getInputStream(); int read = 0; while (read > -1) { read = is.read(readBuffer); accumulated.write(readBuffer, 0, read); SyncProtocolDecoderOutput responseOut = new SyncProtocolDecoderOutput(); IoSession session = new DummySession(); session.setAttribute(RequestEnvelope.class.getSimpleName(), requestEnv); if (syncDecoder.doDecode(session, IoBuffer.wrap(accumulated.toByteArray()), responseOut)) { sessionTicket = requestEnv.ticket; return (R) responseOut.getFiltered(); } } throw new Exception("No response was recieved via platypus protocol"); } @Override public void shutdown() { try { if (syncSocket.isConnected()) { syncSocket.close(); } } catch (IOException ex) { Logger.getLogger(PlatypusPlatypusConnection.class.getName()).log(Level.SEVERE, null, ex); } } }