JDev11.1.2.1.0: Handling images/files in ADF (Part 3)

This blog article is part 3 of a series of posts showing how to deal with images or files in an ADF application. Each of the techniques to do this are described in other blog posts or documents, but I couldn’t find a sample doing it all together in on sample application.
The goal of this series is to provide a sample showing how to upload a file from a client to the server, store the data on the server, make it available for later retrieval and show the data in the user interface (form and table).

    Part 1 gives an overview of the sample application I’m going to build and how to set it up
    Part 2 shows how to upload a file, store it and download it back to the client
    Part 3 implements two techniques to show the data (image) on the user interface
    Part 4 backport of the sample to JDeveloper 11gR1

Implements two techniques to show the data (image) on the user interface

This part of the mini series handles about embedding images in JSF pages. Images can be embedded in tables and forms as shown in the picture below:

Embedded image in table and form

Embedded image in table and form

To do this we use an af:image tag inside an af:column for the table or inside an af:form instead of an af:inputText or af:outputText. The af:image tag needs an URI to retrieve the image data from and present them to the client. The problem here is that you can’t access the whole file system of the server your application is running on or access images from other servers, NAS or DB storage. Only image data deployed with the application below the web root (not within the WEB-INF folder) is directly accessible.
For all ohter locations you need some code in a servlet where you can access your image data and pass it to the client. The rest of this post shows two different way to access image data in a servlet.

Setting up the servlet in hte application
To make a servlet accessible to the application we need to define a path, relative to you web root, which your servlet listen to. This definition is done in the web.xml file:

  <servlet>
    <display-name>ImageServlet</display-name>
    <servlet-name>ImageServlet</servlet-name>
    <servlet-class>de.hahn.blog.uldl.view.frkext.servlet.ImageServlet</servlet-class>
    <load-on-startup>100</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>ImageServlet</servlet-name>
    <url-pattern>/render_image</url-pattern>
  </servlet-mapping>

Here you define the servlet name, class and the URI part (‘/render_image’) the servlet is listening to. To access this URI from an af:image tag you use code like this:

<af:image source="/render_image?id=#{bindings.ImageId.inputValue}" id="i1"
                              shortDesc="#{bindings.ImageName.hints.tooltip}"
                              inlineStyle="width:200px;"/>

The servlet implements the doGet(…) method where all the real work is done. First of all you can get the parameters passed to together with the URI: ‘id=#{bindings.ImageId.inputValue}’ in the sample above. The parameter(s) should be sufficient to get the image data from within the servlet. In the sample I pass the primary key of the row holding the image data in a blob.

1. Accessing the image data using a RootApplicationModule
The first method I like to show is the use of an RootApplicationModule to access the view object holding the image data.

    public void doGet(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException
    {
        synchronized (this)
        {
            StringBuilder sb = new StringBuilder(100);
            String appModuleName = "de.hahn.blog.uldl.model.facade.ULDLAppModule";
            String appModuleConfig = "ULDLAppModuleLocal";
            ApplicationModule am = null;
            ViewObject vo = null;
            try
            {
                am = Configuration.createRootApplicationModule(appModuleName, appModuleConfig);
                sb.append("ImageServletRooAppModule ").append(appModuleName);
                ULDLAppModuleImpl amULDL = (ULDLAppModuleImpl) am;
                vo = am.findViewObject("ImageAccessView1");
                if (vo == null)
                {
                    throw new Exception("ImageAccessView1 not found!");
                }
                ImageAccessView imageView = (ImageAccessView) vo;
                
                // get parameter from request
                Map paramMap = request.getParameterMap();
                oracle.jbo.domain.Number id = null;
                if (paramMap.containsKey("id"))
                {
                    String[] pVal = (String[]) paramMap.get("id");
                    id = new oracle.jbo.domain.Number(pVal[0]);
                    sb.append(" id=").append(pVal[0]);
                }                

                // Get the result (only the first row is taken into account
                ImageAccessViewRow imageRow = (ImageAccessViewRow) imageView.getImageById(id);
                BlobDomain image = null;
                String mimeType = null;
                // Check if a row has been found
                if (imageRow != null)
                {
                    // We assume the Blob to be the first a field
                    image = imageRow.getImageData();
                    mimeType = (String) imageRow.getContentType();
                }
                else
                {
                    mLogger.warning("No row found to get image from !!! (id = " + id + ")");
                    return;
                }
                sb.append(" ").append(mimeType).append(" ...");
                mLogger.info(sb.toString());

                // Set the content-type. Only images are taken into account
                response.setContentType(mimeType + "; charset=utf8");
                OutputStream outputStream = response.getOutputStream();
                IOUtils.copy(image.getInputStream(), outputStream);
                // cloase the blob to release the recources
                image.closeInputStream();
                // flush the outout stream
                outputStream.flush();
            }
            catch (Exception e)
            {
                mLogger.warning("Fehler bei der Ausführung: " + e.getMessage());
            }
            finally
            {

                if (am != null)
                {
                    //Release the appModule
                    Configuration.releaseRootApplicationModule(am, true);
                }
            }

            mLogger.info("...done!");
        }
    }

Line 13 shows the code to create a rot ApplicationModule which is then used in line 16 to get the view object holding the data. Lines 23 to 31 retrieve the parameter passed with the URI. Line 34 get the row holding the image data, the next passage gets the mime type and image name and finally the image blob to copy it to the servlets output stream.
The rest of the code cleans up and releases the root ApplicationModule.
Releasing the ApplicationModule is essential as you running out off resources quickly otherwise.

This approach does have some disadvantages. First creating a root ApplicationModule is a costly operation, second you can’t share the context with the application itself. This leads to the solution presented next.

2. Using the existing BindingLayer of the application

This is my preferred method, however it need some more preparation. Still the advantages out wight this. One advantage is that there is no difference in the way you talk to the model layer. This helps to avoid errors. Next you can get a ‘Shared BindingContext’, meaning that you share the same data control context (frame) as the underlying UI pages and Task Flows within that application. For more information check out this sample Sharing ADF context with a Servlet.
In this article I only show how to setup the binding layer in the servlet and use it to load image data.

First of all we need to build a PageDef.xml file which defines the binding layer for the servlet as it does for normal JSF pages. Directly creating a PageDef.xml is not suppoerted in the current JDev version (11.1.2.1.0) or earlier versions.
Here are a step by step instruction to create a PageDef.xml file:

A. Create an new XML file in the same folder where you found the existing PageDef.xml files and name it e.g. ‘xyz_dummyPageDef.xml’. You see hte result in the image below.

Create new empty PageDef.xml file

Create new empty PageDef.xml file


B. Open any of your existing PageDef.xml files in Source mode and copy the whole content into the newly created xyz_dummyPageDef.xml file
C. Now edit the new file so that the content looks like below. Make sure that you change the id property to the name of of the new file.

<?xml version="1.0" encoding="UTF-8" ?>
<pageDefinition xmlns="http://xmlns.oracle.com/adfm/uimodel" version="11.1.2.60.81"
                id="xyz_dummyPageDef" Package="de.hahn.blog.uldl.view.pageDefs">
  <parameters/>
  <executables>
    <variableIterator id="variables"/>
  </executables>
  <bindings>
  </bindings>
</pageDefinition>

D. As you see the new file does not have the same icon as the other files (4.). Save your work and close JDev. Open JDev again and it will look OK. Now we can begin to setup the PageDef file.

E. We start with adding an iterator into the executable section

Setup PageDef.xml file for servlet  (part 1)

Setup PageDef.xml file for servlet (part 1)

Then we add an methodAction into the binding section on the left (1.), for this we select view iterator (or application module) which holds the method (2.) and select the method in the operation drop down box. The final result looks like (3.)

Setup PageDef.xml file for servlet  (part 2)

Setup PageDef.xml file for servlet (part 2)

The source of the PageDef.xml file looks like

<?xml version="1.0" encoding="UTF-8" ?>
<pageDefinition xmlns="http://xmlns.oracle.com/adfm/uimodel" version="11.1.2.60.81"
                id="image_dummyPageDef" Package="de.hahn.blog.uldl.view.pageDefs">
  <parameters/>
  <executables>
    <variableIterator id="variables"/>
    <iterator id="ImageAccessView1Iterator" Binds="ImageAccessView1"
              DataControl="ULDLAppModuleDataControl" RangeSize="25"/>
  </executables>
  <bindings>
    <methodAction IterBinding="ImageAccessView1Iterator" id="getImageById"
                  RequiresUpdateModel="true" Action="invokeMethod" MethodName="getImageById"
                  IsViewObjectMethod="true" DataControl="ULDLAppModuleDataControl"
                  InstanceName="data.ULDLAppModuleDataControl.ImageAccessView1"
                  ReturnName="data.ULDLAppModuleDataControl.methodResults.getImageById_ULDLAppModuleDataControl_ImageAccessView1_getImageById_result">
      <NamedData NDName="aId" NDType="oracle.jbo.domain.Number"/>
    </methodAction>
  </bindings>
</pageDefinition>

After creating the PageDef.xml the second step is to create a ‘Page Definition Usage’ for the PageDef file in Databindings.cpx.
For this we open the Databindings.cpx, select the ‘PageDefinitionUsages’ node in the structure window and right click to add a page. In the dialog you enter an id for the ‘Page Definition Usage’ and the path to the PageDef.xml file. The id is used later, when we load the BindingContext inside the servlet.

Create entry in Databindings.cpx

Create entry in Databindings.cpx

The third and final step is to configure the adf binding filter that it also filters requests to the image servlet. This is done in the web.xml. Here we select the ‘Filters’ node and look for the ‘adfBindings’. Now we insert a new servlet mapping. For this we select the adfBindings filter, select the ‘Filter Mapplings’ tab. Hitting the green ‘+’ sign we add a new mapping. Select ‘Servlet’ as ‘Mapping Type’, in the mappling column we select the ‘ImageServlet’ and select ‘FORWARD’ and ‘REQUEST’ as ‘Dispatcher Type’. The result of this should look like

Activate ADFbinding filter for the new servlet

Activate ADFbinding filter for the new servlet

This concludes the preparation for the access of the binding layer from a servlet. Now we look at the code which actually implements the doGet() method in the servlet.

    public void doGet(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException
    {
        synchronized (this)
        {
            StringBuilder sb = new StringBuilder(100);
            sb.append("ImageServlet ");

            ViewObject vo = null;
            try
            {
                // get parameter from request
                Map paramMap = request.getParameterMap();
                oracle.jbo.domain.Number id = null;
                if (paramMap.containsKey("id"))
                {
                    String[] pVal = (String[]) paramMap.get("id");
                    id = new oracle.jbo.domain.Number(pVal[0]);
                    sb.append(" id=").append(pVal[0]);
                }

                // get method action from pagedef
                BindingContext bindingContext = BindingContext.getCurrent();
                DCBindingContainer amx =
                    bindingContext.findBindingContainer("de_hahn_blog_uldl_view_image_dummyPageDef");
                JUCtrlActionBinding lBinding =
                    (JUCtrlActionBinding) amx.findCtrlBinding("getImageById");
                // set parameter
                lBinding.getParamsMap().put("aId", id);
                // execute method
                lBinding.invoke();
                // get result
                Object obj = lBinding.getResult();                
                ImageAccessViewRow imageRow = (ImageAccessViewRow) obj;
                BlobDomain image = null;
                String mimeType = null;

                // Check if a row has been found
                if (imageRow != null)
                {
                    // Get the blob data
                    image = imageRow.getImageData();
                    mimeType = (String) imageRow.getContentType();
                    if (image==null)
                    {
                        mLogger.info("No data found !!! (id = " + id + ")");
                        return;                        
                    }
                }
                else
                {
                    mLogger.warning("No row found to get image from !!! (id = " + id + ")");
                    return;
                }
                sb.append(" ").append(mimeType).append(" ...");
                mLogger.info(sb.toString());

                // Set the content-type. Only images are taken into account
                response.setContentType(mimeType + "; charset=utf8");
                OutputStream outputStream = response.getOutputStream();
                IOUtils.copy(image.getInputStream(), outputStream);
                // cloase the blob to release the recources
                image.closeInputStream();
                // flush the outout stream
                outputStream.flush();
            }
            catch (Exception e)
            {
                mLogger.log(Level.WARNING, "Fehler bei der Ausführung: " + e.getMessage(), e);
            }
            finally
            {

            }

            mLogger.info("...done!");
        }
    }

The first part of the code looks like the doGet() method of the other servlet which uses a root application module to get the image data. The interesting part starts a line 22 in the code above. Here we get the binding layer like we do inside a managed bean. The only difference is that we use the

DCBindingContainer amx =
     bindingContext.findBindingContainer("de_hahn_blog_uldl_view_image_dummyPageDef");

to get the binding container instead of using

DCBindingContainer amx =
     (DCBindingContainer) bindingContext.getCurrentBindingsEntry();

all other code is identically to code you would use in a managed bean to call a method defined in the binding layer. This is one big advantage as the access method and the way to call a method are ‘equal’. This should result in lesser errors.

This finishes the mini series about file/image handling in ADF. The workspace can be downloaded from BlogUploadDownloadV2.zip. Please rename the file to ‘.zip’ after downloading it! You have to check the DB connection. Please don’t forget to add the needed tables for the catalog and the image data. The SQL DDL can be fond in the ULDLDatabaseModel. Instructions on how to setup the DB can be found in Part 1
The Commons IO package in the version 2.0.1 you can download from the Apache Software Foundation apache web side

About these ads

31 thoughts on “JDev11.1.2.1.0: Handling images/files in ADF (Part 3)

  1. Dear Timo
    you can upload pdf file and the application know the format but when you try to download it become without extension (mybook.pdf) became (mybook) but you can add the extension .pdf and you can open it after downloading
    how you can resolve this issue

    best regards

    • Sorry, I don’t get your question. My sample stores the file name, suffix and the content type. The content type is ‘guessed’ from the file name suffix. So if you send the file name and content type back to the client it should work OK. The essential part is that the content type is set correctly as this it used by the browser to act on. If the content type is e.g. ‘application/pdf’ the browser sends the data to the pdf reader set up in your system.

      Timo

  2. Dear Timo

    it is my mistake it is working , but what about the other types of files like audio and video files

  3. Hi ,
    Thanks for the tutorial.
    When i m uploading image, it uploaded successfully but when i m updating image with existing image it doesn’t refresh.
    For refreshing we have to call reload method of browser.
    Is there another way of doing this as i don’t want to call reload.

    • Hello Raj,
      I meet exactly the same problem : When I’m changing my image, I’m doing a partial refresh of the component, in order to see the new image.
      It works fine on Google Chrome, but not on Internet Explorer and Firefox (the servlet is not called again, so the new image doesn’t appear).
      Did you find a workaround for this problem.
      Thanks,
      Laurent

  4. hi,
    I am getting following error while executing the application. The only change i made from your approach is instead mapping to imageServlet I mapped to QuartzServlet(I am executing Appmodule method in quartzServlet) in ADFBindings filter mappings.

    Automatically initializing a DefaultContext for getCurrent.
    Caller should ensure that a DefaultContext is proper for this use.
    Memory leaks and/or unexpected behaviour may occur if the automatic initialization is performed improperly.
    This message may be avoided by performing initADFContext before using getCurrent().
    For more information please enable logging for oracle.adf.share.ADFContext at FINEST level.
    Inside catch of Quartz servlet
    java.lang.NullPointerException
    at com.mmi.enc.servlet.QuartzServlet.init(QuartzServlet.java:65)
    at weblogic.servlet.internal.StubSecurityHelper$ServletInitAction.run(StubSecurityHelper.java:283)
    at weblogic.security.acl.internal.AuthenticatedSubject.doAs(AuthenticatedSubject.java:321)
    at weblogic.security.service.SecurityManager.runAs(SecurityManager.java:120)

    From what i understand is BindingContext is not yet initialized to access the method in servlet.

    Please suggest me if I missed anything in web.xml mapping

  5. I only want to say if this is the proper way to uploade/dowan load club or blub, ADF and all it users are screwed, No wonder nobody is useng ADF

    • Well, this is not the place to criticize ADF and it’s usage. I don’t know if this is the ‘proper’ way, it’s one way to make it work. Your comment don’t let us know what you feel is so bad about it.
      I’m not responsible for ADF, I’m only using it and try to help others to use it too. If you don’t like it use whatever technique may suits your needs or abandon ADF at all. It’s your choice!

  6. Hi Timo, I have tried to use the approach suggested by you.I am using a programmatic VO to store the Image returned by a webservice .
    When i try to do lBinding.getResult() – to fetch the result of method action ivoke,it actually returns a null.Any pointers on what could be the issue.

    • Sorry, I can’t comment on this as I don’t know how the WS is implemented and/or bound to the method you call. Have you tested that the call to the WS is OK and that the WS returns the data (on the server side)?

      • Yes, The ws returns data.To simplify the process, I have just harcoded the response from WS in Image Servlet and trying to use it directly…in the below way:

        String str = “” [hardcoding the image str returned from the WS]
        byte[] bytes = str.getBytes();
        Blob blob = null;
        blob = new javax.sql.rowset.serial.SerialBlob(bytes);
        image = new BlobDomain(blob);
        IOUtils.copy(image.getInputStream(), outputStream);

        I am not able to see any image on my jspx page even when I follow the above approach.
        I am not passing any parameter from jspx page.So in that case what would my image tag be..

      • You should omit the BlobDomain as it is the Oracle db data type. As you are using a byte array you can just pass this array to the output stream like:
        byte[] bytes = str.getBytes();
        IOUtils.write(bytes, outputStream);

        That should do the trick.

  7. I’m having an error:

    Error 500–Internal Server Error after running it.

    oracle.jbo.DMLException: JBO-29114 ADFContext is not setup to process messages for this exception. Use the exception stack trace and error code to investigate the root cause of this exception. Root cause error code is JBO-27200. Error message parameters are {0=Data Source, 1=jdbc/HRDS}

    • You have to define the datasource jdbc/HRDS in your manages server (the one you deploy the app to, or the embedded if you run the app inside jdev).

      • but how can i do that? can you help me if you are not busy…..thanks for the quick reply :)

  8. I am getting below error , any body can you help me , i have create data source with jdbc/HRDS . Even i am getting below error in this application. Caused By: javax.naming.NameNotFoundException: Unable to resolve ‘jdbc.HRDS’. Resolved ‘jdbc'; remaining name ‘HRDS’

    regards.

    • You have to setup the jdbc connection (jdbc data source) so that you can reach the your DB. If you don’t want to install the connection in the WLS server, you have to change the application module configuration to use e.g. a jdbc url instead of a jdbc data source.

      • HI Timo, I am new to ADF jdev , I am using 12c can you please help me where i have to go to change this data source to jdbc url. I need exact step. Thanks for your response.

      • HI Timo , Applicaiton is working now. I had a mistake while creating data source, i didn’t select target server while creating data source. But still I need need step to configure how set up data source and jdbc url.

  9. Hi Tom ,

    I have a requirement like , the upload and display image should be same page. I am able to get the image from persistence object using another Vo ( like your imageAccess) but i could not do in the same view. Can you please advice me.

  10. I have a requirement like , the upload and display image should be in the same page , user can view the image before going to commit the form. I am able to get the image from persistence object using another Vo ( like your imageAccess) but i could not do in the same view. Can you please advice me

  11. synchronized (this) ???

    I just find out that one of our developers used this example and implemented such feature in our production application and we spotted the introduced performance problem during stress testing:

    Synchronising servlets is far from a good idea. A servlet works as a singleton and since there is a single instance created, if you synchronise that instance, it means the servlet will handle one single request at time. You don’t need syncronization in that piece of code anyway.

  12. Pingback: ADF Tutorials: Time Manager and Application Scope Updates. | Oralublog - Oralution's Blog

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