I've written an application in JSF which generates some CRUD pages based on some configuration files. To configure those pages it is possible to provide parameters. They can be of any type and also EL expressions. Currently I create and evaluate these expressions for each getter-invocation, which is not the ideal solution, I think:
Now I have two simple questions which I can't answer for myself:
It should be possible to cache those ValueExpression instances per request. On first access (in my case I use JSF's postAddToView event) I create the expression and evaluate in in each getter-invokation.
public void populateComponent(ComponentSystemEvent event) {
FacesContext context = FacesContext.getCurrentContext();
_valueExpression = context.getApplication().getExpressionFactory()
.createValueExpression(context.getELContext(), _expression, String.class);
}
public String getExpressionValue() {
FacesContext context = FacesContext.getCurrentContext();
return (String) _valueExpression.getValue(context.getELContext())
}
This should be the same as using the expression directly in the XHTML which is processed by JSF itself. Any problems with this solution that I don't see at the moment?
Is it also possible to create the instance of the ValueExpression once and use it for all request? The cache would be on the application level instead of per request. This removes the postAddToView event and moves that logic to my configuration parser (which has access to the FacesContext). Or may I hit any side effects when a single value expression is used in multiple requests/threads?
Related
Using Weld 1.1.13.Final in test with Arquillian....
Let's say I inject into a field something volatile. Something like a property subject to change that I want the bean owning the injection point to receive change events. Thought about creating a CDI extension.
Caught ProcessAnnotatedType event and looking for all fields that have an custom annotation on field injection points:
<T> void pat(#Observes ProcessAnnotatedType<T> event, BeanManager bm) {
final AnnotatedType<T> target = event.getAnnotatedType();
for (AnnotatedField<? super T> field : target.getFields())
if (field.isAnnotationPresent(Value.class)) { // ignore that I don't check #Inject here for the moment
CtClass wrapper = pool.get(target.getJavaClass().getName());
ConstPool cp = wrapper.getClassFile().getConstPool();
CtMethod m = CtNewMethod.make(....)
....
wrapper.addMethod(m);
event.setAnnotatedType(bm.createAnnotatedType(wrapper.toClass()));
}
}
Had even grabbed thereafter all the injection points for fields and replaced the underlying WeldField with a new Field corresponding the "wrapper" type. Otherwise bean validation fails.
But this only works for stuff setup during startup not when for example Arquillian uses the Bean Manager to initialize a class that injects one of my "wraps". Things fail since the Bean Resolver uses the Type as a hash key to find beans.
Basically I don't think I can "mask" a class that is annotated (made into a bean) by the CDI with an extra method to receive custom events. Would have been cool but a Type is a Type (i.e. no idea how to proxy or fake the equals/hashCode).
Got it. Turns out the compute value function (google extension) inside the TypeSafeBeanResolver resolver (at least the CDI Weld implementation) is smart. If I just extend the class:
CtClass wrapper = pool.makeClass(target.getJavaClass().getName()+"Proxy");
wrapper.setSuperclass(pool.get(target.getJavaClass().getName()));
.....
final AnnotatedType<T> other = bm.createAnnotatedType(wrapper
.toClass());
then everything works fine. Tested capturing an event in a bean. Will post the code on a Gist with a comment.
I aim to setup the jsf thread to a specific state that I derive from the fact if a given tag is in the subtree of my custom component. This custom component declares a custom cdi scope.
<my:scope area="info">
<rich:tree selectionChangeListener="#{taskTreeManager.selectionChanged}"
<my:scope>
<my:scope area="work">
<rich:tree selectionChangeListener="#{taskTreeManager.selectionChanged}"
<my:scope>
HERE is my idea: the cdi bean 'taskTreeManager' is different for the two rich:trees, since it comes from a custom cdi scope:
#TaskScoped
public TaskTreeManager { .... }
Now I created the jsf tag .
What is missing is a way that this tag has impact on its children, i a generic way (ie. without making assupmtions and without having to change the children).
The needs to activate the cdi scope on each action happening on any tag in its subtree.
I also failed to find a listener for that, especially since Ajax calls go quite directly into the listeners defined on the very component ( the rich:tree). The hierarchy of the component as can be seen in the xhtml is not so relevant, so it seem: when taskTreeManager.selectionChanged gets called, my:scope tag does not know and thus cannot switch the cdi scope properly.
Then I tried to setup listeners programmatically on each component that resides in the subtree.
In the meantime I turned around: what I am trying to achieve is that each java callback finds the right scope active. This can be reduced to: each EL expression evaluation finds its cdi beans in the right scope.
So my way was to introduce a new ELResolver with the sole purpose of discovering if the current component resides within tag.
Fortunately that is possible:
private String computeViewArea(UIComponent cc) {
String retval = EMPTY;
while (null != cc) {
// attention: cc.toString() causes stackoverflow !! so don't log cc!
if (cc instanceof TaskScopedComponent) {
TaskScopedComponent c = (TaskScopedComponent) cc;
retval = (String) c.getAttributes().get("viewArea");
break;
}
cc = cc.getParent();
}
return retval;
}
I've been joyfully using omnifaces' Faces.getLocale() to aquire the locale used by the currently logged in user (which in turn gets this from a <f:view> definition). I really like the fallback approach from view to client to system default locale as it fits the requirements for locale selection in my application:
If a user is logged in, use his language preference (obtained from the backend entity)
If no user preference can be found, use the highest ranking language from the Accept-Languages HTTP header
If no locale has been selected by now, use the system default.
Now I've started using JAX-RS (resteasy implementation) and find it quite difficult to write a service that will provide my backend code with the current user's locale.
I can't use Faces.getLocale(), since that requires a FacesContext which isn't present during JAX-RS request processing.
I can't use the #Context SecurityContext annotation in a #Provider (which would give me the user preferred locale) or #Context HttpHeaders (access to the client locale) since JAX-RS only injects those when it uses the provider itself, not when my backend code instantiates the class.
And I don't want to litter my method signatures with Locale parameters, since virtually everything requires a locale to be present.
To have a concrete example: I have a vcard generator that generates little NOTE fields depending on the user's preferred locale. I can both call the vcard generating method via JSF/EL:
<h:commandLink action="#{vcfGenerator.forPerson(person)}"
value="Go" target="_blank" />
And via a REST service:
#GET #Path('person/{id:[1-9][0-9]*}/vcard')
#Produces('text/vcard')
String exportVcard(#PathParam('id') Long personId, #Context HttpHeaders headers) {
VcfGenerator exporter = Component.getInstance(VcfGenerator) as VcfGenerator
Person person = entityManager.find(Person, personId)
if (! person)
return Response.noContent().build()
def locale = headers.acceptableLanguages[0] ?: Locale.ROOT
return exporter.generateVCF(person, locale).toString()
}
This works (VcfGenerator has a set of JSF-only methods that use Faces.getLocale()), but is a pain to maintain. So instead of passing the Locale object, I'd like to say:
Vcard generateVCF(Person person) {
Locale activeLocale = LocaleProvider.instance().getContext(VcfGenerator.class)
ResourceBundle bundle = ResourceBundle.getBundle("messages", activeLocale, new MyControl())
// use bundle to construct the vcard
}
Has anyone done similar work and can share insights?
I know this has been posted a while ago, but as it has not been marked as resolved, here is how I got a workaround working for this specific case:
First I got a custom ResourceBundle working, as #BalusC described here: http://balusc.blogspot.fr/2010/10/internationalization-in-jsf-with-utf-8.html
Then I updated the constructor in order to detect if a FacesContext is currently being in use, from this :
public Text() {
setParent(ResourceBundle.getBundle(BUNDLE_NAME,
FacesContext.getCurrentInstance().getViewRoot().getLocale(), UTF8_CONTROL));
}
To This:
public Text() {
FacesContext ctx = FacesContext.getCurrentInstance();
setParent(ResourceBundle.getBundle(BUNDLE_NAME,
ctx != null ? ctx.getViewRoot().getLocale() : Locale.ENGLISH, UTF8_CONTROL));
}
This now works both in JSF and JAX-RS context.
Hope this help,
I have implemented Autoconverter (with forceSelection=false) in maintainance screen.
To edit existing record, User will select ID from Autocomplete list.
To add new record, user will enter new ID in same box.
In converter, Application will try to search record in DB using ID.
If not found, New empty object is created with supplied ID and to avoid duplications, this object is added to array list maintained in Converter.
This works as expected on single browser session. but while testing with multiple browser, I found that Array list is shared across all instances.
I am not sure whether approach I have taken is right? if not can you please suggest me an alternative approach.
private List<SchoolMasterDetails> schoolMasterDetailsDB = new ArrayList<SchoolMasterDetails>();
#Override
public Object getAsObject(FacesContext facesContext, UIComponent component, String submittedValue) {
SchoolMasterDetails selectedObject = null;
System.out.println("getAsObject ==> Entering.");
System.out.println("getAsObject ==> '" + submittedValue + "'");
if (!submittedValue.trim().equals("")) {
selectedObject = (SchoolMasterDetails) getMasterService().getSchoolbyCode(submittedValue);
if (selectedObject == null) {
// search Object on localDB
for (SchoolMasterDetails p : schoolMasterDetailsDB) {
if (p.getSchoolCode().equalsIgnoreCase(submittedValue.trim())) {
System.out.println("getAsObject from ArrayList ==> " + p);
return p; // return selectedObject from list of created objects
}
}
System.out.println("getAsObject ==> selectedObject is null, Hence Creating new Object");
selectedObject = new SchoolMasterDetails();
selectedObject.setSchoolCode(submittedValue.trim());
selectedObject.setSchoolName("TEST TEST TEST");
schoolMasterDetailsDB.add(selectedObject);
}
else {
System.out.println("getAsObject from Database ==> " + selectedObject);
}
}
System.out.println("getAsObject ==> " + selectedObject);
}
System.out.println("getAsObject ==> Exiting.");
return selectedObject;
}
Regards,
Shirish
As far as I understand this (still learning myself), a converter fulfills exactly one purpose: It prepares your custom objects to be used in the views (getAsString) and translates Strings back into objects (getAsObject). It will be used whenever an input (a radio list, textfield, autocomplete) is tied to a variable in a backing bean that is of the type of your custom object. It is in your freedom to decide what String should be used to represent your object and how you use this String in return to look up objects.
With this in mind I would not use a converter to store a local list of objects, nor let it handle the creation process itself. Instead, I'd assume there is a backing bean somewhere, which holds your data objects and takes care of all your logic. This bean can have a list of, say, schoolMasters that can be queried for the objects it contains (similar to what your doing). You could then either implement the lookup there in a way that it handles the not-found case and always returns a valid object (which may be a new one), or you could catch the not-found-case in the converter and then trigger a createNew() from the bean to get a new instance.
IMHO this separates the management of the instances more clearly from the translating purpose of your converter. Also, from your code, it seems like you have two places to look up objects - via getMasterService() (a local method?) and inside your stored ArrayList. I don't quite get this...
As for your problem with the browsers sharing an instance: This sounds like a scope issue. If your backing bean, which is supposed to store and manage your data, is in application scope then the same set of data will be available as long as the application runs. This data will be available across browsers and also across users.
On the other hand, if you put the bean in session scope, each session will create its own instance of the bean and store unique data. Similarly, view scoped beans live as long as a single view and request beans are trashed and regenerate for each http request. You can read more here: How to choose the right scope
The answers there talk about beans (which is where your data usually lives). I'm not sure about converters, I see them as classes that are available application wide, so that each session and view can use them for translation - if you maintain a list there, it may well be globally available.
A request scoped bean collects data, from all many other request beans & business logic. This bean is used through the EL expressions in the page but before this request scoped bean may be used in the page, it needs to build a directory using the collected data (This is done after all collection is over but before the bean properties may be used in page).
How can I execute the building of the directory in this bean after all collection but before it is used through the EL expressions in the page without using <f:event>? I need to build it only once.
#ManagedBean(name="namesDirectory")
#RequestScoped
public class NamesDirectory {
public void addForPersonNameRetrieval(Integer id) { // this is used to collect the data in bean
peopleNamesMap.put(id,null);
.......
}
public void buildDirectory(){ // used, when all collection is over, to build the diirectory
.......
}
public String getPersonName(Integer id) { // used in the JSF page through EL expressions
name = peopleNamesMap.get(id);
}
}
Here buildDirectory() needs to be executed at the end of all collection but before using getPersonName() in the JSF page
You have got several options. You could rebuild the directory after every insert or before every retrieval, however this may cause unnecessary rebuilds. You could rebuild the directory only when needed and called:
Add a flag requiresRebuild and default it to true.
Set it to true in addForPersonaNameRetrieval.
Set it to false in buildDirectory.
Call buildDirectory in getPersonName if a rebuild is necessary.