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

com.hcl.domino.jna.data.JNADatabase Maven / Gradle / Ivy

There is a newer version: 1.44.0
Show 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.data;

import java.lang.ref.ReferenceQueue;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
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.Formatter;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.NavigableMap;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.TreeMap;
import java.util.function.BiConsumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import com.hcl.domino.BuildVersionInfo;
import com.hcl.domino.DominoClient.Encryption;
import com.hcl.domino.DominoClient.IBreakHandler;
import com.hcl.domino.DominoClient.NotesReplicationStats;
import com.hcl.domino.DominoClient.OpenDatabase;
import com.hcl.domino.DominoClient.ReplicationStateListener;
import com.hcl.domino.DominoException;
import com.hcl.domino.UserNamesList;
import com.hcl.domino.admin.replication.ReplicaInfo;
import com.hcl.domino.commons.constants.UpdateNote;
import com.hcl.domino.commons.data.AccessInfoImpl;
import com.hcl.domino.commons.data.BuildVersionInfoImpl;
import com.hcl.domino.commons.data.EncryptionInfoImpl;
import com.hcl.domino.commons.data.NSFVersionInfoImpl;
import com.hcl.domino.commons.design.DesignUtil;
import com.hcl.domino.commons.errors.INotesErrorConstants;
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.IGCDominoClient;
import com.hcl.domino.commons.util.NotesErrorUtils;
import com.hcl.domino.commons.util.PlatformUtils;
import com.hcl.domino.commons.util.StringTokenizerExt;
import com.hcl.domino.commons.util.StringUtil;
import com.hcl.domino.commons.views.IItemTableData;
import com.hcl.domino.commons.views.OpenCollection;
import com.hcl.domino.crypt.DatabaseEncryptionState;
import com.hcl.domino.data.Agent;
import com.hcl.domino.data.AutoCloseableDocument;
import com.hcl.domino.data.DBQuery;
import com.hcl.domino.data.DQLQueryResult;
import com.hcl.domino.data.Database;
import com.hcl.domino.data.DatabaseOption;
import com.hcl.domino.data.Document;
import com.hcl.domino.data.DocumentClass;
import com.hcl.domino.data.DocumentSelection;
import com.hcl.domino.data.DocumentSummaryQueryResult;
import com.hcl.domino.data.DominoCollection;
import com.hcl.domino.data.DominoCollectionInfo;
import com.hcl.domino.data.DominoDateTime;
import com.hcl.domino.data.DominoOriginatorId;
import com.hcl.domino.data.FTIndex;
import com.hcl.domino.data.FTIndexStats;
import com.hcl.domino.data.FTQuery;
import com.hcl.domino.data.FTQueryResult;
import com.hcl.domino.data.FormulaQueryResult;
import com.hcl.domino.data.IAdaptable;
import com.hcl.domino.data.IDTable;
import com.hcl.domino.data.ItemDataType;
import com.hcl.domino.data.NoteIdWithScore;
import com.hcl.domino.dbdirectory.DirectorySearchQuery.SearchFlag;
import com.hcl.domino.design.RichTextBuilder;
import com.hcl.domino.dql.DQL.DQLTerm;
import com.hcl.domino.dql.QueryResultsProcessor;
import com.hcl.domino.exception.DocumentDeletedException;
import com.hcl.domino.exception.IncompatibleImplementationException;
import com.hcl.domino.exception.ObjectDisposedException;
import com.hcl.domino.jna.BaseJNAAPIObject;
import com.hcl.domino.jna.JNADominoClient;
import com.hcl.domino.jna.design.JNADbDesign;
import com.hcl.domino.jna.internal.DisposableMemory;
import com.hcl.domino.jna.internal.FTSearchResultsDecoder;
import com.hcl.domino.jna.internal.JNANotesConstants;
import com.hcl.domino.jna.internal.Mem;
import com.hcl.domino.jna.internal.Mem.LockedMemory;
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.callbacks.NotesCallbacks;
import com.hcl.domino.jna.internal.callbacks.Win32NotesCallbacks;
import com.hcl.domino.jna.internal.capi.INotesCAPI;
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.JNAIDTableAllocations;
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.search.NotesSearch;
import com.hcl.domino.jna.internal.search.NotesSearch.JNASearchMatch;
import com.hcl.domino.jna.internal.structs.NamedObjectEntryStruct;
import com.hcl.domino.jna.internal.structs.NotesBuildVersionStruct;
import com.hcl.domino.jna.internal.structs.NotesFTIndexStatsStruct;
import com.hcl.domino.jna.internal.structs.NotesItemDefinitionTableExt;
import com.hcl.domino.jna.internal.structs.NotesItemDefinitionTableLock;
import com.hcl.domino.jna.internal.structs.NotesOriginatorIdStruct;
import com.hcl.domino.jna.internal.structs.NotesTimeDateStruct;
import com.hcl.domino.jna.internal.structs.NotesUniversalNoteIdStruct;
import com.hcl.domino.jna.internal.views.JNADominoCollectionInfo;
import com.hcl.domino.jna.richtext.JNARichTextBuilder;
import com.hcl.domino.jna.utils.JNADominoUtils;
import com.hcl.domino.misc.DominoEnumUtil;
import com.hcl.domino.misc.Loop;
import com.hcl.domino.misc.NotesConstants;
import com.hcl.domino.misc.Ref;
import com.hcl.domino.security.Acl;
import com.hcl.domino.security.AclFlag;
import com.hcl.domino.security.AclLevel;
import com.sun.jna.Memory;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.ptr.IntByReference;
import com.sun.jna.ptr.ShortByReference;

public class JNADatabase extends BaseJNAAPIObject implements Database {
	static final String NAMEDNOTES_APPLICATION_PREFIX = "$app_"; //$NON-NLS-1$

	private static class LoopImpl extends Loop {
		
		public void next() {
			super.setIndex(getIndex()+1);
		}
		
		@Override
		public void setIsLast() {
			super.setIsLast();
		}
	}

	private String m_server;
	private String m_filePath;
	private String m_replicaID;
	@SuppressWarnings("unused")
	private Set m_options;
	private boolean m_openAsIdUser;
	private List m_namesStringList;
	private EnumSet m_namesListPrivileges;
	private String[] m_paths;
	private DbMode m_dbMode;
	private JNAAcl m_acl;
	private Boolean m_hasLargeItemSupport;
	
	public JNADatabase(IGCDominoClient parent, String server, String filePath, Set options) {
		this(parent, server, filePath, options, (JNAUserNamesList) null);
	}

	public JNADatabase(IGCDominoClient parent, String server, String filePath, Set options,
			JNAUserNamesList namesListOverride) {
		super(parent);
		
		init(server, filePath, options, namesListOverride);
		
		setInitialized();
	}

	public JNADatabase(IGCDominoClient parent, IAdaptable adaptable) {
		super(parent);
		
		HANDLE hdl = adaptable.getAdapter(HANDLE.class);
		if (hdl==null) {
			throw new IllegalArgumentException("Missing DB handle");
		}
		
		JNADatabaseAllocations allocations = getAllocations();
		allocations.setDBHandle(hdl);
		
		JNAUserNamesList namesList = adaptable.getAdapter(JNAUserNamesList.class);
		if (namesList==null) {
			//try to retrieve the NAMES_LIST from the DB handle
			DHANDLE.ByReference rethNamesList = DHANDLE.newInstanceByReference();
			
			short result = LockUtil.lockHandle(hdl, (hDbByVal) -> {
				return NotesCAPI.get().NSFDbGetNamesList(hDbByVal, 0, rethNamesList);
			});
			NotesErrorUtils.checkResult(result);
			
			if (!rethNamesList.isNull()) {
				namesList = new JNAUserNamesList(this, rethNamesList);
			}
		}
		
		allocations.setNamesList(namesList);
		
		JNADominoClient parentClient = getParentDominoClient();
		List builderNames = parentClient.getBuilderNamesList();

		if (namesList==null) {
			m_openAsIdUser = true;
		}
		else if (builderNames.isEmpty()) {
			m_openAsIdUser = NotesNamingUtils.equalNames(namesList.getPrimaryName(), parentClient.getIDUserName());
		}
		else {
			m_openAsIdUser = false;
		}
		
		m_namesStringList = namesList==null ? null : namesList.toList();
		m_namesListPrivileges = namesList==null ? null : NotesNamingUtils.getPrivileges(namesList);

		setInitialized();
	}
	
	@Override
	public JNADominoClient getParentDominoClient() {
		return (JNADominoClient)super.getParentDominoClient();
	}
	
	@SuppressWarnings("rawtypes")
	@Override
	protected JNADatabaseAllocations createAllocations(IGCDominoClient parentDominoClient,
			APIObjectAllocations parentAllocations, ReferenceQueue queue) {
		
		return new JNADatabaseAllocations(parentDominoClient, parentAllocations, this, queue);
	}
	
	private void init(String server, String filePath, Set options,
			JNAUserNamesList namesListOverride) {
		
		if (namesListOverride!=null) {
			if (namesListOverride.isDisposed()) {
				throw new ObjectDisposedException(this);
			}
		}
		
		JNADominoClient parentClient = getParentDominoClient();
		boolean isOnServer = parentClient.isOnServer();
		
		// TODO figure out if there's a safe way to do this. Though it should be beneficial
		//   in that it queues database access properly, it fails very commonly with "server
		//   is not responding" and similar connection problems
//		if (StringUtil.isEmpty(server) && isOnServer) {
//			server = getParentDominoClient().getIDUserName();
//		}
		if (server==null) {
			server = ""; //$NON-NLS-1$
		}
		
		m_server = server;
		m_filePath = filePath;
		m_options = options;

		List builderNames = parentClient.getBuilderNamesList();
		
		
//		if (!"".equals(m_server)) { //$NON-NLS-1$
//			if (isOnServer) {
//				String serverCN = NotesNamingUtils.toCommonName(server);
//				String currServerCanonical = parentClient.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
//					m_server = ""; //$NON-NLS-1$
//				}
//			}
//		}
		
		try(DisposableMemory retFullNetPath = JNADominoUtils.constructNetPath(getParentDominoClient(), server, filePath)) {
			short openOptions = DominoEnumUtil.toBitField(OpenDatabase.class, options);

			JNAUserNamesList namesList = namesListOverride!=null ? namesListOverride : (JNAUserNamesList) getParentDominoClient().getEffectiveUserNamesList(server);
			
			if (namesListOverride!=null) {
				m_openAsIdUser = NotesNamingUtils.equalNames(namesListOverride.getPrimaryName(), parentClient.getIDUserName());
			}
			else {
				if (builderNames.isEmpty()) {
					m_openAsIdUser = NotesNamingUtils.equalNames(namesList.getPrimaryName(), parentClient.getIDUserName());
				}
				else {
					m_openAsIdUser = false;
				}
			}
			
			m_namesStringList = namesList==null ? null : namesList.toList();
			m_namesListPrivileges = namesList==null ? null : NotesNamingUtils.getPrivileges(namesList);

			JNADatabaseAllocations allocations = getAllocations();
			allocations.setNamesList(namesList);

			DHANDLE namesListHandle = namesList==null ? null : namesList.getAdapter(DHANDLE.class);
			if (namesList!=null && (namesListHandle==null || namesListHandle.isNull())) {
				throw new DominoException(0, "Could not read expected names list handle");
			}
			
			LockUtil.lockHandle(namesListHandle, (namesListHandleByVal) -> {
				if (namesList != null && namesList.isDisposed()) {
					throw new ObjectDisposedException(namesList);
				}
				
				HANDLE.ByReference retHDB = HANDLE.newInstanceByReference();
				NotesTimeDateStruct retDataModified = NotesTimeDateStruct.newInstance();
				NotesTimeDateStruct retNonDataModified = NotesTimeDateStruct.newInstance();
				
				// TODO support ModifiedTime
				short localResult;
				if (m_openAsIdUser && (m_namesListPrivileges==null || !m_namesListPrivileges.contains(Privileges.FullAdminAccess))) {
					localResult = NotesCAPI.get().NSFDbOpenExtended(retFullNetPath, openOptions, null,
							null, retHDB, retDataModified, retNonDataModified);
				}
				else {
					localResult = NotesCAPI.get().NSFDbOpenExtended(retFullNetPath, openOptions, namesListHandleByVal,
							null, retHDB, retDataModified, retNonDataModified);
				}

				if ((localResult & NotesConstants.ERR_MASK)==582 &&
						StringUtil.isEmpty(m_server) && !parentClient.isOnServer() && m_openAsIdUser) {
					localResult = NotesCAPI.get().NSFDbOpenExtended(retFullNetPath, openOptions, (DHANDLE.ByValue) null,
							null, retHDB, retDataModified, retNonDataModified);
				}
				
				if ((localResult & NotesConstants.ERR_MASK) == 259) { // File does not exist
					
					//try to find this database in the folder configured via SharedDataDirectory
					//in the Notes.ini
					boolean hasShared = StringUtil.isNotEmpty(getParentDominoClient().getDominoRuntime().getPropertyString("SharedDataDirectory")); //$NON-NLS-1$
					
					if(hasShared) {
						if (m_openAsIdUser && (m_namesListPrivileges==null || !m_namesListPrivileges.contains(Privileges.FullAdminAccess))) {
							localResult = NotesCAPI.get().NSFDbOpenTemplateExtended(retFullNetPath, openOptions,
									null,
									null, retHDB, retDataModified, retNonDataModified);
						}
						else {
							localResult = NotesCAPI.get().NSFDbOpenTemplateExtended(retFullNetPath, openOptions,
									namesListHandleByVal,
									null, retHDB, retDataModified, retNonDataModified);
						}
					}
				}
				
				if ((localResult & NotesConstants.ERR_MASK) == INotesErrorConstants.ERR_NOEXIST) {
					throw NotesErrorUtils.toNotesError(INotesErrorConstants.ERR_NOEXIST, MessageFormat.format("No database found on server ''{0}'' with filepath {1}", m_server, m_filePath)).get();
				}
				
				NotesErrorUtils.checkResult(localResult);
				
				getAllocations().setDBHandle(retHDB);
				
				loadPaths();
				return 0;
			});
			
		}
	}
	
	@Override
	@SuppressWarnings("unchecked")
	protected  T getAdapterLocal(Class clazz) {
		if (JNAUserNamesList.class.equals(clazz) || UserNamesList.class.equals(clazz)) {
			return (T) getAllocations().getNamesList();
		}
		else if(HANDLE.class.isAssignableFrom(clazz)) {
			return (T)getAllocations().getDBHandle();
		}
		
		return null;
	}
	
	@Override
	public String getServer() {
		loadPaths();
		return m_server;
	}

	/**
	 * Loads the path information from Notes
	 */
	private void loadPaths() {
		if (m_paths==null) {
			checkDisposed();
			JNADatabaseAllocations allocations = getAllocations();

			try(DisposableMemory retCanonicalPathName = new DisposableMemory(NotesConstants.MAXPATH);
      DisposableMemory retExpandedPathName = new DisposableMemory(NotesConstants.MAXPATH)) {
				short result = LockUtil.lockHandle(allocations.getDBHandle(), (handleByVal) -> {
					return NotesCAPI.get().NSFDbPathGet(handleByVal, retCanonicalPathName, retExpandedPathName);
				});
				NotesErrorUtils.checkResult(result);
				
				String canonicalPathName = NotesStringUtils.fromLMBCS(retCanonicalPathName, NotesStringUtils.getNullTerminatedLength(retCanonicalPathName));
				String expandedPathName = NotesStringUtils.fromLMBCS(retExpandedPathName, NotesStringUtils.getNullTerminatedLength(retExpandedPathName));
				String relDbPath;
				String absDbPath;

				int iPos = canonicalPathName.indexOf("!!"); //$NON-NLS-1$
				if (iPos==-1) {
					//local db
					m_server = ""; //$NON-NLS-1$
					relDbPath = canonicalPathName;
				}
				else {
					m_server = canonicalPathName.substring(0, iPos);
					relDbPath = canonicalPathName.substring(iPos+2);
				}
				iPos = expandedPathName.indexOf("!!"); //$NON-NLS-1$
				if (iPos==-1) {
					absDbPath = expandedPathName;
				}
				else {
					absDbPath = expandedPathName.substring(iPos+2);
				}
				m_paths = new String[] {relDbPath, absDbPath};
			}
		}
	}
	
	@Override
	public String getRelativeFilePath() {
		loadPaths();
		return m_paths[0];
	}

	@Override
	public String getAbsoluteFilePath() {
		loadPaths();
		return m_paths[1];
	}

	@Override
	public String getReplicaID() {
		if (m_replicaID==null) {
			ReplicaInfo replInfo = getParentDominoClient().getReplication().getReplicaInfo(this);
			m_replicaID = replInfo.getReplicaID();
		}
		return m_replicaID;
	}

	public void _resetCachedReplicaId() {
		m_replicaID = null;
	}
	
	/**
	 * This function gets the database information buffer of a Domino database.
*
* The information buffer is a NULL terminated string and consists of one or more of the * following pieces of information:
*
    *
  • database title
  • *
  • categories
  • *
  • class
  • *
  • and design class
  • *
*
* Use NSFDbInfoParse to retrieve any one piece of information from the buffer.
*
* Database information appears in the Notes UI, in the File, Database, Properties InfoBox.
* Clicking the Basics tab displays the Title field with the database title.
*
* Selecting the Design tab opens the Design tabbed page. The database class is displayed in the * Database is a template/Template Name field and the database design class is displayed in the * Inherit design from template/Template Name field. The Categories field displays the database * categories.
*
* Database categories are different than view categories.
* Database categories are keywords specified for the database.
* Each server's database catalog (CATALOG.NSF) contains a view, called Databases by Category, * which lists only the categorized databases.
*
* The database title also appears on the Notes Desktop below each database icon. * * @return buffer */ private DisposableMemory getDbInfoBuffer() { checkDisposed(); JNADatabaseAllocations allocations = getAllocations(); DisposableMemory infoBuf = new DisposableMemory(NotesConstants.NSF_INFO_SIZE); short result = LockUtil.lockHandle(allocations.getDBHandle(), (handleByVal) -> { return NotesCAPI.get().NSFDbInfoGet(handleByVal, infoBuf); }); NotesErrorUtils.checkResult(result); return infoBuf; } @Override public String getTitle() { checkDisposed(); try(DisposableMemory infoBuf = getDbInfoBuffer(); DisposableMemory titleMem = new DisposableMemory(NotesConstants.NSF_INFO_SIZE - 1)) { NotesCAPI.get().NSFDbInfoParse(infoBuf, NotesConstants.INFOPARSE_TITLE, titleMem, (short) (titleMem.size() & 0xffff)); return NotesStringUtils.fromLMBCS(titleMem, -1); } } /** * Returns the icon document * * @return an {@link Optional} describing the icon document or an empty one if not found */ public Optional openIconDocument() { checkDisposed(); JNADatabaseAllocations allocations = getAllocations(); IntByReference retNoteID = new IntByReference(); short result = LockUtil.lockHandle(allocations.getDBHandle(), (handleByVal) -> { return NotesCAPI.get().NSFDbGetSpecialNoteID(handleByVal, (short) ((NotesConstants.SPECIAL_ID_NOTE | NotesConstants.NOTE_CLASS_ICON) & 0xffff), retNoteID); }); if ((result & NotesConstants.ERR_MASK)==1028) { //not found return Optional.empty(); } NotesErrorUtils.checkResult(result); int noteId = retNoteID.getValue(); if (noteId==0) { return Optional.empty(); } return getDocumentById(noteId); } /** * Writes the modified db info buffer * * @param infoBuf info buffer */ private void writeDbInfoBuffer(Memory infoBuf) { checkDisposed(); JNADatabaseAllocations allocations = getAllocations(); short result = LockUtil.lockHandle(allocations.getDBHandle(), (handleByVal) -> { return NotesCAPI.get().NSFDbInfoSet(handleByVal, infoBuf); }); NotesErrorUtils.checkResult(result); //as documented in NSFDbInfoSet, we need to update the icon document as well try { JNADocument iconDoc = (JNADocument) openIconDocument().get(); if (iconDoc.hasItem("$TITLE")) { //$NON-NLS-1$ DHANDLE iconNoteHandle = iconDoc.getAdapter(DHANDLE.class); result = LockUtil.lockHandle(iconNoteHandle, (iconNoteHandleByVal) -> { return NotesCAPI.get().NSFItemSetText(iconNoteHandleByVal, NotesStringUtils.toLMBCS("$TITLE", true), infoBuf, NotesConstants.MAXWORD); //$NON-NLS-1$ }); NotesErrorUtils.checkResult(result); iconDoc.save(); } iconDoc.dispose(); } catch (DominoException e) { if (e.getId() != 578) { throw e; } } } @Override public void setTitle(String title) { checkDisposed(); try(DisposableMemory infoBuf = getDbInfoBuffer()) { Memory newTitleMem = NotesStringUtils.toLMBCS(title, true); NotesCAPI.get().NSFDbInfoModify(infoBuf, NotesConstants.INFOPARSE_TITLE, newTitleMem); writeDbInfoBuffer(infoBuf); } } @Override public Acl getACL() { checkDisposed(); synchronized(this) { if (m_acl==null || m_acl.isDisposed()) { JNADatabaseAllocations allocations = getAllocations(); DHANDLE.ByReference rethACL=DHANDLE.newInstanceByReference(); short result = LockUtil.lockHandle(allocations.getDBHandle(), (handleByVal) -> { return NotesCAPI.get().NSFDbReadACL(handleByVal, rethACL); }); NotesErrorUtils.checkResult(result); m_acl=new JNAAcl(this, rethACL); } return m_acl; } } @Override public Optional openDefaultCollection() { checkDisposed(); short result; IntByReference retNoteID = new IntByReference(); result = LockUtil.lockHandle(getAllocations().getDBHandle(), (hDbByVal) -> { return NotesCAPI.get().NSFDbGetSpecialNoteID(hDbByVal, (short) ((NotesConstants.SPECIAL_ID_NOTE | NotesConstants.NOTE_CLASS_VIEW) & 0xffff), retNoteID); }); if ((result & NotesConstants.ERR_MASK)==1028) { //not found return Optional.empty(); } NotesErrorUtils.checkResult(result); int noteId = retNoteID.getValue(); if (noteId==0) { return Optional.empty(); } DominoCollection col = openCollection(noteId, (EnumSet ) null); return Optional.of(col); } @Override public Optional openCollection(String collectionName) { return openCollection(collectionName, (EnumSet) null); } @Override public Optional openCollectionByUNID(String unid) { return openCollectionByUNID(unid, (EnumSet) null); } public Optional openCollectionByUNID(String unid, EnumSet openFlagSet) { int viewNoteId = toNoteId(unid); if (viewNoteId==0) { return Optional.empty(); } return Optional.ofNullable(openCollection(viewNoteId, openFlagSet)); } private Optional openCollection(String collectionName, EnumSet openFlagSet) { checkDisposed(); int viewNoteId = findCollectionId(collectionName, CollectionType.Both); if (viewNoteId==0) { return Optional.empty(); } return Optional.ofNullable(openCollection(viewNoteId, openFlagSet)); } @Override public Optional openCollection(int viewNoteId) { checkDisposed(); return Optional.ofNullable(openCollection(viewNoteId, (EnumSet) null)); } public DominoCollection openCollection(int viewNoteId, EnumSet openFlagSet) { return openCollection(viewNoteId, openFlagSet, this); } @Override public Optional openCollection(int viewNoteId, Database dbData) { return Optional.ofNullable(openCollection(viewNoteId, (EnumSet ) null, dbData)); } private DominoCollection openCollection(int viewNoteId, EnumSet openFlagSet, Database dbData) { checkDisposed(); if (viewNoteId==0) { return null; } if (dbData!=null && dbData instanceof JNADatabase==false) { throw new IllegalArgumentException("Database to read data must be of type JNADatabase"); } JNADatabase jnaDbData = dbData==null ? this : (JNADatabase) dbData; jnaDbData.checkDisposed(); JNADatabaseAllocations dbViewAllocations = getAllocations(); JNADatabaseAllocations dbDataAllocations = jnaDbData.getAllocations(); try(DisposableMemory retViewUNID = new DisposableMemory(16)) { JNAIDTable unreadTable = new JNAIDTable(getParentDominoClient()); //always enforce reopening; funny things can happen on a Domino server //without this flag like sharing collections between users resulting in //users seeing the wrong data *sometimes*... EnumSet openFlagSetClone = openFlagSet==null ? EnumSet.noneOf(OpenCollection.class) : openFlagSet.clone(); openFlagSetClone.add(OpenCollection.OPEN_REOPEN_COLLECTION); short openFlags = DominoEnumUtil.toBitField(OpenCollection.class, openFlagSetClone); JNAUserNamesList namesList = dbViewAllocations.getNamesList(); if (namesList.isDisposed()) { throw new ObjectDisposedException(namesList); } DHANDLE.ByReference rethCollection = DHANDLE.newInstanceByReference(); rethCollection.clear(); DHANDLE.ByReference rethCollapsedList = DHANDLE.newInstanceByReference(); rethCollapsedList.clear(); DHANDLE.ByReference rethSelectedList = DHANDLE.newInstanceByReference(); rethSelectedList.clear(); JNAUserNamesListAllocations namesListAllocations = (JNAUserNamesListAllocations) namesList.getAdapter(APIObjectAllocations.class); JNAIDTableAllocations unreadTableAllocations = (JNAIDTableAllocations) unreadTable.getAdapter(APIObjectAllocations.class); short result = LockUtil.lockHandles( namesListAllocations.getHandle(), unreadTableAllocations.getIdTableHandle(), dbViewAllocations.getDBHandle(), dbDataAllocations.getDBHandle(), (namesListHandleByVal, unreadTableHandleByVal, dbViewHandleByVal, dbDataHandleByVal) -> { short localResult = NotesCAPI.get().NIFOpenCollectionWithUserNameList(dbViewHandleByVal, dbDataHandleByVal, viewNoteId, openFlags, unreadTableHandleByVal, rethCollection, null, retViewUNID, rethCollapsedList, rethSelectedList, namesListHandleByVal); return localResult; }); NotesErrorUtils.checkResult(result); String sViewUNID = toUNID(retViewUNID); JNAIDTable collapsedList = new JNAIDTable(getParentDominoClient(), rethCollapsedList, true); JNAIDTable selectedList = new JNAIDTable(getParentDominoClient(), rethSelectedList, true); return new JNADominoCollection(this, jnaDbData, rethCollection, viewNoteId, sViewUNID, collapsedList, selectedList, unreadTable); } } /** * Converts bytes in memory to a UNID * * @param buf memory * @return unid */ private static String toUNID(Memory buf) { Formatter formatter = new Formatter(); ByteBuffer data = buf.getByteBuffer(0, buf.size()).order(ByteOrder.LITTLE_ENDIAN); formatter.format("%016x", data.getLong()); //$NON-NLS-1$ formatter.format("%016x", data.getLong()); //$NON-NLS-1$ String unid = formatter.toString().toUpperCase(); formatter.close(); return unid; } @Override public Optional getDocumentById(int noteId) { return getDocumentById(noteId, EnumSet.noneOf(OpenDocumentMode.class)); } public int _toDocumentOpenOptions(Set flags) { int options = 0; if (flags!=null) { if (flags.contains(OpenDocumentMode.SUMMARY_ONLY)) { options = options | NotesConstants.OPEN_SUMMARY; } if (flags.contains(OpenDocumentMode.MARK_READ)) { options = options | NotesConstants.OPEN_MARK_READ; } if (flags.contains(OpenDocumentMode.ABSTRACT_ONLY)) { options = options | NotesConstants.OPEN_ABSTRACT; } if (flags.contains(OpenDocumentMode.LOAD_RESPONSES)) { options = options | NotesConstants.OPEN_RESPONSE_ID_TABLE; } if (flags.contains(OpenDocumentMode.CACHE)) { options = options | NotesConstants.OPEN_CACHE; } if (flags.contains(OpenDocumentMode.NOOBJECTS)) { options = options | NotesConstants.OPEN_NOOBJECTS; } } // we negated the following two OPEN_XXX constants, so we keep // the items in their native format if conversion is not explicitly requested if (flags==null || !flags.contains(OpenDocumentMode.CONVERT_RFC822_TO_TEXT_AND_TIME)) { options = options | NotesConstants.OPEN_RAW_RFC822_TEXT; } if (flags==null || !flags.contains(OpenDocumentMode.CONVERT_MIME_TO_RICHTEXT)) { options = options | NotesConstants.OPEN_RAW_MIME_PART; } return options; } @Override public Optional getDocumentById(int noteId, Set flags) { checkDisposed(); JNADatabaseAllocations allocations = getAllocations(); int openOptions = _toDocumentOpenOptions(flags); DHANDLE.ByReference rethNote = DHANDLE.newInstanceByReference(); short result = LockUtil.lockHandle(allocations.getDBHandle(), (dbHandleByVal) -> { return NotesCAPI.get().NSFNoteOpenExt(dbHandleByVal, noteId, openOptions, rethNote); }); if ((result & NotesConstants.ERR_MASK)==INotesErrorConstants.ERR_NOT_FOUND) { return Optional.empty(); } NotesErrorUtils.checkResult(result); return Optional.of(new JNADocument(this, rethNote)); } @Override public Optional getSoftDeletedDocumentById(int noteId) { checkDisposed(); JNADatabaseAllocations allocations = getAllocations(); DHANDLE.ByReference rethNote = DHANDLE.newInstanceByReference(); short result = LockUtil.lockHandle(allocations.getDBHandle(), (dbHandleByVal) -> { return NotesCAPI.get().NSFNoteOpenSoftDelete(dbHandleByVal, noteId, 0, rethNote); }); if ((result & NotesConstants.ERR_MASK)==1028) { return Optional.empty(); } NotesErrorUtils.checkResult(result); return Optional.of(new JNADocument(this, rethNote)); } @Override public Optional getSoftDeletedDocumentByUNID(String unid) { int noteId = toNoteId(unid) & ~0x80000000; // remove RRV_DELETED bit if (noteId==0) { return Optional.empty(); } return getSoftDeletedDocumentById(noteId); } @Override public Optional getDocumentByUNID(String unid) { return getDocumentByUNID(unid, EnumSet.noneOf(OpenDocumentMode.class)); } @Override public Optional getDocumentByUNID(String unid, Set flags) { checkDisposed(); JNADatabaseAllocations allocations = getAllocations(); int openOptions = _toDocumentOpenOptions(flags); NotesUniversalNoteIdStruct unidObj = NotesUniversalNoteIdStruct.fromString(unid); DHANDLE.ByReference rethNote = DHANDLE.newInstanceByReference(); short result = LockUtil.lockHandle(allocations.getDBHandle(), (dbHandleByVal) -> { return NotesCAPI.get().NSFNoteOpenByUNIDExtended(dbHandleByVal, unidObj, openOptions, rethNote); }); if ((result & NotesConstants.ERR_MASK)==1028) { return Optional.empty(); } NotesErrorUtils.checkResult(result); return Optional.of(new JNADocument(this, rethNote)); } @Override public void deleteDocument(int noteId) { if (noteId==0) { return; } deleteDocument(noteId, EnumSet.noneOf(UpdateNote.class)); } void deleteDocument(int noteId, Set flags) { checkDisposed(); JNADatabaseAllocations allocations = getAllocations(); int flagsAsInt = DominoEnumUtil.toBitField(UpdateNote.class, flags); short result = LockUtil.lockHandle(allocations.getDBHandle(), (dbHandleByVal) -> { return NotesCAPI.get().NSFNoteDeleteExtended(dbHandleByVal, noteId, flagsAsInt); }); NotesErrorUtils.checkResult(result); } @Override public void deleteDocument(String unid) { deleteDocument(unid, EnumSet.noneOf(UpdateNote.class)); } void deleteDocument(String unid, Set flags) { int noteId = toNoteId(unid); if (noteId!=0) { deleteDocument(noteId, flags); } } @Override public void deleteDocuments(Collection noteIds) { checkDisposed(); JNADatabaseAllocations allocations = getAllocations(); JNAIDTable idTable; boolean disposeIDTable; if (noteIds instanceof JNAIDTable) { idTable = (JNAIDTable) noteIds; disposeIDTable = false; } else { idTable = new JNAIDTable(getParentDominoClient(), noteIds); disposeIDTable = true; } try { DHANDLE idTableHandle = idTable.getAdapter(DHANDLE.class); short result = LockUtil.lockHandles( allocations.getDBHandle(), idTableHandle, (dbHandleByVal, idTableHandleByVal) -> { return NotesCAPI.get().NSFDbDeleteNotes(dbHandleByVal, idTableHandleByVal, null); }); NotesErrorUtils.checkResult(result); } finally { if (disposeIDTable) { idTable.dispose(); } } } @Override public void deleteDocumentsByUNID(Collection unids) { Map resolvedNoteIDsByUNID = new HashMap<>(); Set unresolvedUNIDs = new HashSet<>(); toNoteIds(unids, resolvedNoteIDsByUNID, unresolvedUNIDs); deleteDocuments(resolvedNoteIDsByUNID.values()); } @Override public Document createDocument() { return createDocument(Collections.emptySet()); } @Override public Document createDocument(Set flags) { checkDisposed(); JNADatabaseAllocations allocations = getAllocations(); DHANDLE.ByReference retNoteHandle = DHANDLE.newInstanceByReference(); short result = LockUtil.lockHandle(allocations.getDBHandle(), (dbHandleByVal) -> { return NotesCAPI.get().NSFNoteCreate(dbHandleByVal, retNoteHandle); }); NotesErrorUtils.checkResult(result); JNADocument doc = new JNADocument(this, retNoteHandle); if (flags!=null && flags.contains(CreateFlags.HIDE_FROM_VIEWS)) { doc.setHiddenFromViews(true); } return doc; } @Override public Optional getProfileDocument(String profileName) { return getProfileDocument(profileName, (String) null); } @Override public Optional getProfileDocument(String profileName, String userName) { checkDisposed(); JNADatabaseAllocations allocations = getAllocations(); Memory profileNameMem = NotesStringUtils.toLMBCS(profileName, false); Memory userNameMem = StringUtil.isEmpty(userName) ? null : NotesStringUtils.toLMBCS(userName, false); DHANDLE.ByReference rethProfileNote = DHANDLE.newInstanceByReference(); short result = LockUtil.lockHandle(allocations.getDBHandle(), (dbHandleByVal) -> { return NotesCAPI.get().NSFProfileOpen(dbHandleByVal, profileNameMem, (short) (profileNameMem.size() & 0xffff), userNameMem, (short) (userNameMem==null ? 0 : (userNameMem.size() & 0xffff)), (short) 1, rethProfileNote); }); if ((result & NotesConstants.ERR_MASK)==1028) { return Optional.empty(); } NotesErrorUtils.checkResult(result); return Optional.of(new JNADocument(this, rethProfileNote)); } private static class ProfileNoteInfo { private String m_profileName; private String m_username; private int m_noteId; private ProfileNoteInfo(String profileName, String username, int noteId) { m_profileName = profileName; m_username = username; m_noteId = noteId; } public int getNoteId() { return m_noteId; } @SuppressWarnings("unused") public String getProfileName() { return m_profileName; } public String getUserName() { return m_username; } @Override public String toString() { return MessageFormat.format( "ProfileNoteInfo [profileName={0}, username={1}, noteId={2}]", //$NON-NLS-1$ m_profileName, m_username, m_noteId ); } } /** * Returns infos about the profile notes with the specified name in the database * * @param profileName Name of the profile. To enumerate all profile documents within a database, use null * @return list of profile note infos */ private List getProfileNoteInfos(String profileName) { checkDisposed(); List retNoteInfos = new ArrayList<>(); Memory profileNameMem = StringUtil.isEmpty(profileName) ? null : NotesStringUtils.toLMBCS(profileName, false); NotesCallbacks.NSFPROFILEENUMPROC callback; if (PlatformUtils.isWin32()) { callback = (Win32NotesCallbacks.NSFPROFILEENUMPROCWin32) (hDB, ctx, profileNameMem1, profileNameLength, usernameMem, usernameLength, noteId) -> { String profileName1 = ""; //$NON-NLS-1$ if (profileName1 != null) { profileName1 = NotesStringUtils.fromLMBCS(profileNameMem1, ((profileNameLength & 0xffff))); } String userName = ""; //$NON-NLS-1$ if (usernameMem != null) { userName = NotesStringUtils.fromLMBCS(usernameMem, ((profileNameLength & 0xffff))); } retNoteInfos.add(new ProfileNoteInfo(profileName1, userName, noteId)); return 0; }; } else { callback = (hDB, ctx, profileNameMem1, profileNameLength, usernameMem, usernameLength, noteId) -> { String profileName1 = ""; //$NON-NLS-1$ if (profileName1 != null) { profileName1 = NotesStringUtils.fromLMBCS(profileNameMem1, ((profileNameLength & 0xffff))); } String userName = ""; //$NON-NLS-1$ if (usernameMem != null) { userName = NotesStringUtils.fromLMBCS(usernameMem, ((usernameLength & 0xffff))); } retNoteInfos.add(new ProfileNoteInfo(profileName1, userName, noteId)); return 0; }; } short result = LockUtil.lockHandle(getAllocations().getDBHandle(), (dbHandleByVal) -> { return NotesCAPI.get().NSFProfileEnum(dbHandleByVal, profileNameMem, profileNameMem == null ? (short) 0 : (short) (profileNameMem.size() & 0xffff), callback, null, 0); }); NotesErrorUtils.checkResult(result); return retNoteInfos; } @Override public void forEachProfileDocument(BiConsumer consumer) { forEachProfileDocument(null, null, consumer); } @Override public void forEachProfileDocument(String profileName, BiConsumer consumer) { forEachProfileDocument(profileName, null, consumer); } @Override public void forEachProfileDocument(String profileName, String userName, BiConsumer consumer) { Stream docs = getProfileNoteInfos(profileName) .stream() .filter((profileInfo) -> { boolean result = StringUtil.isEmpty(userName) || NotesNamingUtils.equalNames(profileInfo.getUserName(), userName); return result; }) .map((profileInfo) -> { checkDisposed(); JNADatabaseAllocations allocations = getAllocations(); int openOptions = 0; DHANDLE.ByReference rethNote = DHANDLE.newInstanceByReference(); short result = LockUtil.lockHandle(allocations.getDBHandle(), (dbHandleByVal) -> { return NotesCAPI.get().NSFNoteOpenExt(dbHandleByVal, profileInfo.getNoteId(), openOptions, rethNote); }); if ((result & NotesConstants.ERR_MASK)==1028) { return null; } NotesErrorUtils.checkResult(result); Document doc = new JNADocument(this, rethNote); return doc; }) .filter((profileDoc) -> { boolean result = profileDoc!=null; return result; }); LoopImpl loop = new LoopImpl(); Iterator docsIt = docs.iterator(); while (docsIt.hasNext()) { Document doc = docsIt.next(); boolean isLast = !docsIt.hasNext(); if (isLast) { loop.setIsLast(); } consumer.accept(doc, loop); if (loop.isStopped()) { break; } loop.next(); } } @Override public NotesReplicationStats replicate(String serverName) { return replicate(serverName, Collections.emptySet(), 0, null); } @Override public NotesReplicationStats replicate(String serverName, Set options, int timeLimitMin, ReplicationStateListener progressListener) { String dbPathWithServer; String server = getServer(); if (!StringUtil.isEmpty(server)) { dbPathWithServer = server + "!!" + getRelativeFilePath(); //$NON-NLS-1$ } else { dbPathWithServer = getAbsoluteFilePath(); } return getParentDominoClient().replicateDbsWithServer(serverName, options, Arrays.asList(dbPathWithServer), timeLimitMin, progressListener); } @Override public String toUNID(int noteId) { Map retUnidsByNoteId = new HashMap<>(1); Set retNoteIdsNotFound = new HashSet<>(1); toUNIDs(Arrays.asList(noteId), retUnidsByNoteId, retNoteIdsNotFound); return retUnidsByNoteId.get(noteId); } @Override public int toNoteId(String unid) { Map retNoteIdsByUnid = new HashMap<>(1); Set retNoteUnidsNotFound = new HashSet<>(1); toNoteIds(Arrays.asList(unid), retNoteIdsByUnid, retNoteUnidsNotFound); Integer noteId = retNoteIdsByUnid.get(unid); return noteId!=null ? noteId : 0; } @Override public void toUNIDs(Collection noteIds, Map resolvedUNIDsByNoteId, Set unresolvedNoteIds) { int[] noteIDsArr = new int[noteIds.size()]; int idx=0; for (Integer currNoteId : noteIds) { noteIDsArr[idx++] = currNoteId; } DocInfo[] infoArr = getMultiDocumentInfo(noteIDsArr); for (int i=0; i unids, Map resolvedNoteIDsByUNID, Set unresolvedUNIDs) { String[] unidsArr = new String[unids.size()]; int idx=0; for (String currUnids : unids) { unidsArr[idx++] = currUnids; } DocInfo[] infoArr = getMultiDocumentInfo(unidsArr); for (int i=0; i { return NotesCAPI.get().NSFDbLargeSummaryEnabled(dbHandleByVal) == 1; }); } @Override public Set getOptions() { checkDisposed(); try(DisposableMemory retDbOptions = new DisposableMemory(4 * 4)) { //DWORD[4] short result = LockUtil.lockHandle(getAllocations().getDBHandle(), (hDbByVal) -> { return NotesCAPI.get().NSFDbGetOptionsExt(hDbByVal, retDbOptions); }); NotesErrorUtils.checkResult(result); byte[] dbOptionsArr = retDbOptions.getByteArray(0, 4 * 4); Set dbOptions = EnumSet.noneOf(DatabaseOption.class); for (DatabaseOption currOpt : DatabaseOption.values()) { int optionBit = currOpt.getValue(); int byteOffsetWithBit = optionBit / 8; byte byteValueWithBit = dbOptionsArr[byteOffsetWithBit]; int bitToCheck = (int) Math.pow(2, optionBit % 8); boolean enabled = (byteValueWithBit & bitToCheck) == bitToCheck; if (enabled) { dbOptions.add(currOpt); } } return dbOptions; } } @Override public boolean getOption(DatabaseOption option) { checkDisposed(); Memory retDbOptions = new Memory(4 * 4); //DWORD[4] short result = LockUtil.lockHandle(getAllocations().getDBHandle(), (dbHandleByVal) -> { return NotesCAPI.get().NSFDbGetOptionsExt(dbHandleByVal, retDbOptions); }); NotesErrorUtils.checkResult(result); byte[] dbOptionsArr = retDbOptions.getByteArray(0, 4 * 4); int optionBit = option.getValue(); int byteOffsetWithBit = optionBit / 8; byte byteValueWithBit = dbOptionsArr[byteOffsetWithBit]; int bitToCheck = (int) Math.pow(2, optionBit % 8); boolean enabled = (byteValueWithBit & bitToCheck) == bitToCheck; return enabled; } @Override public void setOption(DatabaseOption option, boolean flag) { checkDisposed(); int optionBit = option.getValue(); int byteOffsetWithBit = optionBit / 8; int bitToCheck = (int) Math.pow(2, optionBit % 8); byte[] optionsWithBitSetArr = new byte[4*4]; optionsWithBitSetArr[byteOffsetWithBit] = (byte) (bitToCheck & 0xff); Memory dbOptionsWithBitSetMem = new Memory(4 * 4); dbOptionsWithBitSetMem.write(0, optionsWithBitSetArr, 0, 4*4); short result = LockUtil.lockHandle(getAllocations().getDBHandle(), (dbHandleByVal) -> { if (flag) { //use dbOptionsMem both for the new value and for the bitmask, since the new value is 1 return NotesCAPI.get().NSFDbSetOptionsExt(dbHandleByVal, dbOptionsWithBitSetMem, dbOptionsWithBitSetMem); } else { Memory nullBytesMem = new Memory(4 * 4); nullBytesMem.clear(); return NotesCAPI.get().NSFDbSetOptionsExt(dbHandleByVal, nullBytesMem, dbOptionsWithBitSetMem); } }); NotesErrorUtils.checkResult(result); } @Override public boolean isFTIndex() { return getLastFTIndexTime() != null; } @Override public Optional getLastFTIndexTime() { checkDisposed(); return LockUtil.lockHandle(getAllocations().getDBHandle(), (dbHandleByVal) -> { NotesTimeDateStruct retTime = NotesTimeDateStruct.newInstance(); short result = NotesCAPI.get().FTGetLastIndexTime(dbHandleByVal, retTime); if (result == INotesErrorConstants.ERR_FT_NOT_INDEXED) { return Optional.empty(); } NotesErrorUtils.checkResult(result); retTime.read(); DominoDateTime retTimeWrap = new JNADominoDateTime(retTime); return Optional.of(retTimeWrap); }); } @Override public void deleteFTIndex() { checkDisposed(); short result = LockUtil.lockHandle(getAllocations().getDBHandle(), (dbHandleByVal) -> { return NotesCAPI.get().FTDeleteIndex(dbHandleByVal); }); NotesErrorUtils.checkResult(result); setOption(DatabaseOption.FT_INDEX, false); } @Override public FTIndexStats ftIndex(Set options) { checkDisposed(); short optionsBitMask = DominoEnumUtil.toBitField(FTIndex.class, options); NotesFTIndexStatsStruct retStats = NotesFTIndexStatsStruct.newInstance(); short result = LockUtil.lockHandle(getAllocations().getDBHandle(), (hDbByVal) -> { return NotesCAPI.get().FTIndex(hDbByVal, optionsBitMask, null, retStats); }); NotesErrorUtils.checkResult(result); retStats.read(); return new JNAFTIndexStats(getServer(), getRelativeFilePath(), retStats.DocsAdded, retStats.DocsUpdated, retStats.DocsDeleted, retStats.BytesIndexed); } private class JNAFTIndexStats implements FTIndexStats { private String m_server; private String m_filePath; private int m_docsAdded; private int m_docsUpdated; private int m_docsDeleted; private int m_bytesIndexed; private JNAFTIndexStats(String server, String filePath, int docsAdded, int docsUpdated, int docsDeleted, int bytesIndexed) { m_server = server; m_filePath = filePath; m_docsAdded = docsAdded; m_docsUpdated = docsUpdated; m_docsDeleted = docsDeleted; m_bytesIndexed = bytesIndexed; } @Override public String getServer() { return m_server; } @Override public String getFilePath() { return m_filePath; } @Override public int getDocsAdded() { return m_docsAdded; } @Override public int getDocsUpdated() { return m_docsUpdated; } @Override public int getDocsDeleted() { return m_docsDeleted; } @Override public int getBytesIndexed() { return m_bytesIndexed; } @Override public String toString() { return MessageFormat.format( "JNAFTIndexStats [server={0}, filePath={1}, docsAdded={2}, docsUpdated={3}, docsDeleted={4}, bytesIndexed={5}]", //$NON-NLS-1$ m_server, m_filePath, m_docsAdded, m_docsUpdated, m_docsDeleted, m_bytesIndexed ); } } @Override public void ftIndexRequest() { checkDisposed(); short result = LockUtil.lockHandle(getAllocations().getDBHandle(), (hDbByVal) -> { return NotesCAPI.get().ClientFTIndexRequest(hDbByVal); }); NotesErrorUtils.checkResult(result); } @Override public boolean isLocallyEncrypted() { checkDisposed(); IntByReference retVal = new IntByReference(); short result = LockUtil.lockHandle(getAllocations().getDBHandle(), (dbHandleByVal) -> { return NotesCAPI.get().NSFDbIsLocallyEncrypted(dbHandleByVal, retVal); }); NotesErrorUtils.checkResult(result); return retVal.getValue() == 1; } @Override public String generateUNID() { return generateOID().getUNID(); } @Override public String getCategories() { checkDisposed(); Memory infoBuf = getDbInfoBuffer(); Memory categoriesMem = new Memory(NotesConstants.NSF_INFO_SIZE - 1); NotesCAPI.get().NSFDbInfoParse(infoBuf, NotesConstants.INFOPARSE_CATEGORIES, categoriesMem, (short) (categoriesMem.size() & 0xffff)); return NotesStringUtils.fromLMBCS(categoriesMem, -1); } @Override public DominoDateTime getCreated() { checkDisposed(); return LockUtil.lockHandle(getAllocations().getDBHandle(), hDb -> { NotesTimeDateStruct dbid = NotesTimeDateStruct.newInstance(); NotesCAPI.get().NSFDbIDGet(hDb, dbid); return new JNADominoDateTime(dbid); }); } @Override public void setCategories(String categories) { checkDisposed(); Memory infoBuf = getDbInfoBuffer(); Memory newCategoriesMem = NotesStringUtils.toLMBCS(categories, true); NotesCAPI.get().NSFDbInfoModify(infoBuf, NotesConstants.INFOPARSE_CATEGORIES, newCategoriesMem); writeDbInfoBuffer(infoBuf); } @Override public DocInfo[] getMultiDocumentInfo(int[] noteIds) { checkDisposed(); int entrySize = 4 /* note id */ + JNANotesConstants.oidSize; //not more than 32767 entries and output buffer cannot exceed 64k final int ENTRIESBYCALL = Math.min(65535, 64000 / entrySize); if (noteIds.length < ENTRIESBYCALL) { return _getMultiDocumentInfo(noteIds); } //work around C API limit of max 65535 entries per call DocInfo[] noteInfos = new DocInfo[noteIds.length]; int startOffset = 0; while (startOffset < noteIds.length) { int endOffsetExclusive = Math.min(noteIds.length, startOffset + ENTRIESBYCALL); int[] currNoteIds = new int[endOffsetExclusive - startOffset]; System.arraycopy(noteIds, startOffset, currNoteIds, 0, endOffsetExclusive - startOffset); DocInfo[] currNoteInfos = _getMultiDocumentInfo(currNoteIds); System.arraycopy(currNoteInfos, 0, noteInfos, startOffset, currNoteInfos.length); startOffset += ENTRIESBYCALL; } return noteInfos; } /** * This method can be used to get information for a number documents in a * database from their note ids in a single call.
* The data returned by this method is the note id, {@link NotesOriginatorIdStruct}, which contains * the UNID of the document, the sequence number and the sequence time ("Modified initially" time).
*
* In addition, the method checks whether a document exists or has been deleted.
*
* Please note that the method can only handle max. 65535 note ids, because it's * using a WORD / short datatype for the count internally to call the C API. * * @param noteIds array of note ids * @return lookup results, same size and order as noteIds array * @throws IllegalArgumentException if note id array has too many entries (more than 65535) */ private DocInfo[] _getMultiDocumentInfo(int[] noteIds) { if (noteIds.length ==0) { return new DocInfo[0]; } if (noteIds.length > 65535) { throw new IllegalArgumentException("Max 65535 note ids are supported"); } DHANDLE.ByReference retHandle = DHANDLE.newInstanceByReference(); short result = Mem.OSMemAlloc((short) 0, noteIds.length * 4, retHandle); NotesErrorUtils.checkResult(result); JNADatabaseAllocations allocations = getAllocations(); return LockUtil.lockHandles(allocations.getDBHandle(), retHandle, (dbHandleByVal, retHandleByVal) -> { boolean inMemHandleLocked = false; try { Pointer inBufPtr = Mem.OSLockObject(retHandleByVal); inMemHandleLocked = true; Pointer currInBufPtr = inBufPtr; int offset = 0; for (int i=0; i { //decode return buffer int entrySize = 4 /* note id */ + JNANotesConstants.oidSize; long retSizeLong = retSize.getValue(); if (retSizeLong != noteIds.length*entrySize) { throw new IllegalStateException( MessageFormat.format( "Unexpected size of return data. Expected {0} bytes for data of {1} ids, got {2} bytes", noteIds.length*entrySize, noteIds.length, retSizeLong ) ); } Pointer outBufPtr = Mem.OSLockObject(rethOutBufByVal); try { DocInfo[] infos = decodeMultiNoteLookupData(noteIds.length, outBufPtr); return infos; } finally { Mem.OSUnlockObject(rethOutBufByVal); short resultLocal1 = Mem.OSMemFree(rethOutBufByVal); NotesErrorUtils.checkResult(resultLocal1); } }); } finally { if (inMemHandleLocked) { Mem.OSUnlockObject(retHandleByVal); } short resultMemFree = Mem.OSMemFree(retHandleByVal); NotesErrorUtils.checkResult(resultMemFree); } }); } @Override public DocInfo[] getMultiDocumentInfo(String[] noteUNIDs) { checkDisposed(); int entrySize = 4 /* note id */ + JNANotesConstants.oidSize; //not more than 32767 entries and output buffer cannot exceed 64k final int ENTRIESBYCALL = Math.min(32767, 64000 / entrySize); if (noteUNIDs.length < ENTRIESBYCALL) { return _getMultiDocumentInfo(noteUNIDs); } //work around C API limit of max 32767 entries per call DocInfo[] noteInfos = new DocInfo[noteUNIDs.length]; int startOffset = 0; while (startOffset < noteUNIDs.length) { int endOffsetExclusive = Math.min(noteUNIDs.length, startOffset + ENTRIESBYCALL); String[] currNoteUNIDs = new String[endOffsetExclusive - startOffset]; System.arraycopy(noteUNIDs, startOffset, currNoteUNIDs, 0, endOffsetExclusive - startOffset); DocInfo[] currNoteInfos = _getMultiDocumentInfo(currNoteUNIDs); System.arraycopy(currNoteInfos, 0, noteInfos, startOffset, currNoteInfos.length); startOffset += ENTRIESBYCALL; } return noteInfos; } /** * This method can be used to get information for a number documents in a * database from their document unids in a single call.
* The data returned by this method is the note id, {@link NotesOriginatorIdStruct}, which contains * the UNID of the document, the sequence number and the sequence time ("Modified initially" time).
*
* In addition, the method checks whether a document exists or has been deleted.
*
* Please note that the method can only handle max. 32767 note ids in one call. * * @param unids array of note unids * @return lookup results, same size and order as unids array * @throws IllegalArgumentException if note unid array has too many entries (more than 32767) */ private DocInfo[] _getMultiDocumentInfo(String[] unids) { if (unids.length ==0) { return new DocInfo[0]; } if (unids.length > 32767) { throw new IllegalArgumentException("Max 32767 note ids are supported"); } DHANDLE.ByReference retHandle = DHANDLE.newInstanceByReference(); short result = Mem.OSMemAlloc((short) 0, unids.length * 16, retHandle); NotesErrorUtils.checkResult(result); JNADatabaseAllocations allocations = getAllocations(); DocInfo[] retNoteInfo = LockUtil.lockHandles(allocations.getDBHandle(), retHandle, (dbHandleByVal, retHandleByVal) -> { boolean inMemHandleLocked = false; try { Pointer inBufPtr = Mem.OSLockObject(retHandleByVal); inMemHandleLocked = true; Pointer currInBufPtr = inBufPtr; int offset = 0; for (int i=0; i { //decode return buffer int entrySize = 4 /* note id */ + JNANotesConstants.oidSize; long retSizeLong = retSize.getValue(); if (retSizeLong != unids.length*entrySize) { throw new IllegalStateException( MessageFormat.format( "Unexpected size of return data. Expected {0} bytes for data of {1} ids, got {2} bytes", unids.length*entrySize, unids.length, retSizeLong ) ); } Pointer outBufPtr = Mem.OSLockObject(rethOutBufByVal); try { return decodeMultiNoteLookupData(unids.length, outBufPtr); } finally { Mem.OSUnlockObject(rethOutBufByVal); short resultMemFree = Mem.OSMemFree(rethOutBufByVal); NotesErrorUtils.checkResult(resultMemFree); } }); return infos; } finally { if (inMemHandleLocked) { Mem.OSUnlockObject(retHandleByVal); } short resultMemFree = Mem.OSMemFree(retHandleByVal); NotesErrorUtils.checkResult(resultMemFree); } }); return retNoteInfo; } /** * Helper method to extract the return data of method {@link #getMultiNoteInfo(int[])} or {@link #getMultiNoteInfo(String[])} * * @param nrOfElements number of list elements * @param outBufPtr buffer pointer * @return array of note info objects */ private DocInfo[] decodeMultiNoteLookupData(int nrOfElements, Pointer outBufPtr) { DocInfo[] retNoteInfo = new DocInfo[nrOfElements]; Pointer entryBufPtr = outBufPtr; for (int i=0; i getSequenceTime() { return Optional.ofNullable(m_sequenceTime); } /** * Returns the UNID as hex string * * @return UNID or null if the note could not be found */ @Override public String getUnid() { return StringUtil.toString(m_unid); } /** * Returns true if the note has already been deleted * * @return true if deleted */ @Override public boolean isDeleted() { return m_isDeleted; } /** * Returns true if the note currently exists in the database * * @return true if note exists */ @Override public boolean exists() { return !m_notPresent; } } /** * Extension of {@link DocInfoImpl} with additional note lookup data * * @author Karsten Lehmann */ private static class DocInfoExtImpl extends DocInfoImpl implements DocInfoExt { private NotesTimeDateStruct m_modified; private short m_noteClass; private NotesTimeDateStruct m_addedToFile; private short m_responseCount; private int m_parentNoteId; private DocInfoExtImpl(int noteId, String unid, DominoDateTime sequenceTime, int sequence, boolean isDeleted, boolean notPresent, NotesTimeDateStruct modified, short noteClass, NotesTimeDateStruct addedToFile, short responseCount, int parentNoteId) { super(noteId, unid, sequenceTime, sequence, isDeleted, notPresent); m_modified = modified; m_noteClass = noteClass; m_addedToFile = addedToFile; m_responseCount = responseCount; m_parentNoteId = parentNoteId; } @Override public Optional getModified() { return Optional.ofNullable(m_modified==null ? null : new JNADominoDateTime(m_modified)); } @Override public Set getNoteClass() { Set docClass = EnumSet.noneOf(DocumentClass.class); if (m_noteClass==0) { docClass.add(DocumentClass.NONE); } else { for (DocumentClass currClass : DocumentClass.values()) { if (currClass.getValue()!=0) { if ((m_noteClass & currClass.getValue()) == currClass.getValue()) { docClass.add(currClass); } } } } return docClass; } @Override public Optional getAddedToFile() { return Optional.ofNullable(m_addedToFile==null ? null : new JNADominoDateTime(m_addedToFile)); } @Override public short getResponseCount() { return m_responseCount; } @Override public int getParentNoteId() { return m_parentNoteId; } } @Override public DocInfoExt getDocumentInfo(int noteId) { checkDisposed(); JNADatabaseAllocations allocations = getAllocations(); return LockUtil.lockHandle(allocations.getDBHandle(), (dbHandleByVal) -> { NotesOriginatorIdStruct retNoteOID = NotesOriginatorIdStruct.newInstance(); NotesTimeDateStruct retModified = NotesTimeDateStruct.newInstance(); ShortByReference retNoteClass = new ShortByReference(); NotesTimeDateStruct retAddedToFile = NotesTimeDateStruct.newInstance(); ShortByReference retResponseCount = new ShortByReference(); IntByReference retParentNoteID = new IntByReference(); boolean isDeleted = false; //not sure if we can check this via error code: boolean notPresent = false; short result = NotesCAPI.get().NSFDbGetNoteInfoExt(dbHandleByVal, noteId, retNoteOID, retModified, retNoteClass, retAddedToFile, retResponseCount, retParentNoteID); if (result==INotesErrorConstants.ERR_NOTE_DELETED) { isDeleted = true; } else if (result==INotesErrorConstants.ERR_INVALID_NOTE) { notPresent = true; } else { NotesErrorUtils.checkResult(result); } String unid = retNoteOID.getUNIDAsString(); DominoDateTime sequenceTime = new JNADominoDateTime(retNoteOID.SequenceTime.Innards); int sequence = retNoteOID.Sequence; DocInfoExt info = new DocInfoExtImpl(noteId, unid, sequenceTime, sequence, isDeleted, notPresent, retModified, retNoteClass.getValue(), retAddedToFile, retResponseCount.getValue(), retParentNoteID.getValue()); return info; }); } @Override public Optional getAgent(String agentName) { checkDisposed(); Memory agentNameLMBCS = NotesStringUtils.toLMBCS(agentName, true); IntByReference retAgentNoteID = new IntByReference(); retAgentNoteID.setValue(0); short result = LockUtil.lockHandle(getAllocations().getDBHandle(), (hDbByVal)-> { return NotesCAPI.get().NIFFindDesignNoteExt(hDbByVal, agentNameLMBCS, NotesConstants.NOTE_CLASS_FILTER, NotesStringUtils.toLMBCS(NotesConstants.DFLAGPAT_AGENTSLIST, true), retAgentNoteID, NotesConstants.DGN_STRIPUNDERS); }); if ((result & NotesConstants.ERR_MASK)==1028) { //Entry not found in index return Optional.empty(); } //throws an error if agent cannot be found: NotesErrorUtils.checkResult(result); int agentNoteId = retAgentNoteID.getValue(); if (agentNoteId==0) { throw new DominoException(MessageFormat.format("Agent not found in database: {0}", agentName)); } DHANDLE.ByReference rethAgent = DHANDLE.newInstanceByReference(); result = LockUtil.lockHandle(getAllocations().getDBHandle(), (hDbByVal)-> { return NotesCAPI.get().AgentOpen(hDbByVal, agentNoteId, rethAgent); }); NotesErrorUtils.checkResult(result); JNAAgent agent = new JNAAgent(this, agentNoteId, rethAgent); return Optional.of(agent); } @Override public Optional getAgentSavedData(String agentName) { checkDisposed(); Memory agentNameLMBCS = NotesStringUtils.toLMBCS(agentName, true); IntByReference retAgentNoteID = new IntByReference(); retAgentNoteID.setValue(0); short result = LockUtil.lockHandle(getAllocations().getDBHandle(), (hDbByVal)-> { return NotesCAPI.get().NIFFindDesignNoteExt(hDbByVal, agentNameLMBCS, NotesConstants.NOTE_CLASS_FILTER, NotesStringUtils.toLMBCS(NotesConstants.DFLAGPAT_AGENTSLIST, true), retAgentNoteID, NotesConstants.DGN_STRIPUNDERS); }); if ((result & NotesConstants.ERR_MASK)==1028) { //Entry not found in index return Optional.empty(); } //throws an error if agent cannot be found: NotesErrorUtils.checkResult(result); int agentNoteId = retAgentNoteID.getValue(); if (agentNoteId==0) { throw new DominoException(MessageFormat.format("Agent not found in database: {0}", agentName)); } NotesUniversalNoteIdStruct.ByReference retUNID = NotesUniversalNoteIdStruct.newInstanceByReference(); result = LockUtil.lockHandle(getAllocations().getDBHandle(), (hDbByVal) -> { return NotesCAPI.get().AssistantGetLSDataNote(hDbByVal, agentNoteId, retUNID); }); NotesErrorUtils.checkResult(result); String unid = retUNID.toString(); if (StringUtil.isEmpty(unid) || "00000000000000000000000000000000".equals(unid)) { //$NON-NLS-1$ return Optional.empty(); } else { return getDocumentByUNID(unid); } } @Override public DominoOriginatorId generateOID() { NotesOriginatorIdStruct oidStruct = generateOIDStruct(); return new JNADominoOriginatorId(oidStruct); } NotesOriginatorIdStruct generateOIDStruct() { checkDisposed(); JNADatabaseAllocations allocations = getAllocations(); NotesOriginatorIdStruct retOIDStruct = NotesOriginatorIdStruct.newInstance(); short result = LockUtil.lockHandle(allocations.getDBHandle(), (handleByVal) -> { return NotesCAPI.get().NSFDbGenerateOID(handleByVal, retOIDStruct); }); NotesErrorUtils.checkResult(result); retOIDStruct.read(); return retOIDStruct; } public enum DbMode { /** internal db handle refers to a "directory" and not a file */ DIRECTORY, /** internal db handle refers to a normal database file */ DATABASE } /** * Use this function to find out whether the {@link JNADatabase} is a database or a directory. * (The C API uses the db handle also to scan directory contents) * * @return mode */ public DbMode getMode() { if (m_dbMode==null) { checkDisposed(); ShortByReference retMode = new ShortByReference(); short result; JNADatabaseAllocations allocations = getAllocations(); result = LockUtil.lockHandle(allocations.getDBHandle(), (handleByVal) -> { return NotesCAPI.get().NSFDbModeGet(handleByVal, retMode); }); NotesErrorUtils.checkResult(result); if (retMode.getValue() == NotesConstants.DB_LOADED) { m_dbMode = DbMode.DATABASE; } else { m_dbMode = DbMode.DIRECTORY; } } return m_dbMode; } /** * Checks whether a database is located on a remote server * * @return true if remote */ @Override public boolean isRemote() { checkDisposed(); short isRemote = LockUtil.lockHandle(getAllocations().getDBHandle(), (hDbByVal) -> { return NotesCAPI.get().NSFDbIsRemote(hDbByVal); }); return isRemote==1; } @Override public boolean hasFullAccess() { checkDisposed(); short hasFullAccess = LockUtil.lockHandle(getAllocations().getDBHandle(), (hDbByVal) -> { return NotesCAPI.get().NSFDbHasFullAccess(hDbByVal); }); return hasFullAccess==1; } @Override public Database reopen() { checkDisposed(); HANDLE.ByReference rethNewDb = HANDLE.newInstanceByReference(); short result = LockUtil.lockHandle(getAllocations().getDBHandle(), (hDbByVal) -> { return NotesCAPI.get().NSFDbReopen(hDbByVal, rethNewDb); }); NotesErrorUtils.checkResult(result); JNAUserNamesList userNamesList = NotesNamingUtils.writeNewNamesList(getParent(), m_namesStringList); return new JNADatabase(getParentDominoClient(), 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) userNamesList; } else { return null; } } }); } @Override public Optional getDocumentByPrimaryKey(String category, String objectId) { String fullNodeName = getApplicationNoteName(category, objectId); int rrv = getNamedObjectRRV(fullNodeName, NotesConstants.NONS_NAMED_NOTE); try { return rrv==0 ? Optional.empty() : getDocumentById(rrv); } catch(DocumentDeletedException e) { return Optional.empty(); } } /** * Computes a $name value from category/objectkey similar to the name * of profile notes, e.g. "$app_015calcolorprofile_" or "$app_015calcolorprofile_myobjectname" * * @param category category part of primary key * @param objectKey object key part of primary key * @return note name */ static String getApplicationNoteName(String category, String objectKey) { // use a format similar to profile docs $name value, // e.g. "$app_015calcolorprofile_" or "$app_015calcolorprofile_myobjectname" String fullNodeName = (NAMEDNOTES_APPLICATION_PREFIX + StringUtil.pad(Integer.toString(category.length()), 3, '0', false) + category + "_" + //$NON-NLS-1$ objectKey) .toLowerCase(Locale.ENGLISH); return fullNodeName; } /** * Parses a string like "$app_015calcolorprofile_myobjectname" into * category and object id * * @param name name * @return array of [category,objectid] or null if unsupported format */ static String[] parseApplicationNamedNoteName(String name) { if (!name.startsWith(NAMEDNOTES_APPLICATION_PREFIX)) { return null; } String remainder = name.substring(5); //"$app_".length() if (remainder.length()<3) { return null; } String categoryNameLengthStr = remainder.substring(0, 3); int categoryNameLength = Integer.parseInt(categoryNameLengthStr); remainder = remainder.substring(3); String category = remainder.substring(0, categoryNameLength); remainder = remainder.substring(categoryNameLength+1); String objectKey = remainder; return new String[] {category, objectKey}; } /** * Returns the RRV of a named object in the database * * @param name name to look for * @return rrv or 0 if not found */ private int getNamedObjectRRV(String name, short namespace) { checkDisposed(); if (StringUtil.isEmpty(name)) { throw new IllegalArgumentException("Name cannot be empty"); } Memory nameMem = NotesStringUtils.toLMBCS(name, false); IntByReference rtnObjectID = new IntByReference(); short nsWithNoAssignBit = (short) (namespace | NotesConstants.NONS_NOASSIGN); //prevent assigning a new RRV if not found short result = LockUtil.lockHandle(getAllocations().getDBHandle(), (dbHandleByVal) -> { return NotesCAPI.get().NSFDbGetNamedObjectID(dbHandleByVal, nsWithNoAssignBit, nameMem, (short) (nameMem.size() & 0xffff), rtnObjectID); }); if ((result & NotesConstants.ERR_MASK)==578) { //special database object cannot be located return 0; } NotesErrorUtils.checkResult(result); return rtnObjectID.getValue(); } @Override public Map> getAllDocumentsByPrimaryKey() { Map> result = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); getNamedObjects(NotesConstants.NONS_NAMED_NOTE) .forEach((entry) -> { int noteId = entry.getNoteID(); if (noteId!=0) { String currName = entry.getNameOfDocument(); if (StringUtil.startsWithIgnoreCase(currName, NAMEDNOTES_APPLICATION_PREFIX)) { String[] parsedNamedNoteInfos = parseApplicationNamedNoteName(currName); if (parsedNamedNoteInfos!=null) { String category = parsedNamedNoteInfos[0]; String objectKey = parsedNamedNoteInfos[1]; Map entriesForCategory = result.get(category); if (entriesForCategory==null) { entriesForCategory = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); result.put(category, entriesForCategory); } entriesForCategory.put(objectKey, noteId); } } } }); return result; } @Override public Map getAllDocumentsByPrimaryKey(String category) { if (StringUtil.isEmpty(category)) { throw new IllegalArgumentException("Category cannot be empty"); } String prefix = getApplicationNoteName(category, ""); //$NON-NLS-1$ Map result = new HashMap<>(); getNamedObjects(NotesConstants.NONS_NAMED_NOTE) .forEach((entry) -> { int noteId = entry.getNoteID(); if (noteId!=0) { String currName = entry.getNameOfDocument(); if (StringUtil.startsWithIgnoreCase(currName, prefix)) { String objectKey = currName.substring(prefix.length()).toLowerCase(Locale.ENGLISH); result.put(objectKey, noteId); } } }); return result; } @Override public DominoCollection openDesignCollection() { checkDisposed(); try { DominoCollection col = openCollection(NotesConstants.NOTE_ID_SPECIAL | NotesConstants.NOTE_CLASS_DESIGN, (EnumSet) null); return col; } catch (DominoException e) { //ignore, we call DesignOpenCollection next which creates the design collection } short result = LockUtil.lockHandle(getAllocations().getDBHandle(), (hDbByVal) -> { DHANDLE.ByReference rethCollection = DHANDLE.newInstanceByReference(); IntByReference retCollectionNoteID = new IntByReference(); short openResult = NotesCAPI.get().DesignOpenCollection(hDbByVal, false, (short) 0, rethCollection, retCollectionNoteID); if (openResult==0) { LockUtil.lockHandle(rethCollection, (hDesignColByVal) -> { NotesErrorUtils.checkResult(NotesCAPI.get().NIFCloseCollection(hDesignColByVal)); return 0; }); } return openResult; }); NotesErrorUtils.checkResult(result); //try again: DominoCollection col = openCollection(NotesConstants.NOTE_ID_SPECIAL | NotesConstants.NOTE_CLASS_DESIGN, (EnumSet ) null); return col; } @Override public Stream getAllCollections() { // This goes the route of doing an NSFSearch in order to avoid trouble when the design collection // has not yet been initialized. // Additionally, it doesn't pull $TITLE, etc. from the summary buffer, as this can sometimes be // errantly empty. List noteIds = new ArrayList<>(); NotesSearch.search(this, null, "@True", "-", EnumSet.noneOf(SearchFlag.class), EnumSet.of(DocumentClass.VIEW), null, new NotesSearch.SearchCallback() { //$NON-NLS-1$ //$NON-NLS-2$ @Override public Action noteFound(JNADatabase parentDb, JNASearchMatch searchMatch, IItemTableData summaryBufferData) { noteIds.add(searchMatch.getNoteID()); return Action.Continue; } }); return noteIds.stream() .map((id) -> { return getDocumentById(id).orElse(null); } ) .filter(Objects::nonNull) .map((doc) -> { try (AutoCloseableDocument closeableDoc = doc.autoClosable()) { JNADominoCollectionInfo info = new JNADominoCollectionInfo(this); List titles = closeableDoc.getAsList(NotesConstants.FIELD_TITLE, String.class, Collections.emptyList()); titles = DesignUtil.toTitlesList(titles); String title = titles.isEmpty() ? "" : titles.get(0); //$NON-NLS-1$ info.setTitle(title); List aliases = titles.size() < 2 ? Collections.emptyList() : titles.subList(1, titles.size()); info.setAliases(aliases); info.setComment(closeableDoc.getAsText(NotesConstants.FILTER_COMMENT_ITEM, ' ')); info.setFlags(closeableDoc.getAsText(NotesConstants.DESIGN_FLAGS, ' ')); info.setLanguage(closeableDoc.getAsText(NotesConstants.FIELD_LANGUAGE, ' ')); info.setNoteID(closeableDoc.getNoteID()); info.setUNID(closeableDoc.getUNID()); return info; } }); } @Override public void forEachCollection(BiConsumer consumer) { LoopImpl loop = new LoopImpl(); Iterator it = getAllCollections().iterator(); while (it.hasNext()) { DominoCollectionInfo currInfo = it.next(); if (!it.hasNext()) { loop.setIsLast(); } consumer.accept(currInfo, loop); if (loop.isStopped()) { break; } loop.next(); } } @Override public String toStringLocal() { if (isDisposed()) { return MessageFormat.format("JNADatabase [disposed, server={0}, filepath={1}]", m_server, m_filePath); //$NON-NLS-1$ } else { return MessageFormat.format("JNADatabase [handle={0}, server={1}, filepath={2}]", getAllocations().getDBHandle(), getServer(), getRelativeFilePath()); //$NON-NLS-1$ } } @Override public void close() { if(!isDisposed()) { CAPIGarbageCollector.dispose(this); } } @Override public String getTemplateName() { checkDisposed(); Memory infoBuf = getDbInfoBuffer(); Memory templateNameMem = new Memory(NotesConstants.NSF_INFO_SIZE - 1); NotesCAPI.get().NSFDbInfoParse(infoBuf, NotesConstants.INFOPARSE_CLASS, templateNameMem, (short) (templateNameMem.size() & 0xffff)); return NotesStringUtils.fromLMBCS(templateNameMem, -1); } @Override public void setTemplateName(String newTemplateName) { checkDisposed(); Memory infoBuf = getDbInfoBuffer(); Memory newTemplateNameMem = NotesStringUtils.toLMBCS(newTemplateName, true); NotesCAPI.get().NSFDbInfoModify(infoBuf, NotesConstants.INFOPARSE_CLASS, newTemplateNameMem); writeDbInfoBuffer(infoBuf); } @Override public String getDesignTemplateName() { checkDisposed(); Memory infoBuf = getDbInfoBuffer(); Memory designTemplateNameMem = new Memory(NotesConstants.NSF_INFO_SIZE - 1); NotesCAPI.get().NSFDbInfoParse(infoBuf, NotesConstants.INFOPARSE_DESIGN_CLASS, designTemplateNameMem, (short) (designTemplateNameMem.size() & 0xffff)); return NotesStringUtils.fromLMBCS(designTemplateNameMem, -1); } @Override public void setDesignTemplateName(String newDesignTemplateName) { checkDisposed(); Memory infoBuf = getDbInfoBuffer(); Memory newDesignTemplateNameMem = NotesStringUtils.toLMBCS(newDesignTemplateName, true); NotesCAPI.get().NSFDbInfoModify(infoBuf, NotesConstants.INFOPARSE_DESIGN_CLASS, newDesignTemplateNameMem); writeDbInfoBuffer(infoBuf); } @Override public void updateDQLDesignCatalog(boolean rebuild) { checkDisposed(); short result = LockUtil.lockHandle(getAllocations().getDBHandle(), (hDbByVal) -> { return NotesCAPI.get().NSFDesignHarvest(hDbByVal, rebuild); }); NotesErrorUtils.checkResult(result); } @Override public DQLQueryResult queryDQL(DQLTerm query) { return queryDQL(query.toString(), null, 0, 0, 0); } @Override public DQLQueryResult queryDQL(DQLTerm query, Set flags) { return queryDQL(query, flags, 0, 0, 0); } @Override public DQLQueryResult queryDQL(DQLTerm query, Set flags, int maxDocsScanned, int maxEntriesScanned, int maxMsecs) { return queryDQL(query.toString(), flags, maxDocsScanned, maxEntriesScanned, maxMsecs); } @Override public DQLQueryResult queryDQL(String query) { return queryDQL(query, null, 0, 0, 0); } @Override public DQLQueryResult queryDQL(String query, Set flags) { return queryDQL(query, flags, 0, 0, 0); } @Override public DQLQueryResult queryDQL(String query, Set flags, int maxDocsScanned, int maxEntriesScanned, int maxMsecs) { checkDisposed(); Memory queryMem = NotesStringUtils.toLMBCS(query, true); int flagsAsInt = flags==null ? 0 : DominoEnumUtil.toBitField(DBQuery.class, flags); JNAIDTable idTable = null; String errorTxt = ""; //$NON-NLS-1$ String explainTxt = ""; //$NON-NLS-1$ IntByReference retError = new IntByReference(); retError.setValue(0); IntByReference retExplain = new IntByReference(); retExplain.setValue(0); long t0=System.currentTimeMillis(); DHANDLE.ByReference retResults = DHANDLE.newInstanceByReference(); retResults.clear(); short result = LockUtil.lockHandle(getAllocations().getDBHandle(), (hdbByVal) -> { return NotesCAPI.get().NSFQueryDB(hdbByVal, queryMem, flagsAsInt, maxDocsScanned, maxEntriesScanned, maxMsecs, retResults, retError, retExplain); }); if (retError.getValue()!=0) { try (LockedMemory m = Mem.OSMemoryLock(retError.getValue())) { errorTxt = NotesStringUtils.fromLMBCS(m.getPointer(), -1); } finally { Mem.OSMemoryFree(retError.getValue()); } } if (result!=0) { if (!StringUtil.isEmpty(errorTxt)) { throw new DominoException(result, errorTxt, NotesErrorUtils.toNotesError(result).orElse(null)); } else { NotesErrorUtils.checkResult(result); } } if (!retResults.isNull()) { idTable = new JNAIDTable(getParentDominoClient(), retResults, false); } if (retExplain.getValue()!=0) { try (LockedMemory m = Mem.OSMemoryLock(retExplain.getValue())) { explainTxt = NotesStringUtils.fromLMBCS(m.getPointer(), -1); } finally { Mem.OSMemoryFree(retExplain.getValue()); } } long t1=System.currentTimeMillis(); return new JNADQLQueryResult(this, query, idTable, explainTxt, t1-t0); } @Override public FTQueryResult queryFTIndex(String query, int maxResults, Set options, Set filterIds, int start, int count) { checkDisposed(); if (maxResults<0 || maxResults>65535) { throw new IllegalArgumentException("MaxResults must be between 0 and 65535"); } if (count>65535) { throw new IllegalArgumentException("Count must be between 0 and 65535"); } EnumSet searchOptionsToUse = EnumSet.noneOf(FTQuery.class); searchOptionsToUse.addAll(options); if (filterIds!=null) { //automatically set refine option if id table is not null searchOptionsToUse.add(FTQuery.REFINE); } int searchOptionsBitMask = DominoEnumUtil.toBitField(FTQuery.class, searchOptionsToUse); short limitAsShort = (short) (maxResults & 0xffff); JNAIDTable filterIDTable; if (filterIds instanceof JNAIDTable) { filterIDTable = (JNAIDTable) filterIds; } else if (filterIds!=null) { filterIDTable = new JNAIDTable(getParentDominoClient(), filterIds); } else { filterIDTable = null; } DHANDLE filterIDTableHandle = filterIDTable==null ? null : filterIDTable.getAdapter(DHANDLE.class); List builderNames = getParentDominoClient().getBuilderNamesList(); DHANDLE hNamesList = null; UserNamesList namesList = getAdapter(UserNamesList.class); if (namesList!=null) { boolean openAsIdUser; if (builderNames.isEmpty()) { openAsIdUser = NotesNamingUtils.equalNames(namesList.getPrimaryName(), getParentDominoClient().getIDUserName()); } else { openAsIdUser = false; } if (openAsIdUser) { hNamesList = null; } else { JNAUserNamesListAllocations namesListAllocations = (JNAUserNamesListAllocations) namesList.getAdapter(APIObjectAllocations.class); hNamesList = namesListAllocations.getHandle(); } } else { hNamesList = null; } return LockUtil.lockHandles(getAllocations().getDBHandle(), filterIDTableHandle, hNamesList, (hDbByVal, filterIDTableHandleByVal, hNamesListByVal) -> { long t0=System.currentTimeMillis(); DHANDLE.ByReference rethSearch = DHANDLE.newInstanceByReference(); short result = NotesCAPI.get().FTOpenSearch(rethSearch); NotesErrorUtils.checkResult(result); Memory queryLMBCS = NotesStringUtils.toLMBCS(query, true); IntByReference retNumDocs = new IntByReference(); DHANDLE.ByReference rethResults = DHANDLE.newInstanceByReference(); DHANDLE.ByReference rethStrings = DHANDLE.newInstanceByReference(); IntByReference retNumHits = new IntByReference(); short arg = 0; DHANDLE.ByValue hColl = null; short countAsShort = (short) count; result = NotesCAPI.get().FTSearchExt(hDbByVal, rethSearch, hColl, queryLMBCS, searchOptionsBitMask, limitAsShort, filterIDTableHandleByVal, retNumDocs, rethStrings, rethResults, retNumHits, start, countAsShort, arg, hNamesListByVal); long t1=System.currentTimeMillis(); if (result==3874) { //no documents found return LockUtil.lockHandle(rethSearch, (rethSearchByVal) -> { short closeResult = NotesCAPI.get().FTCloseSearch(rethSearchByVal); NotesErrorUtils.checkResult(closeResult); return new JNAFTQueryResult(JNADatabase.this, new JNAIDTable(JNADatabase.this.getParentDominoClient()), 0, 0, null, null, t1-t0); }); } NotesErrorUtils.checkResult(result); List highlightStrings = null; if (searchOptionsToUse.contains(FTQuery.RETURN_HIGHLIGHT_STRINGS)) { //decode highlights if (!rethStrings.isNull()) { highlightStrings = LockUtil.lockHandle(rethStrings, (rethStringsByVal) -> { if (rethStringsByVal!=null) { Pointer ptr = Mem.OSLockObject(rethStringsByVal); try { short varLength = ptr.getShort(0); ptr = ptr.share(2); short flags = ptr.getShort(0); ptr = ptr.share(2); String strHighlights = NotesStringUtils.fromLMBCS(ptr, varLength & 0xffff); List strings = new ArrayList<>(); StringTokenizerExt st = new StringTokenizerExt(strHighlights, "\n"); //$NON-NLS-1$ while (st.hasMoreTokens()) { String currToken = st.nextToken(); if (!StringUtil.isEmpty(currToken)) { strings.add(currToken); } } return strings; } finally { Mem.OSUnlockObject(rethStringsByVal); Mem.OSMemFree(rethStringsByVal); } } return null; }); } } JNAIDTable resultsIdTable = null; List matchesWithScore = null; if (searchOptionsToUse.contains(FTQuery.RETURN_IDTABLE)) { if (!rethResults.isNull()) { resultsIdTable = LockUtil.lockHandle(rethResults, (rethResultsByVal) -> { return new JNAIDTable(JNADatabase.this.getParentDominoClient(), rethResultsByVal, false); }); } else { resultsIdTable = new JNAIDTable(JNADatabase.this.getParentDominoClient()); } } else { if (!rethResults.isNull()) { matchesWithScore = LockUtil.lockHandle(rethResults, (rethResultsByVal) -> { Pointer ptr = Mem.OSLockObject(rethResultsByVal); try { return FTSearchResultsDecoder.decodeNoteIdsWithStoreSearchResult(ptr, searchOptionsToUse); } finally { Mem.OSUnlockObject(rethResultsByVal); Mem.OSMemFree(rethResultsByVal); } }); } } result = LockUtil.lockHandle(rethSearch, (rethSearchByVal) -> { return NotesCAPI.get().FTCloseSearch(rethSearchByVal); }); NotesErrorUtils.checkResult(result); return new JNAFTQueryResult(JNADatabase.this, resultsIdTable, retNumDocs.getValue(), retNumHits.getValue(), highlightStrings, matchesWithScore, t1-t0); }); } @Override public IDTable getAllNoteIds(Set docClasses, boolean includeDeletions) { return getModifiedNoteIds(docClasses, null, includeDeletions); } public static class JNAModifiedNoteInfo implements ModifiedNoteInfo { private int noteId; private String unid; private DominoDateTime seqTime; private String threadRootUnid; private boolean isDeleted; private byte[] entryData; @Override public int getNoteId() { return noteId; } @Override public String getUNID() { return unid; } @Override public DominoDateTime getSeqTime() { return seqTime; } @Override public String getThreadRootUNID() { return threadRootUnid; } @Override public boolean isDeleted() { return isDeleted; } @Override @SuppressWarnings("unchecked") public T getAdapter(Class clazz) { if (byte[].class == clazz) { return (T) entryData; } return null; } @Override public String toString() { return MessageFormat.format( "JNAModifiedNoteInfo [noteId={0}, unid={1}, seqTime={2}, threadRootUnid={3}, isDeleted={4}]", //$NON-NLS-1$ noteId, unid, seqTime, threadRootUnid, isDeleted ); } } @Override public ModifiedNoteInfos getModifiedNotesInfo(Set docClasses, Set infoRequested, final TemporalAccessor sinceParam, boolean includeDeletions, TemporalAccessor optUntilParam) { checkDisposed(); List retList = new ArrayList<>(); short docClassMask = DominoEnumUtil.toBitField(DocumentClass.class, docClasses); int infoRequestedMask = DominoEnumUtil.toBitField(ModifiedNotesInfoFlags.class, infoRequested); DominoDateTime since; if (sinceParam==null) { if (includeDeletions) { since = JNADominoDateTime.createWildcardDateTime(); } else { since = JNADominoDateTime.createMinimumDateTime(); } } else { since = JNADominoDateTime.from(sinceParam); } NotesTimeDateStruct sinceStruct = NotesTimeDateStruct.newInstance(since.getAdapter(int[].class)); NotesTimeDateStruct.ByValue sinceStructByVal = NotesTimeDateStruct.ByValue.newInstance(); sinceStructByVal.Innards[0] = sinceStruct.Innards[0]; sinceStructByVal.Innards[1] = sinceStruct.Innards[1]; IntByReference retArrayCount = new IntByReference(); NotesTimeDateStruct.ByReference retUntilStruct = NotesTimeDateStruct.newInstanceByReference(); if (optUntilParam!=null) { infoRequestedMask |= NotesConstants.DB_GET_MODIFIED_NOTES_INFO_USE_UNTIL; retUntilStruct.Innards = JNADominoDateTime.from(optUntilParam).getInnards(); retUntilStruct.write(); } else { retUntilStruct.Innards[0] = 0; retUntilStruct.Innards[1] = 0; } IntByReference retmhArray = new IntByReference(); int fInfoRequestedMask = infoRequestedMask; short result = LockUtil.lockHandle(getAllocations().getDBHandle(), (hDbByVal) -> { return NotesCAPI.get().NSFDbGetModifiedNotesInfo(hDbByVal, docClassMask, fInfoRequestedMask, sinceStructByVal, retArrayCount, retUntilStruct, retmhArray); }); NotesErrorUtils.checkResult(result); try (LockedMemory mem = Mem.OSMemoryLock(retmhArray.getValue());) { Pointer ptr = mem.getPointer(); int arrayCount = retArrayCount.getValue(); for (int i=0; i < arrayCount; i++) { byte[] entryData = ptr.getByteArray(0, 80); NotesTimeDateStruct tdStruct = NotesTimeDateStruct.newInstance(ptr); tdStruct.read(); DominoDateTime td = new JNADominoDateTime(tdStruct.Innards); ptr = ptr.share(JNANotesConstants.timeDateSize); NotesUniversalNoteIdStruct unidStruct = NotesUniversalNoteIdStruct.newInstance(ptr); unidStruct.read(); String unid = unidStruct.toString(); ptr = ptr.share(JNANotesConstants.notesUniversalNoteIdSize); short bDeleted = ptr.getShort(0); ptr = ptr.share(2); String threadRootUnid = null; if (infoRequested.contains(ModifiedNotesInfoFlags.THREAD_ROOT_UNID)) { NotesUniversalNoteIdStruct threadRootUnidStruct = NotesUniversalNoteIdStruct.newInstance(ptr); threadRootUnidStruct.read(); threadRootUnid = threadRootUnidStruct.toString(); ptr = ptr.share(JNANotesConstants.notesUniversalNoteIdSize); } else { threadRootUnid = null; } int noteID; if (infoRequested.contains(ModifiedNotesInfoFlags.NOTEID)) { noteID = ptr.getInt(0); ptr = ptr.share(4); /* set high bit on deletions if necessary */ if (bDeleted==1 && !infoRequested.contains(ModifiedNotesInfoFlags.NODELETED_BIT)) { noteID |= NotesConstants.NOTEID_RESERVED; } } else { noteID = 0; } JNAModifiedNoteInfo newEntry = new JNAModifiedNoteInfo(); newEntry.noteId = noteID; newEntry.isDeleted = bDeleted==1; newEntry.unid = unid; newEntry.seqTime = td; newEntry.threadRootUnid = threadRootUnid; newEntry.entryData = entryData; retList.add(newEntry); } } finally { Mem.OSMemoryFree(retmhArray.getValue()); } DominoDateTime until = new JNADominoDateTime(retUntilStruct.Innards); return new ModifiedNoteInfos() { @Override public List getInfos() { return retList; } @Override public DominoDateTime getUntil() { return until; } @Override public String toString() { return MessageFormat.format("JNAModifiedNoteInfos [size={0}, until={1}]", retList.size(), until); //$NON-NLS-1$ } }; } @Override public IDTable getModifiedNoteIds(Set docClasses, final TemporalAccessor sinceParam, boolean includeDeletions) { checkDisposed(); short docClassMask = DominoEnumUtil.toBitField(DocumentClass.class, docClasses); DominoDateTime since; if (sinceParam==null) { if (includeDeletions) { since = JNADominoDateTime.createWildcardDateTime(); } else { since = JNADominoDateTime.createMinimumDateTime(); } } else { since = JNADominoDateTime.from(sinceParam); } NotesTimeDateStruct.ByReference retUntilStruct = NotesTimeDateStruct.newInstanceByReference(); NotesTimeDateStruct sinceStruct = NotesTimeDateStruct.newInstance(since.getAdapter(int[].class)); NotesTimeDateStruct.ByValue sinceStructByVal = NotesTimeDateStruct.ByValue.newInstance(); sinceStructByVal.Innards[0] = sinceStruct.Innards[0]; sinceStructByVal.Innards[1] = sinceStruct.Innards[1]; sinceStructByVal.write(); return LockUtil.lockHandle(getAllocations().getDBHandle(), (hDbByVal) -> { DHANDLE.ByReference rethTable = DHANDLE.newInstanceByReference(); rethTable.clear(); short result = NotesCAPI.get().NSFDbGetModifiedNoteTable(hDbByVal, docClassMask, sinceStructByVal, retUntilStruct, rethTable); if (result == INotesErrorConstants.ERR_NO_MODIFIED_NOTES) { return new JNAIDTable(JNADatabase.this.getParentDominoClient()); } else { NotesErrorUtils.checkResult(result); } retUntilStruct.read(); if (rethTable.isNull()) { return getParentDominoClient().createIDTable(); } JNAIDTable idTable = new JNAIDTable(JNADatabase.this.getParentDominoClient(), rethTable, false); if (sinceParam==null) { DominoDateTime retUntil = new JNADominoDateTime(retUntilStruct.Innards); idTable.setDateTime(retUntil); //all documents requested; we already handle returning delete note ids with wildcard/minimum date return idTable; } else { if (includeDeletions) { DominoDateTime retUntil = new JNADominoDateTime(retUntilStruct.Innards); idTable.setDateTime(retUntil); //returning deletions as well is ok return idTable; } else { //check highest noteids for deletion bit int lastId = idTable.getLastId(); if ((lastId & IDTable.NOTEID_FLAG_DELETED) != IDTable.NOTEID_FLAG_DELETED) { //no deletions anyway return idTable; } else { //remove deleted notes from the idtable JNAIDTable idTableNoDeletions = new JNAIDTable(JNADatabase.this.getParentDominoClient()); for (Integer currNoteId : idTable) { if ((currNoteId & IDTable.NOTEID_FLAG_DELETED) != IDTable.NOTEID_FLAG_DELETED) { idTableNoDeletions.add(currNoteId); } } idTable.dispose(); DominoDateTime retUntil = new JNADominoDateTime(retUntilStruct.Innards); idTableNoDeletions.setDateTime(retUntil); return idTableNoDeletions; } } } }); } @Override public FormulaQueryResult queryFormula(String selectionFormula, IDTable filter, Set searchFlags, TemporalAccessor since, Set docClass) { JNAIDTable filterIdTable; if (filter==null) { filterIdTable = null; } else if (filter instanceof JNAIDTable) { filterIdTable = (JNAIDTable) filter; } else { filterIdTable = new JNAIDTable(getParentDominoClient(), filter); } JNAIDTable matchesIDTable = new JNAIDTable(getParentDominoClient()); List matches = new ArrayList<>(); List nonMatches = new ArrayList<>(); List deletions = new ArrayList<>(); JNADominoDateTime until = NotesSearch.search(this, filterIdTable, selectionFormula, "-", searchFlags, //$NON-NLS-1$ docClass, JNADominoDateTime.from(since), new NotesSearch.SearchCallback() { @Override public Action noteFound(JNADatabase parentDb, JNASearchMatch searchMatch, IItemTableData summaryBufferData) { matchesIDTable.add(searchMatch.getNoteID()); matches.add(searchMatch); return Action.Continue; } @Override public Action noteFoundNotMatchingFormula(JNADatabase parentDb, JNASearchMatch searchMatch, IItemTableData summaryBufferData) { nonMatches.add(searchMatch); return Action.Continue; } @Override public Action deletionStubFound(JNADatabase parentDb, JNASearchMatch searchMatch, IItemTableData summaryBufferData) { deletions.add(searchMatch); return Action.Continue; } }); matchesIDTable.setDateTime(until); return new JNAFormulaQueryResult(this, matchesIDTable, matches, nonMatches, deletions); } @Override public DominoDateTime queryFormula(String selectionFormula, Set filter, Set searchFlags, TemporalAccessor since, Set docClass, Map computeValues, FormulaQueryCallback callback) { JNAIDTable filterIdTable; if (filter==null) { filterIdTable = null; } else if (filter instanceof JNAIDTable) { filterIdTable = (JNAIDTable) filter; } else { filterIdTable = new JNAIDTable(getParentDominoClient(), filter); } return NotesSearch.search(this, filterIdTable, selectionFormula, computeValues, "-", searchFlags, //$NON-NLS-1$ docClass, JNADominoDateTime.from(since), new NotesSearch.SearchCallback() { @Override public Action noteFound(JNADatabase parentDb, JNASearchMatch searchMatch, IItemTableData summaryBufferData) { return callback.matchFound(JNADatabase.this, searchMatch, summaryBufferData); } @Override public Action noteFoundNotMatchingFormula(JNADatabase parentDb, JNASearchMatch searchMatch, IItemTableData summaryBufferData) { return callback.nonMatchFound(JNADatabase.this, searchMatch, summaryBufferData); } @Override public Action deletionStubFound(JNADatabase parentDb, JNASearchMatch searchMatch, IItemTableData summaryBufferData) { return callback.deletionFound(JNADatabase.this, searchMatch, summaryBufferData); } }); } @Override public int findCollectionId(String name, CollectionType type) { checkDisposed(); String findFlags; switch (type) { case Both: findFlags = NotesConstants.DFLAGPAT_VIEWS_AND_FOLDERS_DESIGN; break; case Folder: findFlags = NotesConstants.DFLAGPAT_FOLDER_DESIGN; break; case View: findFlags = NotesConstants.DFLAGPAT_VIEW_DESIGN; break; default: throw new IllegalArgumentException(MessageFormat.format("Unknown collection type: {0}", type)); } Memory viewNameLMBCS = NotesStringUtils.toLMBCS(name, true); IntByReference collectionNoteID = new IntByReference(); collectionNoteID.setValue(0); JNADatabaseAllocations allocations = getAllocations(); short result = LockUtil.lockHandle(allocations.getDBHandle(), (handleByVal) -> { return NotesCAPI.get().NIFFindDesignNoteExt(handleByVal, viewNameLMBCS, NotesConstants.NOTE_CLASS_VIEW, NotesStringUtils.toLMBCS(findFlags, true), collectionNoteID, 0); }); if ((result & NotesConstants.ERR_MASK)==1028) { //view not found return 0; } //throws an error if view cannot be found: NotesErrorUtils.checkResult(result); return collectionNoteID.getValue(); } @Override public int createFolder(String newFolderName) { return createFolder((Database) null, (String) null, newFolderName); } @Override public int createFolder(String formatFolderName, String newFolderName) { return createFolder((Database) null, formatFolderName, newFolderName); } @Override public int createFolder(int formatFolderNoteId, String newFolderName) { return createFolder((Database) null, formatFolderNoteId, newFolderName); } @Override public int createFolder(Database formatDb, String formatFolderName, String newFolderName) { if (StringUtil.isEmpty(formatFolderName)) { return createFolder(formatDb, 0, newFolderName); } int formatNoteId = formatDb==null ? findCollectionId(formatFolderName, CollectionType.Folder) : formatDb.findCollectionId(formatFolderName, CollectionType.Folder); if (formatNoteId==0) { formatNoteId = formatDb==null ? findCollectionId(formatFolderName, CollectionType.View) : formatDb.findCollectionId(formatFolderName, CollectionType.View); } if (formatNoteId==0) { throw new DominoException(1028, MessageFormat.format("No format view/folder found with name {0}", formatFolderName)); } return createFolder(formatDb, formatNoteId, newFolderName); } @Override public int createFolder(Database formatDb, int formatFolderNoteId, String newFolderName) { checkDisposed(); if (formatDb!=null) { if (!(formatDb instanceof JNADatabase)) { throw new IncompatibleImplementationException(formatDb, JNADatabase.class); } JNADatabase jnaFormatDb = (JNADatabase) formatDb; if (jnaFormatDb.isDisposed()) { throw new ObjectDisposedException(jnaFormatDb); } } Memory newFolderNameMem = NotesStringUtils.toLMBCS(newFolderName, false); if (newFolderNameMem.size() > NotesConstants.DESIGN_FOLDER_MAX_NAME) { throw new IllegalArgumentException(MessageFormat.format("Folder name too long (max {0} bytes, found {1} bytes)", NotesConstants.DESIGN_FOLDER_MAX_NAME, newFolderNameMem.size())); } short newFolderNameLength = (short) (newFolderNameMem.size() & 0xffff); IntByReference retNoteId = new IntByReference(); HANDLE formatDbHandle = formatDb==null ? null : formatDb.getAdapter(HANDLE.class); short result = LockUtil.lockHandles(getAllocations().getDBHandle(), formatDbHandle, (hDbByVal, hFormatDbByVal) -> { return NotesCAPI.get().FolderCreate(hDbByVal, hDbByVal, formatFolderNoteId, hFormatDbByVal, newFolderNameMem, newFolderNameLength, NotesConstants.DESIGN_TYPE_SHARED, 0, retNoteId); }); NotesErrorUtils.checkResult(result); return retNoteId.getValue(); } @Override public void deleteFolder(String folderName) { int folderNoteId = findCollectionId(folderName, CollectionType.Folder); if (folderNoteId==0) { throw new DominoException(1028, MessageFormat.format("No source folder found with name {0}", folderName)); } deleteFolder(folderNoteId); } @Override public void deleteFolder(int folderNoteId) { if (folderNoteId==0) { throw new IllegalArgumentException("Folder note id cannot be 0"); } checkDisposed(); short result = LockUtil.lockHandle(getAllocations().getDBHandle(), (hDbByVal) -> { return NotesCAPI.get().FolderDelete(hDbByVal, hDbByVal, folderNoteId, 0); }); NotesErrorUtils.checkResult(result); } @Override public void moveFolder(String folderName, String newParentFolderName) { int folderNoteId = findCollectionId(folderName, CollectionType.Folder); if (folderNoteId==0) { throw new DominoException(1028, MessageFormat.format("No folder found with name {0}", folderName)); } int newParentFolderNoteId = findCollectionId(newParentFolderName, CollectionType.Folder); if (newParentFolderNoteId==0) { throw new DominoException(1028, MessageFormat.format("No folder found with name {0}", newParentFolderName)); } moveFolder(folderNoteId, newParentFolderNoteId); } @Override public void moveFolder(int folderNoteId, int newParentFolderNoteId) { if (folderNoteId==0) { throw new IllegalArgumentException("Folder note id cannot be 0"); } if (newParentFolderNoteId==0) { throw new IllegalArgumentException("Target folder note id cannot be 0"); } checkDisposed(); short result = LockUtil.lockHandle(getAllocations().getDBHandle(), (hDbByVal) -> { return NotesCAPI.get().FolderMove(hDbByVal, null, folderNoteId, null, newParentFolderNoteId, 0); }); NotesErrorUtils.checkResult(result); } @Override public void renameFolder(String oldFolderName, String newFolderName) { int folderNoteId = findCollectionId(oldFolderName, CollectionType.Folder); if (folderNoteId==0) { throw new DominoException(1028, MessageFormat.format("No folder found with name {0}", oldFolderName)); } renameFolder(folderNoteId, newFolderName); } @Override public void renameFolder(int oldFolderNoteId, String newFolderName) { if (oldFolderNoteId==0) { throw new IllegalArgumentException("Folder note id cannot be 0"); } checkDisposed(); Memory pszName = NotesStringUtils.toLMBCS(newFolderName, false); if (pszName.size() > NotesConstants.DESIGN_FOLDER_MAX_NAME) { throw new IllegalArgumentException(MessageFormat.format("Folder name too long (max {0} bytes, found {1} bytes)", NotesConstants.DESIGN_FOLDER_MAX_NAME, pszName.size())); } short result = LockUtil.lockHandle(getAllocations().getDBHandle(), (hDbByVal) -> { return NotesCAPI.get().FolderRename(hDbByVal, null, oldFolderNoteId, pszName, (short) pszName.size(), 0); }); NotesErrorUtils.checkResult(result); } @Override public int copyFolder(String sourceFolderName, String newFolderName) { int sourceFolderNoteId = findCollectionId(sourceFolderName, CollectionType.Folder); if (sourceFolderNoteId==0) { throw new DominoException(1028, MessageFormat.format("No source folder found with name {0}", sourceFolderName)); } return copyFolder(sourceFolderNoteId, newFolderName); } @Override public int copyFolder(int sourceFolderNoteId, String newFolderName) { if (sourceFolderNoteId==0) { throw new IllegalArgumentException("Source folder note id cannot be 0"); } checkDisposed(); Memory newFolderNameMem = NotesStringUtils.toLMBCS(newFolderName, false); if (newFolderNameMem.size() > NotesConstants.DESIGN_FOLDER_MAX_NAME) { throw new IllegalArgumentException(MessageFormat.format("Folder name too long (max {0} bytes, found {1} bytes)", NotesConstants.DESIGN_FOLDER_MAX_NAME, newFolderNameMem.size())); } short newFolderNameLength = (short) (newFolderNameMem.size() & 0xffff); IntByReference retNewNoteId = new IntByReference(); short result = LockUtil.lockHandle(getAllocations().getDBHandle(), (hDbByVal) -> { return NotesCAPI.get().FolderCopy(hDbByVal, hDbByVal, sourceFolderNoteId, newFolderNameMem, newFolderNameLength, 0, retNewNoteId); }); NotesErrorUtils.checkResult(result); return retNewNoteId.getValue(); } @Override public void addToFolder(String folderName, Collection noteIds) { checkDisposed(); int folderNoteId = findCollectionId(folderName, CollectionType.Folder); if (folderNoteId==0) { throw new DominoException(1028, MessageFormat.format("No folder found with name {0}", folderName)); } addToFolder(folderNoteId, noteIds); } @Override public void addToFolder(int folderNoteId, Collection noteIds) { checkDisposed(); JNAIDTable idTable; boolean disposeTable = false; if (noteIds instanceof JNAIDTable) { idTable = (JNAIDTable) noteIds; if (idTable.isDisposed()) { throw new ObjectDisposedException(idTable); } } else { idTable = getParentDominoClient().createIDTable(); idTable.addAll(noteIds); disposeTable = true; } JNAIDTableAllocations idTableAllocations = (JNAIDTableAllocations) idTable.getAdapter(APIObjectAllocations.class); short result = LockUtil.lockHandles( getAllocations().getDBHandle(), idTableAllocations.getIdTableHandle(), (hDbByVal, hIdTableByVal) -> { return NotesCAPI.get().FolderDocAdd(hDbByVal, (HANDLE.ByValue) null, folderNoteId, hIdTableByVal, 0); } ); NotesErrorUtils.checkResult(result); if (disposeTable) { idTable.dispose(); } } @Override public void removeFromFolder(String folderName, Collection idTable) { checkDisposed(); int folderNoteId = findCollectionId(folderName, CollectionType.Folder); if (folderNoteId==0) { throw new DominoException(1028, MessageFormat.format("No folder found with name {0}", folderName)); } removeFromFolder(folderNoteId, idTable); } @Override public void removeFromFolder(int folderNoteId, Collection noteIds) { checkDisposed(); JNAIDTable idTable; boolean disposeTable = false; if (noteIds instanceof JNAIDTable) { idTable = (JNAIDTable) noteIds; if (idTable.isDisposed()) { throw new ObjectDisposedException(idTable); } } else { idTable = getParentDominoClient().createIDTable(); idTable.addAll(noteIds); disposeTable = true; } JNAIDTableAllocations idTableAllocations = (JNAIDTableAllocations) idTable.getAdapter(APIObjectAllocations.class); short result = LockUtil.lockHandles( getAllocations().getDBHandle(), idTableAllocations.getIdTableHandle(), (hDbByVal, hIdTableByVal) -> { return NotesCAPI.get().FolderDocRemove(hDbByVal, null, folderNoteId, hIdTableByVal, 0); } ); NotesErrorUtils.checkResult(result); if (disposeTable) { idTable.dispose(); } } @Override public void removeAllFromFolder(int folderNoteId) { checkDisposed(); short result = LockUtil.lockHandle(getAllocations().getDBHandle(), (hDbByVal) -> { return NotesCAPI.get().FolderDocRemoveAll(hDbByVal, null, folderNoteId, 0); }); NotesErrorUtils.checkResult(result); } @Override public void removeAllFromFolder(String folderName) { checkDisposed(); int folderNoteId = findCollectionId(folderName, CollectionType.Folder); if (folderNoteId==0) { throw new DominoException(1028, MessageFormat.format("No folder found with name {0}", folderName)); } removeAllFromFolder(folderNoteId); } @Override public int getFolderDocCount(String folderName) { checkDisposed(); int folderNoteId = findCollectionId(folderName, CollectionType.Folder); if (folderNoteId==0) { throw new DominoException(1028, MessageFormat.format("No folder found with name {0}", folderName)); } return getFolderDocCount(folderNoteId); } @Override public int getFolderDocCount(int folderNoteId) { checkDisposed(); IntByReference pdwNumDocs = new IntByReference(); short result = LockUtil.lockHandle(getAllocations().getDBHandle(), (hDbByVal) -> { return NotesCAPI.get().FolderDocCount(hDbByVal, null, folderNoteId, 0, pdwNumDocs); }); NotesErrorUtils.checkResult(result); return pdwNumDocs.getValue(); } @Override public IDTable getIDTableForFolder(String folderName, boolean validateIds) { checkDisposed(); int folderNoteId = findCollectionId(folderName, CollectionType.Folder); if (folderNoteId==0) { throw new DominoException(1028, MessageFormat.format("No folder found with name {0}", folderName)); } return getIDTableForFolder(folderNoteId, validateIds); } @Override public IDTable getIDTableForFolder(int folderNoteId, boolean validateIds) { checkDisposed(); DHANDLE.ByReference rethTable = DHANDLE.newInstanceByReference(); short result = LockUtil.lockHandle(getAllocations().getDBHandle(), (hDbByVal) -> { return NotesCAPI.get().NSFFolderGetIDTable(hDbByVal, hDbByVal, folderNoteId, validateIds ? NotesConstants.DB_GETIDTABLE_VALIDATE : 0, rethTable); }); NotesErrorUtils.checkResult(result); return new JNAIDTable(getParentDominoClient(), rethTable, false); } @Override public JNADbDesign getDesign() { checkDisposed(); return new JNADbDesign(this); } private String m_cachedUnreadTableUsernameCanonical; private JNAIDTable m_cachedUnreadTable; @Override public boolean isDocumentUnread(String userNameParam, int noteId) { String userName = StringUtil.isEmpty(userNameParam) ? getParentDominoClient().getEffectiveUserName() : userNameParam; String userNameCanonical = NotesNamingUtils.toCanonicalName(userName); if ( StringUtil.isEmpty(m_cachedUnreadTableUsernameCanonical) || m_cachedUnreadTable==null || m_cachedUnreadTable.isDisposed() || !NotesNamingUtils.equalNames(userNameCanonical, m_cachedUnreadTableUsernameCanonical)) { if (m_cachedUnreadTable!=null) { m_cachedUnreadTable.dispose(); m_cachedUnreadTable = null; } m_cachedUnreadTable = (JNAIDTable) getUnreadDocumentTable(userNameCanonical, true, true).orElse(null); m_cachedUnreadTableUsernameCanonical = userNameCanonical; } return m_cachedUnreadTable != null && m_cachedUnreadTable.contains(noteId); } @Override public Optional getUnreadDocumentTable(String userNameParam, boolean createIfNotAvailable, boolean updateUnread) { checkDisposed(); String userName = StringUtil.isEmpty(userNameParam) ? getParentDominoClient().getEffectiveUserName() : userNameParam; String userNameCanonical = NotesNamingUtils.toCanonicalName(userName); Memory userNameCanonicalMem = NotesStringUtils.toLMBCS(userNameCanonical, false); if (userNameCanonicalMem.size() > 65535) { throw new IllegalArgumentException("Username exceeds max length of 65535 bytes"); } short userNameLength = (short) (userNameCanonicalMem.size() & 0xffff); DHANDLE.ByReference rethUnreadList = DHANDLE.newInstanceByReference(); short result = LockUtil.lockHandle(getAllocations().getDBHandle(), (hDbByVal) -> { return NotesCAPI.get().NSFDbGetUnreadNoteTable2(hDbByVal, userNameCanonicalMem, userNameLength, createIfNotAvailable, updateUnread, rethUnreadList); }); NotesErrorUtils.checkResult(result); if (rethUnreadList.isNull()) { return Optional.empty(); } else { //make the cached ID table reflect the latest DB changes result = LockUtil.lockHandles(getAllocations().getDBHandle(), rethUnreadList, (hDbByVal, rethUnreadListByVal) -> { return NotesCAPI.get().NSFDbUpdateUnread(hDbByVal, rethUnreadListByVal); }); NotesErrorUtils.checkResult(result); return Optional.of(new JNAIDTable(getParentDominoClient(), rethUnreadList, false)); } } @Override public void updateUnreadDocumentTable(String userNameParam, Set noteIdToMarkRead, Set noteIdsToMarkUnread) { checkDisposed(); String userName = StringUtil.isEmpty(userNameParam) ? getParentDominoClient().getEffectiveUserName() : userNameParam; String userNameCanonical = NotesNamingUtils.toCanonicalName(userName); Memory userNameCanonicalMem = NotesStringUtils.toLMBCS(userNameCanonical, false); if (userNameCanonicalMem.size() > 65535) { throw new IllegalArgumentException("Username exceeds max length of 65535 bytes"); } short userNameLength = (short) (userNameCanonicalMem.size() & 0xffff); JNAIDTable unreadTable = (JNAIDTable) getUnreadDocumentTable(userNameCanonical, true, true).orElse(null); JNAIDTable unreadTableOrig; JNAIDTableAllocations unreadTableAllocations; if (unreadTable != null) { unreadTableAllocations = (JNAIDTableAllocations) unreadTable.getAdapter(APIObjectAllocations.class); unreadTableAllocations.checkDisposed(); //make the cached ID table reflect the latest DB changes short result = LockUtil.lockHandles(getAllocations().getDBHandle(), unreadTableAllocations.getIdTableHandle(), (hDbByVal, hUnreadTableByVal) -> { return NotesCAPI.get().NSFDbUpdateUnread(hDbByVal, hUnreadTableByVal); }); NotesErrorUtils.checkResult(result); unreadTableOrig = (JNAIDTable) unreadTable.clone(); } else { unreadTable = new JNAIDTable(getParentDominoClient()); unreadTableAllocations = (JNAIDTableAllocations) unreadTable.getAdapter(APIObjectAllocations.class); unreadTableAllocations.checkDisposed(); unreadTableOrig = new JNAIDTable(getParentDominoClient()); } if (noteIdToMarkRead != null && !noteIdToMarkRead.isEmpty()) { unreadTable.removeAll(noteIdToMarkRead); } if (noteIdsToMarkUnread != null && !noteIdsToMarkUnread.isEmpty()) { unreadTable.addAll(noteIdsToMarkUnread); } JNAIDTableAllocations unreadOrigTableAllocations = (JNAIDTableAllocations) unreadTableOrig.getAdapter(APIObjectAllocations.class); unreadOrigTableAllocations.checkDisposed(); short result = LockUtil.lockHandles( getAllocations().getDBHandle(), unreadOrigTableAllocations.getIdTableHandle(), unreadTableAllocations.getIdTableHandle(), (hDbByVal, hUnreadTableOrigByVal, hUnreadTableByVal) -> { return NotesCAPI.get().NSFDbSetUnreadNoteTable(hDbByVal, userNameCanonicalMem, userNameLength, true, hUnreadTableOrigByVal, hUnreadTableByVal); }); NotesErrorUtils.checkResult(result); unreadTable.dispose(); unreadTableOrig.dispose(); //remove cached unread table for this user that is used by isDocumentUnread if (m_cachedUnreadTable!=null && NotesNamingUtils.equalNames(userNameCanonical, m_cachedUnreadTableUsernameCanonical)) { m_cachedUnreadTable.dispose(); m_cachedUnreadTable = null; m_cachedUnreadTableUsernameCanonical = null; } } @Override public List queryAccessRoles(String userName) { JNAUserNamesList namesList = null; try { namesList = NotesNamingUtils.buildNamesList(getParentDominoClient(), getServer(), userName); List roles = getACL().lookupAccess(namesList).getRoles(); return roles; } catch (DominoException e) { throw new DominoException(e.getId(), MessageFormat.format("Error computing roles for {0} on server \"{1}\"", userName, m_server), e); } finally { if (namesList!=null) { namesList.dispose(); } } } @Override public void refreshDesign(String server) { refreshDesign(server, true, true, null); } @Override public void refreshDesign(String server, boolean force, boolean errIfTemplateNotFound, final IBreakHandler abortHandler) { checkDisposed(); Memory serverMem = NotesStringUtils.toLMBCS(server, true); NotesCallbacks.ABORTCHECKPROC abortProc; if (abortHandler!=null) { if (PlatformUtils.isWin32()) { abortProc = (Win32NotesCallbacks.ABORTCHECKPROCWin32) () -> { if (abortHandler.shouldInterrupt()==Action.Stop) { return INotesErrorConstants.ERR_CANCEL; } return 0; }; } else { abortProc = () -> { if (abortHandler.shouldInterrupt()==Action.Stop) { return INotesErrorConstants.ERR_CANCEL; } return 0; }; } } else { abortProc = null; } int dwFlags = 0; if (force) { dwFlags |= NotesConstants.DESIGN_FORCE; } if (errIfTemplateNotFound) { dwFlags |= NotesConstants.DESIGN_ERR_TMPL_NOT_FOUND; } int fDwFlags = dwFlags; short result = LockUtil.lockHandle(getAllocations().getDBHandle(), (hDbByVal) -> { return NotesCAPI.get().DesignRefresh(serverMem, hDbByVal, fDwFlags, abortProc, null); }); NotesErrorUtils.checkResult(result); } @Override public void hardDeleteDocument(int softDelNoteId) { checkDisposed(); short result = LockUtil.lockHandle(getAllocations().getDBHandle(), (hDbByVal) -> { return NotesCAPI.get().NSFNoteHardDelete(hDbByVal, softDelNoteId, 0); }); NotesErrorUtils.checkResult(result); } @Override public void getModifiedTime(Ref retDataModified, Ref retNonDataModified) { checkDisposed(); NotesTimeDateStruct.ByReference retDataModifiedStruct = NotesTimeDateStruct.newInstanceByReference(); NotesTimeDateStruct.ByReference retNonDataModifiedStruct = NotesTimeDateStruct.newInstanceByReference(); short result = LockUtil.lockHandle(getAllocations().getDBHandle(), (hDbByVal) -> { return NotesCAPI.get().NSFDbModifiedTime(hDbByVal, retDataModifiedStruct, retNonDataModifiedStruct); }); NotesErrorUtils.checkResult(result); if (retDataModified!=null) { retDataModified.set(new JNADominoDateTime(retDataModifiedStruct.Innards)); } if (retNonDataModified!=null) { retNonDataModified.set(new JNADominoDateTime(retNonDataModifiedStruct.Innards)); } } @Override public DocumentSummaryQueryResult queryDocuments() { IDTable ids = getAllNoteIds(EnumSet.of(DocumentClass.DATA), false); return queryDocuments(ids); } @Override public DocumentSummaryQueryResult queryDocuments(Collection noteIds) { return new JNADocumentSummaryQueryResult(this, noteIds); } @Override public NavigableMap getItemDefinitionTable() { checkDisposed(); JNADatabaseAllocations dbAllocations = getAllocations(); //we return a map with sorted case-insensitive keys that contain the item names: return LockUtil.lockHandle(dbAllocations.getDBHandle(), (hDbByVal) -> { INotesCAPI api = NotesCAPI.get(); HANDLE.ByReference retItemNameTable = HANDLE.newInstanceByReference(); short openTableResult = api.NSFDbItemDefTableExt(hDbByVal, retItemNameTable); NotesErrorUtils.checkResult(openTableResult); return LockUtil.lockHandle(retItemNameTable, (hItemNameTableByVal) -> { NavigableMap table = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); Pointer pItemDefExt = Mem.OSLockObject(hItemNameTableByVal); try { NotesItemDefinitionTableExt itemDefTableExt = NotesItemDefinitionTableExt.newInstance(pItemDefExt); itemDefTableExt.read(); NotesItemDefinitionTableLock itemDefTableLock = NotesItemDefinitionTableLock.newInstance(); short lockTableResult = api.NSFItemDefExtLock(pItemDefExt, itemDefTableLock); NotesErrorUtils.checkResult(lockTableResult); try { IntByReference retNumEntries = new IntByReference(); short accessTableDataResult = api.NSFItemDefExtEntries(itemDefTableLock, retNumEntries); NotesErrorUtils.checkResult(accessTableDataResult); int iNumEntries = retNumEntries.getValue(); ShortByReference retItemType = new ShortByReference(); ShortByReference retItemNameLength = new ShortByReference(); //implemented this like the sample code for NSFDbItemDefTableExt //provided in the C API documentation, but it looks like the following //128 byte buffer is never used, instead the NSFItemDefExtGetEntry //call redirects the pointer stored in retItemNamePtr to //the item name in memory try(DisposableMemory retItemName = new DisposableMemory(128); DisposableMemory retItemNamePtr = new DisposableMemory(Native.POINTER_SIZE)) { retItemNamePtr.setPointer(0, retItemName); for (int i=0; i { return NotesCAPI.get().NSFDbLocalSecInfoGetLocal(dbHandleByVal, state, strength); })); return new EncryptionInfoImpl( DominoEnumUtil.valueOf(DatabaseEncryptionState.class, state.getValue()), DominoEnumUtil.valueOf(Encryption.class, strength.getValue()) ); } @Override public void setLocalEncryptionInfo(Encryption encryption, String userName) { checkDisposed(); HANDLE hDb = getAllocations().getDBHandle(); if (userName==null) { userName = ""; //$NON-NLS-1$ } String userNameCanonical = NotesNamingUtils.toCanonicalName(userName); Memory userNameCanonicalMem = NotesStringUtils.toLMBCS(userNameCanonical, true); short option = NotesConstants.LSECINFOSET_MODIFY; if (encryption == Encryption.None) { option = NotesConstants.LSECINFOSET_CLEAR; } byte strengthAsByte = (byte) (encryption.getValue() & 0xff); short fOption = option; short result = LockUtil.lockHandle(hDb, (hDbByVal) -> { return NotesCAPI.get().NSFDbLocalSecInfoSet(hDbByVal, fOption, strengthAsByte, userNameCanonicalMem); }); NotesErrorUtils.checkResult(result); } @Override public AccessInfo getEffectiveAccessInfo() { checkDisposed(); ShortByReference retAccessLevel = new ShortByReference(); ShortByReference retAccessFlag = new ShortByReference(); LockUtil.lockHandle(getAllocations().getDBHandle(), (dbHandleByVal) -> { NotesCAPI.get().NSFDbAccessGet(dbHandleByVal, retAccessLevel, retAccessFlag); return null; }); return new AccessInfoImpl( DominoEnumUtil.valueOf(AclLevel.class, retAccessLevel.getValue()) .orElseThrow(() -> new IllegalStateException(MessageFormat.format("Cannot identify access level for {0}", retAccessLevel.getValue()))), DominoEnumUtil.valuesOf(AclFlag.class, retAccessFlag.getValue()) ); } @Override public NSFVersionInfo getNSFVersionInfo() { checkDisposed(); ShortByReference retMajorVersion = new ShortByReference(); ShortByReference retMinorVersion = new ShortByReference(); short result = LockUtil.lockHandle(getAllocations().getDBHandle(), (dbHandleByVal) -> { return NotesCAPI.get().NSFDbMajorMinorVersionGet(dbHandleByVal, retMajorVersion, retMinorVersion); }); NotesErrorUtils.checkResult(result); return new NSFVersionInfoImpl((int) (retMajorVersion.getValue() & 0xffff), (int) (retMinorVersion.getValue() & 0xffff)); } @Override public BuildVersionInfo getBuildVersionInfo() { checkDisposed(); NotesBuildVersionStruct retVersion = NotesBuildVersionStruct.newInstance(); short result = LockUtil.lockHandle(getAllocations().getDBHandle(), (dbHandleByVal) -> { return NotesCAPI.get().NSFDbGetMajMinVersion(dbHandleByVal, retVersion); }); NotesErrorUtils.checkResult(result); ShortByReference retBuildNumber = new ShortByReference(); result = LockUtil.lockHandle(getAllocations().getDBHandle(), (hDbByVal) -> { return NotesCAPI.get().NSFDbGetBuildVersion(hDbByVal, retBuildNumber); }); NotesErrorUtils.checkResult(result); retVersion.read(); return new BuildVersionInfoImpl(retVersion.MajorVersion, retVersion.MinorVersion, retVersion.QMRNumber, retVersion.QMUNumber, retVersion.HotfixNumber, retVersion.Flags, retVersion.FixpackNumber, retBuildNumber.getValue() & 0xffff); } @Override public Optional getNamesList() { checkDisposed(); DHANDLE.ByReference rethNamesList = DHANDLE.newInstanceByReference(); short result = LockUtil.lockHandle(getAllocations().getDBHandle(), (hDbByVal) -> { return NotesCAPI.get().NSFDbGetNamesList(hDbByVal, 0, rethNamesList); }); NotesErrorUtils.checkResult(result); if (rethNamesList.isNull()) { return Optional.empty(); } else { JNAUserNamesList userNamesList = new JNAUserNamesList(this, rethNamesList); return Optional.of(userNamesList); } } @Override public int getSpecialNoteId(DocumentClass documentClass) { Objects.requireNonNull(documentClass, "documentClass cannot be null"); IntByReference retNoteID = new IntByReference(); short result = LockUtil.lockHandle(getAllocations().getDBHandle(), (hDbByVal) -> { return NotesCAPI.get().NSFDbGetSpecialNoteID(hDbByVal, (short)(NotesConstants.SPECIAL_ID_NOTE | documentClass.getValue()), retNoteID); }); NotesErrorUtils.checkResult(result); return retNoteID.getValue(); } @Override public RichTextBuilder getRichTextBuilder() { return new JNARichTextBuilder(this); } /** * Computes the $name value for ghost notes as they are written since 12.0.1 by the Java/LS API ("named documents") * * @param name name * @param username username * @return $name value */ private String computeNamedNoteValue(String name, String username) { if (name==null) { throw new IllegalArgumentException("Name parameter cannot be null"); } else if (StringUtil.isEmpty(name)) { throw new IllegalArgumentException("Name parameter cannot be empty"); } else if (name.contains("*")) { //$NON-NLS-1$ throw new IllegalArgumentException(MessageFormat.format("Invalid character '*' in name parameter: {0}", name)); } String usernameNotNull = username==null ? "" : username; //$NON-NLS-1$ if (usernameNotNull.contains("*")) { //$NON-NLS-1$ throw new IllegalArgumentException(MessageFormat.format("Invalid character '*' in username parameter: {0}", usernameNotNull)); } return "$DGHST_" + name + "*" + usernameNotNull; //$NON-NLS-1$ //$NON-NLS-2$ } /** * Parses a string like "$DGHST_nameddoc*user" into * name/username * * @param name name * @return array of [name,username] if provided name format is supported */ static Optional parseLegacyAPINamedNoteName(String name) { if (!name.startsWith("$DGHST_")) { //$NON-NLS-1$ return Optional.empty(); } String remainder = name.substring(7); //"$DGHST_".length() int iPos = remainder.indexOf("*"); //$NON-NLS-1$ if (iPos==-1) { return Optional.empty(); } String namePart = remainder.substring(0, iPos); String userNamePart = remainder.substring(iPos+1); return Optional.of(new String[] {namePart, userNamePart}); } @Override public Document getNamedDocument(final String name, final String username) { if (name==null) { throw new IllegalArgumentException("Name parameter cannot be null"); } else if (name.contains("*")) { //$NON-NLS-1$ throw new IllegalArgumentException(MessageFormat.format("Invalid character '*' in name parameter: {0}", name)); } String usernameNotNull = username==null ? "" : username; //$NON-NLS-1$ if (usernameNotNull.contains("*")) { //$NON-NLS-1$ throw new IllegalArgumentException(MessageFormat.format("Invalid character '*' in username parameter: {0}", usernameNotNull)); } String nameAndUser = "$DGHST_" + name + "*" + usernameNotNull; //$NON-NLS-1$ //$NON-NLS-2$ int rrv = getNamedObjectRRV(nameAndUser, NotesConstants.NONS_NAMED_NOTE); if (rrv!=0) { Document doc = getDocumentById(rrv).orElse(null); if (doc!=null) { return doc; } } Document newDoc = createDocument(EnumSet.of(CreateFlags.HIDE_FROM_VIEWS)); newDoc.replaceItemValue("$name", nameAndUser); //$NON-NLS-1$ return newDoc; } /** * Reads infos about named notes. Implementation is private, because there's currently no * usage for other namespaces than NONS_NAMED_NOTE and the risk is high to break internal NSF stuff * * @param namespace namespace * @return named object infos */ private List getNamedObjects(short namespace) { checkDisposed(); IntByReference rethBuffer = new IntByReference(); IntByReference retBufferLength = new IntByReference(); short result = LockUtil.lockHandle(getAllocations().getDBHandle(), (hDbByVal) -> { return NotesCAPI.get().NSFGetNamedObjects(hDbByVal, namespace, rethBuffer, retBufferLength); }); NotesErrorUtils.checkResult(result); int hBuffer = rethBuffer.getValue(); if (hBuffer==0) { return Collections.emptyList(); } try (LockedMemory lockedMem = Mem.OSMemoryLock(rethBuffer.getValue(), true);) { Pointer ptr = lockedMem.getPointer(); int numEntries = Short.toUnsignedInt(ptr.getShort(0)); if (numEntries==0) { return Collections.emptyList(); } ptr = ptr.share(2); NamedObjectInfoImpl[] objInfos = new NamedObjectInfoImpl[numEntries]; for (int i=0; i parsedName; private int nameLength; private short namespace; private int noteId; public String getRawName() { return rawName; } private void setRawName(String name) { this.rawName = name; } @Override public int getNoteID() { return noteId; } private void setNoteId(int noteId) { this.noteId = noteId; } public short getNamespace() { return namespace; } private void setNamespace(short namespace) { this.namespace = namespace; } private int getNameLength() { return nameLength; } private void setNameLength(int nameLength) { this.nameLength = nameLength; } @Override public String getNameOfDocument() { if (parsedName==null) { parsedName = parseLegacyAPINamedNoteName(getRawName()); } return parsedName.map((v) -> { return v[0]; }).orElse(""); //$NON-NLS-1$ } @Override public String getUserNameOfDocument() { if (parsedName==null) { parsedName = parseLegacyAPINamedNoteName(getRawName()); } return parsedName.map((v) -> { return v[1]; }).orElse(""); //$NON-NLS-1$ } @Override public String toString() { return MessageFormat.format("NamedObjectInfo [name={0}, username={1}, noteId={2}]", getNameOfDocument(), getUserNameOfDocument(), getNoteID()); } } @Override public Collection getNamedDocumentInfos(String name) { String prefix = StringUtil.isEmpty(name) ? "$DGHST_": computeNamedNoteValue(name, ""); //$NON-NLS-1$ //$NON-NLS-2$ return getNamedObjects(NotesConstants.NONS_NAMED_NOTE) .stream() .filter((entry) -> { return StringUtil.startsWithIgnoreCase(entry.getRawName(), prefix); }) .collect(Collectors.toList()); } /** * Caches the database option LARGE_ITEMS_ENABLED internally to improve performance * * @return true if large items are supported */ boolean hasLargeItemSupport() { if (m_hasLargeItemSupport==null) { m_hasLargeItemSupport = getOption(DatabaseOption.LARGE_ITEMS_ENABLED); } return m_hasLargeItemSupport; } @Override public void createIndex(String name, Collection fields, boolean isvisible, boolean nobuild) { checkDisposed(); if (StringUtil.isEmpty(name)) { throw new IllegalArgumentException("Index name cannot be empty"); } List fieldsMem = new ArrayList<>(); for (String currField : fields) { if (StringUtil.isEmpty(currField)) { throw new IllegalArgumentException(MessageFormat.format("Method does not support empty field names: {0}", fields)); } Memory currFieldMem = NotesStringUtils.toLMBCS(currField, true); if (currFieldMem.size() > (NotesConstants.DESIGN_NAME_MAX-1)) { throw new IllegalArgumentException(MessageFormat.format("Field exceeds max length of {0}: {1}", NotesConstants.DESIGN_NAME_MAX-1, currField)); } fieldsMem.add(currFieldMem); } IntByReference hdsgncmd = new IntByReference(); int v0 = hdsgncmd.getValue(); for (Memory currFieldMem : fieldsMem) { short result = NotesCAPI1201.get().NSFDesignCommandAddComponent(currFieldMem, NotesConstants.DESIGN_COMPONENT_ATTR.VALS_ASCENDING.getValue(), hdsgncmd); int v1 = hdsgncmd.getValue(); NotesErrorUtils.checkResult(result); } Memory nameMem = NotesStringUtils.toLMBCS(name, true); IntByReference hretval = new IntByReference(); IntByReference hreterror = new IntByReference(); int dwFlags = 0; if (isvisible) { dwFlags |= NotesConstants.CREATE_INDEX_NOHIDE; } if (nobuild) { dwFlags |= NotesConstants.CREATE_INDEX_NOBUILD; } int dwFlagsFinal = dwFlags; short result = LockUtil.lockHandle(getAllocations().getDBHandle(), (hDbByVal) -> { return NotesCAPI1201.get().NSFDesignCommand(hDbByVal, NotesConstants.DESIGN_COMMAND_TYPE.CREATE_INDEX.getValue(), dwFlagsFinal, nameMem, hretval, hreterror, hdsgncmd.getValue()); }); String errorTxt = ""; //$NON-NLS-1$ if (hreterror.getValue()!=0) { try (LockedMemory m = Mem.OSMemoryLock(hreterror.getValue())) { errorTxt = NotesStringUtils.fromLMBCS(m.getPointer(), -1); } finally { Mem.OSMemoryFree(hreterror.getValue()); } } if (result!=0) { if (!StringUtil.isEmpty(errorTxt)) { throw new DominoException(result, errorTxt, NotesErrorUtils.toNotesError(result).orElse(null)); } else { NotesErrorUtils.checkResult(result); } } } @Override public void removeIndex(String name) { checkDisposed(); if (StringUtil.isEmpty(name)) { throw new IllegalArgumentException("Index name cannot be empty"); } Memory nameMem = NotesStringUtils.toLMBCS(name, true); IntByReference hidx = new IntByReference(); IntByReference hreterror = new IntByReference(); short result = LockUtil.lockHandle(getAllocations().getDBHandle(), (hDbByVal) -> { return NotesCAPI1201.get().NSFDesignCommand(hDbByVal, NotesConstants.DESIGN_COMMAND_TYPE.DELETE_INDEX.getValue(), 0, nameMem, hidx, hreterror, 0); }); if (hidx.getValue()!=0) { Mem.OSMemoryFree(hidx.getValue()); } if ((result & NotesConstants.ERR_MASK)==1028) { //index view not found if (hreterror.getValue()!=0) { Mem.OSMemoryFree(hreterror.getValue()); } return; } String errorTxt = ""; //$NON-NLS-1$ if (hreterror.getValue()!=0) { try (LockedMemory m = Mem.OSMemoryLock(hreterror.getValue())) { errorTxt = NotesStringUtils.fromLMBCS(m.getPointer(), -1); } finally { Mem.OSMemoryFree(hreterror.getValue()); } } if (result!=0) { if (!StringUtil.isEmpty(errorTxt)) { throw new DominoException(result, errorTxt, NotesErrorUtils.toNotesError(result).orElse(null)); } else { NotesErrorUtils.checkResult(result); } } } @Override public String listIndexes() { checkDisposed(); IntByReference hret = new IntByReference(); IntByReference hreterror = new IntByReference(); short result = LockUtil.lockHandle(getAllocations().getDBHandle(), (hDbByVal) -> { return NotesCAPI1201.get().NSFDesignCommand(hDbByVal, NotesConstants.DESIGN_COMMAND_TYPE.LIST_INDEXES.getValue(), 0, null, hret, hreterror, 0); }); String errorTxt = ""; //$NON-NLS-1$ if (hreterror.getValue()!=0) { try (LockedMemory m = Mem.OSMemoryLock(hreterror.getValue())) { errorTxt = NotesStringUtils.fromLMBCS(m.getPointer(), -1); } finally { Mem.OSMemoryFree(hreterror.getValue()); } } if (result!=0) { if (!StringUtil.isEmpty(errorTxt)) { throw new DominoException(result, errorTxt, NotesErrorUtils.toNotesError(result).orElse(null)); } else { NotesErrorUtils.checkResult(result); } } String retTxt = ""; //$NON-NLS-1$ if (hret.getValue()!=0) { try (LockedMemory m = Mem.OSMemoryLock(hret.getValue())) { retTxt = NotesStringUtils.fromLMBCS(m.getPointer(), -1); } finally { Mem.OSMemoryFree(hret.getValue()); } } return retTxt; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy