fis.retransmission.service.ts 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435
  1. import * as _ from 'lodash'
  2. import mongoose, { Model, Schema } from 'mongoose';
  3. import { BehaviorSubject, Observable, Subject, Subscription, from } from 'rxjs'
  4. import { ColorCode, Message, MessageLog, ReportStatus, Status } from '../interfaces/general.interface'
  5. require('dotenv').config();
  6. // Implement status chain refactoring
  7. export class FisRetransmissionService {
  8. private mongoUrl: string = process.env.MONGO as string
  9. private bufferedStorage: Message[] = []
  10. private mongoConnection: any
  11. private messageModel: any
  12. private maximumBufferLength: number = parseInt(process.env.MaxBufferLoad as string) // please configure at environment
  13. // private statusReport: Subject<ReportStatus> = new Subject()
  14. constructor(private databaseName: string) {
  15. // Connect to mongoDB.
  16. this.manageMongoConnection(databaseName)
  17. }
  18. // Main function that intercepts outgoing messages by communicating || intepreting report status from grpc connection as indicator
  19. public handleMessage(applicationOutgoingMessage: Subject<Message>, statusReport: BehaviorSubject<ReportStatus>): Subject<Message> {
  20. let releaseMessageSubject: Subject<Message> = new Subject() // Every message subscribed from applicationOutgoingMessage will be released through this subject
  21. let messageReleaseSubscription: Subscription | null = null
  22. let messageBufferSubscription: Subscription | null = null
  23. let messageStreamToMongo: Subscription | null = null
  24. this.checkBufferLimit(applicationOutgoingMessage, statusReport)
  25. statusReport.subscribe((report: ReportStatus) => {
  26. /* Green should release all data from buffer and mongo and also redirect the applicationOutgoingMessage back into the return subject(releaseMessageSubject)
  27. if there's any. */
  28. if (report.code == ColorCode.GREEN) {
  29. console.log(`Connection status report && ${report.message ?? 'No Message'}`)
  30. /* Status Chain begins */
  31. let status: Status = 1
  32. if (status === 1) {
  33. messageStreamToMongo = this.deactivateMongoStreamSubscription(messageStreamToMongo)
  34. if (messageStreamToMongo) status = 0
  35. }
  36. if (status === 1) {
  37. messageBufferSubscription = this.deactivateBufferSubscription(messageBufferSubscription)
  38. if (messageBufferSubscription) status = 0
  39. }
  40. if (status === 1) {
  41. messageReleaseSubscription = this.activateReleaseSubscription(messageReleaseSubscription, applicationOutgoingMessage, releaseMessageSubject)
  42. if (!messageReleaseSubscription) status = 0
  43. }
  44. if (status === 1) {
  45. this.releaseMessageFromLocalBuffer(this.bufferedStorage).then((resObs: Observable<Message>) => {
  46. resObs.subscribe({
  47. next: message => releaseMessageSubject.next(message),
  48. error: err => console.error(err),
  49. complete: () => {
  50. this.bufferedStorage = []
  51. console.log(`Reset buffer Storage count: ${this.bufferedStorage.length}. All messages have been released back into the stream.`)
  52. }
  53. })
  54. }).catch((err) => {
  55. status = 0
  56. console.error(err)
  57. })
  58. }
  59. if (status === 1) {
  60. this.releaseMessageFromMongoStorage().then((resObs: Subject<Message>) => {
  61. resObs.subscribe({
  62. next: message => releaseMessageSubject.next(message),
  63. error: err => console.error(err),
  64. complete: () => console.log(`All Mongo data are transferred `)
  65. })
  66. }).catch((err) => {
  67. status = 0
  68. console.error(err)
  69. })
  70. }
  71. if (status === 0) {
  72. console.log(`Something Went Wrong in handling ${ColorCode.RED} report.`)
  73. }
  74. }
  75. /* Start buffering the messages coming in from applicationOutgonigMessages and also stop it from flowing into the release subject */
  76. if (report.code == ColorCode.YELLOW) {
  77. if (report.payload) {
  78. console.log(`Rebuffering ${report.payload.message?.appData?.msgId} into buffer...`)
  79. this.bufferedStorage.push(report.payload)
  80. }
  81. console.log(`Connection status report && ${report.message ?? 'No Message'}`)
  82. let status: Status = 1
  83. /* Status Chain begins */
  84. if (status === 1) {
  85. messageBufferSubscription = this.activateBufferSubscription(this.bufferedStorage, messageBufferSubscription, applicationOutgoingMessage)
  86. if (!messageBufferSubscription) status = 0
  87. }
  88. if (status === 1) {
  89. messageReleaseSubscription = this.deactivateReleaseSubscription(messageReleaseSubscription)
  90. if (messageReleaseSubscription) status = 0
  91. }
  92. if (status === 0) {
  93. console.log(`Something Went Wrong in handling ${ColorCode.RED} report.`)
  94. }
  95. }
  96. /* Stop buffering the message in local instance, but start saving them in database. Must first transfer the ones in local buffer before redirecting the
  97. flow from applicationOutgoingMessage into Mongo */
  98. if (report.code == ColorCode.RED) {
  99. console.log(`Connection status report: ${report.message}`)
  100. if (report.payload) {
  101. console.log(`Rebuffering ${report.payload.message?.appData?.msgId} into storage...`)
  102. this.saveToMongo(report.payload)
  103. }
  104. console.log(`Connection status report && ${report.message ?? 'No Message'}`)
  105. let status: Status = 1
  106. if (status === 1) {
  107. messageStreamToMongo = this.activateMongoStreamSubscription(messageStreamToMongo, applicationOutgoingMessage)
  108. if (!messageStreamToMongo) status = 0
  109. }
  110. if (status === 1) {
  111. messageReleaseSubscription = this.deactivateReleaseSubscription(messageReleaseSubscription)
  112. if (messageReleaseSubscription) status = 0
  113. }
  114. if (status === 1) {
  115. messageBufferSubscription = this.deactivateBufferSubscription(messageBufferSubscription)
  116. if (messageBufferSubscription) status = 0
  117. }
  118. if (status === 1) {
  119. this.transferBufferedMessageToMongoStorage(this.bufferedStorage, messageBufferSubscription).then((res: any[]) => {
  120. if (res.length !== this.bufferedStorage.length || this.bufferedStorage.length > 0) status = -1 // this promise function should return an empty array
  121. })
  122. }
  123. if (status === 0) {
  124. console.log(`Something Went Wrong in handling ${ColorCode.RED} report.`)
  125. }
  126. }
  127. if (!report.code) {
  128. console.log(`Unknown message...`)
  129. }
  130. })
  131. return releaseMessageSubject
  132. }
  133. // IF Buffer exceeds a certain limit, it will trigger RED. Configure in .env file. There's the concern of 2 RED status, one from this and another from other means.
  134. // Behaviour of this needs to be investigated further
  135. private checkBufferLimit(message: Subject<Message>, statusReport: Subject<ReportStatus>) {
  136. let status: Status = 1
  137. if (status = 1) {
  138. message.subscribe(() => {
  139. if (this.bufferedStorage.length >= this.maximumBufferLength) {
  140. // for every messges that comes in, check the bufffer size, if it exceesd more than designated amount, push a red report status i
  141. console.log(`Buffer length exceeds limit imposed!!!`)
  142. let report: ReportStatus = {
  143. code: ColorCode.RED,
  144. message: `Buffer is exceeding limit. Initiate storage transfer to designated database.`,
  145. }
  146. statusReport.next(report)
  147. }
  148. })
  149. }
  150. }
  151. // Release the incoming Messages to be returned to the caller
  152. private activateReleaseSubscription(messageReleaseSubscription: Subscription | null, applicationOutgoingMessage: Subject<Message>, releaseMessageSubject: Subject<Message>): Subscription | null {
  153. let status: Status = 1
  154. if (status = 1) {
  155. if (!messageReleaseSubscription) {
  156. messageReleaseSubscription = applicationOutgoingMessage.subscribe({
  157. next: (message: Message) => {
  158. console.log(`Releasing ${(message.message as MessageLog).appData.msgId}...`);
  159. releaseMessageSubject.next(message);
  160. },
  161. error: (err) => console.error(err),
  162. complete: () => { },
  163. });
  164. console.log(`Subscription message release activated.`);
  165. } else {
  166. status = 0
  167. console.log(`Subscription message release is already active.`);
  168. }
  169. }
  170. return messageReleaseSubscription
  171. }
  172. // Stop the incoming Messages to be returned to caller
  173. private deactivateReleaseSubscription(messageReleaseSubscription: Subscription | null): Subscription | null {
  174. let status: Status = 1
  175. if (status = 1) {
  176. if (messageReleaseSubscription) {
  177. messageReleaseSubscription.unsubscribe();
  178. messageReleaseSubscription = null;
  179. console.log(`Subscription message release deactivated.`);
  180. } else {
  181. console.log(`Subscription message release is already deactivated.`);
  182. }
  183. }
  184. return messageReleaseSubscription
  185. }
  186. // Begin to push the incoming messages into local instantarray
  187. private activateBufferSubscription(bufferStorage: Message[], messageBufferSubscription: Subscription | null, applicationOutgoingMessage: Subject<Message>): Subscription | null {
  188. let status: Status = 1
  189. if (status = 1) {
  190. if (!messageBufferSubscription) {
  191. messageBufferSubscription = applicationOutgoingMessage.subscribe({
  192. next: (message: any) => {
  193. console.log(`Buffering ${(message.message as MessageLog).appData.msgId}... Local array length: ${bufferStorage.length}`);
  194. bufferStorage.push(message)
  195. },
  196. error: (err) => console.error(err),
  197. complete: () => { },
  198. });
  199. console.log(`Subscription message buffer activated.`);
  200. } else {
  201. status = 0
  202. console.log(`Subscription message buffer is already active.`);
  203. }
  204. }
  205. return messageBufferSubscription
  206. }
  207. // Stop pushing the incoming messages into local instantarray
  208. private deactivateBufferSubscription(messageBufferSubscription: Subscription | null): Subscription | null {
  209. let status: Status = 1
  210. if (status) {
  211. if (messageBufferSubscription) {
  212. messageBufferSubscription.unsubscribe();
  213. messageBufferSubscription = null;
  214. console.log(`Subscription message buffer deactivated.`);
  215. } else {
  216. status = 0
  217. console.log(`Subscription message buffer is already deactivated.`);
  218. }
  219. }
  220. return null
  221. }
  222. // Change the streaming direction of the incoming messages into mongo streaming subject( to be saved in local databse )
  223. private activateMongoStreamSubscription(messageStreamToMongo: Subscription | null, applicationOutgoingMessage: Subject<Message>): Subscription | null {
  224. let status: Status = 1
  225. if (status = 1) {
  226. if (!messageStreamToMongo) {
  227. messageStreamToMongo = applicationOutgoingMessage.subscribe({
  228. next: (message: any) => {
  229. console.log(`Saving ${(message.message as MessageLog).appData.msgId}...`);
  230. this.saveToMongo(message)
  231. },
  232. error: (err) => console.error(err),
  233. complete: () => { },
  234. });
  235. console.log(`Subscription message streaming to Mongo activated.`);
  236. } else {
  237. status = 0
  238. console.log(`Subscription message streaming to Mongo is already active.`);
  239. }
  240. }
  241. return messageStreamToMongo
  242. }
  243. // Stop or cut off the mongo streaming
  244. private deactivateMongoStreamSubscription(messageStreamToMongo: Subscription | null): Subscription | null {
  245. let status: Status = 1
  246. if (status = 1) {
  247. if (messageStreamToMongo) {
  248. messageStreamToMongo.unsubscribe();
  249. messageStreamToMongo = null;
  250. console.log(`Subscription message streaming to Mongo deactivated.`);
  251. } else {
  252. status = 0
  253. console.log(`Subscription message streaming to Mongo is already deactivated.`);
  254. }
  255. }
  256. return messageStreamToMongo
  257. }
  258. // To be used by mongoStreamSubscription to perform the saving execution
  259. private async saveToMongo(message: Message): Promise<boolean> {
  260. return new Promise((resolve, reject) => {
  261. // let messageModel: Model<any> = this.mongoConnection.model('Message', require('../models/message.schema'))
  262. this.messageModel.create(message).then(() => {
  263. console.log(`Saved MessageID ${(message.message as MessageLog).appData.msgId} into ${this.mongoUrl}`);
  264. resolve(true)
  265. }).catch((err) => {
  266. console.log(`MongoSaveError: ${err.message}`)
  267. reject(err)
  268. })
  269. })
  270. }
  271. // As the name implies, transder all the messages from the local instance into mongoStorage. Local instance should be emptied after transfer is completed
  272. private async transferBufferedMessageToMongoStorage(bufferedMessage: Message[], messageBufferSubscription: Subscription | null): Promise<Message[]> {
  273. return new Promise((resolve, reject) => {
  274. let status: Status = 1
  275. if (status = 1) {
  276. let bufferedStorage: Observable<Message> = from(bufferedMessage)
  277. bufferedStorage.subscribe({
  278. next: (message: any) => {
  279. this.saveToMongo(message).then((res) => {
  280. console.log(`Message ${(message.message as MessageLog).appData.msgId} saved successfully...`)
  281. }).catch((err) => console.error(err))
  282. },
  283. error: (error) => {
  284. reject(error)
  285. console.error(error)
  286. },
  287. complete: () => {
  288. this.bufferedStorage = []
  289. if (messageBufferSubscription) {
  290. console.log(`All ${bufferedMessage.length} buffered messages have been sent for transfer to ${this.mongoUrl}. Current length: ${this.bufferedStorage.length}`)
  291. }
  292. resolve(this.bufferedStorage)
  293. }
  294. })
  295. }
  296. })
  297. }
  298. // Transfer stored messages from the local instance back into the stream to be returned to the caller.
  299. private async releaseMessageFromLocalBuffer(bufferedStorage: Message[]): Promise<Observable<Message>> {
  300. return new Promise((resolve, reject) => {
  301. let status: Status = 1
  302. if (status = 1) {
  303. if (bufferedStorage.length > 1) {
  304. let caseVariable = this.bufferedStorage.length > 1;
  305. console.log(`Releasing data from local buffer instance. There ${caseVariable ? "is" : "are"} ${this.bufferedStorage.length} messages...`);
  306. let returnArrayObs: Observable<Message> = from(bufferedStorage)
  307. resolve(returnArrayObs)
  308. } else {
  309. let message = `There is no data in stored in local instance`
  310. reject(message)
  311. }
  312. }
  313. })
  314. }
  315. // Transder all the stored messages in designated mongo databases. It should be empty after all the data has been transferred.
  316. private async releaseMessageFromMongoStorage(): Promise<Subject<Message>> {
  317. return new Promise((resolve, reject) => {
  318. let status: Status = 1
  319. if (status = 1) {
  320. let dataSubject: Subject<Message> = new Subject()
  321. this.extractAllMessages(dataSubject)
  322. resolve(dataSubject)
  323. }
  324. })
  325. }
  326. // Connect to designated mongodatabase.
  327. private async connectToMongoDatabase(databaseName: string): Promise<any> {
  328. return new Promise((resolve, reject) => {
  329. let status: Status = 1
  330. if (status = 1) {
  331. let database = this.mongoUrl + databaseName
  332. console.log(database)
  333. this.mongoConnection = mongoose.createConnection(database)
  334. this.mongoConnection.on('error', (error) => {
  335. console.error('Connection error:', error);
  336. resolve('')
  337. });
  338. this.mongoConnection.once('open', () => {
  339. console.log(`Connected to ${process.env.MONGO}`);
  340. this.messageModel = this.mongoConnection.model('Message', require('../models/message.schema'));
  341. });
  342. }
  343. })
  344. }
  345. // Manage mongoCOnnectino. The logic used would be different across differnet application. This will loop the process indefinitely os it is always trying to connect to database.
  346. private async manageMongoConnection(databaseName: string): Promise<boolean> {
  347. while (true) {
  348. try {
  349. await this.connectToMongoDatabase(databaseName)
  350. } catch (error) {
  351. console.log(`Something Wrong occured. Please check at manageMongoConnection`)
  352. }
  353. await new Promise(resolve => setTimeout(resolve, 1000)); // Wait for 1 second before the next attempt
  354. }
  355. }
  356. // This will be used to release all the hostage messages once the light is green.
  357. public async extractAllMessages(subjectArgs: Subject<Message>): Promise<void> {
  358. // Need to resolve the issue of streaming in a specific order that is sequential
  359. let status: Status = 1
  360. if (status = 1) {
  361. if (this.messageModel) {
  362. const eventStream = this.messageModel.find().lean().cursor()
  363. eventStream.on('data', (message) => {
  364. // Emit each document to the subject
  365. subjectArgs.next(message);
  366. });
  367. eventStream.on('end', async () => {
  368. // All data has been streamed, complete the subject
  369. subjectArgs.complete();
  370. // Delete the data once it has been streamed
  371. try {
  372. await this.messageModel.deleteMany({});
  373. console.log('Data in Mongo deleted successfully.');
  374. } catch (err) {
  375. console.error('Error deleting data:', err);
  376. }
  377. });
  378. } else {
  379. status = 0
  380. console.log(`Error: Message Model is ${this.messageModel}!! Please set up the mongoose connection properly!`)
  381. }
  382. }
  383. }
  384. }
  385. // Store in json file in this project folder. To be enabled in future
  386. // private async transferMessageToLocalStorage(message: Subject<any>): Promise<void> {
  387. // let localArray: any[] = this.bufferedStorage
  388. // let filename = `localstorage.json`;
  389. // while (localArray.length > 0) {
  390. // let objectToWrite = this.bufferedStorage[0];
  391. // await writeMessage(objectToWrite, filename)
  392. // }
  393. // message.subscribe((message: any) => {
  394. // writeMessage(message, filename)
  395. // })
  396. // if (localArray.length < 1) this.bufferedStorage = localArray
  397. // console.log('Local Array is empty. Finished transferring to files.')
  398. // async function writeMessage(message: any, filename: string) {
  399. // try {
  400. // let stringifiedMessage = JSON.stringify(message);
  401. // await fs.promises.appendFile(filename, stringifiedMessage + "\r\n")
  402. // console.log(`Successfully transferred ${filename}`);
  403. // localArray.shift();
  404. // } catch (err) {
  405. // console.error(`Error trasferring ${filename}:`, err);
  406. // }
  407. // }
  408. // }