/* ***** 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) 2013 * 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.locationmgmt.impl; import org.dcm4che3.net.Device; import org.dcm4chee.archive.ArchiveServiceReloaded; import org.dcm4chee.archive.ArchiveServiceStarted; import org.dcm4chee.archive.ArchiveServiceStopped; import org.dcm4chee.archive.conf.ArchiveDeviceExtension; import org.dcm4chee.archive.conf.DeletionRule; import org.dcm4chee.archive.dto.ActiveService; import org.dcm4chee.archive.entity.ExternalRetrieveLocation; import org.dcm4chee.archive.entity.Instance; import org.dcm4chee.archive.entity.Location; import org.dcm4chee.archive.event.StartStopReloadEvent; import org.dcm4chee.archive.locationmgmt.DeleterService; import org.dcm4chee.archive.locationmgmt.LocationDeleteResult; import org.dcm4chee.archive.locationmgmt.LocationDeleteResult.DeletionStatus; import org.dcm4chee.archive.locationmgmt.LocationMgmt; import org.dcm4chee.archive.processing.ActiveProcessingService; import org.dcm4chee.storage.conf.*; import org.dcm4chee.storage.spi.StorageSystemProvider; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.annotation.PostConstruct; import javax.enterprise.context.ApplicationScoped; import javax.enterprise.event.Observes; import javax.inject.Inject; import javax.jms.JMSException; import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; /** * @author Hesham Elbadawi <bsdreko@gmail.com> * */ @ApplicationScoped public class LocationDeleteServiceImpl implements DeleterService { private static final Logger LOG = LoggerFactory .getLogger(LocationDeleteServiceImpl.class); @Inject private Device device; @Inject private LocationMgmt locationManager; @Inject private javax.enterprise.inject.Instance<StorageSystemProvider> storageSystemProviders; @Inject private ActiveProcessingService activeProcessingService; private int lastPollInterval; private int deletionRetries; private ScheduledFuture<?> deleteTask; private Map<String, Date> lastDVDCalculationDateMap; private Map<String, Long> lastCalculatedDVDInBytesMap; private static final List<ActiveService> ACTIVE_ARCHIVE_OR_DELETER_SERVICES = Arrays.asList(ActiveService.LOCAL_ARCHIVING, ActiveService.DELETER_SERVICE, ActiveService.STORE_REMEMBER_ARCHIVING); @PostConstruct public void init() { lastDVDCalculationDateMap = new ConcurrentHashMap<String, Date>(); lastCalculatedDVDInBytesMap = new ConcurrentHashMap<String, Long>(); } @Override public void freeUpSpaceDeleteSeries(String seriesInstanceUID, String groupID) { freeSpaceOnRequest(null, seriesInstanceUID, groupID); } @Override public void freeUpSpaceDeleteStudy(String studyInstanceUID, String groupID) { freeSpaceOnRequest(studyInstanceUID, null, groupID); } @Override public void freeUpSpace(String groupID) { ArchiveDeviceExtension arcExt = device .getDeviceExtension(ArchiveDeviceExtension.class); DeletionRule rule = arcExt.getDeletionRules().findByStorageSystemGroupID(groupID); if (rule != null) { freeSpace(rule); } } @Override public void freeUpSpace() { ArchiveDeviceExtension arcExt = device.getDeviceExtension(ArchiveDeviceExtension.class); List<DeletionRule> deletionRules = arcExt.getDeletionRules().getList(); if(deletionRules.isEmpty()) { LOG.error("Location Deleter Service: No deletion rules configured, " + " Malformed Configuration, some archive services might not function"); return; } for(DeletionRule rule : deletionRules) { //check need to free up spacee try { if (canDeleteNow(rule.getStorageSystemGroupID()) || emergencyReached(rule.getStorageSystemGroupID())) freeSpace(rule); } catch (IOException e) { LOG.error("Unable to calculate emergency case, " + "error calculating emergency for group " + "{} - reason {}", rule.getStorageSystemGroupID(), e); } catch (Throwable t) { LOG.error("Exception occured while attempting to " + "freespace from group {} - reason {}", rule.getStorageSystemGroupID(), t); } } } @Override public boolean validateGroupForDeletion(String groupID) { ArchiveDeviceExtension arcExt = device .getDeviceExtension(ArchiveDeviceExtension.class); DeletionRule rule = arcExt.getDeletionRules().findByStorageSystemGroupID(groupID); return rule == null ? false : rule.validate(); } public void onArchiveServiceStarted( @Observes @ArchiveServiceStarted StartStopReloadEvent start) { int pollInterval = device.getDeviceExtension( ArchiveDeviceExtension.class).getDeletionServicePollInterval(); startPolling(pollInterval); } public void onArchiveServiceStopped( @Observes @ArchiveServiceStopped StartStopReloadEvent stop) { stopPolling(); } public void onArchiveSeriviceReloaded( @Observes @ArchiveServiceReloaded StartStopReloadEvent reload) { init(); int pollInterval = device.getDeviceExtension( ArchiveDeviceExtension.class).getDeletionServicePollInterval(); if (lastPollInterval != pollInterval) { if (deleteTask != null) { stopPolling(); startPolling(pollInterval); } else startPolling(pollInterval); } } @Override public long calculateDataVolumePerDayInBytes(String groupID) { StorageDeviceExtension stgDevExt = device .getDeviceExtension(StorageDeviceExtension.class); StorageSystemGroup group = stgDevExt.getStorageSystemGroup(groupID); if (group == null) { LOG.error("Location Deleter Service: Group {} not configured, " + " Malformed Configuration, some archive services" + " might not function", groupID); return lastCalculatedDVDInBytesMap.get(groupID) != null ? lastCalculatedDVDInBytesMap.get(groupID) : 0L; } if (!isDueCalculation(dataVolumePerDayCalculationRange(), lastDVDCalculationDateMap.get(groupID))) { return lastCalculatedDVDInBytesMap.get(groupID); } else { long dvdInBytes = locationManager.calculateDataVolumePerDayInBytes( groupID, dataVolumePerDayAverageOnNDays()); lastCalculatedDVDInBytesMap.put(groupID, dvdInBytes); lastDVDCalculationDateMap.put(groupID, new Date()); return dvdInBytes; } } private LocationDeleteResult freeSpaceOnRequest(String studyInstanceUID, String seriesInstanceUID, String groupID) { ArchiveDeviceExtension arcExt = device .getDeviceExtension(ArchiveDeviceExtension.class); DeletionRule rule = arcExt.getDeletionRules().findByStorageSystemGroupID(groupID); if (validateGroupForDeletion(groupID)) { try{ int minTimeToKeepStudy = rule.getMinTimeStudyNotAccessed(); String minTimeToKeppStudyUnit = rule .getMinTimeStudyNotAccessedUnit(); List<Instance> allInstancesDueDeleteOnGroup = (ArrayList<Instance>) locationManager .findInstancesDueDelete(minTimeToKeepStudy, minTimeToKeppStudyUnit, rule.getStorageSystemGroupID(), studyInstanceUID, seriesInstanceUID); List<Instance> actualInstancesToDelete = (ArrayList<Instance>) filterCopiesExist( (ArrayList<Instance>) allInstancesDueDeleteOnGroup, rule); markCorrespondingStudyAndDoDeletion(studyInstanceUID, rule, removePendingArchivingOrDeletion(studyInstanceUID, actualInstancesToDelete)); handleFailedToDeleteLocations(rule.getStorageSystemGroupID()); } catch (Exception e) { return new LocationDeleteResult(DeletionStatus.FAILED, "Deletion Failed, Reason " + getFailureReason(e)); } return new LocationDeleteResult(DeletionStatus.SCHEDULED, "No Failure"); } return new LocationDeleteResult(DeletionStatus.CRITERIA_NOT_MET, "Validation Criteria not met, rule on group " + rule.getStorageSystemGroupID() + "can not be applied"); } private String getFailureReason(Exception e) { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw, true); e.printStackTrace(pw); LOG.info("Error scheduling delete requests, reason - {}", e); return sw.getBuffer().toString(); } private boolean emergencyReached(String groupID) throws IOException { StorageDeviceExtension stgExt = device .getDeviceExtension(StorageDeviceExtension.class); StorageSystemGroup group = stgExt.getStorageSystemGroup(groupID); List<StorageSystem> tmpFlaggedsystems = new ArrayList<StorageSystem>(); for(String systemID : group.getStorageSystems().keySet()) { StorageSystem system = group.getStorageSystem(systemID); StorageSystemProvider provider = system .getStorageSystemProvider(storageSystemProviders); if(isUsableSystem(system)) { if (system.getMinFreeSpace() != null && system.getMinFreeSpaceInBytes() == -1L) system.setMinFreeSpaceInBytes(provider.getTotalSpace() * Integer.parseInt(system.getMinFreeSpace() .replace("%", ""))/100); long usableSpace = provider.getUsableSpace() ; long minSpaceRequired = system.getMinFreeSpaceInBytes(); if(usableSpace < minSpaceRequired) { system.setStorageSystemStatus(StorageSystemStatus.FULL); system.getStorageDeviceExtension().setDirty(true); LOG.info("System {} is about to fill up, " + "emergency deletion in order, currently flagged dirty", system); tmpFlaggedsystems.add(system); } } } if(tmpFlaggedsystems.isEmpty()) { stgExt.setDirty(false); return false; } else { return true; } } private boolean canDeleteNow(String groupID) { String deleteServiceAllowedInterval = deleteServiceAllowedInterval(); if(deleteServiceAllowedInterval == null) return false; if(deleteServiceAllowedInterval.split("-").length < 1) { LOG.error("Location Deleter Service: Allowed interval for deletion" + " is not configured properly, service will not try to" + " free up disk space on group {}", groupID); return false; } try{ int min = Integer.parseInt(deleteServiceAllowedInterval.split("-")[0]); int max = Integer.parseInt(deleteServiceAllowedInterval.split("-")[1]); int hourNow = Calendar.getInstance().get(Calendar.HOUR_OF_DAY); return hourNow > min && hourNow < max ? true : false; } catch (Exception e) { LOG.error("Location Deleter Service: Unable to decide allowed" + " deletion interval , service will not attempt to" + " free up disk space on group {} - reason {}", groupID, e); return false; } } private boolean isDueCalculation(String dataVolumePerDayCalculationRange, Date lastDVDCalculationDate) { String dvdRange; if(lastDVDCalculationDate == null) return true; Calendar lastCalculatedOn = Calendar.getInstance(); lastCalculatedOn.setTime(lastDVDCalculationDate); Calendar currentTime = Calendar.getInstance(); currentTime.setTimeInMillis(System.currentTimeMillis()); if(lastCalculatedOn.get(Calendar.DAY_OF_MONTH) == currentTime .get(Calendar.DAY_OF_MONTH)) return false; if (dataVolumePerDayCalculationRange.split("-").length < 1) { LOG.error("Location Deleter Service: Error calculating " + "data volume per day, configuration " + "Invalid data volume per day calculation range" + "Using 23-0"); dvdRange = "23-0" ; } else dvdRange = dataVolumePerDayCalculationRange; int min = Integer.parseInt(dvdRange .split("-")[0]); int max = Integer.parseInt(dvdRange .split("-")[1]); int now = currentTime.get(Calendar.HOUR_OF_DAY); if(now > min && now < max) return true; return false; } private void freeSpace(DeletionRule rule) { if (validateGroupForDeletion(rule.getStorageSystemGroupID())) { int minTimeToKeepStudy = rule.getMinTimeStudyNotAccessed(); String minTimeToKeppStudyUnit = rule .getMinTimeStudyNotAccessedUnit(); List<Instance> allInstancesDueDeleteOnGroup = locationManager.findInstancesDueDelete(minTimeToKeepStudy , minTimeToKeppStudyUnit, rule.getStorageSystemGroupID(), null, null); Map<String, List<Instance>> mapInstancesFoundOnGroupToStudy = getInstancesOnGroupPerStudyMap(allInstancesDueDeleteOnGroup, rule); for (String studyUID : mapInstancesFoundOnGroupToStudy .keySet()) { if (!rule.isDeleteAsMuchAsPossible() && !needsFreeSpace(rule.getStorageSystemGroupID(), calculateExpectedDataVolumePerDay(rule))) break; markCorrespondingStudyAndScheduleForDeletion( studyUID, rule, filterCopiesExist(mapInstancesFoundOnGroupToStudy.get(studyUID), rule)); } handleFailedToDeleteLocations(rule.getStorageSystemGroupID()); } } private void handleFailedToDeleteLocations(String groupID) { //handle failedToDeleteLocations LOG.debug("Finding locations that previously failed deletions"); List<Location> failedToDeleteLocations = (ArrayList<Location>) locationManager.findFailedToDeleteLocations(groupID); try { locationManager.scheduleDelete( failedToDeleteLocations, 1000, false); } catch (JMSException e) { LOG.error( "Location Deleter Service: Failed to delete locations " + "previously failing deletion - reason {}", e); } } private boolean needsFreeSpace(String groupID, long thresholdInBytes) { StorageDeviceExtension stgExt = device.getDeviceExtension(StorageDeviceExtension.class); StorageSystemGroup group = stgExt.getStorageSystemGroup(groupID); for(String systemID : group.getStorageSystems().keySet()) { StorageSystem system = group.getStorageSystem(systemID); StorageSystemProvider provider = system .getStorageSystemProvider(storageSystemProviders); if(provider == null) { LOG.info("Location Deleter Service : system {}'s " + "has no configured provider, deletion " + "will not apply", system); return false; } if(isUsableSystem(system)) { try { if(system.getMinFreeSpace() != null && system.getMinFreeSpaceInBytes() == -1L) system.setMinFreeSpaceInBytes(provider.getTotalSpace() * Integer.parseInt(system.getMinFreeSpace() .replace("%", ""))/100); if(provider.getUsableSpace() < system.getMinFreeSpaceInBytes() + thresholdInBytes) return true; } catch (IOException e) { LOG.error("Location Deleter Service : " + "failed to determine usable/total space on " + "volume configured for system {} - reason {}" , system, e); return false; } } } return false; } private long calculateExpectedDataVolumePerDay(DeletionRule rule) { long dvdInBytes = calculateDataVolumePerDayInBytes(rule.getStorageSystemGroupID()); ThresholdInterval currentInterval = null; String deletionThreshold = rule.getDeletionThreshold(); List<ThresholdInterval> intervals = createthresholdDurations(deletionThreshold, dvdInBytes); int now = Calendar.getInstance().get(Calendar.HOUR_OF_DAY); for(ThresholdInterval interval : intervals){ if(interval.end == 0) interval.end = 24; if(now >= interval.start && now < interval.end) currentInterval = interval; } return currentInterval.expectedInBytes; } private List<ThresholdInterval> createthresholdDurations( String deletionThreshold, long dvdInBytes) { List<ThresholdInterval> intervals = new ArrayList<ThresholdInterval>(); if (deletionThreshold == null) return intervals; String[] thresholds = deletionThreshold.split(";"); Arrays.sort(thresholds, new Comparator<String>() { @Override public int compare(String o1, String o2) { return Integer.parseInt(o1.split(":")[0]) < Integer.parseInt(o2 .split(":")[0]) ? -1 : Integer.parseInt(o1.split(":")[0]) == Integer .parseInt(o2.split(":")[0]) ? 0 : 1; } }); if (thresholds.length == 0) return intervals; int end = 0, start; for (int i = 0; i < thresholds.length; i++) { if (thresholds[i].contains(":")) if (i+1<thresholds.length && thresholds[i + 1].contains(":")) { end = Integer.parseInt(thresholds[i+1].split(":")[0]); } else { end = 0; } start = Integer.parseInt(thresholds[i].split(":")[0]); long value = Long.parseLong(thresholds[0].split(":")[1] .replaceAll("[GBgbmbMBhH]", "")); long bytes = toValueInBytes(value, thresholds[i].split(":")[1].replaceAll("\\d+", ""), dvdInBytes); ThresholdInterval newInterval = new ThresholdInterval( start, end, bytes); intervals.add(newInterval); } if(intervals.get(0).start != 0) { ThresholdInterval firstInterval = new ThresholdInterval(0, intervals.get(0).start, intervals.get(thresholds.length - 1).expectedInBytes); intervals.add(0, firstInterval); } return intervals; } private boolean isUsableSystem(StorageSystem system) { return !system.isReadOnly() && system.getAvailability() != Availability.OFFLINE && system.getAvailability() != Availability.UNAVAILABLE; } private Map<String,List<Instance>> getInstancesOnGroupPerStudyMap( List<Instance> allInstancesDueDelete , DeletionRule rule) { Map<String,List<Instance>> instancesOnGroupPerStudyMap = new HashMap<String, List<Instance>>(); if(allInstancesDueDelete == null) return instancesOnGroupPerStudyMap; for(Instance inst : allInstancesDueDelete) { String studyUID = inst.getSeries().getStudy().getStudyInstanceUID(); if(!instancesOnGroupPerStudyMap.containsKey(studyUID)) instancesOnGroupPerStudyMap.put(studyUID, new ArrayList<Instance>()); for(Location loc : inst.getLocations()) { if(loc.getStorageSystemGroupID().compareTo(rule.getStorageSystemGroupID()) == 0) instancesOnGroupPerStudyMap.get(studyUID).add(inst); } } return removePendingArchivingOrDeletion(instancesOnGroupPerStudyMap); } private Map<String, List<Instance>> removePendingArchivingOrDeletion( Map<String, List<Instance>> instancesOnGroupPerStudyMap) { Map<String, List<Instance>> adjustedInstancesOnGroupPerStudyMap = new HashMap<String, List<Instance>>(); for(String studyUID : instancesOnGroupPerStudyMap.keySet()) { if (!activeProcessingService.isStudyUnderProcessingByServices(studyUID, ACTIVE_ARCHIVE_OR_DELETER_SERVICES)) { adjustedInstancesOnGroupPerStudyMap.put(studyUID, instancesOnGroupPerStudyMap.get(studyUID)); } } return adjustedInstancesOnGroupPerStudyMap; } private List<Instance> removePendingArchivingOrDeletion(String studyIUID, List<Instance>instancesOnGroup) { if (activeProcessingService.isStudyUnderProcessingByServices(studyIUID, ACTIVE_ARCHIVE_OR_DELETER_SERVICES)) { return Collections.emptyList(); } return instancesOnGroup; } private synchronized void startPolling(int pollInterval) { if (deleteTask == null && pollInterval > 0) { deleteTask = device.scheduleWithFixedDelay(new Runnable() { @Override public void run() { freeUpSpace(); } }, pollInterval, pollInterval, TimeUnit.SECONDS); lastPollInterval = pollInterval; LOG.info( "Location Deleter Service: started deletion task with interval {}s", pollInterval); } } private synchronized void stopPolling() { if (deleteTask != null) { deleteTask.cancel(false); deleteTask = null; LOG.info("Location Deleter Service: stopped deletion task, last interval {}", lastPollInterval); } } private void markCorrespondingStudyAndScheduleForDeletion( String studyInstanceUID, DeletionRule rule, List<Instance> instancesDueDelete) { deletionRetries = maxDeleteServiceRetries(); if (!instancesDueDelete.isEmpty()) locationManager.markForDeletion(studyInstanceUID, rule.getStorageSystemGroupID()); List<Instance> tmpInstancesScheduled = new ArrayList<Instance>(); for(int i = -1; i<deletionRetries; i++) { instancesDueDelete.removeAll(tmpInstancesScheduled); tmpInstancesScheduled.clear(); for (Instance inst : instancesDueDelete) try { locationManager.scheduleDelete( getLocationsOnGroup(inst, rule.getStorageSystemGroupID()), 1000, true); tmpInstancesScheduled.add(inst); activeProcessingService.addActiveProcess(studyInstanceUID, inst.getSeries().getSeriesInstanceUID(), inst.getSopInstanceUID(), ActiveService.DELETER_SERVICE); } catch (JMSException e) { LOG.error("Location Deleter Service: error scheduling " + "deletion, attemting retry no {} - reason {}" , i, e); activeProcessingService.deleteActiveProcessBySOPInstanceUIDandService( inst.getSopInstanceUID(), ActiveService.DELETER_SERVICE); break; } } } private void markCorrespondingStudyAndDoDeletion( String studyInstanceUID, DeletionRule rule, List<Instance> instancesDueDelete) { if (!instancesDueDelete.isEmpty()) { locationManager.markForDeletion(studyInstanceUID, rule.getStorageSystemGroupID()); for (Instance inst : instancesDueDelete) { Collection<Long> pks = getLocationPksOnGroup(inst, rule.getStorageSystemGroupID()); activeProcessingService.addActiveProcess(studyInstanceUID, inst.getSeries().getSeriesInstanceUID(), inst.getSopInstanceUID(), ActiveService.DELETER_SERVICE); locationManager.doDelete(locationManager.filterForMarkedForDeletionStudiesOnGroup(pks), true); } locationManager.purgeStudiesRejectedOrDeletedOnAllGroups(); } } private Collection<Location> getLocationsOnGroup(Instance inst, String groupID) { Collection<Location> locationsOnGroup = new ArrayList<Location>(); for(Location loc : inst.getLocations()) if(loc.getStorageSystemGroupID().compareTo(groupID) == 0) locationsOnGroup.add(loc); return locationsOnGroup; } private Collection<Long> getLocationPksOnGroup(Instance inst, String groupID) { Collection<Long> locationPksOnGroup = new ArrayList<Long>(); for (Location loc : inst.getLocations()) { if (loc.getStorageSystemGroupID().compareTo(groupID) == 0) { locationPksOnGroup.add(loc.getPk()); } } return locationPksOnGroup; } private List<Instance> filterCopiesExist( List<Instance> instancesDueDeleteOnGroup, DeletionRule rule) { List<String> hasToBeOnSystems = Arrays.asList(rule.getArchivedOnExternalSystems()); List<String> hasToBeOnGroups = Arrays.asList(rule.getArchivedOnGroups()); List<Instance> filteredOnMany = new ArrayList<Instance>(); if((Integer.parseInt(rule.getNumberOfArchivedCopies()) == 0)) { return instancesDueDeleteOnGroup; } else { filteredOnMany = filterSafeNCopiesExist(instancesDueDeleteOnGroup, rule); if( hasToBeOnSystems != null && !hasToBeOnSystems.isEmpty()) { filteredOnMany = filterOnExternalSystem(hasToBeOnSystems, instancesDueDeleteOnGroup); } if(hasToBeOnGroups != null && !hasToBeOnGroups.isEmpty()) { filteredOnMany = filterOnGroups(hasToBeOnGroups,filteredOnMany, rule); } return filteredOnMany; } } private List<Instance> filterOnGroups(List<String> hasToBeOnGroups, List<Instance> filteredOnMany, DeletionRule rule) { List<String> tmpFoundOnGroups = new ArrayList<String>(); List<Instance> foundOnConfiguredGroups = new ArrayList<Instance>(); for (Instance inst : filteredOnMany) { for (Location loc : inst.getLocations()) { StorageSystemGroup locationGroup = device .getDeviceExtension(StorageDeviceExtension.class) .getStorageSystemGroup(loc.getStorageSystemGroupID()); if (!tmpFoundOnGroups.contains(loc.getStorageSystemGroupID()) && (locationGroup.getStorageSystemGroupType() == null || locationGroup.getStorageSystemGroupType() .compareTo(rule.getSafeArchivingType()) == 0 || rule.getSafeArchivingType().compareTo("*") == 0)) tmpFoundOnGroups.add(loc.getStorageSystemGroupID()); } if (tmpFoundOnGroups.containsAll(hasToBeOnGroups)) foundOnConfiguredGroups.add(inst); } return foundOnConfiguredGroups; } private List<Instance> filterOnExternalSystem( List<String> hasToBeOnSystems, List<Instance> filteredOnMany) { List<String> tmpFoundOnSystems = new ArrayList<String>(); List<Instance> foundOnConfiguredSystems = new ArrayList<Instance>(); for (Instance inst : filteredOnMany) { for (ExternalRetrieveLocation extLoc : inst.getExternalRetrieveLocations()) { if (!tmpFoundOnSystems.contains(extLoc.getRetrieveDeviceName())) tmpFoundOnSystems.add(extLoc.getRetrieveDeviceName()); } if (tmpFoundOnSystems.containsAll(hasToBeOnSystems)) foundOnConfiguredSystems.add(inst); } return foundOnConfiguredSystems; } private List<Instance> filterSafeNCopiesExist( List<Instance> instancesDueDeleteOnGroup, DeletionRule rule) { String groupID = rule.getStorageSystemGroupID(); List<Instance> foundOnNSafeLocations = new ArrayList<Instance>(); int found = 0; for (Instance inst : instancesDueDeleteOnGroup) { if (inst.getExternalRetrieveLocations() != null && !inst.getExternalRetrieveLocations().isEmpty()) { for(int i=0; i<inst.getExternalRetrieveLocations().size(); i++) found++; } for(Location loc : inst.getLocations()) { StorageSystemGroup locationGroup = device .getDeviceExtension(StorageDeviceExtension.class) .getStorageSystemGroup(loc.getStorageSystemGroupID()); if(!loc.isWithoutBulkData() && locationGroup.getGroupID().compareTo(groupID) != 0 && (locationGroup.getStorageSystemGroupType() == null || locationGroup.getStorageSystemGroupType() .compareTo(rule.getSafeArchivingType()) == 0 || rule.getSafeArchivingType().compareTo("*") == 0)) { found++; } } if(found >= Integer.parseInt(rule.getNumberOfArchivedCopies())) foundOnNSafeLocations.add(inst); } return foundOnNSafeLocations; } private long toValueInBytes(long value, String unit, long dvdInBytes) { if ("GB".equalsIgnoreCase(unit)) return value * 1073741824; if("GIB".equalsIgnoreCase(unit)) return value * 134217728; else if ("MB".equalsIgnoreCase(unit)) return value * 1048576; else if ("MIB".equalsIgnoreCase(unit)) return value * 131072; else if ("KB".equalsIgnoreCase(unit)) return value * 1024; else if ("KIB".equalsIgnoreCase(unit)) return value * 128; else if ("H".equalsIgnoreCase(unit)) return (dvdInBytes * value)/24; else if ("D".equalsIgnoreCase(unit)) return dvdInBytes * value; else return value; } private String deleteServiceAllowedInterval() { ArchiveDeviceExtension arcExt = device .getDeviceExtension(ArchiveDeviceExtension.class); return arcExt.getDeleteServiceAllowedInterval(); } private int maxDeleteServiceRetries() { ArchiveDeviceExtension arcExt = device .getDeviceExtension(ArchiveDeviceExtension.class); return arcExt.getMaxDeleteServiceRetries(); } private String dataVolumePerDayCalculationRange() { ArchiveDeviceExtension arcExt = device .getDeviceExtension(ArchiveDeviceExtension.class); return arcExt.getDataVolumePerDayCalculationRange(); } private int dataVolumePerDayAverageOnNDays() { ArchiveDeviceExtension arcExt = device .getDeviceExtension(ArchiveDeviceExtension.class); return arcExt.getDataVolumePerDayAverageOnNDays(); } protected class ThresholdInterval { int start, end; long expectedInBytes; public ThresholdInterval(int start, int end, long bytes) { this.start = start; this.end = end; this.expectedInBytes = bytes; } } }