/* * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package com.ok2c.lightmtp.impl.agent; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.channels.SelectionKey; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import org.apache.http.concurrent.BasicFuture; import org.apache.http.concurrent.FutureCallback; import org.apache.http.impl.nio.reactor.ExceptionEvent; import org.apache.http.impl.nio.reactor.IOReactorConfig; import org.apache.http.nio.reactor.IOReactorExceptionHandler; import org.apache.http.nio.reactor.IOReactorStatus; import org.apache.http.nio.reactor.IOSession; import com.ok2c.lightmtp.agent.MailUserAgent; import com.ok2c.lightmtp.agent.SessionEndpoint; import com.ok2c.lightmtp.agent.TransportType; import com.ok2c.lightmtp.impl.pool.LeasedSession; import com.ok2c.lightmtp.impl.pool.MailIOSessionManager; import com.ok2c.lightmtp.impl.protocol.ClientSession; import com.ok2c.lightmtp.impl.protocol.ClientSessionFactory; import com.ok2c.lightmtp.impl.protocol.LocalClientSessionFactory; import com.ok2c.lightmtp.protocol.DeliveryRequest; import com.ok2c.lightmtp.protocol.DeliveryRequestHandler; import com.ok2c.lightmtp.protocol.DeliveryResult; import com.ok2c.lightmtp.protocol.SessionContext; import com.ok2c.lightmtp.protocol.SessionFactory; public class DefaultMailUserAgent implements MailUserAgent { private static final String PENDING_DELIVERY = "com.ok2c.lightmtp.delivery"; private final MailIOSessionManager sessionManager; private final DefaultMailClientTransport transport; private final TransportType type; private final Set<PendingDelivery> pendingDeliveries; private volatile boolean started; private volatile boolean shutdown; private String heloName; private String username; private String password; public DefaultMailUserAgent( final TransportType type, final IOReactorConfig config) throws IOException { super(); this.type = type; this.transport = new DefaultMailClientTransport( null, new InternalIOReactorThreadCallback(), config); this.sessionManager = new MailIOSessionManager(this.transport.getIOReactor()); this.pendingDeliveries = Collections.synchronizedSet(new HashSet<PendingDelivery>()); } @Override public void start() { started = true; DeliveryRequestHandler handler = new InternalDeliveryRequestHandler(); SessionFactory<ClientSession> sessionFactory; switch (this.type) { case SMTP: sessionFactory = new ClientSessionFactory(handler, heloName, username, password); break; case LMTP: sessionFactory = new LocalClientSessionFactory(handler, heloName); break; default: sessionFactory = new ClientSessionFactory(handler); } this.transport.start(sessionFactory); } /** * Set the helo name to use. Must be called before {@link #start()} to take affect */ public void setHeloName(final String heloName) { if (started) throw new IllegalStateException("Can only be set when not started"); this.heloName = heloName; } /** * Set the authentication to use. Must be called before {@link #start()} */ public void setAuthentication(final String username, final String password) { if (started) throw new IllegalStateException("Can only be set when not started"); if ((username == null || password == null) && (username != null || password != null)) { throw new IllegalArgumentException("You need to set username and password to null or none of them"); } this.username = username; this.password = password; } @Override public Future<DeliveryResult> deliver( final SessionEndpoint endpoint, final int connectTimeout, final DeliveryRequest request, final FutureCallback<DeliveryResult> callback) { if (this.shutdown) { throw new IllegalStateException("Mail transport has been shut down"); } BasicFuture<DeliveryResult> future = new BasicFuture<DeliveryResult>(callback); PendingDelivery delivery = new PendingDelivery(request, future); this.pendingDeliveries.add(delivery); this.sessionManager.leaseSession(endpoint, connectTimeout, TimeUnit.MILLISECONDS, new IOSessionReadyCallback(delivery)); return future; } public void setExceptionHandler(final IOReactorExceptionHandler exceptionHandler) { this.transport.setExceptionHandler(exceptionHandler); } @Override public IOReactorStatus getStatus() { return this.transport.getStatus(); } @Override public Exception getException() { return this.transport.getException(); } @Override public List<ExceptionEvent> getAuditLog() { return this.transport.getAuditLog(); } @Override public void shutdown() throws IOException { this.shutdown = true; this.started = false; this.transport.closeActiveSessions(); this.sessionManager.shutdown(); this.transport.shutdown(); } @Override public void forceShutdown() { this.transport.forceShutdown(); } class InternalIOReactorThreadCallback implements IOReactorThreadCallback { private void cancelDeliveries() { synchronized (pendingDeliveries) { for (PendingDelivery delivery: pendingDeliveries) { delivery.getDeliveryFuture().cancel(true); } pendingDeliveries.clear(); } } @Override public void terminated() { shutdown = true; started = false; cancelDeliveries(); } @Override public void terminated(final Exception ex) { shutdown = true; started = false; cancelDeliveries(); } } class IOSessionReadyCallback implements FutureCallback<LeasedSession> { private final PendingDelivery pendingDelivery; public IOSessionReadyCallback(final PendingDelivery pendingDelivery) { super(); this.pendingDelivery = pendingDelivery; } private void deliveryDone() { pendingDeliveries.remove(this.pendingDelivery); } @Override public void completed(final LeasedSession leasedSession) { deliveryDone(); this.pendingDelivery.setLeasedSession(leasedSession); IOSession iosession = leasedSession.getIOSession(); iosession.setAttribute(PENDING_DELIVERY, this.pendingDelivery); iosession.setEvent(SelectionKey.OP_WRITE); } @Override public void failed(final Exception ex) { deliveryDone(); this.pendingDelivery.getDeliveryFuture().failed(ex); } @Override public void cancelled() { deliveryDone(); this.pendingDelivery.getDeliveryFuture().cancel(true); } } class InternalDeliveryRequestHandler implements DeliveryRequestHandler { public InternalDeliveryRequestHandler() { super(); } @Override public void connected(final SessionContext context) { } @Override public void disconnected(final SessionContext context) { PendingDelivery delivery = (PendingDelivery) context.removeAttribute(PENDING_DELIVERY); if (delivery != null) { delivery.getDeliveryFuture().cancel(true); sessionManager.releaseSession(delivery.getLeasedSession()); } } @Override public void exception(final Exception ex, final SessionContext context) { PendingDelivery delivery = (PendingDelivery) context.removeAttribute(PENDING_DELIVERY); if (delivery != null) { delivery.getDeliveryFuture().failed(ex); sessionManager.releaseSession(delivery.getLeasedSession()); } } @Override public void completed( final DeliveryRequest request, final DeliveryResult result, final SessionContext context) { PendingDelivery delivery = (PendingDelivery) context.removeAttribute(PENDING_DELIVERY); if (delivery != null) { delivery.getDeliveryFuture().completed(result); sessionManager.releaseSession(delivery.getLeasedSession()); } } @Override public void failed( final DeliveryRequest request, final DeliveryResult result, final SessionContext context) { completed(request, result, context); } @Override public DeliveryRequest submitRequest(final SessionContext context) { PendingDelivery delivery = (PendingDelivery) context.getAttribute(PENDING_DELIVERY); if (delivery != null) { return delivery.getRequest(); } else { return null; } } } }