├── .markdownlint.json ├── Connect ├── ChannelUtils.js ├── DBConnection.js ├── FixHL7NodeOrder.js ├── HowDoI.md ├── ImprovedChannelCloning.js ├── Mirth.md ├── PluginIdeas.md ├── Report.js ├── SendFileToEmail.xml ├── ServiceIdeas.md ├── docker-compose.yml ├── getCached.js ├── screenshot_2023-07-19_at_2.12.27_pm_720.png ├── screenshot_2023-07-19_at_2.24.41_pm_720.png ├── screenshot_2023-07-19_at_2.25.05_pm_720.png ├── too-big-sample-1.txt ├── too-big-sample-2.txt ├── too-big-sample-3.txt └── x12_edi_834_reader.js ├── Networking.md ├── PowerShell ├── PowerShell.md ├── PowerShellNotesForProfessionals.pdf └── powershell-cheat-sheet-ramblingcookiemonster.pdf ├── README.md ├── Rust.md └── vscode-markdown-toc-config /.markdownlint.json: -------------------------------------------------------------------------------- 1 | { 2 | "MD013": false, 3 | "MD033": { "allowed_elements": ["a","details","summary","ol","li"] }, 4 | "markdownlint.run": "onSave" 5 | } -------------------------------------------------------------------------------- /Connect/ChannelUtils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * ChannelUtils 3 | * @param config: {name, user, password, url, dbClass, cacheConnection, cacheName} 4 | * @param config.name: a db connection name must be unique or will cause issues 5 | * @param config.user: user name to use for db connection 6 | * @param config.password: db user password 7 | * @param config.url: db url 8 | * @param config.dbClass: db class - Default: org.postgresql.Driver 9 | * @param config.cacheConnection: Should cache the connection? true/false 10 | * @param config.cacheName: default - cachedConnection:ChannelUtils:channelName:config.name 11 | * @constructor 12 | */ 13 | function ChannelUtils(config) { 14 | config = config || $cfg('mirthDB') 15 | DBConnection.call(this, config) 16 | } 17 | 18 | ChannelUtils.prototype = Object.create(DBConnection.prototype) 19 | 20 | // $tryElse inherited from DBConnection 21 | 22 | ChannelUtils.prototype.getDBID = function (cid, debug) { 23 | cid = cid || channelId 24 | const statement = 'SELECT local_channel_id from d_channels where channel_id = ?;' 25 | if (debug) { 26 | logger.debug(['ChannelUtils.prototype.getDBID(cid=', cid, ', debug=', debug, ')\n ', this.denormalizeSQL(statement, [cid])].join('')) 27 | } 28 | const resultSet = this.executeDBStatement(statement, true, [cid]) 29 | if (resultSet.next()) { 30 | const result = parseInt(JSON.parse(resultSet.getString(1))) 31 | if (debug) { 32 | logger.debug(['ChannelUtils.prototype.getDBID(cid=', cid, ', debug=', debug, ') result = ', result].join('')) 33 | } 34 | return result 35 | } 36 | throw new Error([channelId, ':', channelId, ': ', 'Failed to get DB ID for channelId: ', cid].join('')) 37 | } 38 | 39 | ChannelUtils.prototype.createMetaDataIndex = function (metadata, debug) { 40 | const columnName = metadata.toUpperCase() 41 | const tableName = 'd_mcm' + this.getDBID() 42 | const sqlStmt = 'CREATE INDEX CONCURRENTLY IF NOT EXISTS ' + 'idx_' + tableName + '_' + columnName + ' ON ' + tableName + ' ("' + columnName + '");' 43 | if (debug) { 44 | logger.debug(['ChannelUtils.prototype.createMetaDataIndex(metadata=', metadata, ', debug=', debug, ') sqlStmt = ', sqlStmt].join('')) 45 | } 46 | this.executeDBStatement(sqlStmt, false) 47 | } 48 | 49 | ChannelUtils.prototype.getMessageByMetadata = function (key, value, cid, debug) { 50 | const dbID = this.getDBID(cid, debug) 51 | if (!dbID) { 52 | this.throwError('getMessageByMetadata()', 'No dbID found for channel ID: ' + cid) 53 | } 54 | const sql = [ 55 | 'select * from d_mc## right join d_mcm## on d_mcm##.message_id = d_mc##.message_id and d_mcm##.metadata_id = d_mc##.metadata_id where "', 56 | key.toUpperCase(), 57 | '" = ?::varchar and content_type = 1' 58 | ].join('').replace(/##/g, dbID) 59 | const sqlStmnt = this.sqlRowsAsJSON(sql) 60 | if (debug) { 61 | logger.debug([ 62 | 'ChannelUtils.prototype.getMessageByMetadata(key=', key, ', value=', value, ', cid=', cid, ', debug=', debug, ') sqlStmnt = ', 63 | this.denormalizeSQL(sqlStmnt, [String(value)]) 64 | ].join('')) 65 | } 66 | // value is explicitly converted to a string for mirth 3.7.0 to fix: 67 | // org.postgresql.util.PSQLException: Can't infer the SQL type to use for an instance of org.mozilla.javascript.NativeString 68 | const resultSet = this.executeDBStatement(sqlStmnt || sql, true, [String(value)]) 69 | if (resultSet.next()) { 70 | const result = sqlStmnt ? JSON.parse(resultSet.getString(1)) : resultSet 71 | if (debug) { 72 | logger.debug([ 73 | 'ChannelUtils.prototype.getMessageByMetadata(key=', key, ', value=', value, ', cid=', cid, ', debug=', debug, ') result = ', 74 | result 75 | ].join('')) 76 | } 77 | return result 78 | } 79 | return sqlStmnt ? [] : resultSet 80 | } 81 | 82 | ChannelUtils._updateIndex = function (name, cid) { 83 | const globalIndex = globalMap.get('ChannelUtilsIndex') || {} 84 | globalIndex[name] = cid 85 | globalMap.put('ChannelUtilsIndex', globalIndex) 86 | } 87 | 88 | ChannelUtils.setMessageIndex = function (key, value, name, dbConfig) { 89 | const channelUtils = new ChannelUtils(String($cfg(dbConfig))) 90 | channelUtils.createMetaDataIndex(key) 91 | channelMap.put(key, value) 92 | ChannelUtils._updateIndex(name, channelId) 93 | } 94 | 95 | ChannelUtils.getMessageByIndex = function (key, value, name, dbConfig, options) { 96 | options = options || {sort: true} 97 | const channelUtils = new ChannelUtils(String($cfg(dbConfig))) 98 | const globalIndex = globalMap.get('ChannelUtilsIndex') 99 | const cid = globalIndex[name] 100 | var result = channelUtils.getMessageByMetadata(key, value, cid) || [] 101 | if (options.sort) { 102 | result = result.sort((a, b) => a.message_id > b.message_id) 103 | } 104 | if (options.parseXml) { 105 | result = result.map(order => new XML(SerializerFactory.getSerializer('HL7V2').toXML(order.content))) 106 | if (options.filter) { 107 | result = result.filter(order => options.filter.indexOf(order['ORC']['ORC.1']['ORC.1.1'].toString()) > -1) 108 | } 109 | } 110 | return result 111 | } 112 | 113 | /** 114 | * Gets messages from channel with {channelID} by metadata column {key} with value of {value} 115 | * @param {string} key metadata column 116 | * @param {string} value metadata value 117 | * @param {string} channelID 118 | * @param {string} dbConfig $cfg map key for db config 119 | * @param {boolean} [parseXml=false] should parse to XML? 120 | * @param {boolean} [sort=true] should sort by message id? 121 | * @param {[string]} [filter] should filter on ORC.1.1 example ['XO', 'NW', 'SC'] 122 | * @param debug 123 | * @return {[*]} 124 | */ 125 | ChannelUtils.getMessageByIndexV2 = function ({key, value, channelID, dbConfig, parseXml, sort, filter, debug}) { 126 | const channelUtils = new ChannelUtils(String($cfg(dbConfig))) 127 | var result = channelUtils.getMessageByMetadata(key, value, channelID, debug) || [] 128 | if (debug) { 129 | logger.debug([ 130 | 'ChannelUtils.prototype.getMessageByIndexV2(key=', key, ', value=', value, ', channelID=', channelID, ', dbConfig=', dbConfig, 131 | ', parseXml=', parseXml, ', sort=', sort, 'filter=', filter, ', debug=', debug, ') result = ', 132 | JSON.stringify(result,null,2) 133 | ].join('')) 134 | } 135 | if (sort) { 136 | result = result.sort((a, b) => a.message_id > b.message_id) 137 | } 138 | if (parseXml) { 139 | result = result.map(order => new XML(SerializerFactory.getSerializer('HL7V2').toXML(order.content))) 140 | if (Array.isArray(filter)) { 141 | result = result.filter(order => filter.indexOf(order['ORC']['ORC.1']['ORC.1.1'].toString()) > -1) 142 | } 143 | } 144 | return result 145 | } 146 | 147 | /* global $cfg, SerializerFactory, XML, globalMap, channelMap, channelId, DBConnection, logger */ 148 | -------------------------------------------------------------------------------- /Connect/DBConnection.js: -------------------------------------------------------------------------------- 1 | /** 2 | * DBConnection is a base class designed to be extended and provide basic DB functionality in a reusable fashion. 3 | * 4 | * Author: Michael Lee Hobbs 5 | * LICENSE: GNU GENERAL PUBLIC LICENSE - https://www.gnu.org/licenses/gpl-3.0.html 6 | * History: 7 | * - 20201226 - Initial Release 8 | * - 20210406 - PACMANO Release, the Less is MORE edition! Line count cut by %40! Simplification of code. 9 | * 10 | * @example 11 | * const config = { 12 | * "name": "mirthdb", 13 | * "user": "mirthdb", 14 | * "password": "mirthdb", 15 | * "url": "jdbc:postgresql://postgres:5432/mirthdb", 16 | * "dbClass": "org.postgresql.Driver", 17 | * "cacheConnection": true 18 | * } 19 | * 20 | * @param {object} config: {name, user, password, url, dbClass, cacheConnection, cacheName} 21 | * @param {string} config.name: a db connection name must be unique or will cause issues 22 | * @param {string} config.user: user name to use for db connection 23 | * @param {string} config.password: db user password 24 | * @param {string} config.url: db url 25 | * @param {string} config.dbClass: db class - Default: org.postgresql.Driver 26 | * @param {string} config.cacheConnection: Should cache the connection? true/false 27 | * @param {string} config.cacheName: optional for cross channel caching - defaults to cachedConnection:className:channelName:config.name 28 | * @constructor 29 | */ 30 | function DBConnection(config) { 31 | if (!config) { 32 | this.throwError(null, 'config is undefined!') 33 | } 34 | this._config = (typeof config === 'string') ? JSON.parse(config) : JSON.parse(JSON.stringify(config)); 35 | ['name', 'url', 'user', 'password'].forEach(key => !this._config[key] && this.throwError(null, 'config.' + key + ' is undefined!')) 36 | this._config.cacheName = this._config.cacheName || ['cachedConnection', channelName, this._config.name].join(':') 37 | } 38 | 39 | // utility functions 40 | function $sleep(ms) { 41 | java.lang.Thread.sleep(ms) 42 | } 43 | 44 | /** 45 | * Closes the DB connection 46 | */ 47 | DBConnection.prototype.closeConnection = function () { 48 | $t(() => globalMap.get(this._config.cacheName).close()) 49 | } 50 | 51 | /** 52 | * Executes a SQL statement 53 | * @param statement 54 | * @param isQuery 55 | * @param paramList - Java ArrayList or JS Array 56 | * @return {*|undefined} - results or undefined 57 | */ 58 | DBConnection.prototype.executeDBStatement = function (statement, isQuery, paramList, _retry) { 59 | statement = String(statement) 60 | _retry = (_retry || 0) + 1 // recursive call 61 | const dbConnection = this._getConnection() 62 | const arrList = Array.isArray(paramList) ? paramList : [] 63 | if (paramList instanceof Packages.java.util.ArrayList) { 64 | const paramListIterator = paramList.iterator() 65 | while (paramListIterator.hasNext()) { 66 | arrList.push('' + paramListIterator.next()) 67 | } 68 | } 69 | 70 | try { 71 | return (isQuery) ? dbConnection.executeCachedQuery(statement, paramList || arrList) : dbConnection.executeUpdate(statement, paramList || arrList) 72 | } catch (e) { 73 | const errorCheck = [ 74 | 'I/O error', 'This connection has been closed.', 75 | 'FATAL: sorry, too many clients already', 76 | 'FATAL: terminating connection due to administrator command', 77 | 'The connection attempt failed.' 78 | ] // temporary issues // 20211005 pcoyne ADD AWS timeout 79 | if (_retry < 10 && errorCheck.some(check => e.message.indexOf(check) > -1)) { 80 | $sleep(_retry * 100) 81 | return this.executeDBStatement(statement, isQuery, paramList, _retry) 82 | } 83 | const debugStatement = arrList.reduce((acc, cur) => acc.replace('?', "'" + cur + "'"), statement) 84 | throw this.errorPrefix('executeDBStatement', 'statement: ' + debugStatement + ', isQuery: ' + isQuery + ' on "' + this._config.name + '"', e) 85 | } finally { 86 | if (!this._config.cacheConnection) { 87 | this.closeConnection() 88 | } 89 | } 90 | } 91 | 92 | DBConnection.prototype.denormalizeSQL = function (sql, params) { 93 | var result = sql 94 | params.forEach(param => { 95 | var replacer = typeof param === 'number' ? param : ["'", param, "'"].join('') 96 | result = result.replace('?', replacer) 97 | }) 98 | return result 99 | } 100 | 101 | /** 102 | * Executes multiple request in order 103 | * @param paramsArr [statement, isQuery, paramList] - See executeDBStatement 104 | * @return [{*|undefined}] 105 | */ 106 | DBConnection.prototype.executeDBStatements = function (paramsArr) { 107 | return paramsArr.map(([statement, isQuery, paramList]) => this.executeDBStatement(statement, isQuery, paramList)) 108 | } 109 | 110 | DBConnection.prototype.errorPrefix = function (func, msg, error) { 111 | error = error || new Error() 112 | error.message = [ 113 | channelName, ': ', this.constructor.name, (func && '.' + func || ''), (msg && ' - ' + msg || ''), '\n', error.message, '\n', error.stack 114 | ].join('') 115 | return error 116 | } 117 | 118 | /** 119 | * Throw or log and error adding additional information 120 | * @param func - optional - Name of function that is throwing 121 | * @param msg - optional - Additional message, example could be additional error information 122 | * @param error - original error object 123 | */ 124 | DBConnection.prototype.throwError = function (func, msg, error) { 125 | throw this.errorPrefix(func, msg, error) 126 | // error = error || new Error() 127 | // error.message = [ 128 | // channelName, ': ', this.constructor.name, (func && '.' + func || ''), (msg && ' - ' + msg || ''), '\n', error.message, '\n', error.stack 129 | // ].join('') 130 | // throw error 131 | } 132 | 133 | DBConnection.prototype._getConnection = function (_retry) { 134 | _retry = (_retry || 0) + 1 135 | const dbConnection = globalMap.get(this._config.cacheName) 136 | const result = $t(() => !globalMap.get(this._config.cacheName).getConnection().isClosed()) 137 | if (result === true) { 138 | return dbConnection 139 | } 140 | if (result instanceof Error && _retry > 5) { 141 | this.throwError('_getConnection()', 'Failed to open a connection!', result) 142 | } 143 | $sleep(_retry * 100) 144 | const {dbClass, url, user, password} = this._config 145 | try { 146 | globalMap.put(this._config.cacheName, DatabaseConnectionFactory.createDatabaseConnection(dbClass, url, user, password)) 147 | } catch (e) { 148 | this.throwError('_getConnection', '', e) 149 | } 150 | return this._getConnection(_retry) 151 | } 152 | 153 | /** 154 | * Wraps sql statement in array to json and array agg if wrapper for a given DB is found. 155 | * @param sql 156 | * @return {String} sql statement 157 | */ 158 | DBConnection.prototype.sqlRowsAsJSON = function (sql) { 159 | const wrappers = { 160 | 'org.postgresql.Driver': (sql) => 'select array_to_json(array_agg(t)) from (' + sql + ') as t;' 161 | } 162 | if (wrappers[this._config.dbClass]) { 163 | return wrappers[this._config.dbClass](sql) 164 | } 165 | } 166 | 167 | /** 168 | * Wraps sql statement in array agg if wrapper for a given DB is found. 169 | * @param sql 170 | * @return {String} sql statement 171 | */ 172 | DBConnection.prototype.sqlAsJSON = function (sql) { 173 | const wrappers = { 174 | 'org.postgresql.Driver': (sql) => 'select array_agg(t) from (' + sql + ') as t;' 175 | } 176 | if (wrappers[this._config.dbClass]) { 177 | return wrappers[this._config.dbClass](sql) 178 | } 179 | } 180 | 181 | /* global globalMap, channelName, java, Packages, DatabaseConnectionFactory*/ -------------------------------------------------------------------------------- /Connect/FixHL7NodeOrder.js: -------------------------------------------------------------------------------- 1 | function fixHL7NodeOrder(node) { 2 | // Create output node 3 | var newNode = new XML(); 4 | // In case the node is an XMLList of multiple siblings, loop through each sibling 5 | for each (sibling in node) { 6 | // Create new sibling node 7 | var newSibling = new XML('<'+sibling.name().toString()+'/>'); 8 | // Iterate through each child node 9 | for each (child in sibling.children()) { 10 | // If the child has its own children, then recursively fix the node order of the child 11 | if (child.hasComplexContent()) { 12 | newSibling.appendChild(fixHL7NodeOrder(child)); 13 | } 14 | // If the child doesn't have its own children, then just add the child to the new sibling node 15 | else { 16 | newSibling.appendChild(child); 17 | } 18 | } 19 | // After recursively fixing all of the child nodes, now we'll fix the current node 20 | newNode += sortHL7Node(newSibling); 21 | } 22 | // Return the fixed node 23 | return newNode; 24 | } 25 | 26 | // Helper function for fixHL7NodeOrder 27 | function sortHL7Node(node) { 28 | // If the node has no children, then there's nothing to sort 29 | if (node.hasSimpleContent()) { 30 | return node; 31 | } 32 | // Create new output node 33 | var newNode = new XML('<'+node.name().toString()+'/>'); 34 | // Iterate through each child in the node 35 | for each (child in node.children()) { 36 | // If the child has a QName, then we can sort on it 37 | if (child.name()) { 38 | // Get the current "index" of the child. Id est, if the QName is PID.3.1, then the index is 1 39 | curChildIndex = parseInt(child.name().toString().substring(child.name().toString().lastIndexOf('.')+1),10); 40 | // Boolean placeholder 41 | var inserted = false; 42 | // Iterate through each child currently in the NEW node 43 | for (var i = 0; i <= newNode.children().length()-1; i++) { 44 | // Get the index of the child of the new node 45 | loopChildIndex = parseInt(newNode.child(i).name().toString().substring(newNode.child(i).name().toString().lastIndexOf('.')+1),10); 46 | // If the child we want to insert has a lower index then the current child of the new node, then we're going to insert the child 47 | // right before the current newNode child 48 | if (curChildIndex < loopChildIndex) { 49 | // Insert the child 50 | newNode.insertChildBefore(newNode.children()[i],child); 51 | // Set our flag, indicating that an insertion was made 52 | inserted = true; 53 | // No need to continue iteration 54 | break; 55 | } 56 | } 57 | // If no insertion was made, then the index of the child we want to insert is greater than or equal to all of the 58 | // indices of the children that have already been inserted in newNode. So, we'll just append the child to the end. 59 | if (!inserted) { 60 | newNode.appendChild(child); 61 | } 62 | } 63 | } 64 | // Return the sorted HL7 node 65 | return newNode; 66 | } 67 | -------------------------------------------------------------------------------- /Connect/ImprovedChannelCloning.js: -------------------------------------------------------------------------------- 1 | //helper functions 2 | function cloneSingleChannel(templateChannelName, targetChannelName, tagNames, serverEventContext) { 3 | if(targetChannelName.length > 40) { 4 | throw "Channel name is too long (40 characters max): " + targetChannelName; 5 | } 6 | 7 | var channelCtl = com.mirth.connect.server.controllers.ControllerFactory.getFactory().createChannelController(); 8 | 9 | var existingChannel = channelCtl.getChannelByName(targetChannelName); 10 | if(existingChannel) 11 | throw "Channel '" + targetChannelName + "' already exists"; 12 | 13 | var newChannel = channelCtl.getChannelByName(templateChannelName); 14 | if(!newChannel) 15 | throw "Template channel '" + templateChannelName + "' does not exist"; 16 | 17 | newChannel.setId(UUIDGenerator.getUUID()); 18 | newChannel.setName(targetChannelName); 19 | newChannel.setRevision(0); 20 | var script = newChannel.getDeployScript(); 21 | script = script.replaceAll('CLIENT_SYSTEM_ID_SHORTENED', shortenedSystemId).replaceAll('CLIENT_SYSTEM_ID', siteId); 22 | newChannel.setDeployScript(script); 23 | 24 | // Generate the proper channel "export data", which includes tags and pruning settings 25 | var tags = new java.util.ArrayList(); // Use ArrayList instead of [] because it must be mutable 26 | for(i=0; i, and the "removedChannelGroupIds" must not be null 124 | var result = channelCtl.updateChannelGroups(new java.util.HashSet(groups), new java.util.HashSet(), false); 125 | 126 | if(!result) { 127 | logger.error('Failed to update channel groups'); 128 | } 129 | 130 | return result; 131 | } else { 132 | logger.debug('No channel group updates are necessary.'); 133 | return true; // All is well 134 | } 135 | } -------------------------------------------------------------------------------- /Connect/PluginIdeas.md: -------------------------------------------------------------------------------- 1 | # MC Plugin Ideas 2 | 3 | 4 |
5 | Table of Contents 6 |
    7 |
  1. Export and import from clipboard
  2. 8 |
  3. View messages in thread queues
  4. 9 |
  5. Dump scheduled jobs
  6. 10 |
  7. Image conversion
  8. 11 |
  9. Mirth UIs from JSON config
  10. 12 |
  11. Channel dependency graph
  12. 13 |
  13. What is my IP
  14. 14 |
15 |
16 | 17 | ## Export and import from clipboard 18 | 19 | _jonb_ 20 | ya know what plugin I want? Export and import from clipboard. I’m poking at an MCVE and being albe to just copy-paste the channel code would be faster than moving files. 21 | 22 | ## View messages in thread queues 23 | 24 | _Kirby Knight_ 25 | I wish there was a way to see what messages are queued on each thread. Is there a way to configure a channel to log or display this information? 26 | 27 | (back and forth on how to get the expected data) 28 | 29 | _jonb_ 30 | See [#3669](https://github.com/nextgenhealthcare/connect/issues/3669#issuecomment-626961190) if you’re interested in the details of how all this works. OH - `connectorMessage.getQueueBucket()` at `com/mirth/connect/donkey/server/queue/DestinationQueue.java:274`. I wonder if you can just log that out from MC 31 | 32 | _agermano_ 33 | That's not the same connectorMessage that is available in JavaScript. Different types. 34 | 35 | _jonb_ 36 | `com.mirth.connect.userutil.ImmutableConnectorMessage` would have to be updated with a getter to expose the queue bucket. Or mess with reflection. I almost have this working. I am running into a lot of Java module errors because I’m running Java 17. @Kirby Knight ON A TEST INSTANCE try this 37 | 38 | ````javascript 39 | return org.apache.commons.lang3.builder.ReflectionToStringBuilder(connectorMessage, 40 | new org.apache.commons.lang3.builder.RecursiveToStringStyle()); 41 | ```` 42 | 43 | It will return a string with the immutable connector message and the private connector message. The private connector message then has your queue thread ID as an int. There is probably a cleaner way to do this by reflecting into the specific fields needed. 44 | 45 | It's possible to see what thread a message was assigned to. 46 | 47 | ````javascript 48 | var internalConnectorMessage = org.apache.commons.lang3.reflect. 49 | FieldUtils.readField(connectorMessage, "connectorMessage", true); 50 | return internalConnectorMessage.getQueueBucket(); 51 | ```` 52 | 53 | _agermano_ 54 | That will just tell you which bucket the current message belongs to. I think you'd need to read the current buffer, and then count how many messages are assigned to each thread... but the buffer is constantly changing and not thread-safe unless you access it through the synchronized methods. 55 | 56 | _jonb_ 57 | I’m running this code in a JS Writer destination so the queue bucket would definitely be assigned because to get to the destination it would have to have come out of the DB and into the queue buffer. I wonder if the value is null in the destination transformer before it goes into the queue. Yes it is null. __So Tony is right, you cannot see the queue bucket until after the message is out of the queue. I think in practical terms that means that only a JS Writer would be able to capture this information.__ 58 | 59 | ## Dump scheduled jobs 60 | 61 | _jonb_ 62 | Plugin idea - A settings plugin that just dumps the current list of scheduled jobs from Quartz 63 | 64 | _agermano_ 65 | I may run into that same issue from yesterday that a bunch of stuff you need is behind private fields/methods. 66 | 67 | _jonb_'s prototype [here](https://gist.github.com/jonbartels/27b09865b2b48051920564af83fca09e), its output: 68 | 69 | ````javascript 70 | 71 | { 72 | "schedulerCount" : 9, 73 | "schedulers" : [ 74 | { 75 | "summary" : "Quartz Scheduler (v2.1.7) '538c6291-7dfc-4835-9f35-a3cf93a23615' with instanceId 'NON_CLUSTERED'\n Scheduler class: 'org.quartz.impl.StdScheduler' - running locally.\n Running since: Sat May 06 14:08:16 UTC 2023\n Not currently in standby mode.\n Number of jobs executed: 1\n Using thread pool 'org.quartz.simpl.SimpleThreadPool' - with 1 threads.\n Using job-store 'org.quartz.simpl.RAMJobStore' - which does not support persistence. and is not clustered.\n", 76 | "groups" : [ 77 | { 78 | "groupName" : "538c6291-7dfc-4835-9f35-a3cf93a23615", 79 | "jobs" : [ 80 | { 81 | "jobName" : "PollConnector538c6291-7dfc-4835-9f35-a3cf93a23615", 82 | "jobGroup" : "538c6291-7dfc-4835-9f35-a3cf93a23615", 83 | "triggerCount" : 1, 84 | "nextTriggerDate" : "2023-05-06T15:41Z" 85 | } 86 | ] 87 | } 88 | ] 89 | }, 90 | { 91 | "summary" : "Quartz Scheduler (v2.1.7) 'b626b2d9-8c3c-400f-848c-7213e0350e1e' with instanceId 'NON_CLUSTERED'\n Scheduler class: 'org.quartz.impl.StdScheduler' - running locally.\n Running since: Sat May 06 14:08:16 UTC 2023\n Not currently in standby mode.\n Number of jobs executed: 1\n Using thread pool 'org.quartz.simpl.SimpleThreadPool' - with 1 threads.\n Using job-store 'org.quartz.simpl.RAMJobStore' - which does not support persistence. and is not clustered.\n", 92 | "groups" : [ 93 | { 94 | "groupName" : "b626b2d9-8c3c-400f-848c-7213e0350e1e", 95 | "jobs" : [ 96 | { 97 | "jobName" : "PollConnectorb626b2d9-8c3c-400f-848c-7213e0350e1e", 98 | "jobGroup" : "b626b2d9-8c3c-400f-848c-7213e0350e1e", 99 | "triggerCount" : 1, 100 | "nextTriggerDate" : "2023-05-08T04:00Z" 101 | } 102 | ] 103 | } 104 | ] 105 | }, 106 | { 107 | "summary" : "Quartz Scheduler (v2.1.7) 'e1e9a603-f009-4222-8cf7-09690adad6b1' with instanceId 'NON_CLUSTERED'\n Scheduler class: 'org.quartz.impl.StdScheduler' - running locally.\n Running since: Sat May 06 14:32:21 UTC 2023\n Not currently in standby mode.\n Number of jobs executed: 1\n Using thread pool 'org.quartz.simpl.SimpleThreadPool' - with 1 threads.\n Using job-store 'org.quartz.simpl.RAMJobStore' - which does not support persistence. and is not clustered.\n", 108 | "groups" : [ 109 | { 110 | "groupName" : "e1e9a603-f009-4222-8cf7-09690adad6b1", 111 | "jobs" : [ 112 | { 113 | "jobName" : "PollConnectore1e9a603-f009-4222-8cf7-09690adad6b1", 114 | "jobGroup" : "e1e9a603-f009-4222-8cf7-09690adad6b1", 115 | "triggerCount" : 2, 116 | "nextTriggerDate" : "2023-05-06T14:35Z" 117 | } 118 | ] 119 | } 120 | ] 121 | }, 122 | { 123 | "summary" : "Quartz Scheduler (v2.1.7) 'DataPruner' with instanceId 'NON_CLUSTERED'\n Scheduler class: 'org.quartz.impl.StdScheduler' - running locally.\n Running since: Sat May 06 14:08:10 UTC 2023\n Not currently in standby mode.\n Number of jobs executed: 0\n Using thread pool 'org.quartz.simpl.SimpleThreadPool' - with 1 threads.\n Using job-store 'org.quartz.simpl.RAMJobStore' - which does not support persistence. and is not clustered.\n", 124 | "groups" : [ 125 | { 126 | "groupName" : "DataPruner", 127 | "jobs" : [ 128 | { 129 | "jobName" : "DataPrunerDataPruner", 130 | "jobGroup" : "DataPruner", 131 | "triggerCount" : 1, 132 | "nextTriggerDate" : "2023-05-06T15:00Z" 133 | } 134 | ] 135 | } 136 | ] 137 | }, 138 | { 139 | "summary" : "Quartz Scheduler (v2.1.7) '74e44712-f77a-488e-933c-423eab6b30d1' with instanceId 'NON_CLUSTERED'\n Scheduler class: 'org.quartz.impl.StdScheduler' - running locally.\n Running since: Sat May 06 14:08:16 UTC 2023\n Not currently in standby mode.\n Number of jobs executed: 1\n Using thread pool 'org.quartz.simpl.SimpleThreadPool' - with 1 threads.\n Using job-store 'org.quartz.simpl.RAMJobStore' - which does not support persistence. and is not clustered.\n", 140 | "groups" : [ 141 | { 142 | "groupName" : "74e44712-f77a-488e-933c-423eab6b30d1", 143 | "jobs" : [ 144 | { 145 | "jobName" : "PollConnector74e44712-f77a-488e-933c-423eab6b30d1", 146 | "jobGroup" : "74e44712-f77a-488e-933c-423eab6b30d1", 147 | "triggerCount" : 1, 148 | "nextTriggerDate" : "2023-05-06T15:00Z" 149 | } 150 | ] 151 | } 152 | ] 153 | }, 154 | { 155 | "summary" : "Quartz Scheduler (v2.1.7) '616ca323-6ae0-4eaa-a7a1-7f7595db3967' with instanceId 'NON_CLUSTERED'\n Scheduler class: 'org.quartz.impl.StdScheduler' - running locally.\n Running since: Sat May 06 14:08:16 UTC 2023\n Not currently in standby mode.\n Number of jobs executed: 290\n Using thread pool 'org.quartz.simpl.SimpleThreadPool' - with 1 threads.\n Using job-store 'org.quartz.simpl.RAMJobStore' - which does not support persistence. and is not clustered.\n", 156 | "groups" : [ 157 | { 158 | "groupName" : "616ca323-6ae0-4eaa-a7a1-7f7595db3967", 159 | "jobs" : [ 160 | { 161 | "jobName" : "PollConnector616ca323-6ae0-4eaa-a7a1-7f7595db3967", 162 | "jobGroup" : "616ca323-6ae0-4eaa-a7a1-7f7595db3967", 163 | "triggerCount" : 1, 164 | "nextTriggerDate" : "2023-05-06T14:32Z" 165 | } 166 | ] 167 | } 168 | ] 169 | }, 170 | { 171 | "summary" : "Quartz Scheduler (v2.1.7) 'a89533ac-4cd9-46a0-b8a7-393c528252ce' with instanceId 'NON_CLUSTERED'\n Scheduler class: 'org.quartz.impl.StdScheduler' - running locally.\n Running since: Sat May 06 14:08:16 UTC 2023\n Not currently in standby mode.\n Number of jobs executed: 1\n Using thread pool 'org.quartz.simpl.SimpleThreadPool' - with 1 threads.\n Using job-store 'org.quartz.simpl.RAMJobStore' - which does not support persistence. and is not clustered.\n", 172 | "groups" : [ 173 | { 174 | "groupName" : "a89533ac-4cd9-46a0-b8a7-393c528252ce", 175 | "jobs" : [ 176 | { 177 | "jobName" : "PollConnectora89533ac-4cd9-46a0-b8a7-393c528252ce", 178 | "jobGroup" : "a89533ac-4cd9-46a0-b8a7-393c528252ce", 179 | "triggerCount" : 1, 180 | "nextTriggerDate" : "2023-05-07T00:00Z" 181 | } 182 | ] 183 | } 184 | ] 185 | }, 186 | { 187 | "summary" : "Quartz Scheduler (v2.1.7) '695c0176-fc79-4dcb-a92a-1240ceaca007' with instanceId 'NON_CLUSTERED'\n Scheduler class: 'org.quartz.impl.StdScheduler' - running locally.\n Running since: Sat May 06 14:08:16 UTC 2023\n Not currently in standby mode.\n Number of jobs executed: 290\n Using thread pool 'org.quartz.simpl.SimpleThreadPool' - with 1 threads.\n Using job-store 'org.quartz.simpl.RAMJobStore' - which does not support persistence. and is not clustered.\n", 188 | "groups" : [ 189 | { 190 | "groupName" : "695c0176-fc79-4dcb-a92a-1240ceaca007", 191 | "jobs" : [ 192 | { 193 | "jobName" : "PollConnector695c0176-fc79-4dcb-a92a-1240ceaca007", 194 | "jobGroup" : "695c0176-fc79-4dcb-a92a-1240ceaca007", 195 | "triggerCount" : 1, 196 | "nextTriggerDate" : "2023-05-06T14:32Z" 197 | } 198 | ] 199 | } 200 | ] 201 | }, 202 | { 203 | "summary" : "Quartz Scheduler (v2.1.7) '76ef38af-1226-43a7-b909-92105b0063a5' with instanceId 'NON_CLUSTERED'\n Scheduler class: 'org.quartz.impl.StdScheduler' - running locally.\n Running since: Sat May 06 14:08:16 UTC 2023\n Not currently in standby mode.\n Number of jobs executed: 289\n Using thread pool 'org.quartz.simpl.SimpleThreadPool' - with 1 threads.\n Using job-store 'org.quartz.simpl.RAMJobStore' - which does not support persistence. and is not clustered.\n", 204 | "groups" : [ 205 | { 206 | "groupName" : "76ef38af-1226-43a7-b909-92105b0063a5", 207 | "jobs" : [ 208 | { 209 | "jobName" : "PollConnector76ef38af-1226-43a7-b909-92105b0063a5", 210 | "jobGroup" : "76ef38af-1226-43a7-b909-92105b0063a5", 211 | "triggerCount" : 1, 212 | "nextTriggerDate" : "2023-05-06T14:32Z" 213 | } 214 | ] 215 | } 216 | ] 217 | } 218 | ] 219 | } 220 | ```` 221 | 222 | ## Image conversion 223 | 224 | _RunnenLate_ 225 | Does anyone use mirth for image conversion? 226 | 227 | _tiskinty_ 228 | I've used it for image compression before. 229 | 230 | _dforesman_ 231 | I have used it to call imagemagick to convert to PDF 232 | 233 | _RunnenLate_ 234 | I'm using the imageio library to convert PDF to TIF and visa versa. I'm just wondering if it's something that should be integrated into Mirth... thinking about making a plugin maybe. 235 | 236 | ## Mirth UIs from JSON config 237 | 238 | _jonb_ 239 | I have another good idea that I’m probably never going to implement. 240 | Inspired by [Josh's idea](https://github.com/nextgenhealthcare/connect/issues/5749). 241 | I think the hard part of writing plugins is the UI. IIRC the current standard is still “use Netbeans for Swing”. What if there was a sample plugin where the UI was just “heres a big text area, enter a JSON blob in that for your config object” then the plugin is responsible for parsing the JSON and applying its properties. This would let the developer focus on making the libraries and connector operation work well separately from the quirky bits of the Swing UI. 242 | 243 | ## Channel dependency graph 244 | 245 | _Richard_ developed a PowerShell script to export the channel dependencies to DOT language, then used graphviz to generate a SVG. This would be helpful directly in Mirth as a plugin. 246 | 247 | ## What is my IP 248 | 249 | _jonb_ 250 | MC plugin idea - “What is my IP?” 251 | Its trivial to do with a channel that calls ifconfig.me but a button would be handy. 252 | 253 | _pacmano_ 254 | what is my local IP, what is my public IP, what is my container host IP. 255 | 256 | _jonb_ 257 | How is the container host IP discoverable from software? 258 | Local interfaces can be enumerated 259 | 260 | _Jarrod_ with [SO question](https://stackoverflow.com/questions/9481865/getting-the-ip-address-of-the-current-machine-using-java) 261 | -------------------------------------------------------------------------------- /Connect/Report.js: -------------------------------------------------------------------------------- 1 | //Source transformer 2 | //======================= 3 | 4 | if (msg['row'][1]) { 5 | $c('HasMessage','yes'); 6 | var yesterday = msg['row'][0]['column1'].toString(); 7 | var yesteryear = yesterday.split("-")[0]; 8 | var yestermonth = yesterday.split("-")[1]; 9 | var yesterdayday = yesterday.split("-")[2].slice(0,2); 10 | var today = msg['row'][1]['column1'].toString(); 11 | var todayyear = today.split("-")[0]; 12 | var todaymonth = today.split("-")[1]; 13 | var todayday = today.split("-")[2].slice(0,2); 14 | var todayHeader = todaymonth + '-' + todayday + '-' + todayyear; 15 | var yesterdayHeader = yestermonth + '-' + yesterdayday + '-' + yesteryear; 16 | } else { 17 | $c('HasMessage','no'); 18 | var todayRaw = DateUtil.getCurrentDate("yyyy-MM-dd"); 19 | var todayHeader = DateUtil.getCurrentDate("MM-dd-yyyy"); 20 | var yesterdayRaw = org.joda.time.format.DateTimeFormat.forPattern('yyyy-MM-dd').print((new org.joda.time.DateTime()).minusDays(1)); 21 | var yesterdayHeader = org.joda.time.format.DateTimeFormat.forPattern('MM-dd-yyyy').print((new org.joda.time.DateTime()).minusDays(1)); 22 | var today = todayRaw.toString() + 'T09:00:00.000-0500'; 23 | // var yesterday = yesterdayRaw.toString() + 'T09:00:00.000-0400'; 24 | // var today = todayRaw.toString() + 'T23:55:00.000-0400'; 25 | var yesterday = yesterdayRaw.toString() + 'T09:00:00.000-0500'; 26 | } 27 | $c("Today",today); 28 | $c("Yesterday",yesterday); 29 | $c("todayHeader",todayHeader); 30 | $c("yesterdayHeader",yesterdayHeader); 31 | 32 | //3rd Destination Transformer 33 | //=============================== 34 | 35 | // ======================================= 36 | // GENERATE REPORTS AND EMAIL 37 | // ======================================= 38 | 39 | // get server info 40 | var server = java.net.InetAddress.getLocalHost().getHostName(); 41 | var configurationController = Packages.com.mirth.connect.server.controllers.ConfigurationController.getInstance(); 42 | var serverSettings = configurationController.getServerSettings(); 43 | var environmentName = serverSettings.getEnvironmentName(); 44 | var serverName = serverSettings.getServerName(); 45 | $c("Server", server); 46 | $c("ServerName", serverName); 47 | $c("ServerEnv", environmentName); 48 | 49 | // turn the API responses into new XML 50 | var users = new XML($('usersList')); 51 | var msg = new XML($('responseList')); 52 | 53 | // get the length of the lists for purposes of iteration 54 | var userlistLength = users['user'].length(); 55 | var listLength = msg['event'].length(); 56 | 57 | // create the user array for getting user name from the login id 58 | var userArray = []; 59 | for (var d = 0; d < userlistLength; d++) { 60 | var userName = users['user'][d]['username'].toString(); 61 | var userId = users['user'][d]['id'].toString(); 62 | userArray[userId] = userName; 63 | } 64 | 65 | // format for date/time(s) 66 | function epochToTZ(epochSeconds, tz, pattern) { 67 | const {Instant, ZoneId, format: {DateTimeFormatter}} = java.time; 68 | return Instant.ofEpochSecond(epochSeconds) 69 | .atZone(ZoneId.of(tz)) 70 | .format(DateTimeFormatter.ofPattern(pattern)); 71 | } 72 | 73 | const epochToEastern = epochSeconds => epochToTZ(epochSeconds, 'America/New_York', 'uuuu-MM-dd HH:mm'); 74 | 75 | // ======================================= 76 | // REPORT FOR USER WHO Created New User 77 | // ======================================= 78 | 79 | // create report title and header row 80 | var createReport = ''; 81 | createReport += ''; 82 | 83 | // iterate through events and add to report 84 | for (var i = 0; i < listLength; i++) { 85 | if (msg['event'][i]['name'].indexOf('Create new user') > -1) { 86 | var eventUserId = msg['event'][i]['userId'].toString(); 87 | var eventUserName = userArray[eventUserId]; 88 | //var eventTime = msg['event'][i]['dateTime'].toString(); 89 | var eventTime = msg['event'][i]['eventTime']['time'].toString(); 90 | eventTime = eventTime - (7 * 60 * 60 * 1000); 91 | eventTime = eventTime.toString().slice(0, -3); 92 | // var eventTimeStamp = epochToEastern(eventTime); 93 | var eventTimeStamp = java.time.Instant.ofEpochSecond(eventTime); 94 | eventTimeStamp = eventTimeStamp.toString().replace("T", " "); 95 | eventTimeStamp = eventTimeStamp.replace("Z", " "); 96 | var eventUserIp = msg['event'][i]['ipAddress'].toString(); 97 | var eventCreatedUser = msg['event'][i]['attributes']['entry']['string'][1].toString(); 98 | 99 | createReport += ''; 100 | } 101 | } 102 | createReport += '
CREATED USER:
User NameUser IDTimestampCreated User
' + eventUserName + '' + eventUserId + '' + eventTimeStamp + '' + eventCreatedUser + '
'; 103 | 104 | // ======================================= 105 | // REPORT FOR USER WHO Removed User 106 | // ======================================= 107 | 108 | // create report title and header row 109 | var removeReport = ''; 110 | removeReport += ''; 111 | 112 | // iterate through events and add to report 113 | for (var i = 0; i < listLength; i++) { 114 | if (msg['event'][i]['name'].indexOf('Remove user') > -1) { 115 | var eventUserId = msg['event'][i]['userId'].toString(); 116 | var eventUserName = userArray[eventUserId]; 117 | //var eventTime = msg['event'][i]['dateTime'].toString(); 118 | var eventTime = msg['event'][i]['eventTime']['time'].toString(); 119 | eventTime = eventTime - (7 * 60 * 60 * 1000); 120 | eventTime = eventTime.toString().slice(0, -3); 121 | // var eventTimeStamp = epochToEastern(eventTime); 122 | var eventTimeStamp = java.time.Instant.ofEpochSecond(eventTime); 123 | eventTimeStamp = eventTimeStamp.toString().replace("T", " "); 124 | eventTimeStamp = eventTimeStamp.replace("Z", " "); 125 | var eventUserIp = msg['event'][i]['ipAddress'].toString(); 126 | var removedUserId = msg['event'][i]['attributes']['entry']['string'][1].toString(); 127 | 128 | removeReport += ''; 129 | } 130 | } 131 | removeReport += '
REMOVED USER:
User NameUser IDTimestampRemoved User
' + eventUserName + '' + eventUserId + '' + eventTimeStamp + '' + removedUserId + '
'; 132 | 133 | // ======================================= 134 | // REPORT FOR USER WHO Updated User 135 | // ======================================= 136 | 137 | // create report title and header row 138 | var updateUserReport = ''; 139 | updateUserReport += ''; 140 | 141 | // iterate through events and add to report 142 | for (var i = 0; i < listLength; i++) { 143 | if (msg['event'][i]['name'].indexOf('Update user') > -1) { 144 | var eventUserId = msg['event'][i]['userId'].toString(); 145 | var eventUserName = userArray[eventUserId]; 146 | //var eventTime = msg['event'][i]['dateTime'].toString(); 147 | var eventTime = msg['event'][i]['eventTime']['time'].toString(); 148 | eventTime = eventTime - (7 * 60 * 60 * 1000); 149 | eventTime = eventTime.toString().slice(0, -3); 150 | // var eventTimeStamp = epochToEastern(eventTime); 151 | var eventTimeStamp = java.time.Instant.ofEpochSecond(eventTime); 152 | eventTimeStamp = eventTimeStamp.toString().replace("T", " "); 153 | eventTimeStamp = eventTimeStamp.replace("Z", " "); 154 | var eventUserIp = msg['event'][i]['ipAddress'].toString(); 155 | var updatedUserRaw = msg['event'][i]['attributes']['entry'][1]['string'][1].toString(); 156 | var updatedUserId = updatedUserRaw.substring(updatedUserRaw.lastIndexOf('id=') + 3, updatedUserRaw.lastIndexOf(',username=')); 157 | var updatedUserName = updatedUserRaw.substring(updatedUserRaw.lastIndexOf('username=') + 9).slice(0, -2); 158 | updateUserReport += ''; 159 | } 160 | } 161 | updateUserReport += '
UPDATED USER:
User NameUser IDTimestampUpdated User IDUpdated User
' + eventUserName + '' + eventUserId + '' + eventTimeStamp + '' + updatedUserId + '' + updatedUserName + '
'; 162 | 163 | // ======================================= 164 | // REPORT FOR USER WHO Updated A User's Password 165 | // ======================================= 166 | 167 | // create report title and header row 168 | var updateUserPassReport = ''; 169 | updateUserPassReport += ''; 170 | 171 | // iterate through events and add to report 172 | for (var i = 0; i < listLength; i++) { 173 | if (msg['event'][i]['name'].indexOf('Update a user\'s password') > -1) { 174 | var eventUserId = msg['event'][i]['userId'].toString(); 175 | var eventUserName = userArray[eventUserId]; 176 | //var eventTime = msg['event'][i]['dateTime'].toString(); 177 | var eventTime = msg['event'][i]['eventTime']['time'].toString(); 178 | eventTime = eventTime - (7 * 60 * 60 * 1000); 179 | eventTime = eventTime.toString().slice(0, -3); 180 | // var eventTimeStamp = epochToEastern(eventTime); 181 | var eventTimeStamp = java.time.Instant.ofEpochSecond(eventTime); 182 | eventTimeStamp = eventTimeStamp.toString().replace("T", " "); 183 | eventTimeStamp = eventTimeStamp.replace("Z", " "); 184 | var eventUserIp = msg['event'][i]['ipAddress'].toString(); 185 | var updatedPassUserId = msg['event'][i]['attributes']['entry']['string'][1].toString().trim(); 186 | var updatedPassUserName = userArray[updatedPassUserId]; 187 | 188 | updateUserPassReport += ''; 189 | } 190 | } 191 | updateUserPassReport += '
UPDATED USER PASSWORD:
User NameUser IDTimestampAffected User IDAffected User
' + eventUserName + '' + eventUserId + '' + eventTimeStamp + '' + updatedPassUserId + '' + updatedPassUserName + '
'; 192 | 193 | // ======================================= 194 | // REPORT FOR USER WHO Updated a User's Preferences 195 | // ======================================= 196 | 197 | // create report title and header row 198 | var updateUserPrefReport = ''; 199 | updateUserPrefReport += ''; 200 | 201 | // iterate through events and add to report 202 | for (var i = 0; i < listLength; i++) { 203 | if (msg['event'][i]['name'].indexOf('Set user preferences') > -1) { 204 | var eventUserId = msg['event'][i]['userId'].toString(); 205 | var eventUserName = userArray[eventUserId]; 206 | //var eventTime = msg['event'][i]['dateTime'].toString(); 207 | var eventTime = msg['event'][i]['eventTime']['time'].toString(); 208 | eventTime = eventTime - (7 * 60 * 60 * 1000); 209 | eventTime = eventTime.toString().slice(0, -3); 210 | // var eventTimeStamp = epochToEastern(eventTime); 211 | var eventTimeStamp = java.time.Instant.ofEpochSecond(eventTime); 212 | eventTimeStamp = eventTimeStamp.toString().replace("T", " "); 213 | eventTimeStamp = eventTimeStamp.replace("Z", " "); 214 | var eventUserIp = msg['event'][i]['ipAddress'].toString(); 215 | var updatedUserId = msg['event'][i]['attributes']['entry'][0]['string'][1].toString().trim(); 216 | var updatedUserName = userArray[updatedUserId]; 217 | 218 | updateUserPrefReport += ''; 219 | } 220 | } 221 | updateUserPrefReport += '
UPDATED USER PREFERENCES:
User NameUser IDTimestampAffected User IDAffected User
' + eventUserName + '' + eventUserId + '' + eventTimeStamp + '' + updatedUserId + '' + updatedUserName + '
'; 222 | 223 | // ======================================= 224 | // REPORT FOR USER WHO DEPLOYED CHANNELS 225 | // ======================================= 226 | 227 | // create report title and header row 228 | var depReport = ''; 229 | depReport += ''; 230 | 231 | // iterate through events and add to report 232 | for (var i = 0; i < listLength; i++) { 233 | if (msg['event'][i]['name'].indexOf('Deploy') > -1) { 234 | var eventUserId = msg['event'][i]['userId'].toString(); 235 | var eventUserName = userArray[eventUserId]; 236 | //var eventTime = msg['event'][i]['dateTime'].toString(); 237 | var eventTime = msg['event'][i]['eventTime']['time'].toString(); 238 | eventTime = eventTime - (7 * 60 * 60 * 1000); 239 | eventTime = eventTime.toString().slice(0, -3); 240 | // var eventTimeStamp = epochToEastern(eventTime); 241 | var eventTimeStamp = java.time.Instant.ofEpochSecond(eventTime); 242 | eventTimeStamp = eventTimeStamp.toString().replace("T", " "); 243 | eventTimeStamp = eventTimeStamp.replace("Z", " "); 244 | var eventUserIp = msg['event'][i]['ipAddress'].toString(); 245 | var channelInfo = msg['event'][i]['attributes']['entry'][2]['string'][1].toString(); 246 | var channelName = channelInfo.substring(channelInfo.lastIndexOf('e=') + 2); 247 | channelName = channelName.slice(0, -2); 248 | if (channelName != undefined) { 249 | 250 | depReport += ''; 251 | } 252 | } 253 | 254 | } 255 | depReport += '
DEPLOYED CHANNELS:
User NameUser IDTimestampChannel Name
' + eventUserName + '' + eventUserId + '' + eventTimeStamp + '' + channelName + '
'; 256 | 257 | // ======================================= 258 | // REPORT FOR USER WHO UNDEPLOYED CHANNELS 259 | // ======================================= 260 | 261 | // create report title and header row 262 | var undepReport = ''; 263 | undepReport += ''; 264 | 265 | // iterate through events and add to report 266 | for (var i = 0; i < listLength; i++) { 267 | if (msg['event'][i]['name'].indexOf('Undeploy') > -1) { 268 | var eventUserId = msg['event'][i]['userId'].toString(); 269 | var eventUserName = userArray[eventUserId]; 270 | //var eventTime = msg['event'][i]['dateTime'].toString(); 271 | var eventTime = msg['event'][i]['eventTime']['time'].toString(); 272 | eventTime = eventTime - (7 * 60 * 60 * 1000); 273 | eventTime = eventTime.toString().slice(0, -3); 274 | // var eventTimeStamp = epochToEastern(eventTime); 275 | var eventTimeStamp = java.time.Instant.ofEpochSecond(eventTime); 276 | eventTimeStamp = eventTimeStamp.toString().replace("T", " "); 277 | eventTimeStamp = eventTimeStamp.replace("Z", " "); 278 | var eventUserIp = msg['event'][i]['ipAddress'].toString(); 279 | var channelInfo = msg['event'][i]['attributes']['entry'][1]['string'][1].toString(); 280 | var channelName = channelInfo.substring(channelInfo.lastIndexOf('e=') + 2); 281 | channelName = channelName.slice(0, -2); 282 | if (channelName != undefined) { 283 | 284 | undepReport += ''; 285 | } 286 | } 287 | 288 | } 289 | undepReport += '
UNDEPLOYED CHANNELS:
User NameUser IDTimestampChannel Name
' + eventUserName + '' + eventUserId + '' + eventTimeStamp + '' + channelName + '
'; 290 | 291 | // ======================================= 292 | // REPORT FOR USER WHO STARTED CHANNELS 293 | // ======================================= 294 | 295 | // create report title and header row 296 | var startReport = ''; 297 | startReport += ''; 298 | 299 | // iterate through events and add to report 300 | for (var i = 0; i < listLength; i++) { 301 | if (msg['event'][i]['name'].indexOf('Start channels') > -1) { 302 | var eventUserId = msg['event'][i]['userId'].toString(); 303 | var eventUserName = userArray[eventUserId]; 304 | //var eventTime = msg['event'][i]['dateTime'].toString(); 305 | var eventTime = msg['event'][i]['eventTime']['time'].toString(); 306 | eventTime = eventTime - (7 * 60 * 60 * 1000); 307 | eventTime = eventTime.toString().slice(0, -3); 308 | // var eventTimeStamp = epochToEastern(eventTime); 309 | var eventTimeStamp = java.time.Instant.ofEpochSecond(eventTime); 310 | eventTimeStamp = eventTimeStamp.toString().replace("T", " "); 311 | eventTimeStamp = eventTimeStamp.replace("Z", " "); 312 | var eventUserIp = msg['event'][i]['ipAddress'].toString(); 313 | var channelInfo = msg['event'][i]['attributes']['entry'][1]['string'][1].toString(); 314 | var channelName = channelInfo.substring(channelInfo.lastIndexOf('e=') + 2); 315 | channelName = channelName.slice(0, -2); 316 | if (channelName != undefined) { 317 | 318 | startReport += ''; 319 | } 320 | } 321 | } 322 | startReport += '
STARTED CHANNELS:
User NameUser IDTimestampChannel Name
' + eventUserName + '' + eventUserId + '' + eventTimeStamp + '' + channelName + '
'; 323 | 324 | // ======================================= 325 | // REPORT FOR USER WHO STOPPED CHANNELS 326 | // ======================================= 327 | 328 | // create report title and header row 329 | var stopReport = ''; 330 | stopReport += ''; 331 | 332 | // iterate through events and add to report 333 | for (var i = 0; i < listLength; i++) { 334 | if (msg['event'][i]['name'].indexOf('Stop channels') > -1) { 335 | var eventUserId = msg['event'][i]['userId'].toString(); 336 | var eventUserName = userArray[eventUserId]; 337 | //var eventTime = msg['event'][i]['dateTime'].toString(); 338 | var eventTime = msg['event'][i]['eventTime']['time'].toString(); 339 | eventTime = eventTime - (7 * 60 * 60 * 1000); 340 | eventTime = eventTime.toString().slice(0, -3); 341 | // var eventTimeStamp = epochToEastern(eventTime); 342 | var eventTimeStamp = java.time.Instant.ofEpochSecond(eventTime); 343 | eventTimeStamp = eventTimeStamp.toString().replace("T", " "); 344 | eventTimeStamp = eventTimeStamp.replace("Z", " "); 345 | var eventUserIp = msg['event'][i]['ipAddress'].toString(); 346 | var channelInfo = msg['event'][i]['attributes']['entry'][1]['string'][1].toString(); 347 | var channelName = channelInfo.substring(channelInfo.lastIndexOf('e=') + 2); 348 | channelName = channelName.slice(0, -2); 349 | if (channelName != undefined) { 350 | 351 | stopReport += ''; 352 | } 353 | } 354 | } 355 | stopReport += '
STOPPED CHANNELS:
User NameUser IDTimestampChannel Name
' + eventUserName + '' + eventUserId + '' + eventTimeStamp + '' + channelName + '
'; 356 | 357 | // ======================================= 358 | // REPORT FOR USER WHO UPDATED CHANNELS 359 | // ======================================= 360 | 361 | // create report title and header row 362 | var updateChanReport = ''; 363 | updateChanReport += ''; 364 | 365 | // iterate through events and add to report 366 | for (var i = 0; i < listLength; i++) { 367 | if (msg['event'][i]['name'].indexOf('Update channel') > -1) { 368 | var eventUserId = msg['event'][i]['userId'].toString(); 369 | var eventUserName = userArray[eventUserId]; 370 | //var eventTime = msg['event'][i]['dateTime'].toString(); 371 | var eventTime = msg['event'][i]['eventTime']['time'].toString(); 372 | eventTime = eventTime - (7 * 60 * 60 * 1000); 373 | eventTime = eventTime.toString().slice(0, -3); 374 | // var eventTimeStamp = epochToEastern(eventTime); 375 | var eventTimeStamp = java.time.Instant.ofEpochSecond(eventTime); 376 | eventTimeStamp = eventTimeStamp.toString().replace("T", " "); 377 | eventTimeStamp = eventTimeStamp.replace("Z", " "); 378 | var eventUserIp = msg['event'][i]['ipAddress'].toString(); 379 | var channelInfo = msg['event'][i]['attributes']['entry'][0]['string'][1].toString(); 380 | var channelName = channelInfo.substring(channelInfo.lastIndexOf('name=') + 5).slice(0, 60); 381 | channelName = channelName.slice(0, -2); 382 | if (channelName != undefined) { 383 | 384 | updateChanReport += ''; 385 | } 386 | } 387 | } 388 | updateChanReport += '
UPDATED CHANNELS:
User NameUser IDTimestampChannel Name
' + eventUserName + '' + eventUserId + '' + eventTimeStamp + '' + channelName + '
'; 389 | 390 | // ======================================= 391 | // REPORT FOR USER WHO UPDATED CODE TEMPLATES 392 | // ======================================= 393 | 394 | // create report title and header row 395 | var updateCodeReport = ''; 396 | updateCodeReport += ''; 397 | 398 | // iterate through events and add to report 399 | for (var i = 0; i < listLength; i++) { 400 | if (msg['event'][i]['name'].indexOf('Update code templates and libraries') > -1) { 401 | var eventUserId = msg['event'][i]['userId'].toString(); 402 | var eventUserName = userArray[eventUserId]; 403 | //var eventTime = msg['event'][i]['dateTime'].toString(); 404 | var eventTime = msg['event'][i]['eventTime']['time'].toString(); 405 | eventTime = eventTime - (7 * 60 * 60 * 1000); 406 | eventTime = eventTime.toString().slice(0, -3); 407 | // var eventTimeStamp = epochToEastern(eventTime); 408 | var eventTimeStamp = java.time.Instant.ofEpochSecond(eventTime); 409 | eventTimeStamp = eventTimeStamp.toString().replace("T", " "); 410 | eventTimeStamp = eventTimeStamp.replace("Z", " "); 411 | var eventUserIp = msg['event'][i]['ipAddress'].toString(); 412 | 413 | updateCodeReport += ''; 414 | } 415 | } 416 | updateCodeReport += '
UPDATED CODE TEMPLATES:
User NameUser IDTimestamp
' + eventUserName + '' + eventUserId + '' + eventTimeStamp + '
'; 417 | 418 | // ======================================= 419 | // REPORT FOR USER WHO PROCESSED MESSAGES 420 | // ======================================= 421 | 422 | // create report title and header row 423 | var processMessageReport = ''; 424 | processMessageReport += ''; 425 | 426 | // iterate through events and add to report 427 | for (var i = 0; i < listLength; i++) { 428 | if (msg['event'][i]['name'].indexOf('Process messages') > -1) { 429 | var eventUserId = msg['event'][i]['userId'].toString(); 430 | var eventUserName = userArray[eventUserId]; 431 | //var eventTime = msg['event'][i]['dateTime'].toString(); 432 | var eventTime = msg['event'][i]['eventTime']['time'].toString(); 433 | eventTime = eventTime - (7 * 60 * 60 * 1000); 434 | eventTime = eventTime.toString().slice(0, -3); 435 | // var eventTimeStamp = epochToEastern(eventTime); 436 | var eventTimeStamp = java.time.Instant.ofEpochSecond(eventTime); 437 | eventTimeStamp = eventTimeStamp.toString().replace("T", " "); 438 | eventTimeStamp = eventTimeStamp.replace("Z", " "); 439 | var eventUserIp = msg['event'][i]['ipAddress'].toString(); 440 | 441 | processMessageReport += ''; 442 | } 443 | } 444 | processMessageReport += '
PROCESSED MESSAGES:
User NameUser IDTimestamp
' + eventUserName + '' + eventUserId + '' + eventTimeStamp + '
'; 445 | 446 | // ======================================= 447 | // REPORT FOR USER WHO REPROCESSED MESSAGES 448 | // ======================================= 449 | 450 | // create report title and header row 451 | var reprocessMessageReport = ''; 452 | reprocessMessageReport += ''; 453 | 454 | // iterate through events and add to report 455 | for (var i = 0; i < listLength; i++) { 456 | if (msg['event'][i]['name'].indexOf('Reprocess messages') > -1) { 457 | var eventUserId = msg['event'][i]['userId'].toString(); 458 | var eventUserName = userArray[eventUserId]; 459 | //var eventTime = msg['event'][i]['dateTime'].toString(); 460 | var eventTime = msg['event'][i]['eventTime']['time'].toString(); 461 | eventTime = eventTime - (7 * 60 * 60 * 1000); 462 | eventTime = eventTime.toString().slice(0, -3); 463 | // var eventTimeStamp = epochToEastern(eventTime); 464 | var eventTimeStamp = java.time.Instant.ofEpochSecond(eventTime); 465 | eventTimeStamp = eventTimeStamp.toString().replace("T", " "); 466 | eventTimeStamp = eventTimeStamp.replace("Z", " "); 467 | var eventUserIp = msg['event'][i]['ipAddress'].toString(); 468 | 469 | reprocessMessageReport += ''; 470 | } 471 | } 472 | reprocessMessageReport += '
REPROCESSED MESSAGES:
User NameUser IDTimestamp
' + eventUserName + '' + eventUserId + '' + eventTimeStamp + '
'; 473 | 474 | // ======================================= 475 | // REPORT FOR UNSUCCESSFUL LOGIN ATTEMPTS 476 | // ======================================= 477 | 478 | // create report title and header row 479 | var unsuccessfulLoginReport = ''; 480 | unsuccessfulLoginReport += ''; 481 | 482 | // iterate through events and add to report 483 | for (var i = 0; i < listLength; i++) { 484 | if (msg['event'][i]['name'].indexOf('Login') > -1) { 485 | if (msg['event'][i]['outcome'].indexOf('FAILURE') > -1) { 486 | //var eventUserId = msg['event'][i]['userId'].toString(); 487 | //var eventUserName = userArray[eventUserId]; 488 | //var eventTime = msg['event'][i]['dateTime'].toString(); 489 | var eventUserName = msg['event'][i]['attributes']['entry']['string'][1].toString(); 490 | var eventUserId = userArray.indexOf(eventUserName); 491 | var eventTime = msg['event'][i]['eventTime']['time'].toString(); 492 | eventTime = eventTime - (7 * 60 * 60 * 1000); 493 | eventTime = eventTime.toString().slice(0, -3); 494 | // var eventTimeStamp = epochToEastern(eventTime); 495 | var eventTimeStamp = java.time.Instant.ofEpochSecond(eventTime); 496 | eventTimeStamp = eventTimeStamp.toString().replace("T", " "); 497 | eventTimeStamp = eventTimeStamp.replace("Z", " "); 498 | var eventUserIp = msg['event'][i]['ipAddress'].toString(); 499 | 500 | unsuccessfulLoginReport += ''; 501 | } 502 | } 503 | } 504 | unsuccessfulLoginReport += '
FAILED LOGIN ATTEMPTS:
User NameUser IDTimestamp
' + eventUserName + '' + eventUserId + '' + eventTimeStamp + '
'; 505 | 506 | 507 | // ======================================= 508 | // REPORT FOR SUCCESSFUL LOGIN 509 | // ======================================= 510 | 511 | // create report title and header row 512 | var successfulLoginReport = ''; 513 | successfulLoginReport += ''; 514 | 515 | // iterate through events and add to report 516 | for (var i = 0; i < listLength; i++) { 517 | if (msg['event'][i]['name'].indexOf('Login') > -1) { 518 | if (msg['event'][i]['outcome'].indexOf('SUCCESS') > -1) { 519 | //var eventUserId = msg['event'][i]['userId'].toString(); 520 | //var eventUserName = userArray[eventUserId]; 521 | //var eventTime = msg['event'][i]['dateTime'].toString(); 522 | var eventUserName = msg['event'][i]['attributes']['entry']['string'][1].toString(); 523 | var eventUserId = userArray.indexOf(eventUserName); 524 | var eventTime = msg['event'][i]['eventTime']['time'].toString(); 525 | eventTime = eventTime - (7 * 60 * 60 * 1000); 526 | eventTime = eventTime.toString().slice(0, -3); 527 | // var eventTimeStamp = epochToEastern(eventTime); 528 | var eventTimeStamp = java.time.Instant.ofEpochSecond(eventTime); 529 | eventTimeStamp = eventTimeStamp.toString().replace("T", " "); 530 | eventTimeStamp = eventTimeStamp.replace("Z", " "); 531 | var eventUserIp = msg['event'][i]['ipAddress'].toString(); 532 | 533 | successfulLoginReport += ''; 534 | } 535 | } 536 | } 537 | successfulLoginReport += '
SUCCESSFUL LOGINS:
User NameUser IDTimestamp
' + eventUserName + '' + eventUserId + '' + eventTimeStamp + '
'; 538 | 539 | 540 | 541 | // ======================================= 542 | // REPORT FOR LOGOUT 543 | // ======================================= 544 | 545 | // create report title and header row 546 | var logoutReport = ''; 547 | logoutReport += ''; 548 | 549 | // iterate through events and add to report 550 | for (var i = 0; i < listLength; i++) { 551 | if (msg['event'][i]['name'].indexOf('Logout') > -1) { 552 | if (msg['event'][i]['outcome'].indexOf('SUCCESS') > -1) { 553 | var eventUserId = msg['event'][i]['userId'].toString(); 554 | var eventUserName = userArray[eventUserId]; 555 | //var eventTime = msg['event'][i]['dateTime'].toString(); 556 | //var eventUserName = msg['event'][i]['attributes']['entry']['string'][1].toString(); 557 | //var eventUserId = userArray.indexOf(eventUserName); 558 | var eventTime = msg['event'][i]['eventTime']['time'].toString(); 559 | eventTime = eventTime - (7 * 60 * 60 * 1000); 560 | eventTime = eventTime.toString().slice(0, -3); 561 | // var eventTimeStamp = epochToEastern(eventTime); 562 | var eventTimeStamp = java.time.Instant.ofEpochSecond(eventTime); 563 | eventTimeStamp = eventTimeStamp.toString().replace("T", " "); 564 | eventTimeStamp = eventTimeStamp.replace("Z", " "); 565 | var eventUserIp = msg['event'][i]['ipAddress'].toString(); 566 | 567 | logoutReport += ''; 568 | } 569 | } 570 | } 571 | logoutReport += '
LOGOUTS:
User NameUser IDTimestamp
' + eventUserName + '' + eventUserId + '' + eventTimeStamp + '
'; 572 | 573 | 574 | // ======================================= 575 | // CREATE OUTBOUND REPORT AND SEND EMAIL 576 | // ======================================= 577 | 578 | // add variables to the channel map 579 | // $c("userList", JSON.stringify(userArray)); 580 | // $c("createReport", createReport); 581 | // $c("removeReport", removeReport); 582 | // $c("updateUserReport", updateUserReport); 583 | // $c("updateUserPassReport", updateUserPassReport); 584 | // $c("updateUserPrefReport", updateUserPrefReport); 585 | // $c("depReport", depReport); 586 | // $c("undepReport", undepReport); 587 | // $c("startReport", startReport); 588 | // $c("stopReport", stopReport); 589 | // $c("updateChanReport", updateChanReport); 590 | // $c("updateCodeReport", updateCodeReport); 591 | // $c("processMessageReport", processMessageReport); 592 | // $c("reprocessMessageReport", reprocessMessageReport); 593 | // $c("unsuccessfulLoginReport", unsuccessfulLoginReport); 594 | // $c("successfulLoginReport", successfulLoginReport); 595 | // $c("logoutReport", logoutReport); 596 | 597 | // create outbound report 598 | var outReport = ''; 599 | outReport += ''; 600 | outReport += '

Mirth Administrative Events

' + environmentName + '    ' + serverName + '    ' + server + '
From: ' + $('yesterdayHeader') + ' 9am EST     To: ' + $('todayHeader') + ' 9am EST

'; 601 | //outReport += '
' + environmentName + '    ' + serverName + '    ' + server + '
From: 03-01-2022 9am EST     To: 03-10-2022 9am EST

'; 602 | outReport += createReport + '
' + removeReport + '
' + updateUserReport + '
' + updateUserPassReport + '
' + updateUserPrefReport + '
' + depReport + '
' + undepReport + '
' + startReport + '
' + stopReport + '
' + updateChanReport + '
' + updateCodeReport + '
' + processMessageReport + '
' + reprocessMessageReport + '
' + unsuccessfulLoginReport + '
' + successfulLoginReport + '
' + logoutReport; 603 | outReport += '
'; 604 | $c("outReport", outReport); 605 | -------------------------------------------------------------------------------- /Connect/SendFileToEmail.xml: -------------------------------------------------------------------------------- 1 | 2 | 91bfcd1e-33bb-4a7f-894b-ccfa8689b9dd 3 | 2 4 | SendFileToEmail 5 | 6 | 1 7 | 8 | 0 9 | sourceConnector 10 | 11 | 12 | 13 | TIME 14 | true 15 | 5000 16 | 9 17 | 0 18 | 19 | 20 | true 21 | 22 | false 23 | true 24 | false 25 | true 26 | true 27 | true 28 | true 29 | true 30 | 31 | 1 32 | true 33 | 8 34 | 0 35 | 17 36 | 0 37 | 38 | 39 | 40 | None 41 | true 42 | false 43 | false 44 | 1 45 | 46 | 47 | Default Resource 48 | [Default Resource] 49 | 50 | 51 | 1000 52 | 53 | FILE 54 | //path/to/files 55 | * 56 | false 57 | false 58 | true 59 | true 60 | anonymous 61 | anonymous 62 | 10000 63 | true 64 | true 65 | true 66 | MOVE 67 | //path/to/archives 68 | ${DATE}_${originalFilename} 69 | MOVE 70 | AFTER_PROCESSING 71 | //path/to/errors 72 | ${DATE}_${originalFilename} 73 | false 74 | 1000 75 | 0 76 | 77 | true 78 | date 79 | false 80 | DEFAULT_ENCODING 81 | 82 | 83 | 84 | DELIMITED 85 | DELIMITED 86 | 87 | 88 | , 89 | \n 90 | " 91 | true 92 | \ 93 | false 94 | true 95 | 96 | 97 | , 98 | \n 99 | " 100 | true 101 | \ 102 | 103 | 104 | Record 105 | 0 106 | 107 | false 108 | 109 | 110 | 111 | 112 | 113 | 114 | , 115 | \n 116 | " 117 | true 118 | \ 119 | false 120 | true 121 | 122 | 123 | , 124 | \n 125 | " 126 | true 127 | \ 128 | 129 | 130 | Record 131 | 0 132 | 133 | false 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | File Reader 143 | SOURCE 144 | true 145 | true 146 | 147 | 148 | 149 | 1 150 | Send Email 151 | 152 | 153 | 154 | false 155 | false 156 | 10000 157 | false 158 | 0 159 | false 160 | false 161 | 1 162 | 163 | false 164 | 165 | 166 | Default Resource 167 | [Default Resource] 168 | 169 | 170 | 1000 171 | true 172 | 173 | smtp.example.com 174 | 25 175 | false 176 | 0.0.0.0 177 | 0 178 | 5000 179 | none 180 | false 181 | 182 | 183 | your_email@example.com 184 | your_sender_email@example.com 185 | 186 | 187 | 188 | 189 | Your Subject Here 190 | DEFAULT_ENCODING 191 | false 192 | Your Body Here 193 | 194 | 195 | ${DATE}_${originalFilename} 196 | ${message.rawData} 197 | text/csv 198 | 199 | 200 | 201 | 202 | 203 | DELIMITED 204 | DELIMITED 205 | 206 | 207 | , 208 | \n 209 | " 210 | true 211 | \ 212 | false 213 | true 214 | 215 | 216 | , 217 | \n 218 | " 219 | true 220 | \ 221 | 222 | 223 | Record 224 | 0 225 | 226 | false 227 | 228 | 229 | 230 | 231 | 232 | 233 | , 234 | \n 235 | " 236 | true 237 | \ 238 | false 239 | true 240 | 241 | 242 | , 243 | \n 244 | " 245 | true 246 | \ 247 | 248 | 249 | Record 250 | 0 251 | 252 | false 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | DELIMITED 261 | DELIMITED 262 | 263 | 264 | , 265 | \n 266 | " 267 | true 268 | \ 269 | false 270 | true 271 | 272 | 273 | , 274 | \n 275 | " 276 | true 277 | \ 278 | 279 | 280 | Record 281 | 0 282 | 283 | false 284 | 285 | 286 | 287 | 288 | 289 | 290 | , 291 | \n 292 | " 293 | true 294 | \ 295 | false 296 | true 297 | 298 | 299 | , 300 | \n 301 | " 302 | true 303 | \ 304 | 305 | 306 | Record 307 | 0 308 | 309 | false 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | SMTP Sender 319 | DESTINATION 320 | true 321 | true 322 | 323 | 324 | // Modify the message variable below to pre process data 325 | return message; 326 | // This script executes once after a message has been processed 327 | // Responses returned from here will be stored as "Postprocessor" in the response map 328 | return; 329 | // This script executes once when the channel is deployed 330 | // You only have access to the globalMap and globalChannelMap here to persist data 331 | return; 332 | // This script executes once when the channel is undeployed 333 | // You only have access to the globalMap and globalChannelMap here to persist data 334 | return; 335 | 336 | true 337 | DISABLED 338 | false 339 | false 340 | false 341 | false 342 | STARTED 343 | false 344 | 345 | 346 | SOURCE 347 | STRING 348 | mirth_source 349 | 350 | 351 | TYPE 352 | STRING 353 | mirth_type 354 | 355 | 356 | 357 | None 358 | 359 | 360 | 361 | 362 | Default Resource 363 | [Default Resource] 364 | 365 | 366 | 367 | 368 | 369 | true 370 | 371 | 372 | America/Chicago 373 | 374 | 375 | 1 376 | 1 377 | false 378 | 379 | 380 | 381 | -------------------------------------------------------------------------------- /Connect/ServiceIdeas.md: -------------------------------------------------------------------------------- 1 | 2 | # Service Ideas 3 | 4 | 5 |
6 | Table of Contents 7 |
    8 |
  1. InterOp for Pharmacy Inventory
  2. 9 |
10 |
11 | 12 | ## InterOp for Pharmacy Inventory 13 | 14 | Chat [link](https://mirthconnect.slack.com/archives/CFMP7CNHX/p1688674954765619) 15 | 16 | _jonb_ 17 | Is anyone doing interop for pharmacy inventory? 18 | Just got hit with a “we dont have this drug in, you use your time to call around to all the pharmacists in town and see if they have it”. Surely there should be a way to broadcast the drug, dose, and insurance group without disclosing PHI/PII to get a response back from the pharmacies with inventory and pricing. I’m mildly annoyed but like if someone is really sick or working-poor and can’t call around what are they supposed to do? This feels like a solvable problem 19 | 20 | _joshm_ 21 | generally, my pharmacy has called around to other locations in effort to locate it for me. They even called a competitor because they couldn’t get it at one of their own stores. Granted, that’s still a lot of manual effort that could be solved with an API or similar. Then you just have to call one place to verify accuracy of the inventory. feels like a natural extension of something like GoodRx. 22 | 23 | _jonb_ 24 | I’m happy to have GoodRx in the market but it feels scammy. Since if you buy thru GoodRx it doesn’t hit your deductible so insurance still wins. Or when I’m buying $100s in meds they hand me a coupon, that feels really careless and cheap. 25 | 26 | _CreepySheep_ 27 | so what shall we do about this drug inventory situation? Mirth based API to query pharmacies' inventory systems? 28 | 29 | _jonb_ 30 | roughly yes. if they expose those inventories at all. 31 | 32 | _Anthony Master_ 33 | We do but only inter hospital between Pyxis machines. We don't communicate those pulls/inventory external to our hospital. 34 | So how does something like this become a thing? Our town could use it too from my own experience, we have half a dozen or more pharmacies in town and they run into the same problem from my customer perspective. What would it take to get on board the big ones like Walmart, CVS, Walgreens? And there would have to be some kind of local to it as well, I don't see a need to usually know if three states over has something. 35 | 36 | _jonb_ 37 | Geolocation is important, I assume openmaps would be close enough 38 | The risks with getting buy in from ANY pharmacy, big or small would probably be: 39 | 40 | 1. Having a queryable API 41 | 1. Dealing with any PII/PHI issues, even if we don't send PII/PHI they'd likely want a BAA in place 42 | 1. I am assuming that none of the participants want open, up-front pricing because if they did they would have solved this already and be able to advertise prices 43 | 44 | actually due to prescribing regs we can probably limit it to in state... how does that work? Can a doc in Ohio prescribe in Indiana? 45 | 46 | _Anthony Master_ 47 | IMO, It would be harder to get a buy-in for an event based messaging that sends all of the Pharmacy's inventory to the Cloud somewhere to be stored in a db to be queryable by others. I think though, that if you play it off like, let's just automate what we already do. And not even need to share the price, just if needed quantity is on hand and available. So a workflow like this maybe: 48 | 49 | 1. Pharmacy receives a rx 50 | 1. Checks local inventory and cannot meet rx quantity 51 | 1. Sends a query out to a central API to check for nearby avail 52 | 1. Central API sends query out to (preset range/agreed parties) to check for specific avail 53 | 1. Other Pharmac(y|ies) receive query for avail from inventory 54 | 1. Other Pharmac(y|ies) respond with yes/no can meet request 55 | 1. After allotted timeout or after all selected Pharmac(y|ies) reply, Central API sends out the tallied reply to the original requestor. 56 | 57 | This would not need to long time store inventories in the cloud, and a Pharmacy could even make their own reply process less automated in case of lacking interrop experts. For instance, Mom/Pa Pharmacy could interrop by email only, and receive requests and reply via email instead of API. They might not be as fast, but can still be a part of the process if the timeout gives them enough time to receive the email, check their inventory, and reply. 58 | 59 | I can talk to our veteran hospital pharmacist next week and see what his feedback is on this idea. 60 | 61 | Maybe try to contact [this group](https://www.hcinnovationgroup.com/interoperability-hie/interoperability/news/53063666/onc-hitac-creates-pharmacy-interoperability-task-force) 62 | 63 | And there is a [meeting scheduled for 7/12/23](https://www.healthit.gov/hitac/events/pharmacy-interoperability-and-emerging-therapeutics-task-force-2023-1), 64 | I signed up for this zoom webinar, I wonder if I will have a voice or just be viewer/listener only? 65 | 66 | _joshm_ 67 | Just having availability would probably be sufficient. 68 | I dare say that MOST pharmacies will be relatively competitive on price. 69 | 70 | _jonb_ 71 | Main committee page is [here](https://www.healthit.gov/hitac/committees/pharmacy-interoperability-and-emerging-therapeutics-task-force-2023). I have a tab open to skim their publications 72 | 73 | _Anthony Master_'s pharmacist: 74 | _In years past the pharmacist would call the other pharmacies in town to locate a medication for their patient. Currently with the issues with the supply chain for meds being so challenging each pharmacy will have 15-20 out of stock situations and also have 50-100 prescriptions not picked up each week. With these issues and the one you presented you can understand why the pharmacy askes the patient to locate the medication. I really like your solution but don’t see very many people having the time or resources to implement._ 75 | 76 | _jonb_ 77 | The time cost to the pharmacies might be a good proposition, I sat on hold for three pharmacies, 10 minutes each. 30 minutes of time for a pharmacist or tech gets costly. 78 | 79 | _Anthony Master_ 80 | The Pharmacy Interoperability and Emerging Therapeutics Task Force 2023 Roster: 81 | 82 | chat so far. This is already on the radar of discussion in some manner. Anyone heard of RxChange before? or NCPDP Pharmacy Product Locator Task Group? 83 | 84 | 85 | 86 | 87 | 88 | 89 | _Editor_: there's more discussed in the thread, but the fancy Task Force is stalled by red tape and canceled their future meetings. 90 | -------------------------------------------------------------------------------- /Connect/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.1" 2 | services: 3 | mc: 4 | image: nextgenhealthcare/connect:4.1.0 5 | platform: linux/amd64 6 | environment: 7 | - DATABASE=postgres 8 | - DATABASE_URL=jdbc:postgresql://db:5432/mirthdb 9 | - DATABASE_MAX_CONNECTIONS=20 10 | - DATABASE_USERNAME=mirthdb 11 | - DATABASE_PASSWORD=mirthdb 12 | - DATABASE_MAX_RETRY=2 13 | - DATABASE_RETRY_WAIT=10000 14 | - KEYSTORE_STOREPASS=docker_storepass 15 | - KEYSTORE_KEYPASS=docker_keypass 16 | - VMOPTIONS=-Xmx512m 17 | - _MP_HTTPS_PORT=8493 18 | ports: 19 | - 8090:8080/tcp 20 | - 8493:8493/tcp 21 | depends_on: 22 | - db 23 | volumes: 24 | - ./data:/tmp/data 25 | db: 26 | image: postgres:12 27 | environment: 28 | - POSTGRES_USER=mirthdb 29 | - POSTGRES_PASSWORD=mirthdb 30 | - POSTGRES_DB=mirthdb 31 | ports: 32 | - "5436:5432" 33 | volumes: 34 | - pgdata:/var/lib/postgresql/data 35 | volumes: 36 | pgdata: {} -------------------------------------------------------------------------------- /Connect/getCached.js: -------------------------------------------------------------------------------- 1 | //created by Micheal Hobbs? 2 | function getCached(cacheKey, params, callback, maxCacheSize = 100) { 3 | // Generate the key for the specific data 4 | const key = JSON.stringify(params) 5 | ​ 6 | // If no cache exists, create one 7 | if (!$gc(cacheKey)) { 8 | $gc(cacheKey, JSON.stringify({keys: [], data: {}})) 9 | } 10 | ​ 11 | // Get the cache 12 | let cache = JSON.parse($gc(cacheKey)) 13 | ​ 14 | if (cache.data[key]) { 15 | // Remove key from its current position and push it to the end (most recently used) 16 | const index = cache.keys.indexOf(key) 17 | if (index > -1) { 18 | cache.keys.splice(index, 1) 19 | } 20 | cache.keys.push(key) 21 | $gc(cacheKey, JSON.stringify(cache)) 22 | return cache.data[key] 23 | } else { 24 | const result = callback(params) 25 | ​ 26 | // If we are about to exceed the max cache size, evict the least recently used item 27 | if (cache.keys.length >= maxCacheSize) { 28 | const lruKey = cache.keys.shift() 29 | delete cache.data[lruKey] 30 | } 31 | ​ 32 | // Add the new item to the cache 33 | cache.data[key] = result 34 | cache.keys.push(key) 35 | $gc(cacheKey, JSON.stringify(cache)) 36 | return result 37 | } 38 | } -------------------------------------------------------------------------------- /Connect/screenshot_2023-07-19_at_2.12.27_pm_720.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rogin/Snippets/862fe77a4dd2ef37766c7252e0a8b58b61bcbca6/Connect/screenshot_2023-07-19_at_2.12.27_pm_720.png -------------------------------------------------------------------------------- /Connect/screenshot_2023-07-19_at_2.24.41_pm_720.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rogin/Snippets/862fe77a4dd2ef37766c7252e0a8b58b61bcbca6/Connect/screenshot_2023-07-19_at_2.24.41_pm_720.png -------------------------------------------------------------------------------- /Connect/screenshot_2023-07-19_at_2.25.05_pm_720.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rogin/Snippets/862fe77a4dd2ef37766c7252e0a8b58b61bcbca6/Connect/screenshot_2023-07-19_at_2.25.05_pm_720.png -------------------------------------------------------------------------------- /Connect/too-big-sample-1.txt: -------------------------------------------------------------------------------- 1 | [2023-03-30 06:22:51,994] ERROR (com.mirth.connect.donkey.server.channel.DestinationChain:198): Error processing destination extract data and set vars for channel a57e5ff4-df52-4de2-be27-c2aef5b7c2b6. 2 | com.mirth.connect.donkey.util.xstream.SerializerException: com.thoughtworks.xstream.converters.ConversionException: Failed calling method---- Debugging information ----message : Failed calling methodcause-exception : com.thoughtworks.xstream.converters.ConversionExceptioncause-message : Failed calling methodmethod : org.mozilla.javascript.ImporterTopLevel.writeObject()------------------------------- 3 | at com.mirth.connect.donkey.util.xstream.XStreamSerializer.serialize(XStreamSerializer.java:94) 4 | at com.mirth.connect.model.converters.ObjectXMLSerializer.serialize(ObjectXMLSerializer.java:231) 5 | at com.mirth.connect.donkey.util.MapUtil.serializeMap(MapUtil.java:77) 6 | at com.mirth.connect.donkey.server.data.jdbc.JdbcDao.updateMap(JdbcDao.java:1036) 7 | at com.mirth.connect.donkey.server.data.jdbc.JdbcDao.updateMaps(JdbcDao.java:1021) 8 | at com.mirth.connect.donkey.server.data.jdbc.JdbcDao.insertConnectorMessage(JdbcDao.java:818) 9 | at com.mirth.connect.donkey.server.data.buffered.BufferedDao.executeTasks(BufferedDao.java:110) 10 | at com.mirth.connect.donkey.server.data.buffered.BufferedDao.commit(BufferedDao.java:85) 11 | at com.mirth.connect.donkey.server.data.buffered.BufferedDao.commit(BufferedDao.java:72) 12 | at com.mirth.connect.donkey.server.channel.DestinationChain.doCall(DestinationChain.java:182) 13 | at com.mirth.connect.donkey.server.channel.DestinationChain.call(DestinationChain.java:63) 14 | at com.mirth.connect.donkey.server.channel.Channel.process(Channel.java:1836) 15 | at com.mirth.connect.donkey.server.channel.Channel.processSourceQueue(Channel.java:1938) 16 | at com.mirth.connect.donkey.server.channel.Channel.run(Channel.java:1924) 17 | at java.base/java.lang.Thread.run(Thread.java:829)Caused by: com.thoughtworks.xstream.converters.ConversionException: Failed calling method---- Debugging information ----message : Failed calling methodcause-exception : com.thoughtworks.xstream.converters.ConversionExceptioncause-message : Failed calling methodmethod : org.mozilla.javascript.ImporterTopLevel.writeObject()------------------------------- 18 | at com.thoughtworks.xstream.core.util.SerializationMembers.callWriteObject(SerializationMembers.java:158) 19 | at com.thoughtworks.xstream.converters.reflection.SerializableConverter.doMarshal(SerializableConverter.java:257) 20 | at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.marshal(AbstractReflectionConverter.java:90) 21 | at com.thoughtworks.xstream.core.TreeMarshaller.convert(TreeMarshaller.java:70) 22 | at com.thoughtworks.xstream.core.TreeMarshaller.convertAnother(TreeMarshaller.java:58) 23 | at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.marshallField(AbstractReflectionConverter.java:270) 24 | at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter$2.writeField(AbstractReflectionConverter.java:174) 25 | at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.doMarshal(AbstractReflectionConverter.java:262) 26 | at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.marshal(AbstractReflectionConverter.java:90) 27 | at com.mirth.connect.model.converters.JavaScriptObjectConverter.marshal(JavaScriptObjectConverter.java:46) 28 | at com.thoughtworks.xstream.core.TreeMarshaller.convert(TreeMarshaller.java:70) 29 | at com.thoughtworks.xstream.core.TreeMarshaller.convertAnother(TreeMarshaller.java:58) 30 | at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.marshallField(AbstractReflectionConverter.java:270) 31 | at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter$2.writeField(AbstractReflectionConverter.java:174) 32 | at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.doMarshal(AbstractReflectionConverter.java:262) 33 | at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.marshal(AbstractReflectionConverter.java:90) 34 | at com.mirth.connect.model.converters.JavaScriptObjectConverter.marshal(JavaScriptObjectConverter.java:46) 35 | at com.thoughtworks.xstream.core.TreeMarshaller.convert(TreeMarshaller.java:70) 36 | at com.thoughtworks.xstream.core.TreeMarshaller.convertAnother(TreeMarshaller.java:58) 37 | at com.thoughtworks.xstream.core.TreeMarshaller.convertAnother(TreeMarshaller.java:43) 38 | at com.thoughtworks.xstream.converters.collections.AbstractCollectionConverter.writeBareItem(AbstractCollectionConverter.java:94) 39 | at com.thoughtworks.xstream.converters.collections.AbstractCollectionConverter.writeItem(AbstractCollectionConverter.java:66) 40 | at com.thoughtworks.xstream.converters.collections.AbstractCollectionConverter.writeCompleteItem(AbstractCollectionConverter.java:81) 41 | at com.thoughtworks.xstream.converters.collections.ArrayConverter.marshal(ArrayConverter.java:45) 42 | at com.thoughtworks.xstream.core.TreeMarshaller.convert(TreeMarshaller.java:70) 43 | at com.thoughtworks.xstream.core.TreeMarshaller.convertAnother(TreeMarshaller.java:58) 44 | at com.thoughtworks.xstream.core.TreeMarshaller.convertAnother(TreeMarshaller.java:43) 45 | at com.thoughtworks.xstream.converters.collections.AbstractCollectionConverter.writeBareItem(AbstractCollectionConverter.java:94) 46 | at com.thoughtworks.xstream.converters.collections.AbstractCollectionConverter.writeItem(AbstractCollectionConverter.java:66) 47 | at com.thoughtworks.xstream.converters.collections.AbstractCollectionConverter.writeCompleteItem(AbstractCollectionConverter.java:81) 48 | at com.thoughtworks.xstream.converters.collections.MapConverter.marshal(MapConverter.java:79) 49 | at com.thoughtworks.xstream.core.TreeMarshaller.convert(TreeMarshaller.java:70) 50 | at com.thoughtworks.xstream.core.TreeMarshaller.convertAnother(TreeMarshaller.java:58) 51 | at com.thoughtworks.xstream.core.TreeMarshaller.convertAnother(TreeMarshaller.java:43) 52 | at com.thoughtworks.xstream.core.TreeMarshaller.start(TreeMarshaller.java:82) 53 | at com.thoughtworks.xstream.core.AbstractTreeMarshallingStrategy.marshal(AbstractTreeMarshallingStrategy.java:37) 54 | at com.thoughtworks.xstream.XStream.marshal(XStream.java:1320) 55 | at com.thoughtworks.xstream.XStream.marshal(XStream.java:1309) 56 | at com.thoughtworks.xstream.XStream.toXML(XStream.java:1282) 57 | at com.thoughtworks.xstream.XStream.toXML(XStream.java:1269) 58 | at com.mirth.connect.donkey.util.xstream.XStreamSerializer.serialize(XStreamSerializer.java:92) 59 | ... 14 moreCaused by: com.thoughtworks.xstream.converters.ConversionException: Failed calling method---- Debugging information ----message : Failed calling methodcause-exception : com.thoughtworks.xstream.core.TreeMarshaller$CircularReferenceExceptioncause-message : Recursive reference to parent objectmethod : org.mozilla.javascript.NativeStringIterator.writeObject()------------------------------- 60 | at com.thoughtworks.xstream.core.util.SerializationMembers.callWriteObject(SerializationMembers.java:158) 61 | at com.thoughtworks.xstream.converters.reflection.SerializableConverter.doMarshal(SerializableConverter.java:257) 62 | at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.marshal(AbstractReflectionConverter.java:90) 63 | at com.thoughtworks.xstream.core.TreeMarshaller.convert(TreeMarshaller.java:70) 64 | at com.thoughtworks.xstream.core.TreeMarshaller.convertAnother(TreeMarshaller.java:58) 65 | at com.thoughtworks.xstream.core.TreeMarshaller.convertAnother(TreeMarshaller.java:43) 66 | at com.thoughtworks.xstream.converters.collections.AbstractCollectionConverter.writeBareItem(AbstractCollectionConverter.java:94) 67 | at com.thoughtworks.xstream.converters.collections.AbstractCollectionConverter.writeItem(AbstractCollectionConverter.java:66) 68 | at com.thoughtworks.xstream.converters.collections.AbstractCollectionConverter.writeCompleteItem(AbstractCollectionConverter.java:81) 69 | at com.thoughtworks.xstream.converters.collections.MapConverter.marshal(MapConverter.java:79) 70 | at com.thoughtworks.xstream.core.TreeMarshaller.convert(TreeMarshaller.java:70) 71 | at com.thoughtworks.xstream.core.TreeMarshaller.convertAnother(TreeMarshaller.java:58) 72 | at com.thoughtworks.xstream.core.TreeMarshaller.convertAnother(TreeMarshaller.java:43) 73 | at com.thoughtworks.xstream.converters.reflection.SerializableConverter$1.defaultWriteObject(SerializableConverter.java:212) 74 | at com.thoughtworks.xstream.core.util.CustomObjectOutputStream.defaultWriteObject(CustomObjectOutputStream.java:83) 75 | at org.mozilla.javascript.ScriptableObject.writeObject(ScriptableObject.java:2958) 76 | at jdk.internal.reflect.GeneratedMethodAccessor156.invoke(Unknown Source) 77 | at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) 78 | at java.base/java.lang.reflect.Method.invoke(Method.java:566) 79 | at com.thoughtworks.xstream.core.util.SerializationMembers.callWriteObject(SerializationMembers.java:154) 80 | ... 54 moreCaused by: com.thoughtworks.xstream.core.TreeMarshaller$CircularReferenceException: Recursive reference to parent object---- Debugging information ----message : Recursive reference to parent objectitem-type : org.mozilla.javascript.ImporterTopLevelconverter-type : com.thoughtworks.xstream.converters.reflection.SerializableConverter------------------------------- 81 | at com.thoughtworks.xstream.core.TreeMarshaller.convert(TreeMarshaller.java:63) 82 | at com.thoughtworks.xstream.core.TreeMarshaller.convertAnother(TreeMarshaller.java:58) 83 | at com.thoughtworks.xstream.core.TreeMarshaller.convertAnother(TreeMarshaller.java:43) 84 | at com.thoughtworks.xstream.converters.reflection.SerializableConverter$1.defaultWriteObject(SerializableConverter.java:212) 85 | at com.thoughtworks.xstream.core.util.CustomObjectOutputStream.defaultWriteObject(CustomObjectOutputStream.java:83) 86 | at org.mozilla.javascript.ScriptableObject.writeObject(ScriptableObject.java:2958) 87 | at jdk.internal.reflect.GeneratedMethodAccessor156.invoke(Unknown Source) 88 | at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) 89 | at java.base/java.lang.reflect.Method.invoke(Method.java:566) 90 | at com.thoughtworks.xstream.core.util.SerializationMembers.callWriteObject(SerializationMembers.java:154) 91 | ... 73 more -------------------------------------------------------------------------------- /Connect/too-big-sample-2.txt: -------------------------------------------------------------------------------- 1 | [2023-03-30 06:22:51,996] ERROR (com.mirth.connect.donkey.server.channel.Channel:1944): An error occurred in channel VA OUTBOUND Z03 MED (a57e5ff4-df52-4de2-be27-c2aef5b7c2b6) while processing message ID 3042700 from the source queue 2 | java.lang.RuntimeException: com.mirth.connect.donkey.util.xstream.SerializerException: com.thoughtworks.xstream.converters.ConversionException: Failed calling method---- Debugging information ----message : Failed calling methodcause-exception : com.thoughtworks.xstream.converters.ConversionExceptioncause-message : Failed calling methodmethod : org.mozilla.javascript.ImporterTopLevel.writeObject()------------------------------- 3 | at com.mirth.connect.donkey.server.channel.Channel.handleDestinationChainThrowable(Channel.java:1910) 4 | at com.mirth.connect.donkey.server.channel.Channel.process(Channel.java:1838) 5 | at com.mirth.connect.donkey.server.channel.Channel.processSourceQueue(Channel.java:1938) 6 | at com.mirth.connect.donkey.server.channel.Channel.run(Channel.java:1924) 7 | at java.base/java.lang.Thread.run(Thread.java:829)Caused by: com.mirth.connect.donkey.util.xstream.SerializerException: com.thoughtworks.xstream.converters.ConversionException: Failed calling method---- Debugging information ----message : Failed calling methodcause-exception : com.thoughtworks.xstream.converters.ConversionExceptioncause-message : Failed calling methodmethod : org.mozilla.javascript.ImporterTopLevel.writeObject()------------------------------- 8 | at com.mirth.connect.donkey.util.xstream.XStreamSerializer.serialize(XStreamSerializer.java:94) 9 | at com.mirth.connect.model.converters.ObjectXMLSerializer.serialize(ObjectXMLSerializer.java:231) 10 | at com.mirth.connect.donkey.util.MapUtil.serializeMap(MapUtil.java:77) 11 | at com.mirth.connect.donkey.server.data.jdbc.JdbcDao.updateMap(JdbcDao.java:1036) 12 | at com.mirth.connect.donkey.server.data.jdbc.JdbcDao.updateMaps(JdbcDao.java:1021) 13 | at com.mirth.connect.donkey.server.data.jdbc.JdbcDao.insertConnectorMessage(JdbcDao.java:818) 14 | at com.mirth.connect.donkey.server.data.buffered.BufferedDao.executeTasks(BufferedDao.java:110) 15 | at com.mirth.connect.donkey.server.data.buffered.BufferedDao.commit(BufferedDao.java:85) 16 | at com.mirth.connect.donkey.server.data.buffered.BufferedDao.commit(BufferedDao.java:72) 17 | at com.mirth.connect.donkey.server.channel.DestinationChain.doCall(DestinationChain.java:182) 18 | at com.mirth.connect.donkey.server.channel.DestinationChain.call(DestinationChain.java:63) 19 | at com.mirth.connect.donkey.server.channel.Channel.process(Channel.java:1836) 20 | ... 3 moreCaused by: com.thoughtworks.xstream.converters.ConversionException: Failed calling method---- Debugging information ----message : Failed calling methodcause-exception : com.thoughtworks.xstream.converters.ConversionExceptioncause-message : Failed calling methodmethod : org.mozilla.javascript.ImporterTopLevel.writeObject()------------------------------- 21 | at com.thoughtworks.xstream.core.util.SerializationMembers.callWriteObject(SerializationMembers.java:158) 22 | at com.thoughtworks.xstream.converters.reflection.SerializableConverter.doMarshal(SerializableConverter.java:257) 23 | at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.marshal(AbstractReflectionConverter.java:90) 24 | at com.thoughtworks.xstream.core.TreeMarshaller.convert(TreeMarshaller.java:70) 25 | at com.thoughtworks.xstream.core.TreeMarshaller.convertAnother(TreeMarshaller.java:58) 26 | at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.marshallField(AbstractReflectionConverter.java:270) 27 | at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter$2.writeField(AbstractReflectionConverter.java:174) 28 | at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.doMarshal(AbstractReflectionConverter.java:262) 29 | at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.marshal(AbstractReflectionConverter.java:90) 30 | at com.mirth.connect.model.converters.JavaScriptObjectConverter.marshal(JavaScriptObjectConverter.java:46) 31 | at com.thoughtworks.xstream.core.TreeMarshaller.convert(TreeMarshaller.java:70) 32 | at com.thoughtworks.xstream.core.TreeMarshaller.convertAnother(TreeMarshaller.java:58) 33 | at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.marshallField(AbstractReflectionConverter.java:270) 34 | at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter$2.writeField(AbstractReflectionConverter.java:174) 35 | at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.doMarshal(AbstractReflectionConverter.java:262) 36 | at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.marshal(AbstractReflectionConverter.java:90) 37 | at com.mirth.connect.model.converters.JavaScriptObjectConverter.marshal(JavaScriptObjectConverter.java:46) 38 | at com.thoughtworks.xstream.core.TreeMarshaller.convert(TreeMarshaller.java:70) 39 | at com.thoughtworks.xstream.core.TreeMarshaller.convertAnother(TreeMarshaller.java:58) 40 | at com.thoughtworks.xstream.core.TreeMarshaller.convertAnother(TreeMarshaller.java:43) 41 | at com.thoughtworks.xstream.converters.collections.AbstractCollectionConverter.writeBareItem(AbstractCollectionConverter.java:94) 42 | at com.thoughtworks.xstream.converters.collections.AbstractCollectionConverter.writeItem(AbstractCollectionConverter.java:66) 43 | at com.thoughtworks.xstream.converters.collections.AbstractCollectionConverter.writeCompleteItem(AbstractCollectionConverter.java:81) 44 | at com.thoughtworks.xstream.converters.collections.ArrayConverter.marshal(ArrayConverter.java:45) 45 | at com.thoughtworks.xstream.core.TreeMarshaller.convert(TreeMarshaller.java:70) 46 | at com.thoughtworks.xstream.core.TreeMarshaller.convertAnother(TreeMarshaller.java:58) 47 | at com.thoughtworks.xstream.core.TreeMarshaller.convertAnother(TreeMarshaller.java:43) 48 | at com.thoughtworks.xstream.converters.collections.AbstractCollectionConverter.writeBareItem(AbstractCollectionConverter.java:94) 49 | at com.thoughtworks.xstream.converters.collections.AbstractCollectionConverter.writeItem(AbstractCollectionConverter.java:66) 50 | at com.thoughtworks.xstream.converters.collections.AbstractCollectionConverter.writeCompleteItem(AbstractCollectionConverter.java:81) 51 | at com.thoughtworks.xstream.converters.collections.MapConverter.marshal(MapConverter.java:79) 52 | at com.thoughtworks.xstream.core.TreeMarshaller.convert(TreeMarshaller.java:70) 53 | at com.thoughtworks.xstream.core.TreeMarshaller.convertAnother(TreeMarshaller.java:58) 54 | at com.thoughtworks.xstream.core.TreeMarshaller.convertAnother(TreeMarshaller.java:43) 55 | at com.thoughtworks.xstream.core.TreeMarshaller.start(TreeMarshaller.java:82) 56 | at com.thoughtworks.xstream.core.AbstractTreeMarshallingStrategy.marshal(AbstractTreeMarshallingStrategy.java:37) 57 | at com.thoughtworks.xstream.XStream.marshal(XStream.java:1320) 58 | at com.thoughtworks.xstream.XStream.marshal(XStream.java:1309) 59 | at com.thoughtworks.xstream.XStream.toXML(XStream.java:1282) 60 | at com.thoughtworks.xstream.XStream.toXML(XStream.java:1269) 61 | at com.mirth.connect.donkey.util.xstream.XStreamSerializer.serialize(XStreamSerializer.java:92) 62 | ... 14 moreCaused by: com.thoughtworks.xstream.converters.ConversionException: Failed calling method---- Debugging information ----message : Failed calling methodcause-exception : com.thoughtworks.xstream.core.TreeMarshaller$CircularReferenceExceptioncause-message : Recursive reference to parent objectmethod : org.mozilla.javascript.NativeStringIterator.writeObject()------------------------------- 63 | at com.thoughtworks.xstream.core.util.SerializationMembers.callWriteObject(SerializationMembers.java:158) 64 | at com.thoughtworks.xstream.converters.reflection.SerializableConverter.doMarshal(SerializableConverter.java:257) 65 | at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.marshal(AbstractReflectionConverter.java:90) 66 | at com.thoughtworks.xstream.core.TreeMarshaller.convert(TreeMarshaller.java:70) 67 | at com.thoughtworks.xstream.core.TreeMarshaller.convertAnother(TreeMarshaller.java:58) 68 | at com.thoughtworks.xstream.core.TreeMarshaller.convertAnother(TreeMarshaller.java:43) 69 | at com.thoughtworks.xstream.converters.collections.AbstractCollectionConverter.writeBareItem(AbstractCollectionConverter.java:94) 70 | at com.thoughtworks.xstream.converters.collections.AbstractCollectionConverter.writeItem(AbstractCollectionConverter.java:66) 71 | at com.thoughtworks.xstream.converters.collections.AbstractCollectionConverter.writeCompleteItem(AbstractCollectionConverter.java:81) 72 | at com.thoughtworks.xstream.converters.collections.MapConverter.marshal(MapConverter.java:79) 73 | at com.thoughtworks.xstream.core.TreeMarshaller.convert(TreeMarshaller.java:70) 74 | at com.thoughtworks.xstream.core.TreeMarshaller.convertAnother(TreeMarshaller.java:58) 75 | at com.thoughtworks.xstream.core.TreeMarshaller.convertAnother(TreeMarshaller.java:43) 76 | at com.thoughtworks.xstream.converters.reflection.SerializableConverter$1.defaultWriteObject(SerializableConverter.java:212) 77 | at com.thoughtworks.xstream.core.util.CustomObjectOutputStream.defaultWriteObject(CustomObjectOutputStream.java:83) 78 | at org.mozilla.javascript.ScriptableObject.writeObject(ScriptableObject.java:2958) 79 | at jdk.internal.reflect.GeneratedMethodAccessor156.invoke(Unknown Source) 80 | at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) 81 | at java.base/java.lang.reflect.Method.invoke(Method.java:566) 82 | at com.thoughtworks.xstream.core.util.SerializationMembers.callWriteObject(SerializationMembers.java:154) 83 | ... 54 moreCaused by: com.thoughtworks.xstream.core.TreeMarshaller$CircularReferenceException: Recursive reference to parent object---- Debugging information ----message : Recursive reference to parent objectitem-type : org.mozilla.javascript.ImporterTopLevelconverter-type : com.thoughtworks.xstream.converters.reflection.SerializableConverter------------------------------- 84 | at com.thoughtworks.xstream.core.TreeMarshaller.convert(TreeMarshaller.java:63) 85 | at com.thoughtworks.xstream.core.TreeMarshaller.convertAnother(TreeMarshaller.java:58) 86 | at com.thoughtworks.xstream.core.TreeMarshaller.convertAnother(TreeMarshaller.java:43) 87 | at com.thoughtworks.xstream.converters.reflection.SerializableConverter$1.defaultWriteObject(SerializableConverter.java:212) 88 | at com.thoughtworks.xstream.core.util.CustomObjectOutputStream.defaultWriteObject(CustomObjectOutputStream.java:83) 89 | at org.mozilla.javascript.ScriptableObject.writeObject(ScriptableObject.java:2958) 90 | at jdk.internal.reflect.GeneratedMethodAccessor156.invoke(Unknown Source) 91 | at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) 92 | at java.base/java.lang.reflect.Method.invoke(Method.java:566) 93 | at com.thoughtworks.xstream.core.util.SerializationMembers.callWriteObject(SerializationMembers.java:154) 94 | ... 73 more -------------------------------------------------------------------------------- /Connect/too-big-sample-3.txt: -------------------------------------------------------------------------------- 1 | ERROR 2023-03-30 00:44:45,556 [Channel Dispatch Thread on VA INBOUND Z03 MEDS (8391cffb-de0c-46d8-833f-e65cd96b05cc) < Channel Writer Process Thread on MHS Genesis Inbound Med (94be261b-6b4c-4627-a8dd-46a0ea28d14a), send Z03 (RDS_O01 to RDS_O13) (2)] com.mirth.connect.donkey.server.channel.Channel: Error processing message in channel VA INBOUND Z03 MEDS (8391cffb-de0c-46d8-833f-e65cd96b05cc). 2 | com.mirth.connect.donkey.server.channel.ChannelException: 3 | at com.mirth.connect.donkey.server.channel.Channel.dispatchRawMessage(Channel.java:1311) 4 | at com.mirth.connect.donkey.server.channel.SourceConnector.dispatchRawMessage(SourceConnector.java:192) 5 | at com.mirth.connect.server.controllers.DonkeyEngineController.dispatchRawMessage(DonkeyEngineController.java:1119) 6 | at com.mirth.connect.connectors.vm.VmDispatcher.send(VmDispatcher.java:157) 7 | at com.mirth.connect.donkey.server.channel.DestinationConnector.handleSend(DestinationConnector.java:895) 8 | at com.mirth.connect.donkey.server.channel.DestinationConnector.process(DestinationConnector.java:518) 9 | at com.mirth.connect.donkey.server.channel.DestinationChain.doCall(DestinationChain.java:121) 10 | at com.mirth.connect.donkey.server.channel.DestinationChain.call(DestinationChain.java:63) 11 | at com.mirth.connect.donkey.server.channel.Channel.process(Channel.java:1836) 12 | at com.mirth.connect.donkey.server.channel.Channel.processSourceQueue(Channel.java:1938) 13 | at com.mirth.connect.donkey.server.channel.Channel.run(Channel.java:1924) 14 | at java.base/java.lang.Thread.run(Thread.java:829) 15 | Caused by: java.lang.RuntimeException: com.mirth.connect.donkey.util.xstream.SerializerException: com.thoughtworks.xstream.converters.ConversionException: Failed calling method 16 | ---- Debugging information ---- 17 | message : Failed calling method 18 | cause-exception : com.thoughtworks.xstream.converters.ConversionException 19 | cause-message : Failed calling method 20 | method : org.mozilla.javascript.ImporterTopLevel.writeObject() 21 | ------------------------------- 22 | at com.mirth.connect.donkey.server.channel.Channel.handleDestinationChainThrowable(Channel.java:1910) 23 | at com.mirth.connect.donkey.server.channel.Channel.process(Channel.java:1838) 24 | at com.mirth.connect.donkey.server.channel.Channel.dispatchRawMessage(Channel.java:1288) 25 | ... 11 more 26 | Caused by: com.mirth.connect.donkey.util.xstream.SerializerException: com.thoughtworks.xstream.converters.ConversionException: Failed calling method 27 | ---- Debugging information ---- 28 | message : Failed calling method 29 | cause-exception : com.thoughtworks.xstream.converters.ConversionException 30 | cause-message : Failed calling method 31 | method : org.mozilla.javascript.ImporterTopLevel.writeObject() 32 | ------------------------------- 33 | at com.mirth.connect.donkey.util.xstream.XStreamSerializer.serialize(XStreamSerializer.java:94) 34 | at com.mirth.connect.model.converters.ObjectXMLSerializer.serialize(ObjectXMLSerializer.java:231) 35 | at com.mirth.connect.donkey.util.MapUtil.serializeMap(MapUtil.java:77) 36 | at com.mirth.connect.donkey.server.data.jdbc.JdbcDao.updateMap(JdbcDao.java:1036) 37 | at com.mirth.connect.donkey.server.data.jdbc.JdbcDao.updateMaps(JdbcDao.java:1021) 38 | at com.mirth.connect.donkey.server.data.jdbc.JdbcDao.insertConnectorMessage(JdbcDao.java:818) 39 | at com.mirth.connect.donkey.server.data.buffered.BufferedDao.executeTasks(BufferedDao.java:110) 40 | at com.mirth.connect.donkey.server.data.buffered.BufferedDao.commit(BufferedDao.java:85) 41 | at com.mirth.connect.donkey.server.data.buffered.BufferedDao.commit(BufferedDao.java:72) 42 | at com.mirth.connect.donkey.server.channel.DestinationChain.doCall(DestinationChain.java:182) 43 | at com.mirth.connect.donkey.server.channel.DestinationChain.call(DestinationChain.java:63) 44 | at com.mirth.connect.donkey.server.channel.Channel.process(Channel.java:1836) 45 | ... 12 more 46 | Caused by: com.thoughtworks.xstream.converters.ConversionException: Failed calling method 47 | ---- Debugging information ---- 48 | message : Failed calling method 49 | cause-exception : com.thoughtworks.xstream.converters.ConversionException 50 | cause-message : Failed calling method 51 | method : org.mozilla.javascript.ImporterTopLevel.writeObject() 52 | ------------------------------- 53 | at com.thoughtworks.xstream.core.util.SerializationMembers.callWriteObject(SerializationMembers.java:158) 54 | at com.thoughtworks.xstream.converters.reflection.SerializableConverter.doMarshal(SerializableConverter.java:257) 55 | at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.marshal(AbstractReflectionConverter.java:90) 56 | at com.thoughtworks.xstream.core.TreeMarshaller.convert(TreeMarshaller.java:70) 57 | at com.thoughtworks.xstream.core.TreeMarshaller.convertAnother(TreeMarshaller.java:58) 58 | at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.marshallField(AbstractReflectionConverter.java:270) 59 | at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter$2.writeField(AbstractReflectionConverter.java:174) 60 | at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.doMarshal(AbstractReflectionConverter.java:262) 61 | at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.marshal(AbstractReflectionConverter.java:90) 62 | at com.mirth.connect.model.converters.JavaScriptObjectConverter.marshal(JavaScriptObjectConverter.java:46) 63 | at com.thoughtworks.xstream.core.TreeMarshaller.convert(TreeMarshaller.java:70) 64 | at com.thoughtworks.xstream.core.TreeMarshaller.convertAnother(TreeMarshaller.java:58) 65 | at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.marshallField(AbstractReflectionConverter.java:270) 66 | at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter$2.writeField(AbstractReflectionConverter.java:174) 67 | at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.doMarshal(AbstractReflectionConverter.java:262) 68 | at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.marshal(AbstractReflectionConverter.java:90) 69 | at com.mirth.connect.model.converters.JavaScriptObjectConverter.marshal(JavaScriptObjectConverter.java:46) 70 | at com.thoughtworks.xstream.core.TreeMarshaller.convert(TreeMarshaller.java:70) 71 | at com.thoughtworks.xstream.core.TreeMarshaller.convertAnother(TreeMarshaller.java:58) 72 | at com.thoughtworks.xstream.core.TreeMarshaller.convertAnother(TreeMarshaller.java:43) 73 | at com.thoughtworks.xstream.converters.collections.AbstractCollectionConverter.writeBareItem(AbstractCollectionConverter.java:94) 74 | at com.thoughtworks.xstream.converters.collections.AbstractCollectionConverter.writeItem(AbstractCollectionConverter.java:66) 75 | at com.thoughtworks.xstream.converters.collections.AbstractCollectionConverter.writeCompleteItem(AbstractCollectionConverter.java:81) 76 | at com.thoughtworks.xstream.converters.collections.MapConverter.marshal(MapConverter.java:79) 77 | at com.thoughtworks.xstream.core.TreeMarshaller.convert(TreeMarshaller.java:70) 78 | at com.thoughtworks.xstream.core.TreeMarshaller.convertAnother(TreeMarshaller.java:58) 79 | at com.thoughtworks.xstream.core.TreeMarshaller.convertAnother(TreeMarshaller.java:43) 80 | at com.thoughtworks.xstream.core.TreeMarshaller.start(TreeMarshaller.java:82) 81 | at com.thoughtworks.xstream.core.AbstractTreeMarshallingStrategy.marshal(AbstractTreeMarshallingStrategy.java:37) 82 | at com.thoughtworks.xstream.XStream.marshal(XStream.java:1320) 83 | at com.thoughtworks.xstream.XStream.marshal(XStream.java:1309) 84 | at com.thoughtworks.xstream.XStream.toXML(XStream.java:1282) 85 | at com.thoughtworks.xstream.XStream.toXML(XStream.java:1269) 86 | at com.mirth.connect.donkey.util.xstream.XStreamSerializer.serialize(XStreamSerializer.java:92) 87 | ... 23 more 88 | Caused by: com.thoughtworks.xstream.converters.ConversionException: Failed calling method 89 | ---- Debugging information ---- 90 | message : Failed calling method 91 | cause-exception : com.thoughtworks.xstream.core.TreeMarshaller$CircularReferenceException 92 | cause-message : Recursive reference to parent object 93 | method : org.mozilla.javascript.NativeStringIterator.writeObject() 94 | ------------------------------- 95 | at com.thoughtworks.xstream.core.util.SerializationMembers.callWriteObject(SerializationMembers.java:158) 96 | at com.thoughtworks.xstream.converters.reflection.SerializableConverter.doMarshal(SerializableConverter.java:257) 97 | at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.marshal(AbstractReflectionConverter.java:90) 98 | at com.thoughtworks.xstream.core.TreeMarshaller.convert(TreeMarshaller.java:70) 99 | at com.thoughtworks.xstream.core.TreeMarshaller.convertAnother(TreeMarshaller.java:58) 100 | at com.thoughtworks.xstream.core.TreeMarshaller.convertAnother(TreeMarshaller.java:43) 101 | at com.thoughtworks.xstream.converters.collections.AbstractCollectionConverter.writeBareItem(AbstractCollectionConverter.java:94) 102 | at com.thoughtworks.xstream.converters.collections.AbstractCollectionConverter.writeItem(AbstractCollectionConverter.java:66) 103 | at com.thoughtworks.xstream.converters.collections.AbstractCollectionConverter.writeCompleteItem(AbstractCollectionConverter.java:81) 104 | at com.thoughtworks.xstream.converters.collections.MapConverter.marshal(MapConverter.java:79) 105 | at com.thoughtworks.xstream.core.TreeMarshaller.convert(TreeMarshaller.java:70) 106 | at com.thoughtworks.xstream.core.TreeMarshaller.convertAnother(TreeMarshaller.java:58) 107 | at com.thoughtworks.xstream.core.TreeMarshaller.convertAnother(TreeMarshaller.java:43) 108 | at com.thoughtworks.xstream.converters.reflection.SerializableConverter$1.defaultWriteObject(SerializableConverter.java:212) 109 | at com.thoughtworks.xstream.core.util.CustomObjectOutputStream.defaultWriteObject(CustomObjectOutputStream.java:83) 110 | at org.mozilla.javascript.ScriptableObject.writeObject(ScriptableObject.java:2958) 111 | at jdk.internal.reflect.GeneratedMethodAccessor156.invoke(Unknown Source) 112 | at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) 113 | at java.base/java.lang.reflect.Method.invoke(Method.java:566) 114 | at com.thoughtworks.xstream.core.util.SerializationMembers.callWriteObject(SerializationMembers.java:154) 115 | ... 56 more 116 | Caused by: com.thoughtworks.xstream.core.TreeMarshaller$CircularReferenceException: Recursive reference to parent object 117 | ---- Debugging information ---- 118 | message : Recursive reference to parent object 119 | item-type : org.mozilla.javascript.ImporterTopLevel 120 | converter-type : com.thoughtworks.xstream.converters.reflection.SerializableConverter 121 | ------------------------------- 122 | at com.thoughtworks.xstream.core.TreeMarshaller.convert(TreeMarshaller.java:63) 123 | at com.thoughtworks.xstream.core.TreeMarshaller.convertAnother(TreeMarshaller.java:58) 124 | at com.thoughtworks.xstream.core.TreeMarshaller.convertAnother(TreeMarshaller.java:43) 125 | at com.thoughtworks.xstream.converters.reflection.SerializableConverter$1.defaultWriteObject(SerializableConverter.java:212) 126 | at com.thoughtworks.xstream.core.util.CustomObjectOutputStream.defaultWriteObject(CustomObjectOutputStream.java:83) 127 | at org.mozilla.javascript.ScriptableObject.writeObject(ScriptableObject.java:2958) 128 | at jdk.internal.reflect.GeneratedMethodAccessor156.invoke(Unknown Source) 129 | at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) 130 | at java.base/java.lang.reflect.Method.invoke(Method.java:566) 131 | at com.thoughtworks.xstream.core.util.SerializationMembers.callWriteObject(SerializationMembers.java:154) 132 | ... 75 more 133 | -------------------------------------------------------------------------------- /Connect/x12_edi_834_reader.js: -------------------------------------------------------------------------------- 1 | //This is to parse and extract the contents of X12EDI-834 file 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | 6 | // Read EDI 834 file 7 | const fileContent = fs.readFileSync('FilePath or file', 'utf8'); 8 | 9 | // Replace the linefeed-LF with ~CRLF and Split the file into segments 10 | const segments = fileContent.replace(/\n/g,'~\r\n').split(/~\r?\n/); 11 | //console.log(segments); 12 | 13 | // Initialize an empty array to store the extracted data 14 | const extractedData = []; 15 | 16 | // Initialize a flag to determine whether to skip segments 17 | let skipSegments = false; 18 | 19 | // Loop over each segment 20 | for (let i = 0; i < segments.length; i++) { 21 | const segment = segments[i]; 22 | 23 | // Extract the segment ID (e.g. INS, NM1, etc.) 24 | const segmentId = segment.substring(0, 3); 25 | 26 | // Check if the current segment is a DMG segment 27 | if (segmentId === 'DMG') { 28 | // Add the DMG segment to the extracted data array 29 | extractedData.push(segment); 30 | 31 | // Set the skipSegments flag to true to skip segments until the DTP segment is reached 32 | skipSegments = true; 33 | } else if (segmentId === 'INS') { 34 | // Add the DTP segment to the extracted data array 35 | extractedData.push(segment); 36 | 37 | // Set the skipSegments flag to false to include segments until the next DMG segment is reached 38 | skipSegments = false; 39 | } else if (!skipSegments) { 40 | // Add the segment to the extracted data array if skipSegments is false 41 | extractedData.push(segment); 42 | } 43 | } 44 | // Join the extracted segments into a single string 45 | const outputContent = extractedData.join('~\r\n'); 46 | console.log(outputContent); 47 | 48 | //define an array 49 | const output = []; 50 | const modSegments = outputContent.split(/\r?\n/); 51 | for (let j = 0; j < modSegments.length; j++) { 52 | const modSegment = modSegments[j]; 53 | if (typeof modSegment === 'string') { 54 | const fields = modSegment.split('*'); 55 | const segmentCode = fields[0]; 56 | 57 | //Process INS Segment 58 | if (segmentCode === 'INS') { 59 | const updateCode = fields[3]; 60 | output.push({ updateCode: updateCode}); 61 | } 62 | 63 | //Process REF Segment 64 | if (segmentCode === 'REF') { 65 | const subscriberQualfierrID = fields[1]; 66 | const subscriberId = fields[2]; 67 | if(subscriberQualfierrID === '0F'){ 68 | output[output.length - 1].subscriberId = subscriberId.substring(0,subscriberId.length - 1); 69 | } 70 | } 71 | 72 | //Process NM1 Segment 73 | if (segmentCode === 'NM1') { 74 | const firstName = fields[4]; 75 | const lastName = fields[3]; 76 | const ssnFlag = fields[8]; 77 | const ssn = fields[9]; 78 | output[output.length - 1].firstName = firstName; 79 | output[output.length - 1].lastName = lastName; 80 | if(ssnFlag === '34'){ 81 | output[output.length - 1].ssn = ssn.substring(0,ssn.length - 1 ); 82 | } 83 | } 84 | 85 | 86 | //Process DMG Segment 87 | else if (segmentCode === 'DMG') { 88 | const dob = fields[2]; 89 | const gender = fields[3]; 90 | output[output.length - 1].dob = dob; 91 | output[output.length - 1].gender = gender; 92 | } 93 | 94 | //Process N3 Segment 95 | else if (segmentCode === 'N3') { 96 | const address = fields[1]; 97 | output[output.length - 1].address = address.substring(0,address.length - 1); 98 | } 99 | 100 | //Process N4 Segment 101 | else if (segmentCode === 'N4') { 102 | const city = fields[1]; 103 | const state = fields[2]; 104 | const zip = fields[3]; 105 | output[output.length - 1].city = city; 106 | output[output.length - 1].state = state; 107 | output[output.length - 1].zip = zip; 108 | } 109 | 110 | } 111 | } 112 | fs.writeFileSync('outputfilepath/test_4_op.json', JSON.stringify(output)); 113 | console.log(output); 114 | -------------------------------------------------------------------------------- /Networking.md: -------------------------------------------------------------------------------- 1 | # Networking 2 | 3 | ## Wireshark 4 | 5 | ### Capture for 30 seconds 6 | 7 | ````bash 8 | sudo tshark -i eth0 -a duration:30 -w dump.ncap 9 | ```` 10 | 11 | ### Capture only port 9443, treat the traffic as http, and filter the source host 12 | 13 | ````bash 14 | tshark -i eth0 -d tcp.port==9443,http -f "src host x.x.x.x" -w dump.ncap 15 | ```` 16 | 17 | ### Capture only this host (on either end, as sender or receiver) 18 | 19 | ````bash 20 | tshark -i eth0 -f "host x.x.x.x" -w dump.ncap 21 | ```` 22 | -------------------------------------------------------------------------------- /PowerShell/PowerShell.md: -------------------------------------------------------------------------------- 1 | # PowerShell 2 | 3 | ## GitHub star list 4 | 5 | See [here](https://github.com/stars/rogin/lists/powershell). 6 | 7 | ## Tracing commands 8 | 9 | Use [_trace-command_](https://learn.microsoft.com/en-us/powershell/module/Microsoft.PowerShell.Utility/Trace-Command?view=powershell-7.3) 10 | 11 | ````powershell 12 | Trace-Command -expression {"g*","s*" | Get-Alias } -name parameterbinding -pshost 13 | #to see all options for -name above 14 | Get-TraceSource 15 | ```` 16 | 17 | AND IF YOU FORGOT to run that before starting, use ([H/T redog](https://old.reddit.com/r/PowerShell/comments/13bv0be/how_to_get_a_starttranscript_like_file_output/jjcqe2n/)) 18 | 19 | ````powershell 20 | Get-Content (Get-PSReadLineOption | select -ExpandProperty HistorySavePath) | out-gridview 21 | ```` 22 | 23 | ## Updating `Microsoft Store` apps 24 | 25 | From [here](https://social.technet.microsoft.com/Forums/windows/en-US/5ac7daa9-54e6-43c0-9746-293dcb8ef2ec/how-to-force-update-of-windows-store-apps-without-launching-the-store-app): 26 | 27 | ````powershell 28 | Get-CimInstance -Namespace "Root\cimv2\mdm\dmmap" -ClassName "MDM_EnterpriseModernAppManagement_AppManagement01" | 29 | Invoke-CimMethod -MethodName UpdateScanMethod 30 | ```` 31 | 32 | ## Simple things I forget 33 | 34 | ### Converting to int and string 35 | 36 | Examples from [here](https://hostingultraso.com/help/windows/convert-numbers-between-bases-windows-powershell) making use of `Convert` class: 37 | 38 | ````powershell 39 | # Convert accepts bases 2, 8, 10, and 16 40 | PS >[Convert]::ToInt32("10011010010", 2) 41 | 1234 42 | PS >[Convert]::ToString(1234, 16) 43 | 4d2 44 | # or use the formatting operator 45 | PS >"{0:X4}" -f 1234 46 | 04D2 47 | ```` 48 | 49 | ### pre-sized arrays 50 | 51 | from [here](https://learn.microsoft.com/en-us/powershell/scripting/learn/deep-dives/everything-about-arrays?view=powershell-7.2#initialize-with-0) 52 | 53 | ````powershell 54 | PS> [int[]]::new(4) 55 | 0 56 | 0 57 | 0 58 | 0 59 | #We can use the multiplying trick to do this too. 60 | PS> $data = @(0) * 4 61 | PS> $data 62 | 0 63 | 0 64 | 0 65 | 0 66 | ```` 67 | 68 | ### Use Where() to split one collection into two 69 | 70 | Where() accepts [multiple params](https://mcpmag.com/articles/2015/12/02/where-method-in-powershell.aspx). 71 | 72 | ````powershell 73 | @(1,2,3).Where({$_ -gt 0}, 'first') 74 | #1 75 | @(1,2,3).Where({$_ -gt 0}, 'first', 2) 76 | #1 77 | #2 78 | @(1,2,3).Where({$_ -gt 10}, 'first') 79 | @(1,2,3).Where({$_ -gt 10}, 'first', 2) 80 | $Running,$Stopped = (Get-Service).Where({$_.Status -eq 'Running'},'Split') 81 | #use $Running and $Stopped 82 | ```` 83 | 84 | ### Here-strings 85 | 86 | See [here](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_quoting_rules?view=powershell-7.3#here-strings) 87 | 88 | ````powershell 89 | @" 90 | For help, type "get-help" 91 | "@ 92 | ```` 93 | 94 | ### Prefer generic List over ArrayList 95 | 96 | [Comment](https://old.reddit.com/r/PowerShell/comments/14yjs0u/total_powershell_noob_looking_for_some_quick/jrth4of/) to prefer `$NewList = [System.Collections.Generic.List[PSObject]]::new()` over `New-Object System.Collections.ArrayList` to avoid silencing `Add()` returning true/false. 97 | 98 | ### Regex 99 | 100 | Always going back to [the docs](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_regular_expressions?view=powershell-7.4) 101 | 102 | ## Gotchas 103 | 104 | Various items that have bitten me. 105 | 106 | ### Pipelined input to my advanced function isn't processing 107 | 108 | Verify your code is in a process block. To [quote](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_functions_advanced_methods?view=powershell-7.3#process): 109 | 110 | _If a function parameter is set to accept pipeline input, and a process block isn't defined, record-by-record processing will fail. In this case, your function will only execute once, regardless of the input._ 111 | 112 | ### Using -split with "|" 113 | 114 | Be sure to escape the pipe as it's a special character, or substitute Split() instead. 115 | 116 | ````powershell 117 | # does not match expectations 118 | PS >"foo|bar" -split "|" 119 | 120 | f 121 | o 122 | o 123 | | 124 | b 125 | a 126 | r 127 | 128 | # escaping the pipe parses as expected 129 | PS >"foo|bar" -split "\|" 130 | foo 131 | bar 132 | 133 | # you can also opt for Split() to ignore special chars 134 | PS >"foo|bar".Split("|") 135 | foo 136 | bar 137 | ```` 138 | 139 | ## New in PS v7 140 | 141 | ### Ternary 142 | 143 | ````powershell 144 | $IsWindows ? "ok" : "not ok" 145 | ```` 146 | 147 | ### Chain operators 148 | 149 | ````powershell 150 | 1 && 2 151 | 1/0 || Write-Warning "What are you trying to do?" 152 | ```` 153 | 154 | ### Null-Coalescing Assignment 155 | 156 | ````powershell 157 | $foo = $null 158 | $foo ?? "bar" 159 | #Related to this is the Null-Coalescing assignment operator, ??=. 160 | # ??= 161 | $computername ??= ([system.environment]::MachineName) 162 | ```` 163 | 164 | ### Null Conditional Operators 165 | 166 | ````powershell 167 | $p = Get-Process -id $pid 168 | ${p}?.startTime 169 | ```` 170 | 171 | ### ForEach-Object Parallel 172 | 173 | ````powershell 174 | Measure-Command {1..1000 | ForEach-Object -parallel {$_*10}} 175 | ```` 176 | 177 | ### SSH for remoting 178 | 179 | ### Clean block (v7.3+) 180 | 181 | Acts like a **finally** block, see [here](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_functions_advanced_methods?view=powershell-7.3#clean). 182 | 183 | ## Nuggets from "The PowerShell Scripting and Toolmaking Book" 184 | 185 | ### Transcripts 186 | 187 | Use Start-Transcript and Stop-Transcript to write all actions taken (along with their output) to a file. 188 | 189 | ### Write-Information 190 | 191 | From two sections in the book, use this to add tags to its output which can then be filtered. 192 | 193 | ````powershell 194 | $r = "thinkp1","srv1","srv3","bovine320" | 195 | Get-Bits -InformationVariable iv -Verbose 196 | #All of the information records generated by Write-Information are stored in $iv. 197 | #These are objects with their own set of properties. 198 | ```` 199 | 200 | ### Make Powershell ISE 'aware' of v7 201 | 202 | ````powershell 203 | Enter-PSSession -computername $env:COMPUTERNAME -Configuration PowerShell.7 204 | #This hack will open a remoting session to yourself using the PowerShell 7 205 | #endpoint. Now, the scripting panel will be PowerShell 7 “aware”. 206 | ```` 207 | 208 | ## Solutions I've needed but came up empty 209 | 210 | - Can I select XML's #text() in a SelectNode()? 211 | - Can I create a cycled iterator that repeats a list? there's no 'yield' equivalent. I want `('red','green','blue')` to cycle forever. 212 | - Can I iterate with an index like other languages -- `for (index, value) in list.something()`. There's a [language request ticket](https://github.com/PowerShell/PowerShell/issues/13772) for \$PSIndex that was closed. 213 | - I need a PS script to run as admin on a fresh Win10 box to keep the system updated. How to configure the script to allow user to click .ps1 file to run, and have it prompt for creds? Adding `#Requires -RunAsAdministrator` did not work, plus the default PS v5.1 running it failed to parse simple lines correctly, e.g. "& control update" to open the Windows update panel. 214 | - Running a scheduled PS script without a popup. There are [ways](https://superuser.com/questions/62525/run-a-batch-file-in-a-completely-hidden-way) to wrap with VBS. 215 | 216 | ## Range operator 217 | 218 | See [here](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_Operators?view=powershell-7.3#range-operator-). 219 | 220 | ## Predictive IntelliSense 221 | 222 | Install PSReadLine and you can get a list view of selections based on your history by pressing F2 - [details](https://learn.microsoft.com/en-us/powershell/scripting/learn/shell/using-predictors?view=powershell-7.3). 223 | 224 | ## Helpful links 225 | 226 | [Practice and Style Guide](https://poshcode.gitbook.io/powershell-practice-and-style/introduction/readme) 227 | 228 | Local book [PowerShell Notes for Professionals](PowerShellNotesForProfessionals.pdf) from [here](https://goalkicker.com/PowerShellBook/) 229 | 230 | [Powershell cheat sheet](https://devblogs.microsoft.com/powershell-community/cheat-sheet-console-experience/) 231 | 232 | [Big Book of Powershell Gotchas](https://github.com/devops-collective-inc/big-book-of-powershell-gotchas/blob/master/SUMMARY.md) 233 | 234 | [Unit testing vs integration testing](https://www.guru99.com/unit-test-vs-integration-test.html) 235 | 236 | [Info on system testing](https://testsigma.com/blog/system-testing-vs-integration-testing/) and [another](https://u-tor.com/topic/system-vs-integration) 237 | 238 | [Local cheatsheat](powershell-cheat-sheet-ramblingcookiemonster.pdf) from [here](https://ramblingcookiemonster.github.io/images/Cheat-Sheets/powershell-cheat-sheet.pdf) 239 | 240 | [Gist cheatsheet](https://gist.github.com/pcgeek86/336e08d1a09e3dd1a8f0a30a9fe61c8a) 241 | 242 | [Common Cmdlets with examples](https://www.pdq.com/powershell/) 243 | 244 | ## Best Practies 245 | 246 | Azure has a [module best practices](https://github.com/Azure/azure-powershell/blob/main/documentation/development-docs/design-guidelines/module-best-practices.md) as other bests. 247 | 248 | PoshCode has a [best practices](https://github.com/PoshCode/PowerShellPracticeAndStyle) 249 | 250 | MS [strongly encouraged development guidelines](https://learn.microsoft.com/en-us/powershell/scripting/developer/cmdlet/strongly-encouraged-development-guidelines). [Using WriteObject](https://learn.microsoft.com/en-us/powershell/scripting/developer/cmdlet/strongly-encouraged-development-guidelines?view=powershell-7.3#support-the-passthru-parameter) for `-PassThru` caught my eye. 251 | 252 | MS [Standard Cmdlet Parameter Names and Types](https://learn.microsoft.com/en-us/powershell/scripting/developer/cmdlet/standard-cmdlet-parameter-names-and-types?view=powershell-7.3) 253 | 254 | ## Unsorted 255 | 256 | 257 | 258 | 259 | 260 | ## Review one liners 261 | 262 | Pull anything of value from [here](https://www.red-gate.com/simple-talk/sysadmin/powershell/powershell-one-liners--collections,-hashtables,-arrays-and-strings/). 263 | 264 | ## Review operators 265 | 266 | Read over the full [page](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_Operators?view=powershell-7.3). The subexpressions were interesting. 267 | -------------------------------------------------------------------------------- /PowerShell/PowerShellNotesForProfessionals.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rogin/Snippets/862fe77a4dd2ef37766c7252e0a8b58b61bcbca6/PowerShell/PowerShellNotesForProfessionals.pdf -------------------------------------------------------------------------------- /PowerShell/powershell-cheat-sheet-ramblingcookiemonster.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rogin/Snippets/862fe77a4dd2ef37766c7252e0a8b58b61bcbca6/PowerShell/powershell-cheat-sheet-ramblingcookiemonster.pdf -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Snippets 2 | 3 | Snippets from the Internet for later reference. 4 | 5 | ## Categories 6 | 7 | 1. [NextGen Connect](Connect/Mirth.md) (aka Mirth) 8 | 2. [Networking](Networking.md) 9 | 3. [PowerShell](PowerShell/PowerShell.md) 10 | -------------------------------------------------------------------------------- /Rust.md: -------------------------------------------------------------------------------- 1 | # Rust 2 | 3 | ## Getting started 4 | 5 | Use [rustup](https://rustup.rs/) to install the toolchain. 6 | 7 | [Installing the Rust compiler and toolchain](https://subscription.packtpub.com/book/programming/9781789346572/1/ch01lvl1sec11/installing-the-rust-compiler-and-toolchain) 8 | 9 | [Learn Rust](https://www.rust-lang.org/learn) 10 | 11 | Google's [Rust training](https://google.github.io/comprehensive-rust/welcome.html) 12 | 13 | ## Books 14 | 15 | * The [Rust Standard Library Cookbook](https://subscription.packtpub.com/book/programming/9781788623926/8/ch08lvl1sec50) might be the most helpful intro book that I've read in terms of items like _Rc_ and examples using them. Review its bookmarks. 16 | * [Rust book](https://doc.rust-lang.org/book/) 17 | * [The Rustonomicon](https://doc.rust-lang.org/nomicon/index.html) 18 | * [Rust Fuzz book](https://rust-fuzz.github.io/book/introduction.html) 19 | * [Rust by Example](https://doc.rust-lang.org/rust-by-example/index.html) 20 | * [Rust Reference](https://doc.rust-lang.org/reference/index.html) 21 | * [The Cargo book](https://doc.rust-lang.org/cargo/) 22 | * [Command-Line Rust](https://www.amazon.com/Command-Line-Rust-Project-Based-Primer-Writing/dp/1098109430) (which I have) walks through reimplementing core linux tools (_head_, _wc_, etc.). 23 | 24 | ## IDE setup 25 | 26 | [Rust on Nails](https://rust-on-nails.com/docs/ide-setup/introduction/) uses [development containers in VS Code](https://code.visualstudio.com/docs/devcontainers/containers) for a rust app. Very helpful. 27 | 28 | ## Async related 29 | 30 | [async/await](https://tmandry.gitlab.io/blog/posts/optimizing-await-1/) 31 | [Pin related](https://fasterthanli.me/articles/pin-and-suffering) 32 | [Futures](https://fasterthanli.me/articles/understanding-rust-futures-by-going-way-too-deep) 33 | 34 | ## Error related 35 | 36 | [A great walkthrough](https://www.lpalmieri.com/posts/error-handling-rust/) of error handling by the [Zero to Production in Rust](https://www.zero2prod.com/) author. 37 | 38 | [Recoverable Errors with Result](https://doc.rust-lang.org/stable/book/ch09-02-recoverable-errors-with-result.html) 39 | 40 | ## API related 41 | 42 | * [Elegant Library APIs in Rust - Pascal’s Scribbles](https://deterministic.space/elegant-apis-in-rust.html) 43 | * [Checklist](https://rust-lang.github.io/api-guidelines/checklist.html) 44 | 45 | ## Optimizations 46 | 47 | [Paraphrasing](https://old.reddit.com/r/rust/comments/133wk4x/help_with_rust_program_performance/jid46k4/): _But if you need to squeeze every bit of speed out of it, add `lto = true` to your release profile in your `cargo.toml` to enable link time optimizations. It increases build times for your release builds, but I find it gives 10-20% better performance._ 48 | 49 | [More](https://old.reddit.com/r/rust/comments/133wk4x/help_with_rust_program_performance/jibsyge/): `cargo run` will also build, so if you do `cargo build --release` followed by `cargo run` then it will run an __unoptimized__ build 50 | 51 | ## Lifetimes 52 | 53 | [Outlines](https://hashrust.com/blog/lifetimes-in-rust/) the various meanings of 'lifetime' wording. 54 | 55 | ## Closures 56 | 57 | [A great writeup](https://hashrust.com/blog/a-guide-to-closures-in-rust/) detailing differences of `Fn`, `FnOnce`, and `FnMut`. 58 | 59 | ## Tracing 60 | 61 | [This](https://www.lpalmieri.com/posts/2020-09-27-zero-to-production-4-are-we-observable-yet/) covers the hows and whys of moving from logging to tracing. 62 | 63 | ## Ownership 64 | 65 | [OP](https://old.reddit.com/r/rust/comments/14f1mnd/why_is_there_no_standard_way_of_removing_the/) asked about mutability, but [the core issue involves ownership](https://smallcultfollowing.com/babysteps/blog/2014/05/13/focusing-on-ownership/). 66 | 67 | We can use [as &_](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=de64020e4a8c2454c9a9861297971ffe) and others. 68 | 69 | ````rust 70 | //option 1 71 | let x = &mut whatever; 72 | let y = &*x; 73 | //option 2 74 | let y: &_ = x; 75 | //option 3 76 | let x = &*x; 77 | ```` 78 | 79 | ## Standard library goodness 80 | 81 | [Concat](https://doc.rust-lang.org/std/primitive.slice.html#method.concat) various types 82 | 83 | ## Destructuring 84 | 85 | Function arguments can be destructured as shown [in this thread](https://old.reddit.com/r/rust/comments/14rg4pw/rust_doesnt_have_named_arguments_so_what/jqwomyv/). 86 | 87 | _In Rust, anytime you have a binding -- ie, you define a name for a variable -- you have pattern-matching:_ 88 | 89 | ````rust 90 | let Args { opt_a, opt_b } = args; 91 | 92 | fn foo(Args { opt_a, opt_b }: Args); 93 | ```` 94 | 95 | _For let, you can even use refutable patterns, by using let..else:_ 96 | 97 | ````rust 98 | let Some(a) = a /_Option_/ else { 99 | return x; 100 | }; 101 | ```` 102 | 103 | More examples 104 | 105 | ````rust 106 | struct Args { opt_a: i32, opt_b: u32, } 107 | 108 | //fn my_function(args: Args) {} 109 | fn my_function(Args {opt_a, opt_b}: Args) { 110 | println!("{} {}", opt_a, opt_b); 111 | } 112 | 113 | fn main() { 114 | my_function(Args { opt_a: 1, opt_b: 4, }); 115 | } 116 | ```` 117 | 118 | and _Defaults can be added by implementing `Default` on the `Args` struct and using `..Default::default()` at the callsite._ 119 | 120 | ## foo 121 | 122 | From and Into round-up 123 | 124 | TODO: pull the important bits from the following 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | ## Unsorted 135 | 136 | [The Little Book of Rust Books](https://lborb.github.io/book/) 137 | 138 | 139 | 140 | You can [implement a trait](https://old.reddit.com/r/rust/comments/14wsv8b/ampersand_in_impl_statement/) for `Reference` which they say can also be done for `Mutable Reference`. 141 | 142 | [godbolt](https://godbolt.org/) can be used to decompile a rust program. 143 | 144 | [criterion](https://github.com/bheisler/criterion.rs) for benchmarks 145 | [cargo-flamegraph](https://github.com/flamegraph-rs/flamegraph) for performance profiling 146 | [dhat](https://github.com/nnethercote/dhat-rs) for heap profiling 147 | 148 | For any crates you like at crates.io, add them to your [following list](https://crates.io/me/following). 149 | 150 | Check for gems [here](https://old.reddit.com/r/rust/comments/13zq1j8/what_little_known_rust_feature_or_standard/) 151 | 152 | [Using various lists](https://subscription.packtpub.com/book/application-development/9781788995528/4/ch04lvl1sec23/linked-lists) 153 | 154 | Link to [std lib](https://doc.rust-lang.org/std/index.html). 155 | Link to crate [docs](https://docs.rs/). 156 | 157 | [Blessed](https://blessed.rs/) - An unofficial guide to the Rust ecosystem 158 | 159 | Check out the [github star list](https://github.com/stars/rogin/lists/rust). 160 | 161 | [A tour of standard library traits](https://github.com/pretzelhammer/rust-blog/blob/master/posts/tour-of-rusts-standard-library-traits.md). 162 | 163 | [Rustlings](https://github.com/rust-lang/rustlings/) are small exercises. 164 | 165 | From [Learn Rust the Dangerous Way](https://cliffle.com/p/dangerust/6/) 166 | 167 | * Document the compiler version you tested with, or ideally, pin it in your build system. (Rust has the [rust-toolchain](https://docs.rs/rust-toolchain/latest/rust_toolchain/) file to do this.) 168 | * Include a benchmark test that will indicate if things have suddenly gotten slower, so you can at least know to investigate. Ensure that this test runs as part of the build or continuous integration flow. (For Rust projects using Cargo, I recommend [Criterion](https://docs.rs/criterion/).) 169 | 170 | [Rust Language Cheat Sheet](https://cheats.rs/) 171 | 172 | A great deep dive into [unique access](https://limpet.net/mbrubeck/2019/02/07/rust-a-unique-perspective.html). 173 | 174 | [Enabling debug info](https://blog.rust-lang.org/2023/04/20/Rust-1.69.0.html#debug-information-is-not-included-in-build-scripts-by-default-anymore) 175 | -------------------------------------------------------------------------------- /vscode-markdown-toc-config: -------------------------------------------------------------------------------- 1 | numbering=false 2 | autoSave=true --------------------------------------------------------------------------------