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

com.pekinsoft.framework.LocalStorage Maven / Gradle / Ivy

/*
 * Copyright (C) 2024 PekinSOFT Systems
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see .
 *
 * *****************************************************************************
 *  Project    :   application-framework-api
 *  Class      :   LocalStorage.java
 *  Author     :   Sean Carrick
 *  Created    :   Jul 14, 2024
 *  Modified   :   Jul 14, 2024
 *
 *  Purpose: See class JavaDoc for explanation
 *
 *  Revision History:
 *
 *  WHEN          BY                   REASON
 *  ------------  -------------------  -----------------------------------------
 *  Jul 14, 2024  Sean Carrick         Initial creation.
 * *****************************************************************************
 */
package com.pekinsoft.framework;

import java.beans.DefaultPersistenceDelegate;
import java.beans.ExceptionListener;
import java.beans.XMLDecoder;
import java.beans.XMLEncoder;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.io.UncheckedIOException;
import java.io.Writer;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.PosixFileAttributeView;
import java.nio.file.attribute.PosixFileAttributes;
import java.nio.file.attribute.PosixFilePermission;
import java.nio.file.attribute.PosixFilePermissions;
import java.nio.file.attribute.UserPrincipal;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Set;
import java.lang.System.Logger;
import java.lang.System.Logger.Level;

/**
 * Access to per {@code Application}, per user local file
 * storage, using the {@link java.nio.file Java NIO API}. While the original
 * {@code LocalStorage} class did a decent job, we felt that this class would do
 * a better job, in a better manner.
 *
 * @author Sean Carrick <sean at pekinsoft dot com>
 *
 * @version 2.4
 * @since 1.0
 */
public class LocalStorage extends AbstractBean {

    private enum OSId {
        WINDOWS, OSX, UNIX
    }

    private final ApplicationContext context;
    private final Path unspecifiedFile = new File("unspecified").toPath();
    private Logger logger;
    private long storageLimit = -1L;
    private Path directory = unspecifiedFile;

    protected LocalStorage(ApplicationContext context) {
        if (context == null) {
            throw new IllegalArgumentException("{@code null} context");
        }
        this.context = context;
    }

    private void checkLogger() {
        if (!context.isConstructing()) {
            if (logger == null) {
                logger = System.getLogger(LocalStorage.class.getName());
            }
        }
    }

    /**
     * Retrieves the {@link com.pekinsoft.framework.ApplicationContext
     * ApplicationContext singleton}.
     *
     * @return the {@code ApplicationContext} singleton
     */
    protected final ApplicationContext getContext() {
        return context;
    }

    private void checkFileName(String fileName) {
        if (fileName == null) {
            throw new IllegalArgumentException("{@code null} fileName");
        }
    }

    /**
     * Allows for changing the {@link com.pekinsoft.framework.Application
     * Application's} home directory to a different one. If the directory is
     * changed, a property change is fired on the property name "directory".
     *
     * @param directory a {@link java.nio.file.Path Path} to the directory to
     *                  use for the {@code Application}'s home directory
     *
     * @throws IllegalArgumentException if {@code directory} is {@code null}
     */
    public void setDirectory(Path directory) {
        if (directory == null) {
            throw new IllegalArgumentException("{@code null} directory");
        }
        Path oldValue = this.directory;
        this.directory = directory;
        firePropertyChange("directory", oldValue, this.directory);
    }

    /**
     * Retrieves the currently set storage limit for the {@link
     * com.pekinsoft.framework.Application Application}. If the retrieved limit
     * is {@code -1L}, then there is no limit on storage that may be used.
     *
     * @return the current storage limit
     *
     * @see #setStorageLimit(long)
     */
    public long getStorageLimit() {
        return storageLimit;
    }

    /**
     * Sets the storage limit for the {@link com.pekinsoft.framework.Application
     * Application}. If no limit should be enforced, the pass {@code -1L} as the
     * {@code storageLimit} value (this is the default). If the storage limit is
     * changed, a property change is fired on the property name "storageLimit".
     *
     * @param storageLimit the number of bytes to which storage for the {@code
     * Application} should be limited
     *
     * @throws IllegalArgumentException if the specified {@code storageLimit} is
     *                                  less than {@code -1L}
     */
    public void setStorageLimit(long storageLimit) {
        if (storageLimit < -1L) {
            throw new IllegalArgumentException("invalid storageLimit");
        }
        long oldValue = this.storageLimit;
        this.storageLimit = storageLimit;
        firePropertyChange("storageLimit", oldValue, this.storageLimit);
    }

    /**
     * Retrieves a {@link java.io.FileReader FileReader} object for reading data
     * from a file on disk.
     *
     * @param location one of the {@link com.pekinsoft.framework.StorageLocations
     * StorageLocations} enum constants for where the file is located
     * @param fileName the name of the file
     *
     * @return a {@code FileReader} object on the specified {@code fileName}
     *
     * @throws IOException in the event any input/output errors occur, including
     *                     denial of access due to file permissions
     */
    public synchronized Reader openFileReader(StorageLocations location,
            String fileName) throws IOException {
        checkFileName(fileName);
        Path file = Paths.get(getDirectoryPath(location).toString(), fileName);

        boolean readOnly = !canWrite(file);

        if (readOnly) {
            String msg = String.format("The file \"%s\" cannot be created "
                    + "in the folder \"%s\" for the user. \"%s\" is "
                    + "read-only.", fileName, file.getParent(),
                    System.getProperty("user.name"));
            throw new IOException(msg);
        }
        try {
            return Files.newBufferedReader(file);
        } catch (IOException e) {
            String msg = String.format("Unable to open input file \"%s\" "
                    + "for the specified location: ", fileName, location);
            throw new IOException(msg, e);
        }
    }

    /**
     * Retrieves a {@link java.io.FileInputStream FileInputStream} object for
     * reading data from a file on disk.
     *
     * @param location one of the {@link com.pekinsoft.framework.StorageLocations
     * StorageLocations} enum constants for where the file is located
     * @param fileName the name of the file
     *
     * @return a {@code FileInputStream} object on the specified
     *         {@code fileName}
     *
     * @throws IOException in the event any input/output errors occur, including
     *                     denial of access due to file permissions
     */
    public synchronized InputStream openInputFile(StorageLocations location,
            String fileName) throws IOException {
        checkFileName(fileName);
        Path file = Paths.get(getDirectoryPath(location).toString(), fileName);

        boolean readOnly = !canWrite(file);

        if (readOnly) {
            String msg = String.format("The file \"%s\" cannot be created "
                    + "in the folder \"%s\" for the user. \"%s\" is "
                    + "read-only.", fileName, file.getParent(),
                    System.getProperty("user.name"));
            throw new IOException(msg);
        }
        try {
            return Files.newInputStream(file);
        } catch (IOException e) {
            String msg = String.format("Unable to open input file \"%s\" "
                    + "for the specified location: ", fileName, location.name());
            throw new IOException(msg, e);
        }
    }

    /**
     * Retrieves a {@link java.io.FileWriter FileWriter} object for writing data
     * to a file on disk.
     *
     * @param location one of the {@link com.pekinsoft.framework.StorageLocations
     * StorageLocations} enum constants for where the file is located
     * @param fileName the name of the file
     *
     * @return a {@code FileWriter} object on the specified {@code fileName}
     *
     * @throws IOException in the event any input/output errors occur, including
     *                     denial of access due to file permissions
     */
    public synchronized Writer openFileWriter(StorageLocations location,
            String fileName) throws IOException {
        checkFileName(fileName);
        Path file = Paths.get(getDirectoryPath(location).toString(), fileName);

        boolean readOnly = !canWrite(file);

        if (readOnly) {
            String msg = String.format("The file \"%s\" cannot be created "
                    + "in the folder \"%s\" for the user. \"%s\" is "
                    + "read-only.", fileName, file.getParent(),
                    System.getProperty("user.name"));
            throw new IOException(msg);
        }
        try {
            return Files.newBufferedWriter(file);
        } catch (IOException e) {
            String msg = String.format("Unable to open output file \"%s\" "
                    + "for the specified location: ", fileName, location);
            throw new IOException(msg, e);
        }
    }

    /**
     * Retrieves a {@link java.io.FileOutputStream FileOutputStream} object for
     * writing data to a file on disk.
     *
     * @param location one of the {@link com.pekinsoft.framework.StorageLocations
     * StorageLocations} enum constants for where the file is located
     * @param fileName the name of the file
     *
     * @return a {@code FileWriter} object on the specified {@code fileName}
     *
     * @throws IOException in the event any input/output errors occur, including
     *                     denial of access due to file permissions
     */
    public synchronized OutputStream openOutputFile(StorageLocations location,
            String fileName) throws IOException {
        checkFileName(fileName);
        Path parent = getDirectoryPath(location);
        Path file = Paths.get(getDirectoryPath(location).toString(), fileName);

        if (!file.getParent().equals(parent)) {
            throw new UncheckedIOException(
                    new IOException("Illegal file access. Cannot access file: \""
                            + fileName)
            );
        }

        boolean readOnly = !canWrite(file);

        if (readOnly) {
            String msg = String.format("The file \"%s\" cannot be created "
                    + "in the folder \"%s\" for the user. \"%s\" is "
                    + "read-only.", fileName, file.getParent(),
                    System.getProperty("user.name"));
            throw new IOException(msg);
        }
        try {
            return Files.newOutputStream(file);
        } catch (IOException | NullPointerException e) {
            if (e instanceof NullPointerException) {
                String msg = String.format("Trapped NPE in LocalStorage.openOutputFile() "
                        + "for StorageLocations \"%s\" and fileName \"%s\" (file = %s).",
                        location, fileName, file);
                System.err.println(msg);
                return null;
            }
            String msg = String.format("Unable to open output file \"%s\" "
                    + "for the specified location: ", fileName, location);
            throw new IOException(msg, e);
        }
    }

    /**
     * Deletes the specified file from the specified {@link
     * com.pekinsoft.framework.StorageLocations StorageLocations} on the disk.
     * This method first checks to see if the specified {@code fileName} is a
     * directory. If it is, then it is checked to see if it is empty. If not, an
     * {@link java.io.IOException IOException} is thrown, as only empty
     * directories may be deleted by this method.
     *
     * Once it is verified to be either an empty directory or a regular file,
     * the user's permission level on the file is then checked. If the user has
     * proper access rights to the file, the file will be deleted. Otherwise, an
     * {@code IOException} will be thrown.
     *
     * @param location one of the {@link com.pekinsoft.framework.StorageLocations
     * StorageLocations} enum constants for where the file is located
     * @param fileName the name of the file
     *
     * @return {@code true} upon successful deletion; {@code false} upon failure
     *
     * @throws IOException in the event any input/output errors occur, including
     *                     denial of access due to file permissions or the
     *                     specified {@code fileName} is a non-empty directory
     */
    public synchronized boolean deleteFile(StorageLocations location,
            String fileName) throws IOException {
        checkFileName(fileName);
        Path parent = getDirectoryPath(location);
        Path file = Paths.get(getDirectoryPath(location).toString(), fileName);

        if (!file.getParent().equals(parent)) {
            throw new UncheckedIOException(
                    new IOException("Illegal file access. Cannot access file: \""
                            + fileName));
        }

        if (Files.isDirectory(file)) {
            if (Files.newDirectoryStream(file).iterator().hasNext()) {
                String msg = String.format("\"%s\" is a directory and is "
                        + "not empty. Delete operation denied.", fileName);
                throw new IOException(msg);
            }
        }
        boolean readOnly = !canWrite(file);

        if (readOnly) {
            String msg = String.format("The file \"%s\" cannot be deleted "
                    + "from the folder \"%s\" for the user. \"%s\" is "
                    + "read-only.", fileName, file.getParent(),
                    System.getProperty("user.name"));
            throw new IOException(msg);
        }
        return Files.deleteIfExists(file);
    }

    /**
     * Retrieves a {@link java.io.File File} object from the specified {@link
     * com.pekinsoft.framework.StorageLocations storage location}. If the
     * specified {@code location} is {@code StorageLocations.FILE_SYSTEM}, the
     * {@code fileName} specified needs to be the complete path to the file,
     * starting at the root of the file system.
     *
     * Note: This method simply returns the results of calling {@link
     * #getFilePath(com.pekinsoft.framework.StorageLocations, java.lang.String)
     * getFilePath()} and converting that {@link java.nio.file.Path Path} to a
     * {@code File} object:
     * 
     * public File getFile(StorageLocations location, String fileName) throws IOException {
     * return getFilePath(location, fileName).toFile();
     * }
     * 
* * @param location the {@code StorageLocations} enum constant for the * location of the file to get * @param fileName the name (or complete path for {@code * StorageLocations.FILE_SYSTEM} locations) of the file to get * * @return the specified file, guaranteed to physically exist on disk * * @throws IOException in the event that an input/output error occurs, * including if the current user does not have read or * write permissions to the storage location */ public File getFile(StorageLocations location, String fileName) throws IOException { return getFilePath(location, fileName).toFile(); } /** * Retrieves a specific {@link java.nio.file.Path Path} on the physical file * system, from the specified location. If {@code location} is {@link * com.pekinsoft.framework.StorageLocations StorageLocations.FILE_SYSTEM}, * {@code fileName} needs to be a complete and absolute path, starting at * the user's home directory. * * Note: Since we are using the Java NIO API, it is guaranteed that * the requested {@code fileName} is readable/writable and physically exists * on the file system. * * @param location the {@code StorageLocations} enum constant for the * location of the file to get * @param fileName the name (or complete path for {@code * StorageLocations.FILE_SYSTEM} locations) of the file to get * * @return the specified file as a {@code Path} and guaranteed to physically * exist on disk, as well as be readable/writable * * @throws IOException in the event that an input/output error occurs, * including if the current user does not have read or * write permissions to the storage location */ public Path getFilePath(StorageLocations location, String fileName) throws IOException { checkFileName(fileName); Path dir = getDirectoryPath(location); while (dir.iterator().hasNext()) { Path p = dir.iterator().next(); if (!canWrite(p)) { throw new IOException(String.format("Cannot write in directory " + "\"%s\"", p)); } } if (!Files.exists(dir)) { dir = Files.createDirectories(dir); } Path file = Paths.get(dir.toString(), fileName); if (!Files.exists(file)) { Files.createFile(file); } return file; } /** * Retrieves a specific {@link java.nio.file.Path Path} on the physical file * system, from the specified location. If {@code location} is {@link * com.pekinsoft.framework.StorageLocations StorageLocations.FILE_SYSTEM}, * {@code fileName} needs to be a complete and absolute path, starting at * the user's home directory. * * Note: Since we are using the Java NIO API, it is guaranteed that * the requested {@code fileName} is readable/writable and physically exists * on the file system. * * @param location the {@code StorageLocations} enum constant for the * location of the file to get * @param directoryName the name (or complete path for {@code * StorageLocations.FILE_SYSTEM} locations) of the directory to get * * @return the specified directory as a {@code Path} and guaranteed to * physically exist on disk, as well as be readable/writable * * @throws IOException in the event that an input/output error occurs, * including if the current user does not have read or * write permissions to the storage location */ public Path getDirectoryPath(StorageLocations location, String directoryName) throws IOException { checkFileName(directoryName); Path dir = getDirectoryPath(location); while (dir.iterator().hasNext()) { Path p = dir.iterator().next(); if (!canWrite(p)) { throw new IOException(String.format("Cannot write in directory " + "\"%s\"", p)); } } if (!Files.exists(dir)) { dir = Files.createDirectories(dir); } Path directory = Paths.get(dir.toString(), directoryName); if (!Files.exists(directory)) { Files.createDirectories(directory); } return directory; } /** * Gets the OS-specific location for the home folder of a given application. * This method makes sure to follow the "rules" of the operating system as * to where the application stores its files. * * Applications can make whatever folder structure needed on the user's PC * hard drive. However, operating systems tend to desire that applications * do not make folders willy-nilly on the hard drive. Therefore, each * operating system vendor has determined locations for third-party * application developers to store their application's files. The * OS-specific locations are detailed in the table below. * * * * * * * * * * *
OS-Specific Application Top-Level Folder Locations
OSApplication Folder Location
Microsoft Windows{@code ${user.home}\\AppData\\${application.name}\\}
Apple Mac OS-X{@code ${user.home}/Library/Application Data/${application.name}/}
Linux and Solaris{@code ${user.home}/.${application.name}/}
* * In order to follow these guidelines, this method has been created. It * checks the system upon which the Java Virtual Machine (JVM) is running to * get its name, then builds the application folder path for that OS in a * {@code java.lang.String} and returns it to the calling class. * * Note: When the directory is retrieved using * this method, it is guaranteed that the directory exits. * * @return a path representing the application's OS-specific folder location * as a {@code java.nio.file.Path} object * * @see #getConfigDirectory() * @see #getDataDirectory() * @see #getErrorDirectory() * @see #getLogDirectory() */ public Path getDirectory() { if (directory == null || directory == unspecifiedFile) { directory = null; String userHome = getUserHome(); if (userHome != null) { String applicationId = getApplicationId(); OSId osId = getOSId(); if (null == osId) { // ${userHome}/.${applicationId} String path = "." + applicationId + "/"; directory = Paths.get(userHome, path); } else { switch (osId) { case WINDOWS: Path appDataDir = null; try { String appDataEV = System.getenv("APPDATA"); if ((appDataEV != null) && (appDataEV.length() > 0)) { appDataDir = Paths.get(appDataEV); } } catch (SecurityException ignore) { } String vendorId = getVendorId(); if ((appDataDir != null) && appDataDir.toFile().isDirectory()) { // ${APPDATADIR}\${vendorId}\${applicationId} directory = Paths.get(appDataDir.toString(), vendorId, applicationId); } else { // ${userHome}\Application Data\${vendorId}\${applicationId} directory = Paths.get(userHome, "Application Data", vendorId, applicationId); } break; case OSX: // ${userHome}/Library/Application Support/${applicationId} directory = Paths.get(userHome, "Library", "Application " + "Support", applicationId); break; default: // ${userHome}/.${applicationId}/ directory = Paths.get(userHome, "." + applicationId); break; } } } } if (!Files.exists(directory)) { try { if (canWrite(directory)) { Files.createDirectories(directory); } } catch (IOException ioe) { checkLogger(); if (logger != null) { logger.log(Level.ERROR, "Unable to create application home " + "directory: " + directory.toString(), ioe); } } } return directory; } /** * A convenience method for getting a standard configuration file directory * in which applications settings and configurations may be stored. * * For applications that wish to store their config files in a separate * directory from any other files the application may create, this method * will get a directory named "etc", with the application's home directory * prepended to it. The path returned is described in the table below, by * OS. * * * * * * * * * * *
OS-Specific Application Configuration Folder Locations
OSApplication Configuration Folder Location
Microsoft Windows{@code ${user.home}\\AppData\\${application.name}\\config\\}
Apple Mac OS-X{@code ${user.home}/Library/Preferences/${application.name}/}
Linux and Solaris{@code ${user.home}/.${application.name}/etc/}
* * Note: When the directory is retrieved using * this method, it is guaranteed that the directory exits. * * @return a path representing the application's OS-specific folder location * as a {@code java.nio.file.Path} object * * @see #getDirectory() * @see #getDataDirectory() * @see #getErrorDirectory() * @see #getLogDirectory() */ public Path getConfigDirectory() { Path config = null; OSId osId = getOSId(); switch (osId) { case OSX: // ${userHome}/Library/Preferences/${applicationId} config = Paths.get(getUserHome(), "Library", "Preferences", getApplicationId()); break; case WINDOWS: // APPDATA\${vendorId}\${applicationId}\config\ config = Paths.get(getDirectory().toString(), "config"); break; default: config = Paths.get(getDirectory().toString(), "etc"); } if (!config.toFile().exists()) { try { if (canWrite(config)) { Files.createDirectories(config); } } catch (IOException ioe) { checkLogger(); if (logger != null) { logger.log(Level.ERROR, "Unable to create application " + "configuration directory: " + config.toString(), ioe); } } } return config; } /** * A convenience method for getting a standard data directory in which to * store files from an application. * * For applications that wish to store their data in a separate directory * from any other files the application may create, this method will get a * directory named "data", with the application's home directory prepended * to it. The path returned is described in the table below, by OS. * * * * * * * * * * *
OS-Specific Application Data Folder Locations
OSApplication Data Folder Location
Microsoft Windows{@code ${user.home}\\AppData\\${application.name}\\data\\}
Apple Mac OS-X{@code ${user.home}/Library/Application Data/${application.name}/data/} *
Linux and Solaris{@code ${user.home}/.${application.name}/data/}
* * Note: When the directory is retrieved using * this method, it is guaranteed that the directory exits. * * @return a path representing the application's OS-specific folder location * as a {@code java.nio.file.Path} object * * @see #getDirectory() * @see #getConfigDirectory() * @see #getErrorDirectory() * @see #getLogDirectory() */ public Path getDataDirectory() { Path data = Paths.get(getDirectory().toString(), "data"); if (!data.toFile().exists()) { try { if (canWrite(data)) { Files.createDirectories(data); } } catch (IOException ioe) { logger.log(Level.ERROR, "Unable to create application data " + "directory: " + data.toString(), ioe); } } return data; } /** * A convenience method for getting a standard log file directory in which * applications may store log files. * * For applications that wish to store their log files in a separate * directory from any other files the application may create, this method * will get a directory named "var/log" for Linux applications, with the * application's home directory prepended to it. However, other operating * systems have very specific locations where they want log files created. * The path returned for each OS is described in the table below, by OS. * * * * * * * * * * *
OS-Specific Application Log Folder Locations
OSApplication Log Folder Location
Microsoft Windows{@code %SystemRoot%\\System32\\Config\\${application.name}\\}
Apple Mac OS-X{@code ${user.home}/Library/Logs/${application.name}/}
Linux and Solaris{@code /var/log/${application.name}} or * {@code ${user.home}/.${application.name}/var/log/}, if the system logs * directory is not writable. Typically, an application that is installed * system wide will have write access to the * {@code /var/log/${application.name}} folder. If a user installs the * application just for his/herself, then the logs will be stored in the * {@code ${user.home}/.${application.name}/var/log/} folder.
* * Note: When the directory is retrieved using * this method, it is guaranteed that the directory exits. * * @return a path representing the application's OS-specific folder location * as a {@code java.nio.file.Path} object * * @see #getDirectory() * @see #getConfigDirectory() * @see #getDataDirectory() * @see #getErrorDirectory() */ public Path getLogDirectory() { OSId osId = getOSId(); Path log = null; switch (osId) { case WINDOWS: // ${SystemRoot}\System32\config\${applicationId} log = Paths.get(System.getenv("%SystemRoot%"), "System32", "config", getApplicationId()); boolean readOnly = true; try { readOnly = canWrite(log); } catch (IOException ioe) { checkLogger(); if (logger != null) { logger.log(Level.ERROR, "Unable to determine if user " + "can write to the system log directory. " + "Playing it safe and setting the log directory " + "in the user's application directory.", ioe); } } if (readOnly) { // Alternate log file location: // ${APPDATA}\${applicationId}\var\log\ log = Paths.get(getDirectory().toString(), "var", "log"); } break; case OSX: // ${userHome}/Library/Logs/${applicationId}/ log = Paths.get(System.getProperty("user.home"), "Library", "Logs", getApplicationId()); break; default: // /var/log/${applicationId} log = Paths.get("/var/log/", getApplicationId()); boolean canWrite = false; try { canWrite = canWrite(log); } catch (IOException ioe) { checkLogger(); if (logger != null) { logger.log(Level.ERROR, "Unable to determine if " + "user can write to the system log directory. Playing " + "it safe and setting log directory in the user's " + "application directory.", ioe); } } if (!canWrite) { // ${userHome}/.${applicationId}/var/log/ log = Paths.get(getDirectory().toString(), "var", "log"); } break; } if (!Files.exists(log)) { try { if (canWrite(log)) { Files.createDirectories(log); } } catch (IOException ioe) { checkLogger(); if (logger != null) { logger.log(Level.ERROR, "Unable to create the log " + "directory for the application.", ioe); } } } return log; } /** * A convenience method for getting a standard error log file directory in * which applications may store error log files. * * For applications that wish to store their error log files in a separate * directory from any other files the application may create, this method * will get a directory named "var/err" for Linux applications, with the * application's home directory prepended to it. However, other operating * systems have very specific locations where they want log files created. * The path returned for each OS is described in the table below, by OS. * * * * * * * * * * *
OS-Specific Application Error Log Folder Locations
OSApplication Error Log Folder Location
Microsoft Windows{@code %SystemRoot%\\System32\\Config\\${application.name}\\err\\}
Apple Mac OS-X{@code ${user.home}/Library/Logs/${application.name}/err/}
Linux and Solaris{@code /var/log/${application.name}/err/} or * {@code ${user.home}/.${application.name}/var/log/err/}, if the system * logs directory is not writable. Typically, an application that is * installed system wide will have write access to the * {@code /var/log/${application.name}} folder. If a user installs the * application just for his/herself, then the logs will be stored in the * {@code ${user.home}/.${application.name}/var/log/err/} folder.
* * Note: When the directory is retrieved using * this method, it is guaranteed that the directory exits. * * @return a pat representing the application's OS-specific folder location * as a {@code java.nio.file.Path} object * * @see #getDirectory() * @see #getConfigDirectory() * @see #getDataDirectory() * @see #getLogDirectory() */ public Path getErrorDirectory() { Path error = Paths.get(getLogDirectory().toString(), "..", "err"); if (!Files.exists(error)) { try { if (canWrite(error)) { Files.createDirectories(error); } } catch (IOException ioe) { checkLogger(); if (logger != null) { logger.log(Level.ERROR, "Unable to create the error log " + "directory.", ioe); } } } return error; } /** * Loads data from a session storage XML file. The data loaded is returned * as a generic {@link java.lang.Object Object} and it is up to the calling * class to cast it to the appropriate type. * * @param location the {@code StorageLocations} enum constant for the * location of the file to get * @param fileName the name (or complete path for {@code * StorageLocations.FILE_SYSTEM} locations) of the file to get * * @return a generic {@code Object} containing the loaded data * * @throws IOException in the event that an input/output error occurs, * including if the current user does not have read or * write permissions to the storage location */ public Object load(StorageLocations location, String fileName) throws IOException { checkFileName(fileName); InputStream ist = openInputFile(location, fileName); AbortExceptionListener el = new AbortExceptionListener(); try (XMLDecoder d = new XMLDecoder(ist)) { d.setExceptionListener(el); Object bean = d.readObject(); if (el.exception != null) { throw new IOException("Failed to load \"" + fileName + "\"", el.exception); } return bean; } } /** * Saves the data of the specified {@code bean} to a session storage XML * file. * * @param location the {@code StorageLocations} enum constant for the * location of the file to get * @param bean the {@code Object} whose data is to be saved * @param fileName the name (or complete path for {@code * StorageLocations.FILE_SYSTEM} locations) of the file to get * * @throws IOException in the event that an input/output error occurs, * including if the current user does not have read or * write permissions to the storage location */ public void save(StorageLocations location, Object bean, final String fileName) throws IOException { checkFileName(fileName); if (bean == null) { throw new IllegalArgumentException("{@code null} bean"); } AbortExceptionListener el = new AbortExceptionListener(); XMLEncoder e = null; ByteArrayOutputStream bst = new ByteArrayOutputStream(); try { e = new XMLEncoder(bst); e.setExceptionListener(el); e.writeObject(bean); } finally { if (e != null) { e.close(); } } if (el.exception != null) { throw new IOException("Failed to save \"" + fileName + "\"", el.exception); } try (OutputStream ost = openOutputFile(location, fileName)) { ost.write(bst.toByteArray()); } catch (NullPointerException npe) { String msg = String.format("Trapped NPE in LocalStorage.save() on " + "StorageLocation \"%s\" with bean \"%s\" and fileName \"" + "%s\".", location, bean, fileName); System.err.println(msg); } } private boolean canWrite(Path path) throws IOException { boolean readOnly = false; Path parent = null; if (!Files.isDirectory(path)) { parent = path.getParent(); } try { if (parent != null) { UserPrincipal owner = Files.getOwner(parent); if (System.getProperty("user.name").equals(owner.getName())) { readOnly = false; } switch (getOSId()) { case WINDOWS: readOnly = (Boolean) Files.getAttribute(parent, "dos:readonly"); break; default: PosixFileAttributeView view = Files.getFileAttributeView( parent, PosixFileAttributeView.class); PosixFileAttributes attribs = view.readAttributes(); Set perms = attribs.permissions(); String rwxrwxrwx = PosixFilePermissions.toString(perms); if (owner.getName().equals(System.getProperty("user.name"))) { readOnly = rwxrwxrwx.charAt(1) != 'w'; } else { readOnly = rwxrwxrwx.charAt(7) != 'w'; } } } else { UserPrincipal owner = Files.getOwner(path); if (System.getProperty("user.name").equals(owner.getName())) { readOnly = false; } switch (getOSId()) { case WINDOWS: readOnly = (Boolean) Files.getAttribute(path.getParent(), "dos:readonly"); break; default: PosixFileAttributeView view = Files.getFileAttributeView( path.getParent(), PosixFileAttributeView.class); PosixFileAttributes attribs = view.readAttributes(); Set perms = attribs.permissions(); String rwxrwxrwx = PosixFilePermissions.toString(perms); if (owner.getName().equals(System.getProperty("user.name"))) { readOnly = rwxrwxrwx.charAt(1) != 'w'; } else { readOnly = rwxrwxrwx.charAt(7) != 'w'; } break; } } } catch (IOException ex) { if (ex.getMessage().contains("var")) { // We are probably looking at the var directory, so let's see if //+ the user can write the parent. canWrite(path.getParent()); } else { throw ex; } } return !readOnly; } private String getUserHome() { String userHome = null; try { userHome = System.getProperty("user.home"); } catch (SecurityException ignore) { } return userHome == null ? getVendorId() : userHome; } private Path getDirectoryPath(StorageLocations location) { Path path = null; switch (location) { case CONFIG_DIR -> path = getConfigDirectory(); case DATA_DIR -> path = getDataDirectory(); case ERROR_DIR -> path = getErrorDirectory(); case LOG_DIR -> path = getLogDirectory(); case APP_DIR -> path = getDirectory(); default -> { path = Paths.get(getUserHome()); } } // StorageLocations.FILE_SYSTEM // TODO: Figure this out what, if anything, to do as default action. if (path == null) { String msg = String.format("Trapped NPE in LocalStorage.getDirectoryPath() " + "for StorageLocations \"%s\".", location); } return path; } private String getId(String key, String def) { ResourceMap appResourceMap = getContext().getResourceMap(); String id = appResourceMap.getString(key); if (id == null) { checkLogger(); if (logger != null) { logger.log(Level.WARNING, "unspecified resource " + key + " using " + def); } id = def; } else if (id.trim().length() == 0) { checkLogger(); if (logger != null) { logger.log(Level.WARNING, "empty resource " + key + " using " + def); } id = def; } return id; } private String getApplicationId() { return getId("Application.id", getContext().getApplicationClass().getSimpleName()); } private String getVendorId() { return getId("Application.vendorId", "UnknownApplicationVendor"); } private OSId getOSId() { OSId id = OSId.UNIX; String osName = System.getProperty("os.name"); if (osName != null) { if (osName.toLowerCase().startsWith("mac os x")) { id = OSId.OSX; } else if (osName.contains("Windows")) { id = OSId.WINDOWS; } } return id; } private static class AbortExceptionListener implements ExceptionListener { public Exception exception = null; @Override public void exceptionThrown(Exception e) { if (exception == null) { exception = e; } } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy