Is there any way I can ask queries to Notes Database - lotus-notes

I am working on fetching meetings given two dates: e.g. fetch all the meetings that are in the current month.
Suppose that I have around 45 meetings in the specified period. My web service is taking a lot of time.
This is how I'm doing it right now:
I fetch all the documents in the calendar view.
Check all the documents for the start Date and end date.
If any of the meetings fall in the specified period i am constructing an array and i am returning that array.
Is this correct?

This way is correct, but very inefficient. Better use the NotesDatabase- Class and create a Query to use with the search- method:
Here an example in LotusScript (as you do not specify a language)
Dim ses as New NotesSession
Dim db as NotesDatabase
Dim dc as NotesDocumentCollection
Dim strQuery as String
Set db = ses.CurrentDatabase
strQuery = {Form = "Appointment" & _
(StartDate >= [01.01.2014] & StartDate < [01.02.2014]) | _
(EndDate >= [01.01.2014] & EndDate < [01.02.2014])}
Set dc = db.Search( strQuery , Nothing, 0 )
'- Cycle through this collection...
Of course you need to dynamically adjust the strQuery by building it from todays date... But this will be much more performant than your version.

It is correct, but not very performant when you have a lot of documents. Basically you will create a view with first column the meeting (start)date, sorted. In LotusScript you can acces the view, set the "cursor" of the first meeting that matches the starting date and then step thru the view until you reach a date after the end date.
Read about view´s GetDocumentByKey method. Further here: http://publib.boulder.ibm.com/infocenter/domhelp/v8r0/index.jsp?topic=%2Fcom.ibm.designer.domino.main.doc%2FH_LOCATING_DOCUMENTS_WITHIN_A_VIEW_OR_FOLDER.html
Hmmm ... thinking a litlle further, what happens if you have a start date but no matching meeting ... so refer to FTSearch() method.

If you are using Notes / Domino 9.0 or later, you should use the built-in calendar classes. These are available either from LotusScript or Java. Here's an example using Java. Given a database object and a date range, it prints all the entries in the range:
private static void printRange(Database database, DateTime start, DateTime end) throws NotesException {
// Get the calendar object from the database
NotesCalendar calendar = database.getParent().getCalendar(database);
if ( calendar != null ) {
// Get a list of calendar entries
Vector<NotesCalendarEntry> entries = calendar.getEntries(start, end);
if ( entries != null ) {
// For each entry ...
Iterator<NotesCalendarEntry> iterator = entries.iterator();
while (iterator.hasNext()) {
NotesCalendarEntry entry = iterator.next();
// Read the iCalendar representation
String icalendar = entry.read();
// Get the Notes UNID
Document doc = entry.getAsDocument();
String unid = doc.getUniversalID();
// Print UNID and iCalendar to console
System.out.println("Entry UNID: " + unid);
System.out.println(icalendar);
}
}
}
}
The NotesCalendar and NotesCalendarEntry interfaces are in the lotus.domino package. If you are using LotusScript, there are classes of the same name and with the same methods.
A couple of warnings about the above code:
It doesn't handle the nuances of repeating entries. You can have multiple instances of a repeating entry in the same time range. In Notes these entries might have the same UNID. You should test your code with repeating entries to make sure you understand the nuances.
The example doesn't recycle the Document, NotesCalendarEntry and NotesCalendar objects as it should. I skipped this for simplicity, but if you are using the Notes Java classes, you definitely need to use recycle() correctly. It will save headaches down the road.

Related

IBM Lotus - updating room reservation, room name is appended to LOCATION each time

I have a custom XPage java REST api in Lotus to handle reservations, everything is going well so far.
I have one weird "problem" though, whenever I update a meeting, the room name (like Test room/Site) is being appended to the LOCATION in iCalendar string.
The way I'm updating appointments is by finding the entry in user's calendar (NotesCalendar.getEntryByUID), then I call NotesCalendarEntry.read() to get the iCalendar string, I then manually replace values of certain iCalendar fields, like DTSTART to the ones I wish to update (I'm only updating DTSTART, DTEND and SUMMARY). Finally I call NotesCalendarEntry.update(string) to update the event.
It works well, however with each update, the LOCATION field grows bigger and bigger, because the room name is being constantly appended to it, and finally it looks like:
LOCATION: Test Room/Test SiteTest Room/Test SiteTest Room/Test Site
etc.
Am I doing something wrong? How can I prevent this? I don't want to clear the location field each time, because users can put their own location and I'd like to keep it (in that case room name is also being appended to the original location text)
Code:
NotesCalendar cal = session.getCalendar( session.getDbDirectory( session.getServerName() ).openMailDatabase() );
NotesCalendarEntry calEntry = cal.getEntryByUNID(apptUNID); // apptUNID is taken from http json payload
String iCalE = calEntry.read();
// 20190326T160000Z
String dStart = DateUtil.formatICalendar(dtStart);
String dEnd = DateUtil.formatICalendar(dtEnd);
iCalE = iCalE.replace("\\n", ""); // I added this because same was happening to literal \n (not actual line breaks)
StringBuilder sb = new StringBuilder(iCalE);
int StartIndex = iCalE.indexOf("BEGIN:VEVENT"); // DTSTART is also in BEGIN:VTIMEZONE so we need to exclude that
int tmpIndex = iCalE.indexOf("DTSTART", StartIndex) + 7; // 7 = len of DTSTART
int LineBreakIndex = iCalE.indexOf('\n', tmpIndex);
if(iCalE.charAt(LineBreakIndex-1) == '\r')
LineBreakIndex--;
sb.delete(tmpIndex, LineBreakIndex);
sb.insert(tmpIndex, ":" + dStart); // start date
tmpIndex = sb.indexOf("DTEND", StartIndex) + 5;
LineBreakIndex = sb.indexOf(Character.toString('\n'), tmpIndex);
if(sb.charAt(LineBreakIndex-1) == '\r')
LineBreakIndex--;
sb.delete(tmpIndex, LineBreakIndex);
sb.insert(tmpIndex, ":" + dEnd);
calEntry.update(sb.toString());
calEntry.recycle();
Also, can I safely assume that iCalendar lines are always ended with \r\n ? (they currently are, I had some problems with that but I figured it out, I'm not sure if I can safely look for '\r\n' though)
I dont use ical4j because I'm literally only modifying 2 or 3 fields and nothing else.

How truncate time while querying documents for date comparison in Cosmos Db

I have document contains properties like this
{
"id":"1bd13f8f-b56a-48cb-9b49-7fc4d88beeac",
"name":"Sam",
"createdOnDateTime": "2018-07-23T12:47:42.6407069Z"
}
I want to query a document on basis of createdOnDateTime which is stored as string.
query e.g. -
SELECT * FROM c where c.createdOnDateTime>='2018-07-23' AND c.createdOnDateTime<='2018-07-23'
This will return all documents which are created on that day.
I am providing date value from date selector which gives only date without time so, it gives me problem while comparing date.
Is there any way to remove time from createdOnDateTime property or is there any other way to achieve this?
CosmosDB clients are storing timestamps in ISO8601 format and one of the good reasons to do so is that its lexicographical order matches the flow of time. Meaning - you can sort and compare those strings and get them ordered by time they represent.
So in this case you don't need to remove time components just modify the passed in parameters to get the result you need. If you want all entries from entire date of 2018-07-23 then you can use query:
SELECT * FROM c
WHERE c.createdOnDateTime >= '2018-07-23'
AND c.createdOnDateTime < '2018-07-24'
Please note that this query can use a RANGE index on createdOnDateTime.
Please use User Defined Function to implement your requirement, no need to update createdOnDateTime property.
UDF:
function con(date){
var myDate = new Date(date);
var month = myDate.getMonth()+1;
if(month<10){
month = "0"+month;
}
return myDate.getFullYear()+"-"+month+"-"+myDate.getDate();
}
SQL:
SELECT c.id,c.createdOnDateTime FROM c where udf.con(c.createdOnDateTime)>='2018-07-23' AND udf.con(c.createdOnDateTime)<='2018-07-23'
Output :
Hope it helps you.

"At least one object must implement IComparable" exception from LINQ query results

I have not used LINQ very extensively, but I'm trying to read data from a large Excel spreadsheet (14K+ rows) that requires me to make queries from multiple worksheets and even requery the original spreadsheet to filter specific data. Because OleDb queries of Excel can take a relatively long time (500+ms per query for a file on my local machine), I'm doing a couple of these queries at the front of my method, starting a loop through a "base" DataTable, then trying to use LINQ to filter down the data within that loop to put the appropriate data into a more structured DataSet. Here is some code to help explain (VB.NET):
Dim Connection As System.Data.OleDb.OleDbConnection
Dim Command As System.Data.OleDb.OleDbDataAdapter
Dim EXCEL_SHEET_DATA_1 As New DataTable
Dim EXCEL_SHEET_DATA_2 As New DataTable
Dim EXCEL_SHEET_DATA_3 As New DataTable
Dim TapeFile As New FileInfo("C:\TempFolder\tapefile.xls")
Connection = New System.Data.OleDb.OleDbConnection("provider=Microsoft.Jet.OLEDB.4.0; Data Source='" & TapeFile.FullName & "'; Extended Properties=Excel 8.0;")
Command = New System.Data.OleDb.OleDbDataAdapter("SELECT * FROM [SHEET1$] ORDER BY [USER_ID] ASC, [MEMBER_NUMBER] ASC;", Connection)
Command.Fill(EXCEL_SHEET_DATA_1)
Command.Dispose()
Command = New System.Data.OleDb.OleDbDataAdapter("SELECT * FROM [SHEET2$] ORDER BY [USER_ID] ASC, [MEMBER_NUMBER] ASC;", Connection)
Command.Fill(EXCEL_SHEET_DATA_2)
Command.Dispose()
Command = New System.Data.OleDb.OleDbDataAdapter("SELECT * FROM [SHEET3$] ORDER BY [USER_ID] ASC, [MEMBER_NUMBER] ASC;", Connection)
Command.Fill(EXCEL_SHEET_DATA_3)
Command.Dispose()
For Each Row As DataRow In EXCEL_SHEET_DATA_1.Rows
Dim MemberNumber As String = Row("MEMBER_NUMBER").ToString.Trim
Dim UserNumber As String = Row("USER_ID").ToString.Trim
' -- CODE FOR INITIAL PROCESSING OF SHEET1 DATA - NO ERRORS --
Dim CoMemberQuery As IEnumerable(Of DataRow) = From cm In EXCEL_SHEET_DATA_2 Where cm("MEMBER_NUMBER") = MemberNumber And cm("USER_ID") = UserNumber
For Each CoMemberRow As DataRow In CoMemberQuery
' -- CODE FOR PROCESSING OF SHEET2 DATA - NO ERRORS --
Next CoMemberRow
Dim VehicleQuery As IEnumerable(Of DataRow) = From veh In EXCEL_SHEET_DATA_1 Where veh("MEMBER_NUMBER") = MemberNumber And veh("USER_ID") = UserNumber Order By veh("VIN") Ascending
' *******************************************************
' -->> HERE IS WHERE I *SOMETIMES* GET THE EXCEPTION <<--
' *******************************************************
For Each VehicleRow As DataRow In VehicleQuery
' -- CODE FOR SECONDARY PROCESSING OF SHEET1 DATA - NO ERRORS --
Next VehicleRow
Next Row
I don't get the exception every time. The only thing I've noticed as possibly having something to do with it is that for the specific MemberNumber and UserNumber combination that causes the first exception, the first row in the result set would most likely contain a NULL value for the VIN field.
I'm sure the problem has to do with my LINQ query syntax, but I am simply too inexperienced in this regard to know why it's failing. Any assistance would be greatly appreciated. If you require any additional information regarding the code or implementation, let me know and I'll try to add it to the question.
Thank you for your time.
Your VehicleQuery has the following phrase: Order By veh("VIN") Ascending.
So as soon as VehicleQuery gets evaluated (by starting the For loop), LINQ will evaluate all of the items in that query, and then perform a sorting operation, which involves comparing the veh("VIN") values with each other and putting them in order.
When comparing any two items in your query, it tries to see if either value knows how to compare itself with values of the other type (hence implementing the IComparable interface. If they cannot, then it doesn't know which one should go first.
My guess is that veh("VIN") is (sometimes) yielding objects that don't know how to compare themselves with other values returned by this expression. Depending on the kind of data you're using, and how you want it to be compared, you might consider doing some kind of cast or conversion, or simply calling ToString() on the value, to make sure it's comparable: Order By veh("VIN").ToString() Ascending
(Please pardon any syntax errors, as I'm a C# developer.)

Exporting Business Account attributes with Acumatica API

Our Business Accounts in Acumatica have 13 custom Attributes for our main Business Account Class. I've been able to save values to the Attributes successfully, based on Acumatica's example "Adding Records to the Business Accounts and Opportunities Forms". But I have not been able to figure out how to retrieve the values with an Export.
First, I tried using a format similar to how the field was specified when saving them.
Public Function GetCustomerAttributes(ByVal customerID As String) As String()()
Dim customer As CR303000Content = m_context.CR303000GetSchema()
m_context.CR303000Clear()
Dim idFilter As Filter = New Filter()
idFilter.Field = customer.AccountSummary.BusinessAccount
idFilter.Condition = FilterCondition.Equals
idFilter.Value = customerID
' SIMILAR TO EXAMPLE FOR SAVING
Dim awdField As Field = New Field()
awdField.ObjectName = customer.Attributes.Attribute.ObjectName
awdField.FieldName = "AWD Number"
Dim searchfilters() As Filter = {idFilter}
Dim searchCommands() As Command = {awdField}
Dim searchResult As String()() = m_context.CR303000Export(searchCommands, searchfilters, 0, False, False)
Return searchResult
End Function
I thought this would return one result with the value for our attribute named "AWD Number". Instead, it returned 13 results, one for each attribute, and the value of each one was blank. I changed the FieldName to customer.Attributes.Attribute.FieldName and then it started returning the name of each attribute. So I thought if I added another field for the value, then I might get the name and value in separate results, like this:
Public Function GetCustomerAttributes(ByVal customerID As String) As String()()
Dim customer As CR303000Content = m_context.CR303000GetSchema()
m_context.CR303000Clear()
Dim idFilter As Filter = New Filter()
idFilter.Field = customer.AccountSummary.BusinessAccount
idFilter.Condition = FilterCondition.Equals
idFilter.Value = customerID
Dim awdField As Field = New Field()
awdField.ObjectName = customer.Attributes.Attribute.ObjectName
awdField.FieldName = customer.Attributes.Attribute.FieldName
Dim awdValue As Field = New Field()
awdValue.ObjectName = customer.Attributes.Attribute.ObjectName
awdValue.FieldName = customer.Attributes.Attribute.Value
Dim searchfilters() As Filter = {idFilter}
Dim searchCommands() As Command = {awdField, awdValue}
Dim searchResult As String()() = m_context.CR303000Export(searchCommands, searchfilters, 0, False, False)
Return searchResult
End Function
I did get a 2-item array back for each of the 13 results, but the value in the second field was still blank.
Does anyone know how I can get the values? I don't really care if I have to get them one at a time, but I'd prefer to get them all at once with their names or codes so that I don't have to rely on the indices always staying the same. Below are images of the debugger running on my second example and view in Acumatica. Thanks!
Your first attempt is correct, however you're not using the right object name and field name. The system will dynamically add fields to the primary object (view) of the screen, in this case the object name represented by customer.AccountSummary.BusinessAccount.ObjectName variable (I suggest you use the debugger to see what this value equals too - good learning exercise).
The attribute field name will use the same naming convention as used in How To Retrieve An Attribute Field In StockItems In Acumatica API?. The naming convention is _Attributes. The attribute ID is not the attribute name; I don't see your configuration but I doubt in your case the Attribute ID is "AWD Number". To summarize, the code will look like:
Dim awdField As Field = New Field()
awdField.ObjectName = customer.AccountSummary.BusinessAccount.ObjectName
awdField.FieldName = "AWDNumber_Attributes"
In your example, by putting the Attributes.Attribute.ObjectName object, the system will iterate through all values inside this table, and then return for every row the fields you want. I'm not exactly sure why you're not seeing all the attribute values in this case, but I think you should be fine with the example above.

getting a list of forms that contain a specified field, is there a better method

The following code is a script object on an XPage in it I loop through an array of all the forms in a database, looking for all the forms that contain the field "ACIncludeForm". My method works but it takes 2 - 3 seconds to compute which really slows the load of the XPage. My question is - is there a better method to accomplish this. I added code to check to see if the sessionScope variable is null and only execute if needed and the second time the page loads it does so in under a second. So my method really consumes a lot of processor time.
var forms:Array = database.getForms();
var rtn = new Array;
for (i=0 ; i<forms.length; ++i){
var thisForm:NotesForm = forms[i];
var a = thisForm.getFields().indexOf("ACIncludeForm");
if (a >= 0){
if (!thisForm.isSubForm()) {
if (thisForm.getAliases()[0] == ""){
rtn.push(thisForm.getName() + "|" + thisForm.getName() );
}else{
rtn.push(thisForm.getName() + "|" + thisForm.getAliases()[0] );
}
}
}
thisForm.recycle()
}
sessionScope.put("ssAllFormNames",rtn)
One approach would be to build an index of forms by yourself. For example, create an agent (LotusScript or Java) that gets all forms and for each form, create a document with for example a field "form" containing the form name and and a field "fields" containing all field names (beware of 32K limit).
Then create a view that displays all these documents and contains the value of the "fields" field in the first column so that each value of this field creates one line in this view.
Having such a view, you can simply make a #DbLookup from your XPage.
If your forms are changed, you only need to re-run the agent to re-build your index. The #DbLookup should be pretty fast.
Place the form list in a static field of a Java class. It will stay there for a long time (maybe until http boot). In my experience applicationScope values dissappear in 15 minutes.

Resources