/* ***** 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.qc.impl;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import javax.ejb.Stateless;
import javax.inject.Inject;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import org.dcm4che3.data.Attributes;
import org.dcm4che3.data.Tag;
import org.dcm4che3.data.VR;
import org.dcm4che3.net.ApplicationEntity;
import org.dcm4che3.net.Device;
import org.dcm4chee.archive.conf.ArchiveAEExtension;
import org.dcm4chee.archive.conf.ArchiveDeviceExtension;
import org.dcm4chee.archive.conf.QueryParam;
import org.dcm4chee.archive.conf.QueryRetrieveView;
import org.dcm4chee.archive.entity.Patient;
import org.dcm4chee.archive.entity.history.InstanceHistory;
import org.dcm4chee.archive.entity.history.UpdateHistory;
import org.dcm4chee.archive.entity.Series;
import org.dcm4chee.archive.entity.Study;
import org.dcm4chee.archive.qc.QCOperationContext;
import org.dcm4chee.archive.qc.QCRetrieveBean;
import org.dcm4chee.archive.qc.QC_OPERATION;
import org.dcm4chee.archive.query.QueryService;
import org.dcm4chee.archive.sc.STRUCTURAL_CHANGE;
import org.dcm4chee.archive.sc.StructuralChangeContainer;
import org.dcm4chee.archive.sc.StructuralChangeContext;
import org.dcm4chee.archive.store.scu.CStoreSCUContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The Class QCRetrieveBeanImpl.
* Implementes QBRetrieveBean
*
* @author Hesham Elbadawi <bsdreko@gmail.com>
*/
@Stateless
public class QCRetrieveBeanImpl implements QCRetrieveBean{
private static Logger LOG = LoggerFactory.getLogger(QCRetrieveBeanImpl.class);
private static final String CACHED_UIDS = "qc_cached_uids";
private static final String CACHED_HISTORY_OBJECTS="qc_cached_history";
private static final String NUM_QC_REF_QUERIES="number_of_reference_queries_for_qc";
@Inject
private Device device;
@Inject
private QueryService queryService;
@PersistenceContext(name="dcm4chee-arc", unitName="dcm4chee-arc")
EntityManager em;
private String qcSource="Quality Control";
@Override
public boolean requiresReferenceUpdate(String studyInstanceUID, Patient pat) {
if(studyInstanceUID != null) {
Query query = em.createNamedQuery(InstanceHistory.STUDY_EXISTS_IN_QC_HISTORY_AS_OLD_OR_NEXT);
query.setParameter(1, studyInstanceUID);
query.setMaxResults(1);
return query.getResultList().isEmpty()? false: true;
}
if(pat == null) {
LOG.error("{}: QC info[requiresReferenceUpdate] Failure - "
+ "Attributes supplied missing PatientID",qcSource);
throw new IllegalArgumentException("Attributes supplied missing PatientID");
}
Query queryStudy = em.createQuery("SELECT s.studyInstanceUID FROM Study s WHERE s.patient = ?1");
queryStudy.setParameter(1, pat);
List<String> uids = queryStudy.getResultList();
Query query = em.createNamedQuery(InstanceHistory.STUDIES_EXISTS_IN_QC_HISTORY_AS_OLD_OR_NEXT);
query.setParameter("uids", uids);
query.setMaxResults(1);
return query.getResultList().isEmpty()? false : true;
}
@Override
public void scanForReferencedStudyUIDs(Attributes attrs, Collection<String> initialColl) {
if(attrs.contains(Tag.StudyInstanceUID)) {
initialColl.add(attrs.getString(Tag.StudyInstanceUID));
}
for(int i : attrs.tags()) {
if(attrs.getVR(i) == VR.SQ) {
for(Attributes item : attrs.getSequence(i))
scanForReferencedStudyUIDs(item, initialColl);
}
}
}
@Override
@SuppressWarnings("unchecked")
public Collection<InstanceHistory> getReferencedHistory(CStoreSCUContext ctx,
Collection<String> referencedStudyInstanceUIDs) {
//check if cache has UIDs and filter the referencedStudyInstanceUIDs list to remove the cached ones
Collection<String> cachedUIDs = (Collection<String>) ctx.getProperty(CACHED_UIDS);
Collection<InstanceHistory> cachedHistory = (Collection<InstanceHistory>) ctx.getProperty(CACHED_HISTORY_OBJECTS);
Collection<String> diff = getUIDsMissingFromCache(referencedStudyInstanceUIDs, cachedUIDs);
if(diff.size() > 0) {
Collection<InstanceHistory> resultList = new ArrayList<InstanceHistory>();
Query query = em.createNamedQuery(InstanceHistory.FIND_DISTINCT_INSTANCES_WHERE_STUDY_OLD_OR_CURRENT_IN_LIST);
query.setParameter("uids", diff);
for(Iterator<Object[]> iter = query.getResultList().iterator();iter.hasNext();) {
Object[] row = iter.next();
resultList.add((InstanceHistory) row[0]);
}
if(ctx.getProperty(NUM_QC_REF_QUERIES)==null) {
ctx.setProperty(NUM_QC_REF_QUERIES,1);
} else {
ctx.setProperty(NUM_QC_REF_QUERIES, ((int)ctx.getProperty(NUM_QC_REF_QUERIES)+1));
}
cachedHistory = complementCache(ctx, cachedUIDs, cachedHistory, resultList, diff);
}
return cachedHistory;
}
private Collection<InstanceHistory> complementCache(CStoreSCUContext ctx, Collection<String> cachedUIDs,
Collection<InstanceHistory> cachedHistory, Collection<InstanceHistory> resultList, Collection<String> diff) {
if(diff.isEmpty())
return cachedHistory;
else {
if(cachedUIDs==null)
cachedUIDs = new ArrayList<>();
if(cachedHistory==null)
cachedHistory = new ArrayList<>();
for(InstanceHistory qci : resultList) {
if(!cachedHistory.contains(qci)) {
cachedHistory.add(qci);
}
}
for(String uid : diff) {
if(!cachedUIDs.contains(uid)) {
cachedUIDs.add(uid);
}
}
}
ctx.setProperty(CACHED_UIDS, cachedUIDs);
ctx.setProperty(CACHED_HISTORY_OBJECTS, cachedHistory);
return cachedHistory;
}
private Collection<String> getUIDsMissingFromCache(
Collection<String> referencedStudyInstanceUIDs,
Collection<String> cachedUIDS) {
if(cachedUIDS==null)
return referencedStudyInstanceUIDs;
ArrayList<String> diff = new ArrayList<String>(referencedStudyInstanceUIDs.size());
String uid;
for(Iterator<String> iter = referencedStudyInstanceUIDs.iterator();iter.hasNext();) {
uid = iter.next();
diff.add(uid);
}
return diff;
}
@Override
public void recalculateQueryAttributes(StructuralChangeContainer changeContainer) {
LOG.info("Received SC change container , initiating derived fields calculation");
ArchiveDeviceExtension arcDevExt = device.getDeviceExtension(ArchiveDeviceExtension.class);
String defaultAETitle;
try {
defaultAETitle = arcDevExt.getDefaultAETitle();
} catch (Exception e) {
LOG.error("Undefined defaultAETitle, "
+ "cannot calculate derived fields on MPPS COMPLETE");
return;
}
ApplicationEntity archiveAE = device.getApplicationEntity(defaultAETitle);
ArchiveAEExtension arcAEExt = archiveAE.getAEExtension(ArchiveAEExtension.class);
QueryRetrieveView view = arcDevExt.getQueryRetrieveView(arcAEExt.getQueryRetrieveViewID());
QueryParam param = new QueryParam();
param.setQueryRetrieveView(view);
for(StructuralChangeContext changeCtx : changeContainer.getContexts()) {
Enum<?>[] qcUpdateChangeTypes = changeCtx.getSubChangeTypeHierarchy(QC_OPERATION.UPDATE);
if(qcUpdateChangeTypes != null) {
QCOperationContext qcCtx = (QCOperationContext)changeCtx;
UpdateHistory.UpdateScope updateScope = (UpdateHistory.UpdateScope)qcUpdateChangeTypes[1];
try {
if (UpdateHistory.UpdateScope.STUDY.equals(updateScope)) {
Study study = findStudyByUID(qcCtx.getUpdateAttributes().getString(Tag.StudyInstanceUID));
queryService.createStudyView(study.getPk(), param);
}
if (UpdateHistory.UpdateScope.SERIES.equals(updateScope)) {
Series series = findSeriesByUID(qcCtx.getUpdateAttributes().getString(Tag.SeriesInstanceUID));
queryService.createSeriesView(series.getPk(), param);
}
} catch (Exception e) {
LOG.error("Error processing updated object event with scope={}"
+ ", Unable to recalculate query attributes - reason {}", updateScope, e);
}
} else {
Set<String> affectedStudies = changeCtx.getAffectedStudyUIDs();
try {
for (String studyIUID : affectedStudies) {
Study study = findStudyByUID(studyIUID);
queryService.createStudyView(study.getPk(), param);
for (Series series : study.getSeries()) {
queryService.createSeriesView(series.getPk(), param);
}
}
} catch (Exception e) {
LOG.error("Study or Series lookup failed, "
+ "Can not re-calculate derived fields on QC");
return;
}
}
}
}
private Study findStudyByUID(String studyUID) {
String queryStr = "SELECT s FROM Study s JOIN FETCH s.series se WHERE s.studyInstanceUID = ?1";
Query query = em.createQuery(queryStr);
Study study = null;
try {
query.setParameter(1, studyUID);
study = (Study) query.getSingleResult();
}
catch(NoResultException e) {
LOG.error(
"Unable to find study {}, related to"
+ " an already performed procedure",studyUID);
}
return study;
}
private Series findSeriesByUID(String seriesUID) {
String queryStr = "SELECT se FROM Series se WHERE se.seriesInstanceUID = ?1";
Query query = em.createQuery(queryStr);
Series series = null;
try {
query.setParameter(1, seriesUID);
series = (Series) query.getSingleResult();
}
catch(NoResultException e) {
LOG.error(
"Unable to find serties {}, related to"
+ " an already performed procedure",seriesUID);
}
return series;
}
}