# Errorpage nach ViewExpiredException



## Raphalon (10. Aug 2011)

Hallo,

um das übliche Problem (siehe z.B. Beitrag1 oder Beitrag2, [EDIT]mögl. andere Ursache auch Beitrag3) einer ViewExpiredException zu handhaben, welches entsteht
1. nach einem Logout (session.invalidate()), 
2. dem Laden einer vorherigen Seite aus dem Cache mittels Back-button des Browsers und 
3. anschließendem Reload
möchte ich diesen Fehler mittels error-page Konfiguration in der web.xml umleiten. Es wird jedoch nur eine leere Seite angezeigt.

Die Fehlerseite wird dann angezeigt, wenn ich (absichtlich) eine (NullPointer)Exception in einem getter eines Feldes erzeugt, welches auf der Zielseite angezeigt werden soll (es wird also eine Seite geladen und auf dieser Seite ist das Feld eingebunden, dessen getter eine Exception erzeugt).

Entsteht dieselbe Exception aber in einer Methode, ohne daß das Ziel der Navigation bereits bestimmt wurde, wird laut server.log zwar die (NullPointer)Exception geworfen, aber die Fehlerseite wird nicht angezeigt. Das Monitoring des glassfish zeigt einen "500 Internal Server error" an. Doch auch auf die 500.html - Seite wird nicht navigiert. Diese befindet sich auch im Verzeichnis errorpages und enthält nur html-Code.

Wo liegt der Fehler? Wo kann ich hierüber etwas nachlesen?

Die web.xml sieht wie folgt aus:

[XML][...]
	<servlet-mapping>
		<servlet-name>Faces Servlet</servlet-name>
		<url-pattern>/faces/*</url-pattern>
	</servlet-mapping>

	<error-page>
		<exception-type>java.lang.Exception</exception-type>
		<location>/faces/errorpages/exception.xhtml</location>
	</error-page>
	<error-page>
		<error-code>500</error-code>
		<location>/errorpages/500.html</location>
	</error-page>
	<error-page>
		<error-code>404</error-code>
		<location>/errorpages/404.html</location>
	</error-page>
	<error-page>
		<error-code>403</error-code>
		<location>/errorpages/403.html</location>
	</error-page>
</web-app>[/XML]


exception.xhtml 

[XML]<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"  
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
	xmlns:h="http://java.sun.com/jsf/html">
   <h:head>
      <title>#{msgs.errorTitle} - EXCEPTION</title>
   </h:head>
   <h:body>
      <h:form>
         <h:graphicImage library="images" name="error.png" style="float: left;"/>
         <p>#{msgs.errorOccurred}</p>
         <p>#{msgs.errorCopyReport}</p>
         <h:inputTextarea value="#{error.stackTrace}" rows="40" cols="80"
                          readonly="true"/>
      </h:form>
   </h:body>
</html>[/XML]
Die (künstlich erzeugte) Exception wird im server.log folgendermaßen angezeigt:


```
[#|2011-08-10T14:11:36.096+0200|WARNING|oracle-glassfish3.1|javax.enterprise.resource.webcontainer.jsf.lifecycle|_ThreadID=21;_ThreadName=Thread-1;|#{user.login}: java.lang.NullPointerException
javax.faces.FacesException: #{user.login}: java.lang.NullPointerException
	at com.sun.faces.application.ActionListenerImpl.processAction(ActionListenerImpl.java:118)
	at javax.faces.component.UICommand.broadcast(UICommand.java:315)
	at javax.faces.component.UIViewRoot.broadcastEvents(UIViewRoot.java:794)
	at javax.faces.component.UIViewRoot.processApplication(UIViewRoot.java:1259)
	at com.sun.faces.lifecycle.InvokeApplicationPhase.execute(InvokeApplicationPhase.java:81)
	at com.sun.faces.lifecycle.Phase.doPhase(Phase.java:101)
	at com.sun.faces.lifecycle.LifecycleImpl.execute(LifecycleImpl.java:118)
	[...]
```
Die eigentliche Exception (siehe ganz oben und 1., 2., 3.), welche ich dann letztlich auch noch abfangen möchte, sieht wie folgt aus:

```
[#|2011-08-10T14:14:53.052+0200|WARNING|oracle-glassfish3.1|javax.enterprise.system.container.web.com.sun.enterprise.web|_ThreadID=35;_ThreadName=Thread-1;|StandardWrapperValve[Faces Servlet]: PWC1406: Servlet.service() for servlet Faces Servlet threw exception
javax.faces.application.ViewExpiredException: viewId:/login.xhtml - Ansicht /login.xhtml konnte nicht wiederhergestellt werden.
	at com.sun.faces.lifecycle.RestoreViewPhase.execute(RestoreViewPhase.java:202)
	at com.sun.faces.lifecycle.Phase.doPhase(Phase.java:101)
	at com.sun.faces.lifecycle.RestoreViewPhase.doPhase(RestoreViewPhase.java:113)
	at com.sun.faces.lifecycle.LifecycleImpl.execute(LifecycleImpl.java:118)
	at javax.faces.webapp.FacesServlet.service(FacesServlet.java:409)
	at org.apache.catalina.core.StandardWrapper.service(StandardWrapper.java:1534)
	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:281)
	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:175)
	[...]
```
Gruß,

Raphalon


----------



## JayGabriel (11. Aug 2011)

Hallo Raphalon,

ich weiß nicht genau, ob es dir weiterhilft:

Ich arbeite nämlich mit MyFaces und nicht mit der Sun Variante, doch in der web.xml kann man angeben, dass die States client- oder serverseitig gespeichert werden sollen.

[xml]
<context-param>
  <param-name>javax.faces.STATE_SAVING_METHOD</param-name>
  <param-value>client</param-value>
</context-param>
[/xml]

Nach der einen Antwort auf die Frage dort, scheint diese "ViewExpiredException" nur dann aufzutreten, wenn der Wert auf "server" gestellt ist. Und nach dieser Liste ist dieser Context-Parameter für MyFaces und Sun verfügbar.

Ich hatte auch ständig beim Timeout diese "ViewExpiredExceptions", doch seitdem ich den Parameter auf "client" gesetzt habe, wurde ich von diesen verschont. Zumindest bei dem Problem mit dem Ablauf der Session oder dem invalidate() tritt es bei mir nicht mehr auf.

Edit:

Die Größe der Zwischenspeicher und demnach, wie weit man zurück gehen kann, kann auch irgendwo mit angegeben werden. Weiß nur gerade nicht genau mit welchem Parameter.

mfg
Jay


----------



## Raphalon (12. Aug 2011)

javax.faces.STATE_SAVING_METHOD gibt an, wo Informationen der UI Komponenten gespeichert werden. Client-seitige Speicherung ist wohl erforderlich für User, welche die Verwendung von Cookies deaktiviert haben. Es reduziert zudem den von jedem User der Webanwendung erforderlichen Speicherbedarf. Das trifft ja auf meine Anwendung nicht zu, da ich Cookies nicht deaktiviert habe. Habe es aber mal ausprobiert: tatsächlich tritt die ViewExpiredException dann nicht mehr auf, aber der User kann dann mittels Back-Button nicht mehr "valide" Seiten aufrufen und erneut an den Server senden, was er ja nach dem Logout nicht mehr soll. 

Man könnte nun wohl mittels isUserInRole bzw. getUserPrincipal für jede Bean/Methode den Zugriff überprüfen, aber da ich keine Cookies verwende, glaube ich nicht, daß die Konfiguration mittels javax.faces.STATE_SAVING_METHOD das eigentliche Problem löst.

Zudem klärt das noch nicht die Frage, wie die NullPointerException, welche der Server intern wirft und als Code 500 erscheint, abgefangen werden kann!?


```
[...]
@SessionScoped
@Named("user")
public class UserBean implements Serializable {
	private String name;
	private String password;

	[...]
	
	public String getPassword() {
		// return password.substring(0);   // <== getter wirft Exception, kann abgefangen werden
		return password;
	}

	public void setPassword(String newValue) {
		password = newValue;
	}

	public String findGoalOfNavigation() {
		//name = null;
		//name = name.substring(0);   // <== Server wirft Exception; Problem: wie fängt man das ab?
		return "welcome";
	}
}
```


----------



## JayGabriel (12. Aug 2011)

Nun was das Abfangen von den eingelogten bzw. nicht eingelogten Usern angeht... warum benutzt du nicht einen PhaseListener, wie in einer der Antworten auf eine andere Frage von dir vorgeschlagen wurde?

Ich benutze ebenfalls einen PhaseListener, der jedes Mal schaut, wenn der User eine Seite ansteuert bzw. eine adere Aktion startet, ob er denn noch eingelogt ist oder nicht. Wenn nicht, wird er sofort auf die LoginSeite zurück geschickt.

Und NullPointer Exception sind ja meist Fehler im Code, da irgendwelche Variablen nicht vorinitialisiert wurden. Wenn nix drin steht, kann auch nix ausgelesen werden. Solche Fehler sollten doch eigentlich nicht auf Fehlerseiten umgeleitet werden, oder?

Da ich sonst nicht weiter mit Fehlerseiten bisher gearbeitet habe (geb bei allen seltständig Fehlermeldungen aus) und auch nicht mit JSF SUN ri, kann ich dir da leider nicht weiterhelfen. Wüsste nur Seiten für MyFaces zum Nachlesen.

Womöglich bringt dich das aber auf den richtigen Weg!?

mfg
Jay


----------



## Raphalon (12. Aug 2011)

JayGabriel hat gesagt.:


> Nun was das Abfangen von den eingelogten bzw. nicht eingelogten Usern angeht... warum benutzt du nicht einen PhaseListener, wie in einer der Antworten auf eine andere Frage von dir vorgeschlagen wurde?


Habe den SystemEvent durch einen PhaseListener ausgetauscht. Laut Logging wird alles genauso aufgerufen wie angedacht. Der PhaseListener wird vor der RESTORE_VIEW-phase aufgerufen und tut zunächst, was er soll. Letztlich besteht aber das gleiche Problem wie beim SystemEvent.
1.) wenn STATE_SAVING_METHOD=server bringt er eine ViewExpiredException, 
2.) wenn STATE_SAVING_METHOD=client, dann kann man über den Back- und dann Reload-Button (zusammen mit Shift) die Seite neu laden. Grund lt. Server-Monitoring: wenn man die Seite, welche direkt nach dem ersten Login geliefert wird, nach dem logout (session.invalidate und loggedIn=false) mit Back- und Reload neu lädt, dann wird der ursprüngliche Login-Request nochmal an den Server gesendet. Damit erfolgt wieder ein Login und die "welcome"-Seite wird erneut geladen. Vermutlich führt dies hier zu keiner Exception, weil der Status der Komponenten auf dem Client gespeichert wird und wieder an den Server gesendet wird (ein ewig langer String).



JayGabriel hat gesagt.:


> Ich benutze ebenfalls einen PhaseListener, der jedes Mal schaut, wenn der User eine Seite ansteuert bzw. eine adere Aktion startet, ob er denn noch eingelogt ist oder nicht. Wenn nicht, wird er sofort auf die LoginSeite zurück geschickt.


Funktioniert das denn bei Dir (Back- und Reload)?



JayGabriel hat gesagt.:


> Und NullPointer Exception sind ja meist Fehler im Code, da irgendwelche Variablen nicht vorinitialisiert wurden. Wenn nix drin steht, kann auch nix ausgelesen werden. Solche Fehler sollten doch eigentlich nicht auf Fehlerseiten umgeleitet werden, oder?


Ja, aber solche Fehler (die ich oben zunächst zu Testzwecken absichtlich eingebaut habe) passieren. Und dann soll keine leere Seite, sondern eine (aufgehübschte) Fehlerseite angezeigt werden, damit der User irgendwie weitergeführt werden kann.



JayGabriel hat gesagt.:


> Womöglich bringt dich das aber auf den richtigen Weg!?


Ok, werd es mir anschauen. Vielen Dank für Deine Mühe!

VG, Raphalon


----------



## JayGabriel (15. Aug 2011)

Raphalon hat gesagt.:


> Habe den SystemEvent durch einen PhaseListener ausgetauscht. Laut Logging wird alles genauso aufgerufen wie angedacht. Der PhaseListener wird vor der RESTORE_VIEW-phase aufgerufen und tut zunächst, was er soll. Letztlich besteht aber das gleiche Problem wie beim SystemEvent.
> 1.) wenn STATE_SAVING_METHOD=server bringt er eine ViewExpiredException,
> 2.) wenn STATE_SAVING_METHOD=client, dann kann man über den Back- und dann Reload-Button (zusammen mit Shift) die Seite neu laden. Grund lt. Server-Monitoring: wenn man die Seite, welche direkt nach dem ersten Login geliefert wird, nach dem logout (session.invalidate und loggedIn=false) mit Back- und Reload neu lädt, dann wird der ursprüngliche Login-Request nochmal an den Server gesendet. Damit erfolgt wieder ein Login und die "welcome"-Seite wird erneut geladen. Vermutlich führt dies hier zu keiner Exception, weil der Status der Komponenten auf dem Client gespeichert wird und wieder an den Server gesendet wird (ein ewig langer String).
> 
> Funktioniert das denn bei Dir (Back- und Reload)?



Ich habe gerade mal alle möglichen Login, Logout und Reload Kombinationen, die mir eingefallen sind, ausprobiert, um noch einmal ganz sicher zu gehen. 
Bei mir funktioniert es. Ich habe aber auch eine eigene UserVerwaltung eingeführt, in der die Session, die ClientID und die Datenklasse zwischengespeichert werden, wenn sich ein Benutzer einlogt. Das hat erstens den Vorteil, dass die Daten nur ein Mal geladen werden (arbeite mit einem WebService als Backend), selbst wenn er sich zwei Mal einlogt und zum anderen kann 'ich' damit auch bisher besser und einfacher umgehen, als mit UserRoles oder dergleichen 
Beim Logout musste ich nur aufpassen, dass all meine Werte, die ich zwischengespeichert habe, vor dem eigentlichen Löschen auch "zurück gesetzt" wurden. So ist bei einem Reload trotzallem alles "null" oder "0" oder so, und da ich nach dem Inhalt der Datenklasse schaue, wird bei "null" auf die LoginSeite zurück verwiesen, egal ob Reload oder nicht und egal auf welcher Seite und mit welchen Scope.



Raphalon hat gesagt.:


> Ja, aber solche Fehler (die ich oben zunächst zu Testzwecken absichtlich eingebaut habe) passieren. Und dann soll keine leere Seite, sondern eine (aufgehübschte) Fehlerseite angezeigt werden, damit der User irgendwie weitergeführt werden kann.



Hmm, okay. Musst du wissen, ob du diese Fehler behalten willst.
Ich arbeite mit zig Abfragen, um genau solche NullPointerException von vornherein nicht zuzulassen, denn meiner Meinung nach sind NullPointerExceptions meist nur ein Zeichen für falsche Initialisierung oder nicht gesetzte Werte. Und wenn der Benutzer in ein Feld nichts eingetragen hat und das aber soll, kann man durch das Abfangen ja perfekt darauf mit einer Hinweismeldung reagieren.

Vielleicht kannst du ja noch einmal etwas genauer beschreiben, was der Benutzer genau machen muss, damit die NullPointerException auftritt?

Sonst solltest du vielleicht bei der ErrorPage nicht nur den Code angeben, sondern die direkte Exception? Nur weiß ich halt nicht genau, welchen genau du da nehmen musst. Aber probier einfach mal durch: java.lang.NullPointerException, javax.faces.FacesException oder eben javax.faces.application.ViewExpiredException. Je nachdem, welche Exception halt bei dir grad auftreten und ausgegeben werden.

Was mir so gerade auffällt beim Schreiben: wenn dir zumindest schon mal eine leere Seite angezeigt wird, scheint ja immerhin etwas passiert zu sein. Auf welcher Seite befindest du dich da denn?

mfg
Jay


----------



## Raphalon (26. Aug 2011)

Zuerst mal sorry für die späte Antwort - ich war im Urlaub.

Habe die Userverwaltung entsprechend umgestellt, jetzt geht es (u.a. mit javax.faces.STATE_SAVING_METHOD = client).

Die Fehler bei mir waren absichtlich eingebaut, damit ich Fehlerseiten entsprechend konfigurieren (lernen) kann. Natürlich sind diese Fehler letztlich Fehler in der Programmierung. Doch ist es nicht gut, wenn der User eine Java-Fehlermeldung in seinem Browser sehen kann. Kaum zu glauben, aber ich habe vor kurzem genau so eine Fehlermeldung auf der Seite einer Firma nach dem Absenden des Formulars gesehen. So etwas wollte ich halt vermeiden. Vielleicht geht das aber auch gar nicht.

Noch eine letzte Frage: habe mich mal mit MyFaces angefreundet. In den Beispielen auf http://myfaces.apache.org/gettingstarted.html werden jedoch jsp Seiten und keine jsf (*.xhtml) verwendet. Warum? Habe jetzt zwei Bücher durch und da wird von jsp gar nichts mehr erwähnt. Verwendet man nach wie vor jsp?


----------



## JayGabriel (29. Aug 2011)

Hallo Raphalon,

als "Grundform" werden jsp-Dateien benutzt, ebenso wie XHTML oder andere. Aber welche du benutzt ist vollkommen Wurscht. Sie brauchen nur eine andere Form von Taglib-Includes.

Es ist eben einfacher, wenn du eine jsp-Datei anlegst und damit die vordefinierten/selbstdefinierten Vorlagen benutzen kannst (rede jetzt vom Beispiel Eclipse), als wenn du eine andere Datei anlegst und sie von Hand anpassen musst. Im Endeffekt wird durch die Konfiguration von JSF bei der nach HTML genierierte Seite eine andere Endung in der URL angezeigt. Je nachdem, was du angibst (.jsf oder .faces oder sonstwas).

Und ein Vorteil von JSP und XHTML als Vorlage ist auch das automatische Syntaxhighlightning und die vielen Beispiele, die es schon gibt. 

Grüße,
Jay


----------

