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 { /// /// Initializes IMSApiDotNet, logs in once and exposes customFunction to the scheduler. /// Configuration is read from App.config. /// 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); } } }