All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.hcl.domino.jna.internal.JNASignalHandlerUtil 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.internal;

import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;

import com.hcl.domino.DominoClient.IBreakHandler;
import com.hcl.domino.DominoClient.IProgressListener;
import com.hcl.domino.DominoClient.ReplicationStateListener;
import com.hcl.domino.DominoException;
import com.hcl.domino.commons.errors.INotesErrorConstants;
import com.hcl.domino.commons.util.PlatformUtils;
import com.hcl.domino.data.Database.Action;
import com.hcl.domino.jna.internal.callbacks.NotesCallbacks;
import com.hcl.domino.jna.internal.callbacks.NotesCallbacks.OSSIGBREAKPROC;
import com.hcl.domino.jna.internal.callbacks.NotesCallbacks.OSSIGPROGRESSPROC;
import com.hcl.domino.jna.internal.callbacks.NotesCallbacks.OSSIGREPLPROC;
import com.hcl.domino.jna.internal.callbacks.Win32NotesCallbacks;
import com.hcl.domino.jna.internal.capi.NotesCAPI;
import com.hcl.domino.misc.NotesConstants;
import com.sun.jna.Pointer;

/**
 * Utility class that uses signal handlers of Domino to stop and get progress information
 * for long running operations.
 * 
 * @author Karsten Lehmann
 */
public class JNASignalHandlerUtil {
	private static final ConcurrentHashMap m_breakHandlerByThread = new ConcurrentHashMap<>();
	private static final ConcurrentHashMap m_progressListenerByThread = new ConcurrentHashMap<>();
	private static final ConcurrentHashMap m_replicationStateListenerByThread = new ConcurrentHashMap<>();

	private static volatile boolean m_breakProcInstalled = false;
	private static volatile NotesCallbacks.OSSIGBREAKPROC prevBreakProc = null;
	private static ThreadLocal lastBreakHandlerException = new ThreadLocal<>();

	private static final Win32NotesCallbacks.OSSIGBREAKPROCWin32 breakProcWin32 = () -> {
		try {
			Thread thread = Thread.currentThread();
			IBreakHandler breakHandler = m_breakHandlerByThread.get(thread);
			if (breakHandler!=null) {
				if (breakHandler.shouldInterrupt() == Action.Stop) {
					return INotesErrorConstants.ERR_CANCEL;
				}
			}

			if (prevBreakProc!=null) {
				return prevBreakProc.invoke();
			}
			else {
				return 0;
			}
		}
		catch (Exception e) {
			lastBreakHandlerException.set(e);
			return 0;
		}
	};
	private static final NotesCallbacks.OSSIGBREAKPROC breakProc = () -> {
		try {
			Thread thread = Thread.currentThread();
			IBreakHandler breakHandler = m_breakHandlerByThread.get(thread);
			if (breakHandler!=null) {
				if (breakHandler.shouldInterrupt() == Action.Stop) {
					return INotesErrorConstants.ERR_CANCEL;
				}
			}

			if (prevBreakProc!=null) {
				return prevBreakProc.invoke();
			}
			else {
				return 0;
			}
		}
		catch (Exception e) {
			lastBreakHandlerException.set(e);
			return 0;
		}
	};

	private static volatile boolean m_progressProcInstalled = false;
	private static volatile NotesCallbacks.OSSIGPROGRESSPROC prevProgressProc = null;
	private static ThreadLocal lastProgressListenerException = new ThreadLocal<>();

	private static Win32NotesCallbacks.OSSIGPROGRESSPROCWin32 progressProcWin32 = (option, data1, data2) -> {
		try {
			Thread thread = Thread.currentThread();
			IProgressListener progressListener = m_progressListenerByThread.get(thread);
			if (progressListener!=null) {
				if (option == NotesConstants.PROGRESS_SIGNAL_BEGIN) {
					progressListener.begin();
				}
				else if (option == NotesConstants.PROGRESS_SIGNAL_END) {
					progressListener.end();
				}
				else if (option == NotesConstants.PROGRESS_SIGNAL_SETRANGE) {
					long range = Pointer.nativeValue(data1);
					progressListener.setRange(range);
				}
				else if (option == NotesConstants.PROGRESS_SIGNAL_SETTEXT) {
					String str = NotesStringUtils.fromLMBCS(data1, -1);
					progressListener.setText(str);
				}
				else if (option == NotesConstants.PROGRESS_SIGNAL_SETPOS) {
					long pos = Pointer.nativeValue(data1);
					progressListener.setPos(pos);
				}
				else if (option == NotesConstants.PROGRESS_SIGNAL_DELTAPOS) {
					long deltapos = Pointer.nativeValue(data1);
					progressListener.setDeltaPos(deltapos);
				}
				else if (option == NotesConstants.PROGRESS_SIGNAL_SETBYTERANGE) {
					long totalbytes = Pointer.nativeValue(data1);
					progressListener.setByteRange(totalbytes);
				}
				else if (option == NotesConstants.PROGRESS_SIGNAL_SETBYTEPOS) {
					long bytesDone = Pointer.nativeValue(data1);
					progressListener.setBytePos(bytesDone);
				}
			}

			if (prevProgressProc!=null) {
				return prevProgressProc.invoke(option, data1, data2);
			}
			else {
				return 0;
			}
		}
		catch (Exception e) {
			lastProgressListenerException.set(e);
			return 0;
		}
	};


	private static NotesCallbacks.OSSIGPROGRESSPROC progressProc = (option, data1, data2) -> {
		try {
			Thread thread = Thread.currentThread();
			IProgressListener progressListener = m_progressListenerByThread.get(thread);
			if (progressListener!=null) {

				if (option == NotesConstants.PROGRESS_SIGNAL_BEGIN) {
					progressListener.begin();
				}
				else if (option == NotesConstants.PROGRESS_SIGNAL_END) {
					progressListener.end();
				}
				else if (option == NotesConstants.PROGRESS_SIGNAL_SETRANGE) {
					long range = Pointer.nativeValue(data1);
					progressListener.setRange(range);
				}
				else if (option == NotesConstants.PROGRESS_SIGNAL_SETTEXT) {
					String str = NotesStringUtils.fromLMBCS(data1, -1);
					progressListener.setText(str);
				}
				else if (option == NotesConstants.PROGRESS_SIGNAL_SETPOS) {
					long pos = Pointer.nativeValue(data1);
					progressListener.setPos(pos);
				}
				else if (option == NotesConstants.PROGRESS_SIGNAL_DELTAPOS) {
					long deltapos = Pointer.nativeValue(data1);
					progressListener.setDeltaPos(deltapos);
				}
				else if (option == NotesConstants.PROGRESS_SIGNAL_SETBYTERANGE) {
					long totalbytes = Pointer.nativeValue(data1);
					progressListener.setByteRange(totalbytes);
				}
				else if (option == NotesConstants.PROGRESS_SIGNAL_SETBYTEPOS) {
					long bytesDone = Pointer.nativeValue(data1);
					progressListener.setBytePos(bytesDone);
				}
			}

			if (prevProgressProc!=null) {
				return prevProgressProc.invoke(option, data1, data2);
			}
			else {
				return 0;
			}
		}
		catch (Exception e) {
			lastProgressListenerException.set(e);
			return 0;
		}
	};

	private static volatile boolean m_replProcInstalled = false;
	private static volatile NotesCallbacks.OSSIGREPLPROC prevReplProc = null;
	private static ThreadLocal lastReplProcException = new ThreadLocal<>();

	private static Win32NotesCallbacks.OSSIGREPLPROCWin32 replProcWin32 = (state, pText1, pText2) -> {
		try {
			Thread thread = Thread.currentThread();
			ReplicationStateListener progressListener = m_replicationStateListenerByThread.get(thread);
			if (progressListener!=null) {
				if (state == NotesConstants.REPL_SIGNAL_IDLE) {
					progressListener.idle();
				}
				else if (state == NotesConstants.REPL_SIGNAL_PICKSERVER) {
					progressListener.pickServer();
				}
				else if (state == NotesConstants.REPL_SIGNAL_CONNECTING) {
					String server1 = NotesStringUtils.fromLMBCS(pText1, -1);
					String port1 = NotesStringUtils.fromLMBCS(pText2, -1);
					progressListener.connecting(server1, port1);
				}
				else if (state == NotesConstants.REPL_SIGNAL_SEARCHING) {
					String server2 = NotesStringUtils.fromLMBCS(pText1, -1);
					String port2 = NotesStringUtils.fromLMBCS(pText2, -1);
					progressListener.searching(server2, port2);
				}
				else if (state == NotesConstants.REPL_SIGNAL_SENDING) {
					String serverFile1 = NotesStringUtils.fromLMBCS(pText1, -1);
					String localFile1 = NotesStringUtils.fromLMBCS(pText2, -1);
					progressListener.sending(serverFile1, localFile1);
				}
				else if (state == NotesConstants.REPL_SIGNAL_RECEIVING) {
					String serverFile2 = NotesStringUtils.fromLMBCS(pText1, -1);
					String localFile2 = NotesStringUtils.fromLMBCS(pText2, -1);
					progressListener.receiving(serverFile2, localFile2);
				}
				else if (state == NotesConstants.REPL_SIGNAL_SEARCHINGDOCS) {
					String srcFile = NotesStringUtils.fromLMBCS(pText1, -1);
					progressListener.searchingDocs(srcFile);
				}
				else if (state == NotesConstants.REPL_SIGNAL_DONEFILE) {
					String localFile3 = NotesStringUtils.fromLMBCS(pText1, -1);
					String replFileStats = NotesStringUtils.fromLMBCS(pText2, -1);
					progressListener.doneFile(localFile3, replFileStats);
				}
				else if (state == NotesConstants.REPL_SIGNAL_REDIRECT) {
					String serverFile3 = NotesStringUtils.fromLMBCS(pText1, -1);
					String localFile4 = NotesStringUtils.fromLMBCS(pText2, -1);
					progressListener.redirect(serverFile3, localFile4);
				}
				else if (state == NotesConstants.REPL_SIGNAL_BUILDVIEW) {
					progressListener.buildView();
				}
				else if (state == NotesConstants.REPL_SIGNAL_ABORT) {
					progressListener.abort();
				}
			}

			if (prevReplProc!=null) {
				prevReplProc.invoke(state, pText1, pText2);
			}
		}
		catch (Exception e) {
			lastReplProcException.set(e);
		}
	};

	private static NotesCallbacks.OSSIGREPLPROC replProc = (state, pText1, pText2) -> {
		try {
			Thread thread = Thread.currentThread();
			ReplicationStateListener progressListener = m_replicationStateListenerByThread.get(thread);
			if (progressListener!=null) {
				if (state == NotesConstants.REPL_SIGNAL_IDLE) {
					progressListener.idle();
				}
				else if (state == NotesConstants.REPL_SIGNAL_PICKSERVER) {
					progressListener.pickServer();
				}
				else if (state == NotesConstants.REPL_SIGNAL_CONNECTING) {
					String server1 = NotesStringUtils.fromLMBCS(pText1, -1);
					String port1 = NotesStringUtils.fromLMBCS(pText2, -1);
					progressListener.connecting(server1, port1);
				}
				else if (state == NotesConstants.REPL_SIGNAL_SEARCHING) {
					String server2 = NotesStringUtils.fromLMBCS(pText1, -1);
					String port2 = NotesStringUtils.fromLMBCS(pText2, -1);
					progressListener.searching(server2, port2);
				}
				else if (state == NotesConstants.REPL_SIGNAL_SENDING) {
					String serverFile1 = NotesStringUtils.fromLMBCS(pText1, -1);
					String localFile1 = NotesStringUtils.fromLMBCS(pText2, -1);
					progressListener.sending(serverFile1, localFile1);
				}
				else if (state == NotesConstants.REPL_SIGNAL_RECEIVING) {
					String serverFile2 = NotesStringUtils.fromLMBCS(pText1, -1);
					String localFile2 = NotesStringUtils.fromLMBCS(pText2, -1);
					progressListener.receiving(serverFile2, localFile2);
				}
				else if (state == NotesConstants.REPL_SIGNAL_SEARCHINGDOCS) {
					String srcFile = NotesStringUtils.fromLMBCS(pText1, -1);
					progressListener.searchingDocs(srcFile);
				}
				else if (state == NotesConstants.REPL_SIGNAL_DONEFILE) {
					String localFile3 = NotesStringUtils.fromLMBCS(pText1, -1);
					String replFileStats = NotesStringUtils.fromLMBCS(pText2, -1);
					progressListener.doneFile(localFile3, replFileStats);
				}
				else if (state == NotesConstants.REPL_SIGNAL_REDIRECT) {
					String serverFile3 = NotesStringUtils.fromLMBCS(pText1, -1);
					String localFile4 = NotesStringUtils.fromLMBCS(pText2, -1);
					progressListener.redirect(serverFile3, localFile4);
				}
				else if (state == NotesConstants.REPL_SIGNAL_BUILDVIEW) {
					progressListener.buildView();
				}
				else if (state == NotesConstants.REPL_SIGNAL_ABORT) {
					progressListener.abort();
				}
			}

			if (prevReplProc!=null) {
				prevReplProc.invoke(state, pText1, pText2);
			}
		}
		catch (Exception e) {
			lastReplProcException.set(e);
		}
	};

	/**
	 * Registers our central break handler. Signal handlers are stored in the C API per
	 * process block, unfortunately not per thread. Since any other thread in the
	 * process (e.g. http task) can registers its own handler, there is no safe way
	 * to restore the previous handler, so we have to keep it.
	 */
	public static synchronized void installGlobalBreakHandler() {
		if (!m_breakProcInstalled) {
			try {
				//AccessController call required to prevent SecurityException when running in XPages
				prevBreakProc = AccessController.doPrivileged((PrivilegedExceptionAction) () -> {
					if (PlatformUtils.isWin32()) {
						return (NotesCallbacks.OSSIGBREAKPROC) NotesCAPI.get().OSSetBreakSignalHandler(NotesConstants.OS_SIGNAL_CHECK_BREAK, breakProcWin32);
					}
					else {
						return (NotesCallbacks.OSSIGBREAKPROC) NotesCAPI.get().OSSetBreakSignalHandler(NotesConstants.OS_SIGNAL_CHECK_BREAK, breakProc);
					}
				});
			} catch (PrivilegedActionException e) {
				if (e.getCause() instanceof RuntimeException) {
					throw (RuntimeException) e.getCause();
				} else {
					throw new DominoException(0, "Error installing break handler", e);
				}
			}
			m_breakProcInstalled = true;
		}
	}

	/**
	 * Registers our central progress handler. Signal handlers are stored in the C API per
	 * process block, unfortunately not per thread. Since any other thread in the
	 * process (e.g. http task) can registers its own handler, there is no safe way
	 * to restore the previous handler, so we have to keep it.
	 */
	public static synchronized void installGlobalProgressHandler() {
		if (!m_progressProcInstalled) {
			try {
				//AccessController call required to prevent SecurityException when running in XPages
				prevProgressProc = AccessController.doPrivileged((PrivilegedExceptionAction) () -> {
					if (PlatformUtils.isWin32()) {
						return (NotesCallbacks.OSSIGPROGRESSPROC) NotesCAPI.get().OSSetProgressSignalHandler(NotesConstants.OS_SIGNAL_PROGRESS, progressProcWin32);
					}
					else {
						return (NotesCallbacks.OSSIGPROGRESSPROC) NotesCAPI.get().OSSetProgressSignalHandler(NotesConstants.OS_SIGNAL_PROGRESS, progressProc);
					}
				});
			} catch (PrivilegedActionException e) {
				if (e.getCause() instanceof RuntimeException) {
					throw (RuntimeException) e.getCause();
				} else {
					throw new DominoException(0, "Error installing progress listener", e);
				}
			}
			m_progressProcInstalled = true;
		}
	}

	/**
	 * Registers our central replication state handler. Signal handlers are stored in the C API per
	 * process block, unfortunately not per thread. Since any other thread in the
	 * process (e.g. http task) can registers its own handler, there is no safe way
	 * to restore the previous handler, so we have to keep it.
	 */
	public static synchronized void installGlobalReplicationStateHandler() {
		if (!m_replProcInstalled) {
			try {
				//AccessController call required to prevent SecurityException when running in XPages
				prevReplProc = AccessController.doPrivileged((PrivilegedExceptionAction) () -> {
					if (PlatformUtils.isWin32()) {
						return (NotesCallbacks.OSSIGREPLPROC) NotesCAPI.get().OSSetReplicationSignalHandler(NotesConstants.OS_SIGNAL_REPL, replProcWin32);
					}
					else {
						return (NotesCallbacks.OSSIGREPLPROC) NotesCAPI.get().OSSetReplicationSignalHandler(NotesConstants.OS_SIGNAL_REPL, replProc);
					}
				});
			} catch (PrivilegedActionException e) {
				if (e.getCause() instanceof RuntimeException) {
					throw (RuntimeException) e.getCause();
				} else {
					throw new DominoException(0, "Error installing replication state listener", e);
				}
			}
			m_replProcInstalled = true;
		}
	}

	/**
	 * The break signal handler can be used to send a break signal to Domino
	 * so that the current (probably long running) operation, e.g. a fulltext on a remote
	 * database, can be interrupted.
	 * 
	 * @param callable callable to execute
	 * @param breakHandler break handler to interrupt the current operation
	 * @return optional result
	 * @throws DominoException if {@code callable} throws an exception
	 * 
	 * @param  result type
	 */
	public static  T runInterruptable(Callable callable, final IBreakHandler breakHandler) {
		installGlobalBreakHandler();

		Thread thread = Thread.currentThread();
		
		lastBreakHandlerException.set(null);
		m_breakHandlerByThread.put(thread, breakHandler);
		try {
			T result = callable.call();
			
			Exception e = lastBreakHandlerException.get();
			if (e!=null) {
				throw new DominoException("Error occurred in break handler", e);
			}
			return result;
		} catch (DominoException e) {
			throw e;
		} catch (Exception e1) {
			throw new DominoException("Error running operation with break handler", e1);
		}
		finally {
			lastBreakHandlerException.set(null);
			m_breakHandlerByThread.remove(thread);
		}
	}

	/**
	 * The progress signal handler can be used to get notified about the
	 * progress of method execution, e.g. replication or copy operations.
	 * 
	 * @param callable {@link Callable} to execute
	 * @param progressListener progress handler to get notified about progress changes
	 * @return optional result
	 * @throws DominoException if {@code callable} throws an error
	 * 
	 * @param  result type
	 */
	public static  T runWithProgress(Callable callable, final IProgressListener progressListener) {
		installGlobalProgressHandler();

		Thread thread = Thread.currentThread();
		
		lastProgressListenerException.set(null);
		m_progressListenerByThread.put(thread, progressListener);
		try {
			T result = callable.call();
			Exception e = lastProgressListenerException.get();
			if (e!=null) {
				throw new DominoException("Error occurred in progress listener", e);
			}
			return result;
		} catch (DominoException e) {
			throw e;
		} catch (Exception e1) {
			throw new DominoException("Error running operation with progress listener", e1);
		}
		finally {
			lastProgressListenerException.set(null);
			m_progressListenerByThread.remove(thread);
		}
	}

	/**
	 * The replication state handler can be used to get notified about the
	 * replication progress.
	 * 
	 * @param callable callable to execute
	 * @param replStateListener replication state handler to get notified about replication state changes
	 * @return optional result
	 * @throws Exception of callable throws an error
	 * 
	 * @param  result type
	 */
	public static  T runWithReplicationStateTracking(Callable callable,
			final ReplicationStateListener replStateListener) throws Exception {

		installGlobalReplicationStateHandler();

		Thread thread = Thread.currentThread();
		lastReplProcException.set(null);
		m_replicationStateListenerByThread.put(thread, replStateListener);
		try {
			T result = callable.call();
			Exception e = lastReplProcException.get();
			if (e!=null) {
				throw new DominoException("Error occurred in replication state listener", e);
			}
			return result;
		}
		finally {
			lastReplProcException.set(null);
			m_replicationStateListenerByThread.remove(thread);
		}
	}

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy