Wednesday, October 17, 2012

xPage: Optimize Uploaded Image

I had a task to upload images in xPage. The next one is to optimize image lossless. User upload the image in to the Domino media library and the image optimizing on the fly.

Unfortunately there is no good Java solution for lossless optimization of images. There are set of solution to use third party command line programs and run it from java. The problem is that you need to install this program on the server, many of them supports only one OS, so if you have Domino server on the windows and then would move to the Linux (or vice verse) you got a problem.

I decide to use online optimizer. It looks like there is no good paid online services with a good API. So I start from the free one. It is well known Yahoo! Smush.it.

Smush.it has no API, but there is a guy who create stand alone java API for Smush.it service. He did it in the way of command line tool, but it could be used as API in your application.

I like it! The only one problem is that program out process information (log) directly in to the System.out. I need some information to be printed in to my log.nsf and some information to be returned to the user. So I add ability to put StringBuilder object in to the Smush.it API.  

Code example:

//log and statistic contains information 
//about process and compression result 
// 
//file - file to optimize, 
//generally opt rewrite file, but not in all cases. 
//Look "Important" section 
// 
//opt - optimized file

StringBuilder log = new StringBuilder();
StringBuilder statistic = new StringBuilder();

SmushIt smushIt = new SmushIt();
smushIt.setVerbose(log);
smushIt.setVerboseStatistic(statistic);     
smushIt.addFile(file.getCanonicalPath());
List<smushitresultvo> smushItResultVos = smushIt.smush();

ImageDownloader imageDownloader = 
 new ImageDownloader(file.getParent());
imageDownloader.setVerbose(log);

File opt = imageDownloader.download(smushItResultVos.get(0));      

StringBuilder log variable after running code:

Smushing files:

C:\Windows\Temp\notesE3A053\xspupload\0001.jpg

Adding file:C:\Windows\Temp\notesE3A053\xspupload\0001.jpg
{"src":"0b2c68eb%2F0001.jpg","src_size":40895,
"dest":"http:\/\/ysmushit.zenfs.com\
/results\/0b2c68eb%2Fsmush%2F0001.jpg",
"dest_size":38749,
"percent":"5.25","id":""}

Downloaded smushed image - 
http://ysmushit.zenfs.com/results/0b2c68eb%2Fsmush%2F0001.jpg
Saved image - C:\Windows\TEMP\notesE3A053\xspupload\0001.jpg

StringBuilder statistic variable after running code:

Source Image:0001.jpg
Source image size:40895
Smushed image size:38749
Percentage saving:5.25

Projects on github:
https://github.com/andriykuba/smushit
https://github.com/abhirama/smushit

Thursday, October 11, 2012

Custom File Upload in the xPage. Java. Backend.

There are a lot of articles about custom file upload with xPages. Most of them are about JavaScript. They use Java classes, so convert that code in to the Java is not problematic.

It's very easy to do in backend java code within xPages. The only one miss is that we could not write to the file attachment directly from the request. We still need to use a file. Thanks to xPage - it handle server temp files himself.

The code:

public void process() throws Exception {
 HttpServletRequest request = getRequest();
 
 Document document =
  getEs().createDocument().getDocument();
 document.replaceItemValue("Form", "picture");

 Map parameters = request.getParameterMap();
 UploadedFile uploadedFile=
  (UploadedFile) parameters.get("upload");
 document.replaceItemValue(
  "FileName",
  uploadedFile.getClientFileName());
 
 File tmpFile = uploadedFile.getServerFile();
 File fileToUpload = new File(
   tmpFile.getParentFile().getAbsolutePath().
   concat(java.io.File.separator).
   concat(uploadedFile.getClientFileName())); 
 
 boolean success = tmpFile.renameTo(fileToUpload); 
 addAttachment(document, success?fileToUpload:tmpFile);
 fileToUpload.renameTo(tmpFile);
 
 document.save(true, true);
}

private void addAttachment(Document document, File file)
 throws Exception{
 RichTextItem item = document.createRichTextItem("Body");
 item.embedObject(
  EmbeddedObject.EMBED_ATTACHMENT,
  "", 
  file.getCanonicalPath(), null);   
}

Look http://lotusandjava.blogspot.com/2012/09/accessing-xpages-global-objects-in-java.html to find how to get request

Saturday, October 6, 2012

It looks like Planet Lotus users are more familar with programming languages than with natural.

True code header have better rate:



CKFinder for Domino. Downloading

CKFinder have two options to download files (images) - by request to the server "connector" command processor and by direct downloading.

Request to the "connector" ("ckfinder-connector-url\connector.xsp?command=Download...") is not accessible for Domino.

For direct download CKFinder use file(image) link with the "?download" command. Let's modify this way for allowing CKFinder to "download" files from the Domino server.

Browser would download file in the case if "content-disposition" header available (and filled). We need to have ability to open file (no "content-disposition" header) and to download file ("content-disposition" header present). Let assume we have some database with web view "web-files" for open files on the web. Then we will need another one, completely the same, view "web-download" for downloading files and HTTP response headers rule for  adding "content-disposition" header.

HTTP response headers rule:
Incoming URL pattern: */web-download/*
Custom headers:
            Name: content-disposition 
            Value: attachment

Now, when you will open file "web-files/companylogo.jpg", then browser will show it, and when you open file "web-files/companylogo.jpg", then browser will ask to save it.

Domino configured. Now we need to modify ckfinder.js to allow it to load files from another url. I am using "url" attribute of "ResourceType" tag in the response of "Init" command to put files url. So I  add "download" attribute to the "ResourceType" tag to put files download url.

ckfinder.js with modification that supports "download" attribute: 

p.resourceTypes.push(new a.aL.ResourceType(p, {
 name: t.getNamedItem('name').value,
 url: t.getNamedItem('url').value,
 
 //Modification
 download: t.getNamedItem('download').value,
 
 hasChildren: t.getNamedItem('hasChildren').value,
 allowedExtensions: 
  t.getNamedItem('allowedExtensions').value,
 deniedExtensions: 
  t.getNamedItem('deniedExtensions').value,
 acl: t.getNamedItem('acl').value,
 hash: t.getNamedItem('hash').value,
 maxSize: t.getNamedItem('maxSize').value
}));

And

a.aL.ResourceType = function (q, r) {
 var s = this;
 s.app = q;
 s.name = r.name;
 s.url = r.url;
 
 //Modification
 s.download = r.download;
 
 s.hasChildren = r.hasChildren === 'true';
 s.defaultView = 'Thumbnails';
 s.allowedExtensions = r.allowedExtensions;
 s.deniedExtensions = r.deniedExtensions;
 s.oT = p(r.allowedExtensions);
 s.ms = p(r.deniedExtensions);
 s.nS = r.acl;
 s.hash = r.hash;
 s.maxSize = r.maxSize;
};

Now we need to change generation of download url in he CKFinder:

if (R.config.directDownload){
 //Modification
 var filename = encodeURIComponent(S.name);
 
 var basenanme = 
 filename.substring(0, filename.lastIndexOf('.'));
 
 T = S.folder.getResourceType().download + 
 basenanme+'/$file/'+filename;

 //original CKFinder String
 //T = S.folder.getUrl() + S.name + '?download';
}

That's all.

Friday, October 5, 2012

CKFinder for Domino. Images (and Files)

CKFinder open an image in the same way as a thumbnail. So we have exactly the same problem like for thumbnails in Domino.

CKFinder construct link to the file like "base-file-url\filename". In Domino, we have access to the attachment with the help of "\$file\" path. So we must to have URL like:
"base-file-url\filename\$file\filename." or "base-file-url\filename\filename" in the case of substitution.

You need to find in the ckfinder.js string like
J.push('<a href="', D.folder.getUrl(), 
encodeURIComponent(C[K].name), 
'" title="', C[K].name, '" rel="', E, '">a</a>'); 
and change it with
if (!F || F(C[K])) {   
 var filename = encodeURIComponent(C[K].name);
 var basenanme = filename.substring(0, 
  filename.lastIndexOf('.'));
 var url = D.folder.getResourceType().url 
  + basenanme+'/$file/'+filename;
 
 J.push('<a href="', url, '" title="', C[K].name, 
  '" rel="', E, '">a</a>');                    
 if (C[K].isSameFile(D)) H = I;
 I++;
}
That will change image url. You also need to change File url. Find
if (!W.open(S.folder.getUrl() + 
 encodeURIComponent(S.name), '_blank', V)) 
 R.msgDialog('', R.lang.ErrorMsg.oo);

and change it with

var filename = encodeURIComponent(S.name);
var basenanme = filename.substring(0, filename.lastIndexOf('.'));
var url = S.folder.getResourceType().download + basenanme+
 '/$file/'+filename;
if (!W.open(url, '_blank', V)) 
 R.msgDialog('', R.lang.ErrorMsg.oo);

CKFinder for Domino. Thumbnails.

CKFinder have two options to show thumbnails - by request to the server "connector" command processor and by direct downloading.

Request to the "connector" ("ckfinder-connector-url\connector.xsp?command=Thumbnails...") is not accessible for Domino. Reading thumbnail attachment on the server and return data as result of the command is expensive operation for the server.

CKFinder construct direct download like "thumb-base-url\filename?hash=.....". In Domino, we have access to the attachment with the help of "\$file\" path. So we must to have URL like:
"thumb-bas-url\filename\$file\filename?Open&hash=....." 

Or better:
"thumb-bas-url\filename-thumb-without-ext\$file\filename-thumb?Open&hash=....." 

In the case if will use substitution rules for fine url:
"thumb-bas-url\filename-thumb-without-ext\filename-thumb?Open&hash=....." 

You need to find in the ckfinder.js  "getThumbnailUrl" function and modify code in the
"L && N.config.thumbsDirectAccess"
condition:

if (L && N.config.thumbsDirectAccess) {
     var filename = encodeURIComponent(M);
     var dotIndex = filename.lastIndexOf('.');
     var ext = filename.substring(dotIndex+1);
     var basenanme = filename.substring(0, dotIndex);
     return N.config.thumbsUrl + basenanme 
         + '-thumb/$file/'
         + basenanme + '-thumb.' + ext
         + (!K ? '' : '?Open&hash='
         + N.getResourceType(R.folder.type).hash
         + '&fileHash=' + P);
}

Wednesday, October 3, 2012

xPage java.lang.NoClassDefFoundError

Suddenly got "java.lang.NoClassDefFoundError" error on my xPage for the class from "Java" design area. The day before it works without problems.

Project clean, rebuild does not help.  Server restart does not help. I read that Recompile code when opening in designer (xPages) solution helps for someone, unfortunately not for me.

The only one solution helps is to rename class that was not found. I am lucky to have only one such class and I am afraid to get this error again for all of them.


... maybe I will try to move code to the WEB-INF/src ...

My Client version is "20120703.0900-T00099SHF-FP2 (Release 8.5.3FP2 SHF99) "