Java tutorial
/* * Copyright 2010 MINT Working Group * * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.nema.medical.mint.server.processor; import org.apache.commons.io.FileUtils; import org.apache.log4j.Logger; import org.nema.medical.mint.changelog.ChangeOperation; import org.nema.medical.mint.datadictionary.MetadataType; import org.nema.medical.mint.metadata.StudyIO; import org.nema.medical.mint.metadata.StudyMetadata; import org.nema.medical.mint.server.domain.*; import org.nema.medical.mint.server.util.StorageUtil; import org.nema.medical.mint.utils.StudyTraversals; import org.nema.medical.mint.utils.StudyUtils; import java.io.File; import java.io.FileNotFoundException; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * * @author Rex * */ public class StudyUpdateProcessor extends TimerTask { static final Logger LOG = Logger.getLogger(StudyUpdateProcessor.class); protected static final ConcurrentMap<String, Lock> studyIdLocks = new ConcurrentHashMap<String, Lock>(); private final File jobFolder; private final File studyFolder; private final int oldVersion; private final String remoteUser; private final String remoteHost; private final JobInfoDAO jobInfoDAO; private final StudyDAO studyDAO; private final ChangeDAO updateDAO; private final Map<String, MetadataType> availableTypes; /** * extracts files from the jobFolder, merges them in the studyFolder * updates the database * @param jobFolder the folder containing the uploaded files - must contain metadata.xml or metadata.gpb * @param studyFolder the target folder where the study to update exists * @param jobInfoDAO needed to update the database * @param studyDAO needed to update the database */ public StudyUpdateProcessor(final File jobFolder, final File studyFolder, final Map<String, MetadataType> availableTypes, final int oldVersion, final String remoteUser, final String remoteHost, final JobInfoDAO jobInfoDAO, final StudyDAO studyDAO, final ChangeDAO updateDAO) { this.jobFolder = jobFolder; this.studyFolder = studyFolder; this.availableTypes = availableTypes; this.oldVersion = oldVersion; this.remoteUser = remoteUser; this.remoteHost = remoteHost; this.jobInfoDAO = jobInfoDAO; this.studyDAO = studyDAO; this.updateDAO = updateDAO; } @Override public void run() { LOG.debug("Execution started."); String jobID = jobFolder.getName(); String studyUUID = studyFolder.getName(); JobInfo jobInfo = new JobInfo(); jobInfo.setId(jobID); jobInfo.setStudyID(studyUUID); Lock lock = new ReentrantLock(), oldLock; oldLock = studyIdLocks.putIfAbsent(studyUUID, lock); if (oldLock != null) { LOG.debug("Lock was an existing lock."); lock = oldLock; } if (lock.tryLock()) { try { LOG.debug("Got lock, and starting process"); //Not calling mkdir on this because they better already exist File changelogRoot = new File(studyFolder, "changelog"); if (!changelogRoot.exists()) { throw new FileNotFoundException("The changelog for study uuid " + studyUUID + " does not exist, may need to do a create first."); } /* * Need to load new study information */ final StudyMetadata newStudy = StudyIO.loadStudy(jobFolder); final String typeName = newStudy.getType(); final MetadataType dataDictionary = availableTypes.get(typeName); if (dataDictionary == null) { throw new RuntimeException("Invalid study type " + typeName); } if (newStudy.getVersion() >= 0) { throw new RuntimeException("Study update data specifies a version [" + newStudy.getVersion() + "]; versions are controlled by server, not client"); } try { StorageUtil.validateStudy(newStudy, dataDictionary, jobFolder); } catch (final StudyTraversals.TraversalException e) { throw new RuntimeException("Validation of the jobs study failed", e); } final File typeFolder = new File(studyFolder, typeName); final File existingBinaryFolder = new File(typeFolder, "binaryitems"); existingBinaryFolder.mkdirs(); StudyMetadata existingStudy; try { /* * Need to load current study information */ existingStudy = StudyIO.loadStudy(typeFolder); } catch (final RuntimeException e) { /* * Do nothing, just means there is no existing study * which is fine. */ existingStudy = null; } /* * If the study versions are not the same, then this * update is for a version that is not the most recent and * should not be applied. */ if (existingStudy != null && (existingStudy.getVersion() < 0 || existingStudy.getVersion() != oldVersion)) { throw new RuntimeException( "Study update data is of a different version than the current study, " + "cannot update if versions do not match. (" + existingStudy.getVersion() + " : " + oldVersion + ")"); } /* * Need to rename the new binary files so there are no collisions * with existing data files when merging. This also means updating * the new study document. */ final int maxExistingItemNumber = StorageUtil.getHighestNumberedBinaryItem(existingBinaryFolder); StorageUtil.shiftItemIds(newStudy, jobFolder, maxExistingItemNumber + 1); /* * Write metadata update message to change log folder. */ File changelogFolder = StorageUtil.getNextChangelogDir(changelogRoot); StudyUtils.writeStudy(newStudy, changelogFolder); Collection<Integer> excludedBids = new HashSet<Integer>(); if (existingStudy != null) { /* * Need to move through the new study and look for things to exclude * and exclude them from the existing study. */ StudyUtils.applyExcludes(existingStudy, newStudy, excludedBids); } /* * Clean out excludes because excludes should not be left in * the newStudy. */ StudyUtils.removeStudyExcludes(newStudy); /* * Need to merge the study documents and renormalize the result. * This means first denormalize, then merge, then normalize the * result */ StudyUtils.denormalizeStudy(newStudy); if (existingStudy != null) { StudyUtils.denormalizeStudy(existingStudy); StudyUtils.mergeStudy(existingStudy, newStudy, excludedBids); // Get next version number existingStudy.setVersion(existingStudy.getVersion() + 1); } else { /* * If no existing study, new study becomes the existing * study. This happens when an update is done on a type that * has no data yet. */ existingStudy = newStudy; // Set to base level version existingStudy.setVersion(StudyUtils.getBaseVersion()); existingStudy.setType(typeName); } //Rename all excluded binary files to have .exclude StorageUtil.renameExcludedFiles(existingBinaryFolder, excludedBids); StudyUtils.normalizeStudy(existingStudy); /* * Need to copy into the Study folder the new study document and * binary data files. */ StudyUtils.writeStudy(existingStudy, typeFolder); StorageUtil.moveBinaryItems(jobFolder, existingBinaryFolder); FileUtils.deleteDirectory(jobFolder); //Update study DAO only if this is DICOM data; don't update study DAO for other types (DICOM is primary) if (typeName.equals("DICOM")) { MINTStudy studyData = new MINTStudy(); studyData.setID(studyUUID); studyData.setStudyInstanceUID(existingStudy.getStudyInstanceUID()); studyData.setPatientID(existingStudy.getValueForAttribute(0x00100020)); studyData.setAccessionNumber(existingStudy.getValueForAttribute(0x00080050)); // studyData.setDateTime(study.getValueForAttribute(0x00080020)); studyData.setDateTime(MINTStudy.now()); studyData.setStudyVersion(existingStudy.getVersion()); studyDAO.updateStudy(studyData); } //Update change DAO for any type Change updateInfo = new Change(); updateInfo.setId(UUID.randomUUID().toString()); updateInfo.setStudyID(studyUUID); updateInfo.setType(typeName); updateInfo.setRemoteUser(remoteUser); updateInfo.setRemoteHost(remoteHost); updateInfo.setIndex(Integer.parseInt(changelogFolder.getName())); updateInfo.setOperation(ChangeOperation.UPDATE); updateDAO.saveChange(updateInfo); jobInfo.setStatus(JobStatus.SUCCESS); jobInfo.setStatusDescription("complete"); } catch (Exception e) { jobInfo.setStatus(JobStatus.FAILED); jobInfo.setStatusDescription(e.getMessage()); LOG.error("unable to process job " + jobID, e); } finally { lock.unlock(); LOG.debug("Released lock and stopping."); } } else { jobInfo.setStatus(JobStatus.FAILED); jobInfo.setStatusDescription("unable to process job " + jobID + ", another update is current being processed on the same study."); } jobInfoDAO.saveOrUpdateJobInfo(jobInfo); } }