0

In my topbar I have a <o:graphicImage> to show picture from my user.

<o:graphicImage dataURI="true" height="32" width="32" styleClass="img-circle"
    value="#{employeeProfileMenuPictureRequestController.getPicture_32_32(loginBean.currentEmployee)}" 
    lastModified="#{employeeProfileMenuPictureRequestController.lastUpdate}" />

My backend bean is the following:

@GraphicImageBean
public class EmployeeProfileMenuPictureRequestController implements Serializable {

    private Date lastUpdate = new Date();

    public byte[] getPicture_32_32(Employee employee) throws StorageAttachmentNotFoundException, IOException {
        try {

            String path = employeeProfilePictureService.findProfileImageByEmployee(employee, FileSizeType.SIZE_32_32.toString());

            if (employee == null || path == null || path.isEmpty()) {
                return Utils.toByteArray(Faces.getResourceAsStream("/resources/images/no-photo-icon.png"));
            }

            Path fileLocation = Paths.get(path);
            byte[] data = Files.readAllBytes(fileLocation);
            LOGGER.info("END getPicture_32_32");
            return data;
        catch (Exception e) {
            LOGGER.error(ExceptionUtils.getFullStackTrace(e));
        }

        return Utils.toByteArray(Faces.getResourceAsStream("/resources/images/no-photo-icon.png"));
    }

    public Date getLastUpdate() {
        return lastUpdate;
    }
}

Unfortunatelly the getPicture_32_32(Employee) is called for every page request / page navigation. This means it´s also everytime a request against the database, which takes time.

I´ve tried already to add lastModified to the <o:graphicImage>, but the function is called also everytime for each page request.

Can anybody help me to solve this?

BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
vished2000
  • 164
  • 1
  • 11
  • If lastUpdate is always `new Date(l` it make sense that the resource it's not cached – areus Apr 23 '20 at 19:43
  • but I´m using @GraphicImageBean? This is something like SessionScoped? How can I fix it then? – vished2000 Apr 23 '20 at 19:56
  • Why do you use the dataURI? Can you cache those with plain html? Don't think you can. They are embedded in the page... Snafu... – Kukeltje Apr 23 '20 at 20:03
  • 1
    try PrimeFaces `p:cache` around it – Kukeltje Apr 23 '20 at 20:27
  • if I switch dataURI to "false", then I got: request to /javax.faces.resource/EmployeeProfileMenuPictureRequestController_getPicture_32_32.jsf: javax.servlet.ServletException: argument type mismatch – vished2000 Apr 23 '20 at 20:43
  • ok, and suggestions how I can solve this? – vished2000 Apr 24 '20 at 06:15
  • your service should have a method for returning the *real* lastUpdate of the picture, something like: `employeeProfilePictureService.getLastUpdateByEmployee(employee, FileSizeType.SIZE_32_32.toString());` – areus Apr 24 '20 at 08:18
  • @areus: no, read the comment before by BalusC (and his answer from more recent times) – Kukeltje Apr 24 '20 at 10:50
  • @Kukeltje sorry. I missed some of the commets. That only would be a service method name example if the system tracked timestamps of employee images. – areus Apr 24 '20 at 11:25

1 Answers1

2

According to <o:graphicImage> documentation:

Data URI

[...]

This approach is however not recommended for "permanent" and/or "large" images as it doesn't offer the browser any opportunity to cache the images for reuse, ~10KB would typically be the max even less so if there are more such images on the same page.

So, it does not support caching at all. The technical reason is that it basically embeds whole contents of the image in the HTML output. It does not embed an URL to the image. The lastModified is basically ignored. I should probably better document that. At least, you should absolutely remove the dataURI attribute. It's only useful for e.g. preview of an uploaded image.

And,

Image streaming

[...]

In case the property is a method expression taking arguments, each of those arguments will be converted to a string HTTP request parameter and back to actual objects using the converters registered by class as available via Application.createConverter(Class). So, most of standard types like Long are already implicitly supported. In case you need to supply a custom object as argument for some reason, you need to explicitly register a converter for it yourself via @FacesConverter(forClass).

So, because your method take a Employee argument, you basically need to have a @FacesConverter(forClass=Employee.class) so that JSF can automatically convert it from and to String. How to create converters can be found here: Conversion Error setting value for 'null Converter' - Why do I need a Converter in JSF?

You should end up with something like this:

@FacesConverter(forClass=Employee.class)
public class EmployeeConverter implements Converter {

    @Override
    public String getAsString(FacesContext context, UIComponent component, Object modelValue) {
        // Write code here which converts Employee to its unique String representation.
    }

    @Override
    public Object getAsObject(FacesContext context, UIComponent component, String submittedValue) {
        // Write code here which coverts the Employee unique String representation
        // as created in above method back to the original Employee object.
    }

}

An alternative is to adjust your getPicture_32_32() method to take employee ID as e.g. Long instead of employee. Then you don't need a custom converter. JSF has already a built-in converter for Long.

public byte[] getPicture_32_32(Long employeeId) {
    // ...
}
<o:graphicImage
    value="#{employeeProfileMenuPictureRequestController.getPicture_32_32(loginBean.currentEmployee.id)}" />

Coming back to caching, the documentation says this:

Caching

[...]

When unspecified, then the "default resource maximum age" as set in either the Mojarra specific context parameter com.sun.faces.defaultResourceMaxAge or MyFaces specific context parameter org.apache.myfaces.RESOURCE_MAX_TIME_EXPIRES will be used, else a default of 1 week will be assumed.

So, when you have no resource age settings, it's already by default cached for 1 week. The lastModified is thus optional and only useful when you actually track a timestamp in the same database or filesystem when the image is actually changed. You should then really use that instead for most optimal caching. A "random" date is absolutely not the correct way.

Community
  • 1
  • 1
BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
  • Thank you... One more question, which I don´t understand... I´m using the @GraphicImageBean (but it´s also possible @ApplicationScoped)... So the private Date lastUpdate = new Date(); is this then per Employee? I have to set a new date everytime as soon a Employee change his Profile picture, right? – vished2000 Apr 24 '20 at 11:14
  • You need to use the last modified date of the image itself, yes. – BalusC Apr 24 '20 at 17:14