Hi
I'm still thinking about this problem, because it looks like it has the utmost
relevance for users in general. So, I would like to make a contribution in
this part, giving some ideas and specifying some possible use cases where we
could find some inspiration. It is clear there is some interest, but to put it
into the spec it is necessary to be more specific about the requeriments,
and more concrete about the proposals.
I would like to write an extension for this and contribute it into MyFaces
project, but since there is interest on the EG, I would to propose the ideas
here first, because at the end we don't want to replicate work in multiple
places, just do it once and do it right. I believe more in "quantity" of ideas
than in "quality", so the idea is make a depuration of the initiatives, based
of the perception and experience of the people involved.
From start, I would like to focus on this question provided by Arjam Tijms:
AT>> Is it because JSF is not popular enough, or are there specific use cases
AT>> JSF can't address?
I think JSF can handle most of the cases, but it is clear in some cases it
just doesn't help much. JSF Component libraries usually take advantage of the
extensibility of JSF and can fill the gaps, but for a regular user it can
become a complex task. So, we need to find ways to make these tasks easier.
Let's take a look at some cases:
CASE 1: Autocomplete component
In JSF you usually don't want to get your hands dirty with this stuff, instead
you use a JSF library with a custom autocomplete component that do this
integration behind curtains. But let's think for a while that in our particular
case that solution doesn't fit, and we want to do it ourselves by hand.
With jQuery, on the client side you can add a script like this:
$('#input').autocomplete({
source: "/example/location/example.json"
});
or this
$("#input").autocomplete({
source: function (request, response) {
$.post("/AjaxPostURL", request, response);
}
});
The first problem you found with JSF is that you cannot "easily" customize the
response. In this case we need to create an endpoint that returns JSON data.
It can be through a GET or a POST. What can the user do right now in this case
with JSF? There are several options:
1. If you are in a Java EE container, you could use JAX-RS to create the
endpoint and access the CDI beans.
2. If you are not in a JEE container, you can create a custom
PhaseListener, add the controller logic manually and call
facesContext.responseComplete().
3. If you are not in a JEE container, you can provide a custom servlet,
try to create a FacesContext instance and call the code from there, see:
http://myfaces.apache.org/wiki/core/user-guide/jsf-and-myfaces-howtos/backend/access-facescontext-from-servlet.html
4. You can write a JSF page and using f:viewParam and f:viewAction create
the necessary JSON response. This involves generate JSON from facelets.
This only works for the GET case. In this case JSF is used as a
template engine. See some strategies here:
http://stackoverflow.com/questions/8358006/jsf-json-output-plain-text-in-servlet
All previous cases can present the following problems:
1. Some beans are only accesible if the context is correctly set. Beans
created using FlowScope or ConversationScope or anything smaller than
Session scope cannot be accessed from the endpoint.
2. An endpoint like that can be affected by different security problems,
that are avoided by JSF, because there is a ViewState token and in JSF
2.2 a client window id which cannot be guessed easily. So, in some
cases, you would want to send these two parameters and update them,
just like in a ajax request.
3. It could be some parameters necessary for the autocomplete to work.
For example the autocomplete works in one way if the selection box is
selected and in other way if it is not. So, you could want to do a
POST request that execute the lifecycle just like f:ajax, but on the
render response phase, the control is passed to something else like
another jsf template, or a managed bean method or something like that.
Solutions? The GET case can be fixed using facelets to generate the JSON
response.
I could imagine something like this:
$("#input").autocomplete({
source: function (request, response) {
jsf.rest.doPost( $('#input').attr('id'),
event,
{ execute : '_at_form',
renderAction : 'action-to-execute-and-render'}, ... );
}
});
It looks similar to jsf.ajax.request(source, event, options), but it is a
JSF POST request, with javax.faces.ViewState and javax.faces.ClientWindow but
it has a difference: it doesn't trigger an ajax update (it could be, but the
main objective is not that). It is a "partial" request, but it is not an
"partial/ajax" request. I mean, the objective is return somehow the data
in the response in some specified format so the user can take it using
javascript or jQuery and use it.
Please note the lifecycle is executed as usual, but the component does not
render the response, there is some kind of "renderAction" associated to
the component and in that sense to the view and by that way it could be
associated to the managed bean. It is a POST request to the view, but it is
not a POST that renders view markup. Instead it is a POST that comunicate
with the server and bring back some information without alter the current
server side view state. It could change a value in a ViewScope bean for
example, or even alter the view state, but the thing is we should make
it in a way that the user does not see the underlying update. From the
user point of view it is a POST that return JSON, but maybe internally
it could be a POST that use a mix between XML and JSON to deliver the
response.
Is this still JSF? Yes, of course it is. JSF takes care of the templating,
the HTML markup, maintain the context, and javascript provides the logic in
the client.
The difference this is JSF being friendly with Javascript!. There are tons
of javascript components with the same integration problem. If we can fix
this somehow, it would be a big step forward.
CASE 2: Captcha component
Suppose you are creating a custom captcha component. The code could look
something like this:
<input jsf:id="captcha_input" value="#{bean.value}"/>
<img jsf:id="captcha_img" url="..."/>
There is an input, but you need to generate an image on the fly. How? one way
is create a ResourceHandler wrapper implementation and activate it using an
specified library name. Then, inside handleResourceRequest(...) you provide the
necessary logic to take the secret value stored in the server and generate
the image based on that value. And quickly you have found a problem: you want
to store the secret value in the server. The component tree does not work
because that info can travel to the client if state saving is set to client,
and you want to secure the captcha storing the value in the server.
But the request that gets the image from the client does not have the same
context as the page request. It has the same session, because all requests
in a browser share the same session, but it doesn't have any associated
client window id. It raises some challenges:
1. It is not possible to send a POST, because we require an URL, but we don't
want to send the ViewState in the GET, because that let it in the history.
2. Facelets doesn't work to generate images.
3. How to get the secret? using a known key that store the secret in a scope
inside server. Which scope? session scope? window scope? flash scope?
In this case, it could be enough to send a request that restore everything
until window scope but render something arbitrary using the information
available as query parameters. How it looks something like:
<img jsf:id="captcha_img"
url="/javax.faces.endpoint/captcha.jsf?jfwid=#{externalContext.clientWindow.id}&ln=com.my.captcha&k=#{cc.attrs.key}"/>
So, the component by default already sets up an endpoint that receive the
request, it has as parameters:
* The client window url query param, in this case "jfwid"
* The library name, in "ln" query param
* The resource name, in this case captcha.jsf
* Additional parameters for the request
The idea could be simplify the url generation with something like:
<img jsf:id="captcha_img"
url="#{func:endpoint('com.my.captcha:captcha',
'k=#{cc.attrs.key}')}"/>
or do the logic through a some few java lines in the base component
class, passing the parameters as a map.
<img jsf:id="captcha_img" url="#{cc.imgUrl}"/>
The component should look something like this:
@Path(libraryName="com.my.captcha", resourceName="captcha")
@FacesComponent(namespace = "
http://my.namespace.com/components",
createTag = true, tagName = "captcha",
value = "com.myapp.CaptchaComponent")
public class CaptchaComponent extends UIComponentBase
{
@Mapping(...)
public static void handleCaptchaImage(FacesContext context)
{
}
}
I hope you already get the idea. It could look like the ResourceHandler
or the syntax can be fixed to use something closer as an action source
framework looks. Let's make a summary
* JSF scan the component library at startup time and find all endpoints
exposed by components.
* JSF provides an API for syntethize an URL for a component that should
map to an endpoint.
* JSF provides a front controller algorithm in the same way as an action
source framework, so the url can be processed.
* The endpoint has the same client window context as the page request
that renders the URL.
This is getting good, because this also works for the case 1. This is not
create an action source framework inside JSF, it is something even better,
a mixed component/action approach for JSF that fit very well between each
other.
We could extend even more the syntax. Suppose a function that points to
the mapping in this way:
<img jsf:id="captcha_img" url="#{func:endpoint(mybean.handleCaptchaImage)}"/>
The idea could be that the function scan the final method class
automatically and derive the mapping automatically. In this case it is clear
we need to provide the key, so it doesn't really work, but in other cases
it could work.
CASE 3: Excel/PDF/Text/CSV export
In this use case, the user needs to generate some information in excel or PDF
or CSV or whatever, usually to export some data. The known solutions to this
problem are:
1. Use a JSF component that do the necessary work for you.
2. Write a command button in this way ( taken from
http://balusc.blogspot.co.at/2006/05/pdf-handling.html )
public void download() throws IOException {
// Prepare.
FacesContext facesContext = FacesContext.getCurrentInstance();
ExternalContext externalContext = facesContext.getExternalContext();
HttpServletResponse response = (HttpServletResponse)
externalContext.getResponse();
// ... Output the file in the response.
// Inform JSF that it doesn't need to handle response.
// This is very important, otherwise you will get the
following exception in the logs:
// java.lang.IllegalStateException: Cannot forward after
response has been committed.
facesContext.responseComplete();
}
3. Create a servlet that handle the request. See
http://balusc.blogspot.co.at/2007/07/fileservlet.html for an example.
What's the problem here? The context. Suppose solution 2 is not good for you,
because you are using Post-Redirect-GET pattern, or for example you want to
create a link to download the generated file, but on the way preserve the
current context, so the information can be generated. We have the same base
scenario as the captcha component, which is generate an url that has the same
context as the current window. The argument here is the servlet has another
context different that the one defined by JSF. How can you handle this in
a Portlet? again the problem is the context, and the ideal solution is provide
something in JSF that handle these kind of request in a standarized way.
CASE 4: REST
This case is similar to the previous case. What raises my attention here
is that there is evidence, that other component oriented frameworks
like Wicket and Tapestry have already considered the possibility of
define REST services, but that does not means everything is done using
that approach.
In JSF, people has been using for some time PrettyFaces to generate RESTful
URL links. This library has compatibility with JSF 1.1, 1.2 and 2.0, and
seems to be very popular. Also, some users also use the combination between
JSF and other web frameworks like Spring MVC or JAX-RS, specifically to
deal with those cases where it is required to define a REST service. With
Spring MVC you have Spring Beans and with JAX-RS you can integrate it with
CDI, so in this case the evidence suggest people use JSF to do what JSF
do the best (component oriented approach, validation, state management,
server side templating, ...) and use the MVC stuff for the REST services.
Why some users seems to like the idea of define REST service into JSF? by
simplicity, because they have already done 90% of the job just with JSF,
but for the remaining 10% or 5% or 1% they require to define a REST service
or something like that, usually to expose it or consume it using javascript
from the client side, usually embedded in a page written using JSF. In
other words, to avoid the round trip to JAX-RS and the way back to JSF.
CASE 5: Websockets
There are few things to say in this part, but that this is an example where
an "application" scope endpoint can be important. In this case, there is
a communication between the client written in javascript and the server
in java. In this case a declaration of such endpoints works just the same
and the important part here is the integration with CDI. If you have an
application in JSF, you want to access to the same beans. But you could
also use JSF inside a websocket to generate some content. Something like
this:
session.getBasicRemote().sendText(
JSF.renderView("/META-INF/responses/myView.xhtml", ...));
In other words, use JSF template engine to generate all kinds of responses,
without any restriction. Maybe it is a too simplistic example, but in
fact JSF could provide that flexibility with just some fixes and a good
API. That's something really attractive, because we are unleashing the
potential of JSF to do even more things.
CONCLUSIONS:
AT>> Is it because JSF is not popular enough, or are there specific use cases
AT>> JSF can't address?
In my personal opinion, there are some cases that JSF is not solving right
now, but JSF has the potential to solve them even better than anything you
can find out there, in a mixed component/action way.
There are different points of view in all this discussion:
1. Some people want to see JAX-RS on top and JSF as a template engine.
2. Some people want to see JSF on top with built-in action oriented features,
but keeping its own component-oriented approach.
3. Some people want to see JSF with built-in REST features, replicating
the same features as in JAX-RS, because after all that code doesn't have
anything to do with JSF.
My priority list is do 2., then 3. and finally 1. . It can be done, if we push
hard enough. The idea is so tempting that it sounds like a nice extension for
JSF 2. But even better if we can push this for the spec.
It is up to the EG to decide what to do. On the mean time I'll keep trying to
find new ideas.
regards,
Leonardo Uribe
2014-04-05 23:21 GMT+02:00 Leonardo Uribe <lu4242_at_gmail.com>:
> Hi
>
> 2014-04-05 17:52 GMT+02:00 Frank Caputo <frank_at_frankcaputo.de>:
>> Hi all,
>>
>> I've been thinking much about this MVC thing. From my point of view, we
>> should not focus on JSF only, instead take a look at some more specs like
>> JAX-RS, Servlet/JSP and JSF.
>>
>
> +1
>
>> I think many of the samples given by Adrian can be implemented in Java EE by
>> simply forwarding from JAX-RS to the Faces Servlet.
>>
>> Use a JAX-RS resource. Define the model with @Model (which is actually
>> @Named @RequestScoped), inject it in your JAX-RS bean and simply use it from
>> JSF.
>>
>> Then you can do something like this (the code isn't tested):
>>
>> @Path("/exemple/clients")
>> public class ClientResource {
>>
>> @Inject
>> private ClientModel model; // ClientModel is annotated with @Model
>>
>> @Inject
>> private ClientManager clientManager;
>>
>> @GET
>> @Path("{id}")
>> public void show(@Context HttpServletRequest request, @Context
>> HttpServletResponse response, @PathParam("id") String id) throws Exception {
>> Client lClient = clientManager.findById(id);
>> model.setClient(lClient);
>> model.setItemId(id);
>>
>> request.getRequestDispatcher("/exemple/clients/show.faces").forward(request,
>> response);
>> }
>>
>> }
>>
>
> In JSF there is ExternalContext as an abstraction of the underlying environment.
> I would prefer use JSF existing stuff in that part, and refer to the parameters
> as something not coupled with servlet spec. The only flaw I can see is using
> ExternalContext there is no way to know when the request is a GET, a POST
> and so on, which is important at the time to define the endpoint.
>
>> We should simply collect SpringMVC examples and port them to Java EE. I'm
>> willing to do so, but I don't have experience with SpringMVC. So examples
>> should be provided by others.
>>
>
> I ported some time ago a simple booking app to SpringMVC in the performance
> comparison done some time ago. See:
>
> https://github.com/lu4242/performance-comparison-java-web-frameworks
>
> There you can find the same app written in different web frameworks, including
> JSF 2 of course, but it is quite helpful. It could be interesting to check how
> some typical use cases are solved by different frameworks, as a way to get
> some inspiration.
>
> I consider JSF approach is all about define an abstraction so you can build
> complex applications and reuse components easily. One thing that JSF solve
> pretty well is the id problem. In other words, generate ids for the components
> available in the page. In an action source framework there is not such concept
> but that makes things difficult to reuse, because each time you need to use
> something in other place, you usually need to rename the ids.
>
> Now, it could be useful to have a "action source framework" component. That
> means, an special component that work in a way that everything inside it works
> just like any action source framework, but everything outside the box still work
> under JSF rules.
>
> In that sense, allow a syntax to define "endpoints" in managed beans is a part
> of the problem we know that it could be useful. But there are other parts where
> people don't really want to solve everything in the same way as an action
> source framework is solving, because in those parts JSF just do a better job.
>
> Does somebody out there already provided something that we can take as a
> source of inspiration? Just look for the competition.
>
> - Type on google "wicket rest", and you will find a couple of articles that
> aims to do something similar.
> - Type on google "tapestry rest", and you will find something like tynamo.
>
> So, other people has already thought the same as we are trying to do and
> have been doing (JSF 2.2 viewAction and JSF 2.0 viewParam), there are
> some cases where an action oriented approach with a component oriented
> framework can coexist. In that sense, the ability to define REST services
> inside CDI managed beans through Faces Servlet seems to be important.
> For what? It is not hard to find cases. For example, to expose information
> to some other app or component or service that consume it as a REST
> service. If you have a JSF webapp nothing can be simpler as define the
> method right on the managed bean.
>
>> Maybe we can add them to https://github.com/javaee-samples/javaee7-samples
>> or create a dedicated github-repo for it.
>>
>
> +1 for use github.
>
> I have a proposal to discuss different use cases and ideas I have on my sleeve,
> that still need some review from my side, but I hope to send it to the
> list in some
> few days.
>
> regards,
>
> Leonardo
>
>> Ciao Frank
>>
>> Am 01.03.2014 um 21:57 schrieb Adrian Gonzalez <adr_gonzalez_at_yahoo.fr>:
>>
>> [1] An action-based controller with Spring MVC
>> @Controller
>> @RequestMapping(value = "/exemple/clients")
>> public class ClientController {
>> @Inject
>> private ClientManager clientManager;
>>
>> @RequestMapping(value = "/{id}", method = RequestMethod.GET)
>> public String show(@PathVariable Long id, Model uiModel) {
>> Client lClient = clientManager.findById(id);
>> uiModel.addAttribute(lClient);
>> uiModel.addAttribute("itemId", id);
>> return "exemple/client/show";
>> }
>>
>> @RequestMapping(method = RequestMethod.POST)
>> public String create(@Valid Client client, BindingResult bindingResult,
>> Model uiModel) {
>> String inputView = "exemple/client/edit";
>> if (bindingResult.hasErrors()) {
>> return inputView;
>> }
>> client.setDateContact(new Date());
>> try {
>> clientManager.create(client);
>> } catch (SomeBusinessException e) {
>> bindingResult.reject("error.global", e.getMessage());
>> return inputView;
>> }
>> uiModel.asMap().clear();
>> return "redirect:/exemple/clients/" + client.getId();
>> }
>>
>> @RequestMapping(params = "form", method = RequestMethod.GET)
>> public String createForm(Model uiModel, SitePreference sitePreference) {
>> uiModel.addAttribute(newClient());
>> return "exemple/client/edit";
>> }
>> @RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
>> public String delete(@PathVariable Long id, Model uiModel, SitePreference
>> sitePreference) {
>> Client client = clientManager.findById(id);
>> clientManager.delete(client);
>> uiModel.asMap().clear();
>> return "redirect:/exemple/clients" + (sitePreference.isMobile() ? "-mobile"
>> : "");
>> }
>> @RequestMapping(method = RequestMethod.PUT)
>> public String update( @Valid Client client, BindingResult
>> bindingResult,Model uiModel) {
>> String inputView = "exemple/client/edit";
>> if (bindingResult.hasErrors()) {
>> return inputView;
>> }
>> try {
>> clientManager.update(client);
>> } catch (MatriculeExistantException e) {
>> bindingResult.reject("error.global", e.getMessage());
>> return inputView;
>> }
>> uiModel.asMap().clear();
>> return "redirect:/exemple/clients/" + client.getId();
>> }
>>
>> @RequestMapping(value = "/{id}", params = "form", method =
>> RequestMethod.GET)
>> public String updateForm(@PathVariable Long id, Model uiModel) {
>> show(id, uiModel);
>> return "exemple/client/edit";
>> }
>>
>> @RequestMapping(method = RequestMethod.GET)
>> public String list(@Valid ClientCriteriaForm clientCriteriaForm,
>> BindingResult aBindingResult,
>> Model uiModel) {
>> if (aBindingResult.hasErrors()) {
>> return "exemple/client/list";
>> }
>> ...call clientManager...
>> uiModel.addAttribute("clients", clients);
>> return "exemple/client/list";
>> }
>>
>> @RequestMapping(params = "list", method = RequestMethod.GET)
>> public String listForm(ClientCriteriaForm clientCriteriaForm, Model uiModel)
>> {
>> List<Client> clients = new ArrayList<Client>();
>> uiModel.addAttribute("clients", clients);
>> return "exemple/client/list";
>> }
>> @RequestMapping(params="cancel")
>> public String cancel() {
>> return "redirect:/exemple/clients";
>> }
>> }
>>
>> [2] A REST backend controller with Spring MVC
>> @Controller
>> @RequestMapping(value = "/exemple/rest/clients")
>> public class RestClientController {
>>
>> @Inject
>> private ClientManager clientManager;
>>
>>
>> @RequestMapping(method = RequestMethod.GET)
>> @ResponseBody
>> public Map<String, Object> list(@Valid ClientCriteriaForm
>> clientCriteriaForm) {
>> List<Client> clients = clientManager.findByName(
>> clientCriteriaForm.getNom(), clientCriteriaForm.getPage()
>> * clientCriteriaForm.getSize(),
>> clientCriteriaForm.getSize() + 1);
>> Map<String, Object> lResults = new HashMap<String, Object>();
>> if (clients.size() > clientCriteriaForm.getSize()) {
>> lResults.put("hasNextPage", true);
>> lResults.put("page", clientCriteriaForm.getPage());
>> clients.Remove(clientCriteriaForm.getSize().intValue());
>> }
>> lResults.put("clients", clients);
>> return lResults;
>> }
>>
>> @RequestMapping(value = "/{id}", method = RequestMethod.GET,
>> produces="application/json")
>> @ResponseBody
>> public Client show(@PathVariable Long id) {
>> return clientManager.findById(id);
>> }
>> @RequestMapping( method = RequestMethod.POST, produces="application/json")
>> @ResponseBody
>> public Client create(@RequestBody @Valid Client client) throws
>> SomeBusinessException {
>> return clientManager.create(client);
>> }
>> @RequestMapping(value = "/{id}", method = RequestMethod.DELETE,
>> produces="application/json")
>> @ResponseStatus(HttpStatus.NO_CONTENT)
>> public void delete(@PathVariable Long id) {
>> Client client = clientManager.findById(id);
>> clientManager.delete(client);
>> }
>>
>> @RequestMapping(method = RequestMethod.PUT, produces="application/json")
>> @ResponseBody
>> public Client update(@RequestBody @Valid Client client) throws
>> SomeBusinessException {
>> return clientManager.update(client);
>> }
>> @ExceptionHandler(RequestBodyNotValidException.class)
>> @ResponseBody
>> public Map<String,Object> handleException(RequestBodyNotValidException
>> exception, HttpServletResponse response) throws IOException {
>> return RestUtils.convertAndSetStatus(exception, response);
>> }
>> @ExceptionHandler(Exception.class)
>> @ResponseBody
>> public Map<String,Object> handleException(Exception exception,
>> HttpServletResponse response) throws IOException {
>> return RestUtils.convertAndSetStatus(aException, aResponse);
>> }
>> }
>>
>>
>>