How to submit a serialised object from an Applet,

2019-08-08 10:22发布

问题:

I am an applications programmer doing my first JSF 2.0 web site and confess that I don't know as much as I should about JSF. I have been pooring over documents for months and, thanks to these forums in particular, have not gotten stuck up to this point. The bulk of the web site is finished and working and the backing bean used here is used elsewhere without problems.

I have a serialised search criteria object that needs to be submitted from an applet to a backing bean, via a servlet. A backing bean method then processes the search criteria data and applies it to a list of products, held in a database, and then displays the list of ranked products in a new JSF page.

All attempts to open a results page with the correct data have failed. The navigation-case "return "process_MainSearchResult";" is not doing anything in the backing bean (see backing bean code down further and faces-config entry). I have tried opening a results page from the applet using appletContext.showDocument (see below) but the new page does not have the backing bean that the search criteria object was passed to and therefore none of the correct data.

Note that setting POST on the setRequestMethod in the applet has no effect; it always uses a service. If setDoInput and setDoOutput are not set to true and a response sent from the servlet back to the applet then the submitted object is not sent and nothing happens. I suspect that this is at the core of my problems but altering it in any way stops the serialised object from being submitted successfully.

As it stands, the object is successfully sent to the backing bean but the new page will not load with the correct data (using the showDocument in the applet rather than the redirect in the servlet). Is there a way of setting the original backing bean on the new web page or am I doing this all wrong?

Web server is Glassfish 3.x, IDE is Netbeans 7.0.1, System is WinXP. Backing bean class is 'ProductSelection'; servlet class is 'CriteriaServlet'.

Applet "Submit Search Criteria" button code:

private void jButton8ActionPerformed(java.awt.event.ActionEvent evt)                                         
{                                             
    criteriaModel.loadCodeBase();
    int choice = JOptionPane.showConfirmDialog(this,
         "Are you sure you want to submit your search criteria and exit the \"Customise Search Criteria\" web page?",
         "Confirm Submit",
         JOptionPane.YES_NO_OPTION,
         JOptionPane.QUESTION_MESSAGE);

     if (choice == 0)
     {
         try 
         {      
             URL url;

             url = new URL(criteriaModel.getCodeBase(), "CriteriaServlet");

             System.out.println("Servlet address is: " + url);

            // Get the search criteria object. 
            Object searchSubmitObject = criteriaModel.getObjectSlideData();

            HttpURLConnection connection = (HttpURLConnection)url.openConnection();
            connection.setDoInput(true);
            connection.setDoOutput(true);
            connection.setUseCaches(false);
            connection.setRequestMethod("POST");
            connection.setRequestProperty("Content-Type","application/x-java-serialized-object");

            ObjectOutputStream out = new ObjectOutputStream(connection.getOutputStream());
            out.writeObject(searchSubmitObject);
            out.flush();
            out.close();
            out.close();

            System.out.println("Object Written");

            // If this and the corresponding servlet response code is removed
            // then the searchSubmitObject fails to be sent.
            ObjectInputStream in = new ObjectInputStream(connection.getInputStream());
            String response = (String)in.readObject();
            System.out.println(response);
            in.close();
         } 
         catch (MalformedURLException ex)
        {
            JOptionPane.showMessageDialog(jPanel8, "Submit criteria file Malformed URL."
                    + ex.toString());
            System.out.println("MalformedURLException occurred");
            Logger.getLogger(CriteriaInterfaceView.class.getName()).log(Level.SEVERE, null, ex);
        }
        catch (Exception e) 
         {
             System.out.println("Submit criteria file ERROR exception: " + e.toString());
             JOptionPane.showMessageDialog(jPanel8, "Submit criteria file ERROR exception:"
                    + e.toString());
         }
     }

    // This opens a new page but with a new backing bean with the wrong data.
    try
    {
        appletContext.showDocument(new URL(criteriaModel.getCodeBase()+"MainSearchResult.xhtml"),"_SELF");
    }
    catch (MalformedURLException ex)
    {
        Logger.getLogger(CriteriaInterfaceView.class.getName()).log(Level.SEVERE, null, ex);
    }
}   

I have tried redirecting in the servlet using redirect(url) with no success:

@Override
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException
{
    System.out.println("service(ServletRequest req, ServletResponse res)");

    res.setContentType("application/x-java-serialized-object");

    try
    {
        ObjectInputStream in = new ObjectInputStream(req.getInputStream());
        slideData = (MultipleSlideDataObject2)in.readObject();
        in.close();

        if(slideData != null)
        {
            System.out.println("Serial number of submitted slide series is: " + slideData.getSerialNumber());
        }

        String temp = "Criteria file Recieved";
        ObjectOutputStream outputToApplet = new ObjectOutputStream(res.getOutputStream());
        outputToApplet.writeObject(temp);
        outputToApplet.flush();          
        outputToApplet.close();
    }
    catch (ClassNotFoundException ex)
    {
        Logger.getLogger(CriteriaServlet.class.getName()).log(Level.SEVERE, null, ex);
    }

    FacesContext facesContext = FacesUtil.getFacesContext(req, res);
    // Get the backing bean.
    ProductSelection productSelection = (ProductSelection) facesContext.getApplication().evaluateExpressionGet(facesContext, "#{productSelection}", ProductSelection.class);
    productSelection.submitSearchCriteriaFile(slideData);

    // This throws an java.lang.IllegalStateException error.
    try 
    {
        FacesContext context = FacesContext.getCurrentInstance();

        ExternalContext extContext = context.getExternalContext();
        String url = extContext.encodeActionURL(context.getApplication().getViewHandler().getActionURL(context, "/MainSearchResult.xhtml"));

        extContext.redirect(url);
    } 
    catch (IOException e) 
    {
        throw new FacesException(e);
    }

Gives the following error because I suspect the current response has already been committed :

WARNING: StandardWrapperValve[CriteriaServlet]: PWC1406: Servlet.service() for servlet CriteriaServlet threw exception java.lang.IllegalStateException at org.apache.catalina.connector.ResponseFacade.sendRedirect(ResponseFacade.java:522) at com.sun.faces.context.ExternalContextImpl.redirect(ExternalContextImpl.java:572) at searchselection.CriteriaServlet.service(CriteriaServlet.java:217) at org.apache.catalina.core.StandardWrapper.service(StandardWrapper.java:1523) at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:279) at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:188) at org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:641) at com.sun.enterprise.web.WebPipeline.invoke(WebPipeline.java:97) at com.sun.enterprise.web.PESessionLockingStandardPipeline.invoke(PESessionLockingStandardPipeline.java:85) at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:185) at org.apache.catalina.connector.CoyoteAdapter.doService(CoyoteAdapter.java:325) at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:226) at com.sun.enterprise.v3.services.impl.ContainerMapper.service(ContainerMapper.java:165) at com.sun.grizzly.http.ProcessorTask.invokeAdapter(ProcessorTask.java:791) at com.sun.grizzly.http.ProcessorTask.doProcess(ProcessorTask.java:693) at com.sun.grizzly.http.ProcessorTask.process(ProcessorTask.java:954) at com.sun.grizzly.http.DefaultProtocolFilter.execute(DefaultProtocolFilter.java:170) at com.sun.grizzly.DefaultProtocolChain.executeProtocolFilter(DefaultProtocolChain.java:135) at com.sun.grizzly.DefaultProtocolChain.execute(DefaultProtocolChain.java:102) at com.sun.grizzly.DefaultProtocolChain.execute(DefaultProtocolChain.java:88) at com.sun.grizzly.http.HttpProtocolChain.execute(HttpProtocolChain.java:76) at com.sun.grizzly.ProtocolChainContextTask.doCall(ProtocolChainContextTask.java:53) at com.sun.grizzly.SelectionKeyContextTask.call(SelectionKeyContextTask.java:57) at com.sun.grizzly.ContextTask.run(ContextTask.java:69) at com.sun.grizzly.util.AbstractThreadPool$Worker.doWork(AbstractThreadPool.java:330) at com.sun.grizzly.util.AbstractThreadPool$Worker.run(AbstractThreadPool.java:309) at java.lang.Thread.run(Thread.java:619)

The return "process_MainSearchResult"; in the backing bean does not work :

public String submitSearchCriteriaFile(MultipleSlideDataObject2 slideData) throws IOException
{
    System.out.println("Recieved slide series with serial number: " + slideData.getSerialNumber());

    // If there is no slide data then...
    if (slideData == null)
    {
        return "process_MainSearchResultFailed";
    }
    else
    {
        rankProducts(slideData);
    }

    rowStart = 0;
    currentStartPage = 0;
    currentPageIndex = 0;
    calculateNumberPages();
    SetupPaginationValues();

    // Ignores this...
    return "process_MainSearchResult";
}

Faces-config.xml entry:

<navigation-rule>
    <navigation-case>
        <from-outcome>process_MainSearchResult</from-outcome>
        <to-view-id>/MainSearchResult.xhtml</to-view-id>
    </navigation-case>
</navigation-rule>

I have also tried this in the backing bean to force a redirect:

FacesContext context = FacesContext.getCurrentInstance();
    HttpServletResponse response = (HttpServletResponse)context.getExternalContext().getResponse();

    try
    {
        response.sendRedirect("MainSearchResult.xhtml");
        //response.redirect("http://localhost:8080/SearchEngineServer/faces/MainSearchResult.xhtml");
    }
    catch (IOException e)
    {
        e.printStackTrace();
    }

    return null;

and also this in the backing bean:

        redirectToPage("/MainSearchResult.xhtml");

Which calls this method:

private void redirectToPage(String toUrl) 
{
    try 
    {
        FacesContext ctx = FacesContext.getCurrentInstance();

        ExternalContext extContext = ctx.getExternalContext();
        String url = extContext.encodeActionURL(ctx.getApplication().getViewHandler().getActionURL(ctx, toUrl));

        extContext.redirect(url);
    } 
    catch (IOException e) 
    {
        throw new FacesException(e);
    }
}

All give the same java.lang.IllegalStateException error as for the servlet example given above. The documentation for redirect states this:

IllegalStateException - if, in a portlet environment, the current response object is a RenderResponse instead of an ActionResponse 
IllegalStateException - if, in a servlet environment, the current response has already been committed 

回答1:

The redirect has failed because you already wrote and committed the response before performing the redirect in the servlet. You seem to think that you can send multiple responses on a single request. This is actually not true. You can send only one HTTP response back per HTTP request, not more. Remove that whole block starting with the FacesUtil#getFacesContext() line. It doesn't belong there.

I'm not sure what that servlet is doing, it doesn't seem to do anything useful, but you should instead let the applet itself perform the "redirect" by AppletContext#showDocument() after having called the servlet. You can pass the search criteria (the properties of the Java object instance you're trying to serialize) to the JSF page/bean as GET request parameters the usual way and have JSF to collect it by @ManagedProperty or <f:viewParam> and process it by @PostConstruct or <f:event>.

E.g.

String query = "?param1=" + URLEncoder.encode(param1, "UTF-8")
             + "&param2=" + URLEncoder.encode(param2, "UTF-8")
             + "&param3=" + URLEncoder.encode(param3, "UTF-8");

getAppletContext().showDocument(new URL(getCodeBase(), "MainSearchResult.xhtml" + query), "_SELF");

with either

@ManagedBean
@RequestScoped
public class ProductSelection {

    @ManagedProperty("#{param.param1}")
    private String param1;

    @ManagedProperty("#{param.param2}")
    private String param2;

    @ManagedProperty("#{param.param3}")
    private String param3;

    @PostConstruct
    public void init() {
        // Do your business job based on the submitted request parameters.
    }

    // ...
}

or

<f:metadata>
    <f:viewParam name="param1" value="#{productSelection.param1}" />
    <f:viewParam name="param2" value="#{productSelection.param2}" />
    <f:viewParam name="param3" value="#{productSelection.param3}" />
    <f:event type="preRenderView" listener="#{productSelection.init}" />
</f:metadata>

When implementing it this way, then I think that the clumsy servlet step with all that Java serialization is entirely superfluous. You could just remove it. Also, this way you end up with a nicely bookmarkable, reuseable and searchbot-indexable page which can be opened independently from the applet/servlet.

See also:

  • ViewParam vs @ManagedProperty(value = "#{param.id}")
  • Communication in JSF 2.0 - Processing GET request parameters


回答2:

BalusC's answer is the appropriate answer for small amounts of object data, however, the object I am submitting to the backing bean is 2.2 megabytes and not suitable for encoding in the URL. Further more I don't want people bookmarking this particular page as more attributes may be added to the search criteria file in the future and this would make the book mark properties invalid.

The solution I am using is very low tech but it works. The applet submits the serialised object to the servlet, which in turn passes it to the backing bean, and then returns a fail or succeed message to the applet. If the submission succeeds then the applet calls a javascript function on the web page to load the results page. This ensures that the correct backing bean is retained.

The final code is as follows:

Applet "Submit Search Criteria" button code:

private void jButton8ActionPerformed(java.awt.event.ActionEvent evt)                                         
{                                             
    criteriaModel.loadCodeBase();
    int choice = JOptionPane.showConfirmDialog(this,
         "Are you sure you want to submit your search criteria and exit the \"Customise Search Criteria\" web page?",
         "Confirm Submit",
         JOptionPane.YES_NO_OPTION,
         JOptionPane.QUESTION_MESSAGE);

     if (choice == 0)
     {
         try 
         {      
             URL url = new URL(criteriaModel.getCodeBase(), "CriteriaServlet");

             System.out.println("Servlet address is: " + url);

            Object searchSubmitObject = criteriaModel.getObjectSlideData();

            HttpURLConnection connection = (HttpURLConnection)url.openConnection();
            connection.setDoInput(true);
            connection.setDoOutput(true);
            connection.setUseCaches(false);
            connection.setRequestMethod("POST");
            connection.setRequestProperty("Content-Type","application/x-java-serialized-object");

            ObjectOutputStream out = new ObjectOutputStream(connection.getOutputStream());
            out.writeObject(searchSubmitObject);
            out.flush();
            out.close();
            out.close();

            System.out.println("Object Written");

            ObjectInputStream in = new ObjectInputStream(connection.getInputStream());
            String response = (String)in.readObject();
            System.out.println(response);
            in.close();

            if(response.equals("Failed"))
            {
                JOptionPane.showMessageDialog(jPanel8, "Submit Search criteria file to server failed.\n Try Again later.");
            }
            else
            {
                getAppletContext().showDocument(new URL("javascript:openResultsPage()"));
            }
         } 
         catch (MalformedURLException ex)
        {
            JOptionPane.showMessageDialog(jPanel8, "Submit criteria file Malformed URL."
                    + ex.toString());
            System.out.println("MalformedURLException occurred");
            Logger.getLogger(CriteriaInterfaceView.class.getName()).log(Level.SEVERE, null, ex);
        }
        catch (Exception e) 
         {
             System.out.println("Submit criteria file ERROR exception: " + e.toString());
             JOptionPane.showMessageDialog(jPanel8, "Submit criteria file ERROR exception:"
                    + e.toString());
         }
     }         
}      

In the servlet:

@Override
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException
{
    System.out.println("service(ServletRequest req, ServletResponse res)");

    res.setContentType("text/plain");

    try
    {
        ObjectInputStream in = new ObjectInputStream(req.getInputStream());
        slideData = (MultipleSlideDataObject2)in.readObject();
        in.close();

        String reply = "Failed";

        if(slideData != null)
        {
            System.out.println("Serial number of submitted slide series is: " + slideData.getSerialNumber());

            FacesContext facesContext = FacesUtil.getFacesContext(req, res);
            ProductSelection productSelection = (ProductSelection) facesContext.getApplication().evaluateExpressionGet(facesContext, "#{productSelection}", ProductSelection.class);
            productSelection.submitSearchCriteriaFile(slideData);

            reply = "Success";
        }

        ObjectOutputStream outputToApplet = new ObjectOutputStream(res.getOutputStream());
        outputToApplet.writeObject(reply);
        outputToApplet.flush();          
        outputToApplet.close();
    }
    catch (ClassNotFoundException ex)
    {
        Logger.getLogger(CriteriaServlet.class.getName()).log(Level.SEVERE, null, ex);
    }
}

In the backing bean:

public String submitSearchCriteriaFile(MultipleSlideDataObject2 slideData) throws IOException
{
    System.out.println("Recieved slide series with serial number: " + slideData.getSerialNumber());

    // If there is no slide data then...
    if (slideData == null)
    {
        return "process_MainSearchResultFailed";
    }
    else
    {
        rankProducts(slideData);
    }

    return "process_MainSearchResult";
}

In the header of the JSF page, which contains the applet:

<SCRIPT language="javascript">

    function openResultsPage()
    {
        window.location = "MainSearchResult.xhtml";
    }

    </SCRIPT>

FacesUtil based on BalusC's FacesUtil class (couple of minor changes to the request and response types) Used to get the backing bean in the servlet:

    package searchselection;

import javax.faces.FactoryFinder;
import javax.faces.component.UIViewRoot;
import javax.faces.context.FacesContext;
import javax.faces.context.FacesContextFactory;
import javax.faces.lifecycle.Lifecycle;
import javax.faces.lifecycle.LifecycleFactory;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

public class FacesUtil
{
    // Getters -----------------------------------------------------------------------------------

    public static FacesContext getFacesContext(
        ServletRequest request, ServletResponse response)
    {
        // Get current FacesContext.
        FacesContext facesContext = FacesContext.getCurrentInstance();

        // Check current FacesContext.
        if (facesContext == null) {

            // Create new Lifecycle.
            LifecycleFactory lifecycleFactory = (LifecycleFactory)
                FactoryFinder.getFactory(FactoryFinder.LIFECYCLE_FACTORY); 
            Lifecycle lifecycle = lifecycleFactory.getLifecycle(LifecycleFactory.DEFAULT_LIFECYCLE);

            // Create new FacesContext.
            FacesContextFactory contextFactory  = (FacesContextFactory)
                FactoryFinder.getFactory(FactoryFinder.FACES_CONTEXT_FACTORY);
            facesContext = contextFactory.getFacesContext(
                request.getServletContext(), request, response, lifecycle);

            // Create new View.
            UIViewRoot view = facesContext.getApplication().getViewHandler().createView(
                facesContext, "");
            facesContext.setViewRoot(view);                

            // Set current FacesContext.
            FacesContextWrapper.setCurrentInstance(facesContext);
        }

        return facesContext;
    }

    // Helpers -----------------------------------------------------------------------------------

    // Wrap the protected FacesContext.setCurrentInstance() in a inner class.
    private static abstract class FacesContextWrapper extends FacesContext {
        protected static void setCurrentInstance(FacesContext facesContext) {
            FacesContext.setCurrentInstance(facesContext);
        }
    } 

}