2009-05-14 29 views
8

Tengo un servicio web Java en JAX-WS que devuelve un OutputStream desde otro método. Parece que no puedo entender cómo transmitir el OutputStream al DataHandler devuelto de otra manera que no sea crear un archivo temporal, escribir en él y luego abrirlo de nuevo como un InputStream. He aquí un ejemplo:¿Cómo se puede canalizar un OutputStream a un 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; 
    } 
} 

El principal problema es que el método writeToOut() puede devolver los datos que son mucho más grandes que la memoria del ordenador. Es por eso que el método está utilizando MTOM en primer lugar: para transmitir los datos. Parece que no puedo entender cómo transmitir los datos directamente desde el OutputStream que debo proporcionar al DataHandler devuelto (y en última instancia al cliente, que recibe el StreamingDataHandler).

He intentado jugar con PipedInputStream y PipedOutputStream, pero esos no parecen ser todo lo que necesito, porque el DataHandler tendría que ser devuelto después de que se escribió el PipedOutputStream.

¿Alguna idea?

+0

Véase también [esta cuestión] (http: // stackoverflow.com/questions/2830561/how-to-convert-an-inputstream-to-a-datahandler) – schnatterer

Respuesta

4

me di cuenta de la respuesta, a lo largo de las líneas que Christian estaba hablando (la creación de un nuevo hilo para ejecutar 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; 
    } 
} 

Es un poco más complejo debido a final las variables, sino como Hasta donde yo sé, esto es correcto. Cuando se inicia el subproceso, bloquea cuando primero intenta llamar al out.write(); al mismo tiempo, la secuencia de entrada se devuelve al cliente, quien desbloquea la escritura leyendo los datos. (El problema con mis implementaciones anteriores de esta solución era que no estaba cerrando correctamente la secuencia y, por lo tanto, se producían errores)

+0

Realmente no sé mucho sobre esto, pero asegúrese de que las Transmisiones Pipe * sean seguras para hilos o utilicen esa palabra clave "sincronizada" con la que no estoy familiarizado. – Christian

+0

Tenga en cuenta que el [javadoc de 'javax.mail.util.ByteArrayDataSource'] (http://docs.oracle.com/javaee/6/api/javax/mail/util/ByteArrayDataSource.html#ByteArrayDataSource%28java.io .InputStream,% 20java.lang.String% 29) indica que 'InputStream' se lee en la memoria por completo en la construcción. Esto puede dar como resultado un 'OutOfMemoryError' cuando se trata de archivos de gran tamaño – schnatterer

1

¿Patrón de envoltura? :-).

Implementación personalizada javax.activation.DataSource (solo 4 métodos) para poder hacer esto?

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

No tengo el IDE disponible para probar esto, así que solo estoy haciendo una sugerencia. También necesitaría el diseño general writeToOut :-).

+0

Se puede encontrar una implementación en funcionamiento en [esta respuesta] (http://stackoverflow.com/a/10783565/1845976). – schnatterer

3

Lo siento, solo hice esto para C# y no para Java, pero creo que su método debería iniciar un hilo para ejecutar "writeToOut (out);" en parralel. Necesita crear una secuencia especial y pasarla al nuevo subproceso que le da esa secuencia a writeToOut. Después de iniciar el hilo, devuelve ese objeto de flujo a su interlocutor.

Si solo tiene un método que escribe en una secuencia y lo devuelve después y otro método que consume una secuencia y lo devuelve después, no hay otra manera.

De la parte más difícil es agarrar una corriente segura de este tipo: bloqueará cada lado si un búfer interno está demasiado lleno.

No sé si un Java-pipe-stream funciona para eso.

+0

+1 - La idea es correcta, y le agradezco por ponerme en el camino correcto, pero mi respuesta tiene la solución Java real que quiero que las futuras personas puedan encontrar primero si se encuentran con el mismo problema. –

0

En mi aplicación utilizo la implementación InputStreamDataSource que toma InputStream como argumento de constructor en lugar de File en FileDataSource . Funciona hasta ahora.

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"); 
} 

}

Cuestiones relacionadas