2017年1月30日 星期一

將 JSF Composite Component 包裝成 jar 檔供其他 Web 專案使用

1.技術問題

JSF 的 composite component (複合元件) 的功能可以組合現有 UIComponent 成為新的、重複使用的元件。可以組合成的複合元件使用、驗證器、轉換器、JSF 系統事件、及 AJAX。 完成的複合元件會包含:
  • facelet: 檔案名稱為新的標籤的名稱,其內容定義標籤的屬性及組合現有的元件。
  • java class: 支援此複合元件執行所需要的 java 程式碼。
例如,可以組合 h:outputLabel, h:inputText, h:commandButton 等三個元件,再加上一個驗證器成為新的元件專門用來輸入電子郵件。
當你完成複合元件時,你可以在現有 Web 專案的 facelet 中直接使用此元件的標籤。但是,如何將此複合元件變成一個獨立的程式庫供其他的 Web 專案使用呢?



2.參考解答

複合元件可以被包裝成 jar 檔,供其他的 JSF 專案使用。

複合元件被視為應用程式的資源之一,預設需放在 JSF 的 Resource Library 中。如果是在 Web 專案,Resource Library 的位置在 [webroot]/resource 下;當包裝成 war 檔後,其位置在 WEB-INF/resources[2]。如果是在 Java Application 專案,Resource Library 的位置在 META-INF/resources 下。預設資源位置的名稱空間 URL 為 xmlns:my="http://xmlns.jcp.org/jsf/composite/

你也可在 META-INF 下新增一個 [component_name].taglib.xml 檔案自訂自己的名稱空間 URL[3]。

綜合上述,在 jar 檔中的結構為:
  • /META-INF/faces-config.xml
  • /META-INF/[component_name].taglib.xml
  • /META-INF/resources/comp (library with composite components)
  • /META-INF/resources/css (Stylesheets)
  • /META-INF/resources/images
  • /META-INF/resources/js
  • /package_name/class_name.class

Application Configuration Resource File

Application Configuration Resource 檔案,或者 faces-config.xml,是用來註冊及設定物件(object)和資源(resource),並且用來定義瀏覽規則(navigation rule)[4]。 若複合元件中內有使用 annotation 的 java class,則必須在 META-INF 目錄下使用 faces-config.xml,告知 JSF 在 class path 下的類別使用 annotation 註記。此時的 faces-config.xml 的內容為:

<?xml version='1.0' encoding='UTF-8'?>
<faces-config version="2.2"
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-facesconfig_2_2.xsd">
</faces-config>


當應用程式載入時,JSF 會依照以下的方式來尋找一個或多個設定檔案[4]:
  1. 對於 /WEB-INF/lib 目錄下的 jar 檔,尋找此 jar 檔內的 /META-INF/faces-config.xml。如果此檔案存在,會載入此 faces-config.xml 內定義的物件和資源。JSF 會掃描 jar
  2. web.xml 內 javax.faces.application.CONFIG_FILES 參數所指定的一個或多個設定檔案
  3. 在應用程式的 /WEB-INF/faces-config.xml。
你也可將 JSF Facelets 和其相關的 Managed Beans 包裝起來成為 jar 檔供其他專案使用[1]。

3.實作示範

實作一個 DateRangeInput 複合元件,可以輸入起始及結束日期。檢查規則:結束日期不能早於起始日期。日期輸入的元件為 p:calendar (PrimeFaces 的 Calendar 元件)。

Task 1. 製作 DateRangeInput 複合元件

  1. 在 Netbeans 中開啟新的 Java Application 專案
  2. 在專案的 Source Packages 下建立一套件 META-INF.resources.comp
  3. 在 META-INF.resources.comp 下,放入先前完成的 facelet: dateRange.xml
  4. <?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://xmlns.jcp.org/jsf/html"
    xmlns:cc="http://xmlns.jcp.org/jsf/composite"
    xmlns:p="http://primefaces.org/ui"
    xmlns:f="http://xmlns.jcp.org/jsf/core">
    <h:head>
    </h:head>
    <h:body>
    <cc:interface>
    <cc:attribute name="startDate" />
    <cc:attribute name="endDate" />
    <cc:attribute name="errorMsg" />
    </cc:interface>
    <cc:implementation>
    <p:outputLabel value="開始"/>
    <p:calendar id="startDate"
    pattern="yyyy/MM/dd"
    binding="#{startDateComponent}"
    value="#{cc.attrs.startDate}">
    </p:calendar>
    <p:outputLabel value="結束日期"/>
    <p:calendar id="endDate"
    pattern="yyyy/MM/dd"
    value="#{cc.attrs.endDate}">
    <f:validator validatorId="dateRangeValidator" />
    <f:attribute name="startDateComponent"
    value="#{startDateComponent}" />
    <f:attribute name="errorMsg"
    value="#{cc.attrs.errorMsg}" />
    <p:ajax event="change"
    process="startDate endDate"
    update="endDateMsg" />
    <p:ajax event="dateSelect"
    process="startDate endDate"
    update="endDateMsg" />
    </p:calendar>
    <p:message id="endDateMsg" for="endDate" />
    </cc:implementation>
    </h:body>
    </html>
    view raw dateRange.xhtml hosted with ❤ by GitHub
  5. 在 Source Packages 下建立 META-INF 套件。
  6. 在 META-INF 下建立 faces-config.xml。
  7. <?xml version='1.0' encoding='UTF-8'?>
    <faces-config version="2.2"
    xmlns="http://xmlns.jcp.org/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-facesconfig_2_2.xsd">
    </faces-config>
  8. 建立套件 /validator。
  9. 將驗證器 DateRangeValidator.java 放置到套件 /validator。
  10. package validator;
    import java.util.Date;
    import javax.faces.application.FacesMessage;
    import javax.faces.component.UIComponent;
    import javax.faces.component.UIInput;
    import javax.faces.context.FacesContext;
    import javax.faces.validator.FacesValidator;
    import javax.faces.validator.Validator;
    import javax.faces.validator.ValidatorException;
    /**
    * Validate the end date cannot be earlier than the start date. The UIComponent
    * for entering the start date is passed by the attribute with name
    * {@code startDate}.
    *
    *
    * Reference:
    * {@link http://stackoverflow.com/questions/19093192/date-range-validation}
    *
    * @author hychen39@gmail.com
    */
    @FacesValidator("dateRangeValidator")
    public class DateRangeValidator implements Validator {
    public static final String START_DATE_COMPONENT = "startDateComponent";
    public static final String ERROR_MESSAGE = "errorMsg";
    @Override
    public void validate(FacesContext context, UIComponent component, Object value) throws ValidatorException {
    // check the value of the end date
    if (value == null) {
    return;
    }
    // get the start date
    UIInput startDateComponent = (UIInput) component.getAttributes().get(START_DATE_COMPONENT);
    if (!startDateComponent.isValid()) {
    return;
    }
    Date startDate = (Date) startDateComponent.getValue();
    if (startDate == null) {
    return;
    }
    // get the end date
    Date endDate = (Date) value;
    // get the error message
    String msg = (String) component.getAttributes().get(ERROR_MESSAGE);
    String errMsg = (msg != null)? msg: "The end date cannot be before the start date.";
    // validate the end date
    if (startDate.after(endDate)) {
    ((UIInput)component).setValid(false);
    String clientId = component.getClientId();
    FacesContext.getCurrentInstance().addMessage(clientId,
    new FacesMessage(errMsg));
    }
    }
    }
  11. Clean and Build 你的 Java Application 專案,便會產生 jar 檔。
在前述的 Task 中,我們使用預設的 namespace url,所以不需建立 .taglib.xml 檔案來自訂複合元件的 namespace url。 也可以在 Web 專案中使用 Ant 來建立 jar 檔[5]。

Task 2. 在 Web 專案中使用複合元件

  1. 建立新的 JSF Web 專案
  2. 加入 PrimeFaces 及 符合元件的 jar 檔。
  3. 建立一 Managed Bean,其具有 startDate 及 endDate 兩個 properties。
  4. 在 index.xhtml 中加入複合元件的 namespace url,及複合元件的標籤。
  5. Build and Deploy 專案
import java.util.Date;
import javax.inject.Named;
import javax.enterprise.context.RequestScoped;
/**
*
* @author hychen39@gmail.com
*/
@Named(value = "dateRangeBean")
@RequestScoped
public class DateRangeBean {
private Date startDate;
private Date endDate;
public Date getStartDate() {
return startDate;
}
public void setStartDate(Date startDate) {
this.startDate = startDate;
}
public Date getEndDate() {
return endDate;
}
public void setEndDate(Date endDate) {
this.endDate = endDate;
}
/**
* Creates a new instance of DateRangeBean
*/
public DateRangeBean() {
}
}
<?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://xmlns.jcp.org/jsf/html"
xmlns:my="http://xmlns.jcp.org/jsf/composite/comp">
<h:head>
<title>Facelet Title</title>
</h:head>
<h:body>
<h:form>
<my:dateRange startDate="#{dateRangeBean.startDate}"
endDate="#{dateRangeBean.endDate}"/>
</h:form>
</h:body>
</html>
view raw index.xhtml hosted with ❤ by GitHub

4.參考資料

[1] Java EE6> Packaging JSF facelets (xhtml) and ManagedBeans as JAR, http://stackoverflow.com/questions/6104498/java-ee6-packaging-jsf-facelets-xhtml-and-managedbeans-as-jar
[2] Packaging composite component in JSF2 with Netbeans 7.0.1, Maven http://stackoverflow.com/questions/8295935/packaging-composite-component-in-jsf2-with-netbeans-7-0-1-maven
[3] JSF2 Reuse composite components – Packaging in a Jar file - English Article http://www.jcafe.info/2011/02/jsf2-reuse-composite-components.html
[4] Application Configuration Resource File, http://docs.oracle.com/javaee/6/tutorial/doc/bnawp.html
[5] Sharing JSF 2 Composite Components, https://digitaljoel.nerd-herders.com/2009/12/14/sharing-jsf-2-composite-components/

沒有留言: