using System;
using System.Collections.Generic;
using System.Collections;
using System.Text;
using System.Xml;
using System.IO;
using System.Reflection;
using Windsor.Commons.Core;
using Windsor.Commons.Logging;
using Windsor.Commons.Spring;
using Windsor.Commons.XsdOrm;
using Windsor.Node2008.WNOSPlugin.CERS_12;
using Windsor.Commons.Compression;
using Windsor.Node2008.WNOSProviders.Implementation;
using Windsor.Commons.XsdOrm.Implementations;
using System.Threading;
using Windsor.Commons.NodeDomain;
using Windsor.Commons.NodeClient;
namespace Sleis.EIS4Sleis
{
public class EIS4Sleis
{
#region Public Methods
///
/// Submit EIS data, using the specified input parameters. If data was successfully submitted, a SubmissionResults instance will be
/// returned. If any error occurs, an EIS4SleisException will be thrown.
///
/// NOTE: Set nodeEndpointParameters.Type to NodeType.Default and nodeEndpointParameters.Url to null in order to
/// use the default EPA EIS endpoint.
///
public SubmissionResults SubmitEISData(DatabaseParameters databaseParameters, NodeEndpointParameters nodeEndpointParameters,
SubmissionParameters submissionParameters)
{
SubmissionResults results = new SubmissionResults();
try
{
CERSDataType data = LoadEISDataFromDatabase(databaseParameters, submissionParameters);
results.FilePath = GenerateEISSubmissionFile(data, submissionParameters);
results.TransactionId = SubmitEISFileToNode(results.FilePath, nodeEndpointParameters);
return results;
}
catch (EIS4SleisException)
{
FileUtils.SafeDeleteFile(results.FilePath);
throw;
}
catch (Exception e)
{
FileUtils.SafeDeleteFile(results.FilePath);
throw new EIS4SleisException(ExceptionType.Uncategorized, e, ExceptionUtils.GetDeepExceptionMessage(e), null);
}
}
///
/// Check EIS submission status, using the specified input parameters. If the status was successfully checked, a CheckStatusResults instance will be
/// returned. If any error occurs, an EIS4SleisException will be thrown.
///
/// NOTE: Set nodeEndpointParameters.Type to NodeType.Default and nodeEndpointParameters.Url to null in order to
/// use the default EPA EIS endpoint.
///
public CheckStatusResults CheckEISSubmissionStatus(string transactionId, NodeEndpointParameters nodeEndpointParameters)
{
ExceptionUtils.ThrowIfEmptyString(transactionId, "transactionId");
try
{
using (INodeEndpointClient client = GetAuthenticatedNodeClient(nodeEndpointParameters))
{
// Check status
try
{
CommonTransactionStatusCode statusCode = client.GetStatus(transactionId);
CheckStatusResults results = new CheckStatusResults();
results.Status = EnumUtils.ParseEnum(statusCode.ToString());
return results;
}
catch (Exception getStatusEx)
{
throw new EIS4SleisException(ExceptionType.NodeEndpointException, getStatusEx,
"Failed to get status for transaction id \"{0}\" from node endpoint \"{1}\" with exception \"{2}\"",
transactionId, client.Url, ExceptionUtils.GetDeepExceptionMessage(getStatusEx));
}
}
}
catch (EIS4SleisException)
{
throw;
}
catch (Exception e)
{
throw new EIS4SleisException(ExceptionType.Uncategorized, e, ExceptionUtils.GetDeepExceptionMessage(e), null);
}
}
///
/// Download zipped EIS document(s) that are assciated with a specific transaction, using the specified input parameters. If document(s)
/// are successfully downloaded, a DownloadResults instance will be returned. If any error occurs, an EIS4SleisException will be thrown.
///
/// NOTE: Set nodeEndpointParameters.Type to NodeType.Default and nodeEndpointParameters.Url to null in order to
/// use the default EPA EIS endpoint.
///
public DownloadResults DownloadEISSubmissionDocuments(string transactionId, NodeEndpointParameters nodeEndpointParameters)
{
ExceptionUtils.ThrowIfEmptyString(transactionId, "transactionId");
try
{
string[] documents = null;
using (INodeEndpointClient client = GetAuthenticatedNodeClient(nodeEndpointParameters))
{
// Check status
try
{
documents = client.Download(EIS_FLOW_NAME, transactionId);
}
catch (Exception downloadEx)
{
throw new EIS4SleisException(ExceptionType.NodeEndpointException, downloadEx,
"Failed to download documents for transaction id \"{0}\" from node endpoint \"{1}\" with exception \"{2}\"",
transactionId, client.Url, ExceptionUtils.GetDeepExceptionMessage(downloadEx));
}
}
string zippedFilePath = null;
if (!CollectionUtils.IsNullOrEmpty(documents))
{
zippedFilePath = NewTempFilePath(".zip");
CompressionHelper compressionHelper = new CompressionHelper();
compressionHelper.CompressFiles(zippedFilePath, documents);
}
DownloadResults results = new DownloadResults();
results.FilePath = zippedFilePath;
return results;
}
catch (EIS4SleisException)
{
throw;
}
catch (Exception e)
{
throw new EIS4SleisException(ExceptionType.Uncategorized, e, ExceptionUtils.GetDeepExceptionMessage(e), null);
}
}
#endregion Public Methods
#region Internal Methods
protected virtual SpringBaseDao InitDao(DatabaseParameters databaseParameters)
{
ExceptionUtils.ThrowIfNull(databaseParameters, "databaseParameters");
ExceptionUtils.ThrowIfEmptyString(databaseParameters.ConnectionString, "databaseParameters.ConnectionString");
Spring.Data.Common.IDbProvider dbProvider;
dbProvider = Spring.Data.Common.DbProviderFactory.GetDbProvider(EnumUtils.ToDescription(databaseParameters.Type));
dbProvider.ConnectionString = databaseParameters.ConnectionString;
SpringBaseDao dao = new SpringBaseDao(dbProvider);
return dao;
}
protected virtual CERSDataType LoadEISDataFromDatabase(DatabaseParameters databaseParameters, SubmissionParameters submissionParameters)
{
try
{
ExceptionUtils.ThrowIfNull(submissionParameters, "submissionParameters");
if ((submissionParameters.EmissionsYear < 1950) || (submissionParameters.EmissionsYear > DateTime.Now.Year))
{
throw new ArgException("An invalid submissionParameters.EmissionsYear was specified: {0}", submissionParameters.EmissionsYear.ToString());
}
SpringBaseDao dao = InitDao(databaseParameters);
Dictionary selectClauses = new Dictionary();
selectClauses.Add("CERS_CERS",
new DbAppendSelectWhereClause(dao, "DATA_CATEGORY = ? AND EMIS_YEAR = ?", EnumUtils.ToDescription(submissionParameters.Category),
submissionParameters.EmissionsYear));
IObjectsFromDatabase objectsFromDatabase = new ObjectsFromDatabase();
List dataList = objectsFromDatabase.LoadFromDatabase(dao, selectClauses);
if (CollectionUtils.IsNullOrEmpty(dataList))
{
throw new ArgException("No EIS data was found to submit for submission category \"{0}\" and submission year \"{1}\"",
EnumUtils.ToDescription(submissionParameters.Category), submissionParameters.EmissionsYear.ToString());
}
else if (dataList.Count > 1)
{
throw new ArgException("More than one set of EIS data was found to submit for submission category \"{0}\" and submission year \"{1}\"",
EnumUtils.ToDescription(submissionParameters.Category), submissionParameters.EmissionsYear.ToString());
}
else
{
return dataList[0];
}
}
catch (Exception e)
{
throw new EIS4SleisException(ExceptionType.DatabaseException, e, ExceptionUtils.GetDeepExceptionMessage(e), null);
}
}
protected virtual string GenerateEISSubmissionFile(CERSDataType data, SubmissionParameters submissionParameters)
{
string tempZipFilePath = NewTempFilePath(".zip");
string tempXmlFilePath = NewTempFilePath(".xml");
try
{
ExceptionUtils.ThrowIfNull(data, "data");
ExceptionUtils.ThrowIfNull(submissionParameters, "submissionParameters");
if (!string.IsNullOrEmpty(submissionParameters.AttachmentFolderPath))
{
ExceptionUtils.ThrowIfDirectoryNotFound(submissionParameters.AttachmentFolderPath);
}
// Serialize the xml
SerializationHelper serializationHelper = new SerializationHelper();
serializationHelper.Serialize(data, tempXmlFilePath);
CompressionHelper compressionHelper = new CompressionHelper();
// Validate the xml
string validationErrorsFilePath = ValidateEISXml(tempXmlFilePath);
if (validationErrorsFilePath != null)
{
EIS4SleisException exception = new EIS4SleisException(ExceptionType.ValidationException,
"Failed to validate the EIS xml file that was generated for submission category \"{0}\" and submission year \"{1}\"",
EnumUtils.ToDescription(submissionParameters.Category), submissionParameters.EmissionsYear.ToString());
exception.ValidationErrorsFilePath = validationErrorsFilePath;
compressionHelper.CompressFile(tempXmlFilePath, tempZipFilePath);
exception.GeneratedZippedXmlFilePath = tempZipFilePath;
tempZipFilePath = null;
throw exception;
}
// Add Exchange Header
tempXmlFilePath = AddExchangeHeader(tempXmlFilePath, submissionParameters);
// Finally, compress document and any attachments
compressionHelper.CompressFile(tempXmlFilePath, tempZipFilePath);
data.AddAttachmentsToZipFile(tempZipFilePath, submissionParameters.AttachmentFolderPath, null, compressionHelper);
}
catch (EIS4SleisException)
{
FileUtils.SafeDeleteFile(tempZipFilePath);
throw;
}
catch (Exception e)
{
FileUtils.SafeDeleteFile(tempZipFilePath);
throw new EIS4SleisException(ExceptionType.SerializationException, e, ExceptionUtils.GetDeepExceptionMessage(e), null);
}
finally
{
FileUtils.SafeDeleteFile(tempXmlFilePath);
}
return tempZipFilePath;
}
protected virtual string ValidateEISXml(string xmlFilePath)
{
string xmlSchemaResourceName = "xml_schema.zip";
string xmlSchemaRootFileName = "index.xsd";
string errorsFilePath = NewTempFilePath(".txt");
string xmlSchemaFolder = ExtractZippedResourceToTempFolder(xmlSchemaResourceName);
try
{
string xmlSchemaRootFilePath = Path.Combine(xmlSchemaFolder, xmlSchemaRootFileName);
if (!File.Exists(xmlSchemaRootFilePath))
{
throw new FileNotFoundException(string.Format("The root xml schema file \"{0}\" was not found in the schema validation resource \"{1}\"",
xmlSchemaRootFileName, xmlSchemaResourceName));
}
XmlValidationUtils xmlValidator = new XmlValidationUtils(true);
if (!xmlValidator.Validate(xmlFilePath, xmlSchemaRootFilePath, errorsFilePath))
{
return errorsFilePath;
}
else
{
return null;
}
}
catch (Exception)
{
FileUtils.SafeDeleteDirectory(xmlSchemaFolder);
FileUtils.SafeDeleteFile(errorsFilePath);
throw;
}
}
protected virtual string AddExchangeHeader(string xmlFilePath, SubmissionParameters submissionParameters)
{
ExceptionUtils.ThrowIfNull(submissionParameters, "submissionParameters");
ExceptionUtils.ThrowIfEmptyString(submissionParameters.AuthorName, "submissionParameters.AuthorName");
ExceptionUtils.ThrowIfEmptyString(submissionParameters.OrganizationName, "submissionParameters.OrganizationName");
ExceptionUtils.ThrowIfEmptyString(submissionParameters.SenderContactInfo, "submissionParameters.SenderContactInfo");
ExceptionUtils.ThrowIfFileNotFound(xmlFilePath);
HeaderDocument2Helper headerDocumentHelper = new HeaderDocument2Helper();
headerDocumentHelper.SerializationHelper = new SerializationHelper();
headerDocumentHelper.Configure(submissionParameters.AuthorName, submissionParameters.OrganizationName,
"EIS", EIS_FLOW_NAME, string.Empty, submissionParameters.SenderContactInfo, null);
DataCategory submissionDataCategory = EnumUtils.ParseEnum(EnumUtils.ToDescription(submissionParameters.Category));
headerDocumentHelper.AddPropery(SUBMISSION_TYPE_HEADER_KEY, submissionParameters.IsProductionSubmission ?
PRODUCTION_SUBMISSION_TYPE_NAME : QA_SUBMISSION_TYPE_NAME);
headerDocumentHelper.AddPropery(DATA_CATEGORY_HEADER_KEY,
EnumUtils.ToDescription(submissionDataCategory));
string tempXmlFilePath = NewTempFilePath(".xml");
try
{
XmlDocument doc = new XmlDocument();
doc.Load(xmlFilePath);
headerDocumentHelper.AddPayload(string.Empty, doc.DocumentElement);
headerDocumentHelper.Serialize(tempXmlFilePath);
FileUtils.SafeDeleteFile(xmlFilePath);
}
catch(Exception)
{
FileUtils.SafeDeleteFile(tempXmlFilePath);
throw;
}
return tempXmlFilePath;
}
protected virtual string SubmitEISFileToNode(string filePath, NodeEndpointParameters nodeEndpointParameters)
{
try
{
using (INodeEndpointClient client = GetAuthenticatedNodeClient(nodeEndpointParameters))
{
// Submit EIS document
try
{
string transactionId = client.Submit(EIS_FLOW_NAME, null, new string[] { filePath });
return transactionId;
}
catch (Exception submitEx)
{
throw new EIS4SleisException(ExceptionType.NodeEndpointException, submitEx,
"Failed to submit EIS document to node endpoint \"{0}\" with exception \"{1}\"",
client.Url, ExceptionUtils.GetDeepExceptionMessage(submitEx));
}
}
}
catch (EIS4SleisException)
{
throw;
}
catch (Exception e)
{
throw new EIS4SleisException(ExceptionType.NodeEndpointException, e, ExceptionUtils.GetDeepExceptionMessage(e), null);
}
}
protected virtual string ExtractZippedResourceToTempFolder(string resourceName)
{
string qualifiedResourceName = this.GetType().Namespace + "." + resourceName;
Assembly assembly = this.GetType().Assembly;
string folderName = qualifiedResourceName + "." + AssemblyUtils.GetAssemblyFileVersion(assembly);
folderName = FileUtils.ReplaceInvalidFilenameChars(folderName, '_');
CompressionHelper compressionHelper = new CompressionHelper();
string folderPath = Path.Combine(Path.GetTempPath(), folderName);
using (Mutex mutex = AcquireMutex(folderName, 60))
{
if (Directory.Exists(folderPath) && FileUtils.DirectoryContainsFiles(folderPath))
{
return folderPath;
}
Stream resourceStream = assembly.GetManifestResourceStream(qualifiedResourceName);
if (resourceStream == null)
{
throw new ArgumentException(string.Format("Failed to load zipped resource \"{0}\" from the plugin",
resourceName));
}
using (resourceStream)
{
compressionHelper.UncompressDirectory(resourceStream, folderPath);
}
}
return folderPath;
}
protected virtual Mutex AcquireMutex(string uniqueName, int timeoutInSeconds)
{
bool isAcquired;
Mutex mutex = new Mutex(true, uniqueName, out isAcquired);
if (!isAcquired)
{
isAcquired =
mutex.WaitOne((timeoutInSeconds == Timeout.Infinite) ? timeoutInSeconds : timeoutInSeconds * 1000);
if (!isAcquired)
{
mutex.Close();
throw new TimeoutException(string.Format("The mutex \"{0}\" could not be acquired after waiting for {1} seconds",
uniqueName, timeoutInSeconds.ToString()));
}
}
return mutex;
}
protected virtual string NewTempFilePath(string ext)
{
return Path.Combine(Path.GetTempPath(), String.Format("{0}{1}", Guid.NewGuid(), ext));
}
protected virtual void ParseNodeEndpointParameters(NodeEndpointParameters nodeEndpointParameters, out string nodeUrl,
out EndpointVersionType endpointVersion, out AuthenticationCredentials credentials)
{
ExceptionUtils.ThrowIfNull(nodeEndpointParameters, "nodeEndpointParameters");
ExceptionUtils.ThrowIfEmptyString(nodeEndpointParameters.Username, "nodeEndpointParameters.Username");
ExceptionUtils.ThrowIfEmptyString(nodeEndpointParameters.Password, "nodeEndpointParameters.Password");
NodeType nodeType = nodeEndpointParameters.Type;
nodeUrl = nodeEndpointParameters.Url;
if (nodeType == NodeType.Default)
{
nodeType = NodeType.v20;
}
if (string.IsNullOrEmpty(nodeUrl))
{
if (nodeType != NodeType.v20)
{
throw new ArgException("If nodeEndpointParameters.Url is null then nodeEndpointParameters.Type must be either NodeType.Default or NodeType.v20");
}
nodeUrl = "https://noderelay.epa.gov/Node2/Node2WS.svc";
}
endpointVersion = EnumUtils.ParseEnum(EnumUtils.ToDescription(nodeType));
credentials = new AuthenticationCredentials(nodeEndpointParameters.Username, nodeEndpointParameters.Password);
}
protected virtual INodeEndpointClient GetAuthenticatedNodeClient(string nodeUrl, EndpointVersionType endpointVersion,
AuthenticationCredentials credentials)
{
NodeEndpointClientFactory factory = new NodeEndpointClientFactory();
INodeEndpointClient client = factory.Make(nodeUrl, endpointVersion, credentials, null, null);
// Authenticate NAAS user account
try
{
client.Authenticate();
}
catch (Exception authEx)
{
DisposableBase.SafeDispose(client);
throw new EIS4SleisException(ExceptionType.NodeEndpointException, authEx,
"Failed to authenticate NAAS username \"{0}\" against node endpoint \"{1}\" with exception \"{2}\"",
credentials.UserName, nodeUrl, ExceptionUtils.GetDeepExceptionMessage(authEx));
}
return client;
}
protected virtual INodeEndpointClient GetAuthenticatedNodeClient(NodeEndpointParameters nodeEndpointParameters)
{
string nodeUrl;
EndpointVersionType endpointVersion;
AuthenticationCredentials credentials;
ParseNodeEndpointParameters(nodeEndpointParameters, out nodeUrl, out endpointVersion, out credentials);
INodeEndpointClient client = GetAuthenticatedNodeClient(nodeUrl, endpointVersion, credentials);
return client;
}
protected const string PRODUCTION_SUBMISSION_TYPE_NAME = "Production";
protected const string QA_SUBMISSION_TYPE_NAME = "QA";
protected const string DATA_CATEGORY_HEADER_KEY = "DataCategory";
protected const string SUBMISSION_TYPE_HEADER_KEY = "SubmissionType";
protected const string EIS_FLOW_NAME = "EIS_v1_0";
#endregion Internal Methods
}
}