IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

JSF Java Discussion :

[Primefaces] Autocomplete sur session expirée : NullPointerException malgré une vue restaurée.


Sujet :

JSF Java

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre très actif

    Profil pro
    Inscrit en
    Janvier 2007
    Messages
    608
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Janvier 2007
    Messages : 608
    Par défaut [Primefaces] Autocomplete sur session expirée : NullPointerException malgré une vue restaurée.
    Bonjour,

    Un utilisateur utilise une combo-box Primefaces en autocomplete.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    <p:autoComplete id="listeCommunes" styleClass="combo" dropdown="true" maxlength="70"
    	var="commune" itemValue="#{commune.codeCommune}" itemLabel="#{commune == null ? '' : geographie.presentationVille(commune)}" 
    	value="#{accueil.codeCommune}" converter="codeCommuneConverter" 
    	completeMethod="#{geographie.autoCompleteCommune}">
    	 <p:ajax event="itemSelect" process="@form" update=":carteForm :accrocheContourForm :accrocheNomCommuneForm :imageBackgroundCommuneForm" /> 
    </p:autoComplete>
    Parce qu'il délaisse l'application un certain temps ensuite, la session tombe. Et plus tard, il entreprend de faire sur la page où il est une nouvelle sélection dans cette même combo-box. Il ignore bien sûr que la session est tombée.

    Un errorHandler s'enclenche :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    if (t instanceof ViewExpiredException)
    {
       fc.setViewRoot(fc.getApplication().getViewHandler().createView(fc, "/index.xhtml"));
       fc.getPartialViewContext().setRenderAll(true);
       fc.renderResponse();
    }
    dont j'espère qu'avec mes managed beans sérialisables et le partial state saving que j'ai mis dans mon web.xml, l'application parviendra à restaurer la session que je m'autorise à réactiver à loisir tant que l'internaute n'est pas authentifié, c'est à dire qu'il est un simple visiteur.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    <context-param>
    	<param-name>javax.faces.PARTIAL_STATE_SAVING</param-name>
    	<param-value>true</param-value>
    </context-param>
    Mais cela ne suffit pas : mon application échoue en NullPointerException au moment où l'utilisateur entreprend une nouvelle sélection :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    18-Apr-2016 09:05:18.210 SEVERE [http-nio-8080-exec-22] com.sun.faces.application.view.FaceletViewHandlingStrategy.handleRenderException Error Rendering View[/index.xhtml]
     java.lang.NullPointerException
    	at org.primefaces.component.autocomplete.AutoCompleteRenderer.encodeSuggestionsAsList(AutoCompleteRenderer.java:516)
    	at org.primefaces.component.autocomplete.AutoCompleteRenderer.encodeSuggestions(AutoCompleteRenderer.java:411)
    	at org.primefaces.component.autocomplete.AutoCompleteRenderer.encodeResults(AutoCompleteRenderer.java:122)
    	at org.primefaces.component.autocomplete.AutoCompleteRenderer.encodeEnd(AutoCompleteRenderer.java:104)
    	at javax.faces.component.UIComponentBase.encodeEnd(UIComponentBase.java:924)
    [...]
    Un grepcode sur le composant AutoCompleteRenderer.java de Primefaces m'indique cet emplacement comme origine du NullPointerException, et c'est plausible :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    protected void encodeSuggestionsAsList(FacesContext context, AutoComplete ac, List items, Converter converter) throws IOException {
    	ResponseWriter writer = context.getResponseWriter();
    	String var = ac.getVar();
    	Map<String,Object> requestMap = context.getExternalContext().getRequestMap();
    	boolean pojo = var != null;
    	UIComponent itemtip = ac.getFacet("itemtip");
    	boolean hasGroupByTooltip = (ac.getValueExpression("groupByTooltip") != null);
    
    	writer.startElement("ul", ac);
    	writer.writeAttribute("class", AutoComplete.LIST_CLASS, null);
    
    	for(Object item : items) {
    		writer.startElement("li", null);
    		writer.writeAttribute("class", AutoComplete.ITEM_CLASS, null);
    Avez-vous des suggestions à me faire pour résoudre ce problème ?

  2. #2
    Modérateur
    Avatar de OButterlin
    Homme Profil pro
    Inscrit en
    Novembre 2006
    Messages
    7 313
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations forums :
    Inscription : Novembre 2006
    Messages : 7 313
    Billets dans le blog
    1
    Par défaut
    Je ne sais pas quelle version tu utilises mais la 5.3.3 devrait corriger ton problème, le code de la méthode encodeSuggestionsAsList contient un test
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
        protected void encodeSuggestionsAsList(FacesContext context, AutoComplete ac, List items, Converter converter) throws IOException {
            ResponseWriter writer = context.getResponseWriter();
            String var = ac.getVar();
            Map<String,Object> requestMap = context.getExternalContext().getRequestMap();
            boolean pojo = var != null;
            UIComponent itemtip = ac.getFacet("itemtip");
            boolean hasGroupByTooltip = (ac.getValueExpression("groupByTooltip") != null);
            
            writer.startElement("ul", ac);
            writer.writeAttribute("class", AutoComplete.LIST_CLASS, null);
    
            if(items != null) {
                for(Object item : items) {
                    writer.startElement("li", null);
    ...
    Ceci dit, quel est le code de ta méthode autoCompleteCommune et quel est le scope de ton managedBean geographie ?
    N'oubliez pas de consulter les FAQ Java et les cours et tutoriels Java

  3. #3
    Membre très actif

    Profil pro
    Inscrit en
    Janvier 2007
    Messages
    608
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Janvier 2007
    Messages : 608
    Par défaut
    Les beans sont en session scoped. J'aurais peut-être pu en mettre certains en ApplicationScoped, car une liste de communes ne varie pas.

    La combo-box en auto-complete en xhtml est celle-ci :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    <p:autoComplete id="listeCommunes" styleClass="combo" dropdown="true" maxlength="70"
    	var="commune" itemValue="#{commune.codeCommune}" itemLabel="#{commune == null ? '' : geographie.presentationVille(commune)}" 
    	value="#{accueil.codeCommune}" converter="codeCommuneConverter" 
    	completeMethod="#{geographie.autoCompleteCommune}">
    	 <p:ajax event="itemSelect" process="@form" update=":carteForm :accrocheContourForm :accrocheNomCommuneForm :imageBackgroundCommuneForm" /> 
    </p:autoComplete>
    et le composant qu'elle joint via geographie.presentationVille(commune) est celui-ci (je l'ai expurgé de quelques méthodes pour le présenter ici) :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    @Component("geographie")
    @SessionScoped // Pour éviter les rechargements de base importants.
    public class GeographieReferentiel extends AbstractController
    {
       /** Serial ID. */
       private static final long serialVersionUID = -6530489997305989481L;
     
       /** Service d'accès aux données géographiques. */
       @Autowired
       private GeographieService serviceGeographie;
     
       /** Limite autocomplete des communes. */
       private int limiteAutoComplete = 15;
     
       /**
        * Chargement dynamique des communes.
        * @param requete Requête.
        * @return Liste des communes.
        */
       public List<Commune> autoCompleteCommune(String requete) {
          Communes communes = getCommunes();
          List<Commune> resultats = new ArrayList<>();
     
          int nombre = 0;
     
          Locale locale = this.getLocale();
          Collator collator = Collator.getInstance(locale);
          collator.setDecomposition(Collator.CANONICAL_DECOMPOSITION);
          collator.setStrength(Collator.PRIMARY); // A et B différents, mais A = a, et A = à.
     
          for(Commune commune : communes)
          {
             if (startWith(commune.getNom(), requete, collator)) 
             {
                resultats.add(commune);
                nombre ++;
     
                if (nombre >= this.limiteAutoComplete)
                   break;
             }
          }
     
          return resultats;
       }
     
       /**
        * Formatter la commune répondant à un code pour la présenter de manière utile.
        * @param codeCommune code de la commune.
        * @return Chaîne de représentation.
        */
       public String presentationVille(CodeCommune codeCommune)
       {
          if (codeCommune == null)
             return ""; //$NON-NLS-1$
     
          try
          {
             Commune commune = this.serviceGeographie.obtenirCommune(codeCommune);
             return presentationVille(commune);
          }
          catch(CommuneInexistanteException e)
          {
             // Nous avons permis de sélectionner cette commune auparavant.
             throw new RuntimeException(e.getMessage());
          }
       }
     
       /**
        * Formatter une commune pour la présenter de manière utile.
        * @param commune Commune.
        * @return Chaîne de représentation.
        */
       public String presentationVille(Commune commune)
       {
          if (commune == null)
             return ""; //$NON-NLS-1$
     
          Departement departement = this.serviceGeographie.obtenirDepartements().get(commune.getCodeDepartement());
     
          if (departement == null)
          {
             Ressources rsc = new Ressources(GeographieReferentiel.class);
             rsc.warn("warn.departement_non_trouve", commune.getCodeDepartement(), commune.getCodeCommune(), commune.getNom()); //$NON-NLS-1$
             return ""; //$NON-NLS-1$
          }
     
          String format = "{0} ({1} ({2}))"; //$NON-NLS-1$
          String ville = MessageFormat.format(format, commune.getNom(), this.serviceGeographie.obtenirDepartements().get(commune.getCodeDepartement()).getNom(), commune.getCodeDepartement());
          return ville;
       }
     
       /**
        * Déterminer si deux chaînes débutent par la même séquence.
        * @param chaine Chaîne candidate.
        * @param start Chaine à comparer.
        * @param collator Collateur. 
        * @return true si c'est le cas.
        */
       private boolean startWith(String chaine, String start, Collator collator)
       {
          if (start.isEmpty())
             return true;
     
          if (chaine.length() < start.length())
             return false;
     
          return collator.compare(chaine.substring(0, start.length()), start) == 0;
       }
     
       /**
        * Obtenir la liste des communes.
        * @return Liste des communes.
        */
       public Communes getCommunes()
       {
          Communes communes = this.serviceGeographie.obtenirCommunes();
          communes.sort(getLocale());
          return communes;
       }
     
       /**
        * Obtenir la commune ayant un code particulier.
        * @param code Code de la commune recherchée.
        * @return Commune.
        * @throws CommuneInexistanteException si la commune n'existe pas.
        */
       public Commune getCommune(CodeCommune code) throws CommuneInexistanteException
       {
          Objects.requireNonNull(code, "Le code de la commune ne peut pas valoir null."); //$NON-NLS-1$
          return this.serviceGeographie.obtenirCommune(code);
       }
    }
    Mais je crains que mon idée soit vaine et vouée à l'échec, que d'espérer pouvoir durant un évènement ajax d'autocomplete détecter :
    - que la session est tombée,
    - décider de la restituer,
    - faire que l'autocomplete retrouve ses variables membres internes d'avant
    - et qu'il poursuive sa sélection comme si de rien n'était.

    Peut-être un javax.faces.STATE_SAVING_METHOD = client pourrait aider à cela, mais l'ensemble me semble tortueux.

    Les visiteurs vont avoir une durée de session infinie, et les connectés 30 minutes.
    Je pense que je peux dynamiquement changer la durée d'une session ? Je dois le vérifier.
    Un connecté qui perdra sa session reviendra sur une page de login ou sur celle des visiteurs, et ne sera plus devant une combo-box autocomplete.
    Je concède que c'est contourner le problème, mais je ne veux pas produire du code trop industrieux.

  4. #4
    Modérateur
    Avatar de OButterlin
    Homme Profil pro
    Inscrit en
    Novembre 2006
    Messages
    7 313
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations forums :
    Inscription : Novembre 2006
    Messages : 7 313
    Billets dans le blog
    1
    Par défaut
    Personnellement, je suis partisan d'une durée de vie de session extrêmement courte (5mn) et d'un "activateur" cyclique.
    Comme ceci, tant qu'on est sur la page, la session est maintenue et si on quitte l'application (en fermant l'onglet ou écrasement de la page à partir de l'URL), la session ne tardera pas à être supprimée.

    Pour le problème de base, je ne pense pas que ça vienne du composant puisque tu fournis toujours une liste non nulle (à moins que le code que tu as retiré dise le contraire). On dirait plutôt un problème du moteur JSF (com.sun.faces.application.view.FaceletViewHandlingStrategy.handleRenderException) puisque la méthode encodeSuggestionsAsList lance des IOException.

    Bref, je ne sais toujours pas quelle version de primefaces tu utilises, ce que je te suggère c'est de sortir la classe AutoCompleteRenderer du jar que tu utilises et de la passer à JAD pour le décompiler. Comme ceci, tu sauras précisément où ça plante. Peut-être que ça en dira plus...
    N'oubliez pas de consulter les FAQ Java et les cours et tutoriels Java

Discussions similaires

  1. [2008R2] Autorisation sur procédure stockée qui appelle une Vue
    Par billybob2 dans le forum Administration
    Réponses: 6
    Dernier message: 11/03/2014, 09h32
  2. Filtre et tri sur 2 tables => Utilisation d'une vue ?
    Par gmic7 dans le forum Requêtes
    Réponses: 2
    Dernier message: 22/04/2010, 16h28
  3. Réponses: 1
    Dernier message: 18/06/2008, 21h18
  4. Réponses: 4
    Dernier message: 27/11/2006, 18h06
  5. [Tomcat] session expirée redirection vers une page
    Par Casp dans le forum Tomcat et TomEE
    Réponses: 3
    Dernier message: 19/01/2006, 11h45

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo