I have Escrypt Smart Card to sign data bytes and get signature and certificate for it.
I have java tool to do it and everything was fine until Java 8. Now application migrated to Java 11. And problem start from here.
SunPKCS11.jar/ library is not a part of Java 11, hence application breaks.
I tried with different solution available over internet and oracle release notes, it suggest to use java.security package for cryptographic operation.
I am not able to switch from Java 8 to java 11, any support would be really useful.
Edit-1: Minimum usable code
Signature calculator is used to parse signature
It is written with Java 8 & SunPKCS11.jar want to migrate it to Java 11
'''
import java.io.IOException;
import java.util.Scanner;
import javax.xml.bind.annotation.adapters.HexBinaryAdapter;
import sun.security.pkcs11.wrapper.CK_ATTRIBUTE;
import sun.security.pkcs11.wrapper.CK_MECHANISM;
import sun.security.pkcs11.wrapper.CK_RSA_PKCS_PSS_PARAMS;
import sun.security.pkcs11.wrapper.PKCS11;
import sun.security.pkcs11.wrapper.PKCS11Constants;
import sun.security.pkcs11.wrapper.PKCS11Exception;
public class Application {
public static void main(String[] args) {
try {
// PKCS11 middleware (UBK PKI) path
String libraryPath = "";
OpenScPkcs11 openScPkcs11 = new OpenScPkcs11(libraryPath);
openScPkcs11.login("", "");
byte[] hash = null;
String keypath = null;
String oid = null;
String authbits = null;
openScPkcs11.calcSign(hash, keypath, oid, authbits);
}
catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
class OpenScPkcs11 {
private static HexBinaryAdapter hexBinaryAdapter = new HexBinaryAdapter();
private int slotId = 0x3;
private String libraryPath;
private PKCS11 pkcs11Instance;
private String signatureAlgo = "RSA";
private long session = -1;
private boolean isLoginDone = false;
private SignatureMechanism.Algorithms algorithm = SignatureMechanism.Algorithms.SHA256;
public OpenScPkcs11(String libraryPath) throws IOException, PKCS11Exception {
this.libraryPath = libraryPath;
initializeMiddleware();
}
public void calcSign(byte[] hash, String keyPath, String userOid, String authbits) throws Exception {
byte[] signature = new byte[512];
if (this.pkcs11Instance != null) {
if (this.session < 0) {
this.openSession();
}
if (!this.isLoginDone) {
this.login("", "");
}
Mechanism mechanism = SignatureMechanism.getMechanism(this.algorithm);
CK_ATTRIBUTE[] pTemplate = new CK_ATTRIBUTE[3];
pTemplate[0] = new CK_ATTRIBUTE(PKCS11Constants.CKA_CLASS, PKCS11Constants.CKO_PRIVATE_KEY);
pTemplate[1] = new CK_ATTRIBUTE(PKCS11Constants.CKA_VENDOR_DEFINED + 1, keyPath);
pTemplate[2] = new CK_ATTRIBUTE(PKCS11Constants.CKA_VENDOR_DEFINED + 2, authbits);
// define the attibutes for certificate
CK_ATTRIBUTE[] certAttribute = new CK_ATTRIBUTE[1];
certAttribute[0] = new CK_ATTRIBUTE(PKCS11Constants.CKA_VENDOR_DEFINED + 3);
long[] c_FindObjects = null;
this.pkcs11Instance.C_FindObjectsInit(this.session, pTemplate);
c_FindObjects = this.pkcs11Instance.C_FindObjects(this.session, 32);
this.pkcs11Instance.C_FindObjectsFinal(this.session);
CK_MECHANISM pMechanism = null;
// RSA Algorithm
String signatureAlgorithmType = this.getSignatureAlgorithmType();
if (signatureAlgorithmType.equalsIgnoreCase("RSA")) {
pMechanism = new CK_MECHANISM(mechanism.getId());
CK_RSA_PKCS_PSS_PARAMS ck_RSA_PKCS_PSS_PARAMS =
new CK_RSA_PKCS_PSS_PARAMS(this.algorithm.name(), "MGF1", this.algorithm.name(), (int) mechanism.getsLen());
pMechanism.pParameter = ck_RSA_PKCS_PSS_PARAMS;
}
else if (signatureAlgorithmType.equalsIgnoreCase("ECDSA")) { // ECDSA Algorithm
pMechanism = new CK_MECHANISM(PKCS11Constants.CKM_ECDSA);
}
else {
throw new Exception("Signature algorithm " + signatureAlgorithmType + " is not supported");
}
if ((c_FindObjects != null) && (c_FindObjects.length > 0)) {
long c_FindObjectFound = c_FindObjects[0];
boolean objFound = false;
for (long c_FindObject : c_FindObjects) {
this.pkcs11Instance.C_GetAttributeValue(this.session, c_FindObject, certAttribute);
// Binary certificate as byte array
byte[] certificateBytes = certAttribute[0].getByteArray();
if ((userOid != null) && !userOid.isEmpty()) {
// Match certificate with userOid, if matches (Certificate parser used)
if (parseOidInfo(certificateBytes, userOid)) {
c_FindObjectFound = c_FindObject;
objFound = true;
break;
}
}
}
if (objFound) {
System.out.println("Signature found for given OID configuration.");
}
else {
this.pkcs11Instance.C_GetAttributeValue(this.session, c_FindObjectFound, certAttribute);
CertificateParser certificateParser = new CertificateParser(certAttribute[0].getByteArray());
}
this.pkcs11Instance.C_SignInit(this.session, pMechanism, c_FindObjectFound);
this.pkcs11Instance.C_SignUpdate(this.session, 0, hash, 0, (int) mechanism.getsLen());
signature = this.pkcs11Instance.C_SignFinal(this.session, mechanism.getSignFinalArgument());
}
else {
String errorMessage = "Unable to find keys.";
throw new Exception(errorMessage);
}
}
else {
throw new Exception("Initialize middleware first.");
}
}
/**
* #return
*/
private String getSignatureAlgorithmType() {
return this.signatureAlgo;
}
public void login(String userName, String password) throws Exception {
if (this.pkcs11Instance != null) {
openSession();
String pwd = password;
if (pwd == null || pwd.trim().isEmpty()) {
Scanner sc = new Scanner(System.in);
pwd = sc.next();
sc.close();
}
this.pkcs11Instance.C_Login(this.session, PKCS11Constants.CKU_USER, pwd.toCharArray());
this.isLoginDone = true;
}
else {
throw new Exception("Initialize middleware first.");
}
}
public void logout() throws PKCS11Exception {
if (this.pkcs11Instance != null) {
this.pkcs11Instance.C_Logout(this.session);
}
}
public void openSession() throws Exception {
if (this.pkcs11Instance != null) {
if (this.session < 0) {
long[] c_GetSlotList = this.pkcs11Instance.C_GetSlotList(true);
if ((c_GetSlotList != null) && (c_GetSlotList.length > 0)) {
for (long element : c_GetSlotList) {
if (element == this.slotId) {
this.session =
this.pkcs11Instance.C_OpenSession(this.slotId, PKCS11Constants.CKF_SERIAL_SESSION, null, null);
break;
}
}
}
}
}
else {
throw new Exception("Initialize middleware first.");
}
}
public void closeSession() throws PKCS11Exception {
if ((this.pkcs11Instance != null) && (this.session >= 0)) {
this.pkcs11Instance.C_CloseSession(this.session);
this.session = -1;
}
}
public void initializeMiddleware(String libraryPath1) throws IOException, PKCS11Exception {
this.pkcs11Instance = PKCS11.getInstance(libraryPath1, "C_GetFunctionList", null, false);
this.libraryPath = libraryPath1;
}
public void initializeMiddleware() throws IOException, PKCS11Exception {
this.pkcs11Instance = PKCS11.getInstance(this.libraryPath, "C_GetFunctionList", null, false);
}
public static String getString(final byte[] data) {
if ((data != null) && (data.length > 0)) {
return hexBinaryAdapter.marshal(data);
}
return null;
}
}
class SignatureMechanism {
public static enum Algorithms {
SHA256(0x250),
/**
* Hash calculation Algorithm
*/
RIPEMD160(0x240),
/**
* Hash calculation Algorithm
*/
RIPEMD160_1(0x0),
/**
* Secure Hash Algorithm 512 for hash Calculation
*/
SHA512(0x251);
private int value = 0;
private Algorithms(final int algorithmValue) {
this.value = algorithmValue;
}
/**
* #return the hash value
*/
public int getValue() {
return this.value;
}
}
/**
* #param algorithm : Algorithm used for hash calculation
* #return signature mechanism
*/
public static Mechanism getMechanism(final Algorithms algorithm) {
Mechanism mechanism = new Mechanism();
if (algorithm == Algorithms.SHA256) {
mechanism.setHashAlg(PKCS11Constants.CKM_SHA256);
mechanism.setMgf(PKCS11Constants.CKG_MGF1_SHA1 + 1);
mechanism.setsLen(32);
mechanism.setSignFinalArgument(512);
} // TODO Verify with ETAS Middleware team
else if (algorithm == Algorithms.SHA512) {
mechanism.setHashAlg(PKCS11Constants.CKM_SHA512);
mechanism.setMgf(PKCS11Constants.CKG_MGF1_SHA1 + 1);
mechanism.setsLen(64);
mechanism.setSignFinalArgument(1024);
}
else if (algorithm == Algorithms.RIPEMD160) {
mechanism.setHashAlg(PKCS11Constants.CKM_RIPEMD160);
mechanism.setMgf(0x80000001); // hard coded becuase it is defined by escrypt and not present in PKCS11
mechanism.setsLen(20);
}
else if (algorithm == Algorithms.RIPEMD160_1) {
mechanism.setId(PKCS11Constants.CKM_RIPEMD160_RSA_PKCS);
}
return mechanism;
}
}
class Mechanism{
private long hashAlg;
private long mgf;
private long sLen;
private long ulMaxObjectCount = 32;
private int signFinalArgument = 128;
private long id = PKCS11Constants.CKM_RSA_PKCS_PSS;
/**
* #return the hashAlg
*/
public long getHashAlg() {
return hashAlg;
}
/**
* #param hashAlg the hashAlg to set
*/
public void setHashAlg(long hashAlg) {
this.hashAlg = hashAlg;
}
/**
* #return the mgf
*/
public long getMgf() {
return mgf;
}
/**
* #param mgf the mgf to set
*/
public void setMgf(long mgf) {
this.mgf = mgf;
}
/**
* #return the sLen
*/
public long getsLen() {
return sLen;
}
/**
* #param sLen the sLen to set
*/
public void setsLen(long sLen) {
this.sLen = sLen;
}
/**
* #return the ulMaxObjectCount
*/
public long getUlMaxObjectCount() {
return ulMaxObjectCount;
}
/**
* #param ulMaxObjectCount the ulMaxObjectCount to set
*/
public void setUlMaxObjectCount(long ulMaxObjectCount) {
this.ulMaxObjectCount = ulMaxObjectCount;
}
/**
* #return the signFinalArgument
*/
public int getSignFinalArgument() {
return signFinalArgument;
}
/**
* #param signFinalArgument the signFinalArgument to set
*/
public void setSignFinalArgument(int signFinalArgument) {
this.signFinalArgument = signFinalArgument;
}
/**
* #return the id
*/
public long getId() {
return id;
}
/**
* #param id the id to set
*/
public void setId(long id) {
this.id = id;
}
}
'''
Thank You!
Java 9 up no longer puts the standard-library in jar files (rt.jar, etc) but they are still there. Your problem is probably (though I didn't read through all the code and certainly can't test without your hardware) that Java 9 up also introduces modules. Much as in Java forever your code can reference another class or interface (or static member therof) by its simple name only if you import it or it is in the same package as your code (if any) or the special package java.lang, now in 9 up you can only access a package if it is in the same module as your code or the special java.base module or your module and/or the other module explicitly allow the access; the explicit specification closet to older Java is an 'open'.
Thus the quick solution is to add to your java command (explicitly or via _JAVA_OPTS or equivalent) --add-opens jdk.crypto.cryptoki/sun.security.pkcs11.*=ALL-UNNAMED -- this should produce substantially the same result as running on 8 (or lower).
The official solution is to put your code in a module -- either by itself or with other related code -- that requires the (or each) needed module, given it exports the needed parts, which I haven't checked for yours. However, since your code apparently isn't even in a package yet, putting it in a module will be some work.
The javadoc for StandardWatchEventKinds.ENTRY_MODIFY says:
Directory entry modified. When a directory is registered for this
event then the WatchKey is queued when it is observed that an entry in
the directory has been modified. The event count for this event is 1
or greater.
When you edit the content of a file through an editor, it'll modify both date (or other metadata) and content. You therefore get two ENTRY_MODIFY events, but each will have a count of 1 (at least that's what I'm seeing).
I'm trying to monitor a configuration file (servers.cfg previously registered with the WatchService) that is manually updated (ie. through command line vi) with the following code:
while(true) {
watchKey = watchService.take(); // blocks
for (WatchEvent<?> event : watchKey.pollEvents()) {
WatchEvent<Path> watchEvent = (WatchEvent<Path>) event;
WatchEvent.Kind<Path> kind = watchEvent.kind();
System.out.println(watchEvent.context() + ", count: "+ watchEvent.count() + ", event: "+ watchEvent.kind());
// prints (loop on the while twice)
// servers.cfg, count: 1, event: ENTRY_MODIFY
// servers.cfg, count: 1, event: ENTRY_MODIFY
switch(kind.name()) {
case "ENTRY_MODIFY":
handleModify(watchEvent.context()); // reload configuration class
break;
case "ENTRY_DELETE":
handleDelete(watchEvent.context()); // do something else
break;
}
}
watchKey.reset();
}
Since you get two ENTRY_MODIFY events, the above would reload the configuration twice when only once is needed. Is there any way to ignore all but one of these, assuming there could be more than one such event?
If the WatchService API has such a utility so much the better. (I kind of don't want to check times between each event. All the handler methods in my code are synchronous.
The same thing occurs if you create (copy/paste) a file from one directory to the watched directory. How can you combine both of those into one event?
WatcherServices reports events twice because the underlying file is updated twice. Once for the content and once for the file modified time. These events happen within a short time span. To solve this, sleep between the poll() or take() calls and the key.pollEvents() call. For example:
#Override
#SuppressWarnings( "SleepWhileInLoop" )
public void run() {
setListening( true );
while( isListening() ) {
try {
final WatchKey key = getWatchService().take();
final Path path = get( key );
// Prevent receiving two separate ENTRY_MODIFY events: file modified
// and timestamp updated. Instead, receive one ENTRY_MODIFY event
// with two counts.
Thread.sleep( 50 );
for( final WatchEvent<?> event : key.pollEvents() ) {
final Path changed = path.resolve( (Path)event.context() );
if( event.kind() == ENTRY_MODIFY && isListening( changed ) ) {
System.out.println( "Changed: " + changed );
}
}
if( !key.reset() ) {
ignore( path );
}
} catch( IOException | InterruptedException ex ) {
// Stop eavesdropping.
setListening( false );
}
}
}
Calling sleep() helps eliminate the double calls. The delay might have to be as high as three seconds.
I had a similar issue - I am using the WatchService API to keep directories in sync, but observed that in many cases, updates were being performed twice. I seem to have resolved the issue by checking the timestamp on the files - this seems to screen out the second copy operation. (At least in windows 7 - I can't be sure if it will work correctly in other operation systems)
Maybe you could use something similar? Store the timestamp from the file and reload only when the timestamp is updated?
One of my goto solutions for problems like this is to simply queue up the unique event resources and delay processing for an acceptable amount of time. In this case I maintain a Set<String> that contains every file name derived from each event that arrives. Using a Set<> ensures that duplicates don't get added and, therefore, will only be processed once (per delay period).
Each time an interesting event arrives I add the file name to the Set<> and restart my delay timer. When things settle down and the delay period elapses, I proceed to processing the files.
The addFileToProcess() and processFiles() methods are 'synchronized' to ensure that no ConcurrentModificationExceptions are thrown.
This simplified/standalone example is a derivative of Oracle's WatchDir.java:
import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE;
import static java.nio.file.StandardWatchEventKinds.ENTRY_DELETE;
import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY;
import static java.nio.file.StandardWatchEventKinds.OVERFLOW;
import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
public class DirectoryWatcherService implements Runnable {
#SuppressWarnings("unchecked")
static <T> WatchEvent<T> cast(WatchEvent<?> event) {
return (WatchEvent<T>)event;
}
/*
* Wait this long after an event before processing the files.
*/
private final int DELAY = 500;
/*
* Use a SET to prevent duplicates from being added when multiple events on the
* same file arrive in quick succession.
*/
HashSet<String> filesToReload = new HashSet<String>();
/*
* Keep a map that will be used to resolve WatchKeys to the parent directory
* so that we can resolve the full path to an event file.
*/
private final Map<WatchKey,Path> keys;
Timer processDelayTimer = null;
private volatile Thread server;
private boolean trace = false;
private WatchService watcher = null;
public DirectoryWatcherService(Path dir, boolean recursive)
throws IOException {
this.watcher = FileSystems.getDefault().newWatchService();
this.keys = new HashMap<WatchKey,Path>();
if (recursive) {
registerAll(dir);
} else {
register(dir);
}
// enable trace after initial registration
this.trace = true;
}
private synchronized void addFileToProcess(String filename) {
boolean alreadyAdded = filesToReload.add(filename) == false;
System.out.println("Queuing file for processing: "
+ filename + (alreadyAdded?"(already queued)":""));
if (processDelayTimer != null) {
processDelayTimer.cancel();
}
processDelayTimer = new Timer();
processDelayTimer.schedule(new TimerTask() {
#Override
public void run() {
processFiles();
}
}, DELAY);
}
private synchronized void processFiles() {
/*
* Iterate over the set of file to be processed
*/
for (Iterator<String> it = filesToReload.iterator(); it.hasNext();) {
String filename = it.next();
/*
* Sometimes you just have to do what you have to do...
*/
System.out.println("Processing file: " + filename);
/*
* Remove this file from the set.
*/
it.remove();
}
}
/**
* Register the given directory with the WatchService
*/
private void register(Path dir) throws IOException {
WatchKey key = dir.register(watcher, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
if (trace) {
Path prev = keys.get(key);
if (prev == null) {
System.out.format("register: %s\n", dir);
} else {
if (!dir.equals(prev)) {
System.out.format("update: %s -> %s\n", prev, dir);
}
}
}
keys.put(key, dir);
}
/**
* Register the given directory, and all its sub-directories, with the
* WatchService.
*/
private void registerAll(final Path start) throws IOException {
// register directory and sub-directories
Files.walkFileTree(start, new SimpleFileVisitor<Path>() {
#Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs)
throws IOException
{
if (dir.getFileName().toString().startsWith(".")) {
return FileVisitResult.SKIP_SUBTREE;
}
register(dir);
return FileVisitResult.CONTINUE;
}
});
}
#SuppressWarnings("unchecked")
#Override
public void run() {
Thread thisThread = Thread.currentThread();
while (server == thisThread) {
try {
// wait for key to be signaled
WatchKey key;
try {
key = watcher.take();
} catch (InterruptedException x) {
return;
}
Path dir = keys.get(key);
if (dir == null) {
continue;
}
for (WatchEvent<?> event: key.pollEvents()) {
WatchEvent.Kind<?> kind = event.kind();
if (kind == OVERFLOW) {
continue;
}
if (kind == ENTRY_MODIFY) {
WatchEvent<Path> ev = (WatchEvent<Path>)event;
Path name = ev.context();
Path child = dir.resolve(name);
String filename = child.toAbsolutePath().toString();
addFileToProcess(filename);
}
}
key.reset();
} catch (Exception e) {
e.printStackTrace();
}
}
}
public void start() {
server = new Thread(this);
server.setName("Directory Watcher Service");
server.start();
}
public void stop() {
Thread moribund = server;
server = null;
if (moribund != null) {
moribund.interrupt();
}
}
public static void main(String[] args) {
if (args==null || args.length == 0) {
System.err.println("You need to provide a path to watch!");
System.exit(-1);
}
Path p = Paths.get(args[0]);
if (!Files.isDirectory(p)) {
System.err.println(p + " is not a directory!");
System.exit(-1);
}
DirectoryWatcherService watcherService;
try {
watcherService = new DirectoryWatcherService(p, true);
watcherService.start();
} catch (IOException e) {
System.err.println(e.getMessage());
}
}
}
I modified WatchDir.java to receive only human-made modifications. Comparing .lastModified() of a file.
long lastModi=0; //above for loop
if(kind==ENTRY_CREATE){
System.out.format("%s: %s\n", event.kind().name(), child);
}else if(kind==ENTRY_MODIFY){
if(child.toFile().lastModified() - lastModi > 1000){
System.out.format("%s: %s\n", event.kind().name(), child);
}
}else if(kind==ENTRY_DELETE){
System.out.format("%s: %s\n", event.kind().name(), child);
}
lastModi=child.toFile().lastModified();
Here is a full implementation using timestamps to avoid firing multiple events:
import java.io.File;
import java.io.IOException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.HashMap;
import java.util.Map;
import static java.nio.file.LinkOption.NOFOLLOW_LINKS;
import static java.nio.file.StandardWatchEventKinds.*;
public abstract class DirectoryWatcher
{
private WatchService watcher;
private Map<WatchKey, Path> keys;
private Map<Path, Long> fileTimeStamps;
private boolean recursive;
private boolean trace = true;
#SuppressWarnings("unchecked")
private static <T> WatchEvent<T> cast(WatchEvent<?> event)
{
return (WatchEvent<T>) event;
}
/**
* Register the given directory with the WatchService
*/
private void register(Path directory) throws IOException
{
WatchKey watchKey = directory.register(watcher, ENTRY_MODIFY, ENTRY_CREATE, ENTRY_DELETE);
addFileTimeStamps(directory);
if (trace)
{
Path existingFilePath = keys.get(watchKey);
if (existingFilePath == null)
{
System.out.format("register: %s\n", directory);
} else
{
if (!directory.equals(existingFilePath))
{
System.out.format("update: %s -> %s\n", existingFilePath, directory);
}
}
}
keys.put(watchKey, directory);
}
private void addFileTimeStamps(Path directory)
{
File[] files = directory.toFile().listFiles();
if (files != null)
{
for (File file : files)
{
if (file.isFile())
{
fileTimeStamps.put(file.toPath(), file.lastModified());
}
}
}
}
/**
* Register the given directory, and all its sub-directories, with the
* WatchService.
*/
private void registerAll(Path directory) throws IOException
{
Files.walkFileTree(directory, new SimpleFileVisitor<Path>()
{
#Override
public FileVisitResult preVisitDirectory(Path currentDirectory, BasicFileAttributes attrs)
throws IOException
{
register(currentDirectory);
return FileVisitResult.CONTINUE;
}
});
}
/**
* Creates a WatchService and registers the given directory
*/
DirectoryWatcher(Path directory, boolean recursive) throws IOException
{
this.watcher = FileSystems.getDefault().newWatchService();
this.keys = new HashMap<>();
fileTimeStamps = new HashMap<>();
this.recursive = recursive;
if (recursive)
{
System.out.format("Scanning %s ...\n", directory);
registerAll(directory);
System.out.println("Done.");
} else
{
register(directory);
}
// enable trace after initial registration
this.trace = true;
}
/**
* Process all events for keys queued to the watcher
*/
void processEvents() throws InterruptedException, IOException
{
while (true)
{
WatchKey key = watcher.take();
Path dir = keys.get(key);
if (dir == null)
{
System.err.println("WatchKey not recognized!!");
continue;
}
for (WatchEvent<?> event : key.pollEvents())
{
WatchEvent.Kind watchEventKind = event.kind();
// TBD - provide example of how OVERFLOW event is handled
if (watchEventKind == OVERFLOW)
{
continue;
}
// Context for directory entry event is the file name of entry
WatchEvent<Path> watchEvent = cast(event);
Path fileName = watchEvent.context();
Path filePath = dir.resolve(fileName);
long oldFileModifiedTimeStamp = fileTimeStamps.get(filePath);
long newFileModifiedTimeStamp = filePath.toFile().lastModified();
if (newFileModifiedTimeStamp > oldFileModifiedTimeStamp)
{
fileTimeStamps.remove(filePath);
onEventOccurred();
fileTimeStamps.put(filePath, filePath.toFile().lastModified());
}
if (recursive && watchEventKind == ENTRY_CREATE)
{
if (Files.isDirectory(filePath, NOFOLLOW_LINKS))
{
registerAll(filePath);
}
}
break;
}
boolean valid = key.reset();
if (!valid)
{
keys.remove(key);
if (keys.isEmpty())
{
break;
}
}
}
}
public abstract void onEventOccurred();
}
Extend the class and implement the onEventOccurred() method.
Are you sure there is problem with jdk7? It gives correct result for me (jdk7u15, windows)
Code
import java.io.IOException;
import java.nio.file.*;
public class WatchTest {
public void watchMyFiles() throws IOException, InterruptedException {
Path path = Paths.get("c:/temp");
WatchService watchService = path.getFileSystem().newWatchService();
path.register(watchService, StandardWatchEventKinds.ENTRY_MODIFY);
while (true) {
WatchKey watchKey = watchService.take(); // blocks
for (WatchEvent<?> event : watchKey.pollEvents()) {
WatchEvent<Path> watchEvent = (WatchEvent<Path>) event;
WatchEvent.Kind<Path> kind = watchEvent.kind();
System.out.println(watchEvent.context() + ", count: " +
watchEvent.count() + ", event: " + watchEvent.kind());
// prints (loop on the while twice)
// servers.cfg, count: 1, event: ENTRY_MODIFY
// servers.cfg, count: 1, event: ENTRY_MODIFY
switch (kind.name()) {
case "ENTRY_MODIFY":
handleModify(watchEvent.context()); // reload configuration class
break;
case "ENTRY_DELETE":
handleDelete(watchEvent.context()); // do something else
break;
default:
System.out.println("Event not expected " + event.kind().name());
}
}
watchKey.reset();
}
}
private void handleDelete(Path context) {
System.out.println("handleDelete " + context.getFileName());
}
private void handleModify(Path context) {
System.out.println("handleModify " + context.getFileName());
}
public static void main(String[] args) throws IOException, InterruptedException {
new WatchTest().watchMyFiles();
}
}
Output is like below- when file is copied over or edited using notepad.
config.xml, count: 1, event: ENTRY_MODIFY
handleModify config.xml
Vi uses many additional files, and seems to update file attribute multiple times. notepad++ does exactly two times.
If you use RxJava you can use the operator throttleLast. In the example below only the last event in 1000 milliseconds is emitted for each file in the watched directory.
public class FileUtils {
private static final long EVENT_DELAY = 1000L;
public static Observable<FileWatchEvent> watch(Path directory, String glob) {
return Observable.<FileWatchEvent>create(subscriber -> {
final PathMatcher matcher = directory.getFileSystem().getPathMatcher("glob:" + glob);
WatchService watcher = FileSystems.getDefault().newWatchService();
subscriber.setCancellable(watcher::close);
try {
directory.register(watcher,
ENTRY_CREATE,
ENTRY_DELETE,
ENTRY_MODIFY);
} catch (IOException e) {
subscriber.onError(e);
return;
}
while (!subscriber.isDisposed()) {
WatchKey key;
try {
key = watcher.take();
} catch (InterruptedException e) {
if (subscriber.isDisposed())
subscriber.onComplete();
else
subscriber.onError(e);
return;
}
for (WatchEvent<?> event : key.pollEvents()) {
WatchEvent.Kind<?> kind = event.kind();
if (kind != OVERFLOW) {
WatchEvent<Path> ev = (WatchEvent<Path>) event;
Path child = directory.resolve(ev.context());
if (matcher.matches(child.getFileName()))
subscriber.onNext(new FileWatchEvent(kindToType(kind), child));
}
}
if (!key.reset()) {
subscriber.onError(new IOException("Invalid key"));
return;
}
}
}).groupBy(FileWatchEvent::getPath).flatMap(o -> o.throttleLast(EVENT_DELAY, TimeUnit.MILLISECONDS));
}
private static FileWatchEvent.Type kindToType(WatchEvent.Kind kind) {
if (StandardWatchEventKinds.ENTRY_CREATE.equals(kind))
return FileWatchEvent.Type.ADDED;
else if (StandardWatchEventKinds.ENTRY_MODIFY.equals(kind))
return FileWatchEvent.Type.MODIFIED;
else if (StandardWatchEventKinds.ENTRY_DELETE.equals(kind))
return FileWatchEvent.Type.DELETED;
throw new RuntimeException("Invalid kind: " + kind);
}
public static class FileWatchEvent {
public enum Type {
ADDED, DELETED, MODIFIED
}
private Type type;
private Path path;
public FileWatchEvent(Type type, Path path) {
this.type = type;
this.path = path;
}
public Type getType() {
return type;
}
public Path getPath() {
return path;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
FileWatchEvent that = (FileWatchEvent) o;
if (type != that.type) return false;
return path != null ? path.equals(that.path) : that.path == null;
}
#Override
public int hashCode() {
int result = type != null ? type.hashCode() : 0;
result = 31 * result + (path != null ? path.hashCode() : 0);
return result;
}
}
}
I solved this problem by defining a global boolean variable named "modifySolver" which be false by default. You can handle this problem as I show bellow:
else if (eventKind.equals (ENTRY_MODIFY))
{
if (event.count() == 2)
{
getListener(getDirPath(key)).onChange (FileChangeType.MODIFY, file.toString ());
}
/*capture first modify event*/
else if ((event.count() == 1) && (!modifySolver))
{
getListener(getDirPath(key)).onChange (FileChangeType.MODIFY, file.toString ());
modifySolver = true;
}
/*discard the second modify event*/
else if ((event.count() == 1) && (modifySolver))
{
modifySolver = false;
}
}
I compiled Oracle's WatchDir.java and #nilesh's suggestion into an Observable class that will notify it's observers once when the watched file is changed.
I tried to make it as readable and short as possible, but still landed with more than 100 lines. Improvements welcome, of course.
Usage:
FileChangeNotifier fileReloader = new FileChangeNotifier(File file);
fileReloader.addObserver((Observable obj, Object arg) -> {
System.out.println("File changed for the " + arg + " time.");
});
See my solution on GitHub: FileChangeNotifier.java.
I tried this and it's working perfectly :
import java.io.IOException;
import java.nio.file.*;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import static java.nio.file.StandardWatchEventKinds.*;
public class FileWatcher implements Runnable, AutoCloseable {
private final WatchService service;
private final Map<Path, WatchTarget> watchTargets = new HashMap<>();
private final List<FileListener> fileListeners = new CopyOnWriteArrayList<>();
private final ReadWriteLock lock = new ReentrantReadWriteLock();
private final Lock r = lock.readLock();
private final Lock w = lock.writeLock();
private final AtomicBoolean running = new AtomicBoolean(false);
public FileWatcher() throws IOException {
service = FileSystems.getDefault().newWatchService();
}
#Override
public void run() {
if (running.compareAndSet(false, true)) {
while (running.get()) {
WatchKey key;
try {
key = service.take();
} catch (Throwable e) {
break;
}
if (key.isValid()) {
r.lock();
try {
key.pollEvents().stream()
.filter(e -> e.kind() != OVERFLOW)
.forEach(e -> watchTargets.values().stream()
.filter(t -> t.isInterested(e))
.forEach(t -> fireOnEvent(t.path, e.kind())));
} finally {
r.unlock();
}
if (!key.reset()) {
break;
}
}
}
running.set(false);
}
}
public boolean registerPath(Path path, boolean updateIfExists, WatchEvent.Kind... eventKinds) {
w.lock();
try {
WatchTarget target = watchTargets.get(path);
if (!updateIfExists && target != null) {
return false;
}
Path parent = path.getParent();
if (parent != null) {
if (target == null) {
watchTargets.put(path, new WatchTarget(path, eventKinds));
parent.register(service, eventKinds);
} else {
target.setEventKinds(eventKinds);
}
return true;
}
} catch (Throwable e) {
e.printStackTrace();
} finally {
w.unlock();
}
return false;
}
public void addFileListener(FileListener fileListener) {
fileListeners.add(fileListener);
}
public void removeFileListener(FileListener fileListener) {
fileListeners.remove(fileListener);
}
private void fireOnEvent(Path path, WatchEvent.Kind eventKind) {
for (FileListener fileListener : fileListeners) {
fileListener.onEvent(path, eventKind);
}
}
public boolean isRunning() {
return running.get();
}
#Override
public void close() throws IOException {
running.set(false);
w.lock();
try {
service.close();
} finally {
w.unlock();
}
}
private final class WatchTarget {
private final Path path;
private final Path fileName;
private final Set<String> eventNames = new HashSet<>();
private final Event lastEvent = new Event();
private WatchTarget(Path path, WatchEvent.Kind[] eventKinds) {
this.path = path;
this.fileName = path.getFileName();
setEventKinds(eventKinds);
}
private void setEventKinds(WatchEvent.Kind[] eventKinds) {
eventNames.clear();
for (WatchEvent.Kind k : eventKinds) {
eventNames.add(k.name());
}
}
private boolean isInterested(WatchEvent e) {
long now = System.currentTimeMillis();
String name = e.kind().name();
if (e.context().equals(fileName) && eventNames.contains(name)) {
if (lastEvent.name == null || !lastEvent.name.equals(name) || now - lastEvent.when > 100) {
lastEvent.name = name;
lastEvent.when = now;
return true;
}
}
return false;
}
#Override
public int hashCode() {
return path.hashCode();
}
#Override
public boolean equals(Object obj) {
return obj == this || obj != null && obj instanceof WatchTarget && Objects.equals(path, ((WatchTarget) obj).path);
}
}
private final class Event {
private String name;
private long when;
}
public static void main(String[] args) throws IOException, InterruptedException {
FileWatcher watcher = new FileWatcher();
if (watcher.registerPath(Paths.get("filename"), false, ENTRY_MODIFY, ENTRY_CREATE, ENTRY_DELETE)) {
watcher.addFileListener((path, eventKind) -> System.out.println(path + " -> " + eventKind.name()));
new Thread(watcher).start();
System.in.read();
}
watcher.close();
System.exit(0);
}
}
FileListener :
import java.nio.file.Path;
import java.nio.file.WatchEvent;
public interface FileListener {
void onEvent(Path path, WatchEvent.Kind eventKind);
}
I had similar problem. I know this is late but it might help someone.
I just needed to eliminate duplicate ENTRY_MODIFY.
Whenever ENTRY_MODIFY is triggered, count() returns either 2 or 1. If it is 1, then there will be another event with count() 1.
So just put a global counter which keeps the count of return values and carry out the operations only when the counter becomes 2. Something like this can do:
WatchEvent event;
int count = 0;
if(event.count() == 2)
count = 2;
if(event.count() == 1)
count++;
if(count == 2){
//your operations here
count = 0;
}
/**
*
*
* in windows os, multiple event will be fired for a file create action
* this method will combine the event on same file
*
* for example:
*
* pathA -> createEvent -> createEvent
* pathA -> createEvent + modifyEvent, .... -> modifyEvent
* pathA -> createEvent + modifyEvent, ...., deleteEvent -> deleteEvent
*
*
*
* 在windows环境下创建一个文件会产生1个创建事件+多个修改事件, 这个方法用于合并重复事件
* 合并优先级为 删除 > 更新 > 创建
*
*
* #param events
* #return
*/
private List<WatchEvent<?>> filterEvent(List<WatchEvent<?>> events) {
// sorted by event create > modify > delete
Comparator<WatchEvent<?>> eventComparator = (eventA, eventB) -> {
HashMap<WatchEvent.Kind, Integer> map = new HashMap<>();
map.put(StandardWatchEventKinds.ENTRY_CREATE, 0);
map.put(StandardWatchEventKinds.ENTRY_MODIFY, 1);
map.put(StandardWatchEventKinds.ENTRY_DELETE, 2);
return map.get(eventA.kind()) - map.get(eventB.kind());
};
events.sort(eventComparator);
HashMap<String, WatchEvent<?>> hashMap = new HashMap<>();
for (WatchEvent<?> event : events) {
// if this is multiple event on same path
// the create event will added first
// then override by modify event
// then override by delete event
hashMap.put(event.context().toString(), event);
}
return new ArrayList<>(hashMap.values());
}
If you are trying the same in Scala using better-files-akka library, I have comeup with this work around based on the solution proposed in the accepted answer.
https://github.com/pathikrit/better-files/issues/313
trait ConfWatcher {
implicit def actorSystem: ActorSystem
private val confPath = "/home/codingkapoor/application.conf"
private val appConfFile = File(confPath)
private var appConfLastModified = appConfFile.lastModifiedTime
val watcher: ActorRef = appConfFile.newWatcher(recursive = false)
watcher ! on(EventType.ENTRY_MODIFY) { file =>
if (appConfLastModified.compareTo(file.lastModifiedTime) < 0) {
// TODO
appConfLastModified = file.lastModifiedTime
}
}
}
The set of multiple events depends on the tool used to create/modify files.
1.Create new file with Vim
MODIFIED, CREATED events are fired
2.Modify file with Vim
DELETED, MODIFIED, CREATED events are fired
3.Use Linux command 'mv' to move file from other folder to watched folder
MODIFIED event is fired
4.Use Linux command 'cp' to copy file from other folder to watched folder
MODIFIED, CREATED are fired if no file with same file name exists
CREATED is fired if file with same file name exists
I used 3 maps to collect CREATED/MODIFIED/DELETED entries in the for loop iterating over WatchEvent. Then, run 3 for loops over those 3 maps to determine the correct event we need to notify (ex: if a file name appears in all three map, then we could say it is a MODIFIED event)
File f = new File(sourceDir);
if (!f.exists() || !f.isDirectory()) {
LOGGER.warn("File " + sourceDir + " does not exist OR is not a directory");
return;
}
WatchService watchService = FileSystems.getDefault().newWatchService();
Path watchedDir = f.toPath();
watchedDir.register(watchService, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
while (true) {
try {
WatchKey watchKey = watchService.take();
Thread.sleep(checkInterval);
//Events fired by Java NIO file watcher depends on the tool used
//to create/update/delete file. We need those 3 maps to collect
//all entries fired by NIO watcher. Then, make 3 loops at the end
//to determine the correct unique event to notify
final Map<String, Boolean> createdEntries = new HashMap<>();
final Map<String, Boolean> modifiedEntries = new HashMap<>();
final Map<String, Boolean> deletedEntries = new HashMap<>();
List<WatchEvent<?>> events = watchKey.pollEvents();
for (WatchEvent<?> event : events) {
if (event.kind() == OVERFLOW) {
continue;
}
WatchEvent<Path> pathEvent = (WatchEvent<Path>) event;
WatchEvent.Kind<Path> kind = pathEvent.kind();
Path path = pathEvent.context();
String fileName = path.toString();
if (accept(fileName)) {
if (kind == ENTRY_CREATE) {
createdEntries.put(fileName, true);
} else if (kind == ENTRY_MODIFY) {
modifiedEntries.put(fileName, true);
} else if (kind == ENTRY_DELETE) {
deletedEntries.put(fileName, true);
}
}
}
long timeStamp = System.currentTimeMillis();
final Map<String, Boolean> handledEntries = new HashMap<>();
//3 for loops to determine correct event to notify
for (String key : createdEntries.keySet()) {
if (handledEntries.get(key) == null) {
Boolean modified = modifiedEntries.get(key);
Boolean deleted = deletedEntries.get(key);
if (modified != null && deleted != null) {
//A triplet of DELETED/MODIFIED/CREATED means a MODIFIED event
LOGGER.debug("File " + key + " was modified");
notifyFileModified(key, timeStamp);
} else if (modified != null) {
LOGGER.debug("New file " + key + " was created");
notifyFileCreated(key, timeStamp);
} else {
LOGGER.debug("New file " + key + " was created");
notifyFileCreated(key, timeStamp);
}
handledEntries.put(key, true);
}
}
for (String key : modifiedEntries.keySet()) {
if (handledEntries.get(key) == null) {
//Current entry survives from loop on CREATED entries. It is certain
//that we have MODIFIED event
LOGGER.debug("File " + key + " was modified");
notifyFileModified(key, timeStamp);
handledEntries.put(key, true);
}
}
for (String key : deletedEntries.keySet()) {
if (handledEntries.get(key) == null) {
//Current entry survives from two loops on CREATED/MODIFIED entries. It is certain
//that we have DELETE event
LOGGER.debug("File " + key + " was deleted");
notifyFileDeleted(key, timeStamp);
}
}
boolean valid = watchKey.reset();
if (!valid) {
break;
}
} catch (Exception ex) {
LOGGER.warn("Error while handling file events under: " + sourceDir, ex);
}
Thread.sleep(checkInterval);
}
Untested, but perhaps this will work:
AtomicBoolean modifyEventFired = new AtomicBoolean();
modifyEventFired.set(false);
while(true) {
watchKey = watchService.take(); // blocks
for (WatchEvent<?> event : watchKey.pollEvents()) {
WatchEvent<Path> watchEvent = (WatchEvent<Path>) event;
WatchEvent.Kind<Path> kind = watchEvent.kind();
System.out.println(watchEvent.context() + ", count: "+ watchEvent.count() + ", event: "+ watchEvent.kind());
// prints (loop on the while twice)
// servers.cfg, count: 1, event: ENTRY_MODIFY
// servers.cfg, count: 1, event: ENTRY_MODIFY
switch(kind.name()) {
case "ENTRY_MODIFY":
if(!modifyEventFired.get()){
handleModify(watchEvent.context()); // reload configuration class
modifyEventFired.set(true);
}
break;
case "ENTRY_DELETE":
handleDelete(watchEvent.context()); // do something else
break;
}
}
modifyEventFired.set(false);
watchKey.reset();
}
We are aware of the issue with jar softlinker
http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6967414
http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6805618
and have used following class (found on web and modified to take care of JAVA 7 as well.)
import java.io.IOException;
import java.lang.ref.SoftReference;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.util.*;
import java.util.jar.JarFile;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* A utility class for working around the java webstart jar signing/security bug
* <p/>
* see http://bugs.sun.com/view_bug.do?bug_id=6967414 and http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6805618
*
* #author Scott Chan
*/
public class JarSignersHardLinker {
private static final String JRE_1_DOT = "1.";
private static final String DOT_ZERO_UNDERSCORE = ".0_";
/**
* the 1.6.0 update where this problem first occurred
*/
private static final int PROBLEM_JRE_UPDATE = 19;
private static final int PROBLEM_JRE_MAJOR_VERSION = 6;
public static final List sm_hardRefs = new ArrayList();
protected static void makeHardSignersRef(JarFile jar) throws java.io.IOException {
if (jar != null && jar.getClass().getName().equals("com.sun.deploy.cache.CachedJarFile")) {
Logger.info("Making hard refs for: " + jar.getName());
//lets attempt to get at the each of the soft links.
//first need to call the relevant no-arg method to ensure that the soft ref is populated
//then we access the private member, resolve the softlink and throw it in a static list.
callNoArgMethod("getSigners", jar);
makeHardLink("signersRef", jar);
callNoArgMethod("getSignerMap", jar);
makeHardLink("signerMapRef", jar);
// callNoArgMethod("getCodeSources", jar);
// makeHardLink("codeSourcesRef", jar);
callNoArgMethod("getCodeSourceCache", jar);
makeHardLink("codeSourceCacheRef", jar);
}
}
/**
* if the specified field for the given instance is a Softreference
* That soft reference is resolved and the returned ref is stored in a static list,
* making it a hard link that should never be garbage collected
*
* #param fieldName
* #param instance
*/
private static void makeHardLink(String fieldName, Object instance) {
//System.out.println("attempting hard ref to " + instance.getClass().getName() + "." + fieldName);
try {
Field signersRef = instance.getClass().getDeclaredField(fieldName);
signersRef.setAccessible(true);
Object o = signersRef.get(instance);
if (o instanceof SoftReference) {
SoftReference r = (SoftReference) o;
Object o2 = r.get();
sm_hardRefs.add(o2);
} else {
Logger.warn(fieldName + ": is not an instance of soft reference");
}
} catch (NoSuchFieldException e) {
e.printStackTrace();
return;
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
/**
* Call the given no-arg method on the given instance
*
* #param methodName
* #param instance
*/
private static void callNoArgMethod(String methodName, Object instance) {
// System.out.println("calling noarg method hard ref to " + instance.getClass().getName() + "." + methodName + "()");
try {
Method m = instance.getClass().getDeclaredMethod(methodName);
m.setAccessible(true);
m.invoke(instance);
} catch (SecurityException e1) {
e1.printStackTrace();
} catch (NoSuchMethodException e1) {
e1.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
/**
* is the preloader enabled. ie: will the preloader run in the current environment
*
* #return
*/
public static boolean isHardLinkerEnabled() {
boolean isHardLinkerDisabled = false; //change this to use whatever mechanism you use to enable or disable the preloader
return !isHardLinkerDisabled && isRunningOnJre1_6_0_19OrHigher() && isRunningOnWebstart();
}
/**
* is the application currently running on webstart
* <p/>
* detect the presence of a JNLPclassloader
*
* #return
*/
public static boolean isRunningOnWebstart() {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
while (cl != null) {
if (cl.getClass().getName().equals("com.sun.jnlp.JNLPClassLoader")) {
return true;
}
cl = cl.getParent();
}
return false;
}
/**
* Is the JRE 1.6.0_19 or higher?
* TBFI-5349: Java has a bug, sometimes Jars get garbage collected. To resolve this we are making
* hard references to the Jars.
*
* This method checks for java version. The bug is in 1.6.0_19 and above release hence checking for version 19
* #return
*/
public static boolean isRunningOnJre1_6_0_19OrHigher() {
String javaVersion = System.getProperty("java.version");
String updateStr = null;
String javaMajorVersionStr = null;
boolean isHardReferenceRequired = false;
// Problem persist in JAVA 7 and probable in JAVA 8 as well. So changing the patter.
Pattern pattern = Pattern.compile(JRE_1_DOT + "([6-9]+)" + DOT_ZERO_UNDERSCORE + "([0-9]+)(.*)");
Matcher matcher = pattern.matcher(javaVersion);
while (matcher.find()) {
javaMajorVersionStr = matcher.group(1);
updateStr = matcher.group(2);
break;
}
Logger.info("Java version: " + javaMajorVersionStr + " update string: " + updateStr);
try {
if (javaMajorVersionStr != null) {
int java_version = Integer.parseInt(javaMajorVersionStr);
if (java_version > PROBLEM_JRE_MAJOR_VERSION) {
isHardReferenceRequired = true;
} else if (java_version == PROBLEM_JRE_MAJOR_VERSION && Integer.parseInt(updateStr) >= PROBLEM_JRE_UPDATE) {
isHardReferenceRequired = true;
}
}
return isHardReferenceRequired;
} catch (NumberFormatException e) {
//then unable to determine java Major version or update level
e.printStackTrace();
return isHardReferenceRequired;
}
}
/**
* get all the JarFile objects for all of the jars in the classpath
*
* #return
*/
public static Set<JarFile> getAllJarsFilesInClassPath() {
Set<JarFile> jars = new LinkedHashSet<JarFile>();
for (URL url : getAllJarUrls()) {
try {
jars.add(getJarFile(url));
} catch (IOException e) {
Logger.error("unable to retrieve jar at URL: " + url);
}
}
return jars;
}
/**
* Returns set of URLS for the jars in the classpath.
* URLS will have the protocol of jar eg: jar:http://HOST/PATH/JARNAME.jar!/META-INF/MANIFEST.MF
*/
static Set<URL> getAllJarUrls() {
try {
Set<URL> urls = new LinkedHashSet<URL>();
Enumeration<URL> mfUrls = Thread.currentThread().getContextClassLoader().getResources("META-INF/MANIFEST.MF");
while (mfUrls.hasMoreElements()) {
URL jarUrl = mfUrls.nextElement();
// System.out.println(jarUrl);
if (!jarUrl.getProtocol().equals("jar")) {
continue;
}
urls.add(jarUrl);
}
return urls;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* get the jarFile object for the given url
*
* #param jarUrl
* #return
* #throws IOException
*/
public static JarFile getJarFile(URL jarUrl) throws IOException {
URLConnection urlConnnection = jarUrl.openConnection();
if (urlConnnection instanceof JarURLConnection) {
// Using a JarURLConnection will load the JAR from the cache when using Webstart 1.6
// In Webstart 1.5, the URL will point to the cached JAR on the local filesystem
JarURLConnection jcon = (JarURLConnection) urlConnnection;
return jcon.getJarFile();
} else {
throw new AssertionError("Expected JarURLConnection");
}
}
/**
* Spawn a new thread to run through each jar in the classpath and create a hardlink
* to the jars softly referenced signers infomation.
*/
public static void go() {
if (!isHardLinkerEnabled()) {
return;
}
Logger.info("Starting Resource Preloader Hardlinker");
Thread t = new Thread(new Runnable() {
public void run() {
try {
Set<JarFile> jars = getAllJarsFilesInClassPath();
for (JarFile jar : jars) {
makeHardSignersRef(jar);
}
} catch (Exception e) {
Logger.warn("Problem preloading resources", e);
} catch (Error e) {
Logger.error("Error preloading resources", e);
}
}
});
t.start();
}
}
When we launch the application with JRE6 it works fine. But the problem is with JRE7. When the application is launched with JRE7 we get below exception in the log. From the exception we know that the jars are not hard referenced and that the user can have problems if the jars get garbage collected. We have a release next week and need to find a work around for this issue.
java.lang.NoSuchMethodException: com.sun.deploy.cache.CachedJarFile.getSigners()
at java.lang.Class.getDeclaredMethod(Unknown Source)
at com.XXXXXX.ui.main.JarSignersHardLinker.callNoArgMethod(JarSignersHardLinker.java:96)
at com.XXXXXX.ui.main.JarSignersHardLinker.makeHardSignersRef(JarSignersHardLinker.java:45)
at com.XXXXXX.ui.main.JarSignersHardLinker$1.run(JarSignersHardLinker.java:262)
at java.lang.Thread.run(Unknown Source)
java.lang.NoSuchFieldException: signersRef
at java.lang.Class.getDeclaredField(Unknown Source)
at com.XXXXXX.ui.main.JarSignersHardLinker.makeHardLink(JarSignersHardLinker.java:69)
at com.XXXXXX.ui.main.JarSignersHardLinker.makeHardSignersRef(JarSignersHardLinker.java:46)
at com.XXXXXX.ui.main.JarSignersHardLinker$1.run(JarSignersHardLinker.java:262)
at java.lang.Thread.run(Unknown Source)
This exception is repeated 52 times (for all the JAR's).
We have made sure that all the 52 jars/files (except the JNLP itself) are signed properly and that the java cache is cleard before the application is launched.
JAVA version used is JDK 7u40 on windows machine.
Options tried are:
Removing the jdk.certpath.disabledAlgorithms=MD2, RSA keySize < 1024
from java.securites file.
Checking the jar signer certificate. Signer certificate uses SHA1withRSA as signing algorithm.
Note:
THE SOURCE CODE IS COMPILED IN JAVA 5u11 AND RUN IN JAVA 7u40
We have observed one more difference. With JRE6, when we run the same
piece of code, it first loades JAVAWS.jar, Deploy.jar and plugin .jar from java/jre6/lib path but with JRE7 these jars are not loaded.
This has been tried in both 64 and 32 bits java version with no luck.
Any help here is really appreciated.
#jorge_B: We sign the jars using ant task. But due to a problem in JAVA 6u19 (where the softreferenced jars are sometimes garbage collected) we have decided to hardlink the jars as soon as we hit a java version 6u19 or above. This issue is not with jar signing. The issue is when we try to hard reference the jars. our jars are not getting hard referenced, insted we are getting nosuchmethod and nosuchfield exception in the log.
import java.io.IOException;
import java.lang.ref.SoftReference;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.util.*;
import java.util.jar.JarFile;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* A utility class for working around the java webstart jar signing/security bug
* <p/>
* see http://bugs.sun.com/view_bug.do?bug_id=6967414 and http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6805618
*
* #author Scott Chan
*/
public class JarSignersHardLinker {
private static final String JRE_VERSION_START = "1.";
private static final int PROBLEM_JRE_VERSION = 6;
private static final String DOT_ZERO = ".0_";
/**
* the 1.6.0 update where this problem first occurred
*/
private static final int PROBLEM_JRE_UPDATE = 19;
private static String majorVersionStr = null;
public static final List sm_hardRefs = new ArrayList();
protected static void makeHardSignersRef(JarFile jar) throws java.io.IOException {
Logger.info("Making hard refs for: " + (jar != null ? jar.getName() : null) + " with Java Version: "+majorVersionStr);
if (jar != null && jar.getClass().getName().equals("com.sun.deploy.cache.CachedJarFile")) {
//lets attempt to get at the each of the soft links.
//first neet to call the relevant no-arg method to ensure that the soft ref is populated
//then we access the private member, resolve the softlink and throw it in a static list.
if (majorVersionStr != null && Integer.parseInt(majorVersionStr) > PROBLEM_JRE_VERSION) {
callNoArgMethod("getSigningData", jar);
makeHardLink("signingDataRef", jar);
} else {
callNoArgMethod("getSigners", jar);
makeHardLink("signersRef", jar);
callNoArgMethod("getSignerMap", jar);
makeHardLink("signerMapRef", jar);
// callNoArgMethod("getCodeSources", jar);
// makeHardLink("codeSourcesRef", jar);
callNoArgMethod("getCodeSourceCache", jar);
makeHardLink("codeSourceCacheRef", jar);
}
}
}
/**
* if the specified field for the given instance is a Softreference
* That soft reference is resolved and the returned ref is stored in a static list,
* making it a hard link that should never be garbage collected
*
* #param fieldName
* #param instance
*/
private static void makeHardLink(String fieldName, Object instance) {
Logger.info("attempting hard ref to " + instance.getClass().getName() + "." + fieldName);
try {
Field signersRef = instance.getClass().getDeclaredField(fieldName);
signersRef.setAccessible(true);
Object o = signersRef.get(instance);
if (o instanceof SoftReference) {
SoftReference r = (SoftReference) o;
Object o2 = r.get();
sm_hardRefs.add(o2);
} else {
Logger.warn(fieldName + ": is not an instance of soft reference");
}
} catch (NoSuchFieldException e) {
e.printStackTrace();
return;
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
/**
* Call the given no-arg method on the given instance
*
* #param methodName
* #param instance
*/
private static void callNoArgMethod(String methodName, Object instance) {
Logger.info("calling noarg method hard ref to " + instance.getClass().getName() + "." + methodName + "()");
try {
Method m = instance.getClass().getDeclaredMethod(methodName);
m.setAccessible(true);
m.invoke(instance);
} catch (SecurityException e1) {
e1.printStackTrace();
} catch (NoSuchMethodException e1) {
e1.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
/**
* is the preloader enabled. ie: will the preloader run in the current environment
*
* #return
*/
public static boolean isHardLinkerEnabled() {
boolean isHardLinkerDisabled = false; //change this to use whatever mechanism you use to enable or disable the preloader
return !isHardLinkerDisabled && isRunningOnJre1_6_0_19OrHigher() && isRunningOnWebstart();
}
/**
* is the application currently running on webstart
* <p/>
* detect the presence of a JNLPclassloader
*
* #return
*/
public static boolean isRunningOnWebstart() {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
while (cl != null) {
if (cl.getClass().getName().equals("com.sun.jnlp.JNLPClassLoader")) {
return true;
}
cl = cl.getParent();
}
return false;
}
/**
* Is the JRE 1.6.0_19 or higher?
*
* #return
*/
public static boolean isRunningOnJre1_6_0_19OrHigher() {
String javaVersion = System.getProperty("java.version");
// Sometimes java releases version 1.X.0_YY-rev for specific issue and specific user,
// to resolve this we use patter instead of string split.
//ERP-6460: Checking whether JRE is 6 update 19 or higher
String updateStr = null;
majorVersionStr = null;
boolean isHardReferenceRequired = false;
Pattern pattern = Pattern.compile(JRE_VERSION_START + "([6-9]+)" + DOT_ZERO + "([0-9]+)(.*)");
Matcher matcher = pattern.matcher(javaVersion);
while (matcher.find()) {
majorVersionStr = matcher.group(1);
updateStr = matcher.group(2);
break;
}
try {
if (majorVersionStr != null) {
int java_version = Integer.parseInt(majorVersionStr);
if (java_version > PROBLEM_JRE_VERSION) {
isHardReferenceRequired = true;
} else if (java_version == PROBLEM_JRE_VERSION && Integer.parseInt(updateStr) >= PROBLEM_JRE_UPDATE) {
isHardReferenceRequired = true;
}
}
return isHardReferenceRequired;
} catch (NumberFormatException e) {
e.printStackTrace();
return isHardReferenceRequired;
}
}
/**
* get all the JarFile objects for all of the jars in the classpath
*
* #return
*/
public static Set<JarFile> getAllJarsFilesInClassPath() {
Set<JarFile> jars = new LinkedHashSet<JarFile>();
for (URL url : getAllJarUrls()) {
try {
jars.add(getJarFile(url));
} catch (IOException e) {
Logger.error("unable to retrieve jar at URL: " + url);
}
}
return jars;
}
/**
* Returns set of URLS for the jars in the classpath.
* URLS will have the protocol of jar eg: jar:http://HOST/PATH/JARNAME.jar!/META-INF/MANIFEST.MF
*/
static Set<URL> getAllJarUrls() {
try {
Set<URL> urls = new LinkedHashSet<URL>();
Enumeration<URL> mfUrls = Thread.currentThread().getContextClassLoader().getResources("META-INF/MANIFEST.MF");
while (mfUrls.hasMoreElements()) {
URL jarUrl = mfUrls.nextElement();
// System.out.println(jarUrl);
if (!jarUrl.getProtocol().equals("jar")) {
continue;
}
urls.add(jarUrl);
}
return urls;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* get the jarFile object for the given url
*
* #param jarUrl
* #return
* #throws IOException
*/
public static JarFile getJarFile(URL jarUrl) throws IOException {
URLConnection urlConnnection = jarUrl.openConnection();
if (urlConnnection instanceof JarURLConnection) {
// Using a JarURLConnection will load the JAR from the cache when using Webstart 1.6
// In Webstart 1.5, the URL will point to the cached JAR on the local filesystem
JarURLConnection jcon = (JarURLConnection) urlConnnection;
return jcon.getJarFile();
} else {
throw new AssertionError("Expected JarURLConnection");
}
}
/**
* Spawn a new thread to run through each jar in the classpath and create a hardlink
* to the jars softly referenced signers infomation.
*/
public static void go() {
if (!isHardLinkerEnabled()) {
return;
}
Logger.info("Starting Resource Preloader Hardlinker");
Thread t = new Thread(new Runnable() {
public void run() {
try {
Set<JarFile> jars = getAllJarsFilesInClassPath();
for (JarFile jar : jars) {
makeHardSignersRef(jar);
}
} catch (Exception e) {
Logger.error("Problem preloading resources", e);
} catch (Error e) {
Logger.error("Error preloading resources", e);
}
}
});
t.start();
}
}
We have a Lotus Notes Plug-In, developed in Java. The current version of Notes we're supporting is 8.5.2.
This plug-in adds a button to the Notes UI. When the user clicks it, we present a window to the user, which allows the user to add the current item to our web-based application using web services. Everything works fine, except when the user tries to add a new email item.
The listener for the "click event" (if you will) needs to know the current document so that it can pass its field data to the web service call. In order to do that, a separate listener has been set up (DocumentContextService) which essentially gets invoked any time the input focus changes within the Notes UI.
DocumentContextService attempts to retrieve the URI of the current document. And that's where things fall apart. I am finding that an unsent email message has no URI. Further, there appears to be no way to get to the document and save it, so that I can obtain one.
Theoretically, this is by design. Oddly, I can see that the new document has a DocumentKey, so I know it exists (as a draft somewhere), but I cannot get to it. So there doesn't appear to be any way to access the document's data until it is actually saved.
Unless I'm wrong (and I very well could be). And there's the question: Is there a way to obtain the underlying document of a new email before it has been saved so that I can access its data (specifically, its fields)?
The code from the document context listener is below. The problem, again, is that the URI property always evaluates to an empty string for new emails.
package com.ibm.lotuslabs.context.service.internal;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Properties;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.swt.graphics.Image;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.model.IWorkbenchAdapter;
import org.eclipse.ui.views.properties.IPropertyDescriptor;
import org.eclipse.ui.views.properties.IPropertySource;
import com.ibm.lotuslabs.context.service.document.IDocumentContext;
import com.ibm.rcp.jface.launcher.IURIProvider;
import com.satuit.sys.The;
/**
* Extracts document information about about a selection object. Represents the document within the DocumentSelection.
*/
#SuppressWarnings("deprecation")
public class DocumentContext implements IDocumentContext
{
private IWorkbenchPart part;
private Object obj;
private String label;
private URI uri;
private ImageDescriptor icon;
private Properties properties;
private String id;
/**
* Initializes a new instance of the DocumentContext class.
*
* #param part The current view part.
* #param obj The currently selected object.
*/
public DocumentContext(final IWorkbenchPart part, final Object obj)
{
this.part = part;
this.obj = obj;
// Is this object a URIProvider?
final IURIProvider provider = (IURIProvider)ContextUtil.getAdapterObject(obj, IURIProvider.class);
if (provider != null)
{
this.uri = provider.getURI();
this.label = provider.getTitle();
this.icon = provider.getImageDescriptor();
}
if (this.label == null || this.icon == null)
{
// Is this object a workbench adapter?
final IWorkbenchAdapter wba = (IWorkbenchAdapter)ContextUtil.getAdapterObject(obj, IWorkbenchAdapter.class);
if (wba != null)
{
if (this.label != null)
{
this.label = wba.getLabel(obj);
}
if (this.icon != null)
{
this.icon = wba.getImageDescriptor(obj);
}
}
if (this.icon == null)
{
final Image i = part.getTitleImage();
if (i != null)
{
this.icon = ImageDescriptor.createFromImage(i);
}
}
}
// Is this object a URI?
if (this.uri == null)
{
this.uri = (URI)ContextUtil.getAdapterObject(obj, URI.class);
}
// Is this object a PropertySource?
// (A document that isn't a URI provider may provide a URI in its properties.)
final IPropertySource prop = (IPropertySource)ContextUtil.getAdapterObject(obj, IPropertySource.class);
if (prop != null)
{
this.properties = buildProperties(prop);
}
}
/**
* Gets the ID of this instance.
* #return A string containing the ID.
*/
public final String getId()
{
return this.id;
}
/**
* Gets the workbench part used to initialize this instance.
* #return
*/
public final IWorkbenchPart getPart()
{
return this.part;
}
/* (non-Javadoc)
* #see com.ibm.lotuslabs.context.service.document.IDocumentContext#getImageDescriptor()
*/
public final ImageDescriptor getImageDescriptor()
{
return this.icon;
}
/* (non-Javadoc)
* #see com.ibm.lotuslabs.context.service.document.IDocumentContext#getLabel()
*/
public final String getLabel()
{
if (this.label == null && this.part != null)
{
return this.part.getTitle();
}
return this.label;
}
/* (non-Javadoc)
* #see com.ibm.lotuslabs.context.service.document.IDocumentContext#getObject()
*/
public final Object getObject()
{
return this.obj;
}
/* (non-Javadoc)
* #see com.ibm.lotuslabs.context.service.document.IDocumentContext#getProperties()
*/
public final Properties getProperties()
{
return this.properties;
}
/* (non-Javadoc)
* #see com.ibm.lotuslabs.context.service.document.IDocumentContext#getURI()
*/
public final URI getURI()
{
return this.uri;
}
private Properties buildProperties(final IPropertySource source)
{
if (source == null)
{
return null;
}
final IPropertyDescriptor[] descs = source.getPropertyDescriptors();
if (The.Value(null).Is.NullOrEmpty(descs))
{
return null;
}
final Properties prop = new Properties();
for (int i = 0; i < descs.length; i++)
{
final Object id = descs[i].getId();
final String name = descs[i].getDisplayName();
String value = source.getPropertyValue(descs[i].getId()).toString();
if (The.Value(descs[i].getDescription()).Is.Not.Null())
{
value += "|" + source.getPropertyValue(descs[i].getDescription()).toString();
}
if (this.uri == null)
{
if (The.Value("URI").Is.OneOfIgnoreCase(id.toString(), name) ||
The.Value("URL").Is.OneOfIgnoreCase(id.toString(), name))
{
try
{
this.uri = new URI(value);
continue;
}
catch (URISyntaxException e)
{
}
}
}
prop.setProperty(name, value);
}
return prop;
}
}
Sorry, until the document is saved into the backend database, there is no document to get an URI for, as it does not exists anywhere but in memory. It simply does not exists on disk.