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