/** * 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.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.StringReader; import java.util.HashMap; import java.util.Map; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.jumpmind.exception.IoException; import org.jumpmind.symmetric.io.IoConstants; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class StagedResource implements IStagedResource { static final Logger log = LoggerFactory.getLogger(StagedResource.class); private long threshold; private File directory; private String path; private File file; private StringBuilder memoryBuffer; private long lastUpdateTime; private State state; private OutputStream outputStream = null; private Map<Thread, InputStream> inputStreams = null; private Map<Thread, BufferedReader> readers = new HashMap<Thread, BufferedReader>(); private BufferedWriter writer; private StagingManager stagingManager; public StagedResource(long threshold, File directory, File file, StagingManager stagingManager) { this.threshold = threshold; this.directory = directory; this.stagingManager = stagingManager; this.file = file; this.path = file.getAbsolutePath(); this.path = this.path.replaceAll("\\\\", "/"); this.path = this.path.substring(directory.getAbsolutePath().length(), file .getAbsolutePath().length()); this.path = this.path.substring(1, path.lastIndexOf(".")); if (file.exists()) { lastUpdateTime = file.lastModified(); String fileName = file.getName(); String extension = fileName.substring(fileName.lastIndexOf(".") + 1, fileName.length()); this.state = State.valueOf(extension.toUpperCase()); } else { throw new IllegalStateException(String.format("The passed in file, %s, does not exist", file.getAbsolutePath())); } } public StagedResource(long threshold, File directory, String path, StagingManager stagingManager) { this.threshold = threshold; this.directory = directory; this.path = path; this.stagingManager = stagingManager; this.file = new File(directory, String.format("%s.%s", path, State.CREATE.getExtensionName())); lastUpdateTime = System.currentTimeMillis(); this.state = State.CREATE; } public boolean isInUse() { return readers.size() > 0 || writer != null || (inputStreams != null && inputStreams.size() > 0) || outputStream != null; } public boolean isFileResource() { return file != null && file.exists(); } protected File buildFile(State state) { return new File(directory, String.format("%s.%s", path, state.getExtensionName())); } public State getState() { return state; } public void setState(State state) { if (file.exists()) { File newFile = buildFile(state); if (!newFile.equals(file)) { if (newFile.exists()) { if (writer != null || outputStream != null) { throw new IoException("Could not write '{}' it is currently being written to", newFile.getAbsolutePath()); } if (!FileUtils.deleteQuietly(newFile)) { log.warn("Failed to delete '{}' in preparation for renaming '{}'", newFile.getAbsolutePath(), file.getAbsoluteFile()); if (readers.size() > 0) { for (Thread thread : readers.keySet()) { BufferedReader reader = readers.get(thread); log.warn("Closing unwanted reader for '{}' that had been created on thread '{}'", newFile.getAbsolutePath(), thread.getName()); IOUtils.closeQuietly(reader); } } if (!FileUtils.deleteQuietly(newFile)) { log.warn("Failed to delete '{}' for a second time", newFile.getAbsolutePath()); } } } if (!file.renameTo(newFile)) { String msg = String .format("Had trouble renaming file. The current name is %s and the desired state was %s", file.getAbsolutePath(), state); log.warn(msg); throw new IllegalStateException(msg); } else { this.file = newFile; } } } else if (memoryBuffer != null && state == State.DONE) { this.memoryBuffer.setLength(0); this.memoryBuffer = null; } refreshLastUpdateTime(); this.state = state; } public BufferedReader getReader() { Thread thread = Thread.currentThread(); BufferedReader reader = readers.get(thread); if (reader == null) { if (file.exists()) { try { reader = new BufferedReader(new InputStreamReader(new FileInputStream(file), IoConstants.ENCODING)); readers.put(thread, reader); } catch (IOException ex) { throw new IoException(ex); } } else if (memoryBuffer != null && memoryBuffer.length() > 0) { reader = new BufferedReader(new StringReader(memoryBuffer.toString())); readers.put(thread, reader); } else { throw new IllegalStateException( "There is no content to read. Memory buffer was empty and " + file.getAbsolutePath() + " was not found."); } } return reader; } public void close() { Thread thread = Thread.currentThread(); BufferedReader reader = readers.get(thread); if (reader != null) { IOUtils.closeQuietly(reader); readers.remove(thread); } if (writer != null) { IOUtils.closeQuietly(writer); writer = null; } if (outputStream != null) { IOUtils.closeQuietly(outputStream); outputStream = null; } if (inputStreams != null) { InputStream inputStream = inputStreams.get(thread); if (inputStream != null) { IOUtils.closeQuietly(inputStream); inputStreams.remove(thread); } } } public OutputStream getOutputStream() { try { if (outputStream == null) { if (file.exists()) { log.warn("We had to delete {} because it already existed", file.getAbsolutePath()); file.delete(); } file.getParentFile().mkdirs(); outputStream = new BufferedOutputStream(new FileOutputStream(file)); } return outputStream; } catch (FileNotFoundException e) { throw new IoException(e); } } public InputStream getInputStream() { Thread thread = Thread.currentThread(); if (inputStreams == null) { inputStreams = new HashMap<Thread, InputStream>(); } InputStream reader = inputStreams.get(thread); if (reader == null) { if (file.exists()) { try { reader = new BufferedInputStream(new FileInputStream(file)); inputStreams.put(thread, reader); } catch (IOException ex) { throw new IoException(ex); } } else if (memoryBuffer != null && memoryBuffer.length() > 0) { reader = new ByteArrayInputStream(memoryBuffer.toString().getBytes()); inputStreams.put(thread, reader); } else { throw new IllegalStateException( "There is no content to read. Memory buffer was empty and " + file.getAbsolutePath() + " was not found."); } } return reader; } public BufferedWriter getWriter() { if (writer == null) { if (file.exists()) { log.warn("We had to delete {} because it already existed", file.getAbsolutePath()); file.delete(); } else if (this.memoryBuffer != null) { log.warn("We had to delete the memory buffer for {} because it already existed", getPath()); this.memoryBuffer = null; } this.memoryBuffer = new StringBuilder(); writer = new BufferedWriter(new ThresholdFileWriter(threshold, this.memoryBuffer, this.file)); } return writer; } public long getSize() { if (file.exists()) { return file.length(); } else if (memoryBuffer != null) { return memoryBuffer.length(); } else { return 0; } } public boolean exists() { return (file.exists() && file.length() > 0) || (memoryBuffer != null && memoryBuffer.length() > 0); } public long getLastUpdateTime() { return lastUpdateTime; } public void refreshLastUpdateTime() { this.lastUpdateTime = System.currentTimeMillis(); } public boolean delete() { boolean deleted = true; close(); if (file.exists()) { FileUtils.deleteQuietly(file); deleted = !file.exists(); } if (memoryBuffer != null) { memoryBuffer.setLength(0); memoryBuffer = null; } if (deleted) { stagingManager.resourceList.remove(getPath()); } return deleted; } public File getFile() { return file; } public String getPath() { return path; } @Override public String toString() { return file.exists() ? file.getAbsolutePath() : String.format("%d bytes in memory", memoryBuffer.length()); } }