All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sun.enterprise.deploy.shared.FileArchive Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2022, 2023 Contributors to the Eclipse Foundation. All rights reserved.
 * Copyright (c) 2006, 2018 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0, which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * This Source Code may also be made available under the following Secondary
 * Licenses when the conditions for such availability set forth in the
 * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
 * version 2 with the GNU Classpath Exception, which is available at
 * https://www.gnu.org/software/classpath/license.html.
 *
 * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
 */

package com.sun.enterprise.deploy.shared;

import com.sun.enterprise.deployment.deploy.shared.Util;
import com.sun.enterprise.util.io.FileUtils;

import jakarta.inject.Inject;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.LineNumberReader;
import java.io.PrintStream;
import java.net.URI;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.glassfish.api.deployment.archive.Archive;
import org.glassfish.api.deployment.archive.ReadableArchive;
import org.glassfish.api.deployment.archive.WritableArchive;
import org.glassfish.api.deployment.archive.WritableArchiveEntry;
import org.glassfish.deployment.common.DeploymentContextImpl;
import org.glassfish.hk2.api.PerLookup;
import org.glassfish.logging.annotation.LogMessageInfo;
import org.jvnet.hk2.annotations.Service;

/**
 * This implementation of the Archive interface maps to a directory/file structure.
 * 

* If the directory underlying the FileArchive is created by GlassFish then FileArchive filters its contents so only * those files more recent than the creation of the archive itself are visible to consumers. *

* The main motivation is to hide unwanted "left-over" files from previous deployments that might linger, especially on * Windows, after the previous app had been undeployed. (Deployment uses a FileArchive to extract the user's JAR-based * archive into the applications directory.) Historically such left-over files arise after GlassFish expands an archive * into its exploded form but then some code opens but does not close a file in that exploded directory tree. *

* An open left-over file can be overwritten-in-place on Windows, and this happens when a caller invokes * {@link #putNextEntry(java.lang.String) } to create a new entry (file) inside the archive. But a left-over file that * is not in the new app but is still open by GlassFish cannot be deleted or renamed on Windows and so it will remain in * the expansion directory. Such left-over files, if not filtered out, can confuse GlassFish and the application. By * "stamping" the archive creation date we can filter out such old, left-over files. *

* To support this feature, when FileArchive creates a directory it stores a marker file there, the contents of which * records the creation date/time of the archive. We cannot just use the lastModified value for the top-level directory. * Users might legitimately use "touch .reload" in the applications/appName directory to trigger a dynamic reload of the * app. If .reload does not already exist then touch creates it, and this would update the lastModified of the directory * file. * * @author Jerome Dochez * @author Tim Quinn */ @Service(name = "file") @PerLookup public class FileArchive extends AbstractReadableArchive implements WritableArchive { private static final Logger deplLogger = DeploymentContextImpl.deplLogger; @LogMessageInfo(message = "Attempt to list files in {0} failed, perhaps because that is not a valid directory or because file permissions do not allow GlassFish to access it", level = "WARNING") private static final String FILE_LIST_FAILURE = "NCLS-DEPLOYMENT-00022"; @LogMessageInfo(message = "Ignoring {0} because the containing archive {1} recorded it as a pre-existing stale file", level = "WARNING") private static final String STALE_FILES_SKIPPED = "NCLS-DEPLOYMENT-00023"; private final static Level DEBUG_LEVEL = Level.FINE; @Inject private ArchiveFactory archiveFactory; /** the archive abstraction directory. */ private File archive; private URI uri; /** * Tracks stale files in the archive and filters the archive's contents to exclude stale entries */ private StaleFileManager staleFileManager; /** * Records whether open or create has been invoked. Otherwise we can't be sure that * the staleFileManager field has been set. */ private boolean isOpenedOrCreated; public FileArchive() { } public FileArchive(URI uri) throws IOException { open(uri); } /** * Open an abstract archive * * @param uri path to the archive */ @Override public void open(URI uri) throws IOException { if (!uri.getScheme().equals("file")) { throw new IOException("Wrong scheme for FileArchive : " + uri.getScheme()); } this.uri = uri; archive = new File(uri); if (!archive.exists()) { throw new FileNotFoundException(uri.getSchemeSpecificPart()); } isOpenedOrCreated = true; staleFileManager = StaleFileManager.Util.getInstance(archive); } /** * @see #open(URI) * @param uri a string representing URI */ public void open(String uri) throws IOException { open(URI.create(uri)); } /** * Get the size of the archive * * @return tje the size of this archive or -1 on error */ @Override public long getArchiveSize() throws NullPointerException, SecurityException { if (uri == null) { return -1; } return new File(uri).length(); } /** * creates a new abstract archive with the given path * * @param uri path to create the archive */ @Override public void create(URI uri) throws IOException { this.uri = uri; archive = new File(uri); /* * Get the stale file manager before creating the directories; it's slightly faster that way. */ staleFileManager = StaleFileManager.Util.getInstance(archive); if (!archive.exists() && !archive.mkdirs()) { throw new IOException("Unable to create directory for " + archive.getAbsolutePath()); } isOpenedOrCreated = true; } /** * close the abstract archive */ @Override public void close() throws IOException { } /** * delete the archive */ @Override public boolean delete() { // delete the directory structure... try { final boolean result = deleteDir(archive); /* * Create the stale file marker file, if needed. */ StaleFileManager.Util.markDeletedArchive(this); return result; } catch (IOException e) { return false; } } @Override public boolean isDirectory(String name) { final File candidate = new File(this.archive, name); return isEntryValid(candidate) && candidate.isDirectory(); } /** * @return an @see java.util.Enumeration of entries in this abstract archive */ @Override public Enumeration entries() { final List namesList = new ArrayList<>(); getListOfFiles(archive, namesList, null); return Collections.enumeration(namesList); } /** * Returns the enumeration of first level directories in this archive * * @return enumeration of directories under the root of this archive */ @Override public Collection getDirectories() throws IOException { List results = new ArrayList<>(); if (archive != null) { for (File file : archive.listFiles()) { if (file.isDirectory() && isEntryValid(file)) { results.add(file.getName()); } } } return results; } /** * @return an @see java.util.Enumeration of entries in this abstract archive, providing the list of embedded archive to * not count their entries as part of this archive */ public Enumeration entries(Enumeration embeddedArchives) { List nameList = new ArrayList<>(); List massagedNames = new ArrayList(); while (embeddedArchives.hasMoreElements()) { String subArchiveName = (String) embeddedArchives.nextElement(); massagedNames.add(FileUtils.makeFriendlyFilenameExtension(subArchiveName)); } getListOfFiles(archive, nameList, massagedNames); return Collections.enumeration(nameList); } /** * Returns an enumeration of the module file entries with the specified prefix. All elements in the enumeration are of * type String. Each String represents a file name relative to the root of the module. * * @param prefix the prefix of entries to be included * @return an enumeration of the archive file entries. */ @Override public Enumeration entries(String prefix) { prefix = prefix.replace('/', File.separatorChar); File file = new File(archive, prefix); List namesList = new ArrayList<>(); getListOfFiles(file, namesList, null); return Collections.enumeration(namesList); } /** * @return true if this archive exists */ @Override public boolean exists() { if (archive == null) { return false; } return archive.exists(); } /** * * create or obtain an embedded archive within this abstraction. * * @param name name of the embedded archive. */ @Override public ReadableArchive getSubArchive(String name) throws IOException { File subEntry = new File(getFileSubArchivePath(name)); if (!subEntry.exists()) { return null; } if (!isEntryValid(subEntry)) { deplLogger.log(DEBUG_LEVEL, "FileArchive.getSubArchive for {0} found that it is not a valid entry; it is stale", subEntry.getAbsolutePath()); return null; } deplLogger.log(DEBUG_LEVEL, "FileArchive.getSubArchive for {0} found that it is valid", subEntry.getAbsolutePath()); ReadableArchive result = archiveFactory.openArchive(subEntry); if (result instanceof AbstractReadableArchive) { ((AbstractReadableArchive) result).setParentArchive(this); } return result; } /** * create or obtain an embedded archive within this abstraction. * * @param name name of the embedded archive. */ @Override public WritableArchive createSubArchive(String name) throws IOException { String subEntryName = getFileSubArchivePath(name); File subEntry = new File(subEntryName); if (!subEntry.exists()) { // time to create a new sub directory if (!subEntry.exists() && !subEntry.mkdirs()) { throw new IOException("Unable to create directory for " + subEntry.getAbsolutePath()); } deplLogger.log(DEBUG_LEVEL, "FileArchive.createSubArchive created dirs for {0}", subEntry.getAbsolutePath()); } else { deplLogger.log(DEBUG_LEVEL, "FileArchive.createSubArchive found existing dir for {0}", subEntry.getAbsolutePath()); /* * This subdirectory already exists, so it might be marked as stale. Because this invocation is creating the subarchive * in the current archive, the subdirectory is no longer stale. */ staleFileManager().recordValidEntry(subEntry); } final WritableArchive result = archiveFactory.createArchive(subEntry); if (result instanceof AbstractReadableArchive) { ((AbstractReadableArchive) result).setParentArchive(this); } return result; } /** * Returns the existence of the given entry name The file name must be relative to the root of the module. * * @param name the file name relative to the root of the module. * @return the existence the given entry name. */ @Override public boolean exists(String name) throws IOException { name = name.replace('/', File.separatorChar); File input = new File(archive, name); return input.exists() && isEntryValid(input); } /** * @return a @see java.io.InputStream for an existing entry in the current abstract archive * @param name the entry name */ @Override public InputStream getEntry(String name) throws IOException { // If name corresponds to directory, return null as it can not be opened File input = getEntryFile(name); if (!input.exists() || input.isDirectory() || !isEntryValid(input)) { return null; } FileInputStream fis = new FileInputStream(input); try { BufferedInputStream bis = new BufferedInputStream(fis); return bis; } catch (Throwable tx) { try { fis.close(); } catch (Throwable thr) { throw new IOException( "Error closing FileInputStream after error opening BufferedInputStream for entry " + name, thr); } throw new IOException("Error opening BufferedInputStream for entry " + name, tx); } } /** * Returns the entry size for a given entry name or 0 if not known * * @param name the entry name * @return the entry size */ @Override public long getEntrySize(String name) { name = name.replace('/', File.separatorChar); File input = new File(archive, name); if (!input.exists() || !isEntryValid(input)) { return 0; } return input.length(); } /** * @return the manifest information for this abstract archive */ @Override public Manifest getManifest() throws IOException { InputStream is = null; try { is = getEntry(JarFile.MANIFEST_NAME); if (is != null) { Manifest m = new Manifest(is); return m; } } finally { if (is != null) { is.close(); } } return null; } /** * Returns the URI used to create or open the underlyong archive * * @return the URI for this archive. */ @Override public URI getURI() { return uri; } /** * rename the archive * * @param name the archive name */ @Override public boolean renameTo(String name) { return FileUtils.renameFile(archive, new File(name)); } @Override public WritableArchiveEntry putNextEntry(String name) throws java.io.IOException { name = name.replace('/', File.separatorChar); File newFile = new File(archive, name); if (newFile.exists()) { if (!deleteEntry(name, false /* isLogging */) && uri != null) { deplLogger.log(Level.FINE, "Could not delete file {0} in FileArchive {1} during putNextEntry; continuing", new Object[] { name, uri.toASCIIString() }); } } // if the entry name contains directory structure, we need // to create those directories first. if (name.lastIndexOf(File.separatorChar) != -1) { String dirs = name.substring(0, name.lastIndexOf(File.separatorChar)); File dirsFile = new File(archive, dirs); if (!dirsFile.exists() && !dirsFile.mkdirs()) { throw new IOException("Unable to create directory for " + dirsFile.getAbsolutePath()); } } staleFileManager().recordValidEntry(newFile); FileOutputStream outputStream = new FileOutputStream(newFile); return new WritableArchiveEntry(() -> outputStream, outputStream::close); } /** * Returns the name portion of the archive's URI. *

* For FileArhive the name is all of the path that follows the last slash (ignoring a slash at the end of the path). *

* Here are some example archive names for the specified FileArchive paths: *

    *
  • /a/b/c/d/ -> d *
  • /a/b/c/d -> d *
  • /a/b/c.jar -> c.jar *
* * @return the name of the archive * */ @Override public String getName() { return Util.getURIName(getURI()); } /** * @return true if this archive abstraction supports overwriting of elements * */ public boolean supportsElementsOverwriting() { return true; } /** * delete an entry in the archive * * @param name the entry name * @return true if the entry was successfully deleted * */ public boolean deleteEntry(String name) { return deleteEntry(name, true); } // ## Private methods /** * * create or obtain an embedded archive within this abstraction. * * @param name name of the embedded archive. */ private String getFileSubArchivePath(String name) throws IOException { // Convert name to native form. See bug #6345029 for more details. name = name.replace('/', File.separatorChar); File file = new File(name); File subDir; if (file.isAbsolute()) { subDir = file; } else { // first we try to see if a sub directory with the right file // name exist subDir = new File(archive, FileUtils.makeFriendlyFilenameExtension(name)); if (!subDir.exists()) { // now we try to open a sub jar file... subDir = new File(archive, name); if (!subDir.exists()) { // ok, nothing worked, reassing the name to the // sub directory one subDir = new File(archive, FileUtils.makeFriendlyFilenameExtension(name)); } } } return subDir.getPath(); } private File getEntryFile(String name) { name = name.replace('/', File.separatorChar); return new File(archive, name); } /** * Reports whether the entry is valid, in the sense that if this archive has been created during this execution then the * entry requested was created later than the archive itself. *

* It is possible (for example, on Windows) for GlassFish to want to create a new archive in a directory that already * exists and contains stale "left-over" files from a previous deployment, for example. This method causes the * FileArchive implementation to hide any files that reside in the directory for an archive that was created during this * VM execution but were not explicitly added to the archive using putNextEntry. * * @param entry file to check * @return */ private boolean isEntryValid(final File entry) { return isEntryValid(entry, true, deplLogger); } private boolean isEntryValid(final File entry, final boolean isLogging) { return isEntryValid(entry, isLogging, deplLogger); } private boolean isEntryValid(final File entry, final boolean isLogging, final Logger logger) { return staleFileManager().isEntryValid(entry, isLogging, logger); } private StaleFileManager myStaleFileManager() { /* * If the FileArchive has been opened or created then its staleFileManager has been set. */ if (!isOpenedOrCreated) { throw new IllegalStateException(); } return staleFileManager; } private StaleFileManager staleFileManager() { ReadableArchive parent = getParentArchive(); if (parent == null) { return myStaleFileManager(); } if (parent instanceof FileArchive) { return ((FileArchive) parent).staleFileManager(); } else { return null; } } /** * Reports whether the entry is valid, in the sense that the entry is more recent than the archive itself. * * @param entryName name of the entry to check * @return */ private boolean isEntryValid(final String entryName, final Logger logger) { return isEntryValid(getEntryFile(entryName), true, logger); } /** * utility method for deleting a directory and all its content */ private boolean deleteDir(File directory) throws IOException { if (!directory.isDirectory()) { throw new FileNotFoundException(directory.getPath()); } boolean allDeletesSucceeded = true; // delete contents /* * Do not recursively delete the contents if the current directory is a symbolic link. */ /* * Fix for bug Glassfish-21261 , method safeIsRealDirectory(File) might return false in case if the currently directory * has a symbolic link in its hierarchy and the currently directory itself might not be a symbolic link. */ if (!Files.isSymbolicLink(directory.toPath())) { File[] entries = directory.listFiles(); for (File entry : entries) { if (entry.isDirectory()) { allDeletesSucceeded &= deleteDir(entry); } else { if (!entry.equals(StaleFileManager.Util.markerFile(archive))) { final boolean fileDeleteOK = FileUtils.deleteFileWithWaitLoop(entry); if (fileDeleteOK) { myStaleFileManager().recordDeletedEntry(entry); } allDeletesSucceeded &= fileDeleteOK; } } } } // delete self (the directory or the symbolic link) return (allDeletesSucceeded && FileUtils.deleteFileWithWaitLoop(directory)); } /** * utility method for getting contents of directory and sub directories */ private void getListOfFiles(File directory, List files, List embeddedArchives) { getListOfFiles(directory, files, embeddedArchives, deplLogger); } /** * Adds the files in the specified directory to the collection of files already assembled. Excludes the contents of * embedded archives in the current archive which appear in the file tree anchored at the given directory. * * @param directory the directory to scan for files * @param files collection of files already assembled to which this directory's files are to be added * @param embeddedArchives collection of embedded archives in the current archive * @param logger logger to which to report inability to get the list of files from the directory */ void getListOfFiles(File directory, List files, List embeddedArchives, final Logger logger) { if (archive == null || directory == null || !directory.isDirectory()) { return; } final File[] fileList = directory.listFiles(); if (fileList == null) { deplLogger.log(Level.WARNING, FILE_LIST_FAILURE, directory.getAbsolutePath()); return; } for (File aList : fileList) { String fileName = aList.getAbsolutePath().substring(archive.getAbsolutePath().length() + 1); fileName = fileName.replace(File.separatorChar, '/'); if (!aList.isDirectory()) { if (!fileName.equals(JarFile.MANIFEST_NAME) && isEntryValid(fileName, logger)) { files.add(fileName); } } else if (isEntryValid(fileName, logger)) { files.add(fileName); // Add entry corresponding to the directory also to the list if (embeddedArchives != null) { if (!embeddedArchives.contains(fileName)) { getListOfFiles(aList, files, null, logger); } } else { getListOfFiles(aList, files, null, logger); } } } } private boolean deleteEntry(String name, final boolean isLogging) { name = name.replace('/', File.separatorChar); File input = new File(archive, name); if (!input.exists() || !isEntryValid(input, isLogging)) { return false; } final boolean result = input.delete(); myStaleFileManager().recordDeletedEntry(input); return result; } /** * API which FileArchive methods should use for dealing with the StaleFileManager implementation. */ public interface StaleFileManager { /** * Returns whether the specified file is valid - that is, is dated after the archive was created. * * @param f the file to check * @param isLogging whether to log a warning about the check of the entry * @return true if the file is valid; false otherwise */ boolean isEntryValid(File f, boolean isLogging); boolean isEntryValid(File f, boolean isLogging, Logger logger); /** * Returns whether the specified file is for the hidden timestamp file which FileArchive uses internally. * * @param f the file to check * @return true if the File is the hidden timestamp file; false otherwise */ boolean isEntryMarkerFile(File f); void recordValidEntry(File f); void recordDeletedEntry(File f); void flush(); public class Util { private final static String MARKER_FILE_PATH = ".glassfishStaleFiles"; private static File markerFile(final File archive) { return new File(archive, MARKER_FILE_PATH); } /** * Creates a marker file in the archive directory - if it still exists and contains any stale files. * * @param archive the File for the archive to mark */ public static void markDeletedArchive(final Archive archive) { if (!(archive instanceof FileArchive)) { return; } final File archiveFile = new File(archive.getURI()); markDeletedArchive(archiveFile); } /** * Creates a marker file in the archive directory - if it still exists and contains any stale files. * * @param archive the File for the archive to mark */ public static void markDeletedArchive(final File archiveFile) { if (!archiveFile.exists()) { return; } final Iterator staleFileIt = findFiles(archiveFile); if (!staleFileIt.hasNext()) { return; } final URI archiveURI = archiveFile.toURI(); PrintStream ps = null; try { ps = new PrintStream(markerFile(archiveFile)); } catch (FileNotFoundException ex) { throw new RuntimeException(ex); } for (; staleFileIt.hasNext();) { final URI relativeURI = archiveURI.relativize(staleFileIt.next().toURI()); ps.println(relativeURI); deplLogger.log(DEBUG_LEVEL, "FileArchive.StaleFileManager recording left-over file {0}", relativeURI); } ps.close(); } /** * Returns an Iterator over the files contained in the directory tree anchored at the given directory, excluding any * stale file marker file. *

* For efficiency, this implementation avoids creating a list of all the files in the directory tree all at once. It * traverses each directory as it encounters it. * * @param dir root of the directory tree to be traversed * @return Iterator over the contained files */ private static Iterator findFiles(final File dir) { return new Iterator<>() { private final List fileList; private final ListIterator fileListIt; { fileList = new ArrayList<>(Arrays.asList(dir.listFiles(new MarkerExcluderFileFilter()))); fileListIt = fileList.listIterator(); } @Override public boolean hasNext() { return fileListIt.hasNext(); } @Override public File next() { final File result = fileListIt.next(); if (result.isDirectory()) { for (File f : result.listFiles(new MarkerExcluderFileFilter())) { fileListIt.add(f); /* * Back up so the next invocation of this method will return the just-added entry. */ fileListIt.previous(); } } return result; } @Override public void remove() { throw new UnsupportedOperationException(); } }; } private static final class MarkerExcluderFileFilter implements FileFilter { @Override public boolean accept(File pathname) { return !pathname.getName().equals(MARKER_FILE_PATH); } } /** * Factory method for a StaleFileManager. *

* Callers should invoke this method only after they have finished with the FileArchive and have tried to delete it. If * the directory for the archive remains then it contains one or more stale files that could not be deleted, and the * factory method returns a instance that tracks the stale files. If the directory no longer exists then the delete * succeeded, there are * * @param archive the directory to contain the archive * @return StaleFileManager for the FileArchive to use */ public static StaleFileManager getInstance(final File archive) throws IOException { if (archive.exists()) { return new StaleFileManagerImpl(archive); } else { return new StaleFileManagerImplNoop(); } } } } /** * Acts as a stale file manager but does no real work. *

* Used as a stale file manager for an archive that was successfully deleted and therefore contains no stale files. */ private static class StaleFileManagerImplNoop implements StaleFileManager { @Override public boolean isEntryValid(File f, boolean isLogging) { return true; } @Override public boolean isEntryValid(File f, boolean isLogging, Logger logger) { return true; } @Override public boolean isEntryMarkerFile(File f) { return false; } @Override public void recordValidEntry(File f) { } @Override public void recordDeletedEntry(File f) { } @Override public void flush() { } } /** * Implements stale file manager that might contain stale files. */ private static class StaleFileManagerImpl implements StaleFileManager { private final static String LINE_SEP = System.getProperty("line.separator"); private final File archiveFile; private final URI archiveURI; private final Collection staleEntryNames; private final File markerFile; private StaleFileManagerImpl(final File archive) throws FileNotFoundException, IOException { archiveFile = archive; archiveURI = archive.toURI(); markerFile = StaleFileManager.Util.markerFile(archive); staleEntryNames = readStaleEntryNames(markerFile); } /** * Reads entry names of stale files from the marker file, if it exists. * * @param markerFile the marker file to be read * @return Collection of stale entry names. * @throws FileNotFoundException if the marker file existed initially but vanished before it could be opened * @throws IOException in case of errors reading the marker file */ private static Collection readStaleEntryNames(final File markerFile) throws FileNotFoundException, IOException { final Collection result = new ArrayList<>(); if (!markerFile.exists()) { return result; } LineNumberReader reader = null; try { reader = new LineNumberReader(new FileReader(markerFile)); // Avoid some work if logging is coarser than FINE. final boolean isShowEntriesToBeSkipped = deplLogger.isLoggable(DEBUG_LEVEL); final StringBuffer entriesToSkip = isShowEntriesToBeSkipped ? new StringBuffer() : null; String line; while ((line = reader.readLine()) != null) { result.add(line); if (isShowEntriesToBeSkipped) { entriesToSkip.append(line).append(LINE_SEP); } } if (isShowEntriesToBeSkipped) { deplLogger.log(DEBUG_LEVEL, "FileArchive.StaleFileManager will skip following file(s): {0}{1}", new Object[] { LINE_SEP, entriesToSkip.toString() }); } return result; } finally { if (reader != null) { reader.close(); } } } @Override public boolean isEntryValid(final File f, final boolean isLogging) { return isEntryValid(f, isLogging, deplLogger); } @Override public boolean isEntryValid(final File f, final boolean isLogging, final Logger logger) { final boolean result = (!isEntryMarkerFile(f)) && (!staleEntryNames.contains(archiveURI.relativize(f.toURI()).getPath())); if (!result && !isEntryMarkerFile(f) && isLogging) { deplLogger.log(Level.WARNING, STALE_FILES_SKIPPED, new Object[] { archiveURI.relativize(f.toURI()).toASCIIString(), archiveFile.getAbsolutePath() }); } return result; } @Override public boolean isEntryMarkerFile(File f) { return markerFile.equals(f); } /** * Records that the specified file is valid. *

* If the file had previously been marked as stale, it will no longer be considered stale. * * @param f the File which is now valid */ @Override public void recordValidEntry(File f) { if (updateStaleEntry(f, "FileArchive.StaleFileManager marking formerly stale entry {0} as active")) { /* * Process not only the file itself but the directories from the file to the owning archive, since the directories are * now implicitly valid as well. */ do { f = f.getParentFile(); updateStaleEntry(f, "FileArchive.StaleFileManager marking formerly stale ancestor {0} as active"); } while (!f.equals(archiveFile)); flush(); } } @Override public void recordDeletedEntry(File f) { if (updateStaleEntry(f, "FileArchive.StaleFileManager recording deletion of entry {0}")) { /* * If there are no other stale files in the same directory as the file just deleted, then remove the directory from the * stale files collection and check its ancestors. */ do { if (isStaleEntryInDir(f.getParentFile())) { return; } updateStaleEntry(f, "FileArchive.StaleFileManager recording that formerly stale directory {0} is no longer stale"); f = f.getParentFile(); } while (!f.equals(archiveFile)); flush(); } } private boolean isStaleEntryInDir(final File dir) { final String dirURIPath = archiveURI.relativize(dir.toURI()).getPath(); for (String staleEntryName : staleEntryNames) { if (staleEntryName.startsWith(dirURIPath) && !staleEntryName.equals(dirURIPath)) { deplLogger.log(DEBUG_LEVEL, "FileArchive.StaleFileManager.isStaleEntryInDir found remaining stale entry {0} in {1}", new Object[] { staleEntryName, dir.getAbsolutePath() }); return true; } } return false; } private boolean updateStaleEntry(File f, final String msg) { if (staleEntryNames.isEmpty()) { deplLogger.log(DEBUG_LEVEL, "FileArchive.StaleFileManager.updateStaleEntry finds staleEntryNames is empty; skipping"); return false; } final String entryName = archiveURI.relativize(f.toURI()).toASCIIString(); final boolean wasStale = staleEntryNames.remove(entryName); if (wasStale) { deplLogger.log(DEBUG_LEVEL, msg, entryName); } else { deplLogger.log(DEBUG_LEVEL, "updateStaleEntry did not find {0} in the stale entries {1}", new Object[] { entryName, staleEntryNames.toString() }); } return wasStale; } @Override public void flush() { if (staleEntryNames.isEmpty()) { deplLogger.log(DEBUG_LEVEL, "FileArchive.StaleFileManager.flush deleting marker file; no more stale entries"); final File marker = Util.markerFile(archiveFile); if (!marker.exists() || marker.delete()) { return; } /* * Couldn't delete the marker file, so try to write out an empty one so its old contents will not confuse the stale file * manager. */ deplLogger.log(Level.FINE, "FileArchive.StatleFileManager.flush could not delete marker file {0}; continuing by writing out an empty marker file", marker.getAbsolutePath()); } PrintStream ps = null; try { ps = new PrintStream(Util.markerFile(archiveFile)); } catch (FileNotFoundException ex) { throw new RuntimeException(ex); } for (String staleEntryName : staleEntryNames) { ps.println(staleEntryName); } ps.close(); deplLogger.log(DEBUG_LEVEL, "FileArchive.StaleFileManager.flush rewrote on-disk file {0} containing {1}", new Object[] { markerFile.getAbsolutePath(), staleEntryNames.toString() }); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy