Insights into Java Server Faces

2010/04/06

Using File Upload in JSF Portlets with Weblogic Portal

Filed under: JSF, Portal — Hanspeter @ 13:59

According to this Oracle whitepaper it is not possible to use Tomahawk File Upload within a JSF Portlet – they recommend to use a non JSF Portlet for that. Well, we found a solution to get File Upload working within JSF Portlets.

Thanks to Amar for the preparation of this post.

The following description is based on using this environment:

  • JSF RI 1.2
  • Weblogic Portal 10
  • tomahawk-1.1.9.jar
  • commons-fileupload-1.2.1.jar
  • commons-io-1.4.jar

With standard tomahawk for file upload one needs to add the ServletFilter “org.apache.myfaces.webapp.filter.ExtensionsFilter”  to web.xml – for this implementation that ServletFilter is not needed (but it also doesn’t hurt if you still have it). Since this ExtensionFilter allows to configure file upload limits (e.g. uploadMaxSize, uploadMaxFileSize etc.) and we don’t use the Filter anymore, we had to handle the file upload configuration slightly different. But let’s look at the file upload solution first.

Steps

  1. Replace standard FacesContextFactory with Custom one e.g. CustomFacesContextFactory
  2. Wrap the FacesContext e.g. CustomFacesContext
  3. Wrap the ExternalContext : e.g. CustomExternalContext
  4. Enhance CustomExternalContext to handle Multipart request
  5. Create a sample to test

For step 1 to 3, please follow the steps mentioned in the previous post titled ‘Using Custom factories or howto wrap FacesContext’.

Enhance CustomExternalContext to handle Multipart request

package ch.dueni.jsf.context;

import javax.faces.context.ExternalContext;
. . .

public class CustomExternalContext extends ExternalContext {

  private ExternalContext delegate;

  public CustomExternalContext(ExternalContext delegate) {
    this.delegate = delegate;
    handleMultipartRequest();
  }

  private void handleMultipartRequest() {
    Object reqObj = delegate.getRequest();

    // 1. Is request of type HttpServletRequest and is it multipart request?
    if (reqObj instanceof HttpServletRequest && isMultipartContent((HttpServletRequest) reqObj)) {
      HttpServletRequest req = (HttpServletRequest) reqObj;

      // 2. Is the application running under portal environment or as web-app?
      if (delegate instanceof com.sun.faces.context.ExternalContextImpl) {

        // 3. wrap the request with a multipart request for file upload
        delegate.setRequest(getMultipartRequestWrapper(req));
      
      } else { 
        // In Portal Mode it is com.bea.portlet.adapter.faces.context.ExternalContextImpl
        
        // 4. Multipart request wrapper must be placed in between
        // FacesRequest and ServletRequestImpl from portlet-adapter.
        HttpServletRequestWrapper facesRequest = (HttpServletRequestWrapper) delegate.getRequest();
        HttpServletRequestWrapper servletRequest = (HttpServletRequestWrapper) facesRequest.getRequest();
        
        // 5. Wrap the servlet request with MultipartRequestWrapper and set the wrapped request to FacesRequest
        facesRequest.setRequest(getMultipartRequestWrapper(servletRequest));

        try {
          // 6. Need to reset the requestParameterMap cache
          Field map = delegate.getClass().getDeclaredField("requestParameterMap");
          map.setAccessible(true);
          map.set(delegate, null);

        } catch (Exception e) {
          throw new IllegalStateException("Could not reset cached 'requestParameterMap'!", e);
        }

      }
    }
  }

  private MultipartRequestWrapper getMultipartRequestWrapper(HttpServletRequest req) {
    FileUploadConfig config = FileUploadConfig.getFileuploadConfig(req);
    
    MultipartRequestWrapper multipartRequest = new MultipartRequestWrapper(req, config.getUploadMaxFileSize(),
        config.getUploadThresholdSize(), config.getUploadRepositoryPath(), config.getUploadMaxSize(), 
        config.isCacheFileSizeErrors());
    return multipartRequest;
  } 

  private static final boolean isMultipartContent(HttpServletRequest request) {
    if (!("post".equals(request.getMethod().toLowerCase())))
      return false;

    String contentType = request.getContentType();
    if (contentType == null) {
      return false;
    }

    return (contentType.toLowerCase().startsWith("multipart/"));
  }

  /* ... other delegating methods and wrappings */

}

Legend: (to the above source)

  • 4 – 5: when running in the WLS JSF portlet-adapter, the request is actually of type FacesRequest which has a ServletRequestImpl as it’s delegate. We need to put the MultiPartRequestWrapper from tomahawk in between these two – so that FacesRequest delegates to MutliPartRequestWrapper that wraps ServletRequestImpl. The Problem however is, that FacesRequest already cached the requestParameterMap.
  • 6: since there is no other way to reset the cached requestParameterMap we use now reflection to do so – ugly but the only way. Would be nice if there was way to reset such caches using an API method.

FileUploadConfig bean to handle file upload web.xml context-params

package ch.dueni.jsf.fileupload;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
. . .
public class FileUploadConfig {
  // private Logger
  private static Logger LOG = Logger.getLogger(FileUploadConfig.class.getName());

  //Sets the maximum allowable size for uploaded files.
  private int uploadMaxFileSize = 100 * 1024 * 1024; // 10 MB;

  // Sets the size threshold beyond which files are written to disk, smaller file are held in memory.
  private int uploadThresholdSize = 1 * 1024 * 1024; //1 MB

  // Sets the directory in which temporary files (ie caches for those uploaded files that are larger
  // than uploadThresholdSize) are to be stored.
  private String uploadRepositoryPath = null; //standard temp directory

  // Catch and swallow FileSizeLimitExceededExceptions in order to return as many usable items as possible.
  // If you want to show validation error message for upload file size exceeds then set this true.
  private boolean cacheFileSizeErrors = false;

  // Sets the maximum allowable size for the current request. Default value is same as on uploadMaxFileSize param. 
  private int uploadMaxSize = 100 * 1024 * 1024;

  // constants for the web.xml context param configuration
  public static final String BEAN_NAME = "ch.dueni.jsf.fileUploadConfig";
  private static final String UPLOAD_MAX_SIZE = "ch.dueni.jsf.UPLOAD_MAX_SIZE";
  private static final String UPLOAD_MAX_FILE_SIZE = "ch.dueni.jsf.UPLOAD_MAX_FILE_SIZE";
  private static final String UPLOAD_THRESHOLD_SIZE = "ch.dueni.jsf.UPLOAD_THRESHOLD_SIZE";
  private static final String UPLOAD_MAX_REPOSITORY_PATH = "ch.dueni.jsf.UPLOAD_MAX_REPOSITORY_PATH";
  private static final String UPLOAD_CACHE_FILE_SIZE_ERRORS = "ch.dueni.jsf.UPLOAD_CACHE_FILE_SIZE_ERRORS";


  public FileUploadConfig(ServletContext servletCtx) {
    uploadMaxSize = getContextParamAsInt(servletCtx, UPLOAD_MAX_SIZE, uploadMaxSize);
    uploadMaxFileSize = getContextParamAsInt(servletCtx, UPLOAD_MAX_FILE_SIZE, uploadMaxFileSize);
    uploadThresholdSize = getContextParamAsInt(servletCtx, UPLOAD_THRESHOLD_SIZE,
        uploadThresholdSize);

    String _uploadRepositoryPath = servletCtx.getInitParameter(UPLOAD_MAX_REPOSITORY_PATH);
    if (_uploadRepositoryPath != null) {
      uploadRepositoryPath = _uploadRepositoryPath;
    }

    String _cacheFileSizeErrors = servletCtx.getInitParameter(UPLOAD_CACHE_FILE_SIZE_ERRORS);
    if (_cacheFileSizeErrors != null) {
      cacheFileSizeErrors = Boolean.valueOf(_cacheFileSizeErrors);
    }
  }

  private int getContextParamAsInt(ServletContext servletCtx, String paramName, int defaultValue) {
    int intValue = defaultValue;
    String configString = servletCtx.getInitParameter(paramName);
    if (configString != null) {
      try {
        intValue = Integer.valueOf(configString);
      } catch (NumberFormatException nfe) {
        LOG.log(Level.WARNING,
            "Error converting web.xml context param '" + paramName + "' to int!", nfe);
      }
    }
    return intValue;
  }

  public static FileUploadConfig getFileuploadConfig(HttpServletRequest req) {
    // on first access, create and cache the config bean in ServletContext
    ServletContext servletCtx = req.getSession().getServletContext();
    FileUploadConfig cfg = (FileUploadConfig) servletCtx.getAttribute(BEAN_NAME);
    if (cfg == null) {
      cfg = new FileUploadConfig(servletCtx);
      servletCtx.setAttribute(BEAN_NAME, cfg);
    }
    return cfg;
  }
 
  public int getUploadMaxFileSize() {
    return uploadMaxFileSize;
  }

  public void setUploadMaxFileSize(int uploadMaxFileSize) {
    this.uploadMaxFileSize = uploadMaxFileSize;
  }

  public int getUploadThresholdSize() {
    return uploadThresholdSize;
  }

  public void setUploadThresholdSize(int uploadThresholdSize) {
    this.uploadThresholdSize = uploadThresholdSize;
  }

  public String getUploadRepositoryPath() {
    return uploadRepositoryPath;
  }
}

Create a sample to test

fileupload.jsp
<%@ taglib uri="http://myfaces.apache.org/tomahawk" prefix="t" %>
. . .

<h:form id="uploadForm" enctype="multipart/form-data">
. . .
      <t:inputFileUpload id="file" value="#{ fileUploadBackingBean.uploadedFile}" required="true" />

      <h:commandButton value="Submit" action="#{ fileUploadBackingBean.submit}"  />
. . .
 </h:form>
FileuploadbackingBean.java
. . .
import org.apache.myfaces.custom.fileupload.UploadedFile;
. . .
public class FileUploadBackingBean {
  private UploadedFile uploadedFile;
  public String submit() {

    try {
      // Your Business logic . Below is your inputstream
      uploadedFile.getInputStream();

      // Just to demonstrate what info we can get from the uploaded file.

      System.out.println("MyBean.File type: " + uploadedFile.getContentType());
      System.out.println("MyBean.File name: " + uploadedFile.getName());
      System.out.println("MyBean.File size: " + uploadedFile.getSize() + " bytes");

      // Show success message.
      FacesContext.getCurrentInstance().addMessage("uploadForm", 
        new FacesMessage(FacesMessage.SEVERITY_INFO, "File upload succeed!", null));

    } catch (IOException e) {

      // Show error message.
      FacesContext.getCurrentInstance().addMessage("uploadForm",
          new FacesMessage(FacesMessage.SEVERITY_ERROR, "File upload failed with I/O error.", null));

      // Always log stacktraces.
      e.printStackTrace();
    }
    return null;
  }

  public void setUploadedFile(UploadedFile uploadedFile) {
    this.uploadedFile = uploadedFile;
  }

  public UploadedFile getUploadedFile() {
    return uploadedFile;
  } 
}

Use the above file upload page and managed bean in your JSF Portlet setup.

Configure context-params for file upload

If you want other file upload limits then the default values you need to add context-param configurations to your web.xml (similar to the params used for tomahawk ExtensionFilter, but we used different names to prevent name clash). You find the context-param names in the FileUploadConfig bean source shown above.

<context-param>
  <param-name> </param>ch.dueni.jsf.UPLOAD_MAX_FILE_SIZE </param-name>
  <param-value> </param>1024</param-value>
</context-param>

Note: the values are in bytes – so 1024 is 1 kb.

Advertisements

25 Comments »

  1. Hello, i have just did follow what you write but it’s impossible and throw some exception like these :

    javax.portlet.faces.BridgeException: java.lang.NullPointerExceptionjavax.portlet.PortletException: doBridgeDispatch failed: error from Bridge in executing the request
    at javax.portlet.faces.GenericFacesPortlet.doBridgeDispatch(GenericFacesPortlet.java:648)
    at javax.portlet.faces.GenericFacesPortlet.doRenderDispatchInternal(GenericFacesPortlet.java:611)
    at javax.portlet.faces.GenericFacesPortlet.doView(GenericFacesPortlet.java:255)
    at javax.portlet.GenericPortlet.doDispatch(GenericPortlet.java:328)
    at javax.portlet.faces.GenericFacesPortlet.doDispatch(GenericFacesPortlet.java:226)
    at javax.portlet.GenericPortlet.render(GenericPortlet.java:233)
    at com.bea.portlet.container.PortletStub.doRender(PortletStub.java:942)
    at com.bea.portlet.container.FilterChainGenerator.runFilterChain(FilterChainGenerator.java:124)
    at com.bea.portlet.container.PortletStub.render(PortletStub.java:414)
    at com.bea.portlet.container.AppContainer.renderStub(AppContainer.java:1120)
    at com.bea.portlet.container.AppContainer.invokeRender(AppContainer.java:1052)
    at com.bea.netuix.servlets.controls.content.JavaPortletContent.fireRender(JavaPortletContent.java:267)
    at com.bea.netuix.servlets.controls.content.JavaPortletContent.renderInternal(JavaPortletContent.java:162)
    at com.bea.netuix.servlets.controls.content.JavaPortletContent.beginRender(JavaPortletContent.java:108)
    at com.bea.netuix.servlets.controls.application.laf.ContentControlRenderer.beginRender(ContentControlRenderer.java:48)
    at com.bea.netuix.nf.ControlLifecycle$7.visit(ControlLifecycle.java:481)
    at com.bea.netuix.nf.ControlTreeWalker.walkRecursiveRender(ControlTreeWalker.java:518)
    at com.bea.netuix.nf.ControlTreeWalker.walkRecursiveRender(ControlTreeWalker.java:529)
    at com.bea.netuix.nf.ControlTreeWalker.walkRecursiveRender(ControlTreeWalker.java:529)
    at com.bea.netuix.nf.ControlTreeWalker.walk(ControlTreeWalker.java:220)
    etc..

    Can you give me fully code you wrote ? Thanks in advance!

    Comment by Coy — 2010/09/08 @ 06:49

    • Hello,

      I cant’t give full code, because that is part of company internal product. I just wanted to share – it’s possible to do file upload with WLS Portal and JSF using Facelets. But I remember there was a problem with wrapping the FacesContext/ExternalContext since the facesContextFactory of the faces-adapter (WLS JSF Portlet Bridge) was put in front. To ensure our FacesContextFactory was used we needed to configure in the WEB-INF/faces-config.xml.
      Check with debugger if your wrapping FacesContext is used.
      You provided very little information about your environment. Are you using Java Portlet or JSF based using WLS Portlet Bridge (that setup we use), which JSF version, which WLP version.

      regards
      Hanspeter

      Comment by Hanspeter — 2010/09/08 @ 10:00

  2. Yeah, i am using JSF 1.2, WLP 10.3.2, so how to check version of java portlet in JSF based using WLS portlet bridge ? And when i check delegate class in CustomExternalContext it tell me that is : org.apache.myfaces.portlet.faces.context.PortletExternalContextImpl

    How do i need to do ? Thanks in advance!

    Comment by Coy — 2010/09/09 @ 01:59

  3. Hello Coy,
    If you have org.apache.myfaces.portlet.faces.context.PortletExternalContextImpl then its clear you are not using the WLP native bridge instead you are uisng JSR 329 bridge. And in our company we are using Native bridge portlets.
    You can read more details on the differences at –
    http://download.oracle.com/docs/cd/E15919_01/wlp.1032/e14244/jsf.htm

    Next steps I recommend is to create Native bridge portlet –
    1. Create portlet in OEPE with portlet type -> JSF content Portlet (Not JSF Portlet -> this will create java portlet based on JSR 329)
    2. Add Facelet Facet to project -> This will add Facelets view handler.

    Regards,
    Amar

    Comment by Amar Pandav — 2010/09/09 @ 11:06

  4. Thank you for your guide. Can you guide me clearly by step by step to create a portal project ? I think i have a mistake when create portal project, my dynamic web module is version 2.5, Configuration is : weblogic portal web project facet.

    is there something wrong here?

    Comment by Coy — 2010/09/10 @ 02:15

    • Hello,
      You can follow the steps at –
      http://download.oracle.com/docs/cd/E15919_01/wlp.1032/e14243/wizards_and_setup.htm

      Chapter 4 -> You can follow all steps except 4.7.

      We are using JSR RI 1.2.

      Regards,
      Amar Pandav

      Comment by Amar Pandav — 2010/09/10 @ 21:45

      • JSF 1.2

        Comment by Amar Pandav — 2010/09/10 @ 21:54

      • Thank you very much.

        But when i create JSF content portlet, there was a message alert : “the selected project does not supporting this portlet type. This type portlet might not successfully create or function correctly.” . Is there specially something configuration missing here ? Thanks in advance!

        Best Regards,
        Coy.

        Comment by Coy — 2010/09/11 @ 02:31

      • The pre-requisites are dynamic web module version 2.5, JSF 1.2 and Facelets.
        Are you using OEPE?

        Regards,
        Amar Pandav

        Comment by Amar Pandav — 2010/09/11 @ 06:39

      • Yes, i am. i reconfiged it and it can be added JSF content portlet. But when i try to run with your source code. The console display some messages like : “The deferred EL expression is not allowed since deferredSyntaxAllowedAsLiteral is false”.

        I don’t know why. I searched in the internet but can’t found any solution for it. Can you help me ? Thanks in advance!

        Best Regards,
        Coy.

        Comment by Coy — 2010/09/13 @ 06:48

      • Thank you very much, the problem is solved. It worked.

        Comment by Coy — 2010/09/13 @ 10:19

      • Hello, the problem is solved, but i got a litle problem. Here is my code :

        my bean don’t show method for action, just show property value, i have to type action by hand, no intelligent generating. Have you got the same problem with me ? And any solution have you got for it ? Thanks in advance!

        Best Regards,
        Coy.

        Comment by Coy — 2010/09/14 @ 04:33

    • perfect!
      You welcome.

      Regards,
      Amar Pandav

      Comment by Amar Pandav — 2010/09/13 @ 18:12

      • Hi Coy,
        Did not understood your problem.
        Can you pls explain?
        Is this any thing to do with file upload?

        Regards,
        Amar Pandav

        Comment by Amar Pandav — 2010/09/14 @ 17:54

      • Yeah, here is my problem :

        for ex : I have a bean named UploadBean with properties like username and password, methods like validUser() or hashMD5(). But in my code, like : “h:commandButton action=”#{}” ” if i type uploadBean.validUser into action that is impossible. But with upload.username is possible. I don’t know why. The problem just occurred when i change project to facelet for upload file. Have you got the same problem like me ?

        Best Regards,
        Coy.

        Comment by Coy — 2010/09/15 @ 01:12

      • Can you view source your page and see the form tag and it must have encode type multipart.
        But if your action is not getting invoked then how your file upload action is working?

        Regards,
        Amar Pandav

        Comment by Amar Pandav — 2010/09/15 @ 19:24

      • Yes, it’s worked. That for reason i don’t know why i can invoke my method. Have you got the same problem or not ? Thanks in advance!

        Best Regards,
        Coy.

        Comment by Coy — 2010/09/16 @ 11:21

      • no i don’t have this problem

        Comment by Amar Pandav — 2010/09/16 @ 18:28

  5. Hello,

    Are you using JSF version 1.2 or JSF version 1.1 with JSF implement : JSF RI 1.2 ?

    Comment by Coy — 2010/09/10 @ 03:51

  6. Hi

    I need some help

    Currently I am using JSF1.2(portlet) in weblogic portal 10.3 …

    I had some porblem with resetting the JSF Portlet State

    For example a Register JSF Portlet. It has two pages, “A Register Page” and “A Register confirm Page”. After you enter the Registration on “A Register Page” and click submit, you will get a “A Register confirm Page”. Add this JSF Portlet on a page in weblogic portal. Now visit some other page and comeback to the page on which you added the JSF Portlet, I will see that it is showing “A Register confirm Page”, i.e the state is retained. I want to see “A Register Page” every time I visit the page.

    Thanks
    kumar

    Comment by kumar — 2010/09/15 @ 02:27

    • Hi,
      we had similar problem. I don’t know whether this is some specific solution on our portal, but we used a request-header to reset the portlet state. Ther header name is “reset_portlet_state”. The actual resetting was done in a PhaseListener in beforePhase() for the RENDER_RESPONSE phase.
      public static void handleResetPortletState(FacesContext fctx) {
      ExternalContext ectx = fctx.getExternalContext();
      if ("true".equals(ectx.getRequestHeaderMap().get(RESET_PORTLET_STATE))) {
      // save or reset initial view id
      Map sessionMap = ectx.getSessionMap();
      String viewId = (String)sessionMap.get(INITIAL_VIEW_ID);
      if (viewId != null) {
      fctx.setViewRoot(fctx.getApplication().getViewHandler().createView(fctx, viewId));
      fctx.getViewRoot().setViewId(viewId);
      removeErrorMessagesQueuedInFaceContext(fctx);
      } else {
      sessionMap.put(INITIAL_VIEW_ID, fctx.getViewRoot().getViewId());
      }
      }
      }

      Again, I don’t know right now if this request header comes by WLP or our Portal framework – but you need something indicating an initial state of the Portal page.

      Comment by Hanspeter — 2010/09/27 @ 12:57

      • Hi

        THanks for reply…sorry…for late reply from my side…

        Thanks
        kumar

        Comment by kumar — 2010/12/03 @ 01:56

  7. Hello, i am back 😀 Have you got any solution with edit mode in jsf facelet ? I want turn on edit mode in my portlet but when i use jsf facelet portlet, documentation tell me that it only support view mode in this portlet type. Any suggestion are welcome.

    Thanks in advance!

    Best Regards, Coy!

    Comment by Coy — 2010/12/28 @ 09:57

  8. I have configured and implemented everything as suggested in this discussion and I have configured my wrapper classes in faces-config.xml aslo.

    here is my jsp code

    when I submit the form no action invokes in my backing bean this happen because i added enctype=”multipart/form-data” in if I remove this attribute I am able to hit the action.
    On console I got the warning :

    Dec 13, 2010 5:07:45 PM com.sun.faces.config.ConfigureListener contextInitialized
    INFO: Initializing Mojarra (1.2_09-20081212-SNAPSHOT) for context ‘/MyJsfProject’

    ==>> CustomFacesContextFactory.getFacesContext()
    ==>> CustomFacesContextFactory.getFacesContext()
    ==>> CustomFacesContextFactory.getFacesContext()

    Please suggest me how to hit the action with enctype=”multipart/form-data” in <h:form tag in JSF Portlet

    Regards
    Ankur Bhargava

    Comment by ankur — 2011/01/24 @ 21:12

  9. Dec 13, 2010 5:07:57 PM IST Warning netuix BEA-423319 A default JSP response character encoding was not found for webapp [/MyJsfProject]. Defaulting to [UTF-8]. You can override this default response character encoding for all portals in netuix-config.xml or for each portal or desktop in each portal or desktop definition.

    Comment by ankur — 2011/01/24 @ 21:14


RSS feed for comments on this post. TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Create a free website or blog at WordPress.com.

%d bloggers like this: