/**
* This file is part of Waarp Project.
*
* Copyright 2009, Frederic Bregier, and individual contributors by the @author tags. See the
* COPYRIGHT.txt in the distribution for a full listing of individual contributors.
*
* All Waarp Project is free software: you can redistribute it and/or modify it under the terms of
* the GNU General Public License as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version.
*
* Waarp 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 General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Waarp. If not, see
* <http://www.gnu.org/licenses/>.
*/
package org.waarp.openr66.protocol.localhandler;
import java.net.SocketAddress;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.TimeUnit;
import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.channel.local.LocalAddress;
import io.netty.channel.local.LocalChannel;
import io.netty.channel.local.LocalServerChannel;
import org.waarp.common.logging.WaarpLogger;
import org.waarp.common.logging.WaarpLoggerFactory;
import org.waarp.openr66.context.ErrorCode;
import org.waarp.openr66.context.R66FiniteDualStates;
import org.waarp.openr66.context.R66Result;
import org.waarp.openr66.context.R66Session;
import org.waarp.openr66.context.task.exception.OpenR66RunnerErrorException;
import org.waarp.openr66.database.data.DbTaskRunner;
import org.waarp.openr66.protocol.configuration.Configuration;
import org.waarp.openr66.protocol.exception.OpenR66ProtocolNoConnectionException;
import org.waarp.openr66.protocol.exception.OpenR66ProtocolPacketException;
import org.waarp.openr66.protocol.exception.OpenR66ProtocolRemoteShutdownException;
import org.waarp.openr66.protocol.exception.OpenR66ProtocolShutdownException;
import org.waarp.openr66.protocol.exception.OpenR66ProtocolSystemException;
import org.waarp.openr66.protocol.localhandler.packet.ConnectionErrorPacket;
import org.waarp.openr66.protocol.localhandler.packet.LocalPacketFactory;
import org.waarp.openr66.protocol.localhandler.packet.StartupPacket;
import org.waarp.openr66.protocol.localhandler.packet.ValidPacket;
import org.waarp.openr66.protocol.networkhandler.NetworkChannelReference;
import org.waarp.openr66.protocol.networkhandler.NetworkServerHandler;
import org.waarp.openr66.protocol.networkhandler.NetworkTransaction;
import org.waarp.openr66.protocol.networkhandler.packet.NetworkPacket;
import org.waarp.openr66.protocol.utils.R66Future;
import org.waarp.openr66.protocol.utils.R66ShutdownHook;
/**
* This class handles Local Transaction connections
*
* @author frederic bregier
*/
public class LocalTransaction {
/**
* Internal Logger
*/
private static final WaarpLogger logger = WaarpLoggerFactory.getLogger(LocalTransaction.class);
/**
* HashMap of LocalChannelReference using LocalChannelId
*/
private final ConcurrentHashMap<Integer, LocalChannelReference> localChannelHashMap = new ConcurrentHashMap<Integer, LocalChannelReference>();
/**
* HashMap of LocalChannelReference using requested_requester_specialId
*/
private final ConcurrentHashMap<String, LocalChannelReference> localChannelHashMapIdBased = new ConcurrentHashMap<String, LocalChannelReference>();
private final ServerBootstrap serverBootstrap = new ServerBootstrap();
private final Channel serverChannel;
private final LocalAddress socketLocalServerAddress = new LocalAddress("0");
private final Bootstrap clientBootstrap = new Bootstrap();
private final ChannelGroup localChannelGroup = new DefaultChannelGroup("LocalChannels", Configuration.configuration
.getSubTaskGroup().next());
/**
* Constructor
*/
public LocalTransaction() {
serverBootstrap.channel(LocalServerChannel.class);
serverBootstrap.group(Configuration.configuration.getLocalBossGroup(),
Configuration.configuration.getLocalWorkerGroup());
serverBootstrap.option(ChannelOption.TCP_NODELAY, true);
serverBootstrap.option(ChannelOption.SO_REUSEADDR, true);
serverBootstrap.childOption(ChannelOption.CONNECT_TIMEOUT_MILLIS, (int) Configuration.configuration.getTIMEOUTCON());
serverBootstrap.childHandler(new LocalServerInitializer());
try {
serverChannel = serverBootstrap.bind(socketLocalServerAddress).sync().channel();
} catch (InterruptedException e) {
throw new IllegalArgumentException(e.getMessage(), e);
}
localChannelGroup.add(serverChannel);
clientBootstrap.channel(LocalChannel.class);
// Same Group than Network final handler
clientBootstrap.group(Configuration.configuration.getLocalWorkerGroup());
clientBootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, (int) Configuration.configuration.getTIMEOUTCON());
clientBootstrap.handler(new LocalClientInitializer());
}
public String hashStatus() {
return "LocalTransaction: [localChannelHashMap: " + localChannelHashMap.size()
+ " localChannelHashMapIdBased: " + localChannelHashMapIdBased.size() + "] ";
}
/**
* Get the corresponding LocalChannelReference and set the remoteId if different
*
* @param remoteId
* @param localId
* @return the LocalChannelReference
* @throws OpenR66ProtocolSystemException
*/
public LocalChannelReference getClient(Integer remoteId, Integer localId)
throws OpenR66ProtocolSystemException {
LocalChannelReference localChannelReference = getFromId(localId);
if (localChannelReference != null) {
if (localChannelReference.getRemoteId().compareTo(remoteId) != 0) {
localChannelReference.setRemoteId(remoteId);
}
return localChannelReference;
}
throw new OpenR66ProtocolSystemException(
"Cannot find LocalChannelReference");
}
private static class SendLater extends Thread {
private static Map<Integer, SendLater> sendLaters = new ConcurrentHashMap<Integer, SendLater>();
final LocalTransaction lt;
final Channel networkChannel;
final SocketAddress remoteAddress;
Integer remoteId;
final Integer localId;
final Queue<NetworkPacket> packets = new ConcurrentLinkedQueue<NetworkPacket>();
int step = 0;
private SendLater(LocalTransaction lt, Channel nc, Integer remoteId, Integer localId) {
this.lt = lt;
this.networkChannel = nc;
this.remoteId = remoteId;
this.localId = localId;
remoteAddress = networkChannel.remoteAddress();
}
private void add(NetworkPacket packet) {
if (packets.isEmpty()) {
packets.add(packet);
Configuration.configuration.launchInFixedDelay(this, Configuration.WAITFORNETOP, TimeUnit.MILLISECONDS);
}
packets.add(packet);
}
public void run() {
synchronized (sendLaters) {
LocalChannelReference localChannelReference = lt.localChannelHashMap.get(localId);
if (localChannelReference != null) {
if (localChannelReference.getRemoteId().compareTo(remoteId) != 0) {
localChannelReference.setRemoteId(remoteId);
}
NetworkPacket networkPacket = packets.poll();
LocalChannel localChannel = localChannelReference.getLocalChannel();
while (networkPacket != null) {
localChannel.write(networkPacket.getBuffer());
networkPacket = packets.poll();
}
localChannel.flush();
sendLaters.remove(localId);
} else {
step ++;
if (step > 10000) {
if (NetworkTransaction.isShuttingdownNetworkChannel(remoteAddress)
|| R66ShutdownHook.isShutdownStarting()) {
// ignore
sendLaters.remove(localId);
packets.clear();
return;
}
logger.warn("Cannot get LocalChannel: due to LocalId not found: " + localId);
final ConnectionErrorPacket error = new ConnectionErrorPacket(
"Cannot get localChannel since localId is not found anymore", "" + localId);
NetworkServerHandler.writeError(networkChannel, remoteId, localId, error);
sendLaters.remove(localId);
packets.clear();
} else {
Configuration.configuration.launchInFixedDelay(this, Configuration.WAITFORNETOP, TimeUnit.MILLISECONDS);
}
}
}
}
}
/**
* Get the corresponding LocalChannelReference and set the remoteId if different
*
* @param remoteId
* @param localId
* @throws OpenR66ProtocolSystemException
*/
public void sendLaterToClient(Channel networkChannel, Integer remoteId, Integer localId, NetworkPacket packet) {
synchronized (SendLater.sendLaters) {
SendLater sendLater = SendLater.sendLaters.get(localId);
if (sendLater == null) {
sendLater = new SendLater(this, networkChannel, remoteId, localId);
SendLater.sendLaters.put(localId, sendLater);
sendLater.setDaemon(true);
}
sendLater.remoteId = remoteId;
sendLater.add(packet);
}
}
/**
* Create a new Client
*
* @param networkChannelReference
* @param remoteId
* might be set to ChannelUtils.NOCHANNEL (real creation)
* @param futureRequest
* might be null (from NetworkChannel Startup)
* @return the LocalChannelReference
* @throws OpenR66ProtocolSystemException
* @throws OpenR66ProtocolRemoteShutdownException
* @throws OpenR66ProtocolNoConnectionException
*/
public LocalChannelReference createNewClient(NetworkChannelReference networkChannelReference,
Integer remoteId, R66Future futureRequest)
throws OpenR66ProtocolSystemException, OpenR66ProtocolRemoteShutdownException,
OpenR66ProtocolNoConnectionException {
ChannelFuture channelFuture = null;
logger.debug("Status LocalChannelServer: {} {}", serverChannel
.getClass().getName(), serverChannel.config()
.getConnectTimeoutMillis() + " " + serverChannel.isOpen());
for (int i = 0; i < Configuration.RETRYNB; i++) {
if (R66ShutdownHook.isShutdownStarting()) {
// Do not try since already locally in shutdown
throw new OpenR66ProtocolNoConnectionException(
"Cannot connect to local handler: " + socketLocalServerAddress +
" " + serverChannel.isOpen() + " " + serverChannel +
" since the local server is in shutdown.");
}
channelFuture = clientBootstrap.connect(socketLocalServerAddress);
try {
channelFuture.await();
//channelFuture.await(Configuration.configuration.TIMEOUTCON/3);
} catch (InterruptedException e1) {
logger.error("LocalChannelServer Interrupted: " +
serverChannel.getClass().getName() + " " +
serverChannel.config().getConnectTimeoutMillis() +
" " + serverChannel.isOpen());
throw new OpenR66ProtocolSystemException(
"Interruption - Cannot connect to local handler: " +
socketLocalServerAddress + " " +
serverChannel.isOpen() + " " + serverChannel,
e1);
}
if (channelFuture.isSuccess()) {
final LocalChannel channel = (LocalChannel) channelFuture.channel();
localChannelGroup.add(channel);
logger.debug("Will start localChannelReference and eventually generate a new Db Connection if not-thread-safe");
final LocalChannelReference localChannelReference = new LocalChannelReference(
channel, networkChannelReference, remoteId, futureRequest);
localChannelHashMap.put(channel.id().hashCode(), localChannelReference);
logger.debug("Db connection done and Create LocalChannel entry: " + i + " {}",
localChannelReference);
logger.info("Add one localChannel to a Network Channel: " + channel.id());
// Now send first a Startup message
StartupPacket startup = new StartupPacket(localChannelReference.getLocalId());
channel.writeAndFlush(startup);
return localChannelReference;
} else {
logger.error("Can't connect to local server " + i + " (Done: " + channelFuture.isDone() + ")");
}
try {
Thread.sleep(Configuration.RETRYINMS * 10);
} catch (InterruptedException e) {
throw new OpenR66ProtocolSystemException(
"Cannot connect to local handler", e);
}
}
logger.error("LocalChannelServer: " +
serverChannel.getClass().getName() + " " +
serverChannel.config().getConnectTimeoutMillis() + " " +
serverChannel.isOpen());
throw new OpenR66ProtocolSystemException(
"Cannot connect to local handler: " + socketLocalServerAddress +
" " + serverChannel.isOpen() + " " + serverChannel,
channelFuture.cause());
}
/**
*
* @param id
* @return the LocalChannelReference
*/
public LocalChannelReference getFromId(Integer id) {
int maxtry = (int) (Configuration.configuration.getTIMEOUTCON() / Configuration.RETRYINMS) / 2;
maxtry = 100;
for (int i = 0; i < maxtry; i++) {
LocalChannelReference lcr = localChannelHashMap.get(id);
if (lcr == null) {
try {
Thread.sleep(Configuration.RETRYINMS);
Thread.yield();
} catch (InterruptedException e) {
}
} else {
return lcr;
}
}
return localChannelHashMap.get(id);
}
/**
* Remove one local channel
*
* @param localChannelReference
*/
protected void remove(LocalChannelReference localChannelReference) {
logger.debug("DEBUG remove: " + localChannelReference.getLocalId());
localChannelHashMap.remove(localChannelReference.getLocalId());
if (localChannelReference.getRequestId() != null) {
localChannelHashMapIdBased.remove(localChannelReference.getRequestId());
}
}
/**
*
* @param runner
* @param lcr
*/
public void setFromId(DbTaskRunner runner, LocalChannelReference lcr) {
String key = runner.getKey();
lcr.setRequestId(key);
localChannelHashMapIdBased.put(key, lcr);
}
/**
*
* @param key
* as "requested requester specialId"
* @return the LocalChannelReference
*/
public LocalChannelReference getFromRequest(String key) {
return localChannelHashMapIdBased.get(key);
}
/**
*
* @param key as "requested requester specialId"
* @return True if the LocalChannelReference exists
*/
public boolean contained(String key) {
return localChannelHashMapIdBased.containsKey(key);
}
/**
*
* @return the number of active local channels
*/
public int getNumberLocalChannel() {
return localChannelHashMap.size();
}
/**
* Debug function (while shutdown for instance)
*/
public void debugPrintActiveLocalChannels() {
Collection<LocalChannelReference> collection = localChannelHashMap.values();
Iterator<LocalChannelReference> iterator = collection.iterator();
while (iterator.hasNext()) {
LocalChannelReference localChannelReference = iterator.next();
logger.debug("Will close local channel: {}", localChannelReference);
logger.debug(
" Containing: {}",
(localChannelReference.getSession() != null ? localChannelReference
.getSession() : "no session"));
}
}
/**
* Informs all remote client that the server is shutting down
*/
public void shutdownLocalChannels() {
logger.warn("Will inform LocalChannels of Shutdown: " + localChannelHashMap.size());
Collection<LocalChannelReference> collection = localChannelHashMap.values();
Iterator<LocalChannelReference> iterator = collection.iterator();
ValidPacket packet = new ValidPacket("Shutdown forced", null,
LocalPacketFactory.SHUTDOWNPACKET);
ByteBuf buffer = null;
while (iterator.hasNext()) {
LocalChannelReference localChannelReference = iterator.next();
logger.info("Inform Shutdown {}", localChannelReference);
packet.setSmiddle(null);
packet.retain();
// If a transfer is running, save the current rank and inform remote
// host
if (localChannelReference.getSession() != null) {
R66Session session = localChannelReference.getSession();
DbTaskRunner runner = session.getRunner();
if (runner != null && runner.isInTransfer()) {
if (!runner.isSender()) {
int newrank = runner.getRank();
packet.setSmiddle(Integer.toString(newrank));
}
// Save File status
try {
runner.saveStatus();
} catch (OpenR66RunnerErrorException e) {
}
}
if (runner != null && !runner.isFinished()) {
R66Result result = new R66Result(
new OpenR66ProtocolShutdownException(), session,
true, ErrorCode.Shutdown, runner);
result.setOther(packet);
try {
buffer = packet.getLocalPacket(localChannelReference);
} catch (OpenR66ProtocolPacketException e1) {
}
localChannelReference.sessionNewState(R66FiniteDualStates.SHUTDOWN);
NetworkPacket message = new NetworkPacket(
localChannelReference.getLocalId(),
localChannelReference.getRemoteId(),
packet.getType(), buffer);
try {
localChannelReference.getNetworkChannel().writeAndFlush(message)
.await(Configuration.WAITFORNETOP);
} catch (InterruptedException e1) {
}
try {
session.setFinalizeTransfer(false, result);
} catch (OpenR66RunnerErrorException e) {
} catch (OpenR66ProtocolSystemException e) {
}
}
localChannelReference.getLocalChannel().close();
continue;
}
try {
buffer = packet.getLocalPacket(localChannelReference);
} catch (OpenR66ProtocolPacketException e1) {
}
NetworkPacket message = new NetworkPacket(
localChannelReference.getLocalId(),
localChannelReference.getRemoteId(), packet.getType(),
buffer);
localChannelReference.getNetworkChannel().writeAndFlush(message);
}
}
/**
* Close All Local Channels
*/
public void closeAll() {
logger.debug("close All Local Channels");
localChannelGroup.close().awaitUninterruptibly();
}
}