getColumnValues vs cycle through a NotesViewNavigator - xpages

in this post Return from first column of a categorized view when using readers fields I raised the issue that if I do getColumnValue(0) on a categorized view I just the unique category values, but because of Reader fields there could be categories that the user does not have access to the documents under it. The other option is to create a NotesViewNavigator and iterate through the navigator and build a treeMap of the values.
My concern is that the view could contain thousands of entries and it would have to cycle through all the entries to build the category list which would typically have maybe 10 -12 entries.
So my question is what is the performance hit and scalability of the NotesViewNavigator process and/or is there a better way to get the categories in a view and recognize the users Reader rights.
New Edits
Can't seem to get this to work. I have a single categorized view and use this code WFSUtils.sysOut() simply prints to the console.
vw.setAutoUpdate(false);
var nav:NotesViewNavigator = vw.createViewNav();
nav.setEntryOptions(NotesViewNavigator.VN_ENTRYOPT_NOCOUNTDATA);
nav.setBufferMaxEntries(400);
nav.setMaxLevel(0);
var rtn:java.util.TreeMap=new java.util.TreeMap();
var entry:NotesViewEntry = nav.getFirst();
try{
while (entry != null){
WFSUtils.sysOut("Entry not null");
var thisCat:String = entry.getColumnValues().firstElement().toString();
WFSUtils.sysOut("thisCat = " + thisCat);
rtn.put(thisCat,"Nothing");
WFSUtils.sysOut("did put " + thisCat)
var tEntry:NotesViewEntry = nav.getNextCategory(entry);
entry.recycle();
entry = tEntry;
tEntry.recycle();
}
viewScope.put("vsCats", rtn.keySet());
}catch(e){
WFSUtils.sysOut("Error in getCategory " + e.toString())
}
the console print out is this:
25/08/2014 11:47:36 AM HTTP JVM: Get Navigator
25/08/2014 11:47:36 AM HTTP JVM: Entry not null
25/08/2014 11:47:36 AM HTTP JVM: thisCat = Approved~Bill Fox^WFS Automated Back end Process Example
25/08/2014 11:47:36 AM HTTP JVM: did put Approved~Bill Fox^WFS Automated Back end Process Example
25/08/2014 11:47:36 AM HTTP JVM: Error in getCategory Method NotesViewNavigator.getNextCategory(lotus.domino.local.ViewEntry) not found, or illegal parameters
The view has 9 categories so I would expect it to do 9 getNextCategories. I changed it to do getNext and the console out put is:
25/08/2014 11:52:42 AM HTTP JVM: Get Navigator
25/08/2014 11:52:43 AM HTTP JVM: Entry not null
25/08/2014 11:52:43 AM HTTP JVM: thisCat = Approved~Bill Fox^WFS Automated Back end Process Example
25/08/2014 11:52:43 AM HTTP JVM: did put Approved~Bill Fox^WFS Automated Back end Process Example
25/08/2014 11:52:43 AM HTTP JVM: Entry not null
25/08/2014 11:52:43 AM HTTP JVM: Error in getCategory Exception occurred calling method NotesViewEntry.getColumnValues()
25/08/2014 11:52:43 AM HTTP JVM: null
so it fails on the getColumnValues which worked on the first time through but not the second - not sure where the JVM: null is coming from.
any ideas greatly appreciated.

It looks like the View-design-level option "Don't show empty categories" does this for you - I'd previously assumed it was just a tip-off to the client to hide them, but it looks like it affects the behavior of ViewNavigator as well (which otherwise would show the full categories as well).
Once you have that checked, something like this may do the trick:
List<String> categories = new ArrayList<String>();
View view = database.getView("Some View");
view.setAutoUpdate(false);
ViewNavigator nav = view.createViewNav();
nav.setEntryOptions(ViewNavigator.VN_ENTRYOPT_NOCOUNTDATA);
nav.setBufferMaxEntries(400);
nav.setMaxLevel(0);
ViewEntry entry = nav.getFirst();
while (entry != null) {
Vector<?> columnValues = entry.getColumnValues();
String category = String.valueOf(columnValues.get(0));
categories.add(category);
entry.recycle(columnValues);
ViewEntry tempEntry = entry;
entry = nav.getNext();
tempEntry.recycle();
}
I believe that that should be the fastest/safest way to do it at that point. setMaxLevel(0) means that the first/next operations will include only categories (or whatever is your top level), while the other ViewNavigator settings are for performance tweaking.

No, I don't think you have to cycle through all the entries. Without having Domino Designer at hand right now, this is a bit of a rough guess (usually I do a quick test drive before answering here), but I'm quite confident it should work like this:
Just get to the first entry which in your case must be a category, then use .getNextSibling or .getNextCategory (careful if the view has multiple category levels!) jumping from category to category, and skipping all the other entries.
Depending on how the view index is set up, you might also check if there are any children available.
Just to be complete: make sure you set the parent view's AutoUpdate property to false; depending on the size of the view you might also want to experiment with various cache sizes; and of course don't forget to recycle, but you know that, I guess ;)
See also here (= AutoUpdate), here (= recycle properly) and here (= performance tip).
Hope this helps

Related

Cannot create a kit via code after upgrade to Acumatica 2022R1 (22.106.0015) - financial period cannot be specified

We have a custom processing screen for creating kit assemblies that has stopped working after the upgrade to 2022R1.
The code: (I added lines to attempt to set the Tran Date and Financial Period; the date is getting set but the FinPeriod lines have no effect)
protected virtual void GenerateKitAssembly(KitAssemblyEntry kitGraph, ProcessKitAssemblyItemInfoPX soline)
{
kitGraph.Clear();
INKitRegister doc = PXCache<INKitRegister>.CreateCopy(kitGraph.Document.Insert(new INKitRegister()));
doc.InventoryID = soline.InventoryID;
doc.KitRevisionID = soline.KitRevisionID;
doc.Qty = soline.AssemblyQty;
doc.UOM = soline.UOM;
doc.Hold = true;
doc.Status = INDocStatus.Hold;
doc.BranchID = soline.BranchID;
doc.TranBranchID = soline.BranchID;
doc.TranDate = DateTime.Now;
doc.TranTranDate = DateTime.Now;
doc.FinPeriodID = DateTime.Now.ToString("yyyyMM");
doc.TranFinPeriodID = DateTime.Now.ToString("yyyyMM");
doc.TranPeriodID = DateTime.Now.ToString("yyyyMM");
doc.TranTranPeriodID = DateTime.Now.ToString("yyyyMM");
doc.SiteID = soline.SiteID;
doc.GetExtension<INKitRegisterExt>().UsrManualProcess = soline.RoboticsFulfill == null ? true : !soline.RoboticsFulfill;
doc = kitGraph.Document.Update(doc);
The error:
PX.Data.PXFieldValueProcessingException
HResult=0x80131500
Message=Error: An error occurred during processing of the field Post Period value 07-2022 GL Error: The financial period cannot be specified because the branch has not been specified in the Branch box.
Source=PX.Data
StackTrace:
at PX.Data.PXCache`1.FillWithValues(TNode item, TNode copy, TNode newitem)
at PX.Data.PXCache`1.Update(Object data, Boolean bypassinterceptor)
at PX.Data.PXCache`1.Update(Object data, Boolean bypassinterceptor)
at PX.Data.PXSelectBase`1.Update(Table item)
at PX.CreateKitAssemblyFromSO.Ext.ProcessKitAssemblyItemsPX.GenerateKitAssembly(KitAssemblyEntry kitGraph, ProcessKitAssemblyItemInfoPX soline) in C:\Apps\Acumatica\AcumaticaUpgrade\App_Data\Projects\IntegraOptics\IntegraOptics\IN\ProcessKitAssemblyItemsPX.cs:line 143
This exception was originally thrown at this call stack:
[External Code]
Inner Exception 1:
PXSetPropertyException: GL Error: The financial period cannot be specified because the branch has not been specified in the Branch box.
I have a field updating handler that allows me to examine the kit and I can see the fields relating to Fin Period are all null, however the code that is throwing the exception is buried Acumatica code.
Can someone help identify what has changed with the upgraded version and what I need to do to resolve this? I have confirmed the logged in user has a default branch set, and I can see that the company branch is selected in the GUI.
Looks like we had a method in the KitAssemblyEntry extension that was attempting to update some info the kit immediately and causing the create kit processing screen to barf. The function needed to be rewritten to change the order of operations around a bit.

XPages - Date comparison is wrong

For some reason my code is returning the backwards answer in my date comparison. (SSJS)
I have 2 code blocks, one is below, the other simply changes the line if(dtCreated < dtCutoff) to if(dtCreated > dtCutoff)
try{
var sdf = new java.text.SimpleDateFormat("dd-MM-yyyy");
var dtCreated = document1.getItemValueDate("CreatedDate");
var dtCutoff = new Date(2002, 03, 22, 00, 30);
dtCreated = dtCreated == null?"":sdf.format(dtCreated);
dtCutoff = dtCutoff == null?"":sdf.format(dtCutoff);
print("ONE: Created: " + dtCreated);
print("ONE: Cutoff: " + dtCutoff);
if(dtCreated < dtCutoff) {
print ("1.1 created before cutoff return true");
return true;
}else{
print ("1.2 created before cutoff return false")
return false;
}
}catch(e){
openLogBean.addError(e,this.getParent());
}
For some reason, it seems to be getting the result mixed up, where by the created date is after the cutoff and date yet it says created date is before the cutoff date, and vice versa.
Any ideas why? Date stuff has always been my achilles heel. Each code block is used in the loaded property of a custom control. My end goal is to show 1 custom control or the other if a document was created before or after a certain date.
Print from the console is below, thanks:
HTTP JVM: ONE: Created: 26-02-2020
HTTP JVM: ONE: Cutoff: 22-04-2002
HTTP JVM: 1.2 created before cutoff return false
HTTP JVM: TWO: Created: 26-02-2020
HTTP JVM: TWO: Cutoff: 22-04-2002
HTTP JVM: 2.1 created after cutoff return true
The problem is that you're comparing text strings, not dates. As such "22...." is earlier alphabetically than "26....". To compare, you either want to get the field value as a Java date and use .before(). This answer covers getting a Java date from a field Set a Java date Object from a Notes DateTime Object. Alternatively, create a Domino DateTime for dtCutOff and use the Domino DateTime's timeDifferenceDouble() method.

Why can't my xpage see the views in another database?

(This is XPages app for a web browser) I created a view panel for a view from another database. The view showed up empty, even though I can clearly see about 15 documents in it using the Notes client. Here is the view source...
<xp:viewPanel rows="30" id="viewPanel3">
<xp:this.facets>
<xp:pager partialRefresh="true"
layout="Previous Group Next" xp:key="headerPager" id="pager2">
</xp:pager>
</xp:this.facets>
<xp:this.data>
<xp:dominoView var="view3"
databaseName="test\Customer\part.nsf"
viewName="LkpMscParts-55EQUIPMENT">
</xp:dominoView>
</xp:this.data>
I verified the ACL, refreshed the view index, even ran fixup on the database. The page still won't show the documents. (No readers fields, btw)
So, then I added a computed field to show me what's going on.
var filepath = database.getFilePath();
var partfile = filepath.toLowerCase().replace("ereq_main","part");
var partdb = session.getDatabase(database.getServer(),partfile,false);
print("partdb views total = "+partdb.getViews().length)
for(x=0;x<partdb.getViews.length;x++){
print("v name = "+partdb.getViews[x].getName());
}
print("partdb server = "+partdb.getServer());
print("partdb title = "+partdb.getTitle())
var vname = "LkpMscParts-55EQUIPMENT";
sessionScope.PartViewName = vname;
var pview = partdb.getView(vname);
if(pview==null){
sessionScope.PartViewError = "Nothing for LkpMscParts-55EQUIPMENT.";
print("pview is null")
return null;
}
var vecol = pview.getAllEntries();
print("partdb = "+partdb.getFilePath());
print("pview = "+pview.getName());
print("pview lines = "+pview.getRowLines());
print("vecol count = "+vecol.getCount())
return vname;
I get this on the console:
11/18/2017 08:10:48 PM HTTP JVM: partdb views total = 0
11/18/2017 08:10:48 PM HTTP JVM: partdb server = CN=domsvr3/O=abc
11/18/2017 08:10:48 PM HTTP JVM: partdb title = Inventory Parts
11/18/2017 08:10:48 PM HTTP JVM: partdb = test\Customer\part.nsf
11/18/2017 08:10:48 PM HTTP JVM: pview =
and then a crash
Script interpreter error, line=27, col=46: [TypeError] Exception occurred calling method NotesView.getRowLines() Notes error: Invalid or nonexistent document (LkpMscParts-55EQUIPMENT)
Notes error: Invalid or nonexistent document (LkpMscParts-55EQUIPMENT)
Notice the length of the views array is 0 - what gives? There's probably 200 views in the db. The database server is correct, the title is correct and the path is correct. So, it appears like it can see the correct database. But, then the view name comes out blank. So, pview isn't null (cuz I checked for that) but yet it's empty. I don't get it. Such a simple thing is driving me nuts.
Just fyi, I've been working with xpages now for about 5 years, so I'm sure I'm missing something simple, but I just can't 'see the forest for the trees'. Can someone please point me in the right direction?
Do you get that database elsewhere in code in the application? If so, are you calling .recycle() on that Database object?
One of the things I learned early in moving to Java is that if you recycle a handle to a Database, getters that return primitive values (e.g. Strings) will still return the value. Presumably it gets cached. So getTitle() and getFilePath() will still return a value, but anything getting something more complex will not work. It's a number of years since I did that, but I think it does just return nothing.
On the rare occasion I still have to recycle (I use ODA for virtually all projects), I only recycle in loops. The maximum handles per session is so high there's no risk of "PANIC: Lookup handles out of range" for the number of Domino objects outside of loops.
If you are using "No Login/Anonymous" for your web access,
then make sure that "Public Access" is setup
in your CustomerParts.nsf -- in all the following ways:
The ACL for the role "Anonymous" allows "Read Public Documents";
Each document on that view has a field "$PublicAccess" set as text "1" ;
That target view is set as "Public Access" -- via security "key" tab, on the view's "property box";
Otherwise instead, if you do login on the web, then --verify all is ok, on that view's security "key" tab.

How to set timeout for NHibernate LINQ statement

I am using Fluent NHibernate for my ORM. In doing so I am trying to use the NHibernate LINQ syntax to fetch a set of data with the power of LINQ. The code I have works and executes correctly with the exception being that a timeout is thrown if it takes longer than roughly 30 seconds to run. The question I have is how do I extend the default 30 second timeout for LINQ statements via NHibernate?
I have already seen the posts here, here, and here but the first two refer to setting the DataContext's Timeout property, which does not apply here, and the third refers to setting the timeout in XML, which also does not apply because I am using Fluent NHibernate to generate the XML on the fly. Not only that but the post is 2 years old and Fluent NHibernate has changed since.
With the ICriteria objects and even HQL I can specify the timeout, however that is not the goal here. I would like to know how to set that same timeout and use LINQ.
Example code:
using (var session = SessionFactory.OpenSession())
using (var transaction = session.BeginTransaction())
{
var query = (from mem in session.Query<Member>()
select mem);
query = query.Where({where statement});
int start = (currentPage - 1) * max);
if (start > 0)
query = query.Skip(start).Take(max);
else
query = query.Take(max);
var list = query.ToList();
transaction.Commit();
return list;
}
This code (where statement does not matter) works for all purposes except where a timeout occurs.
Any help is appreciated. Thanks in advance!
I ended up setting the command timeout for the Configuration for Fluent NHibernate. The downside to this is that it sets the timeout for ALL of my data access calls and not just the one.
Example code:
.ExposeConfiguration(c => c.SetProperty("command_timeout", (TimeSpan.FromMinutes(10).TotalSeconds).ToString()))
I found this suggestion from this website.
Nhibernate has extended the IQueryable and added a few methods https://github.com/nhibernate/nhibernate-core/blob/master/src/NHibernate/Linq/LinqExtensionMethods.cs
var query = (from c in Session.Query<Puppy>()).Timeout(12);
or
var query = (from c in Session.Query<Puppy>());
query.Timeout(456);
I've just spent fair amount of time fighting with this and hopefully this will save someone else some time.
You should use the .Timeout(120) method call at the very last moment to make sure it is used. TBH I'm not 100% sure on why this is but here are some examples:
WILL WORK
query = query.Where(x => x.Id = 123);
var result = query.Timeout(120).ToList();
DOESN'T WORK
query.Timeout(120);
query = query.Where(x => x.Id = 123);
var result = query.ToList();
If done like the second (DOESN'T WORK) example, it seems to fall back to the default System.Transaction.TransactionManager.DefaultTimeout.
Just in case anyone is still looking for this and finds this old thread too...
Query.Timeout is deprecated.
You should use WithOptions instead:
.WithOptions(o => o.SetTimeout(databaseTimeoutInSeconds))

Checking exception before using GETITEMBYID()

I am getting item by getiembyid...but I want to check before using it that whether item exist or not...I don't want to use query as main purpose of using Getitembyid is performance.....any idea how to achieve this...
itemid = Response.QueryString["loc"];
SPList mylist = myweb.GetList(SPUrlUtility.CombineUrl(myweb.ServerRelativeUrl, "/Lists/Location"));
//now id itemid does not exist it throws exception...so i want to check before using following statement that itemid exist...I know i can check throw SPQuery but as i said above because of performance issue only i m using itemid....
SPListItem myitem = mylist.GetItemById(Convert.ToInt32(itemid));
Any idea how to achieve this?
SPQuery by no way will make your code slow, infact every other SharePoint article on net advice you to use SPQuery to gain Performance. To make things still more interesting GetItemByID internally use SPQuery to fetch the Item back to you,following is part of code taken from the GetItemByID function
SPQuery query = new SPQuery();
query.Query = "<Where><Eq><FieldRef Name=\"ID\"></FieldRef><Value Type=\"Integer\">" + id.ToString(CultureInfo.InvariantCulture) + "</Value></Eq></Where>";
Important to note here is SPQuery has a Internal property called SingleItemId which takes Id of the Item Id you want to fetch, further tracing of how it being used cannot be found as the call finally lands up in COM Object.
Being said that you have two options
Option 1:
Wrap your GetItemByID code inside the catch block and check for exception,if one occurs you can mark a flag to denote that Item Id is invalid and take action for it.
Option 2:Use SPQuery, you can test the time difference GetItemByID & using SPQuery, you will see that there is no and just a very very little.

Resources