com.hcl.domino.jna.JNADominoProcess Maven / Gradle / Ivy
/*
* ==========================================================================
* Copyright (C) 2019-2022 HCL America, Inc. ( http://www.hcl.com/ )
* All rights reserved.
* ==========================================================================
* Licensed under the Apache License, Version 2.0 (the "License"). You may
* not use this file except in compliance with the License. You may obtain a
* copy of the License at .
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
* ==========================================================================
*/
package com.hcl.domino.jna;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.URLClassLoader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.text.MessageFormat;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.hcl.domino.DominoException;
import com.hcl.domino.DominoProcess;
import com.hcl.domino.commons.util.DominoUtils;
import com.hcl.domino.commons.util.NotesErrorUtils;
import com.hcl.domino.commons.util.StringUtil;
import com.hcl.domino.exception.DominoInitException;
import com.hcl.domino.jna.internal.DisposableMemory;
import com.hcl.domino.jna.internal.NotesStringUtils;
import com.hcl.domino.jna.internal.capi.INotesCAPI;
import com.hcl.domino.jna.internal.capi.NotesCAPI;
import com.hcl.domino.misc.NotesConstants;
import com.sun.jna.Memory;
import com.sun.jna.StringArray;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
public class JNADominoProcess implements DominoProcess {
private static boolean processInitialized;
private static DominoPacemakerThread pacemakerThread;
private static boolean processTerminated;
private static Map threadEnabledForDominoRefCount = Collections.synchronizedMap(new HashMap<>());
private static final Object pacemakerThreadlock = new Object();
private static final Method notesThreadInit;
private static final Method notesThreadTerm;
private static URLClassLoader notesThreadCl;
private static final Logger log = Logger.getLogger(JNADominoProcess.class.getPackage().getName());
static {
// If Notes.jar is available, prefer those thread init/term methods to
// account for in-runtime JNI hooks.
Method initMethod = null;
Method termMethod = null;
Class> notesThread = null;
if(!DominoUtils.isSkipNotesThreadInit()) {
try {
notesThread = Class.forName("lotus.domino.NotesThread"); //$NON-NLS-1$
} catch (Throwable t) {
// Then Notes.jar is not present
if(log.isLoggable(Level.WARNING)) {
log.log(Level.WARNING, "Unable to find lotus.domino.NotesThread in the context ClassLoader - skipping full JNI initialization", t);
}
}
}
if(notesThread != null) {
try {
initMethod = notesThread.getDeclaredMethod("sinitThread"); //$NON-NLS-1$
termMethod = notesThread.getDeclaredMethod("stermThread"); //$NON-NLS-1$
} catch(Throwable t) {
if(log.isLoggable(Level.SEVERE)) {
log.log(Level.SEVERE, "Encountered exception locating static init methods in NotesThread", t);
}
}
}
notesThreadInit = initMethod;
notesThreadTerm = termMethod;
}
public static void ensureProcessInitialized() {
synchronized (pacemakerThreadlock) {
if (!processInitialized) {
throw new DominoInitException("DominoProcess.get().initializeProcess(String[]) must be called first to initialize the Domino API for this process.");
}
}
}
public static void ensureProcessNotTerminated() {
synchronized (pacemakerThreadlock) {
if (processTerminated) {
throw new DominoInitException("Domino access for this process has already been terminated.");
}
}
}
@Override
public void initializeProcess(String[] initArgs) {
synchronized (pacemakerThreadlock) {
if (processInitialized) {
return;
}
// Fail early if we can't load the notes shared library. The get() function may throw an unchecked DominoInitException
NotesCAPI.get(true); // true => do not check if thread is initialized for Domino
if (initArgs==null) {
initArgs = new String[0];
}
else {
if (initArgs.length>0) {
if (StringUtil.isNotEmpty(initArgs[0])) {
String dominoProgramDirPathStr = initArgs[0];
Path dominoProgramDirPath = Paths.get(dominoProgramDirPathStr);
if (!Files.exists(dominoProgramDirPath)) {
throw new DominoInitException(MessageFormat.format("Specified Notes/Domino program dir path does not exist: {0}", dominoProgramDirPath.toString()));
}
if (!Files.isDirectory(dominoProgramDirPath)) {
throw new DominoInitException(MessageFormat.format("Specified Notes/Domino program dir path is not a directory: {0}", dominoProgramDirPath.toString()));
}
}
if (initArgs.length>1) {
String notesIniPathStr = initArgs[1];
if (notesIniPathStr.startsWith("=") ) { //$NON-NLS-1$
Path notesIniPath = Paths.get(notesIniPathStr.substring(1));
if (!Files.exists(notesIniPath)) {
throw new DominoInitException(MessageFormat.format("Specified Notes.ini path does not exist: {0}", notesIniPath.toString()));
}
if (!Files.isRegularFile(notesIniPath)) {
throw new DominoInitException(MessageFormat.format("Specified Notes.ini path is not a file: {0}", notesIniPath.toString()));
}
}
}
}
}
StringArray strArr = new StringArray(initArgs);
//make sure we have at least one running thread accessing Domino APIs,
//otherwise the API automatically unloads the native libs when
//no thread has an active initThread ref count
pacemakerThread = new DominoPacemakerThread(initArgs.length, strArr);
pacemakerThread.start();
try {
pacemakerThread.waitUntilStarted();
// validate the connection was set up properly
DominoException e=pacemakerThread.getInitException();
if (e!=null) {
// abort if it wasn't
throw e;
}
// finally mark the process as initialized
processInitialized = true;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private String getPropertyString(String propertyName) {
Memory variableNameMem = NotesStringUtils.toLMBCS(propertyName, true);
try(DisposableMemory rethValueBuffer = new DisposableMemory(NotesConstants.MAXENVVALUE)) {
short result = NotesCAPI.get().OSGetEnvironmentString(variableNameMem, rethValueBuffer, NotesConstants.MAXENVVALUE);
if (result==1) {
String str = NotesStringUtils.fromLMBCS(rethValueBuffer, -1);
return str;
}
else {
return ""; //$NON-NLS-1$
}
}
}
@Override
public String switchToId(Path idPath, String password, boolean dontSetEnvVar) {
if (idPath==null) {
idPath = Paths.get(getPropertyString("KeyFileName")); //$NON-NLS-1$
if (!idPath.isAbsolute()) {
Path dataDirPath = Paths.get(getPropertyString("Directory")); //$NON-NLS-1$
idPath = dataDirPath.resolve(idPath);
}
}
Memory idPathMem = NotesStringUtils.toLMBCS(idPath.toString(), true);
Memory passwordMem = NotesStringUtils.toLMBCS(password, true);
try(DisposableMemory retUserNameMem = new DisposableMemory(NotesConstants.MAXUSERNAME+1)) {
short result = NotesCAPI.get().SECKFMSwitchToIDFile(idPathMem, passwordMem, retUserNameMem,
NotesConstants.MAXUSERNAME, dontSetEnvVar ? NotesConstants.fKFM_switchid_DontSetEnvVar : 0, null);
NotesErrorUtils.checkResult(result);
int userNameLength = 0;
for (int i=0; i {
if (!hasUnmatchedInits.get()) {
System.out.println("**********************************************************************************************************");
System.out.println("* WARNING: We found unmatched DominoProcess.get().initializeThread() calls in threads:");
System.out.println("*");
}
System.out.println(
MessageFormat.format(
"* #{0} - {1} (started: {2}, ref count: {3})",
idx, thread, new Date(threadInfo.getStartTime()), threadInfo.getRefCount()
)
);
StackTraceElement[] callstack = threadInfo.getCallstacks();
if (callstack.length>1) {
System.out.println("* Callstack:");
for (int i=1; i)() -> {
notesThreadInit.invoke(null);
return null;
});
} catch (IllegalArgumentException | PrivilegedActionException e) {
log.log(Level.SEVERE, "Exception initializing NotesThread", e);
throw new RuntimeException(e);
}
} else {
NotesCAPI.get(true).NotesInitThread(); // true => do not check if thread is initialized for Domino, because we do this
}
}
threadInfo = new ThreadInfo(Thread.currentThread().getStackTrace(), System.currentTimeMillis());
threadEnabledForDominoRefCount.put(thread, threadInfo);
}
else {
threadInfo.setRefCount(threadInfo.getRefCount()+1);
}
return new DominoThreadContext() {
boolean terminated = false;
@Override
public void close() {
if (!terminated) {
terminateThread();
terminated = true;
}
}
};
}
@Override
public void terminateThread() {
Thread thread = Thread.currentThread();
ThreadInfo threadInfo = threadEnabledForDominoRefCount.get(thread);
if (threadInfo==null) {
throw new DominoException("WARNING: Unmatched notesInitThread detected!");
}
else if (threadInfo.getRefCount()==1) {
if(!DominoUtils.isNoInitTermThread()) {
if(notesThreadTerm != null) {
try {
AccessController.doPrivileged((PrivilegedExceptionAction)() -> {
notesThreadTerm.invoke(null);
return null;
});
} catch (IllegalArgumentException | PrivilegedActionException e) {
throw new RuntimeException(e);
}
} else {
NotesCAPI.get().NotesTermThread();
}
}
threadEnabledForDominoRefCount.remove(thread);
}
else {
threadInfo.setRefCount(threadInfo.getRefCount()-1);
}
}
/**
* Makes sure that the process and current thread have been initialized for Domino C API access
*
* @throws DominoInitException in case of missing initialization
*/
public static void checkThreadEnabledForDomino() {
ensureProcessInitialized();
ensureProcessNotTerminated();
Thread thread = Thread.currentThread();
ThreadInfo threadInfo = threadEnabledForDominoRefCount.get(thread);
if (threadInfo==null || threadInfo.getRefCount()==0) {
throw new DominoInitException("Please use DominoProcess.get().initializeThread() / terminateThread() to enable Domino access for this thread.");
}
}
private static class DominoPacemakerThread extends Thread {
private LinkedBlockingQueue
© 2015 - 2025 Weber Informatics LLC | Privacy Policy