339 lines
13 KiB
C#
339 lines
13 KiB
C#
using com.itac.mes.commonsmt;
|
|
using com.itac.mes.commonsmt.data;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Net.Sockets;
|
|
using System.Reflection;
|
|
using System.Runtime.CompilerServices;
|
|
using System.Runtime.Remoting.Messaging;
|
|
using System.Runtime.Remoting.Proxies;
|
|
using System.Text;
|
|
|
|
namespace com.itac.mes.commonsmt
|
|
{
|
|
class FailoverInvocationHandler : FailoverHostList
|
|
{
|
|
|
|
// Zugriff auf die folgenden Maps muss synchronisiert erfolgen
|
|
static readonly object _locker = new object();
|
|
// In dieser Map sind alle offenen Verbindungen zum DataInterface enthalten
|
|
private Dictionary<String, IhapEventChannel> openClientChannels = new Dictionary<String, IhapEventChannel>();
|
|
// pro Verbindung wird gezaehlt wie viele Connections es schon gab...
|
|
private Dictionary<String, Int32> channelNameCounter = new Dictionary<String, Int32>();
|
|
|
|
public FailoverInvocationHandler(MyEventHandler OnLog)
|
|
: base(OnLog)
|
|
{
|
|
}
|
|
|
|
/**
|
|
* gets a new communication channel with specified name
|
|
*
|
|
* @param channelName
|
|
* the name for the channel
|
|
* @param connectionListener
|
|
* when a connection listener is set (!= null) a thread is established to cyclically check wether the channel is still
|
|
* active or not. If a connection is shutdown by a server (the remote function host) an event at the listener is fired
|
|
* @return the communication channel where mesFunctions are exceuted (remote calls via IHap)
|
|
* @throws IOException
|
|
* when no connection could be established
|
|
*/
|
|
|
|
private IMesServicesChannel getConnection(string channelName, Object s)
|
|
{
|
|
if (getActiveHost() == null)
|
|
{
|
|
log(TraceEventType.Error, "cannot get new connection " + channelName + " because active host is unset");
|
|
return null;
|
|
}
|
|
// for every channel a new connection Every connection has it's own counter
|
|
string channelNameWithNumber = getChannelName(channelName);
|
|
|
|
// Neue Verbindung zum DataInterface aufmachen
|
|
var tcpClient = new TcpClient(getActiveHost().getHostname(), getActiveHost().getPort());
|
|
// eine neue TCp-Client-Verbindung
|
|
var iHapEventChannel = new IhapEventChannel(tcpClient, channelNameWithNumber);
|
|
if (iHapEventChannel == null)
|
|
{
|
|
log(TraceEventType.Error, "iHapEventChannel not created");
|
|
throw new Exception("iHapEventChannel not created");
|
|
}
|
|
|
|
log(TraceEventType.Information, "before adding " + channelNameWithNumber);
|
|
lock (_locker)
|
|
{
|
|
if (openClientChannels.ContainsKey(channelNameWithNumber))
|
|
{
|
|
openClientChannels.Remove(channelNameWithNumber);
|
|
}
|
|
openClientChannels.Add(channelNameWithNumber, iHapEventChannel);
|
|
}
|
|
log(TraceEventType.Information, "" + channelNameWithNumber + " added");
|
|
|
|
iHapEventChannel.channelName = channelNameWithNumber;
|
|
// Listener installieren, um zu reagieren, wenn das Data-Interface die Verbindung abgebrochen hat
|
|
// iHapEventChannel.connectionStateChangeListener += new PropertyChangeListener(iHapEventChannel_connectionStateChangeListener);
|
|
|
|
var remoteMesService = (IMesServicesChannel)iHapEventChannel.GetTransparentProxy();
|
|
if (remoteMesService == null)
|
|
{
|
|
log(TraceEventType.Error, "iHapEventChannel.transparentProxy not created");
|
|
throw new Exception("iHapEventChannel.transparentProxy not created");
|
|
}
|
|
remoteMesService.setChannelName(channelNameWithNumber);
|
|
remoteMesService.startup();
|
|
return remoteMesService;
|
|
}
|
|
|
|
private string getChannelName(string channelName)
|
|
{
|
|
// gab es fuer diesen channelName bereits eine Connection
|
|
string channelNameWithNumber = channelName;
|
|
var counter = 0;
|
|
lock (_locker)
|
|
{
|
|
if (channelNameCounter.ContainsKey(channelName))
|
|
{
|
|
counter = channelNameCounter[channelName];
|
|
counter++;
|
|
if (counter > 100000) { counter = 1; }
|
|
channelNameCounter.Remove(channelName);
|
|
channelNameCounter.Add(channelName, counter);
|
|
}
|
|
else
|
|
{
|
|
channelNameCounter.Add(channelName, counter);
|
|
}
|
|
channelNameWithNumber = channelName + "#" + counter;
|
|
}
|
|
return channelNameWithNumber;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void handleThrowable(Object response, Exception throwable)
|
|
{
|
|
if (!(response is MesResponse))
|
|
{
|
|
return;
|
|
}
|
|
MesResponse mesResponse = (MesResponse)response;
|
|
mesResponse.setTotalResult(MesServices.MES_RESULT_NOT_OK);
|
|
if (throwable is PluginException)
|
|
{
|
|
setResponseValues(mesResponse, ((PluginException)throwable).getResponseDetail());
|
|
}
|
|
else if (throwable is IOException)
|
|
{
|
|
addError(mesResponse.getErrorDetails(),
|
|
getErrorDetail(throwable.Message, ResponseDetail.COMMUNICATION_FAILURE));
|
|
}
|
|
else
|
|
{
|
|
addError(mesResponse.getErrorDetails(),
|
|
getErrorDetail(throwable.Message, ResponseDetail.PROCESSED_WITH_EXCEPTION));
|
|
}
|
|
}
|
|
|
|
private ErrorDetail[] addError(ErrorDetail[] errorDetails, ErrorDetail errorDetail)
|
|
{
|
|
List<ErrorDetail> list = new List<ErrorDetail>();
|
|
foreach (ErrorDetail item in errorDetails)
|
|
{
|
|
list.Add(item);
|
|
}
|
|
list.Add(errorDetail);
|
|
return list.ToArray();
|
|
}
|
|
|
|
// convenient method
|
|
private ErrorDetail getErrorDetail(String detail, int code)
|
|
{
|
|
ErrorDetail errorDetail = new ErrorDetail();
|
|
errorDetail.setCode(code);
|
|
errorDetail.setDetail(detail);
|
|
return errorDetail;
|
|
}
|
|
|
|
/**
|
|
* finalizing means closing communication channel and loggin response Object
|
|
*
|
|
* @param connection
|
|
* the used connection
|
|
*/
|
|
private void finalizeRequest(IMesServicesChannel connection)
|
|
{
|
|
if (connection != null)
|
|
{
|
|
string connectionName = connection.getChannelName();
|
|
if (connectionName != null)
|
|
{
|
|
openClientChannels.Remove(connectionName);
|
|
}
|
|
connection.shutdown();
|
|
}
|
|
}
|
|
|
|
private IMesServicesChannel getConnection(string channelName)
|
|
{
|
|
return getConnection(channelName, null);
|
|
}
|
|
|
|
protected void setResponseValues(MesResponse response, ResponseDetail responseDetail)
|
|
{
|
|
if (responseDetail.getCode() == ResponseDetail.OK)
|
|
{
|
|
response.setTotalResult(MesServices.MES_RESULT_OK);
|
|
return;
|
|
}
|
|
response.setTotalResult(MesServices.MES_RESULT_NOT_OK);
|
|
addError(response.getErrorDetails(), getErrorDetail(responseDetail.getText(), responseDetail.getCode()));
|
|
}
|
|
|
|
public IMessage Invoke(IMessage msg)
|
|
{
|
|
IMethodCallMessage methodMsg = (IMethodCallMessage)msg;
|
|
|
|
MethodInfo methodInfo = typeof(IMesServices).GetMethod(methodMsg.MethodName, (Type[])methodMsg.MethodSignature);
|
|
if (methodInfo == null)
|
|
{
|
|
// TODO: check if this is longer required
|
|
// object ret = failoverInvocationHandler.invoke(this, methodInfo, methodMsg.InArgs);
|
|
}
|
|
|
|
// special handling for a couple of function (mesStart, mesStop, getFailoverHosts..FailoverInvocationHandler.)
|
|
Object response = Activator.CreateInstance(methodInfo.ReturnType);
|
|
|
|
// if no failoverhost available return error
|
|
if (getActiveHost() == null && size() == 0)
|
|
{
|
|
// no host set, no failover available --> fail
|
|
return null;
|
|
}
|
|
|
|
// do a failover for all methods startswith "mes" (operational methods)
|
|
if (methodInfo.Name.StartsWith("mes"))
|
|
{
|
|
IMesServices connection = null;
|
|
try
|
|
{
|
|
|
|
bool callFailed = false;
|
|
do
|
|
{
|
|
try
|
|
{
|
|
callFailed = false;
|
|
log(TraceEventType.Information, "call " + methodInfo.Name + "@" + getActiveHost().getHostname());
|
|
connection = getConnection(methodInfo.Name);
|
|
if (connection == null)
|
|
{
|
|
callFailed = true;
|
|
}
|
|
else
|
|
{
|
|
response = methodInfo.Invoke(connection, methodMsg.InArgs);
|
|
if (response == null)
|
|
{
|
|
callFailed = true;
|
|
}
|
|
else
|
|
{
|
|
|
|
if ((response is MesResponse))
|
|
{
|
|
MesResponse responseObject = (MesResponse)response;
|
|
if (responseObject.getTotalResult() == MesServices.MES_RESULT_NOT_OK)
|
|
{
|
|
// set DetailResult to COMMUNICATION_FAILURE
|
|
// repeat this call to another host
|
|
callFailed = hasCommunicationFailure(responseObject.getErrorDetails());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch (SocketException)
|
|
{
|
|
callFailed = true;
|
|
}
|
|
|
|
if (callFailed)
|
|
{
|
|
if (connection != null)
|
|
{
|
|
// explicit finalize this connection
|
|
connection = null;
|
|
}
|
|
remove(getActiveHost());
|
|
setActiveHost(getNextFailoverHost());
|
|
}
|
|
} while (callFailed && getActiveHost() != null);
|
|
|
|
// if failover did not find any available host return communication error
|
|
if (getActiveHost() == null)
|
|
{
|
|
|
|
if ((response is MesResponse))
|
|
{
|
|
MesResponse responseObject = (MesResponse)response;
|
|
|
|
addError(responseObject.getErrorDetails(),
|
|
getErrorDetail("no failover host active", ResponseDetail.COMMUNICATION_FAILURE));
|
|
|
|
responseObject.setTotalResult(MesServices.MES_RESULT_NOT_OK);
|
|
}
|
|
}
|
|
}
|
|
catch (Exception throwable)
|
|
{
|
|
handleThrowable(response, throwable);
|
|
}
|
|
finally
|
|
{
|
|
if (response is MesFailoverResponse)
|
|
{
|
|
// internally update the list
|
|
setFailover(((MesFailoverResponse)response).getFailoverHosts());
|
|
}
|
|
finalizeRequest((IMesServicesChannel)connection);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// simply call the method, no failover for them
|
|
response = methodInfo.Invoke(this, methodMsg.InArgs);
|
|
}
|
|
|
|
return new ReturnMessage(response, methodMsg.Args, 0, methodMsg.LogicalCallContext, methodMsg);
|
|
}
|
|
|
|
private Boolean hasCommunicationFailure(ErrorDetail[] errorDetails)
|
|
{
|
|
if (errorDetails == null)
|
|
{
|
|
return false;
|
|
}
|
|
foreach (ErrorDetail errDetail in errorDetails)
|
|
{
|
|
if (errDetail.getCode() == ResponseDetail.COMMUNICATION_FAILURE)
|
|
{
|
|
log(TraceEventType.Error, "communication failure on this connection");
|
|
throw new SocketException();
|
|
}
|
|
if (errDetail.getCode() == -10005)
|
|
{
|
|
log(TraceEventType.Error, "communication failure on this connection");
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
}
|