0

We have a large wicket application that has an annoying bug. We have a form with a file upload field on it, and for the most part it works fine. The only time it fails is when the user has opened multiple browser tabs. Then we get the following exception (once for each extra tab opened):

java.lang.RuntimeException: Could not deserialize object from byte[]
    at org.apache.wicket.serialize.java.JavaSerializer.deserialize(JavaSerializer.java:143)
    at org.apache.wicket.pageStore.AbstractPageStore.deserializePage(AbstractPageStore.java:152)
    at org.apache.wicket.pageStore.AbstractCachingPageStore.getPage(AbstractCachingPageStore.java:67)
    at com.sw.system4.ui.RemovablePageManagerProvider$1.getPage(RemovablePageManagerProvider.java:51)
    at org.apache.wicket.page.PageStoreManager$SessionEntry.getPage(PageStoreManager.java:231)
    at org.apache.wicket.page.PageStoreManager$PersistentRequestAdapter.getPage(PageStoreManager.java:393)
    at org.apache.wicket.page.AbstractPageManager.getPage(AbstractPageManager.java:82)
    at org.apache.wicket.page.PageManagerDecorator.getPage(PageManagerDecorator.java:50)
    at org.apache.wicket.page.PageAccessSynchronizer$2.getPage(PageAccessSynchronizer.java:246)
    at org.apache.wicket.DefaultMapperContext.getPageInstance(DefaultMapperContext.java:113)
    at org.apache.wicket.core.request.handler.PageProvider.getStoredPage(PageProvider.java:299)
    at org.apache.wicket.core.request.handler.PageProvider.isNewPageInstance(PageProvider.java:211)
    at org.apache.wicket.core.request.mapper.AbstractBookmarkableMapper.checkExpiration(AbstractBookmarkableMapper.java:335)
    at org.apache.wicket.core.request.mapper.AbstractBookmarkableMapper.processListener(AbstractBookmarkableMapper.java:309)
    at org.apache.wicket.core.request.mapper.AbstractBookmarkableMapper.mapRequest(AbstractBookmarkableMapper.java:369)
    at org.apache.wicket.request.mapper.CompoundRequestMapper.mapRequest(CompoundRequestMapper.java:147)
    at org.apache.wicket.request.cycle.RequestCycle.resolveRequestHandler(RequestCycle.java:189)
    at org.apache.wicket.request.cycle.RequestCycle.processRequest(RequestCycle.java:219)
    at org.apache.wicket.request.cycle.RequestCycle.processRequestAndDetach(RequestCycle.java:293)
    at org.apache.wicket.protocol.http.WicketFilter.processRequestCycle(WicketFilter.java:261)
    at org.apache.wicket.protocol.http.WicketFilter.processRequest(WicketFilter.java:203)
    at org.apache.wicket.protocol.http.WicketFilter.doFilter(WicketFilter.java:284)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:220)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:122)
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:505)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:169)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:103)
    at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:956)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:116)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:423)
    at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1079)
    at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:625)
    at org.apache.tomcat.util.net.AprEndpoint$SocketProcessor.doRun(AprEndpoint.java:2517)
    at org.apache.tomcat.util.net.AprEndpoint$SocketProcessor.run(AprEndpoint.java:2506)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
    at java.lang.Thread.run(Unknown Source)
Caused by: java.io.FileNotFoundException: C:\Program Files\Apache Software Foundation\Tomcat 7.0\temp\upload_d17f019a_9bfa_48c5_bc2c_6fc0bf74d233_00004982.tmp (The system cannot find the file specified)
    at java.io.FileInputStream.open0(Native Method)
    at java.io.FileInputStream.open(Unknown Source)
    at java.io.FileInputStream.(Unknown Source)
    at org.apache.commons.fileupload.disk.DiskFileItem.readObject(DiskFileItem.java:684)
    at sun.reflect.GeneratedMethodAccessor4472.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at java.io.ObjectStreamClass.invokeReadObject(Unknown Source)
    at java.io.ObjectInputStream.readSerialData(Unknown Source)
    at java.io.ObjectInputStream.readOrdinaryObject(Unknown Source)
    at java.io.ObjectInputStream.readObject0(Unknown Source)
    at java.io.ObjectInputStream.defaultReadFields(Unknown Source)

    ... lots of read lines snipped

    at java.io.ObjectInputStream.readSerialData(Unknown Source)
    at java.io.ObjectInputStream.readOrdinaryObject(Unknown Source)
    at java.io.ObjectInputStream.readObject0(Unknown Source)
    at java.io.ObjectInputStream.readObject(Unknown Source)
    at org.apache.wicket.serialize.java.JavaSerializer.deserialize(JavaSerializer.java:126)

The FileUploadField is in a form that is part of a bigger page, so the user selects a file and uploads it with an ajax submit button before moving on.

Form<?> uploadForm = new Form<Void>( "uploadForm" );
uploadForm.setMultiPart( true );
add( uploadForm );

IModel<List<FileUpload>> filesModel = Model.ofList( new ArrayList<>() );

uploadForm.add( new FileUploadField( "uploadField", filesModel) );

uploadForm.add( new AjaxBeforeSubmitLink( "loadLink" ) {

  private static final long serialVersionUID = 1L;

  @Override
  protected void onSubmit( AjaxRequestTarget target, Form<?> form ) {
    List<? extends FileUpload> files = filesModel.getObject();

    if ( l_files != null && !l_files.isEmpty() ) {
      callEntryModel.getObject().getVendorQuoteData().getFileUploads().addAll( files );
    }

    target.add( getUploadedFilesLabel() );
    target.add( getClearFilesLink() );

    onUpdateContractQuoteData( target );
  }
});

The upload panel is part of a 'breadcrumb' style page using the BreadCrumbPanels from wicket extensions. The upload part is on a panel that is then replaced with the next panel in the breadcrumb. It is on that panel when they try to do anything that results in an ajax call that the error occurs.

From what I can tell, there is some sort of hashing function of the client request to define the name of the temporary file to load, which is different for different tabs? Has anyone else had this problem, I can't seem to find any references to it anywhere.

We are using Java 8/Wicket 7.10

Thanks in advance

fancyplants
  • 1,577
  • 3
  • 14
  • 25

1 Answers1

1

It seems you keep a reference to FileItem object in your page.

Wicket tries to deserialize the page from its page store and during this process it tries to load a org.apache.commons.fileupload.disk.DiskFileItem for which there is no backing file anymore.

Wicket's FileUploadField has a transient field - a List<FileUpload>. But since it is transient it won't be serialized at all. Later when its public List<FileUpload> getFileUploads() is called this field will be null and Wicket will use commons-fileupload APIs to load it again.

To me it seems that your application keeps a reference to the DiskFileItem somewhere and because of this it is serialized/deserialized.

You can use Wicket IObjectChecker to find where it is being referenced. See SessionChecker and DifferentPageChecker for inspiration how to implement a custom checker.

martin-g
  • 17,243
  • 2
  • 23
  • 35
  • Wow! That was a quick response! Thanks I will look into this. I'm guessing at the point the file uploads model is populated, I will need to store their content as a byte[] instead and then discard the FileUploads themselves. – fancyplants Apr 18 '19 at 09:06
  • Hi Martin, thanks for sorting this out it did the trick! The breadcrumb process kept hold of various things before reaching the end, one of which was all the FileUpload entries the user selected. I've created a FileUploadData class as a drop-in replacement that does serialize fine. What I can't work out is that this error is intermittent - 9 times out of 10 it works ok when the FileUpload is serialized; there seems to be some combination of moving between breadcrumbs and uploading files that triggers it. – fancyplants May 08 '19 at 08:10
  • 1
    After usage of a stateful page Wicket stores it both in the http session and the page store (normally on disk). Only the last used page is kept in the http session in non-serialized form, i.e. as a live object. So if your page flow does not involve a different page instance then Wicket will always lookup the page instance from the session and since there is no serialize+deserialize everything will just work. But if there is another page instance in the session than the needed one then Wicket will load it from the disk and deserialize it. Here it will fail in your case. – martin-g May 08 '19 at 08:12