Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* 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:
*
*
* @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.
*
*
* 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.
*
*
* 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.
*
*
* 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.
*
{@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.
*
{@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;
}
}
}
}