2009-05-14 7 views
8

Ho un servizio Web Java in JAX-WS che restituisce un OutputStream da un altro metodo. Non riesco a capire come eseguire lo streaming di OutputStream nel DataHandler restituito in un altro modo se non quello di creare un file temporaneo, scrivere su di esso, quindi aprirlo di nuovo come InputStream. Ecco un esempio:Come si può reindirizzare un OutputStream a uno StreamingDataHandler?

@MTOM 
@WebService 
class Example { 
    @WebMethod 
    public @XmlMimeType("application/octet-stream") DataHandler service() { 
     // Create a temporary file to write to 
     File fTemp = File.createTempFile("my", "tmp"); 
     OutputStream out = new FileOutputStream(fTemp); 

     // Method takes an output stream and writes to it 
     writeToOut(out); 
     out.close(); 

     // Create a data source and data handler based on that temporary file 
     DataSource ds = new FileDataSource(fTemp); 
     DataHandler dh = new DataHandler(ds); 
     return dh; 
    } 
} 

Il problema principale è che il metodo writeToOut() può restituire i dati che sono molto più grandi di memoria del computer. Ecco perché il metodo sta utilizzando MTOM in primo luogo - per trasmettere i dati in streaming. Non riesco a capire come eseguire il flusso dei dati direttamente da OutputStream che devo fornire al DataHandler restituito (e in definitiva al client, che riceve StreamingDataHandler).

Ho provato a giocare con PipedInputStream e PipedOutputStream, ma non sembra proprio quello di cui ho bisogno, perché DataHandler dovrebbe essere restituito dopo che PipedOutputStream è stato scritto.

Qualche idea?

+0

Vedi anche [questa domanda] (http: // StackOverflow.it/questions/2830561/how-to-convert-an-inputstream-to-a-datahandler) – schnatterer

risposta

4

ho capito la risposta, lungo le linee che Christian stava parlando (la creazione di un nuovo thread per eseguire writeToOut()):

@MTOM 
@WebService 
class Example { 
    @WebMethod 
    public @XmlMimeType("application/octet-stream") DataHandler service() { 
     // Create piped output stream, wrap it in a final array so that the 
     // OutputStream doesn't need to be finalized before sending to new Thread. 
     PipedOutputStream out = new PipedOutputStream(); 
     InputStream in = new PipedInputStream(out); 
     final Object[] args = { out }; 

     // Create a new thread which writes to out. 
     new Thread(
      new Runnable(){ 
       public void run() { 
        writeToOut(args); 
        ((OutputStream)args[0]).close(); 
       } 
      } 
     ).start(); 

     // Return the InputStream to the client. 
     DataSource ds = new ByteArrayDataSource(in, "application/octet-stream"); 
     DataHandler dh = new DataHandler(ds); 
     return dh; 
    } 
} 

Si tratta di un po 'più complessa a causa di final variabili, ma come per quanto posso dire questo è corretto. Quando il thread è avviato, blocca quando tenta di chiamare per la prima volta out.write(); allo stesso tempo, lo stream di input viene restituito al client, che sblocca la scrittura leggendo i dati. (Il problema con le mie precedenti implementazioni di questa soluzione era che non stavo chiudendo correttamente lo stream, e quindi incappando in errori.)

+0

Non ne so molto, ma assicurati che i flussi di pipe * siano sicuri o utilizzino la parola chiave "sincronizzata" che non conosco. – Christian

+0

Si noti che [javadoc di 'javax.mail.util.ByteArrayDataSource'] (http://docs.oracle.com/javaee/6/api/javax/mail/util/ByteArrayDataSource.html#ByteArrayDataSource%28java.io .InputStream,% 20java.lang.String% 29) afferma che 'InputStream' viene letto completamente in memoria durante la costruzione. Ciò può causare un 'OutOfMemoryError' quando si ha a che fare con file di grandi dimensioni – schnatterer

1

Modello di avvolgimento? :-).

Implementazione personalizzata javax.activation.DataSource (solo 4 metodi) per poter eseguire questa operazione?

return new DataHandler(new DataSource() { 
    // implement getOutputStream to return the stream used inside writeToOut() 
    ... 
}); 

Non ho l'IDE a disposizione per testare questo modo che io faccio solo un suggerimento. Avrei anche bisogno del layout generale writeToOut :-).

+0

Un'implementazione funzionante può essere trovata in [questa risposta] (http://stackoverflow.com/a/10783565/1845976). – schnatterer

3

Mi dispiace, l'ho fatto solo per C# e non per Java, ma penso che il tuo metodo dovrebbe lanciare un thread per eseguire "writeToOut (out)"; in parallelo. Devi creare uno stream speciale e passarlo al nuovo thread che dà a quel flusso di writeToOut. Dopo aver avviato il thread, si restituisce l'oggetto stream al proprio chiamante.

Se si dispone solo di un metodo che scrive su uno stream e restituisce in seguito e un altro metodo che utilizza uno stream e restituisce in seguito, non c'è altro modo.

La parte più difficile è procurarsi un flusso sicuro di questo tipo: bloccherà ogni lato se un buffer interno è troppo pieno.

Non so se un flusso Java-pipe funziona per questo.

+0

+1 - L'idea è corretta, e ti ringrazio per avermi messo sulla strada giusta, ma la mia risposta ha l'effettiva soluzione Java che voglio che le persone future siano in grado di trovare prima se incontrano lo stesso problema. –

0

Nella mia applicazione utilizzo l'implementazione InputStreamDataSource che accetta InputStream come argomento del costruttore anziché File in FileDataSource . Funziona così lontano.

public class InputStreamDataSource implements DataSource { 

ByteArrayOutputStream buffer = new ByteArrayOutputStream(); 
private final String name; 

public InputStreamDataSource(InputStream inputStream, String name) { 
    this.name = name; 
    try { 
     int nRead; 
     byte[] data = new byte[16384]; 
     while ((nRead = inputStream.read(data, 0, data.length)) != -1) { 
      buffer.write(data, 0, nRead); 
     } 

     buffer.flush(); 
     inputStream.close(); 
    } catch (IOException e) { 
     e.printStackTrace(); 
    } 

} 

@Override 
public String getContentType() { 
    return new MimetypesFileTypeMap().getContentType(name); 
} 

@Override 
public InputStream getInputStream() throws IOException { 
    return new ByteArrayInputStream(buffer.toByteArray()); 
} 

@Override 
public String getName() { 
    return name; 
} 

@Override 
public OutputStream getOutputStream() throws IOException { 
    throw new IOException("Read-only data"); 
} 

}