How can I convert an InputStream to a DataHandler?
Asked Answered
O

8

31

I'm working on a Java web application in which files will be stored in a database. Originally, we retrieved files already in the DB by simply calling getBytes on our result set:

byte[] bytes = resultSet.getBytes(1);
...

This byte array was then converted into a DataHandler using the obvious constructor:

dataHandler = new DataHandler(bytes, "application/octet-stream");

This worked great until we started trying to store and retrieve larger files. Dumping the entire file contents into a byte array and then building a DataHandler out of that simply requires too much memory.

My immediate idea is to retrieve a stream of the data in the database with getBinaryStream and somehow convert that InputStream into a DataHandler in a memory-efficient way. Unfortunately it doesn't seem like there's a direct way to convert an InputStream into a DataHandler. Another idea I've been playing with is reading chunks of data from the InputStream and writing them to the OutputStream of the DataHandler. But... I can't find a way to create an "empty" DataHandler that returns a non-null OutputStream when I call getOutputStream...

Has anyone done this? I'd appreciate any help you can give me or leads in the right direction.

Obliteration answered 13/5, 2010 at 21:49 Comment(0)
S
18

My approach would be to write a custom class implementing DataSource that wraps your InputStream. Then create the DataHandler giving it the created DataSource.

Sunder answered 13/5, 2010 at 21:56 Comment(3)
Ah, that's a great idea. I'll try that when I get a chance.Obliteration
I thought the same. But beware, that then the DataHandler must be used (consume its input), "inside you loop", while the ResultSet is open. For example, you cant probably pass the DataHandler object to an upper layer.Sawfish
@Sawfish The stated goal was to process the data without copying it from result set. This implies that the result set must be open the entire time regardless of how you do it.Sunder
O
24

An implementation of the answer from Kathy Van Stone:

At first, create a helper class, which creates a DataSource from an InputStream:

public class InputStreamDataSource implements DataSource {
    private InputStream inputStream;

    public InputStreamDataSource(InputStream inputStream) {
        this.inputStream = inputStream;
    }

    @Override
    public InputStream getInputStream() throws IOException {
        return inputStream;
    }

    @Override
    public OutputStream getOutputStream() throws IOException {
        throw new UnsupportedOperationException("Not implemented");
    }

    @Override
    public String getContentType() {
        return "*/*";
    }

    @Override
    public String getName() {
        return "InputStreamDataSource";
    }
}

And then you can create a DataHandler from an InputStream:

DataHandler dataHandler = new DataHandler(new InputStreamDataSource(inputStream))

imports:

import javax.activation.DataSource;
import java.io.OutputStream;
import java.io.InputStream;
Oxheart answered 28/5, 2012 at 11:2 Comment(5)
getInputStream should return a new InputStream each time when calledNannettenanni
Could you explain the reason to this please ?Laporte
because reuse the InputStream could have an IOException for "Stream Closed"Discredit
General InputStream can be read only once, there is no reason wrap it into new stream.Oxheart
One could use org.apache.cxf.attachment.AttachmentDataSource instead of the class in this answer (ct = ContentType)Mcmahon
J
19

I also ran into this issue. If your source data is a byte[], Axis already has a class that wraps the InputStream and creates a DataHandler object. Here is the code

// This constructor takes byte[] as input
ByteArrayDataSource rawData = new ByteArrayDataSource(resultSet.getBytes(1));
DataHandler data = new DataHandler(rawData);
yourObject.setData(data);

Related imports

import javax.activation.DataHandler;
import org.apache.axiom.attachments.ByteArrayDataSource;
Jacqui answered 29/11, 2010 at 17:23 Comment(2)
Since it loads all the data to memory, it would cause problems when managing large data.Checkered
There are other implementations for the DataSource interface : I used import javax.mail.util.ByteArrayDataSource;Margenemargent
S
18

My approach would be to write a custom class implementing DataSource that wraps your InputStream. Then create the DataHandler giving it the created DataSource.

Sunder answered 13/5, 2010 at 21:56 Comment(3)
Ah, that's a great idea. I'll try that when I get a chance.Obliteration
I thought the same. But beware, that then the DataHandler must be used (consume its input), "inside you loop", while the ResultSet is open. For example, you cant probably pass the DataHandler object to an upper layer.Sawfish
@Sawfish The stated goal was to process the data without copying it from result set. This implies that the result set must be open the entire time regardless of how you do it.Sunder
N
4

Note that the getInputStream of the DataSource must return a new InputStream every time called. This means you need to copy it somewhere first.

For more information, see https://bugs.java.com/bugdatabase/view_bug?bug_id=4267294

Nitrile answered 30/1, 2011 at 23:32 Comment(4)
I know it is old...is that bug actual ?Grenade
The API says that. However, it says return a new stream or throw an exception. Technically, that means return a stream the first time and then throw exceptions. I assume that most frameworks only retrieve the stream once.Kersten
The link is (effectively) broken: It redirects to a generic page, "Java Bug Database"Jilolo
OK, the OP has left the building: "Last seen more than 11 years ago"Jilolo
M
2

bugs_'s code doesn't work for me. I use DataSource to create attachments to email (from objects that have inputStream and name) and content of attachments lost.

It looks like Stefan is right and a new inputStream must be returned every time. At least in my specific case. The following implementation deals with the problem:

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");
    }
}
Materiality answered 11/2, 2016 at 17:3 Comment(0)
P
0

I've met the situation when InputStream requested from DataSource twice: using a logging handler together with MTOM feature.

With this proxy stream solution, my implementation works fine:

import org.apache.commons.io.input.CloseShieldInputStream;
import javax.activation.DataHandler;
import javax.activation.DataSource;
...

private static class InputStreamDataSource implements DataSource {
    private InputStream inputStream;

    @Override
    public InputStream getInputStream() throws IOException {
        return new CloseShieldInputStream(inputStream);
    }

    @Override
    public OutputStream getOutputStream() throws IOException {
        throw new UnsupportedOperationException("Not implemented");
    }

    @Override
    public String getContentType() {
        return "application/octet-stream";
    }

    @Override
    public String getName() {
        return "";
    }
}
Provolone answered 29/4, 2017 at 0:22 Comment(0)
J
0

Here is an answer for specifically working with the Spring Boot org.springframework.core.io.Resource object which is, I think, how a lot of us are getting here. Note that you might need to modify the content type in the code below as I'm inserting a PNG file into an HTML formatted email.

Note: As others have mentioned, merely attaching an InputStream isn't enough as it gets used multiple times. Just mapping through to Resource.getInputStream() does the trick.

public class SpringResourceDataSource implements DataSource {
    private Resource resource;

    public SpringResourceDataSource(Resource resource) {
        this.resource = resource;
    }

    @Override
    public InputStream getInputStream() throws IOException {
        return resource.getInputStream();
    }

    @Override
    public OutputStream getOutputStream() throws IOException {
        throw new UnsupportedOperationException("Not implemented");
    }

    @Override
    public String getContentType() {
        return "image/png";
    }

    @Override
    public String getName() {
        return "SpringResourceDataSource";
    }
}

Usage of the class looks like this:

PathMatchingResourcePatternResolver pathMatchingResourcePatternResolver = new PathMatchingResourcePatternResolver();
Resource logoImage = pathMatchingResourcePatternResolver.getResource("/static/images/logo.png");
MimeBodyPart logoBodyPart = new MimeBodyPart();
DataSource logoFileDataSource = new SpringResourceDataSource(logoImage);

logoBodyPart.setDataHandler(new DataHandler(logoFileDataSource));
Jackinthebox answered 6/2, 2018 at 2:4 Comment(0)
N
-1
import com.itextpdf.text.Document;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.pdf.PdfWriter;
import com.itextpdf.tool.xml.XMLWorkerHelper;
import org.apache.commons.io.IOUtils;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;

.
.
.

 DataSource ds = new ByteArrayDataSource(convertHtmlToPdf("<span>html here</span>"), "application/pdf");

 DataHandler dataHandler = new DataHandler(ds);

.
.
.

public static byte[] convertHtmlToPdf(String htmlString) throws IOException, DocumentException {
    Document document = new Document();

    ByteArrayOutputStream out = new ByteArrayOutputStream();

    PdfWriter writer = PdfWriter.getInstance(document, out);
    document.open();

    InputStream in = IOUtils.toInputStream(htmlString);
    XMLWorkerHelper.getInstance().parseXHtml(writer, document, in);
    document.close();

    return out.toByteArray();
}

possible error: the meta tag must be closed. <meta></meta>

Nahshunn answered 15/4, 2021 at 13:59 Comment(3)
What is the point of this "answer"?Jilolo
Related: Why do I need 50 reputation to comment? What can I do instead?.Jilolo
Can you provide some context?Jilolo

© 2022 - 2024 — McMap. All rights reserved.