Como você pode canalizar um OutputStream a uma StreamingDataHandler?
Pergunta
Eu tenho um serviço web Java em JAX-WS que retorna um OutputStream de outro método. Eu não consigo descobrir como para transmitir o OutputStream para o DataHandler voltou qualquer outra maneira do que para criar um arquivo temporário, escrever para ele, em seguida, abri-lo novamente para cima como um InputStream. Aqui está um exemplo:
@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;
}
}
A questão principal é que o método writeToOut () pode retornar dados que são muito maiores do que a memória do computador. É por isso que o método está usando MTOM em primeiro lugar - para transmitir os dados. Eu não consigo envolver minha cabeça em torno de como transmitir os dados diretamente do OutputStream que eu preciso para fornecer ao DataHandler retornado (e, finalmente, o cliente, que recebe o StreamingDataHandler).
Eu tentei brincar com PipedInputStream e PipedOutputStream, mas esses não parecem ser bastante o que eu preciso, porque o DataHandler precisaria ser devolvido após o PipedOutputStream está escrito.
Todas as idéias?
Solução
Eu descobri a resposta, ao longo das linhas que Christian estava falando (criando um novo segmento para executar 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;
}
}
É um pouco mais complexo devido a variáveis ??final
, mas tanto quanto eu posso dizer isso é correto. Quando o segmento é iniciado, ele bloqueia quando se tenta primeiro out.write()
chamada; ao mesmo tempo, o fluxo de entrada é devolvido ao cliente, que desbloqueia a escrita através da leitura dos dados. (O problema com as minhas implementações anteriores desta solução foi que eu não estava fechando corretamente o fluxo e, assim, correr em erros.)
Outras dicas
Desculpe, eu só fiz isso para C # e não java, mas eu acho que o seu método deve lançar uma linha para executar "writeToOut (sai);" em parralel. Você precisa criar um fluxo especial e passá-lo para o novo segmento que dá esse fluxo para writeToOut. Depois de iniciar o fio você retornar esse fluxo-objeto com o seu interlocutor.
Se você só tem um método que grava em um fluxo e retorna depois e um outro método que consome um fluxo e retorna depois, não há outro caminho.
De coure a parte difícil é se apossar de tal -multithreading fluxo safe:. Deve bloquear cada lado, se um buffer interno está muito cheia
Não sei se um Java-pipe-stream funciona para isso.
Wrapper? : -).
implementação personalizada javax.activation.DataSource (apenas 4 métodos) para ser capaz de fazer isso?
return new DataHandler(new DataSource() {
// implement getOutputStream to return the stream used inside writeToOut()
...
});
Eu não tenho o IDE disponível para testar isso, então eu só estou fazendo uma sugestão. Eu também preciso do layout geral writeToOut :-).
Na minha aplicação eu uso implementação InputStreamDataSource que tomar InputStream como argumento construtor em vez de arquivo no FileDataSource. Ele funciona até agora.
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");
}
}