/** * Licensed to JumpMind Inc under one or more contributor * license agreements. See the NOTICE file distributed * with this work for additional information regarding * copyright ownership. JumpMind Inc licenses this file * to you under the GNU General Public License, version 3.0 (GPLv3) * (the "License"); you may not use this file except in compliance * with the License. * * You should have received a copy of the GNU General Public License, * version 3.0 (GPLv3) along with this library; if not, see * <http://www.gnu.org/licenses/>. * * 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.jumpmind.symmetric.io.stage; import java.io.File; import java.util.Collection; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import org.apache.commons.io.FileUtils; import org.apache.commons.lang.StringUtils; import org.jumpmind.symmetric.io.stage.IStagedResource.State; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class StagingManager implements IStagingManager { protected static final Logger log = LoggerFactory.getLogger(StagingManager.class); protected File directory; protected Map<String, IStagedResource> resourceList = new ConcurrentHashMap<String, IStagedResource>(); public StagingManager(String directory) { log.info("The staging directory was initialized at the following location: " + directory); this.directory = new File(directory); this.directory.mkdirs(); refreshResourceList(); } public Collection<String> getResourceReferences() { synchronized (StagingManager.class) { return resourceList.keySet(); } } protected void refreshResourceList() { synchronized (StagingManager.class) { Collection<File> files = FileUtils.listFiles(this.directory, new String[] { State.CREATE.getExtensionName(), State.READY.getExtensionName(), State.DONE.getExtensionName() }, true); for (File file : files) { try { StagedResource resource = new StagedResource(0, directory, file, this); String path = resource.getPath(); if (!resourceList.containsKey(path)) { resourceList.put(path, resource); } } catch (IllegalStateException ex) { log.warn(ex.getMessage()); } } } } /** * Clean up resources that are older than the passed in parameter. * * @param ttlInMs * If resources are older than this number of milliseconds they * will be purged */ public long clean(long ttlInMs) { synchronized (StagingManager.class) { log.trace("Cleaning staging area"); Set<String> keys = new HashSet<String>(resourceList.keySet()); long purgedFileCount = 0; long purgedFileSize = 0; long purgedMemCount = 0; long purgedMemSize = 0; for (String key : keys) { IStagedResource resource = resourceList.get(key); /* resource could have deleted itself between the time the keys were cloned and now */ if (resource != null) { boolean resourceIsOld = (System.currentTimeMillis() - resource .getLastUpdateTime()) > ttlInMs; if ((resource.getState() == State.READY || resource.getState() == State.DONE) && (resourceIsOld || !resource.exists())) { if (!resource.isInUse()) { boolean file = resource.isFileResource(); long size = resource.getSize(); if (resource.delete()) { if (file) { purgedFileCount++; purgedFileSize += size; } else { purgedMemCount++; purgedMemSize += size; } resourceList.remove(key); } else { log.warn("Failed to delete the '{}' staging resource", resource.getPath()); } } else { log.info( "The '{}' staging resource qualified for being cleaned, but was in use. It will not be cleaned right now", resource.getPath()); } } } } if (purgedFileCount > 0) { if (purgedFileSize < 1000) { log.debug("Purged {} staged files, freeing {} bytes of disk space", purgedFileCount, (int) (purgedFileSize)); } else { log.debug("Purged {} staged files, freeing {} kbytes of disk space", purgedFileCount, (int) (purgedFileSize / 1000)); } } if (purgedMemCount > 0) { if (purgedMemSize < 1000) { log.debug("Purged {} staged memory buffers, freeing {} bytes of memory", purgedMemCount, (int) (purgedMemSize)); } else { log.debug("Purged {} staged memory buffers, freeing {} kbytes of memory", purgedMemCount, (int) (purgedMemSize / 1000)); } } return purgedFileCount + purgedMemCount; } } /** * Create a handle that can be written to */ public IStagedResource create(long memoryThresholdInBytes, Object... path) { String filePath = buildFilePath(path); StagedResource resource = new StagedResource(memoryThresholdInBytes, directory, filePath, this); this.resourceList.put(filePath, resource); return resource; } protected String buildFilePath(Object... path) { StringBuilder buffer = new StringBuilder(); for (int i = 0; i < path.length; i++) { Object part = path[i]; if (part instanceof Number) { part = StringUtils.leftPad(part.toString(), 10, "0"); } buffer.append(part); if (i < path.length - 1) { buffer.append("/"); } } return buffer.toString(); } public IStagedResource find(String path) { IStagedResource resource = resourceList.get(path); if (resource != null) { if (!resource.exists() && (resource.getState() == State.READY || resource.getState() == State.DONE)) { resource.delete(); resource = null; } } return resource; } public IStagedResource find(Object... path) { return find(buildFilePath(path)); } }