Files
imsInterface/interface/commonsmt/dotnetplugin/Plugin/FailoverInvocationHandler.cs
2025-06-06 09:15:13 +02:00

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;
}
}
}