Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve page session attributes resolution #95

Merged
merged 4 commits into from
Oct 11, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022 Contributors to the Eclipse Foundation. All rights reserved.
* Copyright (c) 2022, 2023 Contributors to the Eclipse Foundation. All rights reserved.
* Copyright (c) 2006, 2022 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
Expand All @@ -21,6 +21,7 @@
import jakarta.el.ELResolver;
import jakarta.el.PropertyNotFoundException;
import jakarta.faces.component.UIViewRoot;
import jakarta.faces.context.ExternalContext;
import jakarta.faces.context.FacesContext;

import java.io.Serializable;
Expand All @@ -31,9 +32,11 @@
* <p>
* This <code>ELResolver</code> exists to resolve "page session" attributes. This concept, borrowed from
* NetDynamics / JATO, stores data w/ the page so that it is available throughout the life of the page. This is longer
* than request scope, but usually shorter than session. This implementation stores the attributes on the
* {@link UIViewRoot#getViewMap()}. Therefore it resolves exactly the same values as the `faces.ScopedAttributeELResolver`, which is
* specified in the Jakarta Faces specification, if there's nothing stored in lower scopes (request scope) and there are no other resolvers.
* than request scope, but usually shorter than session.
* </p>
*
* <p>
* This implementation stores the attributes on the {@link UIViewRoot}.
* </p>
*
* @author Ken Paulsen (ken.paulsen@sun.com)
Expand All @@ -42,14 +45,21 @@ public class PageSessionResolver extends ELResolver {

/**
* <p>
* The name an expression must use when it explicitly specifies page session. ("pageSession")
* The name an expression must use when it explicitly specifies page session ("pageSession").
* </p>
*/
public static final String PAGE_SESSION = "pageSession";

/**
* <p>
* Checks "page session" to see if the value exists.
* The attribute key in which to store the "page" session Map.
* </p>
*/
private static final String PAGE_SESSION_KEY = "_ps";

/**
* <p>
* Checks standard scopes and "page session" to see if the value exists.
* </p>
*/
@Override
Expand All @@ -62,31 +72,60 @@ public Object getValue(ELContext elContext, Object base, Object property) {
throw new PropertyNotFoundException();
}

elContext.setPropertyResolved(true);

FacesContext facesContext = (FacesContext) elContext.getContext(FacesContext.class);
ExternalContext externalContext = facesContext.getExternalContext();
UIViewRoot viewRoot = facesContext.getViewRoot();
Map<String, Serializable> pageSession = getPageSession(facesContext, viewRoot);
String attribute = (String) property;

Object value = null;
// Check to see if expression explicitly asks for PAGE_SESSION
if (property.equals(PAGE_SESSION)) {
// It does, return the Map
if (pageSession == null) {
// No Map! That's ok, create one...
pageSession = createPageSession(facesContext, viewRoot);
}
value = pageSession;
} else {
if (pageSession != null) {
// Check page session
value = pageSession.get(property.toString());
return pageSession;
}

// Check page session exists and contains a property
if (pageSession == null || !pageSession.containsKey(attribute)) {
elContext.setPropertyResolved(false);
return null;
}

// Check request map
Object value = externalContext.getRequestMap().get(attribute);
if (value != null) {
return value;
}

// Check view map
Map<String, Object> viewMap = viewRoot.getViewMap(false);
if (viewMap != null) {
value = viewMap.get(attribute);
if (value != null) {
return value;
}
}

if (value != null || (pageSession != null && pageSession.containsKey(property.toString()))) {
elContext.setPropertyResolved(true);
// Check session map
value = externalContext.getSessionMap().get(attribute);
if (value != null) {
return value;
}

return value;
// Check application map
value = externalContext.getApplicationMap().get(attribute);
if (value != null) {
return value;
}

// Not found updated property in the standard scopes.
// Return value from page session.
return pageSession.get(attribute);
}

@Override
Expand All @@ -97,20 +136,7 @@ public Class<?> getType(ELContext elContext, Object base, Object property) {

@Override
public void setValue(ELContext elContext, Object base, Object property, Object value) {
if (base != null) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does this work? How does this set a page context value? Before, this method expected base == null and property != null and set the property into the page context. This means I think that if a page set some variable (with no base), the variable was added to the page context.

The method after your change expects that base != null, which is different to the condition before. And it doesn't set anything in the page context. What happens if a page just defines a new variable (without base) ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prior to Faces 4, PageSessionResolver was VariableResolver, not ELResolver and therefore did not setup any values.

All page session values are set with our custom handlers, e.g. setPageSessionAttribute.

Now we first try to read updated values, which come to us from standard scopes, and if its not found, read from page session map.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, I understand, thanks :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@OndroMih, page session attributes are "read-only" from the point of view EL engine, imo. Here description from spec https://jakarta.ee/specifications/faces/3.0/jakarta-faces-3.0#a2773.

I tried to implement this part in the getValue() method

Get the head of the VariableResolver chain and call resolveVariable(facesContext, property) and return the result

by checking standard scopes before our custom page session scope. This should prevent us from reading an obsoleted values.

Updates of the values from requests handled by Faces ScopedAttributeELResolver.

return;
}

if (property == null) {
throw new PropertyNotFoundException();
}

FacesContext facesContext = (FacesContext) elContext.getContext(FacesContext.class);
UIViewRoot viewRoot = facesContext.getViewRoot();
Map pageSession = getPageSession(facesContext, viewRoot);
if (pageSession != null) {
pageSession.put(property.toString(), value);
}
checkPropertyFound(base, property);
}

@Override
Expand All @@ -127,29 +153,37 @@ public Class<?> getCommonPropertyType(ELContext elContext, Object base) {
/**
* <p>
* This method provides access to the "page session" <code>Map</code>. If it doesn't exist, it returns
* <code>null</code>. If the given <code>UIViewRoot</code> is null, then the current <code>UIViewRoot</code> will be
* used.
* <code>null</code>. If the given <code>UIViewRoot</code> is null, then the current <code>UIViewRoot</code>
* will be used.
* </p>
*/
@SuppressWarnings("unchecked")
public static Map<String, Serializable> getPageSession(FacesContext facesContext, UIViewRoot viewRoot) {
if (viewRoot == null) {
viewRoot = facesContext.getViewRoot();
}
return (Map)viewRoot.getViewMap(false);
return (Map<String, Serializable>) viewRoot.getAttributes().get(PAGE_SESSION_KEY);
}

/**
* <p>
* This method will create a new "page session" <code>Map</code> if it doesn't exist yet. If it exists,
* it will return it as if {@link #getPageSession(jakarta.faces.context.FacesContext, jakarta.faces.component.UIViewRoot)} was called.
* This method will create a new "page session" <code>Map</code> if it doesn't exist yet.
* It will overwrite any existing "page session" <code>Map</code>, so be careful.
* </p>
*/
public static Map<String, Serializable> createPageSession(FacesContext facesContext, UIViewRoot viewRoot) {
if (viewRoot == null) {
viewRoot = facesContext.getViewRoot();
}
return Map.class.cast(viewRoot.getViewMap());

// Create it...
Map<String, Serializable> pageSession = new HashMap<>(4);

// Store it...
viewRoot.getAttributes().put(PAGE_SESSION_KEY, pageSession);

// Return it...
return pageSession;
}

private static void checkPropertyFound(Object base, Object property) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -247,21 +247,8 @@ public UIViewRoot createView(FacesContext context, String viewId) {
}

// Restore the current UIViewRoot.
// Because new UIViewRoot was temporary set, this will clear
// view map, which also contains our "page session".
// Thus we need reset a new view root's view map after restore original
// view root.
if (currentViewRoot != null) {
Map<String, Object> pageSession = viewRoot.getViewMap(false);
if (pageSession != null) {
pageSession = new HashMap<>(pageSession);
}

if (currentViewRoot != null) {
context.setViewRoot(currentViewRoot);

if (pageSession != null) {
viewRoot.getViewMap().putAll(pageSession);
}
}

// Return the populated UIViewRoot
Expand Down
2 changes: 1 addition & 1 deletion jsftemplating/src/main/resources/META-INF/faces-config.xml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--

Copyright (c) 2022 Contributors to the Eclipse Foundation. All rights reserved.
Copyright (c) 2022, 2023 Contributors to the Eclipse Foundation. All rights reserved.
Copyright (c) 2006, 2022 Oracle and/or its affiliates. All rights reserved.

This program and the accompanying materials are made available under the
Expand Down