Attachments and Java Beans

Up until this point, I must admit that I have been lazy. Even though most of the XPages I have created in the last two years have made extensive use of Java Beans, I have left the attachments to the XspDocument and the typical upload and download controls. I did not want to open that can of worms and just wanted to stick with what I know works. Well, that is dumb. It is fine for one or two minor applications that are never going to be used anyway, but when it comes down to it, I want it to be correct. Today, I started that adventure and, with all new things, a google search was performed for anything that could help me and point me in the correct direction. (Honestly, what did you old folks do without search engines? I’d be lost! Or at the very least spending 10 hours a day at the public library!) What I did not find was a document that contained upload and download information. Since I do not want to loose what I found today, I decided to write a quick post. Thank you to everyone that I am stealing from to write this…. ūüėõ

The first thing that I noticed was that my concept was faulty. At least I think it was. I wanted to have a single file in my Java Bean that I could upload and download at will and access and save in my DAO layer. Of course I could be mistaken, but it does not seem to work that way. Uploading documents and downloading them again needs to be performed in two different actions, and in two different ways, and with different objects. Futhermore, I do not even offer both functions on the same page, though both are possible with the same bean.

First off, my test bean is very simple. If I were to extract an interface (just to get a quick look at what the bean contains, it would hold the following information

public interface IFileTest {

/*
* This function contains the information to save the document. Normally, I do this in a seperate DAO layer Object.
* The example that I used had the majority of the information in one single class.
* I did not experiment as I wanted to keep everything as simple as possible. Such experiments are further on my to-do list.
*/
public abstract void save() throws Exception;

/*
* This function contains the logic to download the attachement. It is performed in a separate XPage containing only this function call.
*/
public abstract void downloadAttachment() throws Exception;

/*
* This function returns a string that points to the xpages with the download attachment function call.
*/
public abstract String getDownloadURL();

/*
* This function will read the parameters from the URL in order to initialize the data for the viewscoped bean.
* Normally with the Beans I create, this function will access the DAO and set the data in an object contained by this Bean.
* This Bean is a controller in the MVC design pattern.
*/
public abstract void init();

/*
* I just find this helpful.
*/
public abstract String getUnid();

/*
* com.ibm.xsp.component.UIFileuploadEx.UploadedFile. This object is used ONLY in the upload core XPage control.
*/
public abstract UploadedFile getFile();

public abstract void setFile(UploadedFile file);

}

 

As i said, this test is done with as simple a construct as possible.

After this was completed, I worked on uploading a document. It seemed the most logical starting point. My primary source for this was a StackOverflow question posted by David Leedy so, note that the following code is primarily coming from Mark Leusink, the accepted answerer of Mr. Leedy’s question. The first part is the most simple. I have a property in my Bean that is of type com.ibm.xsp.component.UIFileuploadEx.UploadedFile . I have a corresponding getter/setter pair. I use EL to link the core control for uploading data to the bean. The real magic happens in the save logic.

public void save() throws Exception{
    
    /* I use the openNTF Domino API (ODA) for nearly all of my
     * applications. If this was not the case, we would have to worry about
     * proper recycling. Keep in mind that this is also just a test. Normally
     * my routines have much cleaner error handling.
     * 
     * The following statement uses a utility that I built that helps me get
     * key objects. I am assuming here that you know how to get a handle on the current
     * document. 
     */
    Document doc = ODASessionHelper.getCurrentDatabase().createDocument();
    doc.replaceItemValue(FIELD_FORM, FORM_NAME);
    
    // file is and instance of UploadedFIle. This is the Property that is bound to the core FileUpload control
    if(file != null){ 
      IUploadedFile fl = file.getUploadedFile();
      
      //File is the standard java.io variant.
      File file = fl.getServerFile();
      
      String fileName = fl.getClientFileName();
      // this gave me ONLY the name of the file without any path information.
      System.out.println(String.format("clientFileName: '%s'", fileName)); 
      
      // on my system, this gave the character ";"
      System.out.println(String.format("seperator is '%s'", File.pathSeparator));
      
      // This gives you the location of the file that was uploaded by the control on the server.
      File realNameFile = new File(file.getAbsoluteFile() + File.pathSeparator + fileName);
      System.out.println(String.format("realFile name: '%s'", realNameFile.getAbsoluteFile()));
      
      boolean renamedFile = file.renameTo(realNameFile);
      if(renamedFile){
        //typical code to attach a file to a document.
        RichTextItem body = doc.createRichTextItem(FIELD_BODY);
        body.embedObject(EmbeddedObject.EMBED_ATTACHMENT, "", realNameFile.getAbsolutePath(), null);
      } else {
        throw new Exception("file could not be renamed");
      }
      doc.save();
      
      /*
       * Normally at this stage, I save the UNID so that I get that document again
       * to prevent a bunch of new documents being created.  This is just me being 
       * lazy and wanting to get a test out ASAP.
       */
    } else {
      throw new NullPointerException("file was null");
    }
  }

The only issue that I have with the above code is that the new name of the attachment is a bit messed up. I do not know if it is because the operating system is windows, or if it is because of the domino version, but the attachment name is changed to “_<strangenumbers>tmp;realAttachmentName.txt” . This is because File.pathSeperator is a semicolon. I have a workaround for this in my download function, but a workaround is still only a workaround.

As I previously said, I did not find a post with both upload and download functionality explained. I did find an awesome article on openNTF regarding downloading attachments programmatically. So, here is a quick shout-out to Naveen Maurya who posted the XSnippet. In the example provided, an XPage was built which called a server-side JavaScript function which got a handle on the FacesContext and the server response to download all files in a zip file. I just edited this to be run in my Bean and not in JavaScript.

/*
   * same disclaimer. I wanted to this quickly. Normally my error handling is 
   * significantly better. I just want the theory here.
   */
  public void downloadAttachment() throws Exception{
    Database db = null;
    Document doc = null;
    
    //java.io.OutputStream;
    OutputStream stream = null;
    
    // java.util.zip.ZipOutputStream;
    ZipOutputStream out = null;
    
    // java.io.BufferedInputStream;
    BufferedInputStream in = null;
    
    try{
      if(StringHelper.isNullOrEmpty(getUnid())) throw new IllegalStateException("Unid is null");
      
      // again, I am using ODA, and this is just a way to get the current database.
      db = ODASessionHelper.getCurrentDatabase();
      
      /*
       * I normally do this in multiple steps.
       * 1. try to get the document with the UNID
       * 2. try to get the document with the noteID
       */
      doc = db.getDocumentByUNID(getUnid());
      if(!doc.hasItem(FIELD_BODY)){
        throw new IllegalStateException("body not located");
      } else {
        Item item = doc.getFirstItem(FIELD_BODY);
        if(!(item instanceof RichTextItem)){
          // I would assume that I would have to come up with a MIME variant as well.
          throw new IllegalStateException("item is not of type richtext");
        } else {
          // normally I ask if item is instanceof RichTextItem
          RichTextItem body = (RichTextItem)item;
          
          Vector objs = body.getEmbeddedObjects();
          if(objs.isEmpty()){
            throw new IllegalStateException("body has no objects to download");
          } else {
            
            ExternalContext extContext = FacesContext.getCurrentInstance().getExternalContext();
            // javax.servlet.http.HttpServletResponse;
            HttpServletResponse response = (HttpServletResponse)extContext.getResponse();
            
            response.setHeader("Cache-Control", "no-cache");
            response.setDateHeader("Expires", -1);
            response.setContentType("application/zip"); // change this for different types.
            // I gave a static name to my zip file, but the original code was dynamic
            response.setHeader("Content-Disposition", "attachment; filename=Attachments.zip");
            
            stream = response.getOutputStream();
            out = new ZipOutputStream(stream);
            
            for(EmbeddedObject att : objs){
              in = new BufferedInputStream(att.getInputStream());
              int length = in.available();
              byte[] data = new byte[length];
              in.read(data, 0, length);
              String nm = att.getName();
              
              /*
               * This is my workaround for the file names. Although they are saved in the document
               * with the incorrect name, I could at least download them again with the proper name.
               */
              ZipEntry entry = new ZipEntry(nm.contains(";") ? StringHelper.rightSubstring(nm, ";") : nm);
              out.putNextEntry(entry);
              out.write(data);
              in.close();
            }
          }
          // cleanup should be done properly.  this is a 'do as I say, not as I do' moment.....
          out.flush();
          out.close();
          stream.flush();
          stream.flush();
          FacesContext.getCurrentInstance().responseComplete();
        }
      }
    } catch(Exception e){
      // very nasty error handling....
      e.printStackTrace();
      throw e;
    }
    
  }

In conclusion, I have a test XPage application with one form, and with two xpages. The one xpage allows saving attachments. It has the File Upload control available by the XPages core and a save button. The second XPage is only used for downloading the attachments. It holds no content, but gets the file to download via the HTTPServletResponse in the beforeRenderResponse XPage action. The UNID of the document is passed with the URL.

Although not implemented in an xpage, I also built the logic to open the URL in a new window using client side javascript:

window.open("#{javascript:FileTest.getDownloadURL()}", "_blank");

FileTest in the above example is the name of the bean as configured in the FacesConfig.xml file.

My next steps would be

  • to build a view with which I could display the file names and other typical information available for file downloads
  • export files without being compressed into a zip file
  • it goes without saying that I would have to refine the above functions and build in proper cleanup and error handling

Happy Programming!!




XPage Java dev Tutorial 2 – AdminPanel prt 1


 

General Feels and Gratitude

Hello Everybody!!  Today I am really happy to present my first NotesIn9 video!  It was really great being able to make this sub-series and be a part of the tutorials that really got me going with XPage development.  I want to thank David Leedy for giving me the opportunity as well as thank my employer for allowing me to use what I learn on company time to create content for others.  This is turning out to be a hobby that is really starting to take off.  Again, I have to thank twitter, David Leedy, and Paul Withers for sharing my first blog posts and spreading the word that this blog is out and about.  I would not be so into this right now if it were not for the fresh feeling of being able to do good for others and not just use this website as my own knowledge repository. Thank you to those who read these posts, watch the videos, and of course who retweet, comment and like what I am doing.  All that really makes it easier to stay motivated.


 

Down to Business

What is this mini/sub -series all about?

As you may or may not be aware, everything that we are doing here has a basis in my day to day development.  I started out using java for my xpage dev about a year ago (I had already used java extensively in my daily programming, just not in combination with XPages for which I mostly relied on JavaScript.) To really get started with Java, I decided to make a utility package that included everything that I needed to know to really get going.  I started off with making a sessionHelper.  I did this because I needed to learn how to get a session object.  I did not know anything about the variable resolver at first, and I also had a lot of trouble finding resources to figure it out.  After I managed that, I moved on to one of the most important features of all databases that I develop, the Admin Panel.

The Admin Panel is (currently) a custom control where I store tables that show the variable names and values of all objects located in the applicationScope, the sessionScope, and the viewScope. ¬†I do not waste my time with the requestScope, although I suspect that such a thing would be possible if I found a use case for it. ¬†Additionally to this, I show another HTML table that presents cookie information. ¬†This is a part of another module that I call “the cookie helper”. ¬†Finally, a larger and more complex table presents logging information to the user with a particular role. ¬†The custom control contains functions that erase the variables located in the scoped maps (although I have not made that work without causing errors) add string variables to the scopes (not very helpful, but cool), and delete logging information shown in the debug section of the admin panel. ¬†A great feature of the debug table is that there are six different error levels that all use different row styles. ¬†This allows the trace information to be shown in a more understated way, and fatal errors to pop out like a sore thumb. ¬†Later, I turned this same functionality around to store and show entire session logging documents in a logging database.

Today we are going to look at how to build your own Admin Panel using information built, retained, and retrieved in Java.

How is this sub-series built?

Due to time, I broke this down into three different parts.  Since I am actually coding on camera as much as possible, it is taking a while to get through it.  I am doing this so that you can see where I have difficulty, so that you can hear me commenting as I go, and because my entire concept in this Java Tutorial is to program an application from start to finish together.  When it is just taking too long and when I do not have a lot to say, I do edit out some parts.

Part one (this part) is going to be a basic introduction.  We do, for this one time only, go through a PowerPoint presentation where I go into what we are doing, the three parts, and some basic information about myself.  One thing that changed from the making of the PowerPoint slides to the videos is that the concept had to be changed from asking question before the recording of the next part to all the questions being asked in comments and through email and me answering them in a written format or in a fourth video.  It makes sense for numerous reasons, but we do not need to get into that.

Part two is primarily Java construction.  Although some Java classes were started in the first part, in part two we really get going.  I do not want to spoil it too much here, but we basically finish up all the beans that we are going to need for the custom control.

Part three is being my slight PITA.  I recorded it once already, but am not pleased with how it turned out.  I really need to stop doing my recordings after 1 AM.  I am going to be deleting the changes and redoing the video.  Part three includes registering the beans that we already made with the faces-config.xml file and designing our custom control.  We go into how to add a few custom properties, although we admittedly do not go into every detail that you could possibly go over.  There are numerous NotesIn9s if you need help specifically with Custom Controls.

 Why is this a sub-series?

This is a sub-series because it is a small part in the project that we are building together. ¬†I was trying to find some way to make videos for this page’s main tutorial while also making content for NotesIn9. ¬†I decided that it would be awesome to make some of the content that can really stand alone its own sub series and give them to David Leedy. ¬†For those who follow along here, it should seem pretty seamless. ¬†To those who watch the NotesIn9s, it is my hope that you can watch that content and get what you need without too much knowledge of what we are doing in the rest of the tutorials. ¬†At any rate, I am posting the code on this website. ¬†That being said, without any further ado- I give you the video, and following the video, the code! ¬†Enjoy!


Video

Find the video here!


Code

COMING SOON!

XPage Java dev Tutorial 1

Greetings!!

The time has finally arrived for the first tutorial in XPage Java development.  In this tutorial we will learn how to get a handle on the current session in both the opentNTF Domino API (ODA) and the lotus domino API.  We will briefly go over what we did last time, and we will really hammer out some java code.  Stay tuned for next time when we will actually use Java and Custom controls to log data out to the user/XPage_Admin!!  That tutorial is planned to be given to David Leedy to be shown on NotesIn9!  Please make sure that you take a look at the comments that I added to the code which is displayed below!

Happy Programming!


 


 

package com.reederprogramming.utils;

import java.io.Serializable;

import javax.faces.context.FacesContext;

import org.openntf.domino.utils.Factory;

import com.ibm.xsp.extlib.util.ExtLibUtil;
import com.reederprogramming.exceptions.NullDatabaseException;
import com.reederprogramming.exceptions.NullSessionException;

import lotus.domino.Database;
import lotus.domino.Session;

/**
 * This class is going to contain key methods for dealing with the current session.
 * It implements Serializable in case I want to put this class in the sessionScope later and
 * use from JavaScript as well.  This may not be necessary, but it is easy to implement the interface
 * and take it out later if I need to, or leave it in since it does not hurt anything.
 * Note that all java beans MUST implement Serializable if they are going to be retained in server
 * memory.
 * 
 * @see java.io.Serializable
 * 
 * 
 * @author Greg@reederprogramming.com
 *
 */
public class SessionHelper implements Serializable {

	
	private static final long serialVersionUID = 2014071001L; //yyyyMMdd0v
	
	/**
	 * This is an example of how to get the current database using the utility class provided 
	 * by the Extension Libraries.  Alternatively, you could alter the getCurrentSession() to
	 * return the database.  Alternatively, you could also create an enum that includes all of
	 * valid strings and use that to get the object you desire from the VariableResolver.
	 * 
	 * @return returns an instance of the lotus.domino.Database.
	 * @throws NullDatabaseException thrown when the current database is null.  Should never happen, but
	 * can during multi-threading operations.
	 * @see lotus.domino.Database
	 * com.reederprogramming.exceptions.NullDatabaseException
	 * 
	 */
	public static Database getCurrentDatabase() throws NullDatabaseException{
		Database d  = null;
		d = ExtLibUtil.getCurrentDatabase();
		if(d == null){
			throw new NullDatabaseException("The current database could not be found!");
			/* This is an example of a checked Exception.  It must be handled or else the 
			*  compiler will yell.
			*/
		}
		return d;
	}
	
	/**
	 * This method shows two different examples.  The first one is how to get the 
	 * current session and the current database using the Factory enum provided by ODA
	 * 
	 * @see org.openntf.domino.utils.Factory
	 * @see org.openntf.domino.Database
	 * @see com.reederprogramming.exceptions.NullDatabaseException
	 * @return returns an instance of org.openntf.domino.Database from the ODA.
	 * @throws NullDatabaseException thrown when the current database is null.  Should never happen, but
	 * can during multi-threading operations.
	 * 
	 */
	public static org.openntf.domino.Database getCurrentODADatabase() throws NullDatabaseException{
		org.openntf.domino.Database d = null;
		d = Factory.getSession().getCurrentDatabase();
		if(d == null){
			throw new NullDatabaseException("The current database could not be found!");
		}
		return d;
	}
	
	/**
	 * This method displays a few things.  First off, we see how to check to see if an object is 
	 * the same as another class, and then we perform a cast to a higher level object.  Additionally,
	 * we see how to use the variableResolver to get the session instance.  This will be the same
	 * session object as returned by the session JavaScript keyword variable.
	 * 
	 * We also see an example of the FIX-ME and TO_DO keywords to aide with development processes.
	 * 
	 * @see lotus.domino.Session
	 * @see com.reederprogramming.exceptions.NullSessionException
	 * 
	 * @return returns an instance of the lotus.domino.Session class
	 * @throws NullSessionException
	 */
	public static Session getCurrentSession() throws NullSessionException{
		Object o = null;
		Session s = null;
		
		FacesContext context = FacesContext.getCurrentInstance();
		o = context.getApplication().getVariableResolver().resolveVariable(context, "session");
		if(o instanceof Session){
			s = (Session)o;
		}
		if(s == null){
			//FIXME add logging!!
			//TODO this must be expanded.
			throw new NullSessionException();
		}
		return s;
	}

	/**
	 * This is a similar example to the getCurrentDatabase() where we use the Extension Library utility
	 * class to get a handle on the current session.  This assumes that the Extension library is used
	 * and will always be used.  Becauase this is a class that should always be available, such an
	 * approach may not be wise.
	 * 
	 * @see com.ibm.xsp.extlib.util.ExtLibUtil
	 * @see lotus.domino.Session
	 * @see com.reederprogramming.exceptions.NullSessionException
	 * 
	 * @return returns an instance of the lotus.domino.Session class.
	 * @throws NullSessionException
	 */
	public static Session getCurrentExtLibSession() throws NullSessionException{
		Session s = null;
		s = ExtLibUtil.getCurrentSession();
		if(s == null){
			//FIXME add logging!!
			throw new NullSessionException();
		}
		return s;
	}
	
	/**
	 * This method illustrates how to get a handle on the current session using ODA.  This is only
	 * one possible way.  Furthermore, it shows how to wrap a normal lotus.domino object (they will always extend
	 * lotus.domino.Base) and return an ODA object.  According to one of the authors (Paul Withers), 
	 * it is however preferable to use the Factory.getSession() method.
	 * 
	 * @see org.openntf.domino.Session
	 * @see com.reederprogramming.exceptions.NullSessionException
	 * @see org.openntf.domino.utils.Factory
	 * 
	 * @return returns an instance of org.openntf.domino.Session
	 * @throws NullSessionException
	 */
	public static org.openntf.domino.Session getCurrentODASession() throws NullSessionException{
		/* 
		 * if you look at the above method declaration, you will notice that I am 
		 * specifying which Session object should be returned.  This is done because other
		 * methods in this class work with the lotus.domino.Session class. I must insert the full
		 * name (including package) in order to prevent ambiguity.  I can, however, debate if
		 * either of them should be imported, or which one makes more sense to import.
		 */
		
		//TODO change this method to simply use the getSession()  This is only for the sake of example
		org.openntf.domino.Session s = null;
		s = Factory.fromLotus(getCurrentSession(), org.openntf.domino.Session.class, null); 
	   
		/* 
		 * normally we would have to catch the NullSessionException given by the getCurrentSession().
		 * in this case, it just makes more sense for it to bubble up to the next method and let it deal with it.
		 */
		if(s == null){
			//FIXME add logging!!
			throw new NullSessionException("Missing session of type: " + org.openntf.domino.Session.class);
		}
		
		return s;
	}
}

 

package com.reederprogramming.exceptions;

/**
 * This is an Exception class to mark that a session object could not be returned.
 * It can be used for ODA or for lotus.
 * 
 * @author greg@reederprogramming.com
 */
public class NullSessionException extends Exception {
	
	private static final long serialVersionUID = 2014071001L; //yyyyMMdd0v

	public NullSessionException() {
	}

	public NullSessionException(String arg0) {
		super(arg0);
	}

	public NullSessionException(Throwable arg0) {
		super(arg0);
	}

	public NullSessionException(String arg0, Throwable arg1) {
		super(arg0, arg1);
	}

}

 

package com.reederprogramming.exceptions;

/**
 * This is an Exception class to mark that a Database object could not be returned.
 * It can be used for ODA or for lotus.
 * 
 * @author greg@reederprogramming.com
 */
public class NullDatabaseException extends Exception {
	
	private static final long serialVersionUID = 2014071001L; //yyyyMMdd0v

	public NullDatabaseException() {
		super();
	}

	public NullDatabaseException(String arg0) {
		super(arg0);
	}

	public NullDatabaseException(String arg0, Throwable arg1) {
		super(arg0, arg1);
	}

	public NullDatabaseException(Throwable arg0) {
		super(arg0);
	}

}