I have a JSF 1.2 view which is kind of a report generator.
Users can select a query, configure some values, then the database is requested and data collected.
After that the data can be viewed in a dataTable and finally is exported to Excel.
Works fine so far.
Depending on the query the columns have different types, one of which is Double.
The Double values should be rounded now...
Since there are many rows (tens or hundreds of thousands) I collect all data as String
to avoid time consuming type conversions.
That's pretty ok because for the export to Excel I need Strings, too (written to Office XML).
But now the rounding comes in. In the dataTable the doubles shall be rounded.
If I had a Double I could easily use f:convertNumber, but I don't have.
My idea would be a String converter which analyzes if it is a numeric value
and then formats the String. (Performace is not so critical because there are
only 30 or so records in the paged dataTable). How would I accomplish this?
Any (other) suggestions? Thanks!
Mark
Edit:
solved for now with
public class ReportStringConverter implements Converter {
DecimalFormat f = new DecimalFormat("#0.00");
public Object getAsObject(FacesContext context, UIComponent component, String value) {
return value;
}
public String getAsString(FacesContext context, UIComponent component, Object value) {
if (isNumeric(value.toString())) {
return f.format(Double.parseDouble(value.toString()));
} else {
return value.toString();
}
}
private boolean isNumeric(String s) {
try {
double d = Double.parseDouble(s);
} catch (NumberFormatException e) {
return false;
}
return true;
}
}
You could use expression language to convert a string to a number by adding 0 to it (example: #{'35.75' + 0}). This allows you to simply use f:convertNumber.
<h:outputText value="#{'35.75' + 0}">
<f:convertNumber pattern="0"/>
</h:outputText>
Related
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?
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.
I'm using dynamic view panel in XPages. The problem that formatting the number does not follow the format defined in view column.
I searched and found that the way to format the number is through a customizer bean. I've Got some code examples of this bean, though none of them had an example with formatting a column containing a number.
Does anyone have an example of this treatment formatting?
Thanks a lot!
Here's a code snippet from a getValueAsString() method from an ExtendedViewColumnConverter class that handles number formatting based on the user locale.
The code assumes that the variable currentLocale contains the user locale. currentLocale is an instance of java.util.Locale.Locale and I assume that you have logik that handles this e.g. as part of a user bean. If not, then one way of getting the current locale is to do this:
Locale currentLocale = FacesContext.getCurrentInstance().getViewRoot().getLocale();
You should of course have an already working customizer bean (that extends DominoViewCustomizer).
public static class ExtendedViewColumnConverter extends ViewColumnConverter {
...
#Override
public String getValueAsString(final FacesContext context, final UIComponent component, final Object value) {
if (value instanceof Number) {
NumberFormat nf = NumberFormat.getInstance(currentLocale);
DecimalFormat df = (DecimalFormat) nf;
df.setRoundingMode(RoundingMode.HALF_UP);
switch (this.colDef.getNumberFmt()) {
case ViewColumn.FMT_GENERAL: {
// no further formatting needed
break;
}
case ViewColumn.FMT_FIXED: {
df.setMinimumFractionDigits(this.colDef.getNumberDigits());
df.setMaximumFractionDigits(this.colDef.getNumberDigits());
break;
}
default: {
// no further formatting needed
break;
}
}
return df.format(value);
}
}
...
}
I have a custom JSF input component, named inputPeriod, which is designed to input date-periods. Each period has a from and to date. The functionality of the component is achieved with Javascript, which generates a JSON string and submits it to the component. The input component then use a default converter which converts the JSON periods into a list of Period objects and sets them on my managed bean. This all works perfectly.
The source of the problem I am having, is that now I want to use the same component with EJB entities. I have a Banner entity with a one-to-many relationship with a BannerPeriod entity. Each instance of the BannerPeriod entity takes a from (begins) and to (ends) date, exactly like the existing Period object I am using with my input component. I have implemented a new converter for this:
#ManagedBean
#RequestScoped
public class BannerPeriodConverter implements Converter {
#Override
public Object getAsObject(FacesContext fc, UIComponent uic, String str) {
if (str != null) {
Date from = null, to = null;
try {
JSONObject period = new JSONObject(str);
if (period.has("from")) {
from = new Date(period.getLong("from"));
}
if (period.has("to")) {
to = new Date(period.getLong("to"));
}
} catch (JSONException ex) {
throw new ConverterException(ex);
}
BannerPeriod bp = new BannerPeriod();
bp.setBegins(from);
bp.setEnds(to);
return bp;
}
return null;
}
#Override
public String getAsString(FacesContext fc, UIComponent uic, Object o) {
if (o != null && o instanceof BannerPeriod) {
BannerPeriod bp = (BannerPeriod) o;
JSONObject period = new JSONObject();
try {
period.put("from", bp.getBegins() != null ? bp.getBegins().getTime() : (Object) null);
period.put("to", bp.getEnds() != null ? bp.getEnds().getTime() : (Object) null);
} catch (JSONException ex) {
throw new ConverterException(ex);
}
return period.toString();
}
return "";
}
}
The converter works fine with the component. The issue I am having is that when I edit a banner with existing banner periods, the entities lose their primary key. So, when I submit my form, instead of updating existing periods, I either get a duplicate exception or the existing periods are created again, making actual duplicates in the database.
So my question is, what can I do to avoid this? My guess would be that the input component somehow needs to keep the primary key on the existing entities, but how can I best make something like that? At the moment, the input component is completely detached from the entities and my EJB project. The input component is even located in its own JSF project, while the converter above is located in an EJB project. By default the input component works with a plain Period object, which has no primary key at all. It should continue to do so.
Or maybe this should be solved in some other way?
In your getAsObject() you're creating a completely unmanaged instance of BannerPeriod instead of obtaining the one straight from DB via JPA.
BannerPeriod bp = new BannerPeriod();
bp.setBegins(from);
bp.setEnds(to);
return bp;
Persisting it will of course create a new entry in DB as it's unmanaged by JPA.
Basically, you should instead be obtaining the instance from the DB via JPA:
#EJB
private BannerPeriodService service;
public Object getAsObject(FacesContext context, UIComponent component, String value) {
// ...
return service.find(from, to);
}
wherein the BannerPeriodService#find() obtains the desired instance via EntityManager.
But this approach is pretty clumsy. In case of entities from the DB, the canonical approach is to use their technical/natural identifier for this, such as the autogenerated primary key.
E.g. (null/instanceof checks and so on omitted):
#EJB
private BannerPeriodService service;
public Object getAsString(FacesContext context, UIComponent component, Object value) {
Long id = ((BannerPeriod) value).getId();
return id.toString();
}
public Object getAsObject(FacesContext context, UIComponent component, String value) {
Long id = Long.valueOf(value);
return service.find(id);
}
No need to mess up with JSON format. If you actually need them in JSON format for some unclear reason, then you're going in the wrong direction by using a JSF converter for this.
I understand that hitting the DB in a converter is a relatively expensive job. In that case, the OmniFaces SelectItemsConverter may be what you're looking for.
In my Entity class I have a HashMap. Now I'm trying to create a Select of this Map to be able to select on of the objects. So I created following classes:
HorseConverter:
#Named
public class HorseConverter implements Converter{
#EJB
private HorseBean bean;
#Override
public Object getAsObject(FacesContext context, UIComponent component, String value) {
return bean.getHorse(Long.valueOf(value));
}
#Override
public String getAsString(FacesContext context, UIComponent component, Object value) {
if(!(value instanceof Horse)){
throw new ConverterException(new FacesMessage("Object is not a Horse"));
} else {
Horse h = (Horse) value;
return Long.toString(h.getId());
}
}
}
Race Entity:
public Map<Horse, Integer> getHorses() {
return horses;
}
public void setHorses(HashMap<Horse, Integer> horses) {
this.horses = horses;
}
And my view:
Horse:
<h:selectOneMenu value="#{betController.horse}" converter="#{horseConverter}">
<f:selectItems value="#{raceController.selectedRace.horses}" var="h" itemLabel="#{h.nickName}" itemValue="#{h}"/>
</h:selectOneMenu>
Seems like the value I'm getting isn't an instance of Horse. I checked the following link:
https://stackoverflow.com/tags/selectonemenu/info So it seems that the key is automaticly used as value. But even writing h.key doesn't make a difference.
EDIT:
Here is my hash and equals code from the Horse Entity:
#Override
public int hashCode() {
int hash = 7;
hash = 97 * hash + (int) (this.id ^ (this.id >>> 32));
return hash;
}
#Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final Horse other = (Horse) obj;
if (this.id != other.id) {
return false;
}
return true;
}
You can't use var on a Map value. This specific <f:selectItems> construct works only if you use List<Horse> instead of Map<Horse, Integer>.
public List<Horse> getHorses() {
return horses;
}
If you really want to use a Map, then you should be returning a Map<String, Horse>, where String is the nickname of the Horse.
public Map<String, Horse> getHorses() {
return horses;
}
In case of using a Map value, don't forget to remove the var:
<f:selectItems value="#{raceController.selectedRace.horses}" />
The map's key becomes the option label and the map's value becomes the option value.
Unrelated to the concrete problem, a HashMap is by nature unordered. If you want to show the dropdown items in insertion order, rather use LinkedHashMap.
Have you overriden hashcode() and equals() in your Horse() object?
Your Converter needs equals() overriden in order to work. If you don't do this, only two references to the same instance of Horse() will be equal, rather than two seperate instances that have exactly the same state. Collections create an implicit copy to compare, so you won't have a single instance on the heap in this case.
Don't forget that the argument in the equals() object is Object(), NOT Horse().
If you don't override hashcode(), the hashcode will be different for every instance of Horse. This means that you will struggle to find the right Horse for comparison, even if your Horses are logically equivalent, because again, you'll have more than one instance that you will be comparing in order to find your Horse in your HashMap.
For further information, see this chapter of Effective Java by Joshua Bloch.
I have an input (JSF) that should be bound to a property in my bean. This property represents another bean and has an auxiliar method that checks if it's null (I use this method a lot).
The problem is that the binding is failing to get the proper getter and setter. Instead of reading the method that returns the bean, it reads the one that return a boolean value.
The property name is guest. The methods are:
getGuest;
setGuest;
isGuest (checks if guest is null).
JSF is trying to bind the object to isGuest and setGuest, instead of getGuest and setGuest.
I cannot rename isGuest to guestIsNull or something, because that would'nt make to much sense (see the class below).
Finally, my question is: how can I bind this property to the object without renaming my methods? Is it possible?
I also accept suggestions of a better method name (but the meaning must be the same).
Entity
#Entity
public class Passenger {
private Employee employee;
private Guest guest;
public Passenger() {
}
#Transient
public boolean isEmployee() {
return null != this.employee;
}
#Transient
public boolean isGuest() {
return null != this.guest;
}
#OneToOne
public Employee getEmployee() {
return this.employee;
}
public void setEmployee(Employee employee) {
this.employee = employee;
}
#OneToOne
public Guest getGuest() {
return this.guest;
}
public void setGuest(Guest guest) {
this.guest = guest;
}
}
JSF
<h:inputText value="#{passenger.employee}" />
<h:inputText value="#{passenger.guest}" />
Change the method name to isGuestNull.
The problem you're seeing is due to the fact that the EL lets you use getFoo or isFoo as the naming style for getter methods that return booleans.
No, that's not possible. You've to rename them.
Another way is to add a single getter returning an enum which covers all cases.
public enum Type {
GUEST, EMPLOYEE;
}
public Type getType() {
return guest != null ? Type.GUEST
: employee != null ? Type.EMPLOYEE
: null;
}
with
<h:something rendered="#{passenger.type == 'GUEST'}">
Binding to any property using any method is possible and quite easy if you create your custom ELResolver (apidocs). elresolvers are registered in faces config, and they are responsible, given an Object and a String defining a property, for determining the value and type of the given properties (and, as the need arises, to change it).
You could easily write your own ELResolver that would only work for your chosen, single type, and use (for example in a switch statement) the specific methods you need to write and read properties. And for other types it would delegate resolving up the resolver chain. It's really easy to do, much easier than it sounds.
But don't do it. The standard naming pattern of properties predates EL by many years. It is part of the JavaBeans™ standard - one of the very few undisputed standards in Javaland, working everywhere - from ant scripts, through spring configuration files to JSF. Seeing methods isPerson and getPerson in one class actually makes me fill uneasy, as it breaks something I always take for granted and can always count on.
If you like DDD and want to have your method's names pure, use an adapter. It's easy, fun, and gives a couple of additional lines, which is not something to sneer at if you get paid for the ammount of code produced:
public class MyNotReallyBean {
public String checkName() { ... }
public String lookUpLastName() { ... }
public String carefullyAskAboutAge() { ... }
public class BeanAdapter {
public String getName() { return checkName(); }
public String getLastName() { return lookUpLastName(); }
public String getAge() { return carefullyAskAboutAge(); }
}
private static BeanAdapter beanAdapter = new BeanAdapter();
private BeanAdapter getBeanAdapter(){ return beanAdapter; }
}