com.hcl.domino.jna.JNADominoProcess Maven / Gradle / Ivy
The newest version!
/*
* ==========================================================================
* 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) {
initializeProcess(initArgs, true, true);
}
@Override
public void initializeProcess(String[] initArgs, boolean nativeProcessInit,
boolean nativeThreadInit)
throws DominoInitException {
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, nativeProcessInit, nativeThreadInit);
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 < retUserNameMem.size(); i++) {
userNameLength = i;
if (retUserNameMem.getByte(i) == 0) {
break;
}
}
return NotesStringUtils.fromLMBCS(retUserNameMem, userNameLength);
}
}
private static boolean isWritePacemakerDebugMessages() {
return DominoUtils.checkBooleanProperty("jnx.debuginit", "JNX_DEBUGINIT"); //$NON-NLS-1$ //$NON-NLS-2$
}
@Override
public void terminateProcess() {
synchronized (pacemakerThreadlock) {
if (!processInitialized) {
// nothing to do
return;
}
if (pacemakerThread == null) {
throw new IllegalStateException("Missing Domino pacemaker thread");
}
if (!threadEnabledForDominoRefCount.isEmpty()) {
if (!DominoUtils.isSkipThreadWarning()) {
AtomicBoolean hasUnmatchedInits = new AtomicBoolean(false);
AtomicInteger idx = new AtomicInteger(1);
threadEnabledForDominoRefCount.forEach((thread, threadInfo) -> {
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 < callstack.length && i <= 20; i++) {
System.out.println("* " + callstack[i]);
}
}
hasUnmatchedInits.set(true);
idx.incrementAndGet();
System.out.println("*");
});
if (hasUnmatchedInits.get()) {
System.out.println(
"* Please close the DominoThreadContext returned by DominoProcess.get().initializeThread() e.g. with\n"
+ "* a try-with-resources-block or call DominoProcess.get().terminateThread().\n"
+ "* Having unmatched initializeThread/terminateThread pairs can cause application crashes.");
System.out.println(
"**********************************************************************************************************");
}
}
}
try {
pacemakerThread.requestShutdown();
pacemakerThread = null;
} catch (InterruptedException e) {
throw new DominoException("Thread has been interrupted", e);
}
processInitialized = false;
if (notesThreadCl != null) {
try {
notesThreadCl.close();
} catch (IOException e) {
// Ignore
}
}
}
}
@Override
public DominoThreadContext initializeThread() {
return initializeThread(true, true);
}
@Override
public DominoThreadContext initializeThread(boolean nativeInit, boolean nativeTerm) {
Thread thread = Thread.currentThread();
ThreadInfo threadInfo = threadEnabledForDominoRefCount.get(thread);
if (threadInfo == null) {
if (nativeInit && !DominoUtils.isNoInitTermThread()) {
if (notesThreadInit != null) {
try {
AccessController.doPrivileged((PrivilegedExceptionAction) () -> {
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(nativeTerm);
terminated = true;
}
}
};
}
@Override
public void terminateThread() {
terminateThread(true);
}
@Override
public void terminateThread(boolean nativeTerm) {
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 (nativeTerm && !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