How do I transfer rich text attachments from one document to another - xpages

I have 2 documents which are supposed work in such a way to be able to transfer files between them. However, it doesn't work. The exception I get is Exception occurred calling method NotesDocument.save() Notes error: One or more of the source document's attachment are missing. Run Fixup to delete the document in the source database. This happens after I try to call save() function on the document to which I've just transfered files from the first.
The functions are below:
function transferFiles(docToGetFrom, docToTransferTo, fileFieldFromFirstName, fileFieldFromSecondName)
{
var rit1:NotesRichTextItem = getFirstNotesRichTextItem(docToGetFrom, fileFieldFromFirstName);
docToTransferTo.copyItem(rit1, fileFieldFromSecondName);
deleteAllFilesFromDocument(docToGetFrom, fileFieldFromFirstName);
docToTransferTo.save();
}
function getFirstNotesRichTextItem(documentToGetFrom, fileFieldName)
{
if (documentToGetFrom == null)
{
return(null);
}
if (!documentToGetFrom.hasItem(fileFieldName))
{
return(null);
}
var rit1:NotesRichTextItem = documentToGetFrom.getFirstItem(fileFieldName);
return rit1;
}
function deleteAllFilesFromDocument(documentToDeleteFrom, fileFieldName)
{
var arr = getAllEmbeddedObjects(documentToDeleteFrom, fileFieldName);
for(var i = 0; i < arr.length; i++)
{
arr[i].remove();
}
documentToDeleteFrom.save();
}
function getAllEmbeddedObjects(documentToGetFrom, fileFieldName)
{
var rit1:NotesRichTextItem = getFirstNotesRichTextItem(documentToGetFrom, fileFieldName);
if (rit1 == null)
{
return(null);
}
try
{
var arr=rit1.getEmbeddedObjects();
return arr;
}
catch(e)
{
return(null);
}
}
According to plain logic, I need to do the following in order to make it work:
Get attachments from the document A
Copy them to the document B
Remove attachments from the document A
Call save() on A
Call save() on B
I did exactly the same, but nevertheless get this nasty exception. Also, I've tried to solve the problem by setting OLEDisableFX to 1, but with no luck. I assume that something must be wrong with the method copyItem() (I guess it can only work properly with simple datatypes). What's the problem? Thanks in advance.

You'll probably need to detach the attachment from the source document and attach it to the target document. See the NotesEmbeddedObject class for examples.

Use the CopyItemToDocument method of the NotesItem class. The following is some code I used in a LotusScript agent but the CopyItemToDocument method is also available in Java and SSJS.
If doc.Hasitem("RTF1") Then
Set item = Nothing
Set item = doc.getFirstItem("RTF1")
Call item.Copyitemtodocument(targetdoc, "targetRTF")
Call item.Remove()
End If

Related

Unable to Invoke Inserted Javascript Fragment

I am attempting to insert a Javascript fragment into a webpage and then invoke it using blue prism. The purpose of this is to analyse what elements are returned from a search to determine where to go next in the overall process flow.
I have tested the Javascript code on the intended website using the IE 11 developer console and it works without issue. The code is below in case it is useful.
function includes(stringToCheck, CharacterToSearchFor)
{
var found = new Boolean();
var splitString = stringToCheck.split("");
for (var index = 0; index < splitString.length; index++)
{
if(splitString[index] == CharacterToSearchFor)
{
return true;
}
}
return false;
}
function getPartners() //declare a function which can be called from BP. once called all code within the enclosing {} will be run
{
var searchResults = document.getElementsByClassName("findASolicitorListItem"); //search the web page for all elements with a specific tag and store them in a variable called searchResults.
if(searchResults.length == 0) // If the number
{
alert( "No Solicitors were found.");
}else if(searchResults.length == 1)
{
var innerSearchResults = searchResults[0].getElementsByTagName("span");
for(i = 0; i < innerSearchResults.length; i++)
{
var spanText = innerSearchResults[i].innerText.toString();
if((spanText != ""))
{
if(!includes(spanText, "|"))
{
alert("One Solicitor found. " + spanText);
}
}
}
}else if (searchResults.length > 1)
{
alert( "More than one solicitor was found. Manual Checking required.");
}
}
This is stored in a data item and is passed into the Navigate stage (Insert Javascript Fragment) parameter.
PrintScreen of Insert Javascript Fragment stage
When this stage is ran it successfully injects the Javascript functions into the webpage.
I then try and invoke this inserted javascript fragment
Printscreen of Invoke Javascript Function stage
When this stage runs I get the following error message raised by Blue Prism.
Internal : Failed to perform step 1 in Navigate Stage 'Analyse Result' on page 'Analyse Search Results' - Failed while invoking javascript method:Exception from HRESULT: 0x80020101-> at mshtml.HTMLWindow2Class.IHTMLWindow2_execScript(String code, String language)
at BluePrism.ApplicationManager.HTML.clsHTMLDocument.InvokeJavascriptMethod(String methodname, String jsonargs, Object& retval, String& sErr)
I have searched for this error code and found this answer which indicates there is a problem with the code however I can manually run this code just fine.
Does anyone have any experience with using these methods in BluePrism or have seen this error message before who can help me resolve?
I was actually never able to get Invoke Function working reliably with parameters, I always use Insert Fragment for everything, invoking included.
If you insert this function as a fragment...
function sayHello(name)
{
alert("Hello " + name + "!");
}
...to invoke it you just insert this as another fragment:
sayHello("World");
Tadaa!
As a side note, I am not sure which element in Application Modeler you are using for fragment insertion, but it seems like you are using the root (application) node. I had better experience with inserting the fragment to a dedicated HTML BODY element, for some reason the performance is much greater.
To invoke function by action "Invoke Javascript function", in Arguments field you should put arguments in JSON syntax. If there is no argument you put "[{}]".
On the above Marek's example the function should look:
function sayHello(name)
{
alert("Hello " + name.name + "!");
}
and arguments: "[{'name':'world'}]".

Notes error: Entry not found in index when reading view entries

My xPage SSJS fails in line:
viewEntry = view.getNext(viewEntry);
with error
Notes error: Entry not found in index
I do have this options set to false but it doesn't help:
view.setAutoUpdate(false);
So I suspect that it fails because user has not access to NEXT document because of reader access set. So such document cannot be seen in the view but in TOTALS. How to fix it?
The side problem is that if crashes Domino server then
Here is my code:
var view:NotesView = database.getView("xxxxxxx");
view.setAutoUpdate(false);
var viewNav:NotesViewNavigator = view.createViewNav();
var viewEntry:NotesViewEntry = viewNav.getFirst();
while (viewEntry != null) {
if (viewEntry.isCategory()){
// I work with category entry data
} else if(viewEntry.isTotal()){
// I collect totals
} else {
// I work with view entry
}
var tmpEntry:NotesViewEntry = viewNav.getNext(viewEntry);
viewEntry.recycle();
viewEntry = tmpEntry;
}
It fails in line: viewNav.getNext(viewEntry)
Script interpreter error, line=1001, col=37: [TypeError] Exception occurred calling method NotesViewNavigator.getNext(lotus.domino.local.ViewEntry)
Notes error: Entry not found in index ((xxxxxxx))
tmpEntry:NotesViewEntry = viewNav.getNext(viewEntry);
So how do I really go to next entry if current or next one is invalid?
It may also be worth verifying which entry is not found in index. It could be the first, depending on the context of your code. For example, it might have been updated to take it out of the view. Check for null first. Reader access may also be an issue, if you're working from a ViewNavigator, there are different reasons for access. Use a try/catch to also verify your hypothesis - sessionAsSigner (or ODA's native session) will have access to the next document, which will allow logging to confirm. Once you can confirm the cause, you can code around it.
ViewEntry.isValid() verifies if a soft deletion or user does not have access, as stated in documentation for ViewEntry and Document, which both have the same method.
Use the view navigator. If the user can not access the entry then a simple check with viewentry.getUniversalId() will return null, and so you could even skip it inside the view entries iteration.
Try this code instead:
view.setAutoUpdate(false);
ViewNavigator nav = view.createViewNav();
ViewEntry entry = nav.getCurrent();
ViewEntry nextEntry = null;
while (entry != null) {
if (entry.isCategory()) {
nextEntry = nav.getNextSibling();
} else {
nextEntry = nav.getNext();
}
if (!entry.isTotal()) {
// do something with the entry
} else {
// skipped entry
}
//don't forget to recycle!
entry.recycle();
entry = nextEntry;
}
view.setAutoUpdate(true);
Combine Ferry's answer (get next entry in advance) with the check of validity of the entry: https://www.ibm.com/support/knowledgecenter/en/SSVRGU_9.0.1/basic/H_ISVALID_PROPERTY_2176.html
That should avoid your problem. Also, use try/catch block to identify what is the last processed document and take a closer look at it (and the next one). It may be corrupted.

How to prevent global event handlers from firing caused by an API call

I have a custom module that uses Kentico API (DocumentHelper) to update certain fields of my document and then publish but I do not want it to trigger the event handlers that are linked to my document page type. I tried adding comments to .Publish("admin_edit") hoping that I can catch it from the WorkflowEventargs parameter but the VersionComment property always return null. Is there a way to accomplish this in Kentico?
update field:
var document = DocumentHelper.GetDocument(documentID, tree);
var workflowManager = WorkflowManager.GetInstance(tree);
var workflow = workflowManager.GetNodeWorkflow(document);
if (workflow != null)
{
document.CheckOut();
document.SetValue("SomeFIeld", "some value");
document.Update(true);
document.CheckIn();
document.Publish("admin_edit");
}
event handler:
public override void Init()
{
WorkflowEvents.Publish.After += Publish_After;
}
private void Publish_After(object sender, WorkflowEventArgs e)
{
if (!string.IsNullOrEmpty(e.VersionComment) &&
e.VersionComment.Contains("admin_edit"))
return;
}
You always get null for Version information, because that is related to the 'Page versioning' events, specially for 'SaveVersion'. You can find more about that on this link. If you expand 'Properties' you will see which properties are populated for the specific event. In your case, you can try something like this, to add your message for last version and then check for that comment on 'Publish_After' event, see code bellow:
var document = DocumentHelper.GetDocument(documentID, tree);
var workflowManager = WorkflowManager.GetInstance(tree);
var workflow = workflowManager.GetNodeWorkflow(document);
if (workflow != null)
{
document.CheckOut();
document.SetValue("SomeFIeld", "some value");
document.Update(true);
document.CheckIn(versionComment: "admin_edit");
document.Publish();
}
and then, in event handler, take last version and check for comment like this:
if (e.PublishedDocument?.VersionHistory?.Count > 0)
{
var lastVersion = e.PublishedDocument.VersionHistory[0] as VersionHistoryInfo;
if (lastVersion.VersionComment.Equals("admin_edit"))
{
return;
}
}
NOTE: In case that you have a lot of concurrent content editors, there is a chance that your last version is not version from API (someone changed content and saved it right after your API call made change). There is a low chance for that, but still is possible. If this is something that you will use often, you must take it in consideration. This code is tested for Kentico 11.

The file has been modifed by error in sharepoint

I am getting the following error when i try to update the item's name in the sharepoint
document library. The item is of type document set and its default values is loaded using javascript. In the Item added event we are updating with the new changed item's name value. But in the item.update() code statement i am getting following error.
The File CZY14389 has been modified by domain\username on current date.
Please provide your commens on resolving this.
You cannot change the name of a sharepoint document like that. You need to "move it".
Item.Update();
Item.File.MoveTo(Item.ParentList.RootFolder.Url + "/" + newFileName, false);
Item.File.Item["FileRef"] = newFileName;
Item.File.Update();
before you update item name and call item.update(), can you try to refresh your item like this:
item = item.ParentList.GetItemById(item.ID);
item.name = "xyz";
item.update();
this can sometimes happen in event handler. the problem is the updation process in the event handler is not as the same of the workflow. In event handler for updating you have to use the followitn steps. Dont use Item.Update() as in workflow.
Follow the steps:
• call and disable event firing before your code with : base.EventFiringEnabled = false;
•update your item by calling item.systemUpdate(false);
•enable event firing with : base.EventFiringEnabled = true;
Disable event firing and call your update code, dont forget to enable event firing.
HandleEventFiring handleEventFiring = new HandleEventFiring();
handleEventFiring.DisableHandleEventFiring();
try
{
item.Update();
//if item.Update doesnt work then use(For me item.update worked only on my local not on prod then i used the below)
//item.SystemUpdate(false)
}
finally
{
handleEventFiring.EnableHandleEventFiring();
}
public class HandleEventFiring : SPItemEventReceiver
{
public void DisableHandleEventFiring()
{
//obsolete
//this.DisableEventFiring();
this.EventFiringEnabled = false;
}
public void EnableHandleEventFiring()
{
//obsotete
//this.EnableEventFiring();
this.EventFiringEnabled = true;
}
}

What is the best way to recycle Domino objects in Java Beans

I use a function to get access to a configuration document:
private Document lookupDoc(String key1) {
try {
Session sess = ExtLibUtil.getCurrentSession();
Database wDb = sess.getDatabase(sess.getServerName(), this.dbname1);
View wView = wDb.getView(this.viewname1);
Document wDoc = wView.getDocumentByKey(key1, true);
this.debug("Got a doc for key: [" + key1 + "]");
return wDoc;
} catch (NotesException ne) {
if (this.DispLookupErrors)
ne.printStackTrace();
this.lastErrorMsg = ne.text;
this.debug(this.lastErrorMsg, "error");
}
return null;
}
In another method I use this function to get the document:
Document wDoc = this.lookupDoc(key1);
if (wdoc != null) {
// do things with the document
wdoc.recycle();
}
Should I be recycling the Database and View objects when I recycle the Document object? Or should those be recycled before the function returns the Document?
The best practice is to recycle all Domino objects during the scope within which they are created. However, recycling any object automatically recycles all objects "beneath" it. Hence, in your example method, you can't recycle wDb, because that would cause wDoc to be recycled as well, so you'd be returning a recycled Document handle.
So if you want to make sure that you're not leaking memory, it's best to recycle objects in reverse order (e.g., document first, then view, then database). This tends to require structuring your methods such that you do whatever you need to/with a Domino object inside whatever method obtains the handle on it.
For instance, I'm assuming the reason you defined a method to get a configuration document is so that you can pull the value of configuration settings from it. So, instead of a method to return the document, perhaps it would be better to define a method to return an item value:
private Object lookupItemValue(String configKey, itemName) {
Object result = null;
Database wDb = null;
View wView = null;
Document wDoc = null;
try {
Session sess = ExtLibUtil.getCurrentSession();
wDb = sess.getDatabase(sess.getServerName(), this.dbname1);
wView = wDb.getView(this.viewname1);
wDoc = wView.getDocumentByKey(configKey, true);
this.debug("Got a doc for key: [" + configKey + "]");
result = wDoc.getItemValue(itemName);
} catch (NotesException ne) {
if (this.DispLookupErrors)
ne.printStackTrace();
this.lastErrorMsg = ne.text;
this.debug(this.lastErrorMsg, "error");
} finally {
incinerate(wDoc, wView, wDb);
}
return result;
}
There are a few things about the above that merit an explanation:
Normally in Java, we declare variables at first use, not Table of Contents style. But with Domino objects, it's best to revert to TOC so that, whether or not an exception was thrown, we can try to recycle them when we're done... hence the use of finally.
The return Object (which should be an item value, not the document itself) is also declared in the TOC, so we can return that Object at the end of the method - again, whether or not an exception was encountered (if there was an exception, presumably it will still be null).
This example calls a utility method that allows us to pass all Domino objects to a single method call for recycling.
Here's the code of that utility method:
private void incinerate(Object... dominoObjects) {
for (Object dominoObject : dominoObjects) {
if (null != dominoObject) {
if (dominoObject instanceof Base) {
try {
((Base)dominoObject).recycle();
} catch (NotesException recycleSucks) {
// optionally log exception
}
}
}
}
}
It's private, as I'm assuming you'll just define it in the same bean, but lately I tend to define this as a public static method of a Util class, allowing me to follow this same pattern from pretty much anywhere.
One final note: if you'll be retrieving numerous item values from a config document, obviously it would be expensive to establish a new database, view, and document handle for every item value you want to return. So I'd recommend overriding this method to accept a List<String> (or String[ ]) of item names and return a Map<String, Object> of the resulting values. That way you can establish a single handle for the database, view, and document, retrieve all the values you need, then recycle the Domino objects before actually making use of the item values returned.
Here's an idea i'm experimenting with. Tim's answer is excellent however for me I really needed the document for other purposes so I've tried this..
Document doc = null;
View view = null;
try {
Database database = ExtLibUtil.getCurrentSessionAsSigner().getCurrentDatabase();
view = database.getView("LocationsByLocationCode");
doc = view.getDocumentByKey(code, true);
//need to get this via the db object directly so we can safely recycle the view
String id = doc.getNoteID();
doc.recycle();
doc = database.getDocumentByID(id);
} catch (Exception e) {
log.error(e);
} finally {
JSFUtils.incinerate(view);
}
return doc;
You then have to make sure you're recycling the doc object safely in whatever method calls this one
I have some temporary documents which exist for a while as config docs then no longer needed, so get deleted. This is kind of enforced by an existing Notes client application: they have to exist to keep that happy.
I've written a class which has a HashMap of Java Date, String and Doubles with the item name as the key. So now I have a serializable representation of the document, plus the original doc noteID so that it can be found quickly and amended/deleted when it's not needed anymore.
That means the config doc can be collected, a standard routine creates a map of all items for the Java representation, taking into account the item type. The doc object can then be recycled right away.
The return object is the Java class representation of the document, with getValue(String name) and setValue(String name, val) where val can be Double String or Date. NB: this structure has no need for rich text or attachments, so it's kept to simple field values.
It works well although if the config doc has lots of Items, it could mean holding a lot of info in memory unnecessarily. Not so in my particular case though.
The point is: the Java object is now serializable so it can remain in memory, and as Tim's brilliant reply suggests, the document can be recycled right away.

Resources