/* ***** 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.ian.scu.impl; import java.io.IOException; import java.util.List; import javax.annotation.Resource; import javax.enterprise.context.ApplicationScoped; import javax.enterprise.event.Observes; import javax.inject.Inject; import javax.jms.Connection; import javax.jms.ConnectionFactory; import javax.jms.JMSException; import javax.jms.MessageProducer; import javax.jms.ObjectMessage; import javax.jms.Queue; import javax.jms.Session; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import org.dcm4che3.conf.api.IApplicationEntityCache; import org.dcm4che3.data.Attributes; import org.dcm4che3.data.Sequence; import org.dcm4che3.data.Tag; import org.dcm4che3.data.UID; import org.dcm4che3.net.ApplicationEntity; import org.dcm4che3.net.Association; import org.dcm4che3.net.Device; import org.dcm4che3.net.DimseRSP; import org.dcm4che3.net.pdu.AAssociateRQ; import org.dcm4che3.net.pdu.PresentationContext; import org.dcm4che3.util.UIDUtils; import org.dcm4chee.archive.conf.ArchiveAEExtension; import org.dcm4chee.archive.conf.ArchiveDeviceExtension; import org.dcm4chee.archive.conf.StoreAction; import org.dcm4chee.archive.entity.Code; import org.dcm4chee.archive.entity.Instance; import org.dcm4chee.archive.entity.MPPS; import org.dcm4chee.archive.entity.QInstance; import org.dcm4chee.archive.entity.QSeries; import org.dcm4chee.archive.entity.QStudy; import org.dcm4chee.archive.entity.Series; import org.dcm4chee.archive.entity.Study; import org.dcm4chee.archive.entity.Utils; import org.dcm4chee.archive.ian.scu.IANSCU; import org.dcm4chee.archive.mpps.MPPSContext; import org.dcm4chee.archive.store.StoreContext; import org.dcm4chee.archive.store.StoreSession; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.mysema.query.Tuple; import com.mysema.query.jpa.hibernate.HibernateQuery; //import org.dcm4chee.archive.iocm.RejectionEvent; //import org.dcm4chee.archive.iocm.RejectionType; /** * @author Gunter Zeilinger <gunterze@gmail.com> */ @ApplicationScoped public class IANSCUImpl implements IANSCU { private static final Logger LOG = LoggerFactory.getLogger(IANSCUImpl.class); @PersistenceContext(name = "dcm4chee-arc", unitName = "dcm4chee-arc") private EntityManager em; @Resource(mappedName = "java:/JmsXA") private ConnectionFactory connFactory; @Resource(mappedName = "java:/queue/ianscu") private Queue ianSCUQueue; @Inject private IApplicationEntityCache aeCache; @Inject private Device device; public void onMPPSReceive(MPPSContext context, Attributes attributes) { ApplicationEntity ae; try { ae = device.getApplicationEntityNotNull(context.getReceivingAET()); ArchiveAEExtension arcAE = ae.getAEExtension(ArchiveAEExtension.class); if (arcAE != null && arcAE.getIANDestinations().length > 0 && !isIncorrectWorklistEntrySelected(attributes)) { IANBuilder builder = createIANBuilder(context.getMppsSopInstanceUID(), attributes); if (builder.numberOfOutstandingInstances() == 0) scheduleSendIAN(ae.getAETitle(), arcAE.getIANDestinations(), builder.getIAN()); } } catch (Exception e) { LOG.error("Error while scheduling IAN", e); } } private boolean isIncorrectWorklistEntrySelected(Attributes mppsAttrs) { try { org.dcm4che3.data.Code discontinuationCodeInMpps = new org.dcm4che3.data.Code(mppsAttrs.getNestedDataset(Tag.PerformedProcedureStepDiscontinuationReasonCodeSequence)); org.dcm4che3.data.Code incorrectWorklistEntrySelectedCode = getIncorrectWorklistEntrySelectedCode(); return discontinuationCodeInMpps.equals(incorrectWorklistEntrySelectedCode); } catch (Exception e) { return false; } } private org.dcm4che3.data.Code getIncorrectWorklistEntrySelectedCode() { return device .getDeviceExtensionNotNull(ArchiveDeviceExtension.class) .getIncorrectWorklistEntrySelectedCode(); } private void scheduleSendIAN(String localAET, String[] remoteAETs, Attributes ian) { String iuid = UIDUtils.createUID(); for (String remoteAET : remoteAETs) { scheduleSendIAN(localAET, remoteAET, iuid, ian, 0, 0); } } private IANBuilder createIANBuilder(String mppsSopInstanceUID, Attributes ppsattrs) { IANBuilder builder = new IANBuilder(); builder.setReferencedMPPS(mppsSopInstanceUID, ppsattrs); Sequence perfSeriesSeq = ppsattrs.getSequence(Tag.PerformedSeriesSequence); for (Attributes series : perfSeriesSeq) { String seriesIUID = series.getString(Tag.SeriesInstanceUID); if (seriesIUID == null) throw new IllegalArgumentException( "Missing Series Instance UID"); List<Tuple> list = new HibernateQuery( em.unwrap(org.hibernate.Session.class)) .from(QInstance.instance) .innerJoin(QInstance.instance.series, QSeries.series) .innerJoin(QSeries.series.study, QStudy.study) .where(QSeries.series.seriesInstanceUID.eq(seriesIUID), QInstance.instance.rejectionNoteCode.isNull()) .list(QStudy.study.studyInstanceUID, QInstance.instance.sopInstanceUID, QInstance.instance.sopClassUID, QInstance.instance.availability, QInstance.instance.retrieveAETs, QInstance.instance.externalRetrieveLocations); for (Tuple tuple : list) { builder.addReferencedInstance( tuple.get(QStudy.study.studyInstanceUID), seriesIUID, tuple.get(QInstance.instance.sopInstanceUID), tuple.get(QInstance.instance.sopClassUID), tuple.get(QInstance.instance.availability), Utils.decodeAETs( tuple.get(QInstance.instance.retrieveAETs))); } } return builder; } public void onStoreInstance(@Observes StoreContext storeContext) { if (storeContext.getStoreAction() != StoreAction.STORE) return; StoreSession storeSession = storeContext.getStoreSession(); ArchiveAEExtension arcAE = storeSession.getArchiveAEExtension(); if (arcAE == null || arcAE.getIANDestinations().length == 0) return; MPPS mpps = (MPPS) storeContext.getProperty(MPPS.class.getName()); if (mpps != null && mpps.getStatus() != MPPS.Status.IN_PROGRESS && mpps.discontinuedForReason((Code) getIncorrectWorklistEntrySelectedCode())) scheduleIANForMPPS(storeContext, mpps); } private void scheduleIANForMPPS(StoreContext storeContext, MPPS mpps) { StoreSession storeSession = storeContext.getStoreSession(); IANBuilder builder = (IANBuilder) storeSession.getProperty(IANBuilder.class.getName()); if (builder == null || !builder.getMPPSInstanceUID().equals(mpps.getSopInstanceUID())) { builder = createIANBuilder(mpps.getSopInstanceUID(), mpps.getAttributes()); storeSession.setProperty(IANBuilder.class.getName(), builder); } else { Instance inst = storeContext.getInstance(); Series series = inst.getSeries(); Study study = series.getStudy(); builder.addReferencedInstance( study.getStudyInstanceUID(), series.getSeriesInstanceUID(), inst.getSopInstanceUID(), inst.getSopClassUID(), inst.getAvailability(), inst.getAllRetrieveAETs()); } if (builder.numberOfOutstandingInstances() == 0) scheduleSendIAN(storeSession.getLocalAET(), storeSession.getArchiveAEExtension().getIANDestinations(), builder.getIAN()); } // public void onRejectInstances(@Observes RejectionEvent event) { // StoreContext storeContext = event.getStoreContext(); // StoreSession storeSession = storeContext.getStoreSession(); // ArchiveAEExtension arcAE = storeSession.getArchiveAEExtension(); // if (arcAE == null || arcAE.getIANDestinations().length == 0) // return; // // try { // Attributes attrs = storeContext.getAttributes(); // if (event.getRejectionType() == RejectionType.IncorrectModalityWorklistEntry) { // for (String mppsIUID : event.getPerformedProcedureStepIUIDs()) { // MPPS mpps = em.createNamedQuery(MPPS.FIND_BY_SOP_INSTANCE_UID, MPPS.class) // .setParameter(1, mppsIUID) // .getSingleResult(); // scheduleSendIAN(storeSession.getLocalAET(), // storeSession.getArchiveAEExtension().getIANDestinations(), // createIANBuilder(mpps).getIAN()); // } // } else { // IANBuilder builder = new IANBuilder(); // for (SOPInstanceReference sopRef : em.createNamedQuery( // Instance.SOP_INSTANCE_REFERENCE_BY_STUDY_INSTANCE_UID, // SOPInstanceReference.class) // .setParameter(1, attrs.getString(Tag.StudyInstanceUID)) // .getResultList()) { // builder.addReferencedInstance( // sopRef.studyInstanceUID, // sopRef.seriesInstanceUID, // sopRef.sopInstanceUID, // sopRef.sopClassUID, // sopRef.availability, // sopRef.getRetrieveAETs()); // } // scheduleSendIAN(storeSession.getLocalAET(), // storeSession.getArchiveAEExtension().getIANDestinations(), // builder.getIAN()); // } // } catch (Exception e) { // Instance inst = storeContext.getInstance(); // LOG.warn("{}: Failed to schedule IAN for Rejection Note[iuid={}, code={}]", // storeSession, // inst.getSopInstanceUID(), // inst.getConceptNameCode(), // e); // } // } private void scheduleSendIAN(String localAET, String remoteAET, String iuid, Attributes attrs, int retries, long delay) { try { Connection conn = connFactory.createConnection(); try { Session session = conn.createSession(false, Session.AUTO_ACKNOWLEDGE); MessageProducer producer = session.createProducer(ianSCUQueue); ObjectMessage msg = session.createObjectMessage(attrs); msg.setStringProperty("SOPInstancesUID", iuid); msg.setStringProperty("LocalAET", localAET); msg.setStringProperty("RemoteAET", remoteAET); msg.setIntProperty("Retries", retries); if (delay > 0) msg.setLongProperty("_HQ_SCHED_DELIVERY", System.currentTimeMillis() + delay); producer.send(msg); } finally { conn.close(); } } catch (JMSException e) { throw new RuntimeException(e); } } @Override public void sendIAN(String localAET, String remoteAET, String iuid, Attributes attrs, int retries) { ApplicationEntity localAE = device .getApplicationEntity(localAET); if (localAE == null) { LOG.warn("Failed to send IAN to {} - no such local AE: {}", remoteAET, localAET); return; } AAssociateRQ aarq = new AAssociateRQ(); aarq.addPresentationContext( new PresentationContext( 1, UID.InstanceAvailabilityNotificationSOPClass, UID.ExplicitVRLittleEndian, UID.ImplicitVRLittleEndian)); try { ApplicationEntity remoteAE = aeCache .findApplicationEntity(remoteAET); Association as = localAE.connect(remoteAE, aarq); DimseRSP rsp = as.ncreate( UID.InstanceAvailabilityNotificationSOPClass, iuid, attrs, null); rsp.next(); try { as.release(); } catch (IOException e) { LOG.info("{}: Failed to release Association to {}", as, remoteAET); } } catch (Exception e) { ArchiveAEExtension aeExt = localAE.getAEExtension(ArchiveAEExtension.class); if (aeExt != null && retries < aeExt.getIANMaxRetries()) { int delay = aeExt.getIANRetryInterval(); LOG.info("Failed to send IAN to {} - retry in {}s: {}", remoteAET, delay, e); scheduleSendIAN(localAET, remoteAET, iuid, attrs, retries + 1, delay * 1000L); } else { LOG.warn("Failed to send IAN to {}: {}", remoteAET, e); } } } }