/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2008, Open Source Geospatial Foundation (OSGeo) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. */ package org.geotools.data.shapefile; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.RandomAccessFile; import java.net.URL; import java.nio.channels.FileChannel; import java.util.Arrays; import java.util.SortedSet; import java.util.TreeSet; import java.util.logging.Level; import org.geotools.data.DataUtilities; /** * Encapsulates the idea of a file for writing data to and then later updating the original. * * @author jesse * * @source $URL$ */ public final class StorageFile implements Comparable<StorageFile>, FileWriter { private final ShpFiles shpFiles; private final File tempFile; private final ShpFileType type; public StorageFile( ShpFiles shpFiles, File tempFile, ShpFileType type ) { this.shpFiles = shpFiles; this.tempFile = tempFile; this.type = type; } /** * Returns the storage file * * @return the storage file */ public File getFile() { return tempFile; } public FileChannel getWriteChannel() throws IOException { return new RandomAccessFile(tempFile, "rw").getChannel(); } /** * Replaces the file that the temporary file is acting as a transactional type cache for. Acts * similar to a commit. * * @see #replaceOriginals(StorageFile...) * @throws IOException */ public void replaceOriginal() throws IOException { replaceOriginals(this); } /** * Takes a collection of StorageFiles and performs the replace functionality described in * {@link #replaceOriginal()}. However, all files that are part of the same {@link ShpFiles} * are done within a lock so all of the updates for all the Files of a Shapefile can be updated * within a single lock. * * @param storageFiles files to execute the replace functionality. * @throws IOException */ public static void replaceOriginals( StorageFile... storageFiles ) throws IOException { SortedSet<StorageFile> files = new TreeSet<StorageFile>(Arrays.asList(storageFiles)); ShpFiles currentShpFiles = null; URL shpURL = null; StorageFile locker = null; for( StorageFile storageFile : files ) { if (currentShpFiles != storageFile.shpFiles) { // there's a new set of files so unlock old and lock new. unlock(currentShpFiles, shpURL, locker); locker = storageFile; currentShpFiles = storageFile.shpFiles; shpURL = currentShpFiles.acquireWrite(ShpFileType.SHP, storageFile); } File storage = storageFile.getFile(); URL url = storageFile.getSrcURLForWrite(); try { File dest = DataUtilities.urlToFile(url); if (storage.equals(dest)) return; if (dest.exists()) { if (!dest.delete()){ ShapefileDataStoreFactory.LOGGER.severe("Unable to delete the file: "+dest+" when attempting to replace with temporary copy."); if( storageFile.shpFiles.numberOfLocks()>0 ){ ShapefileDataStoreFactory.LOGGER.severe("The problem is almost certainly caused by the fact that there are still locks being held on the shapefiles. Probably a reader or writer was left unclosed"); storageFile.shpFiles.logCurrentLockers(Level.SEVERE); } // throw new IOException("Unable to delete original file: " + url); } } if (storage.exists() && !storage.renameTo(dest)) { ShapefileDataStoreFactory.LOGGER.finer("Unable to rename temporary file to the file: "+dest+" when attempting to replace with temporary copy"); copyFile(storage, url, dest); if (!storage.delete()) { storage.deleteOnExit(); } } } finally { storageFile.unlockWriteURL(url); if (storage.exists()) { storage.delete(); } } } unlock(currentShpFiles, shpURL, locker); } private static void copyFile( File storage, URL url, File dest ) throws FileNotFoundException, IOException { FileChannel in = null; FileChannel out = null; try { in = new FileInputStream(storage).getChannel(); out = new FileOutputStream(dest).getChannel(); // magic number for Windows, 64Mb - 32Kb) int maxCount = (64 * 1024 * 1024) - (32 * 1024); long size = in.size(); long position = 0; while( position < size ) { position += in.transferTo(position, maxCount, out); } } finally { if (in != null) { in.close(); } if (out != null) { out.close(); } } } private URL getSrcURLForWrite() { return shpFiles.acquireWrite(type, this); } private void unlockWriteURL( URL url ) { shpFiles.unlockWrite(url, this); } private static void unlock( ShpFiles currentShpFiles, URL shpURL, StorageFile locker ) { // no lock to be unlocked if (currentShpFiles == null) { return; } currentShpFiles.unlockWrite(shpURL, locker); } /** * Just groups together files that have the same ShpFiles instance */ public int compareTo( StorageFile o ) { // group togheter files that have the same shpefile instance if (this == o) { return 0; } // assume two StorageFile that do not share the same ShpFiles // are not given the same temp file return getFile().compareTo(o.getFile()); } @Override public String toString() { return id(); } public String id() { return getClass().getSimpleName() + ": " + tempFile.getName(); } }