Skip to content

Commit

Permalink
Generalize Attribute Coercion and enable to apply multiple matching A…
Browse files Browse the repository at this point in the history
…ttribute Coercions in configurable order #2967
  • Loading branch information
gunterze committed Nov 8, 2021
1 parent f982be1 commit 4a689cd
Show file tree
Hide file tree
Showing 34 changed files with 493 additions and 98 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -53,14 +53,18 @@
*/
@ApplicationScoped
public class CoercionFactory {
private static final CoercionProcessor NOOP =
(coercion, sendingHost, sendingAET, receivingHost, receivingAET, attributes, coercedAttributes) -> true;

@Inject
private NamedCDIBeanCache namedCDIBeanCache;

@Inject
private Instance<CoercionProcessor> processors;

public CoercionProcessor getCoercionProcessor(ArchiveAttributeCoercion2 coercion) {
String scheme = coercion.getAttributeCoercionURI().getScheme();
return namedCDIBeanCache.get(processors, scheme);
String scheme = coercion.getScheme();
return scheme.equals(ArchiveAttributeCoercion2.NULLIFY_PIXEL_DATA) || scheme.equals(ArchiveAttributeCoercion2.RETRIEVE_AS_RECEIVED)
? NOOP : namedCDIBeanCache.get(processors, scheme);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,8 @@ public interface CoercionProcessor {
boolean coerce(ArchiveAttributeCoercion2 coercion,
String sendingHost, String sendingAET, String receivingHost, String receivingAET,
Attributes attributes, Attributes coercedAttributes) throws Exception;

default String remapUID(ArchiveAttributeCoercion2 coercion, String uid) {
return uid;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
* *** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is part of dcm4che, an implementation of DICOM(TM) in
* Java(TM), hosted at https://github.com/dcm4che.
*
* The Initial Developer of the Original Code is
* J4Care.
* Portions created by the Initial Developer are Copyright (C) 2013-2021
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* See @authors listed below
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* *** END LICENSE BLOCK *****
*/

package org.dcm4chee.arc.coerce;

import org.dcm4che3.data.Attributes;
import org.dcm4che3.deident.DeIdentifier;
import org.dcm4che3.util.StringUtils;
import org.dcm4che3.util.UIDUtils;
import org.dcm4chee.arc.conf.ArchiveAttributeCoercion2;

import javax.enterprise.context.ApplicationScoped;
import javax.inject.Named;

/**
* @author Gunter Zeilinger (gunterze@protonmail.com)
* @since Nov 2021
*/
@ApplicationScoped
@Named("deidentify")
public class DeIdentificationCoercionProcessor implements CoercionProcessor {
@Override
public boolean coerce(ArchiveAttributeCoercion2 coercion,
String sendingHost, String sendingAET,
String receivingHost, String receivingAET,
Attributes attrs, Attributes modified)
throws Exception {
String[] names = StringUtils.split(coercion.getSchemeSpecificPart(), ',');
DeIdentifier.Option[] options = new DeIdentifier.Option[names.length];
for (int i = 0; i < names.length; i++) {
options[i] = DeIdentifier.Option.valueOf(names[i]);
}
new DeIdentifier(options).deidentify(attrs);
return true;
}

@Override
public String remapUID(ArchiveAttributeCoercion2 coercion, String uid) {
for (String name : StringUtils.split(coercion.getSchemeSpecificPart(), ',')) {
if (name.equals("RetainUIDsOption")) return uid;
}
return UIDUtils.remapUID(uid);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,12 @@
* *** END LICENSE BLOCK *****
*/

package org.dcm4chee.arc.coerce.xslt;
package org.dcm4chee.arc.coerce;

import org.dcm4che3.data.Attributes;
import org.dcm4che3.io.SAXTransformer;
import org.dcm4che3.io.TemplatesCache;
import org.dcm4che3.util.StringUtils;
import org.dcm4chee.arc.coerce.CoercionProcessor;
import org.dcm4chee.arc.conf.ArchiveAttributeCoercion2;
import org.dcm4chee.arc.conf.Conditions;

Expand All @@ -66,7 +65,7 @@ public boolean coerce(ArchiveAttributeCoercion2 coercion,
String receivingHost, String receivingAET,
Attributes attrs, Attributes modified)
throws Exception {
String xsltStylesheetURI = coercion.getAttributeCoercionURI().getSchemeSpecificPart();
String xsltStylesheetURI = coercion.getSchemeSpecificPart();
Templates tpls = TemplatesCache.getDefault().get(StringUtils.replaceSystemProperties(xsltStylesheetURI));
Attributes newAttrs = SAXTransformer.transform(attrs, tpls, false, true,
t -> setParameters(t, sendingHost, sendingAET, receivingHost, receivingAET));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,7 @@ protected void storeTo(Device device, JsonWriter writer) {
writeArchiveCompressionRules(writer, arcDev.getCompressionRules());
writeStoreAccessControlIDRules(writer, arcDev.getStoreAccessControlIDRules());
writeArchiveAttributeCoercion(writer, arcDev.getAttributeCoercions());
writeArchiveAttributeCoercion2(writer, arcDev.getAttributeCoercions2());
writeRejectionNote(writer, arcDev.getRejectionNotes());
writeStudyRetentionPolicies(writer, arcDev.getStudyRetentionPolicies());
writeHL7StudyRetentionPolicies(writer, arcDev.getHL7StudyRetentionPolicies());
Expand Down Expand Up @@ -788,7 +789,7 @@ private void writeArchiveAttributeCoercion2(
writer.writeNotNullOrDef("dicomTransferRole", aac.getRole(), null);
writer.writeNotEmpty("dcmSOPClass", aac.getSOPClasses());
writer.writeNotEmpty("dcmProperty", aac.getConditions().getMap());
writer.writeNotNullOrDef("dcmURI", aac.getAttributeCoercionURI(), null);
writer.writeNotNullOrDef("dcmURI", aac.getURI(), null);
writer.writeNotNullOrDef("dcmAttributeUpdatePolicy",
aac.getAttributeUpdatePolicy(), Attributes.UpdatePolicy.MERGE);
writer.writeNotNullOrDef("dicomDeviceName", deviceNameOf(aac.getOtherDevice()), null);
Expand Down Expand Up @@ -1300,6 +1301,7 @@ protected void storeTo(ApplicationEntity ae, JsonWriter writer) {
writeArchiveCompressionRules(writer, arcAE.getCompressionRules());
writeStoreAccessControlIDRules(writer, arcAE.getStoreAccessControlIDRules());
writeArchiveAttributeCoercion(writer, arcAE.getAttributeCoercions());
writeArchiveAttributeCoercion2(writer, arcAE.getAttributeCoercions2());
writeStudyRetentionPolicies(writer, arcAE.getStudyRetentionPolicies());
writeRSForwardRules(writer, arcAE.getRSForwardRules());
writeUPSOnStoreList(writer, arcAE.listUPSOnStore());
Expand Down Expand Up @@ -2913,7 +2915,7 @@ private void loadArchiveAttributeCoercion2(Collection<ArchiveAttributeCoercion2>
aac.setConditions(new Conditions(reader.stringArray()));
break;
case "dcmURI":
aac.setAttributeCoercionURI(URI.create(reader.stringValue()));
aac.setURI(reader.stringValue());
break;
case "dcmAttributeUpdatePolicy":
aac.setAttributeUpdatePolicy(Attributes.UpdatePolicy.valueOf(reader.stringValue()));
Expand Down Expand Up @@ -4139,6 +4141,9 @@ private void loadFrom(ArchiveAEExtension arcAE, JsonReader reader, Configuration
case "dcmArchiveAttributeCoercion":
loadArchiveAttributeCoercion(arcAE.getAttributeCoercions(), reader, config);
break;
case "dcmArchiveAttributeCoercion2":
loadArchiveAttributeCoercion2(arcAE.getAttributeCoercions2(), reader, config);
break;
case "dcmStudyRetentionPolicy":
loadStudyRetentionPolicy(arcAE.getStudyRetentionPolicies(), reader);
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1540,6 +1540,7 @@ else if (bb == null)
mergeCompressionRules(diffs, aa.getCompressionRules(), bb.getCompressionRules(), deviceDN);
mergeStoreAccessControlIDRules(diffs, aa.getStoreAccessControlIDRules(), bb.getStoreAccessControlIDRules(), deviceDN);
mergeAttributeCoercions(diffs, aa.getAttributeCoercions(), bb.getAttributeCoercions(), deviceDN);
mergeAttributeCoercions2(diffs, aa.getAttributeCoercions2(), bb.getAttributeCoercions2(), deviceDN);
mergeQueryRetrieveViews(diffs, aa, bb, deviceDN);
mergeRejectNotes(diffs, aa, bb, deviceDN);
mergeStudyRetentionPolicies(diffs, aa.getStudyRetentionPolicies(), bb.getStudyRetentionPolicies(), deviceDN);
Expand Down Expand Up @@ -2208,6 +2209,7 @@ else if (bb == null)
mergeCompressionRules(diffs, aa.getCompressionRules(), bb.getCompressionRules(), aeDN);
mergeStoreAccessControlIDRules(diffs, aa.getStoreAccessControlIDRules(), bb.getStoreAccessControlIDRules(), aeDN);
mergeAttributeCoercions(diffs, aa.getAttributeCoercions(), bb.getAttributeCoercions(), aeDN);
mergeAttributeCoercions2(diffs, aa.getAttributeCoercions2(), bb.getAttributeCoercions2(), aeDN);
mergeStudyRetentionPolicies(diffs, aa.getStudyRetentionPolicies(), bb.getStudyRetentionPolicies(), aeDN);
mergeRSForwardRules(diffs, aa.getRSForwardRules(), bb.getRSForwardRules(), aeDN);
mergeUPSOnStoreList(diffs, aa.listUPSOnStore(), bb.listUPSOnStore(), aeDN);
Expand Down Expand Up @@ -5280,6 +5282,36 @@ private void mergeAttributeCoercions(
}
}

private void mergeAttributeCoercions2(
ConfigurationChanges diffs, Collection<ArchiveAttributeCoercion2> prevCoercions,
Collection<ArchiveAttributeCoercion2> coercions,
String parentDN) throws NamingException {
for (ArchiveAttributeCoercion2 prev : prevCoercions) {
String cn = prev.getCommonName();
if (findAttributeCoercion2ByCN(coercions, cn) == null) {
String dn = LdapUtils.dnOf("cn", cn, parentDN);
config.destroySubcontext(dn);
ConfigurationChanges.addModifiedObject(diffs, dn, ConfigurationChanges.ChangeType.D);
}
}
for (ArchiveAttributeCoercion2 coercion : coercions) {
String cn = coercion.getCommonName();
String dn = LdapUtils.dnOf("cn", cn, parentDN);
ArchiveAttributeCoercion2 prev = findAttributeCoercion2ByCN(prevCoercions, cn);
if (prev == null) {
ConfigurationChanges.ModifiedObject ldapObj =
ConfigurationChanges.addModifiedObject(diffs, dn, ConfigurationChanges.ChangeType.C);
config.createSubcontext(dn,
storeTo(ConfigurationChanges.nullifyIfNotVerbose(diffs, ldapObj),
coercion, new BasicAttributes(true)));
} else {
ConfigurationChanges.ModifiedObject ldapObj =
ConfigurationChanges.addModifiedObject(diffs, dn, ConfigurationChanges.ChangeType.U);
config.modifyAttributes(dn, storeDiffs(ldapObj, prev, coercion, new ArrayList<>()));
ConfigurationChanges.removeLastIfEmpty(diffs, ldapObj);
}
}
}
private void mergeRSForwardRules(
ConfigurationChanges diffs, Collection<RSForwardRule> prevRules, Collection<RSForwardRule> rules, String parentDN)
throws NamingException {
Expand Down Expand Up @@ -5655,15 +5687,15 @@ private void storeAttributeCoercions2(ConfigurationChanges diffs, Collection<Arc
}

private Attributes storeTo(ConfigurationChanges.ModifiedObject ldapObj, ArchiveAttributeCoercion2 coercion, BasicAttributes attrs) {
attrs.put("objectclass", "dcmArchiveAttributeCoercion");
attrs.put("objectclass", "dcmArchiveAttributeCoercion2");
attrs.put("cn", coercion.getCommonName());
LdapUtils.storeNotNullOrDef(ldapObj, attrs, "dicomDescription", coercion.getDescription(), null);
LdapUtils.storeNotDef(ldapObj, attrs, "dcmRulePriority", coercion.getPriority(), 0);
LdapUtils.storeNotNullOrDef(ldapObj, attrs, "dcmDIMSE", coercion.getDIMSE(), null);
LdapUtils.storeNotNullOrDef(ldapObj, attrs, "dicomTransferRole", coercion.getRole(), null);
LdapUtils.storeNotEmpty(ldapObj, attrs, "dcmSOPClass", coercion.getSOPClasses());
LdapUtils.storeNotEmpty(ldapObj, attrs, "dcmProperty", coercion.getConditions().getMap());
LdapUtils.storeNotNullOrDef(ldapObj, attrs, "dcmURI", coercion.getAttributeCoercionURI(), null);
LdapUtils.storeNotNullOrDef(ldapObj, attrs, "dcmURI", coercion.getURI(), null);
LdapUtils.storeNotNullOrDef(ldapObj, attrs, "dcmAttributeUpdatePolicy",
coercion.getAttributeUpdatePolicy(), org.dcm4che3.data.Attributes.UpdatePolicy.MERGE);
LdapUtils.storeNotNullOrDef(ldapObj, attrs, "dicomDeviceName",
Expand Down Expand Up @@ -5742,7 +5774,7 @@ private void loadAttributeCoercions2(Collection<ArchiveAttributeCoercion2> coerc
LdapUtils.enumValue(TransferCapability.Role.class, attrs.get("dicomTransferRole"), null));
coercion.setSOPClasses(LdapUtils.stringArray(attrs.get("dcmSOPClass")));
coercion.setConditions(new Conditions(LdapUtils.stringArray(attrs.get("dcmProperty"))));
coercion.setAttributeCoercionURI(toURI(attrs.get("dcmURI")));
coercion.setURI(LdapUtils.stringValue(attrs.get("dcmURI"), null));
coercion.setAttributeUpdatePolicy(LdapUtils.enumValue(org.dcm4che3.data.Attributes.UpdatePolicy.class,
attrs.get("dcmAttributeUpdatePolicy"), org.dcm4che3.data.Attributes.UpdatePolicy.MERGE));
String otherDevice = LdapUtils.stringValue(attrs.get("dicomDeviceName"), null);
Expand Down Expand Up @@ -5835,6 +5867,40 @@ private ArchiveAttributeCoercion findAttributeCoercionByCN(
return null;
}

private List<ModificationItem> storeDiffs(
ConfigurationChanges.ModifiedObject ldapObj, ArchiveAttributeCoercion2 prev, ArchiveAttributeCoercion2 coercion, ArrayList<ModificationItem> mods) {
LdapUtils.storeDiffObject(ldapObj, mods, "dicomDescription", prev.getDescription(), coercion.getDescription(), null);
LdapUtils.storeDiff(ldapObj, mods, "dcmRulePriority", prev.getPriority(), coercion.getPriority(), 0);
LdapUtils.storeDiffObject(ldapObj, mods, "dcmDIMSE", prev.getDIMSE(), coercion.getDIMSE(), null);
LdapUtils.storeDiffObject(ldapObj, mods, "dicomTransferRole", prev.getRole(), coercion.getRole(), null);
LdapUtils.storeDiff(ldapObj, mods, "dcmSOPClass", prev.getSOPClasses(), coercion.getSOPClasses());
LdapUtils.storeDiffProperties(ldapObj, mods, "dcmProperty",
prev.getConditions().getMap(), coercion.getConditions().getMap());
LdapUtils.storeDiffObject(ldapObj, mods, "dcmURI", prev.getURI(), coercion.getURI(), null);
LdapUtils.storeDiffObject(ldapObj, mods, "dcmAttributeUpdatePolicy",
prev.getAttributeUpdatePolicy(),
coercion.getAttributeUpdatePolicy(),
org.dcm4che3.data.Attributes.UpdatePolicy.MERGE);
LdapUtils.storeDiffObject(ldapObj, mods, "dicomDeviceName",
deviceNameOf(prev.getOtherDevice()),
deviceNameOf(coercion.getOtherDevice()), null);
LdapUtils.storeDiff(ldapObj, mods, "dcmMergeAttribute",
prev.getMergeAttributes(), coercion.getMergeAttributes());
LdapUtils.storeDiffProperties(ldapObj, mods, "dcmCoercionParam",
prev.getCoercionParams(), coercion.getCoercionParams());
LdapUtils.storeDiff(ldapObj, mods, "dcmCoercionSufficient",
prev.isCoercionSufficient(), coercion.isCoercionSufficient(), false);
return mods;
}

private ArchiveAttributeCoercion2 findAttributeCoercion2ByCN(
Collection<ArchiveAttributeCoercion2> coercions, String cn) {
for (ArchiveAttributeCoercion2 coercion : coercions)
if (coercion.getCommonName().equals(cn))
return coercion;
return null;
}

private void storeRejectNotes(ConfigurationChanges diffs, String deviceDN, ArchiveDeviceExtension arcDev) throws NamingException {
for (RejectionNote rejectionNote : arcDev.getRejectionNotes()) {
String dn = LdapUtils.dnOf("dcmRejectionNoteLabel", rejectionNote.getRejectionNoteLabel(), deviceDN);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,7 @@ public AttributesCoercion nullifyIssuerOfPatientID(Attributes attrs, final Attri

return new AttributesCoercion() {
@Override
public void coerce(Attributes attrs, Attributes modified) {
public void coerce(Attributes attrs, Attributes modified) throws Exception {
String issuerOfPatientID = attrs.getString(Tag.IssuerOfPatientID);
if (issuerOfPatientID != null && !issuerOfPatientID.isEmpty()) {
attrs.setNull(Tag.IssuerOfPatientID, VR.LO);
Expand Down Expand Up @@ -387,7 +387,7 @@ public AttributesCoercion mergeAttributes(final AttributesCoercion next) {

return new AttributesCoercion() {
@Override
public void coerce(Attributes attrs, Attributes modified) {
public void coerce(Attributes attrs, Attributes modified) throws Exception {
for (MergeAttribute mergeAttribute : mergeAttributes) {
mergeAttribute.merge(attrs, modified);
}
Expand All @@ -409,7 +409,7 @@ public AttributesCoercion supplementIssuerOfPatientID(final AttributesCoercion n

return new AttributesCoercion() {
@Override
public void coerce(Attributes attrs, Attributes modified) {
public void coerce(Attributes attrs, Attributes modified) throws Exception {
String issuerOfPatientID = attrs.getString(Tag.IssuerOfPatientID);
String supplementIssuerOfPatientID = new AttributesFormat(issuerOfPatientIDFormat).format(attrs);

Expand Down
Loading

0 comments on commit 4a689cd

Please sign in to comment.