Lapas

Monday 27 September 2010

Using XPAGES to pass back binary data

So I would like to write a little article about passing custom generated responses using XPAGES and how to use the for binary output.

Main problem in Lotus Domino editions till 8.5 is that we could not pass back binary data directly to WEB browser (using agent print/printing in form and so on). The only ways were, to create document, attach file, redirect attachment link or create file in shared web dir and pass direct link to user.

Now, when XPAGES with JSF technology comes to place it is possible to do so. Let's get it "how?".

At first You need to create and xpage element and set it render property to false - so nothing to be rendered.
Then You should choose xpage event, where to write the right code, which will serve data. So xpage has following events "beforePageLoad","afterPageLoad","beforeRenderResponse","afterRenderResponse","afterRestoreView". Most interesting for our example are "beforeRenderResponse","afterRenderResponse".

Classes to work with:
1. javax.faces.context.ExternalContext - needed to get response object
2. com.ibm.xsp.webapp.XspHttpServletResponse - needed to get outputstream
3. javax.servlet.ServletOutputStream - stream, where we will write data

XPAGE options to be set:
CreateForm = false
rendered = false

//CODE BLOCK START FOR this example it should be put in beforeRenderResponse
//Initialization
var extContext:javax.faces.context.ExternalContext = facesContext.getexgetExternalContext();
var response:com.ibm.xsp.webapp.XspHttpServletResponse = exCon.getResponse();
var writer:javax.servlet.ServletOutputStream = response.getOutputStream();

//setting response headers for browser to recognize data
response.setContentType("image/gif"); // content type of data, for other binary see mime types or put "application/octet-stream"
response.setHeader("Cache-Control", "no-cache");
response.setHeader("Content-Disposition" ,"attachment; filename=image.gif"); //required if You need to pass it back as file

//convert sample hex to character codes.
var decoded=imgHex.split(" ");
var result=new Array();
for (var i=0;i<decoded.length;i++)
{
result[i]=(parseInt(decoded[i],16))
}
// result is an array containing character codes (byte array). How to prepare for other situations an array You should decide at Your own.
writer.write(result); // just write prepared array to output

//when all output is done. just tell that You have completed the response and close stream
facesContext.responseComplete();
writer.close();




//Sample data
//domino image in hex should move to initialization section
imgHex= "47 49 46 38 39 61 15 00 15 00 e6 00 00 00 00 00 ff ff ff ce 64 66 c9 65 67 e2 7b 7c ff 98 99 ce 65 69 a4 9f a3 68 66 69 a0 b5 ca c8 cc cd c9 cb ca 65 65 63 67 67 65 e5 e5 e2 ca ca c8 c7 c7 c5 f0 f0 ef eb eb ea a8 a8 a7 fe f3 8a fe f2 8c ff f2 8c fe f1 8b d6 d4 d0 ec eb e9 ea e9 e7 c8 c7 c5 f3 ac 38 f2 ad 38 f2 ac 3a f2 ac 3d fc be 59 fc be 5b f9 be 5a d0 cd c8 f4 ab 3a fc bc 5b fc bd 5c fb bc 5d e8 e6 e3 de dc d9 d7 d4 d0 d0 cd c9 d1 cd c8 c0 be bc ee ed ec 81 5b 48 84 59 46 82 5b 4a b7 83 6d b6 82 6d 83 59 49 84 5a 4a b8 81 6d 87 59 4a 84 5a 4c b8 7f 6c ba 7f 6d cd 65 64 cd 67 65 ca 66 66 ff 9a 98 ff 9b 99 fc fc fc ca ca ca ad ad ad ac ac ac 88 88 88 67 67 67 66 66 66 ff ff ff 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 21 f9 04 01 00 00 47 00 2c 00 00 00 00 15 00 15 00 00 07 9e 80 47 82 83 84 85 86 87 88 89 8a 8b 8c 06 03 8c 86 3d 3e 3f 3c 86 42 97 98 99 39 45 05 04 3b 0a 85 42 40 a3 a4 a3 36 14 43 46 02 0f 96 40 11 af b0 3a 14 20 24 30 46 0b ad b0 af 32 14 27 1d 31 07 46 41 ad 19 13 44 12 33 17 25 1c 37 13 18 c2 ad 0e 10 1b 3a 16 26 1f 38 13 0b 40 d0 a1 40 2e 13 33 15 22 1c 2f da 29 dc c3 de 1a 2d 08 21 1e 34 13 2a 18 18 e9 c4 28 0d 0c 35 13 09 2c 2b 23 f6 bc 95 1a 38 aa 1b 21 23 08 13 2a 54 a8 ae 50 90 87 10 23 46 84 44 b1 a2 c5 8b 8c 02 01 00 3b"

REFERENCES, where You could check how to output STRING data.
http://www.wissel.net/blog/d6plinks/SHWL-7MGFBN
http://www-10.lotus.com/ldd/nd85forum.nsf/7756aedc25e6d81285256324005ac76c/1e87cc63dfcf9412852575ae0005079f?OpenDocument - getResponseStream problem.

Saturday 10 April 2010

Embedded view from External DB

1. Standard method replacing replicaid in form using dxl.
2. Advanced method using forms, frameset and embedded editor.

You have 2 databases, form1 (db1) and view1 (db2) which should be displayed. How to make it without composite applications and DXL tools?

Simply:
1. Create frameset1 in db1 with 2 frames, Top and Bottom. Make top frame 1 Pixel height.
2. Create empty form in db1, let's call it Form1Embed, set it to open in frameset1 Top frame.
3. Create form or page in db2, let's call it Form2Embed. Add embedded view1 to this form. Then add simple code in postopen:
dim ws as new notesuiworkspace
dim uidoc as notesuidocument
set uidoc=ws.currentdocument
'// fill some field with universal id of uidoc.document, then set single category from this field for view1
What this code do? the uidoc we get is parent document (form1).

3. Embed Form1Embed in form1 through embedded editor.
4. In frameset1 Bottom frame compute db2 path and our created element Form2Embed (that Form2Embed could be opened)

Now what happeps when you open form1?
1. form1 is loaded
2. Embedded editor loaded for form1embed, lotus opens this form in frameset1 top frame, which is not visible. Then computes Bottom frame with our form2embed from db2, which on load gets form1 document and uses it unid as single category key.


Lotusscript External DB calls

Sometimes, it is required to call functions from other related databases and then we have to choose copy libraries or try to call using parameters, so that libraries stay where they belongs to.
At least we have 3 possible call methods to access script from other LN database.

1. Calling agent with parameterDocID, where we could pass 4 bytes of data (note id length FFFFFFFF). Traditionally noteid of profile document or other document to process is passed. For more flexibility, can be used bit masks if agent works with UI and has access to context.
Main problem for this method is restrictions to passing parameters and for agents contains large libraries each time agent is loaded, script libraries are loaded again and again and no cached.

2. Second method is calling dialogbox in external database, by creating document in that db. Then we can pass parameters with no document save, use lotus cache (form cached) and access external lotusscript. Main problem is to reopen document after processing if needed, so that code should be splitted in 2 dbs, call function will handle return data and that we normally could not hide that dialog if we need if we want to return data.

3. Third method is the trick (some type of mutation of second method) found is that each document or UI element where external functions could be called is enclosed in frameset with 1 pixel top frame. Then form should be setup to open in frameset second frame. Visually document opens as always and no UI difference visible. Top frame is used to call external function. How to call?
3.1 simple method. In external database create form, create postopen event on event start you can access context uidoc using notesuiworkspace.currentdocument. No params could be passed in this case. But this method uses cash, which creatly boosts performance. To call form use notesuiworkspace.composedocument in top frame.
3.2 advanced method. use the same technique as in 3.1, but no post open event, only cache source document, for later use. Then create on form at least 2 editable fields: 1 for initial focus, 2 for function call. in second field write code in entering event. How to pass parameters? in caller db script. like in 3.1, but now we need returning uidoc to pass parameters. Use like set uidoc=ws.composedocument. Now use uidoc.document to write parameters in opened doc, after finished, just use uidoc.GotoField(second field). This method is like calling extenal agents with more flexibility and cache.

Wednesday 10 February 2010

Signing LN databases with Server ID with lotusscript

Sometimes for automation tasks developers need to perform design signing with server ID.
Lotus design updates consists of design refresh and aftersigning. Refresh could be automated by using Lotus C API (write request if you need info) and no Server signing functionality.
We have only some methods to do signing:
1. NotesDatabase.sign(), which works with only user ID and on UI
2. NotesDocument.sign() if we are getting design element by noteID or through NotesNoteCollection. It could be signed, but lastmodifed username will remain the same, so you will need to call signing agent twice 1. sign signing agent, 2. sign other database
3. Using admin requests database. This method is better if admin has normal access rights to perform such operations. You'll simply need to create admin request document for each database to be signed, then wait till things is done.
4. Sign using NotesAdministrationProcess class.
Here is sample code for request document. Check "admin4.nsf" on server, where to create request:


Dim sName As Variant
Dim uname As string
sName=Evaluate("@servername",reqDoc) 'db.server does not returns full server name
uname=s.Effectiveusername
With reqDoc
.Form="AdminRequest"
.FullName=uname
.ProxyAction="101" '// action for request - type of request. see admin4.nsf for more info in ProxyAction shared field
.ProxyAuthor=uname
.ProxyDatabasePath=signDB.FilePath
.ProxyNameList=signDB.Title
.ProxyOriginatingAuthor=s.effectiveusername
.ProxyOriginatingOrganization=nname.Organization
.ProxyOriginatingRequestUNID=reqDoc.universalid
.ProxyOriginatingTimeDate=Now
.ProxyProcess="Adminp"
.ProxyServer=sName(0)
.ProxyTextItem1="0"
.Type="AdminRequest"
Call .Replaceitemvalue("$OnBehalfOf", uname)
ForAll cItem In reqDoc.items
cItem.isSigned=True
End ForAll
Call .Sign()
Call .Save(True,True)
End With