In my last post, I offered a very rough overview of the framework I've been building and adapting. Full credit where it's due, I've been borrowing and adapting from Jesse Gallagher's XPages Scaffolding - an awesome project that I've learned a great deal dissecting. This is one of those projects that, if you make the effort to truly understand it, will give you entirely new tools and approaches for problems. As usual, I've learned the most changing things and breaking them. My project differs from Jesse's mostly in that it supports 8.5.3 and doesn't require any relaxed security permissions - because I'm on 8.5.3 and because the admins here will not grant any relaxed permissions.
One of the big changes I've made is to eliminate the role of the ControllingViewHandler. To rehash it's purpose: it determines which controller should be used for the XPage being generated, and binds the event handlers to the JSF lifecycle. In it's place, I have a ControllerPhaseListener and an XPageControllerFactory.
Let's take a quick look at those:
ControllerPhaseListener.java
XPageControllerFactory.java
Up next: the specific case of the XAgent controller.
One of the big changes I've made is to eliminate the role of the ControllingViewHandler. To rehash it's purpose: it determines which controller should be used for the XPage being generated, and binds the event handlers to the JSF lifecycle. In it's place, I have a ControllerPhaseListener and an XPageControllerFactory.
Let's take a quick look at those:
ControllerPhaseListener.java
package itd.common.jsf;A quick rundown of optional features:
import itd.common.mvc.controller.XPageControllerFactory;
import itd.common.utils.JSF;
import itd.logging.Logger;
import javax.faces.context.FacesContext;
import javax.faces.event.PhaseEvent;
import javax.faces.event.PhaseId;
import javax.faces.event.PhaseListener;
import javax.servlet.http.HttpServletRequest;
public class ControllerPhaseListener implements PhaseListener {
private static final long serialVersionUID = 1L;
private static final Logger logger = Logger.getLogger(ControllerPhaseListener.class.getName());
public PhaseId getPhaseId() {
return PhaseId.ANY_PHASE;
}
public void beforePhase(final PhaseEvent event) {
PhaseId phaseId = event.getPhaseId();
JSF.setCurrentPhaseId(phaseId);
if (PhaseId.RESTORE_VIEW.equals(phaseId)) {
XPageControllerFactory.getController().beforeRestoreView();
}
else if (PhaseId.APPLY_REQUEST_VALUES.equals(phaseId)) {
XPageControllerFactory.getController().beforeApplyRequest();
}
else if (PhaseId.PROCESS_VALIDATIONS.equals(phaseId)) {
XPageControllerFactory.getController().beforeProcessValidations();
}
else if (PhaseId.UPDATE_MODEL_VALUES.equals(phaseId)) {
XPageControllerFactory.getController().beforeUpdateModelValues();
}
else if (PhaseId.INVOKE_APPLICATION.equals(phaseId)) {
XPageControllerFactory.getController().beforeInvokeApplication();
}
else if (PhaseId.RENDER_RESPONSE.equals(phaseId)) {
XPageControllerFactory.getController().beforeRenderResponse();
}
logPhase("BEGIN PHASE [" + event.getPhaseId().getOrdinal() + "] ", event);
}
public void afterPhase(final PhaseEvent event) {
logPhase("END PHASE [" + event.getPhaseId().getOrdinal() + "] ", event);
PhaseId phaseId = event.getPhaseId();
if (PhaseId.RESTORE_VIEW.equals(phaseId)) {
XPageControllerFactory.getController().afterRestoreView();
}
else if (PhaseId.APPLY_REQUEST_VALUES.equals(phaseId)) {
XPageControllerFactory.getController().afterApplyRequest();
}
else if (PhaseId.PROCESS_VALIDATIONS.equals(phaseId)) {
XPageControllerFactory.getController().afterProcessValidations();
}
else if (PhaseId.UPDATE_MODEL_VALUES.equals(phaseId)) {
XPageControllerFactory.getController().afterUpdateModelValues();
}
else if (PhaseId.INVOKE_APPLICATION.equals(phaseId)) {
XPageControllerFactory.getController().afterInvokeApplication();
}
else if (PhaseId.RENDER_RESPONSE.equals(phaseId)) {
XPageControllerFactory.getController().afterRenderResponse();
clearFlashScope(event);
}
JSF.clearCurrentPhaseId();
}
private void logPhase(final String prefix, final PhaseEvent event) {
// LogMan.getLogger(AppControlPhaseListener.class.getName());
String msg = prefix;
if (event.getPhaseId().equals(PhaseId.RESTORE_VIEW)) {
msg += "RESTORE_VIEW";
}
else if (event.getPhaseId().equals(PhaseId.APPLY_REQUEST_VALUES)) {
msg += "APPLY_REQUEST_VALUES";
}
else if (event.getPhaseId().equals(PhaseId.PROCESS_VALIDATIONS)) {
msg += "PROCESS_VALIDATIONS";
}
else if (event.getPhaseId().equals(PhaseId.UPDATE_MODEL_VALUES)) {
msg += "UPDATE_MODEL_VALUES";
}
else if (event.getPhaseId().equals(PhaseId.INVOKE_APPLICATION)) {
msg += "INVOKE_APPLICATION";
}
else if (event.getPhaseId().equals(PhaseId.RENDER_RESPONSE)) {
msg += "RENDER_RESPONSE";
}
logger.debug(msg);
}
private void clearFlashScope(final PhaseEvent event) {
FacesContext context = event.getFacesContext();
HttpServletRequest request = (HttpServletRequest) context.getExternalContext().getRequest();
if (!request.getMethod().equals("POST")) {
JSF.getFlashMap().clear();
}
}
}
- JSF.setCurrentPhaseId() / JSF.clearCurrentPhaseId() : Before the first before[PhaseEvent] method is invoked, I'm setting a requestScope variable to the current PhaseId. It is occasionally useful to know or restrict the event a method is running in.
- logPhase() : This logs the JSF lifecycle. It lets me know which events are running, and can show me at a glance when certain code is executed, or perhaps why it isn't performing as expected in certain cases.
- clearFlashScope() : A more in-depth discussion of flashScope can be found elsewhere, but in short it is a JSF 2.0 construct that is unavailable in XPages but is very easy to implement (in fact, this is the extent of the implementation other than declaring it in faces-config.xml).
XPageControllerFactory.java
package itd.common.mvc.controller;Key features:
import itd.common.utils.JSF;
import itd.common.utils.Strings;
import itd.logging.Logger;
import java.util.Map;
import javax.faces.context.FacesContext;
public final class XPageControllerFactory {
private static final Logger logger = Logger.getLogger(XPageControllerFactory.class.getName());
protected final static String BEAN = "controller";
private XPageControllerFactory() {
}
public static final XPageController getController() {
XPageController controller = null;
String key = XPageController.class.getName();
Map<String, Object> requestMap = JSF.getRequestMap();
if (requestMap.containsKey(key)) {
return (XPageController) requestMap.get(key);
}
else {
Class<? extends XPageController> controllerClass = null;
FacesContext context = JSF.getFacesContext();
String controllerName = JSF.getAppConfig().cachedGet("controller.package") + "."
+ Strings.upperFirst(JSF.getPageName()) + JSF.getAppConfig().cachedGet("controller.suffix");
try {
logger.trace("Getting controller: " + controllerName);
controllerClass = (Class<? extends XPageController>) Class.forName(controllerName);
}
catch (ClassNotFoundException cnfe) {
String baseController = JSF.getAppConfig().cachedGet("controller.baseclass");
controllerName = JSF.getAppConfig().cachedGet("controller.package") + "."
+ JSF.getAppConfig().cachedGet("controller.baseclass");
logger.trace("No page controller. Getting base controller: " + controllerName);
try {
controllerClass = (Class<? extends XPageController>) Class.forName(controllerName);
}
catch (ClassNotFoundException cnfe2) {
logger.trace("No base controller, getting generic controller");
controllerClass = XPageController.class;
}
}
try {
controller = controllerClass.newInstance();
}
catch (IllegalAccessException e) {
logger.fatal("Unable to access page controller class.", e);
throw new RuntimeException(e);
}
catch (InstantiationException e) {
logger.fatal("Unable to instantiate page controller class.", e);
throw new RuntimeException(e);
}
requestMap.put(key, controller);
JSF.getViewRoot().getViewMap().put(BEAN, controller);
return controller;
}
}
}
- The controller is cached in the requestScope, which means we only create it once per request.
- The controller is added to the ViewRoot ViewMap, which allows us to use #{controller} in our EL expressions.
- The package in which Controllers are define is configurable in application.properties, as is the suffix used to name the controller ('Controller' in this case). Also the ControllerBase class (I'll explain that momentarily).
- If a class is found named [controller.package][XPage][controller.suffix] (e.g. app.controllers.HomeController), it is instantiated and used as the controller.
- If that class does not exist, the ControllerBase is used. ControllerBase is where any ActionListeners and EventHandlers that are not page-specific are added. All page controllers extend and call the super() methods of this class so that their unique functionality is added to the core event handlers.
- If that class does not exist, the XPageController class is used, which is really just a fail-safe. All events are implemented with a simple return; statement.
- XPageController is generic and universal to all applications. It has no built-in functionality and primarily exists to provide default handling of events we aren't interested in.
- ControllerBase is application-specific and application-wide. If an ActionListener or EventHandler pertains to more than one page in the application (e.g. navigation), it goes here.
- Specific XPage controller is invoked only for the named page. It can handle, for example, parsing URL parameters to load a specific document.
Up next: the specific case of the XAgent controller.
Comments
Post a Comment