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: