JSF - 如何从backing bean action方法中确定当前的JSP页面

时间:2010-10-04 18:49:24

标签: java jsf myfaces tomahawk

我有一个包含两个JSP页面的JSF应用程序,它们都显示来自会话范围的容器对象的一些相同数据。每个页面以不同的方式显示数据,每个页面在不同页面之间变化的数据表中。到目前为止这一切都正常。

我的问题是我一直在欺骗我如何从我的支持bean操作方法中找出请求的页面。在每个页面上,我都使用了绑定数据表。

draftReport.jsp

<t:dataTable 
    border="1"
    id="reportDraftDataTable"
    binding="#{controller.reportDraftDataTable}"
    value="#{sessionData.reportDraftAdapterList}" 
    var="currentRow" 
    rowClasses="dataTableOddRow, dataTableEvenRow">

report.jsp

<t:dataTable 
    border="1"
    id="reportDataTable"
    binding="#{controller.reportDataTable}"
    value="#{sessionData.reportAdapterList}" 
    var="currentRow" 
    rowClasses="dataTableOddRow, dataTableEvenRow">

我有一个请求范围的支持bean(名为Controller),其中包含这些页面的一些操作方法。我没有在支持bean上复制代码(每个类似的JSP页面有一个类似的方法),而是想弄清楚正在呈现什么页面并将其用作通用处理程序方法的参数(可以处理来自两个页面的操作)支持豆。所以我作弊并做到了这一点:

public class Controller {
    ...

    private HtmlDataTable preArrivalReportDataTable;
    private HtmlDataTable preArrivalReportDraftDataTable;
    private static enum ReportType {
        NON_DRAFT,
        DRAFT
    }
    ...

    private ReportType determineReportTypeFromControlBindings() {
        Validate.isTrue(this.preArrivalReportDataTable != null ^
                this.preArrivalReportDraftDataTable != null,
            "Either preArrivalReportDataTable XOR " +
            "preArrivalReportDraftDataTable must be null in " +
            "determineReportTypeFromControlBindings()");
        if (this.preArrivalReportDataTable != null) {
            return ReportType.NON_DRAFT;
        } else {
            return ReportType.DRAFT;
        }
    }
    ...

    public String actionOnReport() {
        ReportType reportType = null;
        reportType = determineReportTypeFromControlBindings();
        handleReportAction(reportType);
        return "REFRESH";
    }
    ...
}

这在我的Controller类中的动作方法中运行正常,但后来我需要添加另一个最终破坏我的hacky代码的方法:

    public String getStyleClass() {
        ReportType reportType = determineReportTypeFromControlBindings();
        switch (reportType) {
            case NON_DRAFT:
                return "styleA";
            case DRAFT:
                return "styleB";
            default:
                return null;
        }
    }

在我的JSP中,JSF-EL表达式位于我在辅助bean中使用的数据表的控件绑定之上,以确定我所在的页面。此时determineReportTypeFromControlBindings()在Validate检查时抛出异常,可能是因为尚未发生控件绑定。

我对这种情况并不感到惊讶。总觉得这是错误的方式。但我的问题是:

从请求范围的辅助bean操作方法确定当前请求的JSP页面的正确方法是什么?

如果它是相关的,我正在使用MyFaces 1.2 Tomahawk标签库。

2 个答案:

答案 0 :(得分:2)

我可以想到一些方法,一个是主动的,页面告诉bean视图是什么,一个被动的,bean推断视图。最后一个是拥有一个抽象bean,其中包含草案和非草稿的具体实现。

我更喜欢最后一种方法,它感觉最像Java,而且最不像hacky。但是这里有一些关于如何做前两个的基本想法。

主动:在renderResponse阶段调用方法以设置报告类型。我只在会话作用域中完成此操作,不确定它在请求作用域中的工作情况,您可能需要检查其他阶段,或者可能只是应用它而不管实际阶段。

<强> Controller.java

public void draftInitializer(PhaseEvent event) {
  if (event.getPhaseId().equals(PhaseId.RENDER_RESPONSE)) {
    reportType = DRAFT;
  }
} 

<强> draftReport.jsp

<f:view beforePhase="#{controller.draftInitializer}">

反应:从请求中获取网址。

<强> Controller.java

  private String getRequestURL(){
    HttpServletRequest request = (HttpServletRequest)FacesContext.getExternalContext().getRequest();
    return request.getRequestURL();
  }

  private boolean isDraft() {
    return getRequestURL().contains(DRAFT_URL_IDENTIFIER);
  }

答案 1 :(得分:0)

所以我最终通过检查在请求期间从UIViewRoot获得的FacesContext对象来解决这个问题。我用ReportType枚举替换了我的RequestedPage枚举,因为它看起来更具可读性。

public static enum RequestedPage {
    REPORT,
    REPORT_DRAFT,
    UNKNOWN
}

然后我在Controller类中创建了一些String常量和一个新方法。

private final static String REPORT_DRAFT_JSP_NAME = "draftReport.jsp";
private final static String REPORT_JSP_NAME = "report.jsp";

/**
 * This method should only be invoked from inside an action method.
 * An exception will be thrown if the method is called when either
 * the FacesContext or UIViewRoot are not available. This is normally
 * the case outside of an active request or before the RESTORE_VIEW
 * phase has been completed.
 * 
 * @return A non-null RequestedPage reference
 */
private RequestedPage determineRequestedPageFromViewId() {
    FacesContext facesContext = FacesContext.getCurrentInstance();
    Validate.notNull(facesContext);
    UIViewRoot uiViewRoot = facesContext.getViewRoot();
    Validate.notNull(uiViewRoot);
    String viewId = uiViewRoot.getViewId();
    logger.info("view id: " + viewId);
    RequestedPage requestedPage = null;
    if (viewId.contains(REPORT_DRAFT_JSP_NAME)) {
        requestedPage = RequestedPage.REPORT_DRAFT;
    } else if (viewId.contains(REPORT_JSP_NAME)) {
        requestedPage = RequestedPage.REPORT;
    } else {
        requestedPage = RequestedPage.UNKNOWN;
    }
    return requestedPage;
}

UNKNOWN enum旨在涵盖我的行动方法中我不关心的所有页面。只要我遵守我在方法Javadoc注释中提到的约束,这似乎工作正常。

这种方法唯一令人失望的事情是我想在我的RequestedPage类的初始化方法中进行Controller解析。不幸的是,这不起作用,因为初始化器在RESTORE_VIEW阶段开始之前被调用,因此UIViewRoot仍为空。

以下是 NOT 工作的代码:

@PostConstruct
public void init() {
    logger.info("init() has been invoked");
    RequestedPage requestedPage = 
        determineRequestedPageFromViewId();
    // An exception is always thrown before I get here...
    this.theRequestedPage = requestedPage;
    logger.info("init() finished");
}

我可以忍受这个,除非别人有一个简单的替代方法。