Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

like image 348
Cacheing Avatar asked Feb 25 '13 19:02

Cacheing


2 Answers

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:

  • Load images from outside of webapps / webcontext / deploy folder using <h:graphicImage> or <img> tag
  • Display dynamic image from database with p:graphicImage and StreamedContent
like image 75
BalusC Avatar answered Nov 15 '22 04:11

BalusC


There is a way other than calling a servlet: embedding a Base64 encoded image.

Before I give pointers as how to do it, I first need to stress that THIS IS PROBABLY A BAD IDEA (especially if you do not know what the issues are and blindly use this technique). Some of the most important points:

  • Base64 encoding is inefficient and you would use roughly 33% more data with this technique. (You would however use less HTTP requests, which each have their own overhead, so IF your image is very small, the ~33% inefficiency might still be smaller than that of the extra HTTP request.)
  • Some older web browsers do not support the "data:..." protocol, so images won't show with this technique.
  • Depending on various configurations, the HTML page with the embedded image will not be cached, while the image generated from the servlet MAY be cached, so this technique will again cause greater inefficiency. Being knowledgeable again can help here... (On a slight tangent, using this in CSS files might make sense - e.g. small background images - IF the CSS file is cached - as often is the case.)

Some more reading about the above-mentioned warnings (also follow links and google):

  • Stackoverflow: advantages and disadvantages base64 image encode
  • David Calhoun's blog: When to Base64 Encode Images (and When Not To)
  • Stackoverflow: Is embedding background image data into CSS as Base64 good or bad practice?

OK, now for instructions:

Your Facelet would simply include your <h:graphicImage>:

<h:graphicImage id="myimageid" value="${myBean.imgContentsBase64}" />

... and your backing bean MyBean would have a member:

private String imgContentsBase64; // including g/setter

The content of imgContentsBase64 should be of the format: "data:{MIME_TYPE};base64,{BASE64_ENCODED_CONTENTS}".

Where:

  • {MIME_TYPE} e.g. image/png, image/jpg, etc.
  • {BASE64_ENCODED_CONTENTS} is the bytes of the binary image file encoded.

(refer back to the page referenced in the first line, it contains an example).

To do the latter, you get an encoder via java.util.Base64.getUrlEncoder() (or .getEncoder() or .getMimeEncoder() but the first-mentioned is probably appropriate for this case). It seems you would then use the Base64.Encoder.encodeToString() method to convert from a byte[] (containing the image contents) to a String (the bean value). You'll have to read the API docs and/or google for more details, but it looks quite straight-forward and easy, as the encoding functionality is already in the provided library.

And hopefully this is obvious, but the image is only displayed on the first page load (or when the image is updated via e.g. Ajax). So if you want to change the image, you would probably update the imgContentsBase64 contents via an Ajax call listener (etc.), and also render at least the myimageid component.

like image 42
frIT Avatar answered Nov 15 '22 04:11

frIT