/* ***** 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-2014
* 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.impl;
import java.util.Date;
import javax.ejb.EJB;
import javax.ejb.EJBTransactionRolledbackException;
import javax.ejb.Stateless;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import javax.inject.Inject;
import javax.persistence.EntityManager;
import javax.persistence.LockModeType;
import javax.persistence.PersistenceContext;
import org.dcm4che3.data.Attributes;
import org.dcm4che3.net.Device;
import org.dcm4chee.archive.conf.ArchiveDeviceExtension;
import org.dcm4chee.archive.conf.QueryParam;
import org.dcm4chee.archive.entity.QInstance;
import org.dcm4chee.archive.entity.QPatient;
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.Series;
import org.dcm4chee.archive.entity.SeriesQueryAttributes;
import org.dcm4chee.archive.entity.Study;
import org.dcm4chee.archive.entity.StudyQueryAttributes;
import org.dcm4chee.archive.entity.Utils;
import org.dcm4chee.archive.query.DerivedSeriesFields;
import org.dcm4chee.archive.query.DerivedStudyFields;
import org.dcm4chee.archive.query.QueryContext;
import org.dcm4chee.archive.query.util.QueryBuilder;
import org.dcm4chee.mysema.query.jpa.hibernate.DetachedHibernateQueryFactory;
import org.hibernate.Session;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.mysema.commons.lang.CloseableIterator;
import com.mysema.query.BooleanBuilder;
import com.mysema.query.Tuple;
import com.mysema.query.types.Expression;
import com.mysema.query.types.Predicate;
/**
* @author Gunter Zeilinger <gunterze@gmail.com>
*
*/
@Stateless
public class QueryServiceEJB {
private static Logger LOG = LoggerFactory.getLogger(QueryServiceEJB.class);
static final Expression<?>[] PATIENT_STUDY_SERIES_ATTRS = {
QStudy.study.pk,
QSeriesQueryAttributes.seriesQueryAttributes.numberOfInstances,
QSeriesQueryAttributes.seriesQueryAttributes.numberOfVisibleInstances,
QSeriesQueryAttributes.seriesQueryAttributes.lastUpdateTime,
QStudyQueryAttributes.studyQueryAttributes.numberOfInstances,
QStudyQueryAttributes.studyQueryAttributes.numberOfSeries,
QStudyQueryAttributes.studyQueryAttributes.modalitiesInStudy,
QStudyQueryAttributes.studyQueryAttributes.sopClassesInStudy,
QStudyQueryAttributes.studyQueryAttributes.numberOfVisibleInstances,
QStudyQueryAttributes.studyQueryAttributes.lastUpdateTime,
QueryBuilder.seriesAttributesBlob.encodedAttributes,
QueryBuilder.studyAttributesBlob.encodedAttributes,
QueryBuilder.patientAttributesBlob.encodedAttributes
};
@EJB
private QueryServiceEJB self;
@PersistenceContext(name = "dcm4chee-arc", unitName = "dcm4chee-arc")
private EntityManager em;
@Inject
private Device device;
@Inject
private DetachedHibernateQueryFactory queryFactory;
public Attributes getSeriesAttributes(Long seriesPk, QueryContext context) {
String viewID = context.getQueryParam().getQueryRetrieveView().getViewID();
Tuple result = queryFactory.query(em.unwrap(Session.class))
.from(QSeries.series)
.join(QSeries.series.attributesBlob, QueryBuilder.seriesAttributesBlob)
.leftJoin(QSeries.series.queryAttributes, QSeriesQueryAttributes.seriesQueryAttributes)
.on(QSeriesQueryAttributes.seriesQueryAttributes.viewID.eq(viewID))
.join(QSeries.series.study, QStudy.study)
.join(QStudy.study.attributesBlob, QueryBuilder.studyAttributesBlob)
.leftJoin(QStudy.study.queryAttributes, QStudyQueryAttributes.studyQueryAttributes)
.on(QStudyQueryAttributes.studyQueryAttributes.viewID.eq(viewID))
.join(QStudy.study.patient, QPatient.patient)
.join(QPatient.patient.attributesBlob, QueryBuilder.patientAttributesBlob)
.where(QSeries.series.pk.eq(seriesPk))
.singleResult(PATIENT_STUDY_SERIES_ATTRS);
Integer numberOfSeriesRelatedInstances =
result.get(QSeriesQueryAttributes.seriesQueryAttributes.numberOfInstances);
Integer numberOfSeriesVisibleInstances;
Date seriesLastUpdateTime;
if (numberOfSeriesRelatedInstances == null) {
SeriesQueryAttributes seriesQueryAttributes =
calculateSeriesQueryAttributes(seriesPk, context.getQueryParam());
numberOfSeriesRelatedInstances = seriesQueryAttributes.getNumberOfInstances();
numberOfSeriesVisibleInstances = seriesQueryAttributes.getNumberOfVisibleInstances();
seriesLastUpdateTime = seriesQueryAttributes.getLastUpdateTime();
} else {
numberOfSeriesVisibleInstances = result.get(QSeriesQueryAttributes
.seriesQueryAttributes.numberOfVisibleInstances);
seriesLastUpdateTime = result.get(QSeriesQueryAttributes
.seriesQueryAttributes.lastUpdateTime);
}
int numberOfStudyRelatedSeries;
String modalitiesInStudy;
String sopClassesInStudy;
int numberOfStudyVisibleInstances;
Date studyLastUpdateTime;
Integer numberOfStudyRelatedInstances =
result.get(QStudyQueryAttributes.studyQueryAttributes.numberOfInstances);
if (numberOfStudyRelatedInstances == null) {
StudyQueryAttributes studyQueryAttributes =
calculateStudyQueryAttributes(result.get(QStudy.study.pk), context.getQueryParam());
numberOfStudyRelatedInstances = studyQueryAttributes.getNumberOfInstances();
numberOfStudyRelatedSeries = studyQueryAttributes.getNumberOfSeries();
modalitiesInStudy = studyQueryAttributes.getRawModalitiesInStudy();
sopClassesInStudy = studyQueryAttributes.getRawSOPClassesInStudy();
numberOfStudyVisibleInstances = studyQueryAttributes.getNumberOfVisibleInstances();
studyLastUpdateTime = studyQueryAttributes.getLastUpdateTime();
} else {
numberOfStudyRelatedSeries =
result.get(QStudyQueryAttributes.studyQueryAttributes.numberOfSeries);
modalitiesInStudy =
result.get(QStudyQueryAttributes.studyQueryAttributes.modalitiesInStudy);
sopClassesInStudy =
result.get(QStudyQueryAttributes.studyQueryAttributes.sopClassesInStudy);
numberOfStudyVisibleInstances = result.get(QStudyQueryAttributes.studyQueryAttributes.numberOfVisibleInstances);
studyLastUpdateTime = result.get(QStudyQueryAttributes.studyQueryAttributes.lastUpdateTime);
}
byte[] seriesBytes =
result.get(QueryBuilder.seriesAttributesBlob.encodedAttributes);
byte[] studyBytes =
result.get(QueryBuilder.studyAttributesBlob.encodedAttributes);
byte[] patientBytes =
result.get(QueryBuilder.patientAttributesBlob.encodedAttributes);
Attributes patientAttrs = new Attributes();
Attributes studyAttrs = new Attributes();
Attributes seriesAttrs = new Attributes();
Utils.decodeAttributes(patientAttrs, patientBytes);
Utils.decodeAttributes(studyAttrs, studyBytes);
Utils.decodeAttributes(seriesAttrs, seriesBytes);
Attributes attrs = Utils.mergeAndNormalize(patientAttrs, studyAttrs, seriesAttrs);
ArchiveDeviceExtension ade = context.getArchiveAEExtension()
.getApplicationEntity().getDevice().getDeviceExtension
(ArchiveDeviceExtension.class);
Utils.setStudyQueryAttributes(attrs,
numberOfStudyRelatedSeries,
numberOfStudyRelatedInstances,
modalitiesInStudy,
sopClassesInStudy,
numberOfStudyVisibleInstances,
ade.getPrivateDerivedFields().findStudyNumberOfVisibleInstancesTag(),
studyLastUpdateTime,
ade.getPrivateDerivedFields().findStudyUpdateTimeTag());
Utils.setSeriesQueryAttributes(attrs,
numberOfSeriesRelatedInstances,
numberOfSeriesVisibleInstances,
ade.getPrivateDerivedFields().findSeriesNumberOfVisibleInstancesTag(),
seriesLastUpdateTime,
ade.getPrivateDerivedFields().findSeriesUpdateTimeTag());
return attrs;
}
Predicate createPredicate(Predicate initial, QueryParam queryParam) {
BooleanBuilder builder = new BooleanBuilder(initial);
builder.and(QueryBuilder.hideRejectedInstance(queryParam));
builder.and(QueryBuilder.hideRejectionNote(queryParam));
builder.and(QueryBuilder.hideDummyInstances());
return builder;
}
/**
* Creates StudyQueryAttributes
*
* @param studyPk primary key of study
* @param queryParam
* @return updated or created StudyQueryAttributes
*/
public StudyQueryAttributes calculateStudyQueryAttributes(
Long studyPk, QueryParam queryParam) {
DerivedStudyFields studyDerivedFields = new DefaultDerivedStudyFields(device);
Study study = em.find(Study.class, studyPk);
if(study == null) {
LOG.warn("Not calculating study query attributes. Study has been deleted in the meantime. {}", studyPk);
return null;
}
long calculatedForVersion = study.getVersion();
try (
CloseableIterator<Tuple> results = queryFactory.query(
em.unwrap(Session.class))
.from(QInstance.instance)
.innerJoin(QInstance.instance.series, QSeries.series)
.where(createPredicate(
QSeries.series.study.pk.eq(studyPk), queryParam))
.iterate(studyDerivedFields.fields())) {
while (results.hasNext()) {
studyDerivedFields.addInstance(results.next(), queryParam);
}
}
StudyQueryAttributes queryAttrs = new StudyQueryAttributes();
queryAttrs.setViewID(queryParam.getQueryRetrieveView().getViewID());
queryAttrs.setStudy(study);
populateStudyQueryAttributes(studyDerivedFields, queryAttrs);
try {
// should run in own transaction
self.persistStudyQueryAttributes(queryAttrs, calculatedForVersion);
} catch (EJBTransactionRolledbackException transactionRolledbackException) {
LOG.warn("Study derived fields could not be persisted, this is usually okay - probably there was a concurrent calculation/update.", transactionRolledbackException);
// ... it could also mean that we forgot to clean the outdated query attributes when updating a study!
}
return queryAttrs;
}
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public void persistStudyQueryAttributes(StudyQueryAttributes queryAttrs, long calculatedForVersion) {
// locking here ensures that we really never save the wrong version of the calculated fields
Study study = em.find(Study.class, queryAttrs.getStudy().getPk(), LockModeType.PESSIMISTIC_READ);
if(study != null) {
long version = study.getVersion();
// somebody might have changed the study in between (while we were calculating), then we must not save the results
if (calculatedForVersion == version) {
queryAttrs.setStudy(study);
em.persist(queryAttrs);
} else {
LOG.info("Not saving study query attributes, because there was a concurrent modification");
}
}
}
private void populateStudyQueryAttributes(DerivedStudyFields studyDerivedFields, StudyQueryAttributes queryAttrs) {
queryAttrs.setNumberOfInstances(studyDerivedFields.getNumberOfInstances());
if (studyDerivedFields.getNumberOfInstances() > 0) {
queryAttrs.setNumberOfSeries(studyDerivedFields.getSeriesPKs().size());
queryAttrs.setModalitiesInStudy(studyDerivedFields.getMods().toArray
(new String[studyDerivedFields.getMods().size()]));
queryAttrs.setSOPClassesInStudy(studyDerivedFields.getCuids()
.toArray(new String[studyDerivedFields.getCuids().size()]));
queryAttrs.setRetrieveAETs(studyDerivedFields.getRetrieveAETs());
queryAttrs.setAvailability(studyDerivedFields.getAvailability());
queryAttrs.setLastUpdateTime(studyDerivedFields.getLastUpdateTime());
queryAttrs.setNumberOfVisibleInstances(studyDerivedFields.getNumberOfVisibleImages());
queryAttrs.setNumberOfVisibleSeries(studyDerivedFields.getNumberOfVisibleSeries());
}
}
/**
* Creates SeriesQueryAttributes
*
* @param seriesPk primary key of series
* @param queryParam
* @return updated or created SeriesQueryAttributes
*/
public SeriesQueryAttributes calculateSeriesQueryAttributes(
Long seriesPk, QueryParam queryParam) {
DerivedSeriesFields seriesDerivedFields = new DefaultDerivedSeriesFields(device);
Series series = em.find(Series.class, seriesPk);
if(series == null) {
LOG.warn("Not calculating series query attributes. Series has been deleted in the meantime. {}", seriesPk);
return null;
}
long calculatedForVersion = series.getVersion();
try (
CloseableIterator<Tuple> results = queryFactory.query(
em.unwrap(Session.class))
.from(QInstance.instance)
.where(createPredicate(QInstance.instance.series.pk.eq(seriesPk), queryParam))
.iterate(seriesDerivedFields.fields())) {
while (results.hasNext()) {
seriesDerivedFields.addInstance(results.next(), queryParam);
}
}
SeriesQueryAttributes queryAttrs = new SeriesQueryAttributes();
queryAttrs.setSeries(series);
queryAttrs.setViewID(queryParam.getQueryRetrieveView().getViewID());
populateSeriesDerivedFields(seriesDerivedFields, queryAttrs);
try {
// should run in own transaction
self.persistSeriesQueryAttributes(queryAttrs, calculatedForVersion);
} catch (EJBTransactionRolledbackException transactionRolledbackException) {
LOG.warn("Series derived fields could not be persisted, this is usually okay - probably there was a concurrent calculation/update.", transactionRolledbackException);
// ... it could also mean that we forgot to clean the outdated query attributes when updating a series!
}
return queryAttrs;
}
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public void persistSeriesQueryAttributes(SeriesQueryAttributes queryAttrs, long calculatedForVersion) {
// locking here ensures that we really never save the wrong version of the calculated fields
Series series = em.find(Series.class, queryAttrs.getSeries().getPk(), LockModeType.PESSIMISTIC_READ);
if(series != null) {
long version = series.getVersion();
// somebody might have changed the series in between (while we were calculating), then we must not save the results
if (calculatedForVersion == version) {
queryAttrs.setSeries(series);
em.persist(queryAttrs);
} else {
LOG.info("Not saving series query attributes, because there was a concurrent modification");
}
}
}
private void populateSeriesDerivedFields(DerivedSeriesFields seriesDerivedFields, SeriesQueryAttributes queryAttrs) {
queryAttrs.setNumberOfInstances(seriesDerivedFields.getNumberOfInstances());
if (seriesDerivedFields.getNumberOfInstances() > 0) {
queryAttrs.setRetrieveAETs(seriesDerivedFields.getRetrieveAETs());
queryAttrs.setAvailability(seriesDerivedFields.getAvailability());
queryAttrs.setLastUpdateTime(seriesDerivedFields.getLastUpdateTime());
queryAttrs.setNumberOfVisibleInstances(seriesDerivedFields.getNumberOfVisibleImages());
}
}
}