This is a rather lengthy Friday post for those of you who have tried being haunted by a programming problem you simply couldn’t let go. I guess those of you into Java and Notes development will probably appreciate it as well… 🙂
It all started Thursday night when I read a post called I need some help, PLEASE over at Jamie Prices blog. Jamie needed some help to extract the bytes from an image resource in a Notes database using Java. Being the nice guy I am, the fact that it sounded pretty nerdy (direct hit again) and because I didn’t think it would be that difficult I started messing around with it.
Easy? Well no, but I cracked it!
Thursday
I immediately thought “DXL” and started up Eclipse. I guessed that the image resource would probably be base64 encoded and thought it a small matter to solve Jamies problem. I imagined 3 easy steps:
- Get the DXL for the image resource,
- Extract the base64 encoded image resource,
- Decode the base64 string to a byte array
and I would be laughing… Well not so easy.
I pretty quickly got at the base64 encoded image resource but whatever I did I couldn’t get the image properly converted. I thought this was simply my tired fingers so I posted my idea and some code in a comment on Jamies blog and went to bed.
As I read your post you need some way to get a byte array of an image resource in the current database - right? You could use DXL to get at the base64 encoded image and then decode it into a byte array (here I find an image resource called "location.gif"):
Database db = ac.getCurrentDatabase();
NoteCollection nc = db.createNoteCollection(false);
nc.setSelectImageResources(true);
nc.buildCollection();
String noteid = nc.getFirstNoteID();
String noteid2 = null;
while (noteid.length() > 0) {
noteid2 = nc.getNextNoteID(noteid);
Document doc = db.getDocumentByID(noteid);
if (!doc.getItemValue("$TITLE").elementAt(0).equals("location.gif")) {
nc.remove(doc);
}
noteid = noteid2;
}
DxlExporter exporter = session.createDxlExporter();
exporter.setForceNoteFormat(true);
String dxl = exporter.exportDxl(nc);
If you look at the generated DXL a base64 encoded image will appear there. I didn't go further since I wanted to make sure that this is actually what you want to achieve.
<snip total_wrong="true">
I did however find out that I was unable to export design elements to DXL using Java from a local database under Notes 7.0.1 - I had to do it on a server...
</snip>
/lekkim
Friday
Today, Friday, I received an e-mail from Jamie thanking me for looking into the issue. In hindsight I know he just smelled the blood… At that point however the e-mail just reminded me that I hadn’t solved the problem which really bothered me. I did some research and found some evidence that something might be up with the base64 encoded image resource. A post I found on LDD pointed towards the fact that the base64 might contain some header and/or control information as well. Venturing further down this avenue and by comparing the bytes of a decoded image resource with the bytes of the same image not as an image resource revealed the secret.
The structure of an image resource
It turns out that an image resource has 66 bytes of header information followed by 10240 bytes of image data, 10 bytes of “control information”, 10240 bytes of image data, 10 bytes of “control information” and so on… I felt I was on to something.
I extracted the base64 encoded image resource from the DXL using cut-and-paste and wrote some code to process the image resource bytes as per the above pattern an voilá! One perfect, viewable, valid image on my harddrive.
public byte[] getImageBytes() throws NotesException {
// get base64 encoded image
String base64 = this.getBase64();
// convert the base64 to byte array
// using the Base64 encoder/decorder by
// Robert Harder from http://iharder.net/base64
byte[] b_source = Base64.decode(base64);
// remove header and control information
ArrayList list = new ArrayList();
int j=0;
int k=0;
int i=0;
for (i=0; i<b_source.length; i++) {
if (i<66) {
// ignore (some kind of header)
} else if (j < 10240) {
list.add(new Byte(b_source[i]));
j++;
} else if (k<9){
k++;
} else {
j=0;
k=0;
}
}
byte[] result = new byte[list.size()];
i=0;
for (Iterator ite=list.iterator(); ite.hasNext(); i++) {
result[i] = ((Byte)ite.next()).byteValue();
}
// return result
return result;
}
This was all well and good. The extracted bytes was easily written to disk using a java.io.FileOutputStream:
// write to a file on the file system
FileOutputStream out = new FileOutputStream("c:\image_resource.gif");
out.write(my_acquired_image_bytes);
out.flush();
out.close();
Get at the base64 data from Java
Now came the time to get at the base64 data of any image resource. I chose to use a regular expression for the job.
A thing to bear in mind is that the base64 data of an image resource in DXL is sometimes split over two “rawdataitems”. This seems to be filesize related since an image resource of 17 kb is in one item and one of 35 kb is in two items (probably our dear 32kb limit)
public String getBase64() throws NotesException {
// build a note collection of image resources
NoteCollection nc = this.pDb.createNoteCollection(false);
nc.setSelectImageResources(true);
nc.buildCollection();
String noteid = nc.getFirstNoteID();
String noteid2 = null;
while (noteid.length() > 0) {
noteid2 = nc.getNextNoteID(noteid);
Document doc = this.pDb.getDocumentByID(noteid);
if (!doc.getItemValue("$TITLE").elementAt(0).equals(this.pName)) {
// not the one we are looking for
nc.remove(doc);
}
noteid = noteid2;
}
// make sure we have at least one image resource
if (nc.getCount() != 1) {
throw new RuntimeException("Unable to find the requested image resource");
}
// export our collection if the found image resource as DXL
DxlExporter exporter = this.pSession.createDxlExporter();
exporter.setForceNoteFormat(true);
String dxl = exporter.exportDxl(nc);
// use regexp to get the base64 encoded image resource
Pattern p = Pattern.compile("<rawitemdata type='1'>([a-zA-Z0-9/=+\s]*)</rawitemdata>");
Matcher m = p.matcher(dxl);
StringBuffer buf = new StringBuffer();
while (m.find()) {
buf.append(m.group(1));
}
// return
return buf.toString();
}
Please note: Since I used the java.util.regex package it means that the code requires Notes 7.x to run since the package was added in Java 1.4.x. It should however be fairly easy to get the base64 using string operations as well if you’re pre-Notes 7.
Conclusion
Well it turned out not to be as easy as first expected but at least I have peace of mind now that I cracked it. I sent the code to Jamie and he’s trying it out as well so we’ll see how it goes. For those who are interested to see the complete code I have put together an example database available for download.
I hope Jamie can use the code. Happy Friday!