/* ***** 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-2015 * 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.copy.schedule.impl; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.List; import javax.ejb.Stateless; import javax.inject.Inject; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import javax.persistence.Query; import javax.persistence.TemporalType; import org.dcm4che3.conf.api.DicomConfiguration; import org.dcm4che3.data.Attributes; import org.dcm4che3.data.Code; import org.dcm4che3.data.Tag; import org.dcm4che3.net.ApplicationEntity; import org.dcm4che3.net.Device; import org.dcm4chee.archive.code.CodeService; import org.dcm4chee.archive.conf.ArchivingRule; import org.dcm4chee.archive.dto.ActiveService; import org.dcm4chee.archive.entity.ArchivingTask; import org.dcm4chee.archive.entity.Instance; import org.dcm4chee.archive.entity.Series; import org.dcm4chee.archive.hsm.LocationCopyContext; import org.dcm4chee.archive.hsm.LocationCopyService; import org.dcm4chee.archive.processing.ActiveProcessingService; import org.dcm4chee.archive.store.StoreContext; import org.dcm4chee.archive.store.remember.StoreAndRememberContext; import org.dcm4chee.archive.store.remember.StoreAndRememberService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * @author Gunter Zeilinger <gunterze@gmail.com> * */ @Stateless public class ArchivingSchedulerEJB { private static final Logger LOG = LoggerFactory.getLogger(ArchivingSchedulerEJB.class); @PersistenceContext(name = "dcm4chee-arc", unitName = "dcm4chee-arc") private EntityManager em; @Inject private LocationCopyService locationCopyService; @Inject private StoreAndRememberService storeAndRemeberService; @Inject private CodeService codeService; @Inject private ActiveProcessingService activeProcessingService; @Inject private DicomConfiguration conf; public void onStoreInstance(StoreContext storeContext, ArchivingRule archivingRule) { Attributes attrs = storeContext.getAttributes(); String seriesInstanceUID = attrs.getString(Tag.SeriesInstanceUID); Date archivingTime = new Date(System.currentTimeMillis() + archivingRule.getDelayAfterInstanceStored() * 1000L); List<ArchivingTask> alreadyScheduledTasks = em .createNamedQuery(ArchivingTask.FIND_BY_SERIES_INSTANCE_UID, ArchivingTask.class) .setParameter(1, seriesInstanceUID).getResultList(); List<String> storageGroupTargets = new ArrayList<String>( archivingRule.getStorageSystemGroupIDs().length); for (String targetSystemGroupID : archivingRule.getStorageSystemGroupIDs()) { storageGroupTargets.add(targetSystemGroupID); } storageGroupTargets = filterAlreadyScheduledStorageGroupTargets(alreadyScheduledTasks, storageGroupTargets, seriesInstanceUID, archivingTime); if (!storageGroupTargets.isEmpty()) { flagOrUnflagSeriesAsActiveProcess(seriesInstanceUID, ActiveService.LOCAL_ARCHIVING, true); for (String targetGroupID : storageGroupTargets) { createAndPersistStorageGroupArchivingTask(seriesInstanceUID, archivingTime, storeContext.getFileRef().getStorageSystemGroupID(), archivingRule.getDelayReasonCode(), targetGroupID); } } List<String> extDeviceTargets = new ArrayList<String>( archivingRule.getExternalSystemsDeviceName().length); for (String targetExtDevice : archivingRule.getExternalSystemsDeviceName()) { extDeviceTargets.add(targetExtDevice); } extDeviceTargets = filterAlreadyScheduledExtDeviceTargets(alreadyScheduledTasks, extDeviceTargets, seriesInstanceUID, archivingTime); if (!extDeviceTargets.isEmpty()) { flagOrUnflagSeriesAsActiveProcess(seriesInstanceUID, ActiveService.STORE_REMEMBER_ARCHIVING, true); for (String extDeviceTarget : extDeviceTargets) { createAndPersistExtDeviceArchivingTask(seriesInstanceUID, archivingTime, storeContext.getFileRef().getStorageSystemGroupID(), archivingRule.getDelayReasonCode(), extDeviceTarget); } } } private List<String> filterAlreadyScheduledStorageGroupTargets( List<ArchivingTask> alreadyScheduledTasks, List<String> extStorageGroupTargets, String seriesInstanceUID, Date archivingTime) { for (ArchivingTask task : alreadyScheduledTasks) { task.setArchivingTime(archivingTime); LOG.debug("Updates {}", task); if (extStorageGroupTargets.remove(task.getTargetStorageSystemGroupID())) { LOG.debug("Target StorageSystemGroup {} already scheduled!", task.getTargetStorageSystemGroupID()); } } return extStorageGroupTargets; } private List<String> filterAlreadyScheduledExtDeviceTargets( List<ArchivingTask> alreadyScheduledTasks, List<String> extDeviceTargets, String seriesInstanceUID, Date archivingTime) { for (ArchivingTask task : alreadyScheduledTasks) { task.setArchivingTime(archivingTime); LOG.debug("Updates {}", task); if (extDeviceTargets.remove(task.getTargetExternalDevice())) { LOG.debug("Target External Device {} already scheduled!", task.getTargetExternalDevice()); } } return extDeviceTargets; } private void createAndPersistStorageGroupArchivingTask(String seriesInstanceUID, Date archivingTime, String sourceStorageGroupID, Code delayReasonCode, String targetStorageGroupID) { ArchivingTask task = new ArchivingTask(); task.setSeriesInstanceUID(seriesInstanceUID); task.setArchivingTime(archivingTime); task.setSourceStorageSystemGroupID(sourceStorageGroupID); task.setTargetStorageSystemGroupID(targetStorageGroupID); if (delayReasonCode != null) task.setDelayReasonCode(codeService.findOrCreate(delayReasonCode)); em.persist(task); LOG.info("Create {}", task); } private void createAndPersistExtDeviceArchivingTask(String seriesInstanceUID, Date archivingTime, String sourceStorageGroupID, Code delayReasonCode, String targetExtDevice) { ArchivingTask task = new ArchivingTask(); task.setSeriesInstanceUID(seriesInstanceUID); task.setArchivingTime(archivingTime); task.setSourceStorageSystemGroupID(sourceStorageGroupID); task.setTargetExternalDevice(targetExtDevice); if (delayReasonCode != null) task.setDelayReasonCode(codeService.findOrCreate(delayReasonCode)); em.persist(task); LOG.info("Create {}", task); } public ArchivingTask scheduleNextArchivingTask() throws IOException { List<ArchivingTask> results = em .createNamedQuery(ArchivingTask.FIND_READY_TO_ARCHIVE_BY_TIME, ArchivingTask.class) .setParameter(1, new Date(), TemporalType.TIMESTAMP) .setMaxResults(1).getResultList(); if (results.isEmpty()) { LOG.debug("No archiving tasks found to schedule"); return null; } ArchivingTask task = results.get(0); LOG.info("Scheduling {}", task); if (task.getTargetStorageSystemGroupID() != null) { scheduleCopyToHsm(task); } else if (task.getTargetExternalDevice() != null) { scheduleStoreAndRemember(task); } else { throw new IllegalStateException("Invalid archiving task"); } LOG.info("Scheduled {}", task); flagOrUnflagSeriesAsActiveProcess(task.getSeriesInstanceUID(), task.getTargetStorageSystemGroupID() != null? ActiveService.LOCAL_ARCHIVING : ActiveService.STORE_REMEMBER_ARCHIVING, false); em.remove(task); return task; } private void scheduleCopyToHsm(ArchivingTask task) throws IOException { LocationCopyContext ctx = locationCopyService.createContext(task .getTargetStorageSystemGroupID()); ctx.setSourceStorageSystemGroupID(task.getSourceStorageSystemGroupID()); locationCopyService.scheduleCopySeries(ctx, task.getSeriesInstanceUID(), 0); } private void scheduleStoreAndRemember(ArchivingTask task) throws IOException { String extDevice = task.getTargetExternalDevice(); String remoteAE = determineRemoteAETitle(extDevice); if (remoteAE == null) { throw new IOException("Could not determine remote AE title for Store-and-Remember " + "task to external device " + extDevice); } StoreAndRememberContext storeRememberCxt = storeAndRemeberService.createContextBuilder() .seriesUID(task.getSeriesInstanceUID()) .externalDeviceName(extDevice) .remoteAE(remoteAE) .build(); storeAndRemeberService.scheduleStoreAndRemember(storeRememberCxt, 0); } private String determineRemoteAETitle(String extDeviceName) { String remoteAE = null; try { //TODO: find smarter way which AE of external device should be used for Store&Remember Device extDevice = conf.findDevice(extDeviceName); Collection<ApplicationEntity> aes = extDevice.getApplicationEntities(); remoteAE = aes.iterator().next().getAETitle(); } catch(Exception e) { } return remoteAE; } private void flagOrUnflagSeriesAsActiveProcess(String seriesInstanceUID, ActiveService service, boolean flag) { Query query = em.createNamedQuery(Series.FIND_BY_SERIES_INSTANCE_UID_EAGER); query.setParameter(1, seriesInstanceUID); Series series = (Series) query.getSingleResult(); for(Instance inst : series.getInstances()) { if(flag) activeProcessingService.addActiveProcess(series.getStudy() .getStudyInstanceUID(), seriesInstanceUID, inst.getSopInstanceUID(), service); else activeProcessingService .deleteActiveProcessBySOPInstanceUIDandService( inst.getSopInstanceUID(), service); } } }