/* ***** 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/gunterze/dcm4che.
*
* The Initial Developer of the Original Code is
* Agfa Healthcare.
* Portions created by the Initial Developer are Copyright (C) 2011
* 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.archive.hsm;
import static org.junit.Assert.fail;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import org.dcm4che3.data.Attributes;
import org.dcm4che3.data.Tag;
import org.dcm4che3.data.VR;
import org.dcm4chee.archive.conf.ArchivingRule;
import org.dcm4chee.archive.conf.ArchivingRules;
import org.junit.Test;
/**
* @author Franz Willer <franz.willer@gmail.com>
*
*/
public class HsmArchiveRuleIT {
private static final String[] AET_VALUES = {"SRC_AET_1","SRC_AET_2","SRC_AET_3"};
private static final String[] DEVICE_VALUES = {"DEVICE_1","DEVICE_2","DEVICE_3"};
private static final String[] INSTITUTION_VALUES = {"INST_NAME_1","INST_NAME_2","INST_NAME_3"};
private static final String[] DEPARTMENT_VALUES = {"DEP_NAME_1","DEP_NAME_2","DEP_NAME_3"};
private static final String[] MODALITY_VALUES = {"MODALITY_1","MODALITY_2", "MODALITY_3"};
private static final String GROUP_1 = "GROUP_1";
private static final String GROUP_2 = "GROUP_2";
private static final String GROUP_3 = "GROUP_3";
private static final String DUMMY = "DUMMY";
private static final String NOMATCH = "NOMATCH";
private static final int DEVICE = 0;
private static final int AET = 1;
private static final int INSTITUTION = 2;
private static final int DEPARTMENT = 3;
private static final int MODALITY = 4;
private static final String[][] VALUES = new String[][] {
DEVICE_VALUES, AET_VALUES, INSTITUTION_VALUES, DEPARTMENT_VALUES, MODALITY_VALUES
};
private static final Method[] METHODS = new Method[]{
toMethod("setDeviceNames"),
toMethod("setAeTitles"),
toMethod("setInstitutionNames"),
toMethod("setInstitutionalDepartmentNames"),
toMethod("setModalities")
};
private static final Attributes DUMMY_ATTRS = getAttributes(DUMMY,DUMMY,DUMMY);
@Test
public void testMatchAET() throws Exception {
checkMatching(AET);
}
@Test
public void testMatchDevice() throws Exception {
checkMatching(DEVICE);
}
@Test
public void testMatchModality() throws Exception {
checkMatching(MODALITY);
}
@Test
public void testMatchInstitutionName() throws Exception {
checkMatching(INSTITUTION);
}
@Test
public void testMatchDepartmentName() throws Exception {
checkMatching(DEPARTMENT);
}
@Test
public void testMatchTwoRulesSameAET() throws Exception {
ArchivingRules rules = new ArchivingRules();
ArchivingRule rule1 = getRule(AET_VALUES[0], 60, GROUP_1);
rule1.setAeTitles(new String[]{AET_VALUES[0]});
ArchivingRule rule2 = getRule(AET_VALUES[0]+".1", 60, GROUP_2);
rule2.setAeTitles(new String[]{AET_VALUES[0]});
ArchivingRule rule3 = getRule(AET_VALUES[1], 60, GROUP_3);
rule3.setAeTitles(new String[]{AET_VALUES[1]});
rules.add(rule1);
rules.add(rule2);
rules.add(rule3);
List<ArchivingRule> matching = rules.findArchivingRule(DUMMY, AET_VALUES[0], DUMMY_ATTRS);
checkMatchingRules(AET_VALUES[0], matching, rules, rule1, rule2);
matching = rules.findArchivingRule(DUMMY, AET_VALUES[1], DUMMY_ATTRS);
checkMatchingRules(AET_VALUES[1], matching, rules, rule3);
}
@Test
public void testMatchWithoutQueryValues() throws Exception {
ArchivingRules rules = new ArchivingRules();
ArchivingRule rule1 = getRule("DUMMY_AET", 60, GROUP_1);
rule1.setAeTitles(new String[]{DUMMY});
ArchivingRule rule2 = getRule("DUMMY_DEVICE", 60, GROUP_2);
rule2.setDeviceNames(new String[]{DUMMY});
ArchivingRule rule3 = getRule("DUMMY_INSTITUTION_AND_DEPARTMENT", 60, GROUP_3);
rule3.setInstitutionNames(new String[]{DUMMY});
rule3.setInstitutionalDepartmentNames(new String[]{DUMMY});
ArchivingRule rule4 = getRule("DUMMY_MODALITY", 60, GROUP_3);
rule4.setModalities(new String[]{DUMMY});
rules.add(rule1);
rules.add(rule2);
rules.add(rule3);
rules.add(rule4);
List<ArchivingRule> matching = rules.findArchivingRule(null,null, new Attributes());
checkMatchingRules("NO_QUERY_VALUES", matching, rules, rule1, rule2, rule3, rule4);
matching = rules.findArchivingRule(DUMMY, DUMMY, getAttributes(NOMATCH, NOMATCH, NOMATCH));
checkMatchingRules("DUMMY AET and DeviceName", matching, rules, rule1, rule2);
matching = rules.findArchivingRule(DUMMY, DUMMY, getAttributes(NOMATCH, NOMATCH, DUMMY));
checkMatchingRules("DUMMY AET, DeviceName and Modality", matching, rules, rule1, rule2, rule4);
matching = rules.findArchivingRule(NOMATCH, NOMATCH, getAttributes(DUMMY, NOMATCH, NOMATCH));
checkMatchingRules("DUMMY InstitutionName", matching, rules);
matching = rules.findArchivingRule(NOMATCH, NOMATCH, getAttributes(NOMATCH, DUMMY, NOMATCH));
checkMatchingRules("DUMMY DepartmentName", matching, rules);
matching = rules.findArchivingRule(NOMATCH, NOMATCH, getAttributes(DUMMY, DUMMY, NOMATCH));
checkMatchingRules("DUMMY InstitutionName and DepartmentName", matching, rules, rule3);
}
@Test
public void testArchivingRulesCombinations() throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
ArchivingRules rules = new ArchivingRules();
int lenMethods = 1 << METHODS.length;
List<ArchivingRule>[] expected = new ArrayList[lenMethods];
for (int iMethod = 3 ; iMethod < lenMethods ; iMethod++) {
expected[iMethod] = new ArrayList<ArchivingRule>();
int nrMethods = Integer.bitCount(iMethod);
if (nrMethods < 2)
continue;
ArchivingRule rule = getRule(toCommonName(iMethod), 60, GROUP_1);
int mask = 0x01;
int highBit = 0;
for (int j = 0 ; j < METHODS.length ; j++) {
if ( (iMethod & mask) > 0) {
METHODS[j].invoke(rule, new Object[]{new String[]{VALUES[j][0]}});
highBit = j;
}
mask = mask << 1;
}
rules.add(rule);
expected[iMethod].add(rule);
//add aditional matching rules
loop: for (int j = 3 ; j < iMethod ; j++) {
if ( Integer.bitCount(j) < 2 )
continue;
mask = 0x01;
int countTrueBit = 0;
for (int k = 0 ; k < highBit ; k++) {
if ((j & mask) > 0 && (iMethod & mask) == 0) {
continue loop;
} else if ((iMethod & mask) > 0) {
countTrueBit++;
}
mask <<= 1;
}
if (countTrueBit > 1) {
expected[iMethod].add(rules.findByCommonName(toCommonName(j)));
}
}
}
String[] queryValues = new String[METHODS.length];
for (int iMethod = 0 ; iMethod < lenMethods ; iMethod++) {
int mask = 0x01;
for (int j = 0 ; j < METHODS.length ; j++) {
queryValues[j] = (iMethod & mask) > 0 ? VALUES[j][0] : DUMMY;
mask = mask << 1;
}
List<ArchivingRule> matching = rules.findArchivingRule(queryValues[DEVICE], queryValues[AET],
getAttributes(queryValues[INSTITUTION], queryValues[DEPARTMENT], queryValues[MODALITY]));
ArchivingRule[] exp = expected[iMethod] == null ? new ArchivingRule[0] : expected[iMethod].toArray(new ArchivingRule[0]);
checkMatchingRules(toCommonName(iMethod), matching, rules, exp);
}
}
private void checkMatching(int idx) throws Exception {
ArchivingRules rules = new ArchivingRules();
String[] values = VALUES[idx];
String[] queryValues = new String[] {DUMMY,DUMMY,DUMMY,DUMMY,DUMMY};
ArchivingRule[][] expected = prepareArchivingRules(rules, METHODS[idx], values);
for (int i = 0 ; i < values.length ; i++) {
queryValues[idx] = values[i];
List<ArchivingRule> matching = rules.findArchivingRule(queryValues[DEVICE], queryValues[AET],
idx < INSTITUTION ? DUMMY_ATTRS : getAttributes(queryValues[INSTITUTION], queryValues[DEPARTMENT], queryValues[MODALITY]));
checkMatchingRules(values[i], matching, rules, expected[i]);
}
}
private ArchivingRule[][] prepareArchivingRules(ArchivingRules rules, Method method, String[] values) throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
int len = 1 << values.length;
ArchivingRule[][] expected = new ArchivingRule[values.length][(len >> 1)+1];
int[] expectedIdx = new int[values.length];
for (int i = 0 ; i < len ; i++) {
ArchivingRule rule = getRule(method.getName().substring(3)+"_"+i, 60, GROUP_1);
if (i == 0) {
for (int k = 0 ; k < values.length ; k++) {
expected[k][expectedIdx[k]++]=rule;
}
} else {
int j = 0;
String[] ruleValues = new String[Integer.bitCount(i)];
int mask = 0x01;
for (int k = 0 ; k < values.length ; k++) {
if ( (i & mask) > 0) {
ruleValues[j++] = values[k];
expected[k][expectedIdx[k]++]=rule;
}
mask = mask << 1;
}
method.invoke(rule, new Object[]{ruleValues});
}
rules.add(rule);
}
return expected;
}
private String toCommonName(int iMethod) {
StringBuilder sb = new StringBuilder("COMBINATION-");
int mask = 0x01;
for (int i = 0 ; i < METHODS.length ; i++) {
if ( (iMethod & mask) > 0) {
sb.append(METHODS[i].getName().substring(3)).append("_");
}
mask = mask << 1;
}
return sb.toString();
}
private ArchivingRule getRule(String cn, int delay, String... groupIDs) {
ArchivingRule rule = new ArchivingRule();
rule.setCommonName(cn);
rule.setDelayAfterInstanceStored(delay);
rule.setStorageSystemGroupIDs(groupIDs);
return rule;
}
private static Attributes getAttributes(String instName, String departmentName, String modality) {
Attributes attrs = new Attributes();
attrs.setString(Tag.InstitutionName, VR.LO, instName);
attrs.setString(Tag.InstitutionalDepartmentName, VR.LO, departmentName);
attrs.setString(Tag.Modality, VR.CS, modality);
return attrs;
}
private void checkMatchingRules(String condition, List<ArchivingRule> matched, ArchivingRules rules, ArchivingRule... expected) {
if (expected.length == 1 && expected[0] == null)
expected = new ArchivingRule[0];
if (matched.size() > expected.length) {
String allMatching = promptRules("\nMatching rules:", matched, "\n ").toString();
for (ArchivingRule r : expected)
matched.remove(r);
fail("More rules found as expected! Used Condition:" + condition +
promptRules("\nRegistered rules:", rules.getList(), "\n ") + allMatching +
promptRules("\n\nAdditional matching rules:", matched, "\n "));
}
for (ArchivingRule rule : expected) {
if ( !matched.contains(rule)) {
fail("Expected rule not found:" + promptRule(rule) + "\nUsed Condition:" + condition +
promptRules("\nRegistered rules:", rules.getList(), "\n ") +
promptRules("\nMatching rules:", matched, "\n "));
}
}
}
private StringBuilder promptRules(String msg, List<ArchivingRule> rules, String delimiter) {
StringBuilder sb = new StringBuilder(msg);
for (ArchivingRule rule : rules) {
sb.append(delimiter);
addRule(sb, rule);
}
return sb;
}
private String promptRule(ArchivingRule rule) {
StringBuilder sb = new StringBuilder();
return addRule(sb, rule).toString();
}
private StringBuilder addRule(StringBuilder sb, ArchivingRule rule) {
if (rule == null) {
sb.append("NULL(");
} else {
sb.append(rule.getCommonName()).append(" (");
addConditionPrompt(sb, "DeviceNames", rule.getDeviceNames());
addConditionPrompt(sb, "AeTitles", rule.getAeTitles());
addConditionPrompt(sb, "Modalities", rule.getModalities());
addConditionPrompt(sb, "InstitutionNames", rule.getInstitutionNames());
addConditionPrompt(sb, "InstitutionalDepartmentNames", rule.getInstitutionalDepartmentNames());
}
return sb.append(")");
}
private void addConditionPrompt(StringBuilder sb, String conditionName, String[] condition) {
if (condition != null && condition.length > 0) {
sb.append(" #").append(conditionName).append(':').append(condition[0]);
for (int i = 1 ; i < condition.length ; i++)
sb.append(',').append(condition[i]);
}
}
private static Method toMethod(String methodName) {
try {
return ArchivingRule.class.getMethod(methodName, String[].class);
} catch (NoSuchMethodException | SecurityException x) {
throw new RuntimeException("Initialization failed!", x);
}
}
}