{"id":18841238,"url":"https://github.com/dcm4che/dcm4chee-conf","last_synced_at":"2025-04-14T07:08:45.531Z","repository":{"id":29502889,"uuid":"33040948","full_name":"dcm4che/dcm4chee-conf","owner":"dcm4che","description":"Single repo for dcm4che configuration EE extensions: DB storage, web UI/REST generic API, CDI, etc","archived":false,"fork":false,"pushed_at":"2017-06-28T14:11:54.000Z","size":834,"stargazers_count":2,"open_issues_count":0,"forks_count":7,"subscribers_count":8,"default_branch":"master","last_synced_at":"2025-04-14T07:08:41.129Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dcm4che.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2015-03-28T16:33:31.000Z","updated_at":"2017-01-11T12:48:41.000Z","dependencies_parsed_at":"2022-08-22T07:51:12.221Z","dependency_job_id":null,"html_url":"https://github.com/dcm4che/dcm4chee-conf","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dcm4che%2Fdcm4chee-conf","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dcm4che%2Fdcm4chee-conf/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dcm4che%2Fdcm4chee-conf/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dcm4che%2Fdcm4chee-conf/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dcm4che","download_url":"https://codeload.github.com/dcm4che/dcm4chee-conf/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248837281,"owners_count":21169374,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-08T02:50:29.555Z","updated_at":"2025-04-14T07:08:45.510Z","avatar_url":"https://github.com/dcm4che.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"## Architecture\n\nThe architecture of configuration framework contains the following layers:\n\n    Upgrade engine. Allows to peform configuration upgrades (e.g. when updating older site installations) that are executed on startup. \n    \n    DicomConfiguration. Dicom configuration manipulation ( i.e. managing devices, transfer capabilities, ... )\n        \n    JSON conversion. Handles converting configuration nodes (i.e. json-like structures) to type-safe configurable instances and vice-versa (Vitalizer, json de/serialization adapters, ...)\n    \n    ConfigurationEJB - low-level access\n            |\n      t     |   Defaults filter \n      r     |   Hash-based optimistic locking \n      a     |   Extension merging\n      n     |   \n      s     |   Reference index\n      a     |   Infinispan cache\n      c     |   \n      t     |   Storage (DB, json file)\n            |-----\n            \\   (pre-commit)Integrity check\n            \\   (post-commit)Notifications\n\n \n## How to access (read/write) configuration / implement dedicated APIs\n\nTo access the dicom configuration, inject a `DicomConfiguration` bean with CDI like\n\n    @Inject\n    DicomConfiguration config;\n\n\n[DicomConfiguration](https://github.com/dcm4che/dcm4che/blob/master/dcm4che-conf/dcm4che-conf-api/src/main/java/org/dcm4che3/conf/api/DicomConfiguration.java) is the primary interface for configuration access. It is type-safe and its implementation is supposed to perform thorough validation when persisting changes.\n Always give this interface the preference over other access methods.\n\nBuild dedicated APIs on top of `DicomConfiguration` to wrap up some special configuration functionality.\n\n### Shortcut to access the 'primary device'\n\nThe archive contains a device producer so many components can easily lookup the configuration of the primary device that corresponds to the running application, like\n\n    @Inject \n    private Device configDevice;\n\n\n**IMPORTANT - do not change 'injected' device**\n\nThis java object is a singleton and its producer is an application scoped CDI bean, so this Device instance is shared among the application components and is therefore should only be used for reading the config.\nTo make modifications, use DicomConfiguration to lookup the device yourself (you could get the device name from that very configDevice), modify and merge that fresh isolated instance.\nOtherwise, it's not thread-safe and could result into unexpected behavior.       \n\nAn attempt to change the injected device will result into an error log message like the following:\n\n    17:33:34,654 ERROR [org.dcm4che3.conf.dicom.CommonDicomConfiguration] (pool-7-thread-5) Persisting the config for the Device object that is marked as read-only. This error is not affecting the behavior for now, but soon it will be replaced with an exception!If you want to make config modifications, use a separate instance of Device! See CSP configuration docs for details.\n\n If you find such a message in the log related to your config manipulations - then you are doing it wrong.\n\n## Low-level configuration access\n\nFor special cases, one can inject `DicomConfigurationManager` and call `.getConfigurationStorage()` on it to obtain an instance of [Configuration](https://github.com/dcm4che/dcm4che/blob/master/dcm4che-conf/dcm4che-conf-core-api/src/main/java/org/dcm4che3/conf/core/api/Configuration.java).\nThis will allow a not-so-safe low-level access to the configuration. Although the validation of changes will still be enforced, it is more probable to introduce inconsistencies while using this layer. The usage of [Configuration](https://github.com/dcm4che/dcm4che/blob/master/dcm4che-conf/dcm4che-conf-core-api/src/main/java/org/dcm4che3/conf/core/api/Configuration.java) interface for making configuration changes is therefore discouraged.\n\nThe valid direct use-case of `Configuration` is issuing custom queries (i.e. that rely on some AE/Device/HL7 extensions)\n\n## Config storage\n\nCurrently, only the database config storage backend provides the support for clustering and transactional ACID properties, and is therefore recommended for production deployments.\nTo use the database as configuration storage, deploy `org.dcm4che.dcm4chee-conf:dcm4chee-conf-db` as an EJB inside the ear and set the property \n\n    org.dcm4che.conf.storage = db_blobs\n\nAlternatively, e.g. for development/testing purposes, one can use simple json file config storage (org.dcm4che.conf.storage = json_file)  \n\n## Transactions\nAll configuration modifying operations are transactional.\nConfig modifications will NOT join any ongoing transaction. To perform multiple config-related operations in a single transaction, one has to use batching.\n\nOn transaction commit, configuration integrity check is performed.\nAfter successful commit, all cluster nodes are notified (see Config change notifications) so they can perform some actions upon config updates.   \n\nBoth the cache and the DB participate in the 2-phase commit of the transaction. \n\n## Locking\nWrites to the configuration are done in an exclusive manner - one write at most is done at a time within the cluster. This greatly simplifies concurrency concerns and at the same time, \ndue to the not-so-volatile nature of configuration (rare updates), is not critical to the overall system performance.\n  \nEvery call to a method that modifies the configuration thus acquires a pessimistic lock on a certain db record (dcm4che_config table, path = \"/misc/locking/dblock\").\n\nRunning a batch acquires the same exclusive lock.\n\n## Caching\n\nAccess to the configuration is always performed against a replicated clustered Infinispan cache. This implies that if a modification succeeds, the cache is synchronously updated on other cluster nodes (max consistency).\nThe cache contains the full configuration at all times. This simplification should not be a concern unless the configuration size exceeds e.g. 50 MB.\nThe actual content of the cache is the low-level configuration representation, i.e. not typesafe configurable objects, but json-like primitives. \n\nThe cache uses READ-COMMITTED isolation. The updates are therefore only visible to other readers after the successful transaction commit.\n\n## Batching\n\nTo perform multiple changes as a single atomic operation, one should use `org.dcm4che3.conf.api.DicomConfiguration.runBatch` / `org.dcm4che3.conf.core.api.Configuration.runBatch` methods.\nThe batch will be executed in a separate new transaction. It is guaranteed that at most one batch is executed at the same time, and also that no other config modifications will be done during the batch execution.\n\n## Hash-based optimistic locking\n\nWhen updating something in the configuration, hash-based optimistic locking is used to prevent conflicting changes and to preserve the parts of the configuration object \nthat were changed by other user/component, but were not changed by the transaction at hand.     \n\n#### Core principle\n\nThe mechanism is based on \"fingerprinting\" the portions of configuration objects by calculating a hash that represents a certain configuration state. \nIf a node is changed (e.g., a parameter is changed or a map entry is removed or the order in a collection is changed) the corresponding hash will change as well.\nThose hashes are not stored, but calculated when configuration is loaded. The calculated values are then injected into the properties of type \norg.dcm4che3.conf.core.api.ConfigurableProperty.ConfigurablePropertyType.OptimisticLockingHash.\n\nWhen the node is merged back, the hashes are calculated again, and compared to old values (that were calculated on loading of this object), and also to the values of those currently in the storage.\nThe following logic is then applied\n\n- If the user has not changed the node (or its part to which the hash corresponds), then the values for this node (part of the node) from the storage are preserved. \n- If the user has changed the node, then\n    - if it was not changed by other users in the meantime, i.e. the hash from the storage equals to the old hash, the new node is persisted\n    - if it was also changed by other users in the meantime, i.e. the hash from the storage is different from to the old hash, \n    then such modification is not allowed, and org.dcm4che3.conf.core.api.OptimisticLockException is thrown. \n\nThe algorithm is recursively applied to full configuration node. The boundaries for hash calculation and comparison are marked by configurable properties of type \norg.dcm4che3.conf.core.api.ConfigurableProperty.ConfigurablePropertyType.OptimisticLockingHash.\n\nThe mechanism is also applicable to collections if collection elements have UUIDs defined (otherwise there is no straightforward way to conclude that collection elements match).\nFor example, it is enabled for connections of a device.\n\n#### Example\n\nFor example, let three users load the same archive device from the configuration in the same moment in time. \n\n1. first user modifies the connection of a dicom device by changing its port and persists the change. This modification succeeds.\n2. second user modifies the same connection by changing bind address and tries to persist the change. This modification fails, because it conflicts with the previous modification. \n3. third user adds a storage system to storage device extension. This modification succeeds, and even though the device being persisted has the stale configuration for the connection, \nit does not overwrite it because third user has not changed that part, and therefore the values from the config storage are preserved. \n\nThis scenario is implemented as a JUnit test here:\n\n    org.dcm4chee.archive.conf.olock.DicomConfigOptimisticLockingTests#optimisticLockingDemoTest\n\nOther tests that demonstrate the hash-based optimistic locking can be found in the same test class.\n\n#### How it affects components that use configuration \n\nThe users of configuration framework should keep 2 things in mind: \n\n- When trying to merge configuration, be prepared that OptimisticLockException can be thrown. \nIn the most simple case, re-loading the device again, modifying it, and persisting it again should work (be sure to re-execute any conditional logic that could be affected by the new changes).\nMore sophisticated scenarios (e.g. with config UI) could notify the user of the conflict and provide him/her the options to resolve it. \n- If serializing the configuration objects into some different data structures, be sure to preserve the values from the `olockHash` fields. \n\n## Config change notifications\nConfiguration framework triggers cluster-wide notification when a change occurs. \nInterested components may observe [org.dcm4che3.conf.core.api.ConfigChangeEvent](https://github.com/dcm4che/dcm4che/blob/master/dcm4che-conf/dcm4che-conf-core-api/src/main/java/org/dcm4che3/conf/core/api/ConfigChangeEvent.java) CDI event which is fired on each node.\nIn case of batching, the notification is only triggered when the full batch succeeds.\n  \nCurrent implementation uses `topic/DicomConfigurationChangeTopic` JMS topic to distribute the notifications across the cluster. The topic therefore must be added to the server config, e.g. \n    \n    jms-topic add --topic-address=DicomConfigurationChangeTopic --entries=/topic/DicomConfigurationChangeTopic\n\n  \n\n## How to perform upgrade/migration\n\nUpgrade mechanism allows to use both high-level type-safe API ([DicomConfiguration](https://github.com/dcm4che/dcm4che/blob/master/dcm4che-conf/dcm4che-conf-api/src/main/java/org/dcm4che3/conf/api/DicomConfiguration.java)) and low-level access API ([Configuration](https://github.com/dcm4che/dcm4che/blob/master/dcm4che-conf/dcm4che-conf-core-api/src/main/java/org/dcm4che3/conf/core/api/Configuration.java)).\nTo create an upgrade routine one needs to\n\n1. crate a class that implements `org.dcm4che3.conf.api.upgrade.UpgradeScript` interface,\n2. make sure this class is contained in the deployment and is visible by CDI,\n3. include the full class name in the upgrade setting file.\n\nUpgrade is performed on server startup before any method can access the configuration. The steps are the following:\n\n1.  The upgrade runner reads the upgrade settings file. The filename should be specified by `org.dcm4che.conf.upgrade.settingsFile` system property. If the property is not set, the upgrade mechanism is disabled.\n    Example of an upgrade settings file:\n\n        {\n         \"upgradeToVersion\": \"1.5\",\n         \"upgradeScriptsToRun\": [\n           \"com.mycompany.upgrade.UpgradeFirst\",\n           \"com.mycompany.upgrade.UpgradeSecond\",\n          ],\n          \"activeUpgradeRunnerDeployment\": \"my-ear-name-which-should-perform-upgrade\"\n          \"properties\":{\n            \"aPropertyForMyUpgradeScripts\":\"aValue\"\n          }\n        }\n2. The runner will call all the referenced upgrade scripts in the sequence specified by `upgradeScriptsToRun`, making sure that the upgrade is executed on a single node and in an atomic manner (i.e. all-or-nothing).\n`org.dcm4che3.conf.api.upgrade.UpgradeScript` interface provides both typesafe and not typesafe access to the configuration, as well as to upgrade context properties, including\n        `fromVersion`, `toVersion`, and `properties` (these properties are populated from the upgrade settings file, see above);\n\n3. If the upgrade succeeds, the changes are committed and the startup process proceeds. If there is an error during the upgrade, the changes are rolled back and the deployment fails.\n\n\nEvery upgrade script should be marked with `@org.dcm4che3.conf.api.upgrade.ScriptVersion` annotation. The runner will make sure that an upgrade script is only executed when either\n- the script was never executed before\n- the current version of the script is greater than the last executed version (String.compareTo is used to compare)\nIf a script has no such annotation - it will be assigned a default version - see constant org.dcm4che3.conf.api.upgrade.UpgradeScript#NO_VERSION .\n\nIf the property `activeUpgradeRunnerDeployment` is specified (can be either full name (without extension) or a prefix), the upgrade will only be ran from that deployment. \nOther deployments will just wait for the current configuration version to become equal to `upgradeToVersion`.  \n\nExample: [DefaultArchiveConfigInitScript](https://github.com/dcm4che/dcm4chee-arc-cdi/blob/master/dcm4chee-arc-conf-default/src/main/java/org/dcm4chee/archive/conf/defaults/DefaultArchiveConfigInitScript.java)\n   \n   \n   \n## Development\nConfiguration components can be disabled for development purposes.\n\n- To disable referential integrity check performed before transaction commit, set\n\n        org.dcm4che.conf.disableIntegrityCheck = true\n\n- To disable JMS-based cluster config update notifications, set\n\n        org.dcm4che.conf.notifications = false   \n\n## Examples\n\n- [How to create a custom AE extension and use it in a StoreService decorator ](https://github.com/dcm4che/dcm4chee-integration-examples/tree/master/config-extensions-example)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdcm4che%2Fdcm4chee-conf","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdcm4che%2Fdcm4chee-conf","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdcm4che%2Fdcm4chee-conf/lists"}