2015-08-10 20 views
10

INFO - Przykładowy kodDlaczego CXF/JAXB przeczytać cały InputStream do pamięci przed etapowy do wiadomości SOAP

Wcześniej skonfigurować przykładowy kod (SSCCE), aby pomóc śledzić problem:

https://github.com/ljader/test-cxf-base64-marshall

problem

mam integracji z 3rd party usługi JAX-WS, więc ja nie mogę Chang e WSDL.

Usługa internetowa innego producenta oczekuje, że Base64 zakodowane bajty, aby wykonać na nich pewną operację - oczekują, że klient wyśle ​​całe bajty w komunikacie SOAP. One nie chcą zmienić na MTOM/XOP, więc utknąłem z bieżącymi wymaganiami.

Postanowiłem użyć CXF do łatwego ustawienia przykładowego klienta i działało dobrze dla małych plików.

Ale gdy próbuję wysłać Big Data, czyli 200MB, CXF/JAXB zgłasza wyjątek:

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space 
at com.sun.xml.bind.v2.util.ByteArrayOutputStreamEx.readFrom(ByteArrayOutputStreamEx.java:75) 
at com.sun.xml.bind.v2.runtime.unmarshaller.Base64Data.get(Base64Data.java:196) 
at com.sun.xml.bind.v2.runtime.unmarshaller.Base64Data.writeTo(Base64Data.java:312) 
at com.sun.xml.bind.v2.runtime.output.UTF8XmlOutput.text(UTF8XmlOutput.java:312) 
at com.sun.xml.bind.v2.runtime.XMLSerializer.leafElement(XMLSerializer.java:356) 
at com.sun.xml.bind.v2.model.impl.RuntimeBuiltinLeafInfoImpl$PcdataImpl.writeLeafElement(RuntimeBuiltinLeafInfoImpl.java:191) 
at com.sun.xml.bind.v2.runtime.MimeTypedTransducer.writeLeafElement(MimeTypedTransducer.java:96) 
at com.sun.xml.bind.v2.runtime.reflect.TransducedAccessor$CompositeTransducedAccessorImpl.writeLeafElement(TransducedAccessor.java:254) 
at com.sun.xml.bind.v2.runtime.property.SingleElementLeafProperty.serializeBody(SingleElementLeafProperty.java:130) 
at com.sun.xml.bind.v2.runtime.ClassBeanInfoImpl.serializeBody(ClassBeanInfoImpl.java:360) 
at com.sun.xml.bind.v2.runtime.XMLSerializer.childAsXsiType(XMLSerializer.java:696) 
at com.sun.xml.bind.v2.runtime.ElementBeanInfoImpl$1.serializeBody(ElementBeanInfoImpl.java:155) 
at com.sun.xml.bind.v2.runtime.ElementBeanInfoImpl$1.serializeBody(ElementBeanInfoImpl.java:130) 
at com.sun.xml.bind.v2.runtime.ElementBeanInfoImpl.serializeBody(ElementBeanInfoImpl.java:332) 
at com.sun.xml.bind.v2.runtime.ElementBeanInfoImpl.serializeRoot(ElementBeanInfoImpl.java:339) 
at com.sun.xml.bind.v2.runtime.ElementBeanInfoImpl.serializeRoot(ElementBeanInfoImpl.java:75) 
at com.sun.xml.bind.v2.runtime.XMLSerializer.childAsRoot(XMLSerializer.java:494) 
at com.sun.xml.bind.v2.runtime.MarshallerImpl.write(MarshallerImpl.java:323) 
at com.sun.xml.bind.v2.runtime.MarshallerImpl.marshal(MarshallerImpl.java:251) 
at javax.xml.bind.helpers.AbstractMarshallerImpl.marshal(AbstractMarshallerImpl.java:95) 
at org.apache.cxf.jaxb.JAXBEncoderDecoder.writeObject(JAXBEncoderDecoder.java:617) 
at org.apache.cxf.jaxb.JAXBEncoderDecoder.marshall(JAXBEncoderDecoder.java:241) 
at org.apache.cxf.jaxb.io.DataWriterImpl.write(DataWriterImpl.java:237) 
at org.apache.cxf.interceptor.AbstractOutDatabindingInterceptor.writeParts(AbstractOutDatabindingInterceptor.java:117) 
at org.apache.cxf.wsdl.interceptors.BareOutInterceptor.handleMessage(BareOutInterceptor.java:68) 
at org.apache.cxf.phase.PhaseInterceptorChain.doIntercept(PhaseInterceptorChain.java:308) 
at org.apache.cxf.endpoint.ClientImpl.doInvoke(ClientImpl.java:514) 
at org.apache.cxf.endpoint.ClientImpl.invoke(ClientImpl.java:423) 
at org.apache.cxf.endpoint.ClientImpl.invoke(ClientImpl.java:324) 
at org.apache.cxf.endpoint.ClientImpl.invoke(ClientImpl.java:277) 
at org.apache.cxf.frontend.ClientProxy.invokeSync(ClientProxy.java:96) 
at org.apache.cxf.jaxws.JaxWsClientProxy.invoke(JaxWsClientProxy.java:139) 

moich ustaleń

mam błąd gąsienicowe, że w oparciu o typ xsd "base64Binary", to

com.sun.xml.bind.v2.model.impl.RuntimeBuiltinLeafInfoImpl

zdecyduje, że

com.sun.xml.bind.v2.runtime.unmarshaller.Base64Data

powinien obsługiwać rozrządowych danych z

javax.activation.DataHandler

Podczas porządkowania, CAŁKOWITYCH danych z podstawowych InputS tream próbuje zostać odczytanym http://grepcode.com/file/repo1.maven.org/maven2/com.sun.xml.bind/jaxb-impl/2.2.11/com/sun/xml/bind/v2/runtime/unmarshaller/Base64Data.java/#311, co powoduje OOME wyjątek.

Problem

CXF wykorzystuje JAXB podczas zestawiania Java obiektów w wiadomości SOAP - podczas zestawiania InputStream, strumień wejściowy CAŁY jest odczytywany w pamięci przed beeing przekształcony Base64 binarny.

Więc chcę wysłać („strumień”) danych od klienta do serwera w kawałkami (od OutputSteam w naziemnego jest owinięty bezpośredni HttpURLConnection), więc mój klient mógłby może obsługiwać dowolną ilość wysyłania danych.

Szczególnie, gdy wiele wątków używa mojego klienta, przesyłanie strumieniowe jest bardzo pożądane przez IMHO.

Nie mam dobrej wiedzy JAX-WS/CXF/JAXB, stąd pytanie.

Jedyne materiały, które znalazłem i może być przydatne to:

Can JAXB parse large XML files in chunks

http://rezarahim.blogspot.com/2010/05/chunking-out-big-xml-with-stax-and-jaxb.html

pytania

  1. Dlaczego CXF/JAXB ładuje cały InputStream do pamięci - nie jest celą DataHandler, aby zapobiec takiej implementacji n?

  2. Czy znasz jakiś sposób, aby zmienić zachowanie JAXB, aby w różny sposób skonfigurować InputStream?

  3. Czy znasz różnych marszałkowców, którzy mogą obsłużyć tak duże gromadzenie danych?

  4. W ostateczności możesz mieć linki do niektórych materiałów, jak stworzyć niestandardowego koordynatora, który będzie przesyłał dane bezpośrednio na serwer?

Odpowiedz

4

Nie trzeba żadnych niestandardowych marshallers lub zmiany zachowań JAXB aby osiągnąć to, czego potrzebujesz - DataHandler jest Twój przyjaciel tutaj.

Odpowiedź na pierwsze pytanie: JAXB musi przechowywać wszystkie dane w pamięci, ponieważ musi rozwiązać odniesienia.

Wiem, że nie można zmienić odniesień WSDL itp. Ale nadal masz WSDL klienta w projekcie, aby wygenerować klasy klientów, prawda? Więc co możesz zrobić (nie przetestowałem tego z WSDL trzeciej strony, ale może być warte spróbowania) jest dodanie xmime:expectedContentTypes="application/octet-stream" do elementu XSD odpowiedzi, który zwraca dane zakodowane w Base64. Dla np .:

<xsd:element name="generateBigDataResponse"> 
    <xsd:complexType> 
     <xsd:sequence> 
      <xsd:element name="result" 
         type="xsd:base64Binary" 
         minOccurs="0" 
         maxOccurs="1" 
         xmime:expectedContentTypes="application/octet-stream"/> 
     </xsd:sequence> 
    </xsd:complexType> 
</xsd:element> 

Również nie zapomnij dodać nazw: xmlns:xmime="http://www.w3.org/2005/05/xmlmime" w elemencie xsd:schema.

Co tu robisz - nie zmienia żadnych odniesień WSDL, tylko mówi JAXB zamiast generować byte[], aby wygenerować DataHandler. Więc co się dzieje, kiedy wygenerować klasy klienckie tak:

@Override 
public DataHandler generateBigData() { 
    try { 
     final PipedOutputStream pipedOutputStream = new PipedOutputStream(); 
     PipedInputStream pipedInputStream = new PipedInputStream(pipedOutputStream); 
     InputStreamDataSource dataSource = new InputStreamDataSource(pipedInputStream, "application/octet-stream"); 

     executor.execute(new Runnable() { 

      @Override 
      public void run() { 
       //write your stuff here into pipedOutputStream 
      } 
     }); 

     return new DataHandler(dataSource); 
    } catch (IOException e) { 
     //handle exception if any 
    } 
} 

Otrzymasz DataHandler jako rodzaj reakcji dzięki xmime. Proponuję użyć PipedOutputStream, ale upewnij się zrobić napis w innym wątku:

rurami strumień wyjściowy może być połączone z rurami strumienia wejściowego do stworzyć rurę komunikacyjną. Potokowany strumień wyjściowy to przesyłany koniec rury. Zwykle dane są zapisywane do obiektu PipedOutputStream przez jeden wątek, a dane są odczytywane z połączonego obiektu PipedInputStream przez inny wątek. Próba użycia obu obiektów z jednego wątku nie jest zalecana, ponieważ może zakleić wątek. Rura jest przerywana, jeśli wątek, który odbierał bajty danych z podłączonego potokowego strumienia wejściowego, nie jest już aktywny.

Wtedy łączenia go z PipedInputStream która instancja idzie do konstruktora InputStreamDataSource które następnie przechodzą do DataHandler i powrócić instancję DataHandler „s. W ten sposób plik zostanie zapisany w porcjach i nie dostaniesz tego wyjątku, więcej - klient nigdy nie otrzyma limitu czasu.

Mam nadzieję, że to pomoże.

+0

** 1. ** Czy widziałeś mój przykładowy projekt (https://github.com/ljader/test-cxf-base64-marshall), do którego odwołuję się na początku mojego pytania? ** 2. ** W tym przykładzie robię już to, co sugerujesz, ale OOME nadal występuje - to dlatego, że implementacja JAXB ignoruje użycie DataHandler i nadal buforuje wszystko do pamięci (http://grepcode.com/file/ repo1.maven.org/maven2/com.sun.xml.bind/jaxb-impl/2.2.11/com/sun/xml/bind/v2/runtime/unmarshaller/Base64Data.java/#311) ** 3. ** Czy masz jakieś inne pomysły, jak to naprawić? – ljader

Powiązane problemy