[New] Initial commit of the utils
This is initial commit includes * a RMI handler which can be used for easy up- and downloading of files over RMI * a FileHelper * a Log4jConfigurator * a StringHelper * a SystemHelper * a ObjectFilter which is used for caching operations to objects. These objects must implement ITransactionObject
This commit is contained in:
parent
7017a8cfa0
commit
82368adaff
|
@ -1,4 +1,3 @@
|
|||
ch.eitchnet.java.utils
|
||||
======================
|
||||
|
||||
Generic Java XML persistence layer. Implemented to be light-weight and simple to use
|
||||
Java Utilites which ease daily work when programming in the Java language
|
||||
|
|
Binary file not shown.
|
@ -0,0 +1,50 @@
|
|||
package ch.eitchnet.rmi;
|
||||
|
||||
import java.rmi.RemoteException;
|
||||
|
||||
/**
|
||||
* @author Robert von Burg <eitch@eitchnet.ch>
|
||||
*
|
||||
*/
|
||||
public interface RMIFileClient {
|
||||
|
||||
/**
|
||||
* Remote method with which a client can push parts of files to the server. It is up to the client to send as many
|
||||
* parts as needed, the server will write the parts to the associated file
|
||||
*
|
||||
* @param filePart
|
||||
* the part of the file
|
||||
* @throws RemoteException
|
||||
* if something goes wrong with the remote call
|
||||
*/
|
||||
public void uploadFilePart(RmiFilePart filePart) throws RemoteException;
|
||||
|
||||
/**
|
||||
* Remote method with which a client can delete files from the server. It only deletes single files if they exist
|
||||
*
|
||||
* @param fileDeletion
|
||||
* the {@link RmiFileDeletion} defining the deletion request
|
||||
*
|
||||
* @return true if the file was deleted, false if the file did not exist
|
||||
*
|
||||
* @throws RemoteException
|
||||
* if something goes wrong with the remote call
|
||||
*/
|
||||
public boolean deleteFile(RmiFileDeletion fileDeletion) throws RemoteException;
|
||||
|
||||
/**
|
||||
* Remote method which a client can request part of a file. The server will fill the given {@link RmiFilePart} with
|
||||
* a byte array of the file, with bytes from the file, respecting the desired offset. It is up to the client to call
|
||||
* this method multiple times for the entire file. It is a decision of the concrete implementation how much data is
|
||||
* returned in each part, the client may pass a request, but this is not definitive
|
||||
*
|
||||
* @param filePart
|
||||
* the part of the file
|
||||
*
|
||||
* @return the same file part, yet with the part of the file requested as a byte array
|
||||
*
|
||||
* @throws RemoteException
|
||||
* if something goes wrong with the remote call
|
||||
*/
|
||||
public RmiFilePart requestFile(RmiFilePart filePart) throws RemoteException;
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
package ch.eitchnet.rmi;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* @author Robert von Burg <eitch@eitchnet.ch>
|
||||
*/
|
||||
public class RmiFileDeletion implements Serializable {
|
||||
|
||||
//
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private String fileName;
|
||||
private String fileType;
|
||||
|
||||
/**
|
||||
* @param fileName
|
||||
* the name of the file to be deleted. This is either just the name, or a path relative to the type
|
||||
* @param fileType
|
||||
* the type of file to delete. This defines in which path the file resides
|
||||
*/
|
||||
public RmiFileDeletion(String fileName, String fileType) {
|
||||
this.fileName = fileName;
|
||||
this.fileType = fileType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the fileType
|
||||
*/
|
||||
public String getFileType() {
|
||||
return fileType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the fileName
|
||||
*/
|
||||
public String getFileName() {
|
||||
return fileName;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,249 @@
|
|||
package ch.eitchnet.rmi;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import ch.eitchnet.utils.helper.FileHelper;
|
||||
import ch.eitchnet.utils.helper.StringHelper;
|
||||
|
||||
/**
|
||||
* This class handles remote requests of clients to upload or download a file. Uploading a file is done by calling
|
||||
* {@link #handleFilePart(RmiFilePart)} and the downloading a file is done by calling {@link #requestFile(RmiFilePart)}
|
||||
*
|
||||
* @author Robert von Burg <eitch@eitchnet.ch>
|
||||
*/
|
||||
public class RmiFileHandler {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(RmiFileHandler.class);
|
||||
|
||||
/**
|
||||
* DEF_PART_SIZE = default part size which is set to 1048576 bytes (1 MiB)
|
||||
*/
|
||||
public static final int MAX_PART_SIZE = 1048576;
|
||||
|
||||
private String basePath;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public RmiFileHandler(String basePath) {
|
||||
|
||||
File basePathF = new File(basePath);
|
||||
if (!basePathF.exists())
|
||||
throw new RuntimeException("Base Path does not exist " + basePathF.getAbsolutePath());
|
||||
if (!basePathF.canWrite())
|
||||
throw new RuntimeException("Can not write to base path " + basePathF.getAbsolutePath());
|
||||
|
||||
this.basePath = basePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method which a client can request part of a file. The server will fill the given {@link RmiFilePart} with a byte
|
||||
* array of the file, with bytes from the file, respecting the desired offset. It is up to the client to call this
|
||||
* method multiple times for the entire file. It is a decision of the concrete implementation how much data is
|
||||
* returned in each part, the client may pass a request, but this is not definitive
|
||||
*
|
||||
* @param filePart
|
||||
* the part of the file
|
||||
*/
|
||||
public RmiFilePart requestFile(RmiFilePart filePart) {
|
||||
|
||||
// validate file name is legal
|
||||
String fileName = filePart.getFileName();
|
||||
validateFileName(fileName);
|
||||
|
||||
// validate type is legal
|
||||
String fileType = filePart.getFileType();
|
||||
validateFileType(fileType);
|
||||
|
||||
// evaluate the path where the file should reside
|
||||
File file = new File(basePath + "/" + fileType, filePart.getFileName());
|
||||
|
||||
// now evaluate the file exists
|
||||
if (!file.canRead()) {
|
||||
throw new RuntimeException("The file " + fileName
|
||||
+ " could not be found in the location for files of type " + fileType);
|
||||
}
|
||||
|
||||
// if this is the start of the file, then prepare the file part
|
||||
long fileSize = file.length();
|
||||
if (filePart.getPartOffset() == 0) {
|
||||
|
||||
// set the file length
|
||||
filePart.setFileLength(fileSize);
|
||||
|
||||
// set the SHA256 of the file
|
||||
filePart.setFileHash(StringHelper.getHexString(FileHelper.hashFileSha256(file)));
|
||||
}
|
||||
|
||||
// variables defining the part of the file we're going to return
|
||||
long requestOffset = filePart.getPartOffset();
|
||||
int requestSize = filePart.getPartLength();
|
||||
if (requestSize > MAX_PART_SIZE) {
|
||||
throw new RuntimeException("The requested part size " + requestSize + " is greater than the allowed "
|
||||
+ MAX_PART_SIZE);
|
||||
}
|
||||
|
||||
// validate lengths and offsets
|
||||
if (filePart.getFileLength() != fileSize) {
|
||||
throw new RuntimeException("The part request has a file size " + filePart.getFileLength()
|
||||
+ ", but the file is actually " + fileSize);
|
||||
} else if (requestOffset > fileSize) {
|
||||
throw new RuntimeException("The requested file part offset " + requestOffset
|
||||
+ " is greater than the size of the file " + fileSize);
|
||||
}
|
||||
// Otherwise make sure the offset + request length is not larger than the actual file size.
|
||||
// If it is then this is the end part
|
||||
else if (requestOffset + requestSize >= fileSize) {
|
||||
|
||||
long remaining = fileSize - requestOffset;
|
||||
|
||||
// update request size to last part of file
|
||||
long l = Math.min(requestSize, remaining);
|
||||
|
||||
// this is a fail safe
|
||||
if (l > MAX_PART_SIZE)
|
||||
throw new RuntimeException("Something went wrong. Min of requestSize and remaining is > MAX_PART_SIZE!");
|
||||
|
||||
// this is the size of the array we want to return
|
||||
requestSize = (int) l;
|
||||
filePart.setPartLength(requestSize);
|
||||
filePart.setLastPart(true);
|
||||
}
|
||||
|
||||
// now read the part of the file and set it as bytes for the file part
|
||||
FileInputStream fin = null;
|
||||
try {
|
||||
|
||||
// position the stream
|
||||
fin = new FileInputStream(file);
|
||||
long skip = fin.skip(requestOffset);
|
||||
if (skip != requestOffset)
|
||||
throw new IOException("Asked to skip " + requestOffset + " but only skipped " + skip);
|
||||
|
||||
// read the data
|
||||
byte[] bytes = new byte[requestSize];
|
||||
int read = fin.read(bytes);
|
||||
if (read != requestSize)
|
||||
throw new IOException("Asked to read " + requestSize + " but only read " + read);
|
||||
|
||||
// set the return result
|
||||
filePart.setPartBytes(bytes);
|
||||
|
||||
} catch (FileNotFoundException e) {
|
||||
throw new RuntimeException("The file " + fileName
|
||||
+ " could not be found in the location for files of type " + fileType);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("There was an error while reading from the file " + fileName);
|
||||
} finally {
|
||||
if (fin != null) {
|
||||
try {
|
||||
fin.close();
|
||||
} catch (IOException e) {
|
||||
logger.error("Error while closing FileInputStream: " + e.getLocalizedMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// we are returning the same object as the user gave us, just edited
|
||||
return filePart;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method with which a client can push parts of files to the server. It is up to the client to send as many parts as
|
||||
* needed, the server will write the parts to the associated file
|
||||
*
|
||||
* @param filePart
|
||||
* the part of the file
|
||||
*/
|
||||
public void handleFilePart(RmiFilePart filePart) {
|
||||
|
||||
// validate file name is legal
|
||||
String fileName = filePart.getFileName();
|
||||
validateFileName(fileName);
|
||||
|
||||
// validate type is legal
|
||||
String fileType = filePart.getFileType();
|
||||
validateFileType(fileType);
|
||||
|
||||
// evaluate the path where the file should reside
|
||||
File dstFile = new File(basePath + "/" + fileType, filePart.getFileName());
|
||||
|
||||
// if the file already exists, then this may not be a start part
|
||||
if (filePart.getPartOffset() == 0 && dstFile.exists()) {
|
||||
throw new RuntimeException("The file " + fileName + " already exist for type " + fileType);
|
||||
}
|
||||
|
||||
// write the part
|
||||
FileHelper.appendFilePart(dstFile, filePart.getPartBytes());
|
||||
|
||||
// if this is the last part, then validate the hashes
|
||||
if (filePart.isLastPart()) {
|
||||
String dstFileHash = StringHelper.getHexString(FileHelper.hashFileSha256(dstFile));
|
||||
if (!dstFileHash.equals(filePart.getFileHash())) {
|
||||
throw new RuntimeException("Uploading the file " + filePart.getFileName()
|
||||
+ " failed because the hashes don't match. Expected: " + filePart.getFileHash() + " / Actual: "
|
||||
+ dstFileHash);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method with which a client can delete files from the server. It only deletes single files if they exist
|
||||
*
|
||||
* @param fileDeletion
|
||||
* the {@link RmiFileDeletion} defining the deletion request
|
||||
*
|
||||
* @return true if the file was deleted, false if the file did not exist
|
||||
*
|
||||
*/
|
||||
public boolean deleteFile(RmiFileDeletion fileDeletion) {
|
||||
|
||||
// validate file name is legal
|
||||
String fileName = fileDeletion.getFileName();
|
||||
validateFileName(fileName);
|
||||
|
||||
// validate type is legal
|
||||
String fileType = fileDeletion.getFileType();
|
||||
validateFileType(fileType);
|
||||
|
||||
// evaluate the path where the file should reside
|
||||
File fileToDelete = new File(basePath + "/" + fileType, fileDeletion.getFileName());
|
||||
|
||||
// delete the file
|
||||
return FileHelper.deleteFiles(new File[] { fileToDelete }, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that the file name is legal, i.e. not empty or contains references up the tree
|
||||
*
|
||||
* @param fileName
|
||||
*/
|
||||
private void validateFileName(String fileName) {
|
||||
|
||||
if (fileName == null || fileName.isEmpty()) {
|
||||
throw new RuntimeException("The file name was not given! Can not find a file without a name!");
|
||||
} else if (fileName.contains("/")) {
|
||||
throw new RuntimeException(
|
||||
"The given file name contains illegal characters. The file name may not contain slashes!");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that the file type is legal, i.e. not empty or contains references up the tree
|
||||
*
|
||||
* @param fileType
|
||||
*/
|
||||
private void validateFileType(String fileType) {
|
||||
if (fileType == null || fileType.isEmpty()) {
|
||||
throw new RuntimeException("The file type was not given! Can not find a file without a type!");
|
||||
} else if (fileType.contains("/")) {
|
||||
throw new RuntimeException(
|
||||
"The given file type contains illegal characters. The file type may not contain slashes!");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,147 @@
|
|||
package ch.eitchnet.rmi;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* @author Robert von Burg <eitch@eitchnet.ch>
|
||||
*/
|
||||
public class RmiFilePart implements Serializable {
|
||||
|
||||
//
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private String fileName;
|
||||
private long fileLength;
|
||||
private String fileHash;
|
||||
private String fileType;
|
||||
|
||||
private long partOffset;
|
||||
private int partLength;
|
||||
private byte[] partBytes;
|
||||
private boolean lastPart;
|
||||
|
||||
/**
|
||||
* @param fileName
|
||||
* the name of the file being referenced. This is either just the name, or a path relative to the type
|
||||
* @param fileType
|
||||
* defines the type of file being uploaded or retrieved. This defines in which path the file resides
|
||||
*/
|
||||
public RmiFilePart(String fileName, String fileType) {
|
||||
|
||||
if (fileName == null || fileName.isEmpty())
|
||||
throw new RuntimeException("fileName may not be empty!");
|
||||
if (fileType == null || fileType.isEmpty())
|
||||
throw new RuntimeException("fileType may not be empty!");
|
||||
|
||||
this.fileName = fileName;
|
||||
this.fileType = fileType;
|
||||
|
||||
this.partOffset = 0;
|
||||
this.partLength = RmiFileHandler.MAX_PART_SIZE;
|
||||
this.partBytes = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the fileLength
|
||||
*/
|
||||
public long getFileLength() {
|
||||
return fileLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param fileLength
|
||||
* the fileLength to set
|
||||
*/
|
||||
public void setFileLength(long fileLength) {
|
||||
this.fileLength = fileLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the fileHash
|
||||
*/
|
||||
public String getFileHash() {
|
||||
return fileHash;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param fileHash
|
||||
* the fileHash to set
|
||||
*/
|
||||
public void setFileHash(String fileHash) {
|
||||
this.fileHash = fileHash;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the fileType
|
||||
*/
|
||||
public String getFileType() {
|
||||
return fileType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the partOffset
|
||||
*/
|
||||
public long getPartOffset() {
|
||||
return partOffset;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param partOffset
|
||||
* the partOffset to set
|
||||
*/
|
||||
public void setPartOffset(long partOffset) {
|
||||
this.partOffset = partOffset;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the partLength
|
||||
*/
|
||||
public int getPartLength() {
|
||||
return partLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param partLength
|
||||
* the partLength to set
|
||||
*/
|
||||
public void setPartLength(int partLength) {
|
||||
this.partLength = partLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the partBytes
|
||||
*/
|
||||
public byte[] getPartBytes() {
|
||||
return partBytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param partBytes
|
||||
* the partBytes to set
|
||||
*/
|
||||
public void setPartBytes(byte[] partBytes) {
|
||||
this.partBytes = partBytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the lastPart
|
||||
*/
|
||||
public boolean isLastPart() {
|
||||
return lastPart;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param lastPart
|
||||
* the lastPart to set
|
||||
*/
|
||||
public void setLastPart(boolean lastPart) {
|
||||
this.lastPart = lastPart;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the fileName
|
||||
*/
|
||||
public String getFileName() {
|
||||
return fileName;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,221 @@
|
|||
/*
|
||||
* Copyright (c) 2010 - 2011
|
||||
*
|
||||
* Apixxo AG
|
||||
* Hauptgasse 25
|
||||
* 4600 Olten
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
*/
|
||||
package ch.eitchnet.rmi;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.rmi.RemoteException;
|
||||
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import ch.eitchnet.utils.helper.FileHelper;
|
||||
import ch.eitchnet.utils.helper.StringHelper;
|
||||
|
||||
/**
|
||||
* @author Robert von Burg <eitch@eitchnet.ch>
|
||||
*
|
||||
*/
|
||||
public class RmiHelper {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(RmiHelper.class);
|
||||
|
||||
/**
|
||||
* @param rmiFileClient
|
||||
* @param filePart
|
||||
* @param dstFile
|
||||
*/
|
||||
public static void downloadFile(RMIFileClient rmiFileClient, RmiFilePart filePart, File dstFile) {
|
||||
|
||||
// here we don't overwrite, the caller must make sure the destination file does not exist
|
||||
if (dstFile.exists())
|
||||
throw new RuntimeException("The destination file " + dstFile.getAbsolutePath()
|
||||
+ " already exists. Delete it first, if you want to overwrite it!");
|
||||
|
||||
try {
|
||||
int loops = 0;
|
||||
int startLength = filePart.getPartLength();
|
||||
while (true) {
|
||||
loops += 1;
|
||||
|
||||
// get the next part
|
||||
filePart = rmiFileClient.requestFile(filePart);
|
||||
|
||||
// validate length of data
|
||||
if (filePart.getPartLength() != filePart.getPartBytes().length)
|
||||
throw new RuntimeException("Invalid FilePart. Part length is not as long as the bytes passed "
|
||||
+ filePart.getPartLength() + " / " + filePart.getPartBytes().length);
|
||||
|
||||
// validate offset is size of file
|
||||
if (filePart.getPartOffset() != dstFile.length()) {
|
||||
throw new RuntimeException("The part offset $offset is not at the end of the file "
|
||||
+ filePart.getPartOffset() + " / " + dstFile.length());
|
||||
}
|
||||
|
||||
// append the part
|
||||
FileHelper.appendFilePart(dstFile, filePart.getPartBytes());
|
||||
|
||||
// update the offset
|
||||
filePart.setPartOffset(filePart.getPartOffset() + filePart.getPartBytes().length);
|
||||
|
||||
// break if the offset is past the length of the file
|
||||
if (filePart.getPartOffset() >= filePart.getFileLength())
|
||||
break;
|
||||
}
|
||||
logger.info(filePart.getFileType() + ": " + filePart.getFileName() + ": Requested " + loops
|
||||
+ " parts. StartSize: " + startLength + " EndSize: " + filePart.getPartLength());
|
||||
|
||||
// validate that the offset is at the end of the file
|
||||
if (filePart.getPartOffset() != filePart.getFileLength()) {
|
||||
throw new RuntimeException("Offset " + filePart.getPartOffset() + " is not at file length "
|
||||
+ filePart.getFileLength() + " after reading all the file parts!");
|
||||
}
|
||||
|
||||
// now validate hashes
|
||||
String dstFileHash = StringHelper.getHexString(FileHelper.hashFileSha256(dstFile));
|
||||
if (!dstFileHash.equals(filePart.getFileHash())) {
|
||||
throw new RuntimeException("Downloading the file " + filePart.getFileName()
|
||||
+ " failed because the hashes don't match. Expected: " + filePart.getFileHash() + " / Actual: "
|
||||
+ dstFileHash);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
if (e instanceof RuntimeException)
|
||||
throw (RuntimeException) e;
|
||||
throw new RuntimeException("Downloading the file " + filePart.getFileName()
|
||||
+ " failed because of an underlying exception " + e.getLocalizedMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param rmiFileClient
|
||||
* @param srcFile
|
||||
* @param fileType
|
||||
*/
|
||||
public static void uploadFile(RMIFileClient rmiFileClient, File srcFile, String fileType) {
|
||||
|
||||
// make sure the source file exists
|
||||
if (!srcFile.canRead())
|
||||
throw new RuntimeException("The source file does not exist at " + srcFile.getAbsolutePath());
|
||||
|
||||
BufferedInputStream inputStream = null;
|
||||
try {
|
||||
|
||||
// get the size of the file
|
||||
long fileLength = srcFile.length();
|
||||
String fileHash = StringHelper.getHexString(FileHelper.hashFileSha256(srcFile));
|
||||
|
||||
// create the file part to send
|
||||
RmiFilePart filePart = new RmiFilePart(srcFile.getName(), fileType);
|
||||
filePart.setFileLength(fileLength);
|
||||
filePart.setFileHash(fileHash);
|
||||
|
||||
// define the normal size of the parts we're sending. The last part will naturally have a different size
|
||||
int partLength;
|
||||
if (fileLength > RmiFileHandler.MAX_PART_SIZE)
|
||||
partLength = RmiFileHandler.MAX_PART_SIZE;
|
||||
else
|
||||
partLength = (int) fileLength;
|
||||
|
||||
// this is the byte array of data we're sending each time
|
||||
byte[] bytes = new byte[partLength];
|
||||
|
||||
// open the stream to the file
|
||||
inputStream = new BufferedInputStream(new FileInputStream(srcFile));
|
||||
|
||||
int read = 0;
|
||||
int offset = 0;
|
||||
|
||||
// loop by reading the number of bytes needed for each part
|
||||
int loops = 0;
|
||||
int startLength = partLength;
|
||||
while (true) {
|
||||
loops += 1;
|
||||
|
||||
// read the bytes into the array
|
||||
read = inputStream.read(bytes);
|
||||
|
||||
// validate we read the expected number of bytes
|
||||
if (read == -1)
|
||||
throw new IOException("Something went wrong while reading the bytes as -1 was returned!");
|
||||
if (read != bytes.length) {
|
||||
throw new IOException(
|
||||
"Something went wrong while reading the bytes as the wrong number of bytes were read. Expected "
|
||||
+ bytes.length + " Actual: " + read);
|
||||
}
|
||||
|
||||
// set the fields on the FilePart
|
||||
filePart.setPartBytes(bytes);
|
||||
filePart.setPartOffset(offset);
|
||||
filePart.setPartLength(bytes.length);
|
||||
|
||||
// and if this is the last part, then also set that
|
||||
int nextOffset = offset + bytes.length;
|
||||
if (nextOffset == fileLength)
|
||||
filePart.setLastPart(true);
|
||||
|
||||
// now send the part to the server
|
||||
rmiFileClient.uploadFilePart(filePart);
|
||||
|
||||
// if this was the last part, then break
|
||||
if (filePart.isLastPart())
|
||||
break;
|
||||
|
||||
// otherwise update the offset for the next part by also making sure the next part is not larger than
|
||||
// the last part of the file
|
||||
if (nextOffset + bytes.length > fileLength) {
|
||||
long remaining = fileLength - nextOffset;
|
||||
if (remaining > RmiFileHandler.MAX_PART_SIZE)
|
||||
throw new RuntimeException("Something went wrong as the remaining part " + remaining
|
||||
+ " is larger than MAX_PART_SIZE " + RmiFileHandler.MAX_PART_SIZE + "!");
|
||||
partLength = (int) remaining;
|
||||
bytes = new byte[partLength];
|
||||
}
|
||||
|
||||
// and save the offset for the next loop
|
||||
offset = nextOffset;
|
||||
}
|
||||
|
||||
logger.info(filePart.getFileType() + ": " + filePart.getFileName() + ": Sent " + loops
|
||||
+ " parts. StartSize: " + startLength + " EndSize: " + filePart.getPartLength());
|
||||
|
||||
} catch (Exception e) {
|
||||
if (e instanceof RuntimeException)
|
||||
throw (RuntimeException) e;
|
||||
throw new RuntimeException("Uploading the file " + srcFile.getAbsolutePath()
|
||||
+ " failed because of an underlying exception " + e.getLocalizedMessage());
|
||||
} finally {
|
||||
if (inputStream != null) {
|
||||
try {
|
||||
inputStream.close();
|
||||
} catch (IOException e) {
|
||||
logger.error("Exception while closing FileInputStream " + e.getLocalizedMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param rmiFileClient
|
||||
* @param fileDeletion
|
||||
* @param dstFile
|
||||
*/
|
||||
public static void deleteFile(RMIFileClient rmiFileClient, RmiFileDeletion fileDeletion, File dstFile) {
|
||||
|
||||
try {
|
||||
rmiFileClient.deleteFile(fileDeletion);
|
||||
} catch (RemoteException e) {
|
||||
throw new RuntimeException("Deleting the file " + fileDeletion.getFileName()
|
||||
+ " failed because of an underlying exception " + e.getLocalizedMessage());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,471 @@
|
|||
package ch.eitchnet.utils.helper;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FileReader;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
/**
|
||||
* Helper class for dealing with files
|
||||
*
|
||||
* @author Robert von Burg <eitch@eitchnet.ch>
|
||||
*/
|
||||
public class FileHelper {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(FileHelper.class);
|
||||
|
||||
/**
|
||||
* Reads the contents of a file into a string. Note, no encoding is checked. It is expected to be UTF-8
|
||||
*
|
||||
* @param file
|
||||
* the file to read
|
||||
* @return the contents of a file as a string
|
||||
*/
|
||||
public static final String readFileToString(File file) {
|
||||
try {
|
||||
|
||||
BufferedReader bufferedReader = new BufferedReader(new FileReader(file));
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
String line;
|
||||
|
||||
while ((line = bufferedReader.readLine()) != null) {
|
||||
sb.append(line + "\n");
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
|
||||
} catch (FileNotFoundException e) {
|
||||
throw new RuntimeException("Filed does not exist " + file.getAbsolutePath());
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Could not read file " + file.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the string to dstFile
|
||||
*
|
||||
* @param dstFile
|
||||
* the file to write to
|
||||
*/
|
||||
public static final void writeStringToFile(String string, File dstFile) {
|
||||
try {
|
||||
|
||||
BufferedWriter bufferedwriter = new BufferedWriter(new FileWriter(dstFile));
|
||||
bufferedwriter.write(string);
|
||||
|
||||
bufferedwriter.close();
|
||||
|
||||
} catch (FileNotFoundException e) {
|
||||
throw new RuntimeException("Filed does not exist " + dstFile.getAbsolutePath());
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Could not write to file " + dstFile.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes files recursively. No question asked, but logging is done in case of problems
|
||||
*
|
||||
* @param file
|
||||
* the file to delete
|
||||
* @param log
|
||||
* @return true if all went well, and false if it did not work. The log will contain the problems encountered
|
||||
*/
|
||||
public final static boolean deleteFile(File file, boolean log) {
|
||||
return deleteFiles(new File[] { file }, log);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes files recursively. No question asked, but logging is done in case of problems
|
||||
*
|
||||
* @param files
|
||||
* the files to delete
|
||||
* @param log
|
||||
* @return true if all went well, and false if it did not work. The log will contain the problems encountered
|
||||
*/
|
||||
public final static boolean deleteFiles(File[] files, boolean log) {
|
||||
|
||||
boolean worked = true;
|
||||
for (int i = 0; i < files.length; i++) {
|
||||
File file = files[i];
|
||||
if (file.isDirectory()) {
|
||||
boolean done = deleteFiles(file.listFiles(), log);
|
||||
if (!done) {
|
||||
worked = false;
|
||||
logger.warn("Could not empty the directory: " + file.getAbsolutePath());
|
||||
} else {
|
||||
done = file.delete();
|
||||
if (done) {
|
||||
if (log)
|
||||
logger.info("Deleted DIR " + file.getAbsolutePath());
|
||||
} else {
|
||||
worked = false;
|
||||
logger.warn("Could not delete the directory: " + file.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
boolean done = file.delete();
|
||||
if (done) {
|
||||
if (log)
|
||||
logger.info("Deleted FILE " + file.getAbsolutePath());
|
||||
} else {
|
||||
worked = false;
|
||||
logger.warn(("Could not delete the file: " + file.getAbsolutePath()));
|
||||
}
|
||||
}
|
||||
}
|
||||
return worked;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy a {@link File} The renameTo method does not allow action across NFS mounted filesystems this method is the
|
||||
* workaround
|
||||
*
|
||||
* @param fromFile
|
||||
* The existing File
|
||||
* @param toFile
|
||||
* The new File
|
||||
* @param checksum
|
||||
* if true, then a MD5 checksum is made to validate copying
|
||||
* @return <b>true</b> if and only if the renaming succeeded; <b>false</b> otherwise
|
||||
*/
|
||||
public final static boolean copy(File fromFile, File toFile, boolean checksum) {
|
||||
|
||||
BufferedInputStream inBuffer = null;
|
||||
BufferedOutputStream outBuffer = null;
|
||||
try {
|
||||
|
||||
inBuffer = new BufferedInputStream(new FileInputStream(fromFile));
|
||||
outBuffer = new BufferedOutputStream(new FileOutputStream(toFile));
|
||||
|
||||
int theByte = 0;
|
||||
|
||||
while ((theByte = inBuffer.read()) > -1) {
|
||||
outBuffer.write(theByte);
|
||||
}
|
||||
|
||||
inBuffer.close();
|
||||
outBuffer.flush();
|
||||
outBuffer.close();
|
||||
|
||||
if (checksum) {
|
||||
String fromFileMD5 = StringHelper.getHexString(FileHelper.hashFileMd5(fromFile));
|
||||
String toFileMD5 = StringHelper.getHexString(FileHelper.hashFileMd5(toFile));
|
||||
if (!fromFileMD5.equals(toFileMD5)) {
|
||||
logger.error("Copying failed, as MD5 sums are not equal: " + fromFileMD5 + " / " + toFileMD5);
|
||||
toFile.delete();
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// cleanup if files are not the same length
|
||||
if (fromFile.length() != toFile.length()) {
|
||||
logger.error("Copying failed, as new files are not the same length: " + fromFile.length() + " / "
|
||||
+ toFile.length());
|
||||
toFile.delete();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
|
||||
logger.error(e, e);
|
||||
return false;
|
||||
|
||||
} finally {
|
||||
|
||||
if (inBuffer != null) {
|
||||
try {
|
||||
inBuffer.close();
|
||||
} catch (IOException e) {
|
||||
logger.error("Error closing BufferedInputStream" + e);
|
||||
}
|
||||
}
|
||||
|
||||
if (outBuffer != null) {
|
||||
try {
|
||||
outBuffer.close();
|
||||
} catch (IOException e) {
|
||||
logger.error("Error closing BufferedOutputStream" + e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move a File The renameTo method does not allow action across NFS mounted filesystems this method is the
|
||||
* workaround
|
||||
*
|
||||
* @param fromFile
|
||||
* The existing File
|
||||
* @param toFile
|
||||
* The new File
|
||||
* @return <b>true</b> if and only if the renaming succeeded; <b>false</b> otherwise
|
||||
*/
|
||||
public final static boolean move(File fromFile, File toFile) {
|
||||
|
||||
if (fromFile.renameTo(toFile)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
logger.warn("Simple File.renameTo failed, trying copy/delete...");
|
||||
|
||||
// delete if copy was successful, otherwise move will fail
|
||||
if (FileHelper.copy(fromFile, toFile, true)) {
|
||||
logger.info("Deleting fromFile: " + fromFile.getAbsolutePath());
|
||||
return fromFile.delete();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the common parent for the files in the given list
|
||||
*
|
||||
* @param files
|
||||
* the files to find the common parent for
|
||||
*
|
||||
* @return the {@link File} representing the common parent, or null if there is none
|
||||
*/
|
||||
public static File findCommonParent(List<File> files) {
|
||||
|
||||
// be gentle with bad input data
|
||||
if (files.size() == 0)
|
||||
return null;
|
||||
if (files.size() == 1)
|
||||
return files.get(0).getParentFile();
|
||||
|
||||
File commonParent = null;
|
||||
int commonParentDepth = -1;
|
||||
|
||||
// find the common parent among all the files
|
||||
for (int i = 0; i < files.size() - 1; i++) {
|
||||
|
||||
// get first file
|
||||
File file = files.get(i);
|
||||
|
||||
// get list of parents for this file
|
||||
List<File> parents = new ArrayList<File>();
|
||||
File parent = file.getParentFile();
|
||||
while (parent != null) {
|
||||
parents.add(parent);
|
||||
parent = parent.getParentFile();
|
||||
}
|
||||
// reverse
|
||||
Collections.reverse(parents);
|
||||
|
||||
// and now the same for the next file
|
||||
File fileNext = files.get(i + 1);
|
||||
List<File> parentsNext = new ArrayList<File>();
|
||||
File parentNext = fileNext.getParentFile();
|
||||
while (parentNext != null) {
|
||||
parentsNext.add(parentNext);
|
||||
parentNext = parentNext.getParentFile();
|
||||
}
|
||||
// reverse
|
||||
Collections.reverse(parentsNext);
|
||||
|
||||
//logger.info("Files: " + file + " / " + fileNext);
|
||||
|
||||
// now find the common parent
|
||||
File newCommonParent = null;
|
||||
int newCommonParentDepth = -1;
|
||||
for (int j = 0; j < (parents.size()); j++) {
|
||||
|
||||
// don't overflow the size of the next list of parents
|
||||
if (j >= parentsNext.size())
|
||||
break;
|
||||
|
||||
// if we once found a common parent, and our current depth is
|
||||
// greater than the one for the common parent, then stop as
|
||||
// there can't be a deeper parent
|
||||
if (commonParent != null && j > commonParentDepth)
|
||||
break;
|
||||
|
||||
// get the next parents to compare
|
||||
File aParent = parents.get(j);
|
||||
File bParent = parentsNext.get(j);
|
||||
|
||||
//logger.info("Comparing " + aParent + " |||| " + bParent);
|
||||
|
||||
// if they parent are the same, then break, as we won't
|
||||
// have another match
|
||||
if (!aParent.equals(bParent))
|
||||
break;
|
||||
|
||||
// save the parent and the depth where we found the parent
|
||||
newCommonParent = aParent;
|
||||
newCommonParentDepth = j;
|
||||
}
|
||||
|
||||
// if no common parent was found, then break as there won't be one
|
||||
if (commonParent == null && newCommonParent == null)
|
||||
break;
|
||||
|
||||
// if there is no new common parent, then check the next file
|
||||
if (newCommonParent == null)
|
||||
continue;
|
||||
|
||||
// store the common parent
|
||||
commonParent = newCommonParent;
|
||||
commonParentDepth = newCommonParentDepth;
|
||||
//logger.info("Temporary common parent: (" + commonParentDepth + ") " + commonParent);
|
||||
}
|
||||
|
||||
//logger.info("Common parent: " + commonParent);
|
||||
return commonParent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the size of the file in a human readable form. Everything smaller than 1024 bytes is returned as x bytes,
|
||||
* next is KB, then MB and then GB
|
||||
*
|
||||
* @param file
|
||||
* the file for which the humanized size is to be returned
|
||||
*
|
||||
* @return the humanized form of the files size
|
||||
*/
|
||||
public final static String humanizeFileSize(File file) {
|
||||
return humanizeFileSize(file.length());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the size of the file in a human readable form. Everything smaller than 1024 bytes is returned as x bytes,
|
||||
* next is KB, then MB and then GB
|
||||
*
|
||||
* @param fileSize
|
||||
* the size of a file for which the humanized size is to be returned
|
||||
*
|
||||
* @return the humanized form of the files size
|
||||
*/
|
||||
public final static String humanizeFileSize(long fileSize) {
|
||||
if (fileSize < 1024)
|
||||
return String.format("%d bytes", fileSize);
|
||||
|
||||
if (fileSize < 1048576)
|
||||
return String.format("%.1f KB", (fileSize / 1024.0d));
|
||||
|
||||
if (fileSize < 1073741824)
|
||||
return String.format("%.1f MB", (fileSize / 1048576.0d));
|
||||
|
||||
return String.format("%.1f GB", (fileSize / 1073741824.0d));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the MD5 hash of the given file, returning the hash as a byte array. Use
|
||||
* {@link StringHelper#getHexString(byte[])} to create a HEX string of the bytes
|
||||
*
|
||||
* @param file
|
||||
* the file to hash
|
||||
*
|
||||
* @return the hash as a byte array
|
||||
*/
|
||||
public static byte[] hashFileMd5(File file) {
|
||||
return hashFile(file, "MD5");
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the SHA1 hash of the given file, returning the hash as a byte array. Use
|
||||
* {@link StringHelper#getHexString(byte[])} to create a HEX string of the bytes
|
||||
*
|
||||
* @param file
|
||||
* the file to hash
|
||||
*
|
||||
* @return the hash as a byte array
|
||||
*/
|
||||
public static byte[] hashFileSha1(File file) {
|
||||
return hashFile(file, "SHA-1");
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the SHA256 hash of the given file, returning the hash as a byte array. Use
|
||||
* {@link StringHelper#getHexString(byte[])} to create a HEX string of the bytes
|
||||
*
|
||||
* @param file
|
||||
* the file to hash
|
||||
*
|
||||
* @return the hash as a byte array
|
||||
*/
|
||||
public static byte[] hashFileSha256(File file) {
|
||||
return hashFile(file, "SHA-256");
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the hash of the given file with the given algorithm, returning the hash as a byte array. Use
|
||||
* {@link StringHelper#getHexString(byte[])} to create a HEX string of the bytes
|
||||
*
|
||||
* @param file
|
||||
* the file to hash
|
||||
* @param algorithm
|
||||
* the hashing algorithm to use
|
||||
*
|
||||
* @return the hash as a byte array
|
||||
*/
|
||||
public static byte[] hashFile(File file, String algorithm) {
|
||||
try {
|
||||
InputStream fis = new FileInputStream(file);
|
||||
|
||||
byte[] buffer = new byte[1024];
|
||||
MessageDigest complete = MessageDigest.getInstance(algorithm);
|
||||
int numRead;
|
||||
do {
|
||||
numRead = fis.read(buffer);
|
||||
if (numRead > 0) {
|
||||
complete.update(buffer, 0, numRead);
|
||||
}
|
||||
} while (numRead != -1);
|
||||
fis.close();
|
||||
|
||||
return complete.digest();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Something went wrong while hashing file: " + file.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to append bytes to a specified file. The file is created if it does not exist otherwise the bytes
|
||||
* are simply appended
|
||||
*
|
||||
* @param dstFile
|
||||
* the file to append to
|
||||
* @param bytes
|
||||
* the bytes to append
|
||||
*/
|
||||
public static void appendFilePart(File dstFile, byte[] bytes) {
|
||||
FileOutputStream outputStream = null;
|
||||
try {
|
||||
|
||||
outputStream = new FileOutputStream(dstFile, true);
|
||||
outputStream.write(bytes);
|
||||
outputStream.flush();
|
||||
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Could not create and append the bytes to the file " + dstFile.getAbsolutePath());
|
||||
} finally {
|
||||
if (outputStream != null) {
|
||||
try {
|
||||
outputStream.close();
|
||||
} catch (IOException e) {
|
||||
logger.error("Exception while closing FileOutputStream " + e.getLocalizedMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,169 @@
|
|||
package ch.eitchnet.utils.helper;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.apache.log4j.BasicConfigurator;
|
||||
import org.apache.log4j.ConsoleAppender;
|
||||
import org.apache.log4j.Level;
|
||||
import org.apache.log4j.Logger;
|
||||
import org.apache.log4j.PatternLayout;
|
||||
import org.apache.log4j.PropertyConfigurator;
|
||||
|
||||
/**
|
||||
* A simple configurator to configure log4j, with fall back default configuration
|
||||
*
|
||||
* @author Robert von Burg <eitch@eitchnet.ch>
|
||||
*/
|
||||
public class Log4jConfigurator {
|
||||
|
||||
/**
|
||||
* system property used to override the log4j configuration file
|
||||
*/
|
||||
public static final String PROP_FILE_LOG4J = "rsp.log4j.properties";
|
||||
|
||||
/**
|
||||
* default log4j configuration file
|
||||
*/
|
||||
public static final String FILE_LOG4J = "log4j.properties";
|
||||
|
||||
/**
|
||||
* runtime log4j configuration file which is a copy of the original file but has any place holders overwritten
|
||||
*/
|
||||
public static final String FILE_LOG4J_TEMP = "log4j.properties.tmp";
|
||||
|
||||
private static final Logger logger = Logger.getLogger(Log4jConfigurator.class);
|
||||
private static Log4jPropertyWatchDog watchDog;
|
||||
|
||||
/**
|
||||
* Configures log4j with the default {@link ConsoleAppender}
|
||||
*/
|
||||
public static synchronized void configure() {
|
||||
cleanupOldWatchdog();
|
||||
BasicConfigurator.resetConfiguration();
|
||||
BasicConfigurator.configure(new ConsoleAppender(getDefaulLayout()));
|
||||
Logger.getRootLogger().setLevel(Level.INFO);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the default layout: %d %5p [%t] %C{1} %M - %m%n
|
||||
*
|
||||
* @return the default layout
|
||||
*/
|
||||
public static PatternLayout getDefaulLayout() {
|
||||
return new PatternLayout("%d %5p [%t] %C{1} %M - %m%n");
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Loads the log4j configuration
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* This file is configurable through the {@link Log4jConfigurator#PROP_FILE_LOG4J} system property, but uses the
|
||||
* default {@link Log4jConfigurator#FILE_LOG4J} file, if no configuration option is set. The path used is
|
||||
* <user.dir>/config/ whereas <user.dir> is a system property
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Any properties in the properties are substituted using
|
||||
* {@link StringHelper#replaceProperties(Properties, Properties)} and then the configuration file is written to a
|
||||
* new file <user.dir>/tmp/{@link Log4jConfigurator#FILE_LOG4J_TEMP} and then finally
|
||||
* {@link PropertyConfigurator#configureAndWatch(String)} is called so that the configuration is loaded and log4j
|
||||
* watches the temporary file for configuration changes
|
||||
* </p>
|
||||
*/
|
||||
public static synchronized void loadLog4jConfiguration() {
|
||||
|
||||
// first clean up any old watch dog in case of a programmatic re-load of hte configuration
|
||||
cleanupOldWatchdog();
|
||||
|
||||
// get a configured log4j properties file, or use default RSPConfigConstants.FILE_LOG4J
|
||||
String fileLog4j = SystemHelper.getProperty(Log4jConfigurator.class.getName(),
|
||||
Log4jConfigurator.PROP_FILE_LOG4J, Log4jConfigurator.FILE_LOG4J);
|
||||
|
||||
// get the root directory
|
||||
String userDir = System.getProperty("user.dir");
|
||||
String configDir = userDir + "/config/";
|
||||
String tmpDir = userDir + "/tmp/";
|
||||
|
||||
// load the log4j.properties file
|
||||
String pathNameToLog4j = configDir + fileLog4j;
|
||||
File log4JPath = new File(pathNameToLog4j);
|
||||
if (!log4JPath.exists())
|
||||
throw new RuntimeException("The log4j configuration file does not exist at " + log4JPath.getAbsolutePath());
|
||||
|
||||
String pathNameToLog4jTemp = tmpDir + Log4jConfigurator.FILE_LOG4J_TEMP;
|
||||
Properties log4jProperties = new Properties();
|
||||
FileInputStream fin = null;
|
||||
FileOutputStream fout = null;
|
||||
try {
|
||||
fin = new FileInputStream(pathNameToLog4j);
|
||||
log4jProperties.load(fin);
|
||||
fin.close();
|
||||
|
||||
// replace any variables
|
||||
StringHelper.replaceProperties(log4jProperties, System.getProperties());
|
||||
|
||||
// write this as the temporary log4j file
|
||||
File logsFileDir = new File(tmpDir);
|
||||
if (!logsFileDir.exists() && !logsFileDir.mkdirs())
|
||||
throw new RuntimeException("Could not create log path " + logsFileDir.getAbsolutePath());
|
||||
|
||||
fout = new FileOutputStream(pathNameToLog4jTemp);
|
||||
log4jProperties.store(fout, "Running instance log4j configuration " + new Date());
|
||||
fout.close();
|
||||
|
||||
// XXX if the server is in a web context, then we may not use the FileWatchDog
|
||||
BasicConfigurator.resetConfiguration();
|
||||
watchDog = new Log4jPropertyWatchDog(pathNameToLog4jTemp);
|
||||
watchDog.start();
|
||||
|
||||
logger.info("Log4j is configured to use and watch file " + pathNameToLog4jTemp);
|
||||
|
||||
} catch (Exception e) {
|
||||
Log4jConfigurator.configure();
|
||||
logger.error(e, e);
|
||||
logger.error("Log4j COULD NOT BE INITIALIZED. Please check the " + fileLog4j + " file at "
|
||||
+ pathNameToLog4j);
|
||||
} finally {
|
||||
if (fin != null) {
|
||||
try {
|
||||
fin.close();
|
||||
} catch (IOException e) {
|
||||
logger.error("Exception closing input file: " + e, e);
|
||||
}
|
||||
}
|
||||
if (fout != null) {
|
||||
try {
|
||||
fout.close();
|
||||
} catch (IOException e) {
|
||||
logger.error("Exception closing output file: " + e, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup a running watch dog
|
||||
*/
|
||||
public static synchronized void cleanupOldWatchdog() {
|
||||
// clean up an old watch dog
|
||||
if (watchDog != null) {
|
||||
logger.info("Stopping old Log4j watchdog.");
|
||||
watchDog.interrupt();
|
||||
try {
|
||||
watchDog.join(1000l);
|
||||
} catch (InterruptedException e) {
|
||||
logger.error("Oops. Could not terminate an old WatchDog.");
|
||||
} finally {
|
||||
watchDog = null;
|
||||
}
|
||||
logger.info("Done.");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,104 @@
|
|||
package ch.eitchnet.utils.helper;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import org.apache.log4j.LogManager;
|
||||
import org.apache.log4j.PropertyConfigurator;
|
||||
import org.apache.log4j.helpers.LogLog;
|
||||
|
||||
/**
|
||||
* @author Robert von Burg <eitch@eitchnet.ch>
|
||||
*
|
||||
*/
|
||||
public class Log4jPropertyWatchDog extends Thread {
|
||||
|
||||
/**
|
||||
* The default delay between every file modification check, set to 60 seconds.
|
||||
*/
|
||||
public static final long DEFAULT_DELAY = 60000;
|
||||
|
||||
/**
|
||||
* The name of the file to observe for changes.
|
||||
*/
|
||||
protected String filename;
|
||||
|
||||
/**
|
||||
* The delay to observe between every check. By default set {@link #DEFAULT_DELAY}.
|
||||
*/
|
||||
protected long delay = DEFAULT_DELAY;
|
||||
|
||||
protected File file;
|
||||
protected long lastModif = 0;
|
||||
protected boolean warnedAlready = false;
|
||||
protected boolean interrupted = false;
|
||||
|
||||
/**
|
||||
* @param filename
|
||||
*/
|
||||
protected Log4jPropertyWatchDog(String filename) {
|
||||
super("FileWatchdog");
|
||||
this.filename = filename;
|
||||
file = new File(filename);
|
||||
setDaemon(true);
|
||||
checkAndConfigure();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the delay to observe between each check of the file changes.
|
||||
*/
|
||||
public void setDelay(long delay) {
|
||||
this.delay = delay;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
protected void checkAndConfigure() {
|
||||
boolean fileExists;
|
||||
try {
|
||||
fileExists = file.exists();
|
||||
} catch (SecurityException e) {
|
||||
LogLog.warn("Was not allowed to read check file existance, file:[" + filename + "].");
|
||||
interrupted = true; // there is no point in continuing
|
||||
return;
|
||||
}
|
||||
|
||||
if (fileExists) {
|
||||
long l = file.lastModified(); // this can also throw a SecurityException
|
||||
if (l > lastModif) { // however, if we reached this point this
|
||||
lastModif = l; // is very unlikely.
|
||||
doOnChange();
|
||||
warnedAlready = false;
|
||||
}
|
||||
} else {
|
||||
if (!warnedAlready) {
|
||||
LogLog.debug("[" + filename + "] does not exist.");
|
||||
warnedAlready = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Call {@link PropertyConfigurator#configure(String)} with the <code>filename</code> to reconfigure log4j.
|
||||
*/
|
||||
public void doOnChange() {
|
||||
PropertyConfigurator propertyConfigurator = new PropertyConfigurator();
|
||||
propertyConfigurator.doConfigure(filename, LogManager.getLoggerRepository());
|
||||
}
|
||||
|
||||
/**
|
||||
* @see java.lang.Thread#run()
|
||||
*/
|
||||
@Override
|
||||
public void run() {
|
||||
while (!interrupted) {
|
||||
try {
|
||||
Thread.sleep(delay);
|
||||
} catch (InterruptedException e) {
|
||||
// no interruption expected
|
||||
interrupted = true;
|
||||
}
|
||||
checkAndConfigure();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,397 @@
|
|||
package ch.eitchnet.utils.helper;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
/**
|
||||
* A helper class to perform different actions on {@link String}s
|
||||
*
|
||||
* @author Robert von Burg <eitch@eitchnet.ch>
|
||||
*/
|
||||
public class StringHelper {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(StringHelper.class);
|
||||
|
||||
/**
|
||||
* Hex char table for fast calculating of hex value
|
||||
*/
|
||||
static final byte[] HEX_CHAR_TABLE = { (byte) '0', (byte) '1', (byte) '2', (byte) '3', (byte) '4', (byte) '5',
|
||||
(byte) '6', (byte) '7', (byte) '8', (byte) '9', (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e',
|
||||
(byte) 'f' };
|
||||
|
||||
/**
|
||||
* Converts each byte of the given byte array to a HEX value and returns the concatenation of these values
|
||||
*
|
||||
* @param raw
|
||||
* the bytes to convert to String using numbers in hexadecimal
|
||||
*
|
||||
* @return the encoded string
|
||||
*
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public static String getHexString(byte[] raw) throws RuntimeException {
|
||||
try {
|
||||
byte[] hex = new byte[2 * raw.length];
|
||||
int index = 0;
|
||||
|
||||
for (byte b : raw) {
|
||||
int v = b & 0xFF;
|
||||
hex[index++] = HEX_CHAR_TABLE[v >>> 4];
|
||||
hex[index++] = HEX_CHAR_TABLE[v & 0xF];
|
||||
}
|
||||
|
||||
return new String(hex, "ASCII");
|
||||
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new RuntimeException("Something went wrong while converting to HEX: " + e.getLocalizedMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a byte array of a given string by converting each character of the string to a number base 16
|
||||
*
|
||||
* @param encoded
|
||||
* the string to convert to a byt string
|
||||
*
|
||||
* @return the encoded byte stream
|
||||
*/
|
||||
public static byte[] fromHexString(String encoded) {
|
||||
if ((encoded.length() % 2) != 0)
|
||||
throw new IllegalArgumentException("Input string must contain an even number of characters.");
|
||||
|
||||
final byte result[] = new byte[encoded.length() / 2];
|
||||
final char enc[] = encoded.toCharArray();
|
||||
for (int i = 0; i < enc.length; i += 2) {
|
||||
StringBuilder curr = new StringBuilder(2);
|
||||
curr.append(enc[i]).append(enc[i + 1]);
|
||||
result[i / 2] = (byte) Integer.parseInt(curr.toString(), 16);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the MD5 Hash of a string Use {@link StringHelper#getHexString(byte[])} to convert the byte array to a
|
||||
* Hex String which is printable
|
||||
*
|
||||
* @param string
|
||||
* the string to hash
|
||||
*
|
||||
* @return the hash or null, if an exception was thrown
|
||||
*/
|
||||
public static byte[] hashMd5(String string) {
|
||||
return hashMd5(string.getBytes());
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the MD5 Hash of a byte array Use {@link StringHelper#getHexString(byte[])} to convert the byte array to
|
||||
* a Hex String which is printable
|
||||
*
|
||||
* @param bytes
|
||||
* the bytes to hash
|
||||
*
|
||||
* @return the hash or null, if an exception was thrown
|
||||
*/
|
||||
public static byte[] hashMd5(byte[] bytes) {
|
||||
return hash("MD5", bytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the SHA1 Hash of a string Use {@link StringHelper#getHexString(byte[])} to convert the byte array to a
|
||||
* Hex String which is printable
|
||||
*
|
||||
* @param string
|
||||
* the string to hash
|
||||
*
|
||||
* @return the hash or null, if an exception was thrown
|
||||
*/
|
||||
public static byte[] hashSha1(String string) {
|
||||
return hashSha1(string.getBytes());
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the SHA1 Hash of a byte array Use {@link StringHelper#getHexString(byte[])} to convert the byte array
|
||||
* to a Hex String which is printable
|
||||
*
|
||||
* @param bytes
|
||||
* the bytes to hash
|
||||
*
|
||||
* @return the hash or null, if an exception was thrown
|
||||
*/
|
||||
public static byte[] hashSha1(byte[] bytes) {
|
||||
return hash("SHA-1", bytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the SHA-256 Hash of a string Use {@link StringHelper#getHexString(byte[])} to convert the byte array to
|
||||
* a Hex String which is printable
|
||||
*
|
||||
* @param string
|
||||
* the string to hash
|
||||
*
|
||||
* @return the hash or null, if an exception was thrown
|
||||
*/
|
||||
public static byte[] hashSha256(String string) {
|
||||
return hashSha256(string.getBytes());
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the SHA1 Hash of a byte array Use {@link StringHelper#getHexString(byte[])} to convert the byte array
|
||||
* to a Hex String which is printable
|
||||
*
|
||||
* @param bytes
|
||||
* the bytes to hash
|
||||
*
|
||||
* @return the hash or null, if an exception was thrown
|
||||
*/
|
||||
public static byte[] hashSha256(byte[] bytes) {
|
||||
return hash("SHA-256", bytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the hash of an algorithm
|
||||
*
|
||||
* @param algorithm
|
||||
* the algorithm to use
|
||||
* @param bytes
|
||||
* the bytes to hash
|
||||
*
|
||||
* @return the hash or null, if an exception was thrown
|
||||
*/
|
||||
public static byte[] hash(String algorithm, byte[] bytes) {
|
||||
try {
|
||||
|
||||
MessageDigest digest = MessageDigest.getInstance(algorithm);
|
||||
byte[] hashArray = digest.digest(bytes);
|
||||
|
||||
return hashArray;
|
||||
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new RuntimeException("Algorithm " + algorithm + " does not exist!", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes the length of a String. Does not shorten it when it is too long, but lengthens it, depending on the
|
||||
* options set: adding the char at the beginning or appending it at the end
|
||||
*
|
||||
* @param value
|
||||
* string to normalize
|
||||
* @param length
|
||||
* length string must have
|
||||
* @param beginning
|
||||
* add at beginning of value
|
||||
* @param c
|
||||
* char to append when appending
|
||||
* @return the new string
|
||||
*/
|
||||
public static String normalizeLength(String value, int length, boolean beginning, char c) {
|
||||
return normalizeLength(value, length, beginning, false, c);
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes the length of a String. Shortens it when it is too long, giving out a logger warning, or lengthens it,
|
||||
* depending on the options set: appending the char at the beginning or the end
|
||||
*
|
||||
* @param value
|
||||
* string to normalize
|
||||
* @param length
|
||||
* length string must have
|
||||
* @param beginning
|
||||
* append at beginning of value
|
||||
* @param shorten
|
||||
* allow shortening of value
|
||||
* @param c
|
||||
* char to append when appending
|
||||
* @return the new string
|
||||
*/
|
||||
public static String normalizeLength(String value, int length, boolean beginning, boolean shorten, char c) {
|
||||
|
||||
if (value.length() == length)
|
||||
return value;
|
||||
|
||||
if (value.length() < length) {
|
||||
|
||||
while (value.length() != length) {
|
||||
if (beginning) {
|
||||
value = c + value;
|
||||
} else {
|
||||
value = value + c;
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
|
||||
} else if (shorten) {
|
||||
|
||||
logger.warn("Shortening length of value: " + value);
|
||||
logger.warn("Length is: " + value.length() + " max: " + length);
|
||||
|
||||
return value.substring(0, length);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls {@link #replacePropertiesIn(Properties, String)}, with {@link System#getProperties()} as input
|
||||
*
|
||||
* @return a new string with all defined system properties replaced or if an error occurred the original value is
|
||||
* returned
|
||||
*/
|
||||
public static String replaceSystemPropertiesIn(String value) {
|
||||
return replacePropertiesIn(System.getProperties(), value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Traverses the given string searching for occurrences of ${...} sequences. Theses sequences are replaced with a
|
||||
* {@link Properties#getProperty(String)} value if such a value exists in the properties map. If the value of the
|
||||
* sequence is not in the properties, then the sequence is not replaced
|
||||
*
|
||||
* @param properties
|
||||
* the {@link Properties} in which to get the value
|
||||
* @param value
|
||||
* the value in which to replace any system properties
|
||||
*
|
||||
* @return a new string with all defined properties replaced or if an error occurred the original value is returned
|
||||
*/
|
||||
public static String replacePropertiesIn(Properties properties, String value) {
|
||||
|
||||
// keep copy of original value
|
||||
String origValue = value;
|
||||
|
||||
// get first occurrence of $ character
|
||||
int pos = -1;
|
||||
int stop = 0;
|
||||
|
||||
// loop on $ character positions
|
||||
while ((pos = value.indexOf('$', pos + 1)) != -1) {
|
||||
|
||||
// if pos+1 is not { character then continue
|
||||
if (value.charAt(pos + 1) != '{') {
|
||||
continue;
|
||||
}
|
||||
|
||||
// find end of sequence with } character
|
||||
stop = value.indexOf('}', pos + 1);
|
||||
|
||||
// if no stop found, then break as another sequence should be able to start
|
||||
if (stop == -1) {
|
||||
logger.error("Sequence starts at offset " + pos + " but does not end!");
|
||||
value = origValue;
|
||||
break;
|
||||
}
|
||||
|
||||
// get sequence enclosed by pos and stop
|
||||
String sequence = value.substring(pos + 2, stop);
|
||||
|
||||
// make sure sequence doesn't contain $ { } characters
|
||||
if (sequence.contains("$") || sequence.contains("{") || sequence.contains("}")) {
|
||||
logger.error("Enclosed sequence in offsets " + pos + " - " + stop
|
||||
+ " contains one of the illegal chars: $ { }: " + sequence);
|
||||
value = origValue;
|
||||
break;
|
||||
}
|
||||
|
||||
// sequence is good, so see if we have a property for it
|
||||
String property = properties.getProperty(sequence, "");
|
||||
|
||||
// if no property exists, then log and continue
|
||||
if (property.isEmpty()) {
|
||||
// logger.warn("No system property found for sequence " + sequence);
|
||||
continue;
|
||||
}
|
||||
|
||||
// property exists, so replace in value
|
||||
value = value.replace("${" + sequence + "}", property);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls {@link #replaceProperties(Properties, Properties)} with null as the second argument. This allows for
|
||||
* replacing all properties with itself
|
||||
*
|
||||
* @param properties
|
||||
* the properties in which the values must have any ${...} replaced by values of the respective key
|
||||
*/
|
||||
public static void replaceProperties(Properties properties) {
|
||||
replaceProperties(properties, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks every value in the {@link Properties} and then then replaces any ${...} variables with keys in this
|
||||
* {@link Properties} value using {@link StringHelper#replacePropertiesIn(Properties, String)}
|
||||
*
|
||||
* @param properties
|
||||
* the properties in which the values must have any ${...} replaced by values of the respective key
|
||||
* @param altProperties
|
||||
* if properties does not contain the ${...} key, then try these alternative properties
|
||||
*/
|
||||
public static void replaceProperties(Properties properties, Properties altProperties) {
|
||||
|
||||
for (Object keyObj : properties.keySet()) {
|
||||
String key = (String) keyObj;
|
||||
String property = properties.getProperty(key);
|
||||
String newProperty = StringHelper.replacePropertiesIn(properties, property);
|
||||
|
||||
// try first properties
|
||||
if (!property.equals(newProperty)) {
|
||||
// logger.info("Key " + key + " has replaced property " + property + " with new value " + newProperty);
|
||||
properties.put(key, newProperty);
|
||||
} else if (altProperties != null) {
|
||||
|
||||
// try alternative properties
|
||||
newProperty = StringHelper.replacePropertiesIn(altProperties, property);
|
||||
if (!property.equals(newProperty)) {
|
||||
// logger.info("Key " + key + " has replaced property " + property + " from alternative properties with new value " + newProperty);
|
||||
properties.put(key, newProperty);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a helper method with which it is possible to print the location in the two given strings where they start
|
||||
* to differ. The length of string returned is currently 40 characters, or less if either of the given strings are
|
||||
* shorter. The format of the string is 3 lines. The first line has information about where in the strings the
|
||||
* difference occurs, and the second and third lines contain contexts
|
||||
*
|
||||
* @param s1
|
||||
* the first string
|
||||
* @param s2
|
||||
* the second string
|
||||
*
|
||||
* @return the string from which the strings differ with a length of 40 characters within the original strings
|
||||
*/
|
||||
public static String printUnequalContext(String s1, String s2) {
|
||||
|
||||
byte[] bytes1 = s1.getBytes();
|
||||
byte[] bytes2 = s2.getBytes();
|
||||
int i = 0;
|
||||
for (; i < bytes1.length; i++) {
|
||||
if (i > bytes2.length)
|
||||
break;
|
||||
|
||||
if (bytes1[i] != bytes2[i])
|
||||
break;
|
||||
}
|
||||
|
||||
int maxContext = 40;
|
||||
int start = Math.max(0, (i - maxContext));
|
||||
int end = Math.min(i + maxContext, (Math.min(bytes1.length, bytes2.length)));
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("Strings are not equal! Start of inequality is at " + i + ". Showing " + maxContext
|
||||
+ " extra characters and start and end:\n");
|
||||
sb.append("context s1: " + s1.substring(start, end) + "\n");
|
||||
sb.append("context s2: " + s2.substring(start, end) + "\n");
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
package ch.eitchnet.utils.helper;
|
||||
|
||||
/**
|
||||
* A helper class for {@link System} methods
|
||||
*
|
||||
* @author Robert von Burg <eitch@eitchnet.ch>
|
||||
*/
|
||||
public class SystemHelper {
|
||||
|
||||
/**
|
||||
* Returns the {@link System#getProperty(String)} with the given key. If def is null, and the property is not set,
|
||||
* then a {@link RuntimeException} is thrown
|
||||
*
|
||||
* @param context
|
||||
* The context should be the name of the caller, so that stack trace gives enough detail as to which
|
||||
* class expected the configuration property if no property was found
|
||||
* @param key
|
||||
* the key of the property to return
|
||||
* @param def
|
||||
* the default value, if null, then an exception will be thrown if the property is not set
|
||||
*
|
||||
* @return the property under the given key
|
||||
*
|
||||
* @throws RuntimeException
|
||||
* if the property is not set and def is null
|
||||
*/
|
||||
public static String getProperty(String context, String key, String def) throws RuntimeException {
|
||||
String property = System.getProperty(key, def);
|
||||
if (property == null)
|
||||
throw new RuntimeException("[" + context + "] Property " + key + " is not set, and no default was given!");
|
||||
|
||||
return property;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegates to {@link #getProperty(String, String, String)} but returns the value as a {@link Boolean} where
|
||||
* {@link Boolean#valueOf(String)} defines the actual value
|
||||
*
|
||||
* @return the property as a {@link Boolean} under the given key
|
||||
*
|
||||
* @throws RuntimeException
|
||||
* if the property is not set and def is null
|
||||
*/
|
||||
public static Boolean getPropertyBool(String context, String key, Boolean def) throws RuntimeException {
|
||||
return Boolean.valueOf(getProperty(context, key, def == null ? null : def.toString()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegates to {@link #getProperty(String, String, String)} but returns the value as an {@link Integer} where
|
||||
* {@link Integer#valueOf(String)} defines the actual value
|
||||
*
|
||||
* @return the property as a {@link Integer} under the given key
|
||||
*
|
||||
* @throws RuntimeException
|
||||
* if the property is not set and def is null
|
||||
*/
|
||||
public static Integer getPropertyInt(String context, String key, Integer def) throws RuntimeException {
|
||||
return Integer.valueOf(getProperty(context, key, def == null ? null : def.toString()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegates to {@link #getProperty(String, String, String)} but returns the value as an {@link Double} where
|
||||
* {@link Double#valueOf(String)} defines the actual value
|
||||
*
|
||||
* @return the property as a {@link Double} under the given key
|
||||
*
|
||||
* @throws RuntimeException
|
||||
* if the property is not set and def is null
|
||||
*/
|
||||
public static Double getPropertyDouble(String context, String key, Double def) throws RuntimeException {
|
||||
return Double.valueOf(getProperty(context, key, def == null ? null : def.toString()));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
package ch.eitchnet.utils.objectfilter;
|
||||
|
||||
/**
|
||||
* This interface serves for objects which are required, at some point, to have a unique ID within a transaction.
|
||||
*
|
||||
* @author Michael Gatto <michael@gatto.ch>
|
||||
*/
|
||||
public interface ITransactionObject {
|
||||
|
||||
/**
|
||||
* UNSET Marker to determine if ids have not been set.
|
||||
* <p>
|
||||
* Beware: this is set to 0 due to transient field in the {@link ITransactionObject} implementations that store the
|
||||
* ID, which are set to zero when de-serialized, and that are not allowed to be serialized.
|
||||
* </p>
|
||||
*/
|
||||
public static final long UNSET = 0;
|
||||
|
||||
/**
|
||||
* Set the ID of this object. This ID is unique for this object within the transaction.
|
||||
*
|
||||
* @param id
|
||||
* The ID to set.
|
||||
*/
|
||||
public void setTransactionID(long id);
|
||||
|
||||
/**
|
||||
* @return The ID of this object, as set within the transaction. This ID shall guarantee that it is unique within
|
||||
* this transaction.
|
||||
*/
|
||||
public long getTransactionID();
|
||||
|
||||
/**
|
||||
* Reset / anul the transaction ID of this object
|
||||
*/
|
||||
public void resetTransactionID();
|
||||
}
|
|
@ -0,0 +1,112 @@
|
|||
package ch.eitchnet.utils.objectfilter;
|
||||
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
/**
|
||||
* This class is a cache for objects whose operations (additions, modifications, removals) are first collected and then
|
||||
* "deployed" in one go.
|
||||
* <p>
|
||||
* Thus, this class keeps:
|
||||
* <ul>
|
||||
* <li>An ID of the object, such that it can be referenced externally.</li.>
|
||||
* <li>A key for an object, which keeps the object's type.</li>
|
||||
* <li>A reference to the current state of the object</li>
|
||||
* <li>An identifier of the operation that needs to be performed on this</li>
|
||||
* </ul>
|
||||
* </p>
|
||||
*
|
||||
* @author Michael Gatto <michael@gatto.ch>
|
||||
*
|
||||
* @param <T>
|
||||
*/
|
||||
public class ObjectCache<T extends ITransactionObject> {
|
||||
|
||||
private final static Logger logger = Logger.getLogger(ObjectCache.class);
|
||||
|
||||
/**
|
||||
* id The unique ID of this object in this session
|
||||
*/
|
||||
private final long id;
|
||||
/**
|
||||
* key The key defining who's registered for this object's state
|
||||
*/
|
||||
private final String key;
|
||||
/**
|
||||
* object The object that shall be cached
|
||||
*/
|
||||
private T object;
|
||||
/**
|
||||
* operation The operation that has occurred on this object.
|
||||
*/
|
||||
private Operation operation;
|
||||
|
||||
/**
|
||||
* @param key
|
||||
* @param object
|
||||
* @param operation
|
||||
*/
|
||||
public ObjectCache(String key, T object, Operation operation) {
|
||||
|
||||
this.id = object.getTransactionID();
|
||||
this.key = key;
|
||||
this.object = object;
|
||||
this.operation = operation;
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Instanciated Cache: ID" + this.id + " / " + key + " OP: " + this.operation + " / "
|
||||
+ object.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the new object version of this cache.
|
||||
*
|
||||
* @param object
|
||||
*/
|
||||
public void setObject(T object) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Updating ID " + this.id + " to value " + object.toString());
|
||||
}
|
||||
this.object = object;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the operation to execute for this object.
|
||||
*
|
||||
* @param newOperation
|
||||
*/
|
||||
public void setOperation(Operation newOperation) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Updating Operation of ID " + this.id + " from " + this.operation + " to " + newOperation);
|
||||
}
|
||||
this.operation = newOperation;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the id
|
||||
*/
|
||||
public long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the key
|
||||
*/
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the object
|
||||
*/
|
||||
public T getObject() {
|
||||
return object;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the operation
|
||||
*/
|
||||
public Operation getOperation() {
|
||||
return operation;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,611 @@
|
|||
package ch.eitchnet.utils.objectfilter;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
/**
|
||||
* This class implements a filter where modifications to an object are collected, and only the most recent action and
|
||||
* version of the object is returned.
|
||||
* <p>
|
||||
* In its current implementation, any instance of an object may "live" in the cache registered only under one single
|
||||
* key.
|
||||
* </p>
|
||||
* <p>
|
||||
* The rules of the cache are defined as follows. The top row represents the state of the cache for an object, given in
|
||||
* version O1. Here, "N/A" symbolizes that the object is not yet present in the cache or, if it is a result of an
|
||||
* operation, that it is removed from the cache. Each other row symbolizes the next action that is performed on an
|
||||
* object, with the cell containing the "final" action for this object with the version of the object to be retained.
|
||||
* Err! symbolize incorrect sequences of events that cause an exception.
|
||||
* </p>
|
||||
* <table border="1">
|
||||
* <tr>
|
||||
* <td>Action \ State in Cache</td>
|
||||
* <td>N/A</td>
|
||||
* <td>Add(01)</td>
|
||||
* <td>Update(O1)</td>
|
||||
* <td>Remove(O1)</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>Add (O2)</td>
|
||||
* <td>Add(O2)</td>
|
||||
* <td>Err!</td>
|
||||
* <td>Err!</td>
|
||||
* <td>Update(O2)</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>Update (O2)</td>
|
||||
* <td>Update(O2)</td>
|
||||
* <td>Add(O2)</td>
|
||||
* <td>Update(O2)</td>
|
||||
* <td>Err!</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>Remove (O2)</td>
|
||||
* <td>Remove(O2)</td>
|
||||
* <td>N/A</td>
|
||||
* <td>Remove(O2)</td>
|
||||
* <td>Err!</td>
|
||||
* </tr>
|
||||
* </table>
|
||||
*
|
||||
* @author Michael Gatto <michael@gatto.ch> (initial version)
|
||||
* @author Robert von Burg <eitch@eitchnet.ch>
|
||||
*
|
||||
* @param <T>
|
||||
*/
|
||||
public class ObjectFilter<T extends ITransactionObject> {
|
||||
|
||||
// XXX think about removing the generic T, as there is no sense in it
|
||||
|
||||
private final static Logger logger = Logger.getLogger(ObjectFilter.class);
|
||||
|
||||
private HashMap<Long, ObjectCache<T>> cache = new HashMap<Long, ObjectCache<T>>();
|
||||
private HashSet<String> keySet = new HashSet<String>();
|
||||
|
||||
private static long id = ITransactionObject.UNSET;
|
||||
|
||||
/**
|
||||
* Register, under the given key, the addition of the given object.
|
||||
* <p>
|
||||
* This is the single point where the updating logic is applied for the cache in case of addition. The logic is:
|
||||
* <table border="1" >
|
||||
* <tr>
|
||||
* <td>Action\State in Cache</td>
|
||||
* <td>N/A</td>
|
||||
* <td>Add(01)</td>
|
||||
* <td>Update(O1)</td>
|
||||
* <td>Remove(O1)</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>Add (O2)</td>
|
||||
* <td>Add(O2)</td>
|
||||
* <td>Err!</td>
|
||||
* <td>Err!</td>
|
||||
* <td>Update(O2)</td>
|
||||
* </tr>
|
||||
* </table>
|
||||
*
|
||||
* @param key
|
||||
* the key to register the object with
|
||||
* @param objectToAdd
|
||||
* The object for which addition shall be registered.
|
||||
*/
|
||||
public void add(String key, T objectToAdd) {
|
||||
|
||||
if (logger.isDebugEnabled())
|
||||
logger.debug("add object " + objectToAdd + " with key " + key);
|
||||
|
||||
// add the key to the set
|
||||
keySet.add(key);
|
||||
|
||||
// BEWARE: you fix a bug here, be sure to update BOTH tables on the logic.
|
||||
long id = objectToAdd.getTransactionID();
|
||||
if (id == ITransactionObject.UNSET) {
|
||||
// The ID of the object has not been set, so it has not been in the cache during this
|
||||
// run. Hence, we create an ID and add it to the cache.
|
||||
id = dispenseID();
|
||||
objectToAdd.setTransactionID(id);
|
||||
ObjectCache<T> cacheObj = new ObjectCache<T>(key, objectToAdd, Operation.ADD);
|
||||
cache.put(id, cacheObj);
|
||||
} else {
|
||||
ObjectCache<T> cached = cache.get(Long.valueOf(objectToAdd.getTransactionID()));
|
||||
if (cached == null) {
|
||||
// The object got an ID during this run, but was not added to the cache.
|
||||
// Hence, we add it now, with the current operation.
|
||||
ObjectCache<T> cacheObj = new ObjectCache<T>(key, objectToAdd, Operation.ADD);
|
||||
cache.put(id, cacheObj);
|
||||
} else {
|
||||
String existingKey = cached.getKey();
|
||||
if (!existingKey.equals(key)) {
|
||||
throw new RuntimeException(
|
||||
"Invalid key provided for object with transaction ID "
|
||||
+ Long.toString(id)
|
||||
+ " and operation "
|
||||
+ Operation.ADD.toString()
|
||||
+ ": existing key is "
|
||||
+ existingKey
|
||||
+ ", new key is "
|
||||
+ key
|
||||
+ ". Object may be present in the same filter instance only once, registered using one key only. Object:"
|
||||
+ objectToAdd.toString());
|
||||
}
|
||||
// The object is in cache: update the version as required, keeping in mind that most
|
||||
// of the cases here will be mistakes...
|
||||
Operation op = cached.getOperation();
|
||||
switch (op) {
|
||||
case ADD:
|
||||
throw new RuntimeException("Stale State exception. Invalid + after +");
|
||||
case MODIFY:
|
||||
throw new RuntimeException("Stale State exception. Invalid + after +=");
|
||||
case REMOVE:
|
||||
cached.setObject(objectToAdd);
|
||||
cached.setOperation(Operation.MODIFY);
|
||||
break;
|
||||
} // switch
|
||||
}// else of object not in cache
|
||||
}// else of ID not set
|
||||
}
|
||||
|
||||
/**
|
||||
* Register, under the given key, the update of the given object. * </p>
|
||||
* <table border="1">
|
||||
* <tr>
|
||||
* <td>Action \ State in Cache</td>
|
||||
* <td>N/A</td>
|
||||
* <td>Add(01)</td>
|
||||
* <td>Update(O1)</td>
|
||||
* <td>Remove(O1)</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>Update (O2)</td>
|
||||
* <td>Update(O2)</td>
|
||||
* <td>Add(O2)</td>
|
||||
* <td>Update(O2)</td>
|
||||
* <td>Err!</td>
|
||||
* </tr>
|
||||
* </table>
|
||||
*
|
||||
* @param key
|
||||
* the key to register the object with
|
||||
* @param objectToUpdate
|
||||
* The object for which update shall be registered.
|
||||
*/
|
||||
public void update(String key, T objectToUpdate) {
|
||||
|
||||
if (logger.isDebugEnabled())
|
||||
logger.debug("update object " + objectToUpdate + " with key " + key);
|
||||
|
||||
// add the key to the keyset
|
||||
keySet.add(key);
|
||||
// BEWARE: you fix a bug here, be sure to update BOTH tables on the logic.
|
||||
|
||||
long id = objectToUpdate.getTransactionID();
|
||||
if (id == ITransactionObject.UNSET) {
|
||||
id = dispenseID();
|
||||
objectToUpdate.setTransactionID(id);
|
||||
ObjectCache<T> cacheObj = new ObjectCache<T>(key, objectToUpdate, Operation.MODIFY);
|
||||
cache.put(id, cacheObj);
|
||||
} else {
|
||||
ObjectCache<T> cached = cache.get(Long.valueOf(objectToUpdate.getTransactionID()));
|
||||
if (cached == null) {
|
||||
// The object got an ID during this run, but was not added to this cache.
|
||||
// Hence, we add it now, with the current operation.
|
||||
ObjectCache<T> cacheObj = new ObjectCache<T>(key, objectToUpdate, Operation.MODIFY);
|
||||
cache.put(id, cacheObj);
|
||||
} else {
|
||||
String existingKey = cached.getKey();
|
||||
if (!existingKey.equals(key)) {
|
||||
|
||||
throw new RuntimeException(
|
||||
"Invalid key provided for object with transaction ID "
|
||||
+ Long.toString(id)
|
||||
+ " and operation "
|
||||
+ Operation.MODIFY.toString()
|
||||
+ ": existing key is "
|
||||
+ existingKey
|
||||
+ ", new key is "
|
||||
+ key
|
||||
+ ". Object may be present in the same filter instance only once, registered using one key only. Object:"
|
||||
+ objectToUpdate.toString());
|
||||
}
|
||||
// The object is in cache: update the version as required.
|
||||
Operation op = cached.getOperation();
|
||||
switch (op) {
|
||||
case ADD:
|
||||
cached.setObject(objectToUpdate);
|
||||
break;
|
||||
case MODIFY:
|
||||
cached.setObject(objectToUpdate);
|
||||
break;
|
||||
case REMOVE:
|
||||
throw new RuntimeException("Stale State exception: Invalid += after -");
|
||||
} // switch
|
||||
}// else of object not in cache
|
||||
}// else of ID not set
|
||||
}
|
||||
|
||||
/**
|
||||
* Register, under the given key, the removal of the given object. * </p>
|
||||
* <table border="1">
|
||||
* <tr>
|
||||
* <td>Action \ State in Cache</td>
|
||||
* <td>N/A</td>
|
||||
* <td>Add(01)</td>
|
||||
* <td>Update(O1)</td>
|
||||
* <td>Remove(O1)</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>Remove (O2)</td>
|
||||
* <td>Remove(O2)</td>
|
||||
* <td>N/A</td>
|
||||
* <td>Remove(O2)</td>
|
||||
* <td>Err!</td>
|
||||
* </tr>
|
||||
* </table>
|
||||
*
|
||||
* @param key
|
||||
* the key to register the object with
|
||||
* @param objectToRemove
|
||||
* The object for which removal shall be registered.
|
||||
*/
|
||||
public void remove(String key, T objectToRemove) {
|
||||
|
||||
if (logger.isDebugEnabled())
|
||||
logger.debug("remove object " + objectToRemove + " with key " + key);
|
||||
|
||||
// add the key to the keyset
|
||||
keySet.add(key);
|
||||
// BEWARE: you fix a bug here, be sure to update BOTH tables on the logic.
|
||||
long id = objectToRemove.getTransactionID();
|
||||
if (id == ITransactionObject.UNSET) {
|
||||
id = dispenseID();
|
||||
objectToRemove.setTransactionID(id);
|
||||
ObjectCache<T> cacheObj = new ObjectCache<T>(key, objectToRemove, Operation.REMOVE);
|
||||
cache.put(id, cacheObj);
|
||||
} else {
|
||||
ObjectCache<T> cached = cache.get(Long.valueOf(id));
|
||||
if (cached == null) {
|
||||
// The object got an ID during this run, but was not added to this cache.
|
||||
// Hence, we add it now, with the current operation.
|
||||
ObjectCache<T> cacheObj = new ObjectCache<T>(key, objectToRemove, Operation.REMOVE);
|
||||
cache.put(id, cacheObj);
|
||||
} else {
|
||||
String existingKey = cached.getKey();
|
||||
if (!existingKey.equals(key)) {
|
||||
throw new RuntimeException(
|
||||
"Invalid key provided for object with transaction ID "
|
||||
+ Long.toString(id)
|
||||
+ " and operation "
|
||||
+ Operation.REMOVE.toString()
|
||||
+ ": existing key is "
|
||||
+ existingKey
|
||||
+ ", new key is "
|
||||
+ key
|
||||
+ ". Object may be present in the same filter instance only once, registered using one key only. Object:"
|
||||
+ objectToRemove.toString());
|
||||
}
|
||||
// The object is in cache: update the version as required.
|
||||
Operation op = cached.getOperation();
|
||||
switch (op) {
|
||||
case ADD:
|
||||
// this is a case where we're removing the object from the cache, since we are
|
||||
// removing it now and it was added previously.
|
||||
cache.remove(Long.valueOf(id));
|
||||
break;
|
||||
case MODIFY:
|
||||
cached.setObject(objectToRemove);
|
||||
cached.setOperation(Operation.REMOVE);
|
||||
break;
|
||||
case REMOVE:
|
||||
throw new RuntimeException("Stale State exception. Invalid - after -");
|
||||
} // switch
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register, under the given key, the addition of the given list of objects.
|
||||
*
|
||||
* @param key
|
||||
* the key to register the objects with
|
||||
* @param addedObjects
|
||||
* The objects for which addition shall be registered.
|
||||
*/
|
||||
public void addAll(String key, Collection<T> addedObjects) {
|
||||
for (T addObj : addedObjects) {
|
||||
add(key, addObj);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register, under the given key, the update of the given list of objects.
|
||||
*
|
||||
* @param key
|
||||
* the key to register the objects with
|
||||
* @param updatedObjects
|
||||
* The objects for which update shall be registered.
|
||||
*/
|
||||
public void updateAll(String key, Collection<T> updatedObjects) {
|
||||
for (T update : updatedObjects) {
|
||||
update(key, update);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register, under the given key, the removal of the given list of objects.
|
||||
*
|
||||
* @param key
|
||||
* the key to register the objects with
|
||||
* @param removedObjects
|
||||
* The objects for which removal shall be registered.
|
||||
*/
|
||||
public void removeAll(String key, Collection<T> removedObjects) {
|
||||
for (T removed : removedObjects) {
|
||||
remove(key, removed);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the addition of the given object. Since no key is provided, the class name is used as a key.
|
||||
*
|
||||
* @param object
|
||||
* The object that shall be registered for addition
|
||||
*/
|
||||
public void add(T object) {
|
||||
add(object.getClass().getName(), object);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the update of the given object. Since no key is provided, the class name is used as a key.
|
||||
*
|
||||
* @param object
|
||||
* The object that shall be registered for updating
|
||||
*/
|
||||
public void update(T object) {
|
||||
update(object.getClass().getName(), object);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the removal of the given object. Since no key is provided, the class name is used as a key.
|
||||
*
|
||||
* @param object
|
||||
* The object that shall be registered for removal
|
||||
*/
|
||||
public void remove(T object) {
|
||||
remove(object.getClass().getName(), object);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the addition of all objects in the list. Since no key is provided, the class name of each object is used
|
||||
* as a key.
|
||||
*
|
||||
* @param objects
|
||||
* The objects that shall be registered for addition
|
||||
*/
|
||||
public void addAll(List<T> objects) {
|
||||
for (T addedObj : objects) {
|
||||
add(addedObj.getClass().getName(), addedObj);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the updating of all objects in the list. Since no key is provided, the class name of each object is used
|
||||
* as a key.
|
||||
*
|
||||
* @param updateObjects
|
||||
* The objects that shall be registered for updating
|
||||
*/
|
||||
public void updateAll(List<T> updateObjects) {
|
||||
for (T update : updateObjects) {
|
||||
update(update.getClass().getName(), update);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the removal of all objects in the list. Since no key is provided, the class name of each object is used
|
||||
* as a key.
|
||||
*
|
||||
* @param removedObjects
|
||||
* The objects that shall be registered for removal
|
||||
*/
|
||||
public void removeAll(List<T> removedObjects) {
|
||||
for (T removed : removedObjects) {
|
||||
remove(removed.getClass().getName(), removed);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all objects that were registered under the given key and that have as a resulting final action an addition.
|
||||
*
|
||||
* @param key
|
||||
* The registration key of the objects to match
|
||||
* @return The list of all objects registered under the given key and that need to be added.
|
||||
*/
|
||||
public List<T> getAdded(String key) {
|
||||
List<T> addedObjects = new LinkedList<T>();
|
||||
Collection<ObjectCache<T>> allObjs = cache.values();
|
||||
for (ObjectCache<T> objectCache : allObjs) {
|
||||
if (objectCache.getOperation() == Operation.ADD && (objectCache.getKey().equals(key))) {
|
||||
addedObjects.add(objectCache.getObject());
|
||||
}
|
||||
}
|
||||
return addedObjects;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all objects that were registered under the given key and that have as a resulting final action an addition.
|
||||
*
|
||||
* @param clazz
|
||||
* The class type of the object to be retrieved, that acts as an additional filter criterion.
|
||||
* @param key
|
||||
* The registration key of the objects to match
|
||||
* @return The list of all objects registered under the given key and that need to be added.
|
||||
*/
|
||||
public <V extends T> List<V> getAdded(Class<V> clazz, String key) {
|
||||
List<V> addedObjects = new LinkedList<V>();
|
||||
Collection<ObjectCache<T>> allObjs = cache.values();
|
||||
for (ObjectCache<T> objectCache : allObjs) {
|
||||
if (objectCache.getOperation() == Operation.ADD && (objectCache.getKey().equals(key))) {
|
||||
if (objectCache.getObject().getClass() == clazz) {
|
||||
@SuppressWarnings("unchecked")
|
||||
V object = (V) objectCache.getObject();
|
||||
addedObjects.add(object);
|
||||
}
|
||||
}
|
||||
}
|
||||
return addedObjects;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all objects that were registered under the given key and that have as a resulting final action an update.
|
||||
*
|
||||
* @param key
|
||||
* registration key of the objects to match
|
||||
* @return The list of all objects registered under the given key and that need to be updated.
|
||||
*/
|
||||
public List<T> getUpdated(String key) {
|
||||
List<T> updatedObjects = new LinkedList<T>();
|
||||
Collection<ObjectCache<T>> allObjs = cache.values();
|
||||
for (ObjectCache<T> objectCache : allObjs) {
|
||||
if (objectCache.getOperation() == Operation.MODIFY && (objectCache.getKey().equals(key))) {
|
||||
updatedObjects.add(objectCache.getObject());
|
||||
}
|
||||
}
|
||||
return updatedObjects;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all objects that were registered under the given key and that have as a resulting final action an update.
|
||||
*
|
||||
* @param key
|
||||
* registration key of the objects to match
|
||||
* @return The list of all objects registered under the given key and that need to be updated.
|
||||
*/
|
||||
public <V extends T> List<V> getUpdated(Class<V> clazz, String key) {
|
||||
List<V> updatedObjects = new LinkedList<V>();
|
||||
Collection<ObjectCache<T>> allObjs = cache.values();
|
||||
for (ObjectCache<T> objectCache : allObjs) {
|
||||
if (objectCache.getOperation() == Operation.MODIFY && (objectCache.getKey().equals(key))) {
|
||||
if (objectCache.getObject().getClass() == clazz) {
|
||||
@SuppressWarnings("unchecked")
|
||||
V object = (V) objectCache.getObject();
|
||||
updatedObjects.add(object);
|
||||
}
|
||||
}
|
||||
}
|
||||
return updatedObjects;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all objects that were registered under the given key that have as a resulting final action their removal.
|
||||
*
|
||||
* @param key
|
||||
* The registration key of the objects to match
|
||||
* @return The list of object registered under the given key that have, as a final action, removal.
|
||||
*/
|
||||
public List<T> getRemoved(String key) {
|
||||
List<T> removedObjects = new LinkedList<T>();
|
||||
Collection<ObjectCache<T>> allObjs = cache.values();
|
||||
for (ObjectCache<T> objectCache : allObjs) {
|
||||
if (objectCache.getOperation() == Operation.REMOVE && (objectCache.getKey().equals(key))) {
|
||||
removedObjects.add(objectCache.getObject());
|
||||
}
|
||||
}
|
||||
return removedObjects;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all objects that were registered under the given key that have as a resulting final action their removal.
|
||||
*
|
||||
* @param key
|
||||
* The registration key of the objects to match
|
||||
* @return The list of object registered under the given key that have, as a final action, removal.
|
||||
*/
|
||||
public <V extends T> List<V> getRemoved(Class<V> clazz, String key) {
|
||||
List<V> removedObjects = new LinkedList<V>();
|
||||
Collection<ObjectCache<T>> allObjs = cache.values();
|
||||
for (ObjectCache<T> objectCache : allObjs) {
|
||||
if (objectCache.getOperation() == Operation.REMOVE && (objectCache.getKey().equals(key))) {
|
||||
if (objectCache.getObject().getClass() == clazz) {
|
||||
@SuppressWarnings("unchecked")
|
||||
V object = (V) objectCache.getObject();
|
||||
removedObjects.add(object);
|
||||
}
|
||||
}
|
||||
}
|
||||
return removedObjects;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the objects that were processed in this transaction, that were registered under the given key. No action
|
||||
* is associated to the object.
|
||||
*
|
||||
* @param key
|
||||
* The registration key for which the objects shall be retrieved
|
||||
* @return The list of objects matching the given key.
|
||||
*/
|
||||
public List<T> getAll(String key) {
|
||||
List<T> allObjects = new LinkedList<T>();
|
||||
Collection<ObjectCache<T>> allObjs = cache.values();
|
||||
for (ObjectCache<T> objectCache : allObjs) {
|
||||
if (objectCache.getKey().equals(key)) {
|
||||
allObjects.add(objectCache.getObject());
|
||||
}
|
||||
}
|
||||
return allObjects;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the objects that were processed in this transaction, that were registered under the given key. No action
|
||||
* is associated to the object.
|
||||
*
|
||||
* @param key
|
||||
* The registration key for which the objects shall be retrieved
|
||||
* @return The list of objects matching the given key.
|
||||
*/
|
||||
public List<ObjectCache<T>> getCache(String key) {
|
||||
List<ObjectCache<T>> allCache = new LinkedList<ObjectCache<T>>();
|
||||
Collection<ObjectCache<T>> allObjs = cache.values();
|
||||
for (ObjectCache<T> objectCache : allObjs) {
|
||||
if (objectCache.getKey().equals(key)) {
|
||||
allCache.add(objectCache);
|
||||
}
|
||||
}
|
||||
return allCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the set of keys that are currently present in the object filter.
|
||||
*
|
||||
* @return The set containing the keys of that have been added to the filter.
|
||||
*/
|
||||
public Set<String> keySet() {
|
||||
return keySet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the cache.
|
||||
*/
|
||||
public void clearCache() {
|
||||
cache.clear();
|
||||
keySet.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return get a unique transaction ID
|
||||
*/
|
||||
public synchronized long dispenseID() {
|
||||
id++;
|
||||
if (id == Long.MAX_VALUE) {
|
||||
logger.error("Rolling IDs of objectFilter back to 1. Hope this is fine.");
|
||||
id = 1;
|
||||
}
|
||||
return id;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
package ch.eitchnet.utils.objectfilter;
|
||||
|
||||
/**
|
||||
* A discrete set of operations associated to some object / state
|
||||
*
|
||||
* @author Michael Gatto <michael@gatto.ch>
|
||||
*/
|
||||
public enum Operation {
|
||||
/**
|
||||
* ADD The operation associated to something is addition.
|
||||
*/
|
||||
ADD,
|
||||
/**
|
||||
* MODIFY The operation associated to something is a modification.
|
||||
*/
|
||||
MODIFY,
|
||||
/**
|
||||
* REMOVE The operation associated to something is removal
|
||||
*/
|
||||
REMOVE;
|
||||
}
|
Loading…
Reference in New Issue