users@jax-rs-spec.java.net

[jax-rs-spec users] Re: [jsr344-experts mirror] JAX-RS MVC

From: Adrian Gonzalez <adr_gonzalez_at_yahoo.fr>
Date: Fri, 30 May 2014 01:10:27 +0100 (BST)

Hello,
First thing, I'm sorry for this long post.

Sections of this post :
 * MVC Objectives ?
 * MVC idea supporting option 4
 * My working notes
=== MVC Objectives ? ===
I would like to know what are the objectives of the MVC functionality.
To quote Leonardo Uribe :
 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.
Personally I would have expected to have an MVC action framework based on JAX-RS annotations with it's own template engine. Let's call it option 4 :
 4. MVC action framework based on JAX-RS annotations with it's own template engine.
I can see people (I've done all this) :
 * developing JSF applications
 * developing a mixed JSF / REST (JSON) application
 * developing REST (JSON) application with some js on the client side (i.e. angularjs...).
 * developing MVC / REST (JSON)
But I don't really see the need for MVC + JSF.
=== MVC idea supporting option 4 ===
After looking 2 hours at the following action frameworks : Playframework / Spring MVC with GSP / ASP.NET MVC 5, all of their latest template engines support templating (layout and reusable blocks) and provide for a shorter syntax (no xml based).
Trying to imagine what JAX-RS MVC could be in order to support option 4 (please note : it's only some sketching with a lot of errors (don't know if this could even work in java) and areas for improvement - cannot do more with so little time ;( - I don't develop in java anymore for now) :
Java Controller :
@Path("/client")
public class ClientController {
 @Inject
 ClientManager clientManager;
 // JAX-RS should be able to bind form data to complex data objects
 // must give us a BindResult to handle binding failure and redirect user to the input page if need be
 @POST
 @Path("/{id}")
 @Produces("application/json")
 public ActionResult edit(Client client, BindResult bindResult) {
  if (bindResult.isValid()) {
   return ok(list.redirect(clientManager.update(client));
  } else {
   return badRequest(show.render(client));
  }
 }
 
 @GET
 @Produces("application/json")
 @Path("{id}")
 public Client show(@PathParam("id") Long id) {
  public Client show(@PathVariable Long id) {
  return clientManager.findById(id);
 }
 ...
}
some notes on the controller :
 * list.redirect : list source code is automatically created when we create list page. This functionnality is from Playframework and enables on to have compile time checking.
 * BindResult : from SpringFramework. In this solution, JAX-RS is responsible for the conversion before calling the method.
   Another option would be for JAX-RS to send a Form object (i.e. Form<Client>) and for us to call explicitely the conversion inside the action (i.e. form.bind) - just like in playframework
 * JAX-RS must be able to convert HTTP POST parameters (multipart or not) to a java object
edit view :
@import static Helper.*;
@views.template.Main.render(clientForm: Form<Client>) {
 @Helper.form(action -> routes.Clients.edit()) {
         
         @inputText(
             clientForm("lastName"),
             '_label -> "First name"
         )
         
         @inputText(
             clientForm("lastName"),
             '_label -> "Last name"
         )
         
     </fieldset>
     <div class="actions">
         <input type="submit" class="btn primary" value="Create">
         <a href="@routes.Application.index" class="btn">Cancel</a>
     </div>
 }
}
list view :
@main(clients: List<Client>)
<ul class="clients" id="clients">
 @for(client <- clients) {
  <li><a href="@routes.ClientController.show(client.id)">@client.lastName @client.firstName</a></li>
 }
</ul>
Notes about the view :
 * template inclusion mechanism (main references a page names 'main' - just like in Play).
 * reusable blocks (i.e. @inputText)
 * @ instead of XML to include dynamic code (less verbosity) - just like in GSP, ASP .NET MVC 5 (Razor) and Playframework
 * real HTML must be output like real HTML (no automatic translation div (class:'pull-right') to <div class="pull-right"> like in groovy - it appeared to me to be too much magic)
 * in edit view : the view parameter is Form<Client> and not Client. Form has all the necessary info for previous invalid values. Perhaps JAX-RS could automatically use Form<Client> instead of Client when we need it ?
=== My working notes ===
If it can help someone...
Really rough.
Some notes on how I would implement a similar use case in Play, Spring MVC, dot Net.
This won't compile (I've used a text editor to aggregate some samples around the web), there will be a lot of errors.
=== Spring MVC with GS ==
@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);
      }
}
Groovy template
see http://spring.io/blog/2014/05/28/using-the-innovative-groovy-template-engine-in-spring-boot
Groovy template features : layout + lambda + concise (no xml) + fragments
layout 'layouts/main.tpl',
    pageTitle: 'Spring Boot - Groovy templates example with layout',
    mainBody: contents {
      ul {
        clients.each { client ->
          li {
            a(href:"/client/$client.id", "$client.lastName $client.firstName")
          }
        }
      }
      div {
        a(href:'/client/add', 'Add new client')
      }
    }
  }
}
see https://github.com/spring-projects/spring-boot/blob/master/spring-boot-samples/spring-boot-sample-web-groovy-templates/src/main/resources/templates/messages/form.tpl
layout 'layout.tpl', title: 'Messages : Create',
  content: contents {
    div (class:'container') {
      form (id:'messageForm', action:'/', method:'post') {
       if (formErrors) {
          div(class:'alert alert-error') {
            formErrors.each { error ->
              p error.defaultMessage
            }
          }
        }
        div (class:'pull-right') {
          a (href:'/', 'Messages')
        }
        label (for:'lastName', 'Last Name')
        input (name:'lastName', type:'text', value:client.lastName?:'',
            class:fieldErrors?.lastName ? 'field-error' : 'none')
        label (for:'firstName', 'First Name')
        input (name:'firstName', type:'text', value:client.firstName?:'',
            class:fieldErrors?.firstName ? 'field-error' : 'none')
        div (class:'form-actions') {
          input (type:'submit', value:'Create')
        }
      }
    }
  }
== Playframework ==
=== routes ===
# Client
GET /exemple/clients controllers.Clients.list()
PUT /exemple/clients/:client controllers.Clients.update(client: Long)
DELETE /exemple/clients/:client controllers.Clients.delete(client: Long)
=== Controller ===
public class Clients extends Controller {
    public static Result list() {
        return ok(
            list.render(
                Client.findAll()
            )
        );
    }
 
    /**
     * Update a client
     */
    public static Result update(Long client) {
     ClientForm clientForm = form().bindFromRequest();
     if(clientForm.hasErrors()) {
      return badRequest(clientForm.render(clientForm));
  } else {
         Client.markAsDone(
             client,
             Boolean.valueOf(
                 clientForm.get()
             )
         );
        return ok();
    }
 
    /**
     * Delete a client
     */
    public static Result delete(Long client) {
        Client.find.ref(client).delete();
        return ok();
    }
}
list.scala.html
@main(clients: List[Client])
<ul class="clients" id="clients">
 @for(client <- clients) {
  <li><a href="@routes.ClientController.show(client.id)">@client.lastName @client.firstName</a></li>
 }
</ul>
create page
@main(clientForm: Form[Client])
@import helper._
@import helper.twitterBootstrap._
@title = {
    Add a new client <small><a href="@routes.Clients.edit">Or edit an existing client</a></small>
}
@main(title, nav = "Client") {
   
    @if(clientForm.hasErrors) {
        <div class="alert-message error">
            <p><strong>Oops</strong> Please fix all errors</p>
        </div>
    }
   
    @helper.form(action = routes.Clients.submit, 'id -> "form") {
       
        <fieldset>
            <legend>General information</legend>
           
            @inputText(
                clientForm("lastName"),
                '_label -> "First name"
            )
           
            @inputText(
                clientForm("lastName"),
                '_label -> "Last name"
            )
           
        </fieldset>
        <div class="actions">
            <input type="submit" class="btn primary" value="Create">
            <a href="@routes.Application.index" class="btn">Cancel</a>
        </div>
    }
 }
public class Clients extends Controller {
   
    /**
     * Defines a form wrapping the Client class.
     */
    final static Form<Client> contactForm = form(Client.class);
 
    /**
     * Display a blank form.
     */
    public static Result blank() {
        return ok(form.render(contactForm));
    }
 
    public static Result edit() {
        Client existingClient = new Client(
            "Fake", "Client", "Fake company",
            new Client.Information(
                "Personal", "fakecontact_at_gmail.com", "01.23.45.67.89", "98.76.54.32.10"
            ),
            new Client.Information(
                "Professional", "fakecontact_at_company.com", "01.23.45.67.89"
            ),
            new Client.Information(
                "Previous", "fakecontact_at_oldcompany.com"
            )
        );
        return ok(form.render(contactForm.fill(existingClient)));
    }
 
    /**
     * Handle the form submission.
     */
    public static Result submit() {
        Form<Client> filledForm = contactForm.bindFromRequest();
       
        if(filledForm.hasErrors()) {
            return badRequest(form.render(filledForm));
        } else {
            Client created = filledForm.get();
            return ok(summary.render(created));
        }
    }
}
== dot Net MVC 5 with Razor template engine ==
from http://www.asp.net/mvc/tutorials/mvc-5/introduction/getting-started
using System.Web;
using System.Web.Mvc;
 
namespace MvcMovie.Controllers
{
    public class ClientsController : Controller
    {
     // GET: /Clients/
     public ActionResult Index()
     {
         return View(clientManager.findAll());
     }
  // POST: /Clients/Edit/5
  [HttpPost]
  [ValidateAntiForgeryToken]
  public ActionResult Edit([Bind(Include="ID,FirstName,LastName")] Client client)
  {
      if (ModelState.IsValid)
      {
          clientManager.update(client);
          return RedirectToAction("Index");
      }
      return View(client);
  }
    }
}
App_Start/RouteConfig.cs :
public static void RegisterRoutes(RouteCollection routes)
{
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
      routes.MapRoute(
          name: "Default",
          url: "{controller}/{action}/{id}",
          defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
      );
      routes.MapRoute(
           name: "Hello",
           url: "{controller}/{action}/{name}/{id}"
       );
   }
=== list.cshtml ===
default layout is defined in Views\_ViewStart.cshtml.
@model IEnumerable<Exemple.Models.Client>
@{
    ViewBag.Title = "Client List";
}
<h2>Client List</h2>
@foreach (var client in Model) {
{
    <li>@Html.ActionLink(client.firstName + " " + client.lastName, "Edit", new { id=item.ID }) |</li>
}
<p>Hello from our View Template!</p>
<ul class="clients" id="clients">
 @for(client <- clients) {
  <li><a href="@routes.ClientController.show(client.id)">@client.lastName @client.firstName</a></li>
 }
</ul>
}
=== edit.cshtml ===
@model Exemple.Models.Client
@{
    ViewBag.Title = "Edit";
}
<h2>Edit</h2>
@using (Html.BeginForm())
{
    @Html.AntiForgeryToken()
    <div class="form-horizontal">
        <h4>Client</h4>
        <hr />
        @Html.ValidationSummary(true)
        @Html.HiddenFor(model => model.ID)
        <div class="form-group">
            @Html.LabelFor(model => model.FirstName, new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.FirstName)
                @Html.ValidationMessageFor(model => model.FirstName)
            </div>
        </div>
        <div class="form-group">
            @Html.LabelFor(model => model.LastName, new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.LastName)
                @Html.ValidationMessageFor(model => model.LastName)
            </div>
        </div>
        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="Save" class="btn btn-default" />
            </div>
        </div>
}
@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
}



----- Mail original -----
De : Frank Caputo <frank_at_frankcaputo.de>
À : users_at_jax-rs-spec.java.net
Cc :
Envoyé le : Mardi 27 mai 2014 19h52
Objet : [jax-rs-spec users] Re: [jsr344-experts mirror] JAX-RS MVC

Hi,

I’m Frank Caputo and happy to continue the discussion here.

Ciao Frank




Am 23.05.2014 um 17:27 schrieb Edward Burns <edward.burns_at_oracle.com>:

>>>>>> On Fri, 23 May 2014 10:10:21 -0400, Santiago Pericas-Geertsen <Santiago.PericasGeertsen_at_oracle.com> said:
>
> SPG> I'm the spec lead for JAX-RS and Ed asked me to pop in here and
> SPG> invite you over to the mailto:users_at_jax-rs-spec.java.net where we
> SPG> are planning to have our discussions. I'd like to preserve the
> SPG> momentum from the thread that was started by Arjan back in February
> SPG> [2] and had discussions through until April [3]. Our plans for
> SPG> JAX-RS MVC include using Facelets and JSP as well as support for
> SPG> Flows, ViewScope and FlashScope. Ed will have more to say about the
> SPG> JSF side of these things.
>
> Hi Santiago,
>
> Thanks for the invitation. 
>
> This email is a call to action to those of us how contributed to the
> discussion of MVC on users_at_javaserverfaces-spec-public list.
> Specifically, I'm counting on Arjan Tijms, Adrian Gonzalez, Andy Bosch,
> Kito Mann, Leonardo Uribe, Manfred Riem, Neil Griffin, and
> Frank Caputo to strongly consider joining users_at_jax-rs-spec.java.net and
> continuing the discussion there.
>
> Here is a brief summary the discussion that happened on
> users_at_javaserverfaces-spec-public thus far, staring with Arjan's message
> on Fri, 28 Feb 2014 23:14:52 +0100 [1] and completing with Leonardo's
> last message on the thread at Tue, 8 Apr 2014 18:22:44 +0200 [2].
>
> Arjan pointed out the work Manfred did to build the action oriented
> framework on top of the JSF lifecycle mechanism.
>
> Adrian Gonzalez wrote:
>
> AG> Both JAX-RS and JSF miss some features for a MVC framework. JSF
> AG> misses (at least) the following stuff to support reasonably well the
> AG> MVC scenario :
>
> AG> 1. it doesn't support for now HTTP REST usage : HTTP PUT, POST
> AG> (without postback), DELETE, etc...  This is a blocker.
>
> AG> 2. templating : plugging in a 3rd party templating engine easily
> AG> should be supported in MVC.
>
> AG> This is not the case for a component and an action based
> AG> framework. As I recall, Spring MVC has been built after
> AG> JAX-RS. Spring MVC authors didn't used JAX-RS because it was too
> AG> limiting for them . Perhaps someone should ask them why JAX-RS was
> AG> too limiting in order to lift those limitations ?
>
> AG> As for PlayFramework, I didn't used it, but here are some goodies :
>
> AG> * code reloading - a must have !
>
> AG> * view are compiled so you can use them in the controllers (to
> AG> populate the view and to navigate to them).
>
> AG> * view templates (and form helpers) are more succinct/understandable
> AG> than the same in JSP / facelets.
>
> Andy Bosch wrote:
>
> AB> Of course I am in favour of enhancing JSF according to user
> AB> feedback. But I am not sure whether it is a good idea to integrate
> AB> too much into it.
>
> Ed Burns wrote:
>
> EB> I'd like to see us extract Facelets from JSF, make it a first class
> EB> citizen of a JAX-RS MVC, *and* provide an Action Oriented
> EB> lifecycle as an alternative to the standard JSF lifecycle.
>
> Andy Bosch replied:
>
> AB> In my personal opinion it sounds good. But I often hear complaints
> AB> of people about the JSF lifecycle (mostly those people do not like
> AB> JSF at all ;-) ). Some people just don't like a complex
> AB> lifecycle. They prefer having a simple controller that is called
> AB> during an action invocation.
>
> AB> So to sum up I would say we have to carefully think about how
> AB> complex or easy such an lifecycle would become.
>
> Kito Mann asked:
>
> KM> Ed, what's your motivation for doing the JAX-RS route _and_ the JSF
> KM> route? Support action-based scenarios within JSF apps, but provide
> KM> JAX-RS for those who don't use JSF?
>
> Ed Burns replied:
>
> EB> Right, it's about making Facelets available outside of JSF because
> EB> templating is useful on its own, even without the JSF lifecycle.
>
> Leonardo Uribe responded by asserting that the combination of Spring MVC
> *and* JSF can be powerful, citing a blog entry and examples.
>
> LU> In other words, what the user want is in this case is:
>
> LU> - Take advantage of JSF 2 template system (facelets) and component
> LU> model.
>
> LU> - Don't use the JSF lifecycle and use something else that fits.
>
> Leonardo further asserts it is nonsense to take Facelets out of JSF:
>
> LU> If you take facelets out of JSF, what you are really doing is get
> LU> rid of JSF lifecycle, but besides that, you are not doing anything
> LU> else.
>
> Frank agreed with Leonardo's assertion.
>
> Though I agree with all of Leonardo's other points in his response, I
> disagree with this one.  You are getting all the page templating from
> the <ui:> tag library which doesn't exist in any other server side java
> solution aside from Struts Tiles.
>
> Leonardo goes on to sketch a solution to allow JSF to plug more cleanly
> into Spring MVC, providing specific details about how the JSF lifecycle
> can be improved in the process. 
>
> Taking another perspective on the matter, Leonardo outlines the
> data-only JSF lifecycle idea we've been kicking around for years and
> discussed at Javaland.  The basic idea is to have a way for a JSF ajax
> request to have access to all JSF features except those relating
> directly to a view.  This would include FacesContext, Flash, Flow, etc
> but would *NOT* include anything to do with a specific view.  This is
> captured in JAVASERVERFACES_SPEC_PUBLIC-1261 and I want to pursue this
> in JSF 2.3.
>
> The discussion continued after Javaland.  Frank sketched out how you
> could forward from JAX-RS to the FacesServlet today, even without Jersey
> MVC.  Leonardo replied,
>
> LU> The only flaw I can see is using ExternalContext there is no way to
> LU> know when the request is a GET, a POST and so on, which is important
> LU> at the time to define the endpoint.
>
> LU> Now, it could be useful to have a "action source framework"
> LU> component. That means, an special component that work in a way that
> LU> everything inside it works just like any action source framework,
> LU> but everything outside the box still work under JSF rules.
>
> LU> So, other people has already thought the same as we are trying to do
> LU> and have been doing (JSF 2.2 viewAction and JSF 2.0 viewParam),
> LU> there are some cases where an action oriented approach with a
> LU> component oriented framework can coexist. In that sense, the ability
> LU> to define REST services inside CDI managed beans through Faces
> LU> Servlet seems to be important. For what? It is not hard to find
> LU> cases. For example, to expose information to some other app or
> LU> component or service that consume it as a REST service. If you have
> LU> a JSF webapp nothing can be simpler as define the method right on
> LU> the managed bean.
>
> Leonardo followed up on this with some very detailed suggestions.
> Leonardo, please join the discussion over on
> users_at_jax-rs-spec.java.net.  He ends by summarizing:
>
> LU> For some users who currently uses JSF at a daily basis, this problem
> LU> is not something important, because there is a workaround and this
> LU> usually suppose a small part of the whole application, so you don't
> LU> really spent a lot of time solving it. It is not really hard to
> LU> write a couple of servlets that can do the job in these cases.
>
> LU> But for some other users that are not into JSF, this is
> LU> important. The reason is JSF Template engine (Facelets) solves a lot
> LU> of problems for them. These guys usually are making applications
> LU> with another architectural paradigm, different to the one proposed
> LU> initially by JSF.
>
> Let's get moving!
>
> Ed
>
> [1] https://java.net/projects/javaserverfaces-spec-public/lists/users/archive/2014-02/message/7
>
> [2] https://java.net/projects/javaserverfaces-spec-public/lists/users/archive/2014-04/message/6
>
>