How to populate h:graphicImage value with image content from database?

Cacheing picture Cacheing · Feb 25, 2013 · Viewed 12.7k times · Source

I have got the Image object in my ManagedBean. How can I get it in my JSF page?

This seems to be not working: <h:graphicImage value="#{userProfile.image}" /> where image is a field variable in the class userProfile.

The Image is got from MySql as below.

int len = rs.getInt("ImageLen");
if(len != 0){
    byte[] b = new byte[len];
    InputStream in = rs.getBinaryStream("Image");
    in.read(b);
    in.close();
    this.image = Toolkit.getDefaultToolkit().createImage(b);
}

The error I got is:java.lang.ClassCastException - sun.awt.image.ToolkitImage cannot be cast to java.lang.String.

Answer

BalusC picture BalusC · Feb 25, 2013

There's a major misunderstanding here. JSF is basically a HTML code producer. In HTML, images are not inlined in the HTML output. Instead, they are supposed to be represented by <img> elements with a (relative) URL in the src attribute which the browser has to download individually during parsing the obtained HTML output. Look at the generated HTML output, the JSF <h:graphicImage> component generates a HTML <img> element which must have a src attribute pointing to a valid URL.

The <h:graphicImage value> must represent a valid URL. If you've however stored images in the DB instead of in the public webcontent, then you should basiclly be creating a standalone servlet which reads the individual image from the DB based on some unique request parameter or URL path and writes it to the response body.

So assuming that you render the image URL as follows from now on,

<h:graphicImage value="/userProfileImageServlet?id=#{userProfile.id}" />

then the following kickoff example (trivial checks like nullchecks etc omitted) of the servlet should do:

@WebServlet("/userProfileImageServlet")
public class UserProfileImageServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        Long userProfileId = Long.valueOf(request.getParameter("id"));

        try (
            Connection connection = dataSource.getConnection();
            PreparedStatement statement = connection.prepareStatement("SELECT image, imageFileName, LENGTH(image) AS imageContentLength FROM userProfile WHERE id=?");
        ) {
            statement.setLong(1, userProfileId);

            try (ResultSet resultSet = statement.executeQuery()) {
                if (resultSet.next()) {
                    response.setContentType(getServletContext().getMimeType(resultSet.getString("imageFileName")));
                    response.setContentLength(resultSet.getInt("imageContentLength"));
                    response.setHeader("Content-Disposition", "inline;filename=\"" + resultSet.getString("imageFileName") + "\"");

                    try (
                        ReadableByteChannel input = Channels.newChannel(resultSet.getBinaryStream("image"));
                        WritableByteChannel output = Channels.newChannel(externalContext.getResponseOutputStream());
                    ) {
                        for (ByteBuffer buffer = ByteBuffer.allocateDirect(10240); input.read(buffer) != -1; buffer.clear()) {
                            output.write((ByteBuffer) buffer.flip());
                        }
                    }
                }
            } else {
                response.sendError(HttpServletResponse.SC_NOT_FOUND);
            }
        } catch (SQLException e) {
            throw new ServletException("Something failed at SQL/DB level.", e);
        }
    }

}

If you happen to use JSF utility library OmniFaces on a JSF 2.2 + CDI environment, then you can instead use its <o:graphicImage> which could be used more intuitively.

<o:graphicImage value="#{userProfileImageBean.getBytes(userProfile.id)}" />

@Named
@ApplicationScoped
public class UserProfileImageBean {

    public byte[] getBytes(Long userProfileId) {
        try (
            Connection connection = dataSource.getConnection();
            PreparedStatement statement = connection.prepareStatement("SELECT image FROM userProfile WHERE id=?");
        ) {
            statement.setLong(1, userProfileId);

            try (ResultSet resultSet = statement.executeQuery()) {
                if (resultSet.next()) {
                    return resultSet.getBytes("image");
                }
            } else {
                return null;
            }
        } catch (SQLException e) {
            throw new FacesException("Something failed at SQL/DB level.", e);
        }
    }

}

It also transparently supports date URI scheme by just setting dataURI="true":

<o:graphicImage value="#{userProfileImageBean.getBytes(userProfile.id)}" dataURI="true" />

See also: