com.hcl.domino.jna.JNADominoClient 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 static com.hcl.domino.commons.util.NotesErrorUtils.checkResult;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.text.MessageFormat;
import java.time.temporal.TemporalAccessor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import javax.naming.AuthenticationException;
import javax.naming.AuthenticationNotSupportedException;
import javax.naming.NameNotFoundException;
import com.hcl.domino.BuildVersionInfo;
import com.hcl.domino.DominoException;
import com.hcl.domino.UserNamesList;
import com.hcl.domino.admin.ServerAdmin;
import com.hcl.domino.admin.ServerStatistics;
import com.hcl.domino.admin.idvault.IdVault;
import com.hcl.domino.admin.replication.Replication;
import com.hcl.domino.calendar.Calendaring;
import com.hcl.domino.commons.NotYetImplementedException;
import com.hcl.domino.commons.constants.CopyDatabase;
import com.hcl.domino.commons.data.DefaultDominoDateRange;
import com.hcl.domino.commons.data.DefaultModificationTimePair;
import com.hcl.domino.commons.gc.APIObjectAllocations;
import com.hcl.domino.commons.gc.CAPIGarbageCollector;
import com.hcl.domino.commons.gc.IAPIObject;
import com.hcl.domino.commons.gc.IGCControl;
import com.hcl.domino.commons.gc.IGCDominoClient;
import com.hcl.domino.commons.server.DefaultServerPingInfo;
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.data.CompactMode;
import com.hcl.domino.data.Database;
import com.hcl.domino.data.Database.ReplicateOption;
import com.hcl.domino.data.DatabaseChangePathList;
import com.hcl.domino.data.DatabaseClass;
import com.hcl.domino.data.DocumentClass;
import com.hcl.domino.data.DominoDateRange;
import com.hcl.domino.data.DominoDateTime;
import com.hcl.domino.data.DominoUniversalNoteId;
import com.hcl.domino.data.Formula;
import com.hcl.domino.data.IAdaptable;
import com.hcl.domino.data.ModificationTimePair;
import com.hcl.domino.dbdirectory.DbDirectory;
import com.hcl.domino.dxl.DxlExporter;
import com.hcl.domino.dxl.DxlImporter;
import com.hcl.domino.dxl.DxlImporter.DXLImportOption;
import com.hcl.domino.exception.BadPasswordException;
import com.hcl.domino.freebusy.FreeBusy;
import com.hcl.domino.html.RichTextHTMLConverter;
import com.hcl.domino.jna.admin.JNAIdVault;
import com.hcl.domino.jna.admin.JNAServerAdmin;
import com.hcl.domino.jna.admin.JNAServerStatistics;
import com.hcl.domino.jna.admin.replication.JNAReplication;
import com.hcl.domino.jna.calendaring.JNACalendaring;
import com.hcl.domino.jna.data.JNADatabase;
import com.hcl.domino.jna.data.JNADominoCollection;
import com.hcl.domino.jna.data.JNADominoDateTime;
import com.hcl.domino.jna.data.JNADominoUniversalNoteId;
import com.hcl.domino.jna.data.JNAFormula;
import com.hcl.domino.jna.data.JNAIDTable;
import com.hcl.domino.jna.data.JNAUserNamesList;
import com.hcl.domino.jna.dbdirectory.JNADbDirectory;
import com.hcl.domino.jna.dxl.JNADxlExporter;
import com.hcl.domino.jna.dxl.JNADxlImporter;
import com.hcl.domino.jna.freebusy.JNAFreeBusy;
import com.hcl.domino.jna.html.JNARichtextHTMLConverter;
import com.hcl.domino.jna.internal.DisposableMemory;
import com.hcl.domino.jna.internal.ItemDecoder;
import com.hcl.domino.jna.internal.JNANotesConstants;
import com.hcl.domino.jna.internal.JNANotesReplicationStats;
import com.hcl.domino.jna.internal.JNASignalHandlerUtil;
import com.hcl.domino.jna.internal.Mem;
import com.hcl.domino.jna.internal.NotesNamingUtils;
import com.hcl.domino.jna.internal.NotesNamingUtils.Privileges;
import com.hcl.domino.jna.internal.NotesStringUtils;
import com.hcl.domino.jna.internal.capi.INotesCAPI1201;
import com.hcl.domino.jna.internal.capi.NotesCAPI;
import com.hcl.domino.jna.internal.capi.NotesCAPI1201;
import com.hcl.domino.jna.internal.gc.allocations.JNADatabaseAllocations;
import com.hcl.domino.jna.internal.gc.allocations.JNADominoClientAllocations;
import com.hcl.domino.jna.internal.gc.allocations.JNAUserNamesListAllocations;
import com.hcl.domino.jna.internal.gc.handles.DHANDLE;
import com.hcl.domino.jna.internal.gc.handles.HANDLE;
import com.hcl.domino.jna.internal.gc.handles.LockUtil;
import com.hcl.domino.jna.internal.structs.DbOptionsStruct;
import com.hcl.domino.jna.internal.structs.NotesTimeDateStruct;
import com.hcl.domino.jna.internal.structs.ReplExtensionsStruct;
import com.hcl.domino.jna.internal.structs.ReplServStatsStruct;
import com.hcl.domino.jna.internal.structs.VerifyLDAPConnectionStruct;
import com.hcl.domino.jna.mime.JNAMimeReader;
import com.hcl.domino.jna.mime.JNAMimeWriter;
import com.hcl.domino.jna.mq.JNAMessageQueues;
import com.hcl.domino.jna.naming.JNAUserDirectory;
import com.hcl.domino.jna.person.JNAPerson;
import com.hcl.domino.jna.runtime.JNADominoRuntime;
import com.hcl.domino.jna.security.JNAEcl;
import com.hcl.domino.jna.utils.JNADominoUtils;
import com.hcl.domino.mime.MimeReader;
import com.hcl.domino.mime.MimeWriter;
import com.hcl.domino.misc.DominoEnumUtil;
import com.hcl.domino.misc.NotesConstants;
import com.hcl.domino.misc.Pair;
import com.hcl.domino.mq.MessageQueues;
import com.hcl.domino.naming.Names;
import com.hcl.domino.naming.UserDirectory;
import com.hcl.domino.person.Person;
import com.hcl.domino.runtime.DominoRuntime;
import com.hcl.domino.security.Acl;
import com.hcl.domino.security.AclFlag;
import com.hcl.domino.security.AclLevel;
import com.hcl.domino.security.Ecl;
import com.hcl.domino.server.ServerPingInfo;
import com.sun.jna.Memory;
import com.sun.jna.Pointer;
import com.sun.jna.ptr.DoubleByReference;
import com.sun.jna.ptr.IntByReference;
import com.sun.jna.ptr.LongByReference;
public class JNADominoClient implements IGCDominoClient {
public static final String PROP_DEFAULTQUEUEFLUSH = "jnx.gc.defaultqueueflush"; //$NON-NLS-1$
public static final String ENV_DEFAULTQUEUEFLUSH = "JNX_GC_DEFAULTQUEUEFLUSH"; //$NON-NLS-1$
public static final String PROP_ALLOWCROSSTHREAD = "jnx.allowCrossThreadAccess"; //$NON-NLS-1$
public static final String ENV_ALLOWCROSSTHREAD = "JNX_ALLOWCROSSTHREADACCESS"; //$NON-NLS-1$
private final List m_builderNamesList;
private final boolean m_builderIsAsId;
private final String m_builderUserName;
private final boolean m_builderIsInternetAccess;
private final boolean m_builderFullAccess;
private Thread m_parentThread;
private Map m_customValues;
private JNADominoClientAllocations m_allocations;
private JNADominoRuntime m_dominoRuntime;
private Boolean m_isOnServer;
private String m_effectiveUserName;
private IGCControl m_gcCtrl;
private final Set listeners = new LinkedHashSet<>();
private boolean m_allowCrossThreadAccess;
private boolean m_registeredForGC;
private BuildVersionInfo localBuildVersionInfo;
JNADominoClient(JNADominoClientBuilder builder) {
List names = builder.getUserNamesList();
m_builderNamesList = names == null ? Collections.emptyList() : Collections.unmodifiableList(new ArrayList<>(names));
m_builderIsAsId = builder.isAsIDUser();
m_builderUserName = builder.getUserName();
m_builderIsInternetAccess = builder.isMaxInternetAccess();
m_builderFullAccess = builder.isFullAccess();
m_parentThread = Thread.currentThread();
m_customValues = new HashMap<>();
// IGCControl implementation to auto-flush the GC reference queue every 50 object allocations by
// default
Integer queueFlushThreshold = AccessController.doPrivileged((PrivilegedAction) () -> {
String str = System.getProperty(PROP_DEFAULTQUEUEFLUSH);
if (StringUtil.isEmpty(str)) {
str = System.getenv(ENV_DEFAULTQUEUEFLUSH);
}
if (!StringUtil.isEmpty(str)) {
try {
return Integer.valueOf(str);
} catch (NumberFormatException e) {
System.err.println(
MessageFormat.format("Invalid value for default GC ref queue flush: {0}", str));
}
}
return null;
});
if (queueFlushThreshold == null) {
queueFlushThreshold = 50;
}
m_gcCtrl = new DefaultGCControl(queueFlushThreshold);
CAPIGarbageCollector.registerDominoClient(this);
m_allocations = new JNADominoClientAllocations(this, null, null,
CAPIGarbageCollector.getReferenceQueueForClient(this));
String credUser = builder.getCredUser();
if (StringUtil.isNotEmpty(credUser)) {
try {
m_effectiveUserName = validateCredentials(builder.getCredServer(), builder.getCredUser(),
builder.getCredPassword());
} catch (AuthenticationNotSupportedException | AuthenticationException
| NameNotFoundException e) {
throw new DominoException("Exception when validating user credentials", e);
}
} else {
Object credToken = builder.getCredToken();
if (credToken != null) {
try {
m_effectiveUserName = validateCredentialsWithToken(builder.getCredServer(), credToken);
} catch (AuthenticationNotSupportedException | AuthenticationException
| NameNotFoundException e) {
throw new DominoException("Exception when validating user credentials", e);
}
}
}
m_allowCrossThreadAccess = AccessController.doPrivileged((PrivilegedAction) () -> {
if (!"false".equals(System.getProperty(PROP_ALLOWCROSSTHREAD)) && //$NON-NLS-1$
!"false".equals(System.getenv(ENV_ALLOWCROSSTHREAD))) { //$NON-NLS-1$
// cross thread access on by default
return true;
} else {
return false;
}
});
}
@Override
public String getEffectiveUserName() {
if (m_effectiveUserName == null) {
if (m_builderIsAsId) {
m_effectiveUserName = getIDUserName();
} else if (!StringUtil.isEmpty(m_builderUserName)) {
m_effectiveUserName = NotesNamingUtils.toCanonicalName(m_builderUserName);
} else if (!m_builderNamesList.isEmpty()) {
m_effectiveUserName =
NotesNamingUtils.toCanonicalName(m_builderNamesList.get(0));
} else {
if (isOnServer()) {
m_effectiveUserName = "Anonymous"; //$NON-NLS-1$
} else {
m_effectiveUserName = getIDUserName();
}
}
}
return m_effectiveUserName;
}
@Override
public UserNamesList getEffectiveUserNamesList(String server) {
JNAUserNamesList namesList;
if (!m_builderNamesList.isEmpty()) {
// special case where the full usernameslist is already provided
namesList = NotesNamingUtils.writeNewNamesList(this, m_builderNamesList);
} else {
namesList = NotesNamingUtils.buildNamesList(this, server, getEffectiveUserName());
}
// setting authenticated flag for the user is required when running on the server
if (isFullAccess()) {
NotesNamingUtils.setPrivileges(namesList, EnumSet.of(Privileges.FullAdminAccess,
Privileges.Authenticated));
} else {
if (m_builderIsInternetAccess) {
NotesNamingUtils.setPrivileges(namesList,
EnumSet.of(Privileges.Authenticated, Privileges.PasswordAuthenticated));
} else {
NotesNamingUtils.setPrivileges(namesList, EnumSet.of(Privileges.Authenticated));
}
}
return namesList;
}
@SuppressWarnings("unchecked")
@Override
public T getAdapter(Class clazz) {
if (clazz == APIObjectAllocations.class) {
return (T) m_allocations;
} else if (clazz == Thread.class) {
return (T) m_parentThread;
} else if (clazz == IGCControl.class) {
return (T) m_gcCtrl;
}
return null;
}
@Override
public String getIDUserName() {
try(DisposableMemory retUserNameMem = new DisposableMemory(NotesConstants.MAXUSERNAME + 1)) {
checkResult(NotesCAPI.get().SECKFMGetUserName(retUserNameMem));
int userNameLength = 0;
for (int i = 0; i < retUserNameMem.size(); i++) {
userNameLength = i;
if (retUserNameMem.getByte(i) == 0) {
break;
}
}
return NotesStringUtils.fromLMBCS(retUserNameMem, userNameLength);
}
}
@Override
public boolean isOnServer() {
if (m_isOnServer == null) {
m_isOnServer = NotesConstants.PROCESS_GROUP_NOTES_SERVER == NotesCAPI.get()
.OSProcessGroup(NotesConstants.PROCESS_GROUP_QUERY);
}
return m_isOnServer;
}
private String[] resolveDbPath(String path) {
int iPos = path.indexOf("!!"); //$NON-NLS-1$
if (iPos != -1) {
String server = path.substring(0, iPos);
String filePath = path.substring(iPos + 2);
return new String[] {server, filePath};
} else {
return new String[] {"", path}; //$NON-NLS-1$
}
}
@Override
public Database createDatabase(String paramServerName, String filePath, boolean forceCreation,
boolean initDesign,
Encryption encryption, DatabaseClass dbClass) {
if (StringUtil.isEmpty(filePath)) {
throw new IllegalArgumentException("filePath cannot be empty");
}
String serverName = paramServerName;
if (serverName == null) {
serverName = ""; //$NON-NLS-1$
}
serverName = NotesNamingUtils.toCanonicalName(serverName);
boolean isOnServer = isOnServer();
if (!"".equals(serverName)) { //$NON-NLS-1$
if (isOnServer) {
String serverCN = NotesNamingUtils.toCommonName(serverName);
String currServerCanonical = getIDUserName();
String currServerCN = NotesNamingUtils.toCommonName(currServerCanonical);
if (serverCN.equalsIgnoreCase(currServerCN)) {
// switch to "" as servername if server points to the server the API is running on
serverName = ""; //$NON-NLS-1$
}
}
}
JNAUserNamesList userNamesList = (JNAUserNamesList) getEffectiveUserNamesList(serverName);
byte encryptStrengthByte = (byte) (encryption.getValue() & 0xff);
short dbClassShort = dbClass.getValue();
short optionsShort =
NotesConstants.DBCREATE_FORM_BUCKET_OPT | NotesConstants.DBCREATE_LARGE_UNKTABLE;
if (encryptStrengthByte != 0) {
optionsShort |= NotesConstants.DBCREATE_LOCALSECURITY;
}
final short fOptionsShort = optionsShort;
DisposableMemory path = JNADominoUtils.constructNetPath(this, serverName, filePath);
JNAUserNamesListAllocations namesListAllocations = userNamesList != null
? (JNAUserNamesListAllocations) userNamesList.getAdapter(APIObjectAllocations.class)
: null;
int options2 = 0;
DbOptionsStruct.ByValue defaultDbOptionsByVal = DbOptionsStruct.newInstanceByVal();
checkResult(LockUtil.lockHandle(
namesListAllocations == null ? null : namesListAllocations.getHandle(), (hNamesList) -> {
return NotesCAPI.get().NSFDbCreateExtended4(path, dbClassShort, forceCreation,
fOptionsShort,
options2, encryptStrengthByte, 0, (Memory) null, (Memory) null,
(short) 0, (short) 0, defaultDbOptionsByVal, hNamesList, (DHANDLE.ByValue) null);
}));
// create ACL
HANDLE.ByReference rethDb = HANDLE.newInstanceByReference();
checkResult(NotesCAPI.get().NSFDbOpen(path, rethDb));
LockUtil.lockHandle(rethDb, (rethDbByVal) -> {
try {
DHANDLE.ByReference rethACL = DHANDLE.newInstanceByReference();
checkResult(NotesCAPI.get().ACLCreate(rethACL));
checkResult(LockUtil.lockHandle(rethACL, (rethACLByVal) -> {
return NotesCAPI.get().NSFDbStoreACL(rethDbByVal, rethACLByVal, 0, (short) 1);
}));
return 0;
} finally {
checkResult(NotesCAPI.get().NSFDbClose(rethDbByVal));
}
});
Database db = openDatabase(serverName, filePath);
// the method creates the design collection which is missing after a NSFDbCreate
JNADominoCollection designCollection = (JNADominoCollection) db.openDesignCollection();
if (designCollection != null) {
designCollection.dispose();
}
if (initDesign) {
InputStream in = null;
try {
// use DXL importer to create basic structures like the icon note
// and the design collection
in = JNANotesConstants.class.getResourceAsStream("blank_dxl.xml"); //$NON-NLS-1$
if (in == null) {
throw new IllegalStateException("File blank_dxl.xml not found");
}
JNADxlImporter importer = (JNADxlImporter) createDxlImporter();
importer.setDesignImportOption(DXLImportOption.REPLACE_ELSE_CREATE);
importer.setReplaceDbProperties(true);
importer.importDxl(in, db);
importer.dispose();
} catch (IOException e) {
throw new DominoException(MessageFormat
.format("Error initializing design of new database {0}!!{1}", serverName, filePath), e);
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
}
}
}
}
// write default acl entries, might lock us out
Acl acl = db.getACL();
AclLevel defaultAccessLevel = AclLevel.NOACCESS;
String manager = getEffectiveUserName();
acl.updateEntry("-Default-", "-Default-", defaultAccessLevel, Collections.emptyList(), //$NON-NLS-1$ //$NON-NLS-2$
EnumSet.noneOf(AclFlag.class));
acl.updateEntry("OtherDomainServers", null, AclLevel.NOACCESS, Collections.emptyList(), //$NON-NLS-1$
EnumSet.of(AclFlag.GROUP, AclFlag.SERVER));
acl.updateEntry(manager, null, AclLevel.MANAGER, Collections.emptyList(),
EnumSet.noneOf(AclFlag.class));
if (db.isRemote()) {
acl.updateEntry(db.getServer(), null, AclLevel.MANAGER, Collections.emptyList(),
EnumSet.of(AclFlag.SERVER, AclFlag.ADMIN_SERVER));
acl.setAdminServer(db.getServer());
}
acl.updateEntry("LocalDomainServers", null, AclLevel.MANAGER, Collections.emptyList(), //$NON-NLS-1$
EnumSet.of(AclFlag.GROUP, AclFlag.SERVER));
acl.save();
return db;
}
@Override
public Database createDatabaseFromTemplate(String sourceServerName, String sourceFilePath,
String targetServerName, String targetFilePath,
Encryption encryption) {
EnumSet copyFlags = EnumSet.noneOf(CopyDatabase.class);
if (encryption == Encryption.Simple) {
copyFlags.add(CopyDatabase.ENCRYPT_SIMPLE);
} else if (encryption == Encryption.Medium) {
copyFlags.add(CopyDatabase.ENCRYPT_MEDIUM);
} else if (encryption == Encryption.Strong || encryption == Encryption.AES128) {
copyFlags.add(CopyDatabase.ENCRYPT_STRONG);
}
Database newDb = createAndCopyDatabase(sourceServerName, sourceFilePath, targetServerName,
targetFilePath, null, 0,
(Set) null, null);
return newDb;
}
@Override
public Database createDatabaseReplica(String sourceServerName, String sourceFilePath,
String targetServerName, String targetFilePath,
Encryption encryption) {
EnumSet copyFlags = EnumSet.of(CopyDatabase.REPLICA, CopyDatabase.REPLICA_NAMELIST);
if (encryption == Encryption.Simple) {
copyFlags.add(CopyDatabase.ENCRYPT_SIMPLE);
} else if (encryption == Encryption.Medium) {
copyFlags.add(CopyDatabase.ENCRYPT_MEDIUM);
} else if (encryption == Encryption.Strong || encryption == Encryption.AES128) {
copyFlags.add(CopyDatabase.ENCRYPT_STRONG);
}
return createAndCopyDatabase(sourceServerName, sourceFilePath, targetServerName, targetFilePath,
null, 0, copyFlags, null);
}
/**
* This function creates a new copy of a Domino database based on database at
* sourceServerName / sourceFilePath
* and allows for a {@link JNAUserNamesList} structure UserName to provide authentication for
* trusted servers.
*
* The database class of the new database is based on the file extension specified by
* sourceServerName / sourceFilePath
.
*
* Specifically, the new copy will contain the replication settings, database options, Access
* Control List,
* Full Text Index (if any), as well as data and non-data notes (dependent on the DocumentClass
* argument) of
* the original database.
*
* You may specify the types of notes that you want copied to the new database with the
* docClassesToCopy
argument.
*
* You may also specify the maximum size (database quota) that the database can grow to with the
* maxFileSize
argument.
*
* Additionally, you may specify that the new database is to be a replica copy of the original
* database,
* meaning that it will share the same replica ID.
*
* @param sourceDb database to copy
* @param targetServerName server name of new database to be created
* @param targetFilePath filepath of new database to be created
* @param docClassesToCopy type of notes to copy or null
to copy all content
* @param maxFileSize Size limit for new database in bytes, will be rounded to full megabytes.
* This argument will control how large the new copy can grow to. Specify a value of zero
* if you do not wish to place a size limit on the newly copied database.
* @param copyFlags Option flags determining type of copy. Currently, the only supported flags are
* {@link CopyDatabase#REPLICA}, {@link CopyDatabase#ENCRYPT_SIMPLE},
* {@link CopyDatabase#ENCRYPT_MEDIUM}, {@link CopyDatabase#ENCRYPT_STRONG},
* {@link CopyDatabase#REPLICA_NAMELIST}, {@link CopyDatabase#OVERRIDE_DEST}.
* @param namesList may be null or a UserName that is used to provide authentication for trusted
* servers. This causes the UserName's ACL permissions in the database to be enforced.
* Please see {@link NotesNamingUtils#buildNamesList(String)} NSFBuildNamesList for more
* information on building a NAMES_LIST structure.
* @return database copy
*/
private Database createAndCopyDatabase(String sourceServerName, String sourceFilePath,
String targetServerName, String targetFilePath,
Set docClassesToCopy,
long maxFileSize, Set copyFlags,
JNAUserNamesList namesList) {
// Open the source database to account for odd cases (e.g. shared data directories)
Database sourceDb = getParentDominoClient().openDatabase(sourceServerName, sourceFilePath);
DisposableMemory fullPathTargetMem =
JNADominoUtils.constructNetPath(this, targetServerName, targetFilePath);
DisposableMemory fullPathSourceMem =
JNADominoUtils.constructNetPath(this, sourceDb.getServer(), sourceDb.getRelativeFilePath());
short noteClassToCopy = docClassesToCopy == null ? NotesConstants.NOTE_CLASS_ALL
: DominoEnumUtil.toBitField(DocumentClass.class, docClassesToCopy);
int createCopyFlags = CopyDatabase.toBitMask(copyFlags);
createCopyFlags |= NotesConstants.DBCOPY_DEST_IS_NSF;
short result;
JNAUserNamesList namesListForDbCreate;
if (namesList == null) {
namesListForDbCreate = (JNAUserNamesList) getEffectiveUserNamesList(targetServerName);
} else {
namesListForDbCreate = namesList;
}
JNAUserNamesList fNamesListForDbCreate = namesListForDbCreate;
short maxFileSizeInMB = (short) ((maxFileSize / (1024 * 1024)) & 0xffff);
HANDLE.ByReference rethNewDb = HANDLE.newInstanceByReference();
result = NotesCAPI.get().NSFDbCreateAndCopyExtended(fullPathSourceMem, fullPathTargetMem,
noteClassToCopy, maxFileSizeInMB, createCopyFlags, null, rethNewDb);
checkResult(result);
return new JNADatabase(this, new IAdaptable() {
@Override
@SuppressWarnings("unchecked")
public T getAdapter(Class clazz) {
if (HANDLE.class.equals(clazz)) {
return (T) rethNewDb;
} else if (JNAUserNamesList.class.equals(clazz)) {
return (T) fNamesListForDbCreate;
} else {
return null;
}
}
});
}
@Override
public Database openDatabase(String path, Set options) {
String[] resolvedServerAndPath = resolveDbPath(path);
if (StringUtil.isEmpty(resolvedServerAndPath[1])) {
throw new DominoException(0,
MessageFormat.format("Unable to find database for path {0}", path));
}
return openDatabase(resolvedServerAndPath[0], resolvedServerAndPath[1], options);
}
@Override
public Database openDatabase(String serverName, String filePath, Set options) {
if (serverName == null) {
serverName = ""; //$NON-NLS-1$
}
serverName = NotesNamingUtils.toCanonicalName(serverName);
if (DominoUtils.isReplicaId(filePath)) {
// scan directory for db with replica id
try (JNADatabase dir =
new JNADatabase(this, serverName, "", EnumSet.noneOf(OpenDatabase.class));) { //$NON-NLS-1$
int[] innards = NotesStringUtils.replicaIdToInnards(filePath);
NotesTimeDateStruct replicaIdStruct = NotesTimeDateStruct.newInstance(innards);
try(DisposableMemory retPathNameMem = new DisposableMemory(NotesConstants.MAXPATH)) {
JNADatabaseAllocations dbAllocations =
(JNADatabaseAllocations) dir.getAdapter(APIObjectAllocations.class);
short result = LockUtil.lockHandle(dbAllocations.getDBHandle(), (hDirByVal) -> {
return NotesCAPI.get().NSFDbLocateByReplicaID(hDirByVal, replicaIdStruct,
retPathNameMem, (short) (NotesConstants.MAXPATH & 0xffff));
});
if (result == 259) {
throw new DominoException(result, MessageFormat.format(
"No database found on server ''{0}'' for replica id {1}", serverName, filePath));
}
checkResult(result);
String retPathName = NotesStringUtils.fromLMBCS(retPathNameMem, -1);
if (retPathName == null || retPathName.length() == 0) {
throw new DominoException(result, MessageFormat.format(
"No database found on server ''{0}'' for replica id {1}", serverName, filePath));
} else {
return new JNADatabase(this, serverName, retPathName, options);
}
}
}
} else {
return new JNADatabase(this, serverName, filePath, options);
}
}
@Override
public Optional openMailDatabase(Set options) {
String mailServer;
String mailFile;
DominoRuntime runtime = getDominoRuntime();
if (!isOnServer()) {
mailServer = runtime.getPropertyString("MailServer"); //$NON-NLS-1$
mailFile = runtime.getPropertyString("MailFile"); //$NON-NLS-1$
} else {
String effectiveUsername = getEffectiveUserName();
JNAFormula serverFormula = (JNAFormula) createFormula(
"@NameLookup( [NoUpdate] ; \"" + effectiveUsername + "\"; \"MailServer\" )"); //$NON-NLS-1$ //$NON-NLS-2$
mailServer = serverFormula.evaluateAsString();
serverFormula.dispose();
JNAFormula mailFileFormula = (JNAFormula) createFormula(
"@NameLookup( [NoUpdate] ; \"" + effectiveUsername + "\"; \"MailFile\" )"); //$NON-NLS-1$ //$NON-NLS-2$
mailFile = mailFileFormula.evaluateAsString();
mailFileFormula.dispose();
}
if (StringUtil.isEmpty(mailFile)) {
return Optional.empty();
}
return Optional.of(openDatabase(mailServer, mailFile, options));
}
@Override
public void deleteDatabase(final String paramServerName, final String filePath) {
if (StringUtil.isEmpty(filePath)) {
throw new IllegalArgumentException("filePath cannot be empty");
}
String serverName = paramServerName;
if (serverName == null) {
serverName = ""; //$NON-NLS-1$
}
serverName = NotesNamingUtils.toCanonicalName(serverName);
DisposableMemory path = JNADominoUtils.constructNetPath(this, serverName, filePath);
checkResult(NotesCAPI.get().NSFDbDelete(path));
}
@Override
public boolean isAdmin() {
// TODO Auto-generated method stub
throw new NotYetImplementedException();
}
@Override
public boolean isFullAccess() {
return m_builderFullAccess;
}
@Override
public DominoRuntime getDominoRuntime() {
if (m_dominoRuntime == null) {
m_dominoRuntime = new JNADominoRuntime(this);
}
return m_dominoRuntime;
}
/**
* Frees all memory and internal handles held by this Domino client session
*/
public void dispose() {
if (m_allocations != null && !m_allocations.isDisposed()) {
CAPIGarbageCollector.dispose(this);
CAPIGarbageCollector.unregisterDominoClient(this);
m_allocations = null;
// CAPIGarbageCollector.decRefCountForDominoEnabledThread();
}
}
/**
* Checks if this Domino client session object is already recycled
*
* @return true if recycled
*/
public boolean isDisposed() {
return m_allocations == null || m_allocations.isDisposed();
}
@Override
public void close() {
if (m_allocations != null && !m_allocations.isDisposed()) {
dispose();
this.listeners.forEach(l -> l.onClose(this));
}
}
/**
* Use this method to store your own custom values for this Domino client.
*
* @param key key
* @param value value, implement interface {@link IDisposableCustomValue} to get called for
* disposal
* @return previous value
*/
public Object setCustomValue(String key, Object value) {
return m_customValues.put(key, value);
}
/**
* Reads a custom value stored via {@link #setCustomValue(String, Object)}
* execution block.
*
* @param key the key of the stored custom value
* @return value or null if not set
*/
public Object getCustomValue(String key) {
return m_customValues.get(key);
}
/**
* Tests if a custom value has been set via {@link #setCustomValue(String, Object)}.
*
* @param key key
* @return true if value is set
*/
public boolean hasCustomValue(String key) {
return m_customValues.containsKey(key);
}
/**
* When using {@code NotesGC#setCustomValue(String, Object)} to store your own
* values, use this
* interface for your value to get called for disposal when the
* {@code NotesGC#runWithAutoGC(Callable)}
* block is finished. Otherwise the value is just removed from the intermap map.
*
* @author Karsten Lehmann
*/
public interface IDisposableCustomValue {
void dispose();
}
@Override
public IAPIObject> getParent() {
return null;
}
@Override
public JNADominoClient getParentDominoClient() {
return this;
}
@Override
public DominoDateTime createDateTime(TemporalAccessor temporal) {
return JNADominoDateTime.from(temporal);
}
@Override
public DominoDateRange createDateRange(TemporalAccessor start, TemporalAccessor end) {
DominoDateTime startDT = createDateTime(start);
DominoDateTime endDT = createDateTime(end);
return new DefaultDominoDateRange(startDT, endDT);
}
@Override
public DxlExporter createDxlExporter() {
IntByReference rethDxlExporter = new IntByReference();
NotesCAPI.get().DXLCreateExporter(rethDxlExporter);
return new JNADxlExporter(this, rethDxlExporter.getValue());
}
@Override
public DxlImporter createDxlImporter() {
IntByReference rethDxlImporter = new IntByReference();
NotesCAPI.get().DXLCreateImporter(rethDxlImporter);
return new JNADxlImporter(this, rethDxlImporter.getValue());
}
@Override
public DbDirectory openDbDirectory() {
return new JNADbDirectory(this);
}
@Override
public FreeBusy getFreeBusy() {
return new JNAFreeBusy(this);
}
@Override
public Calendaring getCalendaring() {
return new JNACalendaring(this);
}
@Override
public MessageQueues getMessageQueues() {
return new JNAMessageQueues(this);
}
@Override
public MimeReader getMimeReader() {
return new JNAMimeReader(this);
}
@Override
public MimeWriter getMimeWriter() {
return new JNAMimeWriter(this);
}
@Override
public JNAIDTable createIDTable() {
return new JNAIDTable(this);
}
@Override
public Formula createFormula(String formula) {
return new JNAFormula(this, formula);
}
@Override
public void verifyLdapConnection(
String hostName, String userName, String password, String dnSearch, boolean useSSL,
short port, boolean acceptExpiredCerts, boolean verifyRemoteServerCert) {
VerifyLDAPConnectionStruct ldap = new VerifyLDAPConnectionStruct();
NotesStringUtils.toLMBCS(hostName, true, ldap.szHostName);
NotesStringUtils.toLMBCS(userName, true, ldap.szUserName);
NotesStringUtils.toLMBCS(password, true, ldap.szPassword);
NotesStringUtils.toLMBCS(dnSearch, true, ldap.szDNSearch);
ldap.bAcceptExpiredCertificates = acceptExpiredCerts;
ldap.bVerifyRemoteSrvCert = verifyRemoteServerCert;
ldap.wPort = port;
ldap.write();
NotesErrorUtils.checkResult(NotesCAPI.get().VerifyLDAPConnection(ldap));
}
@Override
public DominoUniversalNoteId createUNID(String unidStr) {
return new JNADominoUniversalNoteId(unidStr);
}
@Override
public RichTextHTMLConverter getRichTextHtmlConverter() {
return new JNARichtextHTMLConverter(this);
}
@Override
public NotesReplicationStats replicateDbsWithServer(String serverName,
Set options,
List fileList, int timeLimitMin, ReplicationStateListener progressListener) {
if (timeLimitMin > 65535) {
throw new IllegalArgumentException("Time limit cannot be greater than 65535 minutes.");
}
Memory serverNameMem =
NotesStringUtils.toLMBCS(NotesNamingUtils.toAbbreviatedName(serverName), true);
ReplServStatsStruct retStatsStruct = ReplServStatsStruct.newInstance();
ReplExtensionsStruct extendedOptions = ReplExtensionsStruct.newInstance();
extendedOptions.Size = 2 + 2;
extendedOptions.TimeLimit = (short) (timeLimitMin & 0xffff);
extendedOptions.write();
int numFiles = 0;
DisposableMemory fileListMem = null;
if (fileList != null && !fileList.isEmpty()) {
if (fileList.size() > 65535) {
throw new IllegalArgumentException("Number of files cannot be greater than 65535.");
}
numFiles = (short) (fileList.size() & 0xffff);
ByteArrayOutputStream bOut = new ByteArrayOutputStream();
for (String currFileName : fileList) {
if (currFileName.length() > 0) {
Memory currFileNameMem = NotesStringUtils.toLMBCS(currFileName, true);
try {
bOut.write(currFileNameMem.getByteArray(0, (int) currFileNameMem.size()));
} catch (IOException e) {
throw new DominoException(0, "Error writing file list to memory", e);
}
}
}
fileListMem = new DisposableMemory(bOut.size());
byte[] bOutArr = bOut.toByteArray();
fileListMem.write(0, bOutArr, 0, bOutArr.length);
}
int optionsInt = DominoEnumUtil.toBitField(ReplicateOption.class, options);
short result;
if (progressListener != null) {
final int fNumFiles = numFiles;
final DisposableMemory fFileListMem = fileListMem;
try {
JNASignalHandlerUtil.runWithReplicationStateTracking(() -> {
short result1 = NotesCAPI.get().ReplicateWithServerExt(null, serverNameMem, optionsInt,
(short) (fNumFiles & 0xffff),
fFileListMem, extendedOptions, retStatsStruct);
checkResult(result1);
return null;
}, progressListener);
} catch (Exception e) {
throw new DominoException("Error replicating with state tracking", e);
}
} else {
result = NotesCAPI.get().ReplicateWithServerExt(null, serverNameMem, optionsInt,
(short) (numFiles & 0xffff),
fileListMem, extendedOptions, retStatsStruct);
checkResult(result);
}
retStatsStruct.read();
if (fileListMem != null) {
fileListMem.close();
}
NotesReplicationStats retStats = new JNANotesReplicationStats(retStatsStruct);
return retStats;
}
@Override
public T runInterruptable(Callable callable, IBreakHandler breakHandler) {
return JNASignalHandlerUtil.runInterruptable(callable, breakHandler);
}
@Override
public T runWithProgress(Callable callable, IProgressListener progressHandler) {
return JNASignalHandlerUtil.runWithProgress(callable, progressHandler);
}
@Override
public ServerAdmin getServerAdmin() {
return new JNAServerAdmin(this);
}
@Override
public ServerStatistics getServerStatistics() {
return new JNAServerStatistics();
}
@Override
public Collection getKnownServers(String portName) {
Memory pPortName = NotesStringUtils.toLMBCS(portName, true);
DHANDLE.ByReference hServerTextList = DHANDLE.newInstanceByReference();
checkResult(NotesCAPI.get().NSGetServerList(pPortName, hServerTextList));
if (hServerTextList.isNull()) {
return Collections.emptyList();
}
return LockUtil.lockHandle(hServerTextList, hServerTextListVal -> {
return Mem.OSLockObject(hServerTextListVal, ptr -> {
int serverCount = Short.toUnsignedInt(ptr.getShort(0));
Pointer pNameLengths = ptr.share(2); // WORD
Pointer pNames = pNameLengths.share(serverCount * 2); // WORD
int[] nameLengths = IntStream.range(0, serverCount)
.map(i -> Short.toUnsignedInt(pNameLengths.getShort(i * 2)))
.toArray();
Pointer pCurrentName = pNames;
Collection result = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
for (int i = 0; i < serverCount; i++) {
result.add(NotesStringUtils.fromLMBCS(pCurrentName, nameLengths[i]));
pCurrentName = pCurrentName.share(nameLengths[i]);
}
return result;
});
});
}
@Override
public DatabaseChangePathList getDatabasePaths(String serverName, TemporalAccessor since) {
Memory pServerName = NotesStringUtils.toLMBCS(serverName, true);
JNADominoDateTime sinceDt =
new JNADominoDateTime(since == null ? JNADominoDateTime.createMinimumDateTime() : since);
NotesTimeDateStruct.ByReference sinceStruct = NotesTimeDateStruct.newInstanceByReference(sinceDt.getInnards());
LongByReference changesSize = new LongByReference();
DHANDLE.ByReference hChanges = DHANDLE.newInstanceByReference();
NotesTimeDateStruct.ByReference nextSinceTime = NotesTimeDateStruct.newInstanceByReference();
checkResult(NotesCAPI.get().NSFGetChangedDBs(pServerName, sinceStruct, changesSize, hChanges,
nextSinceTime));
nextSinceTime.read();
return LockUtil.lockHandle(hChanges, hChangesVal -> {
Collection paths = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
if (hChangesVal!=null && !hChangesVal.isNull()) {
Mem.OSLockObject(hChangesVal, ptr -> {
long remaining = changesSize.getValue();
Pointer pPath = ptr;
while (remaining > 0) {
int strlen = NotesStringUtils.getNullTerminatedLength(pPath);
paths.add(NotesStringUtils.fromLMBCS(pPath, strlen));
pPath = pPath.share(strlen + 1);
remaining -= strlen + 1;
}
return null;
});
}
return new DatabaseChangePathList(paths, new JNADominoDateTime(nextSinceTime.Innards));
});
}
@Override
public IdVault getIdVault() {
return new JNAIdVault(this);
}
@Override
public Person getPerson(String username) {
return new JNAPerson(this, username);
}
private AtomicReference skipNewNABCredentialCheck = new AtomicReference<>();
private boolean isSkipNewNABCredentialCheck() {
Boolean skip = skipNewNABCredentialCheck.get();
if (skip==null) {
skip = DominoUtils.checkBooleanProperty("jnx.skipnewnabcredentialcheck", //$NON-NLS-1$
"JNX_SKIPNEWNABCREDENTIALCHECK"); //$NON-NLS-1$
skipNewNABCredentialCheck.set(skip);
}
return skip.booleanValue();
}
@Override
public String validateCredentials(String serverName, String userName, String password)
throws NameNotFoundException, AuthenticationNotSupportedException, AuthenticationException {
if (StringUtil.isEmpty(userName)) {
throw new IllegalArgumentException("userName cannot be empty");
}
String fullName;
if (StringUtil.isEmpty(serverName) || Names.equalNames(getIDUserName(), serverName)) {
if (!isSkipNewNABCredentialCheck()) {
//use credential check function added in 12.0.1 GA if possible
try {
INotesCAPI1201 capi1201 = NotesCAPI1201.get();
Memory variableUserNameMem = NotesStringUtils.toLMBCS(userName, true);
Memory variablePasswordMem = NotesStringUtils.toLMBCS(password, true);
try(DisposableMemory rethValueBuffer = new DisposableMemory(NotesConstants.MAXUSERNAME)) {
short result = capi1201.NABLookupBasicAuthentication(variableUserNameMem,
variablePasswordMem, NotesConstants.BASIC_AUTH_ALLOW_AMBIGUOUS_NAMES,
NotesConstants.MAXUSERNAME,
rethValueBuffer);
if (result == 1) {
fullName = NotesStringUtils.fromLMBCS(rethValueBuffer, -1);
} else {
throw new AuthenticationException("Invalid user name or password");
}
}
return StringUtil.isEmpty(fullName) ? userName : fullName;
}
catch (UnsatisfiedLinkError e) {
//may be thrown if C function is not yet available
skipNewNABCredentialCheck.set(Boolean.TRUE);
}
}
}
UserDirectory dir = openUserDirectory(serverName);
List
© 2015 - 2025 Weber Informatics LLC | Privacy Policy