230 lines
9.2 KiB
C#
230 lines
9.2 KiB
C#
using System;
|
|
using System.Configuration;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.Reflection;
|
|
using FileInterface.Scheduler;
|
|
using com.itac.mes.imsapi.client.dotnet;
|
|
using com.itac.mes.imsapi.domain.container;
|
|
|
|
namespace FileinterfaceCronScheduler.IMSApi
|
|
{
|
|
/// <summary>
|
|
/// Initializes IMSApiDotNet, logs in once and exposes customFunction to the scheduler.
|
|
/// Configuration is read from App.config.
|
|
/// </summary>
|
|
public sealed class ImsApiClient : ICustomFunctionInvoker, IDisposable
|
|
{
|
|
private readonly object _sync = new object();
|
|
private readonly object _callSync = new object();
|
|
private IIMSApiDotNet _imsapi;
|
|
private IMSApiSessionContextStruct _sessionContext;
|
|
private bool _initialized;
|
|
private bool _disposed;
|
|
private readonly bool _serializeCalls = String.Equals(ConfigurationManager.AppSettings["IMS.SerializeCalls"], "true", StringComparison.OrdinalIgnoreCase);
|
|
|
|
public bool IsInitialized { get { lock (_sync) return _initialized; } }
|
|
public bool IsLoggedIn { get { lock (_sync) return _sessionContext != null; } }
|
|
public long SessionId { get { lock (_sync) return _sessionContext == null ? 0L : _sessionContext.sessionId; } }
|
|
|
|
public void Connect()
|
|
{
|
|
lock (_sync)
|
|
{
|
|
ThrowIfDisposed();
|
|
EnsureInitializedNoLock();
|
|
EnsureLoggedInNoLock();
|
|
}
|
|
}
|
|
|
|
private void EnsureInitializedNoLock()
|
|
{
|
|
if (_initialized && _imsapi != null)
|
|
return;
|
|
|
|
string appId = Required("IMS.AppID", "AppID");
|
|
string serverUrl = Required("IMS.ServerUrl", "Server_URL");
|
|
string baseDir = AppDomain.CurrentDomain.BaseDirectory;
|
|
string propDir = Get("IMS.PropertyDirectory", baseDir);
|
|
if (!Path.IsPathRooted(propDir))
|
|
propDir = Path.Combine(baseDir, propDir);
|
|
|
|
Directory.CreateDirectory(propDir);
|
|
string ihas = Path.Combine(propDir, "ihas.properties");
|
|
if (!File.Exists(ihas))
|
|
File.WriteAllText(ihas, String.Empty);
|
|
|
|
Trace.TraceInformation("Initializing IMSApiDotNet. AppID={0}, Server={1}, PropDir={2}", appId, serverUrl, propDir);
|
|
|
|
IMSApiDotNet.setProperty("itac.appid", appId);
|
|
IMSApiDotNet.setProperty("itac.artes.clusternodes", serverUrl);
|
|
IMSApiDotNet.setProperty("itac.propdir", propDir);
|
|
|
|
_imsapi = IMSApiDotNet.loadLibrary();
|
|
if (_imsapi == null)
|
|
throw new InvalidOperationException("IMSApiDotNet.loadLibrary() returned null.");
|
|
|
|
string version;
|
|
int versionResult = _imsapi.imsapiGetLibraryVersion(out version);
|
|
if (versionResult == IMSApiDotNetConstants.RES_OK)
|
|
Trace.TraceInformation("IMSApiDotNet library version: " + version);
|
|
else
|
|
Trace.TraceWarning("imsapiGetLibraryVersion returned " + versionResult);
|
|
|
|
int initResult = _imsapi.imsapiInit();
|
|
if (initResult != IMSApiDotNetConstants.RES_OK)
|
|
throw new InvalidOperationException("imsapiInit failed. Result=" + initResult);
|
|
|
|
_initialized = true;
|
|
Trace.TraceInformation("IMSApiDotNet initialized successfully.");
|
|
}
|
|
|
|
private void EnsureLoggedInNoLock()
|
|
{
|
|
if (_sessionContext != null)
|
|
return;
|
|
|
|
string station = Required("IMS.StationNumber", "StationNr");
|
|
// Original FileInterface contains the historic typo 'CleintNO'. Both names are supported.
|
|
string client = FirstNonEmpty(
|
|
ConfigurationManager.AppSettings["IMS.ClientNumber"],
|
|
ConfigurationManager.AppSettings["ClientNO"],
|
|
ConfigurationManager.AppSettings["CleintNO"]);
|
|
if (String.IsNullOrWhiteSpace(client))
|
|
throw new ConfigurationErrorsException("Missing IMS client number. Configure IMS.ClientNumber (or legacy CleintNO).");
|
|
|
|
string registrationType = Get("IMS.RegistrationType", "S");
|
|
var validation = new IMSApiSessionValidationStruct
|
|
{
|
|
stationNumber = station,
|
|
client = client,
|
|
registrationType = registrationType
|
|
};
|
|
|
|
Trace.TraceInformation("Calling regLogin(). Station={0}, Client={1}, RegistrationType={2}", station, client, registrationType);
|
|
|
|
IMSApiSessionContextStruct newSessionContext;
|
|
int loginResult = _imsapi.regLogin(validation, out newSessionContext);
|
|
if (loginResult != IMSApiDotNetConstants.RES_OK || newSessionContext == null)
|
|
throw new InvalidOperationException("IMS regLogin failed. Result=" + loginResult);
|
|
|
|
_sessionContext = newSessionContext;
|
|
Trace.TraceInformation("IMS login successful. SessionId=" + _sessionContext.sessionId);
|
|
}
|
|
|
|
public CustomFunctionCallResult Invoke(string customFunctionName, string[] inArgs)
|
|
{
|
|
if (String.IsNullOrWhiteSpace(customFunctionName))
|
|
return CustomFunctionCallResult.Fail("CustomFunction is empty.");
|
|
|
|
try
|
|
{
|
|
IIMSApiDotNet api;
|
|
IMSApiSessionContextStruct session;
|
|
|
|
// Only connection/login state is protected. The actual CustomFunction call
|
|
// can run concurrently for independent cron jobs.
|
|
lock (_sync)
|
|
{
|
|
ThrowIfDisposed();
|
|
EnsureInitializedNoLock();
|
|
EnsureLoggedInNoLock();
|
|
api = _imsapi;
|
|
session = _sessionContext;
|
|
}
|
|
|
|
if (_serializeCalls)
|
|
{
|
|
lock (_callSync)
|
|
return InvokeCore(api, session, customFunctionName, inArgs);
|
|
}
|
|
|
|
return InvokeCore(api, session, customFunctionName, inArgs);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Trace.TraceError("IMS customFunction failed: " + ex);
|
|
return CustomFunctionCallResult.Fail(ex.ToString());
|
|
}
|
|
}
|
|
|
|
private static CustomFunctionCallResult InvokeCore(
|
|
IIMSApiDotNet api,
|
|
IMSApiSessionContextStruct session,
|
|
string customFunctionName,
|
|
string[] inArgs)
|
|
{
|
|
string[] outArgs;
|
|
string customErrorString;
|
|
int result = api.customFunction(
|
|
session,
|
|
customFunctionName,
|
|
inArgs ?? new string[0],
|
|
out outArgs,
|
|
out customErrorString);
|
|
|
|
string outText = outArgs == null || outArgs.Length == 0
|
|
? String.Empty
|
|
: String.Join(" | ", outArgs);
|
|
|
|
if (result != IMSApiDotNetConstants.RES_OK)
|
|
{
|
|
return CustomFunctionCallResult.Fail(
|
|
"IMS result=" + result +
|
|
(String.IsNullOrWhiteSpace(customErrorString) ? String.Empty : "; Error=" + customErrorString) +
|
|
(String.IsNullOrWhiteSpace(outText) ? String.Empty : "; OutArgs=" + outText));
|
|
}
|
|
|
|
return CustomFunctionCallResult.Ok(
|
|
"IMS result=0" +
|
|
(String.IsNullOrWhiteSpace(customErrorString) ? String.Empty : "; Info=" + customErrorString) +
|
|
(String.IsNullOrWhiteSpace(outText) ? String.Empty : "; OutArgs=" + outText));
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
lock (_sync)
|
|
{
|
|
if (_disposed) return;
|
|
_disposed = true;
|
|
|
|
// The FileInterface source currently has no regLogout/imsapiFinish call.
|
|
// We therefore release managed references here. If the deployed IMSAPI
|
|
// version exposes an explicit logout method, it can safely be added here.
|
|
_sessionContext = null;
|
|
_imsapi = null;
|
|
_initialized = false;
|
|
}
|
|
}
|
|
|
|
private static string Get(string key, string fallback)
|
|
{
|
|
string value = ConfigurationManager.AppSettings[key];
|
|
return String.IsNullOrWhiteSpace(value) ? fallback : value.Trim();
|
|
}
|
|
|
|
private static string Required(string preferredKey, string legacyKey)
|
|
{
|
|
string value = FirstNonEmpty(
|
|
ConfigurationManager.AppSettings[preferredKey],
|
|
ConfigurationManager.AppSettings[legacyKey]);
|
|
if (String.IsNullOrWhiteSpace(value))
|
|
throw new ConfigurationErrorsException("Missing setting '" + preferredKey + "' (legacy: '" + legacyKey + "').");
|
|
return value.Trim();
|
|
}
|
|
|
|
private static string FirstNonEmpty(params string[] values)
|
|
{
|
|
if (values == null) return null;
|
|
foreach (string value in values)
|
|
if (!String.IsNullOrWhiteSpace(value)) return value.Trim();
|
|
return null;
|
|
}
|
|
|
|
private void ThrowIfDisposed()
|
|
{
|
|
if (_disposed) throw new ObjectDisposedException(GetType().FullName);
|
|
}
|
|
}
|
|
}
|