We have implemented a custom ExceptionHandler that attempts to intercept any NonexistentConversationException that are thrown if a user requests a URL with an old cid in it (eg. bookmarked link etc.).
The handler simply removed the cid from the URL and performs a redirect to the updated URL.
This solution seems to work in most cases, we have a number of different user 'roles' within the App each with their own login areas and for one of these user types it seems that fc.getExternalContext().isResponseCommitted() is always true when the ExceptionHandler fires which means we are unable to perform the redirect. All other user types it works ok.
I am not sure exactly what the difference is with this user type for this to happen, I am guessing some CDI bean we using setup differently
Is there a way to unsure the ExceptionHandler kicks in earlier before the response is committed?
Below is the handler...
public class ConversationExpiredExceptionHandler
extends ExceptionHandlerWrapper {
final static Logger log = LoggerFactory.getLogger(ConversationExpiredExceptionHandler.class);
private ExceptionHandler wrapped;
private static String CONVERSATION_PARAM = "cid";
public ConversationExpiredExceptionHandler(ExceptionHandler wrapped) {
this.wrapped = wrapped;
}
#Override
public ExceptionHandler getWrapped() {
return this.wrapped;
}
#Override
public void handle() throws FacesException {
for (Iterator<ExceptionQueuedEvent> i =
getUnhandledExceptionQueuedEvents().iterator();
i.hasNext();) {
ExceptionQueuedEvent event = i.next();
ExceptionQueuedEventContext context = (ExceptionQueuedEventContext) event.getSource();
Throwable t = context.getException();
if (t instanceof javax.enterprise.context.NonexistentConversationException
|| t instanceof org.jboss.weld.contexts.NonexistentConversationException
|| t.getCause() instanceof org.jboss.weld.contexts.NonexistentConversationException /* can be wrapped in FacesException */) {
final FacesContext fc = FacesContext.getCurrentInstance();
try {
if (!fc.getExternalContext().isResponseCommitted()) {
if(Faces.getRequestQueryStringMap().containsKey(CONVERSATION_PARAM)) {
String requestedUrl = Faces.getRequestURLWithQueryString();
String updatedUrl = this.removeQueryParameter(requestedUrl,CONVERSATION_PARAM);
log.debug("No conversation active for {}, redirecting to {}",requestedUrl,updatedUrl);
fc.getExternalContext().redirect(updatedUrl);
}
}
fc.renderResponse();
break;
} catch (Exception ex) {
throw new FacesException(ex);
} finally {
i.remove();
}
}
}
getWrapped().handle();
}
private String removeQueryParameter(String url, String parameterName) throws URISyntaxException {
URIBuilder uriBuilder = new URIBuilder(url);
List<NameValuePair> queryParameters = uriBuilder.getQueryParams();
for (Iterator<NameValuePair> queryParameterItr = queryParameters.iterator(); queryParameterItr.hasNext();) {
NameValuePair queryParameter = queryParameterItr.next();
if (queryParameter.getName().equals(parameterName)) {
queryParameterItr.remove();
}
}
uriBuilder.setParameters(queryParameters);
return uriBuilder.build().toString();
}
Related
I have two Classes that extend from ExceptionHandlerWrapper & ExceptionHandlerFactory
for catching any exceptions that occur in the view layer
and navigate to error page and show this error.
So I need store or save this exceptions somewhere
but I can`t get this exception message.
I tried to get this message in session but I need another way to save this message with out ViewScope and PageFlowScope and RequestScope since the latter ones return 'null'
import javax.faces.context.ExceptionHandler;
import javax.faces.context.ExceptionHandlerFactory;
public class CustomExceptionHandlerFactory extends ExceptionHandlerFactory {
private ExceptionHandlerFactory parent;
public CustomExceptionHandlerFactory(ExceptionHandlerFactory parent) {
this.parent = parent;
}
#Override
public ExceptionHandler getExceptionHandler() {
ExceptionHandler result = new CustomExceptionHandler(parent.getExceptionHandler());
return result;
}
}
public class CustomExceptionHandler extends ExceptionHandlerWrapper {
private ExceptionHandler wrapped;
public CustomExceptionHandler(ExceptionHandler wrapped) {
this.wrapped = wrapped;
}
#Override
public ExceptionHandler getWrapped() {
return wrapped;
}
#Override
public void handle() throws FacesException {
Iterator iterator = getUnhandledExceptionQueuedEvents().iterator();
while (iterator.hasNext()) {
ExceptionQueuedEvent event = (ExceptionQueuedEvent) iterator.next();
ExceptionQueuedEventContext context = (ExceptionQueuedEventContext) event.getSource();
Throwable throwable = context.getException();
FacesContext fc = FacesContext.getCurrentInstance();
// Map<String, Object> sessionMap = fc.getExternalContext().getSessionMap();
NavigationHandler navigator = fc.getApplication().getNavigationHandler();
AdfFacesContext adfc = AdfFacesContext.getCurrentInstance();
Map<String,Object> valueViewScope = adfc.getViewScope();
try {
// Flash flash = fc.getExternalContext().getFlash();
// Put the exception in the flash scope to be displayed in the error
// page if necessary ...
// flash.put("errorDetails", throwable.getMessage());
ADFContext.getCurrent().getSessionScope().put("errorDetails", throwable.getMessage());
valueViewScope.put("errorDetails", throwable.getMessage());
System.out.println("the error is put in the Session: " + throwable.getMessage());
//NavigationHandler navigationHandler = fc.getApplication().getNavigationHandler();
navigator.handleNavigation(fc, null, "ErrorPage?faces-redirect=true");
// navigationHandler.handleNavigation(fc, null, "ErrorPage?faces-redirect=true");
fc.renderResponse();
} finally {
iterator.remove();
}
}
// Let the parent handle the rest
getWrapped().handle();
}
I am working on an application where I would like to include dynamic XHTML content from a stream. To handle this I wrote a taghandler extension which dumps the dynamic XHTML content to output component as
UIOutput htmlChild = (UIOutput) ctx.getFacesContext().getApplication().createComponent(UIOutput.COMPONENT_TYPE);
htmlChild.setValue(new String(outputStream.toByteArray(), "utf-8"));
This works fine for XHTML content which has no JSF tags. If I have JSF tags in my dynamic XHTML content like <h:inputText value="#{bean.item}"/>, then they're printed as plain text. I want them to render as input fields. How can I achieve this?
Essentially, you should be using an <ui:include> in combination with a custom ResourceHandler which is able to return the resource in flavor of an URL. So when having an OutputStream, you should really be writing it to a (temp) file so that you can get an URL out of it.
E.g.
<ui:include src="/dynamic.xhtml" />
with
public class DynamicResourceHandler extends ResourceHandlerWrapper {
private ResourceHandler wrapped;
public DynamicResourceHandler(ResourceHandler wrapped) {
this.wrapped = wrapped;
}
#Override
public ViewResource createViewResource(FacesContext context, String resourceName) {
if (resourceName.equals("/dynamic.xhtml")) {
try {
File file = File.createTempFile("dynamic-", ".xhtml");
try (Writer writer = new FileWriter(file)) {
writer
.append("<ui:composition")
.append(" xmlns:ui='http://java.sun.com/jsf/facelets'")
.append(" xmlns:h='http://java.sun.com/jsf/html'")
.append(">")
.append("<p>Hello from a dynamic include!</p>")
.append("<p>The below should render as a real input field:</p>")
.append("<p><h:inputText /></p>")
.append("</ui:composition>");
}
final URL url = file.toURI().toURL();
return new ViewResource(){
#Override
public URL getURL() {
return url;
}
};
}
catch (IOException e) {
throw new FacesException(e);
}
}
return super.createViewResource(context, resourceName);
}
#Override
public ResourceHandler getWrapped() {
return wrapped;
}
}
(warning: basic kickoff example! this creates a new temp file on every request, a reuse/cache system should be invented on your own)
which is registered in faces-config.xml as follows
<application>
<resource-handler>com.example.DynamicResourceHandler</resource-handler>
</application>
Note: all of above is JSF 2.2 targeted. For JSF 2.0/2.1 users stumbling upon this answer, you should use ResourceResolver instead for which an example is available in this answer: Obtaining Facelets templates/files from an external filesystem or database. Important note: ResourceResolver is deprecated in JSF 2.2 in favor of ResourceHandler#createViewResource().
My solution for JSF 2.2 and custom URLStream Handler
public class DatabaseResourceHandlerWrapper extends ResourceHandlerWrapper {
private ResourceHandler wrapped;
#Inject
UserSessionBean userBeean;
public DatabaseResourceHandlerWrapper(ResourceHandler wrapped) {
this.wrapped = wrapped;
}
#Override
public Resource createResource(String resourceName, String libraryName) {
return super.createResource(resourceName, libraryName); //To change body of generated methods, choose Tools | Templates.
}
#Override
public ViewResource createViewResource(FacesContext context, String resourceName) {
if (resourceName.startsWith("/dynamic.xhtml?")) {
try {
String query = resourceName.substring("/dynamic.xhtml?".length());
Map<String, String> params = splitQuery(query);
//do some query to get content
String content = "<ui:composition"
+ " xmlns='http://www.w3.org/1999/xhtml' xmlns:ui='http://java.sun.com/jsf/facelets'"
+ " xmlns:h='http://java.sun.com/jsf/html'> MY CONTENT"
+ "</ui:composition>";
final URL url = new URL(null, "string://helloworld", new MyCustomHandler(content));
return new ViewResource() {
#Override
public URL getURL() {
return url;
}
};
} catch (IOException e) {
throw new FacesException(e);
}
}
return super.createViewResource(context, resourceName);
}
public static Map<String, String> splitQuery(String query) throws UnsupportedEncodingException {
Map<String, String> params = new LinkedHashMap<>();
String[] pairs = query.split("&");
for (String pair : pairs) {
int idx = pair.indexOf("=");
params.put(URLDecoder.decode(pair.substring(0, idx), "UTF-8"), URLDecoder.decode(pair.substring(idx + 1), "UTF-8"));
}
return params;
}
#Override
public ResourceHandler getWrapped() {
return wrapped;
}
static class MyCustomHandler extends URLStreamHandler {
private String content;
public MyCustomHandler(String content) {
this.content = content;
}
#Override
protected URLConnection openConnection(URL u) throws IOException {
return new UserURLConnection(u, content);
}
private static class UserURLConnection extends URLConnection {
private String content;
public UserURLConnection(URL url, String content) {
super(url);
this.content = content;
}
#Override
public void connect() throws IOException {
}
#Override
public InputStream getInputStream() throws IOException {
return new ByteArrayInputStream(content.getBytes("UTF-8"));
}
}
}
}
I'm currently having trouble regarding the token generated by <protected-views> of JSF.
I added the page I want to protect in faces-config.xml
<protected-views>
<url-pattern>/restricted/account-management/users.xhtml</url-pattern>
<url-pattern>/restricted/account-management/users.jsf</url-pattern>
</protected-views>
Then for example when I go the users page using an <h:link>
<h:link outcome="users" title="View">
<f:param name="user" value="#{e.id}" />
</h:link>
the token generated in the URL is this
/restricted/account-management/users.jsf?javax.faces.Token=OW5KkkfJZrrfmZSXwA%253D%253D&user=4
The page returns a ProtectedViewException
Then I found out that the correct token is actually:
/restricted/account-management/users.jsf?javax.faces.Token=OW5KkkfJZrrfmZSXwA%3D%3D
The token was encoded in the URL, where % became %25. When I copy-paste the correct token into the URL, I get into the users page successfully.
Any help would be appreciated.
This is a problem with the versions 2.2.11 and above of Mojarra JSF Implementation, you can see the details about issue in https://github.com/javaee/javaserverfaces-spec/issues/1161 and here https://github.com/javaserverfaces/mojarra/issues/4139
One of the alternatives to handle the problem is to create a CustomExternalContext to handle the double encoding.
First you need declare in faces-config.xml a CustomExternalContextFactory:
<factory>
<external-context-factory>com.proitc.config.CustomExternalContextFactory</external-context-factory>
</factory>
In the ExternalContextFactory you define the CustomExternalContext:
public class CustomExternalContextFactory extends ExternalContextFactory {
private ExternalContextFactory externalContextFactory;
public CustomExternalContextFactory() {}
public CustomExternalContextFactory(ExternalContextFactory externalContextFactory) {
this.externalContextFactory = externalContextFactory;
}
#Override
public ExternalContext getExternalContext(Object context, Object request, Object response)
throws FacesException {
ExternalContext handler = new CustomExternalContext((ServletContext) context,
(HttpServletRequest) request, (HttpServletResponse) response);
return handler;
}
}
The CustomExternalContext override the methods encodeBookmarkableURL and encodeRedirectURL:
public class CustomExternalContext extends ExternalContextImpl {
public CustomExternalContext(ServletContext sc, ServletRequest request,
ServletResponse response) {
super(sc, request, response);
}
#Override
public String encodeBookmarkableURL(String baseUrl, Map<String, List<String>> parameters) {
FacesContext context = FacesContext.getCurrentInstance();
String encodingFromContext =
(String) context.getAttributes().get(RIConstants.FACELETS_ENCODING_KEY);
if (null == encodingFromContext) {
encodingFromContext =
(String) context.getViewRoot().getAttributes().get(RIConstants.FACELETS_ENCODING_KEY);
}
String currentResponseEncoding =
(null != encodingFromContext) ? encodingFromContext : getResponseCharacterEncoding();
UrlBuilder builder = new UrlBuilder(baseUrl, currentResponseEncoding);
builder.addParameters(parameters);
String secureUrl = builder.createUrl();
//Handle double encoding
if (parameters.size() > 0 && baseUrl.contains("javax.faces.Token")) {
try {
int beginToken = secureUrl.indexOf("javax.faces.Token");
int endToken = secureUrl.indexOf("&") - 1;
String doubleEncodeToken = secureUrl.substring(beginToken, endToken);
String encodeToken = URLDecoder.decode(doubleEncodeToken, currentResponseEncoding);
secureUrl = secureUrl.replace(doubleEncodeToken, encodeToken);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
return secureUrl;
}
#Override
public String encodeRedirectURL(String baseUrl, Map<String, List<String>> parameters) {
FacesContext context = FacesContext.getCurrentInstance();
String encodingFromContext =
(String) context.getAttributes().get(RIConstants.FACELETS_ENCODING_KEY);
if (null == encodingFromContext) {
encodingFromContext =
(String) context.getViewRoot().getAttributes().get(RIConstants.FACELETS_ENCODING_KEY);
}
String currentResponseEncoding =
(null != encodingFromContext) ? encodingFromContext : getResponseCharacterEncoding();
UrlBuilder builder = new UrlBuilder(baseUrl, currentResponseEncoding);
builder.addParameters(parameters);
String secureUrl = builder.createUrl();
//Handle double encoding
if (parameters.size() > 0 && baseUrl.contains("javax.faces.Token")) {
try {
int beginToken = secureUrl.indexOf("javax.faces.Token");
int endToken = secureUrl.indexOf("&") - 1;
String doubleEncodeToken = secureUrl.substring(beginToken, endToken);
String encodeToken = URLDecoder.decode(doubleEncodeToken, currentResponseEncoding);
secureUrl = secureUrl.replace(doubleEncodeToken, encodeToken);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
return secureUrl;
}
}
You can find a working example in https://github.com/earth001/jsf-protected-view
I'm using JSF 2.2 (Mojarra) and Faclets in a web application. I use a custom ExceptionHandler to handle exceptions. I leverage the JSF implicit navigation system and cause the server to navigate to the 'error.xhtml' page.
public class FrontEndExceptionHandler extends ExceptionHandlerWrapper {
private ExceptionHandler wrapped;
FrontEndExceptionHandler(ExceptionHandler exception) {
this.wrapped = exception;
}
#Override
public ExceptionHandler getWrapped() {
return wrapped;
}
#Override
public void handle() throws FacesException {
final Iterator<ExceptionQueuedEvent> iter = getUnhandledExceptionQueuedEvents().iterator();
while (iter.hasNext()) {
ExceptionQueuedEvent event = iter.next();
ExceptionQueuedEventContext context = (ExceptionQueuedEventContext) event.getSource();
// get the exception from context
Throwable t = context.getException();
final FacesContext fc = FacesContext.getCurrentInstance();
final Flash flash = fc.getExternalContext().getFlash();
final NavigationHandler nav = fc.getApplication().getNavigationHandler();
try {
// redirect error page
flash.put("erorrDetails", t.getMessage());
nav.handleNavigation(fc, null, "/errors/error.xhtml");
fc.renderResponse();
} finally {
// remove it from queue
iter.remove();
}
}
// parent handle
getWrapped().handle();
}
}
This assumes that the exception to be handling is not happening during the Render Response phase. But the exception occurs within an Facelets page also during the Render Response phase.Therefore following code doesn't work correct:
nav.handleNavigation(fc, null, "/errors/error.xhtml");
Does anybody has a Idea how to convey the desired information? Is there a way to navigate to the error.xhtml without using NavigationHandler?
I wrote myself a custom NavigationHandler very similar to following one but just using a stack to save the history:
http://jsfatwork.irian.at/book_de/custom_component.html#!idx:/custom_component.html:fig:backnavigationhandler-code
public class HistoryNavigationHandler extends NavigationHandler
{
private final NavigationHandler navigationHandler;
private final Stack<String> outcomes;
public HistoryNavigationHandler(final NavigationHandler navigationHandler)
{
this.navigationHandler = navigationHandler;
this.outcomes = new Stack<String>();
}
#Override
public void handleNavigation(final FacesContext context, final String fromAction, final String outcome)
{
if (outcome != null)
{
if (outcome.equals("back"))
{
final String lastViewId = this.outcomes.pop();
final ViewHandler viewHandler = context.getApplication().getViewHandler();
final UIViewRoot viewRoot = viewHandler.createView(context, lastViewId);
context.setViewRoot(viewRoot);
context.renderResponse();
return;
}
else
{
this.outcomes.push(context.getViewRoot().getViewId());
}
}
this.navigationHandler.handleNavigation(context, fromAction, outcome);
}
}
Registering this one in the faces-config.xml:
<navigation-handler>
package.HistoryNavigationHandler
</navigation-handler>
Results in following log warning and a message where a previously working link was present:
Warning: jsf.outcome.target.invalid.navigationhandler.type
Something like: this link is disabled because a navigation case could not be matched
What is the problem?
Since JSF 2, the NavigationHandler has been replaced by ConfigurableNavigationHandler. All JSF 2 specific tags/components like <h:link> and so on are relying on it. The NavigationHandler is kept for backwards compatibility.
Here's a kickoff example how to properly extend ConfigurableNavigationHandler:
public class HistoryNavigationHandler extends ConfigurableNavigationHandler {
private NavigationHandler wrapped;
public HistoryNavigationHandler(NavigationHandler wrapped) {
this.wrapped = wrapped;
}
#Override
public void handleNavigation(FacesContext context, String from, String outcome) {
// TODO: Do your job here.
wrapped.handleNavigation(context, from, outcome);
}
#Override
public NavigationCase getNavigationCase(FacesContext context, String fromAction, String outcome) {
return (wrapped instanceof ConfigurableNavigationHandler)
? ((ConfigurableNavigationHandler) wrapped).getNavigationCase(context, fromAction, outcome)
: null;
}
#Override
public Map<String, Set<NavigationCase>> getNavigationCases() {
return (wrapped instanceof ConfigurableNavigationHandler)
? ((ConfigurableNavigationHandler) wrapped).getNavigationCases()
: null;
}
}