/* ***** 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.query.util; import java.util.List; import org.dcm4che3.data.Attributes; import org.dcm4che3.data.IDWithIssuer; import org.dcm4che3.data.Issuer; import org.dcm4che3.data.Sequence; import org.dcm4che3.data.Tag; import org.dcm4che3.net.service.QueryRetrieveLevel; import org.dcm4che3.util.StringUtils; import org.dcm4che3.util.TagUtils; import org.dcm4chee.archive.conf.AttributeFilter; import org.dcm4chee.archive.conf.Entity; import org.dcm4chee.archive.conf.QueryParam; import org.dcm4chee.archive.conf.QueryRetrieveView; import org.dcm4chee.archive.entity.Code; import org.dcm4chee.archive.entity.QAttributesBlob; import org.dcm4chee.archive.entity.QCode; import org.dcm4chee.archive.entity.QContentItem; import org.dcm4chee.archive.entity.QInstance; import org.dcm4chee.archive.entity.QIssuer; import org.dcm4chee.archive.entity.QPatient; import org.dcm4chee.archive.entity.QPatientID; import org.dcm4chee.archive.entity.QPersonName; import org.dcm4chee.archive.entity.QRequestAttributes; import org.dcm4chee.archive.entity.QSeries; import org.dcm4chee.archive.entity.QSeriesQueryAttributes; import org.dcm4chee.archive.entity.QStudy; import org.dcm4chee.archive.entity.QStudyQueryAttributes; import org.dcm4chee.archive.entity.QVerifyingObserver; import com.mysema.query.BooleanBuilder; import com.mysema.query.jpa.hibernate.HibernateQuery; import com.mysema.query.jpa.hibernate.HibernateSubQuery; import com.mysema.query.types.CollectionExpression; import com.mysema.query.types.EntityPath; import com.mysema.query.types.ExpressionUtils; import com.mysema.query.types.Path; import com.mysema.query.types.Predicate; import com.mysema.query.types.expr.BooleanExpression; import com.mysema.query.types.expr.SimpleExpression; import com.mysema.query.types.expr.StringExpression; import com.mysema.query.types.path.BeanPath; import com.mysema.query.types.path.CollectionPath; import com.mysema.query.types.path.DateTimePath; import com.mysema.query.types.path.StringPath; /** * @author Gunter Zeilinger <gunterze@gmail.com> * @author Michael Backhaus <michael.backhaus@gmail.com> * @author Hesham Elbadawi <bsdreko@gmail.com> */ public class QueryBuilder { public static final QPersonName patientName = new QPersonName("patientName"); public static final QPersonName referringPhysicianName = new QPersonName("referringPhysicianName"); public static final QPersonName performingPhysicianName = new QPersonName("performingPhysicianName"); public static final QPersonName verifyingObserverName = new QPersonName("verifyingObserverName"); public static final QPersonName requestingPhysician = new QPersonName("requestingPhysician"); public static final QAttributesBlob patientAttributesBlob = new QAttributesBlob("patientAttributesBlob"); public static final QAttributesBlob studyAttributesBlob = new QAttributesBlob("studyAttributesBlob"); public static final QAttributesBlob seriesAttributesBlob = new QAttributesBlob("seriesAttributesBlob"); public static final QAttributesBlob instanceAttributesBlob = new QAttributesBlob("instanceAttributesBlob"); private QueryBuilder() { } public static Path<?>[] stringOrDateTimePathOf(int tag, QueryRetrieveLevel qrLevel) { switch (qrLevel) { case FRAME: case IMAGE: switch (tag) { case Tag.SOPInstanceUID: return arrayOf(QInstance.instance.sopInstanceUID); case Tag.SOPClassUID: return arrayOf(QInstance.instance.sopClassUID); case Tag.InstanceNumber: return arrayOf(QInstance.instance.instanceNumber); case Tag.VerificationFlag: return arrayOf(QInstance.instance.verificationFlag); case Tag.CompletionFlag: return arrayOf(QInstance.instance.completionFlag); case Tag.ContentDate: case Tag.ContentTime: return arrayOf(QInstance.instance.contentDateTime); } case SERIES: switch (tag) { case Tag.SeriesInstanceUID: return arrayOf(QSeries.series.seriesInstanceUID); case Tag.SeriesNumber: return arrayOf(QSeries.series.seriesNumber); case Tag.Modality: return arrayOf(QSeries.series.modality); case Tag.BodyPartExamined: return arrayOf(QSeries.series.bodyPartExamined); case Tag.Laterality: return arrayOf(QSeries.series.laterality); case Tag.PerformedProcedureStepStartDate: case Tag.PerformedProcedureStepStartTime: return arrayOf(QSeries.series.performedProcedureStepStartDateTime); case Tag.PerformingPhysicianName: return arrayOf( QSeries.series.performingPhysicianName.familyName, QSeries.series.performingPhysicianName.givenName, QSeries.series.performingPhysicianName.middleName); case Tag.SeriesDescription: return arrayOf(QSeries.series.seriesDescription); case Tag.StationName: return arrayOf(QSeries.series.stationName); case Tag.InstitutionName: return arrayOf(QSeries.series.institutionName); case Tag.InstitutionalDepartmentName: return arrayOf(QSeries.series.institutionalDepartmentName); } case STUDY: switch (tag) { case Tag.StudyInstanceUID: return arrayOf(QStudy.study.studyInstanceUID); case Tag.StudyID: return arrayOf(QStudy.study.studyID); case Tag.StudyDate: case Tag.StudyTime: return arrayOf(QStudy.study.studyDateTime); case Tag.ReferringPhysicianName: return arrayOf( QStudy.study.referringPhysicianName.familyName, QStudy.study.referringPhysicianName.givenName, QStudy.study.referringPhysicianName.middleName); case Tag.StudyDescription: return arrayOf(QStudy.study.studyDescription); case Tag.AccessionNumber: return arrayOf(QStudy.study.accessionNumber); } case PATIENT: switch (tag) { case Tag.PatientName: return arrayOf( QPatient.patient.patientName.familyName, QPatient.patient.patientName.givenName, QPatient.patient.patientName.middleName); case Tag.PatientSex: return arrayOf(QPatient.patient.patientSex); case Tag.PatientBirthDate: return arrayOf(QPatient.patient.patientBirthDate); } } throw new IllegalArgumentException("tag: " + TagUtils.toString(tag)); } private static StringPath[] arrayOf(StringPath... paths) { return paths; } private static DateTimePath<?>[] arrayOf(DateTimePath<?>... paths) { return paths; } public static void addPatientLevelPredicates(BooleanBuilder builder, IDWithIssuer[] pids, Attributes keys, QueryParam queryParam) { boolean matchUnknown = queryParam.isMatchUnknown(); boolean matchLinkedPatientIDs = queryParam.isMatchLinkedPatientIDs(); builder.and(pids(pids, matchLinkedPatientIDs, matchUnknown)); if (keys == null) return; String nullValue = queryParam.getNullValueForQueryFields(); builder.and(MatchPersonName.match(QueryBuilder.patientName, keys.getString(Tag.PatientName, nullValue), queryParam, nullValue)); builder.and(wildCard(QPatient.patient.patientSex, upper(keys.getString(Tag.PatientSex, nullValue)), matchUnknown, false, nullValue)); builder.and(MatchDateTimeRange.rangeMatch( QPatient.patient.patientBirthDate, keys, Tag.PatientBirthDate, MatchDateTimeRange.FormatDate.DA, matchUnknown)); AttributeFilter attrFilter = queryParam .getAttributeFilter(Entity.Patient); builder.and(wildCard( QPatient.patient.patientCustomAttribute1, AttributeFilter.selectStringValue(keys, attrFilter.getCustomAttribute1(), nullValue), matchUnknown,true,nullValue)); builder.and(wildCard( QPatient.patient.patientCustomAttribute2, AttributeFilter.selectStringValue(keys, attrFilter.getCustomAttribute2(), nullValue), matchUnknown,true, nullValue)); builder.and(wildCard( QPatient.patient.patientCustomAttribute3, AttributeFilter.selectStringValue(keys, attrFilter.getCustomAttribute3(), nullValue), matchUnknown,true, nullValue)); } private static String upper (String value){ if (value == null) return null; return value.toUpperCase(); } private static boolean same (String value, String nullValue) { if (nullValue == null) return (value == null); return nullValue.equals(value); } public static void addStudyLevelPredicates(BooleanBuilder builder, Attributes keys, QueryParam queryParam) { if (keys != null) { boolean matchUnknown = queryParam.isMatchUnknown(); boolean combinedDatetimeMatching = queryParam.isCombinedDatetimeMatching(); String nullValue = queryParam.getNullValueForQueryFields(); builder.and(uids(QStudy.study.studyInstanceUID, keys.getStrings(Tag.StudyInstanceUID), false, nullValue)); builder.and(wildCard(QStudy.study.studyID, keys.getString(Tag.StudyID, nullValue), matchUnknown, false, nullValue)); builder.and(MatchDateTimeRange.rangeMatch(QStudy.study.studyDateTime, Tag.StudyDate, Tag.StudyTime, Tag.StudyDateAndTime, keys, combinedDatetimeMatching, matchUnknown)); builder.and(MatchPersonName.match( QueryBuilder.referringPhysicianName, keys.getString(Tag.ReferringPhysicianName, nullValue), queryParam, nullValue)); builder.and(wildCard(QStudy.study.studyDescription, keys.getString(Tag.StudyDescription, nullValue), matchUnknown, true, nullValue)); String accNo = keys.getString(Tag.AccessionNumber, nullValue); if (!same(accNo, nullValue)) { Issuer issuer = Issuer.valueOf(keys .getNestedDataset(Tag.IssuerOfAccessionNumberSequence)); if (issuer == null) issuer = queryParam.getDefaultIssuerOfAccessionNumber(); builder.and(matchUnknown(idWithIssuer(QStudy.study.accessionNumber, accNo, issuer), QStudy.study.accessionNumber, matchUnknown, nullValue)); } builder.and(modalitiesInStudy( upper(keys.getString(Tag.ModalitiesInStudy, nullValue)), matchUnknown, nullValue)); builder.and(code(QStudy.study.procedureCodes, keys.getNestedDataset(Tag.ProcedureCodeSequence), matchUnknown, nullValue)); AttributeFilter attrFilter = queryParam .getAttributeFilter(Entity.Study); builder.and(wildCard( QStudy.study.studyCustomAttribute1, AttributeFilter.selectStringValue(keys, attrFilter.getCustomAttribute1(), nullValue),matchUnknown, true, nullValue)); builder.and(wildCard( QStudy.study.studyCustomAttribute2, AttributeFilter.selectStringValue(keys, attrFilter.getCustomAttribute2(), nullValue), matchUnknown, true, nullValue)); builder.and(wildCard( QStudy.study.studyCustomAttribute3, AttributeFilter.selectStringValue(keys, attrFilter.getCustomAttribute3(), nullValue), matchUnknown, true, nullValue)); } builder.and(permission(queryParam.getAccessControlIDs())); } private static Predicate permission(String[] accessControlIDs) { return accessControlIDs == null || accessControlIDs.length == 0 ? QStudy.study.accessControlID .isNull() : ExpressionUtils.or( QStudy.study.accessControlID.isNull(), QStudy.study.accessControlID.in(accessControlIDs)); } public static void addSeriesLevelPredicates(BooleanBuilder builder, Attributes keys, QueryParam queryParam) { if (keys != null) { boolean matchUnknown = queryParam.isMatchUnknown(); String nullValue = queryParam.getNullValueForQueryFields(); builder.and(uids(QSeries.series.seriesInstanceUID, keys.getStrings(Tag.SeriesInstanceUID), false, nullValue)); builder.and(wildCard(QSeries.series.seriesNumber, keys.getString(Tag.SeriesNumber, nullValue), matchUnknown, false, nullValue)); builder.and(wildCard(QSeries.series.modality, upper(keys.getString(Tag.Modality, nullValue)), matchUnknown, false, nullValue)); builder.and(wildCard(QSeries.series.bodyPartExamined, upper(keys.getString(Tag.BodyPartExamined, nullValue)), matchUnknown, false, nullValue)); builder.and(wildCard(QSeries.series.laterality, upper(keys.getString(Tag.Laterality, nullValue)), matchUnknown, false, nullValue)); builder.and(MatchDateTimeRange.rangeMatch( QSeries.series.performedProcedureStepStartDateTime, Tag.PerformedProcedureStepStartDate, Tag.PerformedProcedureStepStartTime, Tag.PerformedProcedureStepStartDateAndTime, keys, queryParam.isCombinedDatetimeMatching(), matchUnknown)); builder.and(MatchPersonName.match( QueryBuilder.performingPhysicianName, keys.getString(Tag.PerformingPhysicianName, nullValue), queryParam, nullValue)); builder.and(wildCard(QSeries.series.seriesDescription, keys.getString(Tag.SeriesDescription, nullValue), matchUnknown, true, nullValue)); builder.and(wildCard(QSeries.series.stationName, keys.getString(Tag.StationName, nullValue), matchUnknown, true, nullValue)); builder.and(wildCard(QSeries.series.institutionName, keys.getString(Tag.InstitutionalDepartmentName, nullValue), matchUnknown, true, nullValue)); builder.and(wildCard(QSeries.series.institutionalDepartmentName, keys.getString(Tag.InstitutionName, nullValue), matchUnknown, true, nullValue)); builder.and(requestAttributes( keys.getNestedDataset(Tag.RequestAttributesSequence), queryParam)); builder.and(code(QSeries.series.institutionCode, keys.getNestedDataset(Tag.InstitutionCodeSequence),matchUnknown,nullValue)); AttributeFilter attrFilter = queryParam .getAttributeFilter(Entity.Series); builder.and(wildCard( QSeries.series.seriesCustomAttribute1, AttributeFilter.selectStringValue(keys, attrFilter.getCustomAttribute1(), nullValue), matchUnknown, true, nullValue)); builder.and(wildCard( QSeries.series.seriesCustomAttribute2, AttributeFilter.selectStringValue(keys, attrFilter.getCustomAttribute2(), nullValue), matchUnknown, true, nullValue)); builder.and(wildCard( QSeries.series.seriesCustomAttribute3, AttributeFilter.selectStringValue(keys, attrFilter.getCustomAttribute3(), nullValue), matchUnknown, true, nullValue)); } } public static void addInstanceLevelPredicates(BooleanBuilder builder, Attributes keys, QueryParam queryParam) { if (keys == null) return; boolean matchUnknown = queryParam.isMatchUnknown(); boolean combinedDatetimeMatching = queryParam.isCombinedDatetimeMatching(); String nullValue = queryParam.getNullValueForQueryFields(); builder.and(uids(QInstance.instance.sopInstanceUID, keys.getStrings(Tag.SOPInstanceUID), false, nullValue)); builder.and(uids(QInstance.instance.sopClassUID, keys.getStrings(Tag.SOPClassUID), false, nullValue)); builder.and(wildCard(QInstance.instance.instanceNumber, keys.getString(Tag.InstanceNumber, nullValue), matchUnknown, false, nullValue)); builder.and(wildCard(QInstance.instance.verificationFlag, upper(keys.getString(Tag.VerificationFlag, nullValue)), matchUnknown, false, nullValue)); builder.and(wildCard(QInstance.instance.completionFlag, upper(keys.getString(Tag.CompletionFlag, nullValue)), matchUnknown, false, nullValue)); builder.and(MatchDateTimeRange.rangeMatch( QInstance.instance.contentDateTime, Tag.ContentDate, Tag.ContentTime, Tag.ContentDateAndTime, keys, combinedDatetimeMatching, matchUnknown)); builder.and(code(QInstance.instance.conceptNameCode,keys.getNestedDataset(Tag.ConceptNameCodeSequence), matchUnknown, nullValue)); builder.and(verifyingObserver(keys.getNestedDataset(Tag.VerifyingObserverSequence),queryParam)); Sequence contentSeq = keys.getSequence(Tag.ContentSequence); if (contentSeq != null) for (Attributes item : contentSeq) builder.and(contentItem(item, nullValue)); AttributeFilter attrFilter = queryParam .getAttributeFilter(Entity.Instance); builder.and(wildCard( QInstance.instance.instanceCustomAttribute1, AttributeFilter.selectStringValue(keys, attrFilter.getCustomAttribute1(), nullValue), matchUnknown, true, nullValue)); builder.and(wildCard( QInstance.instance.instanceCustomAttribute2, AttributeFilter.selectStringValue(keys, attrFilter.getCustomAttribute2(), nullValue), matchUnknown, true, nullValue)); builder.and(wildCard( QInstance.instance.instanceCustomAttribute3, AttributeFilter.selectStringValue(keys, attrFilter.getCustomAttribute3(), nullValue), matchUnknown, true, nullValue)); builder.and(hideRejectedInstance(queryParam)); builder.and(hideRejectionNote(queryParam)); builder.and(hideDummyInstances()); } public static Predicate hideDummyInstances() { return QInstance.instance.locations.isNotEmpty() .or(QInstance.instance.externalRetrieveLocations.isNotEmpty()); } public static Predicate hideRejectedInstance(QueryParam queryParam) { QueryRetrieveView queryRetrieveView = queryParam.getQueryRetrieveView(); org.dcm4che3.data.Code[] codes = queryRetrieveView.getShowInstancesRejectedByCodes(); if (codes.length == 0) { return queryRetrieveView.isHideNotRejectedInstances() ? QInstance.instance.rejectionNoteCode.isNotNull() : QInstance.instance.rejectionNoteCode.isNull(); } // BooleanExpression showRejected = // QInstance.instance.rejectionNoteCode.in(toCodes(codes)); CollectionExpression<List<Code>,Code> matchingCodes = getMatchingCodesSubQuery(codes); BooleanExpression showRejected = QInstance.instance.rejectionNoteCode.in(matchingCodes); return queryRetrieveView.isHideNotRejectedInstances() ? showRejected : QInstance.instance.rejectionNoteCode.isNull().or(showRejected); } // TODO: QUICK-FIX: This produces a possibly very slow subquery for Codes: TRY TO FIND OTHER SOLUTION THAT // DOES NOT INVOLVE QUERY private static CollectionExpression<List<Code>, Code> getMatchingCodesSubQuery(org.dcm4che3.data.Code[] codes) { HibernateSubQuery codesSubQuery = new HibernateSubQuery().from(QCode.code); if(codes.length == 0) { throw new IllegalArgumentException("Matching codes subquery not implemented for empty codes"); } else if(codes.length == 1) { return codesSubQuery.where(QCode.code.codeValue.eq(codes[0].getCodeValue()) .and(QCode.code.codingSchemeDesignator.eq(codes[0].getCodingSchemeDesignator()))).list(QCode.code); } else { BooleanBuilder booleanBuilder = new BooleanBuilder(); for (org.dcm4che3.data.Code c : codes) { BooleanExpression exp = QCode.code.codeValue.eq(c.getCodeValue()).and( QCode.code.codingSchemeDesignator.eq(c.getCodingSchemeDesignator())); booleanBuilder.or(exp); } return codesSubQuery.where(booleanBuilder).list(QCode.code); } } public static Predicate hideRejectionNote(QueryParam queryParam) { QueryRetrieveView queryRetrieveView = queryParam.getQueryRetrieveView(); org.dcm4che3.data.Code[] codes = queryRetrieveView.getHideRejectionNotesWithCodes(); if (codes.length == 0) return null; // return QInstance.instance.conceptNameCode.isNull().or( // QInstance.instance.conceptNameCode.notIn(toCodes(codes))); CollectionExpression<List<Code>,Code> matchingCodes = getMatchingCodesSubQuery(codes); return QInstance.instance.conceptNameCode.isNull().or( QInstance.instance.conceptNameCode.notIn(matchingCodes)); } // private static Code[] toCodes(org.dcm4che3.data.Code[] in) { // Code[] out = new Code[in.length]; // for (int i = 0; i < out.length; i++) { // out[i] = (Code) in[i]; // } // return out; // } public static Predicate pids(IDWithIssuer[] pids, boolean matchLinkedPatientIDs, boolean matchUnknown) { if (pids == null || pids.length == 0) return null; BooleanBuilder result = new BooleanBuilder(); boolean joinIssuer = false; for (IDWithIssuer pid : pids) { joinIssuer = joinIssuer || pid.getIssuer() != null; result.or(idWithIssuer(QPatientID.patientID.id, pid.getID(), pid.getIssuer())); } if (!result.hasValue()) return null; Predicate matchPatient = QPatientID.patientID.patient.eq(QPatient.patient); if (matchLinkedPatientIDs) { matchPatient = ExpressionUtils.or(matchPatient, QPatient.patient.linkedPatientIDs.contains(QPatientID.patientID)); } HibernateSubQuery subQuery = new HibernateSubQuery() .from(QPatientID.patientID); if (joinIssuer) subQuery = subQuery.leftJoin(QPatientID.patientID.issuer, QIssuer.issuer); BooleanExpression matchingIDsExists = subQuery.where( ExpressionUtils.and( matchPatient, result)).exists(); return matchUnknown ? ExpressionUtils.or(matchingIDsExists, QPatient.patient.noPatientID.isTrue()) : matchingIDsExists; } static Predicate idWithIssuer(StringPath idPath, String id, Issuer issuer) { Predicate predicate = wildCard(idPath, id); if (predicate == null) return null; if (issuer != null) { String entityID = issuer.getLocalNamespaceEntityID(); String entityUID = issuer.getUniversalEntityID(); String entityUIDType = issuer.getUniversalEntityIDType(); if (!isUniversalMatching(entityID)) predicate = ExpressionUtils.and(predicate, ExpressionUtils.or( QIssuer.issuer.localNamespaceEntityID.isNull(), QIssuer.issuer.localNamespaceEntityID.eq(entityID))); if (!isUniversalMatching(entityUID)) predicate = ExpressionUtils.and(predicate, ExpressionUtils.or( QIssuer.issuer.universalEntityID.isNull(), ExpressionUtils.and(QIssuer.issuer.universalEntityID .eq(entityUID), QIssuer.issuer.universalEntityIDType .eq(entityUIDType)))); } return predicate; } static Predicate wildCard(StringPath path, String value) { if (isUniversalMatching(value)) return null; if (!containsWildcard(value)) return path.eq(value); String pattern = toLikePattern(value); if (pattern.equals("%")) return null; return path.like(pattern, '!'); } static boolean isUniversalMatching(String value) { return value == null || value.equals("*"); } static Predicate wildCard(StringPath path, String value, boolean matchUnknown, boolean ignoreCase, String nullValue) { if (isUniversalMatching(value)) return null; Predicate predicate; StringExpression expr = ignoreCase && StringUtils.isUpperCase(value) ? path .toUpperCase() : path; if (containsWildcard(value)) { String pattern = toLikePattern(value); if (pattern.equals("%")) return null; predicate = expr.like(pattern, '!'); } else predicate = expr.eq(value); return matchUnknown(predicate, path, matchUnknown, nullValue); } static boolean containsWildcard(String s) { return s.indexOf('*') >= 0 || s.indexOf('?') >= 0; } static Predicate matchUnknown(Predicate predicate, StringPath path, boolean matchUnknown, String nullValue) { if (matchUnknown) if (nullValue == null) return ExpressionUtils.or(predicate, path.isNull()); else return ExpressionUtils.or(predicate, path.eq(nullValue)); else return predicate; } static <T> Predicate matchUnknown(Predicate predicate, BeanPath<T> path, boolean matchUnknown) { return matchUnknown ? ExpressionUtils.or(predicate, path.isNull()) : predicate; } static <E, Q extends SimpleExpression<? super E>> Predicate matchUnknown( Predicate predicate, CollectionPath<E, Q> path, boolean matchUnknown) { return matchUnknown ? ExpressionUtils.or(predicate, path.isEmpty()) : predicate; } static String toLikePattern(String s) { StringBuilder like = new StringBuilder(s.length()); char[] cs = s.toCharArray(); char p = 0; for (char c : cs) { switch (c) { case '*': if (c != p) like.append('%'); break; case '?': like.append('_'); break; case '_': case '%': case '!': like.append('!'); // fall through default: like.append(c); } p = c; } return like.toString(); } public static Predicate uids(StringPath path, String[] values, boolean matchUnknown, String nullValue) { if (values == null || values.length == 0 || same(values[0], nullValue)) return null; return matchUnknown(path.in(values), path, matchUnknown, nullValue); } static Predicate modalitiesInStudy(String modality, boolean matchUnknown, String nullValue) { if (same (modality,nullValue)) return null; return new HibernateSubQuery() .from(QSeries.series) .where(QSeries.series.study.eq(QStudy.study), wildCard(QSeries.series.modality, modality, matchUnknown, false, nullValue)).exists(); } static Predicate code(Attributes item, String nullValue) { if (item == null || item.isEmpty()) return null; return ExpressionUtils.allOf( wildCard(QCode.code.codeValue, item.getString(Tag.CodeValue, nullValue)), wildCard(QCode.code.codingSchemeDesignator, item.getString(Tag.CodingSchemeDesignator, nullValue)), wildCard(QCode.code.codingSchemeVersion, item.getString(Tag.CodingSchemeVersion, nullValue))); } static Predicate code(QCode code, Attributes item, boolean matchUnknown, String nullValue) { Predicate predicate = code(item, nullValue); if (predicate == null) return null; return matchUnknown( new HibernateSubQuery().from(QCode.code) .where(QCode.code.eq(code), predicate).exists(), code, matchUnknown); } static Predicate code(CollectionPath<Code, QCode> codes, Attributes item, boolean matchUnknown, String nullValue) { Predicate predicate = code(item, nullValue); if (predicate == null) return null; return matchUnknown( new HibernateSubQuery().from(QCode.code) .where(codes.contains(QCode.code), predicate).exists(), codes, matchUnknown); } public static void andNotInCodes(BooleanBuilder builder, QCode code, List<Code> codes) { if (codes != null && !codes.isEmpty()) builder.and(ExpressionUtils.or(code.isNull(), code.notIn(codes))); } static Predicate requestAttributes(Attributes item, QueryParam queryParam) { if (item == null || item.isEmpty()) return null; boolean matchUnknown = queryParam.isMatchUnknown(); String nullValue = queryParam.getNullValueForQueryFields(); BooleanBuilder builder = new BooleanBuilder(); String accNo = item.getString(Tag.AccessionNumber, nullValue); Issuer issuerOfAccessionNumber = null; if (!accNo.equals("*")) { issuerOfAccessionNumber = Issuer.valueOf(item .getNestedDataset(Tag.IssuerOfAccessionNumberSequence)); if (issuerOfAccessionNumber == null) issuerOfAccessionNumber = queryParam.getDefaultIssuerOfAccessionNumber(); builder.and(matchUnknown( idWithIssuer( QRequestAttributes.requestAttributes.accessionNumber, accNo, issuerOfAccessionNumber), QRequestAttributes.requestAttributes.accessionNumber, matchUnknown, nullValue)); } builder.and(wildCard( QRequestAttributes.requestAttributes.requestingService, item.getString(Tag.RequestingService, nullValue), matchUnknown, true, nullValue)); Predicate matchRequestingPhysician = MatchPersonName.match( QueryBuilder.requestingPhysician, item.getString(Tag.RequestingPhysician, nullValue), queryParam, nullValue); builder.and(matchRequestingPhysician); builder.and(wildCard( QRequestAttributes.requestAttributes.requestedProcedureID, item.getString(Tag.RequestedProcedureID, nullValue), matchUnknown,false, nullValue)); builder.and(uids(QRequestAttributes.requestAttributes.studyInstanceUID, item.getStrings(Tag.StudyInstanceUID), matchUnknown, nullValue)); builder.and(wildCard( QRequestAttributes.requestAttributes.scheduledProcedureStepID, item.getString(Tag.ScheduledProcedureStepID, nullValue), matchUnknown, false, nullValue)); if (!builder.hasValue()) return null; HibernateSubQuery subQuery = new HibernateSubQuery() .from(QRequestAttributes.requestAttributes); if (issuerOfAccessionNumber != null) subQuery.leftJoin( QRequestAttributes.requestAttributes.issuerOfAccessionNumber, QIssuer.issuer); if (matchRequestingPhysician != null) subQuery = matchUnknown ? subQuery.leftJoin(QueryBuilder.requestingPhysician, QueryBuilder.requestingPhysician) : subQuery.join(QueryBuilder.requestingPhysician, QueryBuilder.requestingPhysician); return matchUnknown( subQuery .where(QSeries.series.eq(QRequestAttributes.requestAttributes.series), builder).exists(), QSeries.series.requestAttributes, matchUnknown); } static Predicate verifyingObserver(Attributes item, QueryParam queryParam) { if (item == null || item.isEmpty()) return null; boolean matchUnknown = queryParam.isMatchUnknown(); String nullValue = queryParam.getNullValueForQueryFields(); Predicate matchVerifyingObserverName = MatchPersonName.match( QueryBuilder.verifyingObserverName, item.getString(Tag.VerifyingObserverName, nullValue), queryParam,nullValue); Predicate predicate = ExpressionUtils .allOf(MatchDateTimeRange.rangeMatch( QVerifyingObserver.verifyingObserver.verificationDateTime, item, Tag.VerificationDateTime, MatchDateTimeRange.FormatDate.DT, matchUnknown), matchVerifyingObserverName); if (predicate == null) return null; HibernateSubQuery query = new HibernateSubQuery() .from(QVerifyingObserver.verifyingObserver); if (matchVerifyingObserverName != null) query = matchUnknown ? query.leftJoin( QVerifyingObserver.verifyingObserver.verifyingObserverName, QueryBuilder.verifyingObserverName) : query.join(QVerifyingObserver.verifyingObserver.verifyingObserverName, QueryBuilder.verifyingObserverName); return matchUnknown( query.where(QInstance.instance.eq( QVerifyingObserver.verifyingObserver.instance), predicate).exists(), QInstance.instance.verifyingObservers, matchUnknown); } static Predicate contentItem(Attributes item, String nullValue) { String valueType = item.getString(Tag.ValueType); if (!("CODE".equals(valueType) || "TEXT".equals(valueType))) return null; Predicate predicate = ExpressionUtils.allOf( code(QContentItem.contentItem.conceptName, item.getNestedDataset(Tag.ConceptNameCodeSequence), false, nullValue), wildCard(QContentItem.contentItem.relationshipType, upper(item.getString(Tag.RelationshipType, nullValue))), code(QContentItem.contentItem.conceptCode, item.getNestedDataset(Tag.ConceptCodeSequence), false, nullValue), wildCard(QContentItem.contentItem.textValue, item.getString(Tag.TextValue, nullValue), false, true, nullValue)); if (predicate == null) return null; return new HibernateSubQuery() .from(QContentItem.contentItem) .where(QInstance.instance.contentItems .contains(QContentItem.contentItem), predicate).exists(); } public static HibernateQuery applyPatientLevelJoins(HibernateQuery query, Attributes keys, QueryParam queryParam) { query = query.join(QPatient.patient.attributesBlob, QueryBuilder.patientAttributesBlob); return joinIfMatchingKey(query, keys, Tag.PatientName, QPatient.patient.patientName, QueryBuilder.patientName, queryParam.isMatchUnknown()); } public static HibernateQuery applyStudyLevelJoins(HibernateQuery query, Attributes keys, QueryParam queryParam) { query = query.innerJoin(QStudy.study.patient, QPatient.patient); query = query.leftJoin(QStudy.study.queryAttributes, QStudyQueryAttributes.studyQueryAttributes) .on(QStudyQueryAttributes.studyQueryAttributes.viewID.eq( queryParam.getQueryRetrieveView().getViewID())); query = query.join(QStudy.study.attributesBlob, QueryBuilder.studyAttributesBlob); if (!isUniversalMatching(keys, Tag.AccessionNumber) && !isUniversalMatching(keys.getNestedDataset(Tag.IssuerOfAccessionNumberSequence))) query = query.leftJoin(QStudy.study.issuerOfAccessionNumber, QIssuer.issuer); return joinIfMatchingKey(query, keys, Tag.ReferringPhysicianName, QStudy.study.referringPhysicianName, QueryBuilder.referringPhysicianName, queryParam.isMatchUnknown()); } public static HibernateQuery applySeriesLevelJoins(HibernateQuery query, Attributes keys, QueryParam queryParam) { query = query.innerJoin(QSeries.series.study, QStudy.study); query = query.leftJoin(QSeries.series.queryAttributes, QSeriesQueryAttributes.seriesQueryAttributes) .on(QSeriesQueryAttributes.seriesQueryAttributes.viewID.eq( queryParam.getQueryRetrieveView().getViewID())); query = query.join(QSeries.series.attributesBlob, QueryBuilder.seriesAttributesBlob); return joinIfMatchingKey(query, keys, Tag.PerformingPhysicianName, QSeries.series.performingPhysicianName, QueryBuilder.performingPhysicianName, queryParam.isMatchUnknown()); } public static HibernateQuery applyInstanceLevelJoins(HibernateQuery query, Attributes keys, QueryParam queryParam) { query = query.innerJoin(QInstance.instance.series, QSeries.series); query = query.join(QInstance.instance.attributesBlob, QueryBuilder.instanceAttributesBlob); return query; } static boolean isUniversalMatching(Attributes keys, int tag) { return keys.getString(tag, "*").equals("*"); } static boolean isUniversalMatching(Attributes item) { return item == null || item.isEmpty(); } static <T> HibernateQuery joinIfMatchingKey(HibernateQuery query, Attributes keys, int tag, EntityPath<T> target, Path<T> alias, boolean matchUnknown) { return isUniversalMatching(keys, tag) ? query : matchUnknown ? query.leftJoin(target, alias) : query.join(target, alias); } }