2013年9月25日星期三

ADF_240:Session 过期时的处理方法之二:Redirect 到其它页面

开发运行环境:JDeveloper 11.1.2.4 + Oracle XE Database 11gR2

在前一个实验的基础上,只要修改web.xml中的WARNING_BEFORE_TIMEOUT值就可以,比如默认的120秒。

其它的地方都不需要修改。

1. web.xml

<context-param>
    <param-name>oracle.adf.view.rich.sessionHandling.WARNING_BEFORE_TIMEOUT</param-name>
    <param-value>120</param-value>
</context-param>

<filter>
    <filter-name>SessionTimeOutFilter</filter-name>
    <filter-class>view.SessionTimeOutFilter</filter-class>
    <init-param>
        <param-name>SessionTimeoutRedirect</param-name>
        <param-value>index.jsf</param-value>
    </init-param>
</filter>

<filter-mapping>
    <filter-name>SessionTimeOutFilter</filter-name>
    <servlet-name>Faces Servlet</servlet-name>
</filter-mapping>

2. 运行效果
session过期前两分钟弹出“失效警告”窗口,如果没有理会,那么session过期后,警告窗口自动关闭,然后弹出“页面失效”窗口,点击确定后,会Redirect到你指定的页面,我这里是index.jsf。

我的实验结果发现,后台会报出一个异常:
java.lang.IllegalStateException: HttpSession is invalid
at weblogic.servlet.internal.session.SessionData.getAttributeNames(SessionData.java:483)
at view.SessionTimeOutSessionListener.sessionDestroyed(SessionTimeOutSessionListener.java:30)
at weblogic.servlet.internal.EventsManager.notifySessionLifetimeEvent(EventsManager.java:276)
at weblogic.servlet.internal.session.SessionData.remove(SessionData.java:971)
at weblogic.servlet.internal.session.MemorySessionContext.invalidateSession(MemorySessionContext.java:69)
Truncated. see log file for complete stacktrace
这是因为我定义了一个SessionListener,用来监听session何时创建,何时失效。
但我不知道为何sessionDestroyed时,会抛出这个异常,不过页面没有受到影响。

3. SessionTimeOutSessionListener 

package view;

import java.util.Enumeration;

import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;

public class SessionTimeOutSessionListener implements HttpSessionListener {
    private HttpSession session = null;

    public void sessionCreated(HttpSessionEvent event) {
        System.out.println("%%%%%%%%%%%%%%%%%%%%%%%%% Session Created at " + new java.util.Date());
        session = event.getSession();

        String name = null;
        Object value = null;
        for (Enumeration enu = session.getAttributeNames(); enu.hasMoreElements(); ) {
            name = (String)enu.nextElement();
            value = session.getAttribute(name);
            System.out.println("%%%%%%%%%%%%%%%%%%%%%%%%% name = " + name + ",  value= " + value);
        }
    }

    public void sessionDestroyed(HttpSessionEvent event) {
        System.out.println("%%%%%%%%%%%%%%%%%%%%%%%%% Session Destroyed at " + new java.util.Date());
        String name = null;
        Object value = null;

        for (Enumeration enu = session.getAttributeNames(); enu.hasMoreElements(); ) {
            name = (String)enu.nextElement();
            value = session.getAttribute(name);
            System.out.println("%%%%%%%%%%%%%%%%%%%%%%%%% name = " + name + ",  value= " + value);
        }
    }
}

ADF_239:Session 过期时的处理方法之一:禁止弹出窗口

开发运行环境:JDeveloper 11.1.2.4 + Oracle XE Database 11gR2

访问ADF页面时,如果超过在web.xml中的设置session-timeout的时间,没有任何动作的话,会弹出如下窗口:
图1

点击后,会重新刷新当前页面。
如果,在web.xml中增加oracle.adf.view.rich.sessionHandling.WARNING_BEFORE_TIMEOUT设置如下:

<context-param>
    <param-name>oracle.adf.view.rich.sessionHandling.WARNING_BEFORE_TIMEOUT</param-name>
    <param-value>120</param-value>
</context-param>

会在session过期之前的2分钟(120秒)时自动弹出如下窗口:

 图2
点击确定,会重新刷新当前页面,并且session不会过期。
如果没有理会该警告,那么在session过期之后,该窗口会自动关闭,然后弹出图1的窗口。

以上两个提示窗口是ADF默认提供的功能,一般来说,用户尚可接受。

如果把WARNING_BEFORE_TIMEOUT设置为0,那么session过期后,不会弹出任何窗口。
当用户在页面上进行任何操作时,会弹出如下窗口:

 图3
点击确定后,页面显示如下:
图4
这当然不是我们所希望的。

在实际需求中,用户可能不希望session过期时弹出任何窗口,而是重新刷新当前页面,那该怎么做呢?

我尝试了很多种办法,比如使用SessionListener,PhaseListener,Filter,ExceptionHandler。
最后发现,使用Filter可以解决用户的这个需求。

1. SessionTimeOutFilter.java

package view;


import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class SessionTimeOutFilter implements Filter {
    public SessionTimeOutFilter() {
        super();
    }
    private FilterConfig filterConfig = null;

    public void init(FilterConfig filterConfig) throws ServletException {
        this.filterConfig = filterConfig;
    }

    public void destroy() {
        filterConfig = null;
    }

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException,
                                                                                                  ServletException {
        System.out.println("%%%%%%%%%%%%%%%%%%%%%%%%% doFilter");
        String requestedSession = ((HttpServletRequest)request).getRequestedSessionId();
        String currentWebSession = ((HttpServletRequest)request).getSession().getId();
        boolean sessionOk = currentWebSession.equalsIgnoreCase(requestedSession);
        // if the requested session is null then this is the first application
        // request and "false" is acceptable
        if (!sessionOk && requestedSession != null) {
            System.out.println("%%%%%%%%%%%%%%%%%%%%%%%%% session has expired or renewed. Redirect request.");
            // the session has expired or renewed. Redirect request
            ((HttpServletResponse)response).sendRedirect(filterConfig.getInitParameter("SessionTimeoutRedirect"));
        } else {
            chain.doFilter(request, response);
        }
    }
}

2. 在web.xml中增加如下配置

<context-param>
    <param-name>oracle.adf.view.rich.sessionHandling.WARNING_BEFORE_TIMEOUT</param-name>
    <param-value>0</param-value>
</context-param>

<filter>
    <filter-name>SessionTimeOutFilter</filter-name>
    <filter-class>view.SessionTimeOutFilter</filter-class>
    <init-param>
        <param-name>SessionTimeoutRedirect</param-name>
        <param-value>index.jsf</param-value>
    </init-param>
</filter>

<filter-mapping>
    <filter-name>SessionTimeOutFilter</filter-name>
    <servlet-name>Faces Servlet</servlet-name>
</filter-mapping>

3. 运行效果
当session过期后,不会有任何窗口弹出,用户在页面上进行任何操作时,会重新刷新当前页面。
我实验的结果还发现,后台会报出一个错误:
<RichExceptionHandler> <_logUnhandledException> ADF_FACES-60098:Faces 生命周期在阶段RESTORE_VIEW 1中接收到未处理的异常错误
java.lang.IllegalStateException: null windowId

查了一下,没找出原因,页面上没有任何影响,以后有时间再研究一下。

Project 下载:ADF_SessionTimeout.7z

参考文献:
1. http://www.baigzeeshan.com/2010/12/how-to-set-session-time-out-in-adf.html
2. http://www.baigzeeshan.com/2011/05/how-to-run-java-code-on-every-page-load.html
3. http://maverickshyam88.wordpress.com/2013/01/30/handling-session-time-out-in-adf/
4. http://www.baigzeeshan.com/2011/07/how-to-automatically-redirect-to.html
5. https://forums.oracle.com/thread/549724
6. https://cwiki.apache.org/confluence/display/MYFACES/Access+FacesContext+From+Servlet
7. http://www.coderanch.com/t/467358/JSF/java/access-faces-context-backing-beans
8. http://blog.olrichs.nl/2013/05/support-for-multiple-session-timeouts.html
9. http://adfdevelopers.blogspot.ch/2009/06/detecting-and-handling-user-session.html
10. http://prsync.com/oracle/tips-on-dealing-with-session-time-out-popup-522231/
11. https://forums.oracle.com/message/4239840
12. https://forums.oracle.com/thread/2437276
13. http://ramannanda.blogspot.jp/2013/04/adf-exception-handling-scenario.html
14. https://forums.oracle.com/thread/2358909
15. http://stackoverflow.com/questions/2543094/how-to-redirect-to-index-page-if-session-time-out-happend-in-jsf-application

2013年9月23日星期一

JDev_043:使用Jersey 实现CRUD

开发运行环境:JDeveloper 11.1.2.4

一直对Restful风格的Web Service很感兴趣,正好项目中需要把一个服务包装成Restful服务,就尝试做了一把CRUD。
Restful服务的设计理念:服务的设计以系统资源为中心,而不是以服务的功能为中心。

(1)GET 用来获取集合资源或单个资源。
比如:GET http://www.store.com/products 获取所有商品;
GET http://www.store.com/products/ 呈现指定的某件商品
(2)POST 用来创建一个新资源。
比如:POST http://www.store.com/products 增加一个新商品。
注意,这里不能用型如 http://www.store.com/products/的URI,因为此时新的product_id还没产生呢。
(3)PUT 用来修改集合资源或单个资源。
比如:PUT http://www.store.com/products 修改所有商品;
PUT http://www.store.com/products/ 修改指定的某件商品。
(4)DELETE 用来删除集合资源或单个资源,与PUT的URI一样。
(5)GET、PUT 和 DELETE 方法是幂等的,POST方法则不是。

1. Employee.java
一个很简单的Java Bean,即有些人常说的Data Object,代码从略。

2. EmployeeServcie.java
所有操作Employee Data Object的服务都在这里,即有些人常说的Service Object,代码如下:
package restfulservice;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;

import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;

import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;

import javax.ws.rs.core.Response;

@Path("/employees")
public class EmployeeService {

    private ArrayList employees = new ArrayList();

    public EmployeeService() {
        super();
        employees.add(new Employee(3, "test3", 10, 100));
        employees.add(new Employee(6, "test6", 10, 100));
        employees.add(new Employee(9, "test9", 10, 100));
    }

    @POST
    @Consumes( { MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
    @Produces( { MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
    public Response createEmployee(Employee emp) {
        employees.add(emp);
        return Response.ok(emp).build();
    }


    @PUT
    @Path("{employeeId}")
    @Consumes( { MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
    @Produces( { MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
    public Response upadteEmployee(@PathParam("employeeId")
        int employeeId, Employee emp) {
        deleteEmployeeByPathParam(employeeId);
        createEmployee(emp);
        return Response.ok(emp).build();
    }

    @PUT
    @Consumes( { MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
    @Produces( { MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
    public Response upadteEmployees(Employee[] emps) {
        deleteEmployees();
        employees.addAll(Arrays.asList(emps));
        return Response.ok(emps).build();
    }

    @DELETE
    @Produces( { MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
    public Response deleteEmployeeByQueryParam(@QueryParam("employeeId")
        int employeeId) {
        Employee emp = getEmployee(employeeId);
        employees.remove(emp);
        return Response.ok(emp).build();
    }

    @DELETE
    @Path("{employeeId}")
    @Produces( { MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
    public Response deleteEmployeeByPathParam(@PathParam("employeeId")
        int employeeId) {
        Employee emp = getEmployee(employeeId);
        employees.remove(emp);
        return Response.ok(emp).build();
    }

    @DELETE
    public void deleteEmployees() {
        employees.clear();
    }

    @GET
    @Produces( { MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
    public ArrayList getEmployees() {
        return employees;
    }

    @GET
    @Path("{employeeId}")
    @Produces( { MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
    public Employee getEmployee(@PathParam("employeeId")
        int employeeId) {
        Iterator iter = employees.iterator();
        while (iter.hasNext()) {
            Employee emp = (Employee)iter.next();
            if ((emp.getId()) == employeeId) {
                return emp;
            }
        }
        return null;
    }
}

3. EmployeeClient.java
调用Restful服务的客户端,我还是费了些功夫的,注意CRUD的调用方式各有不同:

package restfulclient;


import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.api.client.filter.LoggingFilter;

import javax.ws.rs.core.MediaType;

public class EmployeeClient {
    public EmployeeClient() {
        super();
    }

    private final static String restfulUrl = "http://localhost:7101/RestfulSample/jersey/employees";

    public static void main(String[] args) {
        System.out.println("\n***************************************** POST ****************************************");
        Client client_post = Client.create();
        WebResource resource_post = client_post.resource(restfulUrl);
        resource_post.addFilter(new LoggingFilter());
        String response_post = resource_post.type(MediaType.APPLICATION_XML).post(String.class, buildPostXml());

        System.out.println("\n***************************************** PUT ONE ****************************************");
        Client client_putOne = Client.create();
        WebResource resource_putOne = client_putOne.resource(restfulUrl + "/1");
        resource_putOne.addFilter(new LoggingFilter());
        String response_putOne = resource_putOne.type(MediaType.APPLICATION_XML).put(String.class, buildPutXml());


        System.out.println("\n******************************* DELETE ONE BY QUERY PARAMETER *********************************");
        Client client_deleteOne = Client.create();
        WebResource resource_deleteOne = client_deleteOne.resource(restfulUrl + "?employeeId=3");
        resource_deleteOne.addFilter(new LoggingFilter());
        resource_deleteOne.delete();
     
        System.out.println("\n********************************** DELETE ONE BY PATH PARAMETER ******************************");
        Client client_deleteOne2 = Client.create();
        WebResource resource_deleteOne2 = client_deleteOne2.resource(restfulUrl + "/6");
        resource_deleteOne2.addFilter(new LoggingFilter());
        String response_deleteOne2 = resource_deleteOne2.type(MediaType.APPLICATION_XML).delete(String.class);

        System.out.println("\n***************************************** GET ALL ****************************************");
        Client client_getAll = Client.create();
        WebResource resource_getAll = client_getAll.resource(restfulUrl);
        resource_getAll.addFilter(new LoggingFilter());
        resource_getAll.get(String.class);
        resource_getAll.accept(MediaType.APPLICATION_JSON).get(String.class);

        System.out.println("\n***************************************** GET ONE ****************************************");

        Client client_getOne = Client.create();
        WebResource resource_getOne = client_getOne.resource(restfulUrl + "/9");
        resource_getOne.addFilter(new LoggingFilter());
        resource_getOne.get(String.class);
        resource_getOne.accept(MediaType.APPLICATION_JSON).get(String.class);

        System.out.println("\n***************************************** PUT ALL ****************************************");
        Client client_putAll = Client.create();
        WebResource resource_putAll = client_putAll.resource(restfulUrl);
        resource_putAll.addFilter(new LoggingFilter());
        String response_putAll = resource_putAll.type(MediaType.APPLICATION_XML).put(String.class, buildPutsXml());

        System.out.println("\n***************************************** DELETE ALL ****************************************");
        Client client_deleteAll = Client.create();
        WebResource resource_deleteAll = client_deleteAll.resource(restfulUrl);
        resource_deleteAll.addFilter(new LoggingFilter());
        resource_deleteAll.delete();    
    }

    private static String buildPostXml() {
        StringBuffer xml = new StringBuffer("\n");
        xml.append("1\n");
        xml.append("test1\n");
        xml.append("10\n");
        xml.append("100\n");
        xml.append("\n");     
        return xml.toString();
    }

    private static String buildPutXml() {
        StringBuffer xml = new StringBuffer("\n");
        xml.append("1\n");
        xml.append("test1\n");
        xml.append("11\n");
        xml.append("111\n");
        xml.append("\n");     
        return xml.toString();
    }

    private static String buildPutsXml() {
        StringBuffer xml = new StringBuffer("\n");
        xml.append("\n");
        xml.append("2\n");
        xml.append("test2\n");
        xml.append("10\n");
        xml.append("100\n");
        xml.append("\n");
        xml.append("\n");
        xml.append("3\n");
        xml.append("test3\n");
        xml.append("10\n");
        xml.append("100\n");
        xml.append("\n");     
        xml.append("\n");     
        return xml.toString();
    }
}

4. 修改web.xml,增加com.sun.jersey.config.feature.Formatted参数,这样输出的XML内容是格式化的。

<servlet>
    <servlet-name>jersey</servlet-name>
    <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
    <init-param>
        <param-name>com.sun.jersey.config.feature.Formatted</param-name>
        <param-value>true</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>

Project 下载:RestfulSample.7z

最后再多说一句,什么时候用SOAP Web Service,什么时候用Restful Service?
简单来说,面向资源的服务使用Restful Web Service,面向功能的使用SOAP Web Service。
即如果想本例一样,都是对员工的CRUD,可以使用Restful Web Service;如果还有其它的操作,比如入职、离职,升迁,调岗等等,应该使用SOAP Web Service。

参考文献:
1. http://stackoverflow.com/questions/630453/put-vs-post-in-rest
2. http://zh.wikipedia.org/wiki/REST
3. http://www.infoq.com/cn/articles/understanding-restful-style?utm_source=infoq&utm_medium=popular_links_homepage
4. http://blog.csdn.net/zouzhile/article/details/5017347
5. http://www.restapitutorial.com/httpstatuscodes.html

2013年9月16日星期一

JDev_042:怎样从XML得到Java?

开发运行环境:JDeveloper 11.1.1.7

本文探讨的问题是:如果你已经有一个XML文件,如何它转换为Java?
这里需要用到JAXB的技术,关于JAXB的介绍,请参考《JAXB介绍》,本文不再赘述。
从XML到Java的过程,用专业的术语表示就是Unmarshal。

1. 原始XML文件:rss.xml
这里以某博客的RSS订阅的XML为例,文件内容比较大,从略。

2. 根据XML文件生成XML Schema文件



3. 根据XML Schema生成JAXB对象
可以发现自动生成的Rss.java中,还有一个内部类:Channel,Channel还有两个内部类:Item和Link。


4. 修改Rss.java
增加main方法如下:
    public static void main(String[] args) throws JAXBException, SAXException {
        JAXBContext jaxbctx = JAXBContext.newInstance("xml2java.generated");
        Unmarshaller unm = jaxbctx.createUnmarshaller();
        File xml =
            new File("D:/JDevRuntime/JDev11117/mywork/Xml2Java/Xml2Java/rss.xml");
        Rss rss = (Rss)unm.unmarshal(xml);
        System.out.println(rss.getChannel().getDescription());
        for (Rss.Channel.Item i : rss.getChannel().getItem()) {
            System.out.println(i.getTitle() + " - " + i.getPubDate());
        }
    }
main函数中使用了UnMarshaller对象,分析XML文件内容,返回Rss对象。

5. 运行Rss.java
输出如下,可以看到,XML内容被Unmarshal成了Java对象。

Weblog for the AMIS Technology corner
How to run into and resolve EL PropertyNotFound exception and
          propertyNotReadable in JSF with private inner class - Thu, 23 Jun 2011 13:22:57 +0000
Using the ADF DVT Radar Graph for comparing series – further analyzing
          ODTUG Kaleidoscope 2011 Session Schedule - Wed, 22 Jun 2011 12:38:51 +0000
JDeveloper 11gR2: New option Test WebService in WSDL editor – quickest
          route to implement and test JAX-WS SOAP WebService - Fri, 17 Jun 2011 14:11:28 +0000
Tracking the moving history of averages and other aggregates –
          Flashback Aggregates in Oracle SQL - Thu, 16 Jun 2011 13:39:07 +0000
Leveraging HTML 5 Navigator API to show the browser’s current location
          on an ADF Faces 11gR2 Thematic Map component - Wed, 15 Jun 2011 09:17:45 +0000
Creating JSON document straight from SQL query – using LISTAGG and
          With Clause - Tue, 14 Jun 2011 10:00:28 +0000
Producing simple Pie Chart straight out of the Oracle Database –
          leveraging dbms_epg, CANVAS, LISTAGG and some JavaScript - Mon, 13 Jun 2011 15:10:33 +0000
Enriching EL evaluation in JSF applications – retrieve values from
          property files or system properties - Sun, 12 Jun 2011 13:42:40 +0000
Leveraging Spring Beans in ADF Faces applications and using the Spring
          PropertyPlaceholderConfigurer bean to dynamically configure bean
          properties from external files - Sat, 11 Jun 2011 08:28:04 +0000
Browser-based log-monitor for database applications (alternative to
          dbms_output,leveraging pipe and Embedded PL/SQL Gateway with a touch
          of AJAX) - Fri, 10 Jun 2011 20:40:54 +0000

Project 下载:Xml2Java.7z

参考文献:
1. http://technology.amis.nl/2011/06/25/turning-any-xml-document-into-java-object-graph-using-jaxb-2-0/

JDev_041:怎样从Java得到XML?

开发运行环境:JDeveloper 11.1.1.7

本文探讨的问题是:如果你已经有了Java类,比如一些POJO类,如何把它们转换为XML?
这里需要用到JAXB的技术,关于JAXB的介绍,请参考《JAXB介绍》,本文不再赘述。
从Java到XML的过程,用专业的术语表示就是Marshal。

1. 创建POJO类
(1)Company.java
package java2xml;

import java.io.ByteArrayOutputStream;

import java.util.List;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;

@XmlType(namespace = "http://myNameSpace")
@XmlRootElement(name = "Corporation")
public class Company {

    private String name;
    @XmlElement
    private List employees;

    public Company() {
    }

    @XmlElement
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public List getEmployees() {
        return EmpManager.getEmpManager().getEmps();
    }

    public void setEmployees() {
        EmpManager.getEmpManager().resetData();
    }
}
注意,原来加在属性name上的批注@XmlElement,改到加在方法getName()上。
如果不改的话,执行schemagen.exe时会抛出如下异常:
com.sun.xml.internal.bind.v2.runtime.IllegalAnnotationsException: 1 counts of IllegalAnnotationExceptions
Class has two properties of the same name "name"
this problem is related to the following location:
at public java.lang.String java2xml.Company.getName()
at java2xml.Company
this problem is related to the following location:
at private java.lang.String java2xml.Company.name
at java2xml.Company

(2)Emp.java
比较简单,从略。

2. 运行schemagen.exe 生成 XML Schema
在JDK的bin目录下有一个命令:schemagen.exe,运行如下脚本生成XML Schema:
schemagen.exe -d D:\JDevRuntime\JDev11117\mywork\Java2Xml\Java2Xml\src -cp D:\JDevRuntime\JDev11117\mywork\Java2Xml\Java2XML\classes java2xml.Company java2xml.Emp

会生成两个xsd文件:
(1)schema1.xsd
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<xs:schema version="1.0" targetNamespace="http://myNameSpace" xmlns:xs="http://www.w3.org/2001/XMLSchema">

  <xs:import schemaLocation="schema2.xsd"/>

  <xs:complexType name="company">
    <xs:sequence>
      <xs:element name="employees" type="emp" minOccurs="0" maxOccurs="unbounded"/>
      <xs:element name="name" type="xs:string" minOccurs="0"/>
    </xs:sequence>
  </xs:complexType>
</xs:schema>

(2)schema2.xsd
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<xs:schema version="1.0" xmlns:ns1="http://myNameSpace" xmlns:xs="http://www.w3.org/2001/XMLSchema">

  <xs:import namespace="http://myNameSpace" schemaLocation="schema1.xsd"/>

  <xs:element name="Corporation" type="ns1:company"/>


  <xs:complexType name="emp">
    <xs:sequence>
      <xs:element name="comm" type="xs:double" minOccurs="0"/>
      <xs:element name="deptno" type="xs:long" minOccurs="0"/>
      <xs:element name="empno" type="xs:long" minOccurs="0"/>
      <xs:element name="ename" type="xs:string" minOccurs="0"/>
      <xs:element name="job" type="xs:string" minOccurs="0"/>
      <xs:element name="mgr" type="xs:long" minOccurs="0"/>
      <xs:element name="sal" type="xs:double" minOccurs="0"/>
    </xs:sequence>
  </xs:complexType>
</xs:schema>

3. 生成JAXB对象
(1)右键schema1.xsd,选择“Generate JAXB 2.0 Content Model”:

(2)为了不与原来的POJO类冲突,这里放到另一个目录

4. 修改自动生成的POJO类
(1)在Company.java中增加如下方法:

    public Company() {
        resetData();
    }

    public void resetData() {
        createEmp((long)7369, "SMITH", "CLERK", (long)7902, (double)800, null,
                  (long)20);
        createEmp((long)7499, "ALLEN", "SALESMAN", (long)7698, (double)1600,
                  (double)300, (long)30);
        createEmp((long)7521, "WARD", "SALESMAN", (long)7698, (double)1250,
                  (double)500, (long)30);
        createEmp((long)7566, "JONES", "MANAGER", (long)7839, (double)2975,
                  null, (long)20);
        createEmp((long)7654, "MARTIN", "SALESMAN", (long)7698, (double)1250,
                  (double)1400, (long)30);
        createEmp((long)7698, "BLAKE", "MANAGER", (long)7839, (double)2850,
                  null, (long)30);
        createEmp((long)7782, "CLARK", "MANAGER", (long)7839, (double)2450,
                  null, (long)10);
        createEmp((long)7788, "SCOTT", "ANALYST", (long)7566, (double)3000,
                  null, (long)20);
        createEmp((long)7839, "KING", "PRESIDENT", null, (double)5000, null,
                  (long)10);
        createEmp((long)7844, "TURNER", "SALESMAN", (long)7698, (double)1500,
                  (double)0, (long)30);
        createEmp((long)7876, "ADAMS", "CLERK", (long)7788, (double)1100, null,
                  (long)20);
        createEmp((long)7900, "JAMES", "CLERK", (long)7698, (double)950, null,
                  (long)30);
        createEmp((long)7902, "FORD", "ANALYST", (long)7566, (double)3000,
                  null, (long)20);
        createEmp((long)7934, "MILLER", "CLERK", (long)7782, (double)1300,
                  null, (long)10);
    }

    public void createEmp(Long empno, String ename, String job, Long mgr,
                          Double sal, Double comm, Long deptno) {
        Emp newEmp = new ObjectFactory().createEmp();

        newEmp.update(empno, ename, job, mgr, sal, comm, deptno);
        getEmployees().add(newEmp);
    }

    public static void main(String[] args) throws JAXBException {
        JAXBContext jaxbContext = JAXBContext.newInstance("java2xml.generated");
        Marshaller marshaller = jaxbContext.createMarshaller();
        marshaller.setProperty("jaxb.formatted.output", Boolean.TRUE);
        ObjectFactory objf = new ObjectFactory();
        Company company = objf.createCompany();
        company.setName("Ma Ping and Friends");
        marshaller.marshal(company, System.out);
    }
main函数中使用了Marshaller对象,使用ObjectFactory创建了Company对象,然后marshal该对象并输出。

(2)在Emp.java中增加如下方法:

    public void update(Long empno, String ename, String job, Long mgr,
                       Double sal, Double comm, Long deptno) {
        this.empno = empno;
        this.ename = ename;
        this.job = job;
        this.mgr = mgr;
        this.sal = sal;
        this.comm = comm;
        this.deptno = deptno;
    }

5. 运行修改后的Company.java
输出的XML内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<Corporation xmlns:ns0="http://myNameSpace">
   <employees>
      <deptno>20</deptno>
      <empno>7369</empno>
      <ename>SMITH</ename>
      <job>CLERK</job>
      <mgr>7902</mgr>
      <sal>800.0</sal>
   </employees>
   <employees>
      <comm>300.0</comm>
      <deptno>30</deptno>
      <empno>7499</empno>
      <ename>ALLEN</ename>
      <job>SALESMAN</job>
      <mgr>7698</mgr>
      <sal>1600.0</sal>
   </employees>
   <employees>
      <comm>500.0</comm>
      <deptno>30</deptno>
      <empno>7521</empno>
      <ename>WARD</ename>
      <job>SALESMAN</job>
      <mgr>7698</mgr>
      <sal>1250.0</sal>
   </employees>
   <employees>
      <deptno>20</deptno>
      <empno>7566</empno>
      <ename>JONES</ename>
      <job>MANAGER</job>
      <mgr>7839</mgr>
      <sal>2975.0</sal>
   </employees>
   <employees>
      <comm>1400.0</comm>
      <deptno>30</deptno>
      <empno>7654</empno>
      <ename>MARTIN</ename>
      <job>SALESMAN</job>
      <mgr>7698</mgr>
      <sal>1250.0</sal>
   </employees>
   <employees>
      <deptno>30</deptno>
      <empno>7698</empno>
      <ename>BLAKE</ename>
      <job>MANAGER</job>
      <mgr>7839</mgr>
      <sal>2850.0</sal>
   </employees>
   <employees>
      <deptno>10</deptno>
      <empno>7782</empno>
      <ename>CLARK</ename>
      <job>MANAGER</job>
      <mgr>7839</mgr>
      <sal>2450.0</sal>
   </employees>
   <employees>
      <deptno>20</deptno>
      <empno>7788</empno>
      <ename>SCOTT</ename>
      <job>ANALYST</job>
      <mgr>7566</mgr>
      <sal>3000.0</sal>
   </employees>
   <employees>
      <deptno>10</deptno>
      <empno>7839</empno>
      <ename>KING</ename>
      <job>PRESIDENT</job>
      <sal>5000.0</sal>
   </employees>
   <employees>
      <comm>0.0</comm>
      <deptno>30</deptno>
      <empno>7844</empno>
      <ename>TURNER</ename>
      <job>SALESMAN</job>
      <mgr>7698</mgr>
      <sal>1500.0</sal>
   </employees>
   <employees>
      <deptno>20</deptno>
      <empno>7876</empno>
      <ename>ADAMS</ename>
      <job>CLERK</job>
      <mgr>7788</mgr>
      <sal>1100.0</sal>
   </employees>
   <employees>
      <deptno>30</deptno>
      <empno>7900</empno>
      <ename>JAMES</ename>
      <job>CLERK</job>
      <mgr>7698</mgr>
      <sal>950.0</sal>
   </employees>
   <employees>
      <deptno>20</deptno>
      <empno>7902</empno>
      <ename>FORD</ename>
      <job>ANALYST</job>
      <mgr>7566</mgr>
      <sal>3000.0</sal>
   </employees>
   <employees>
      <deptno>10</deptno>
      <empno>7934</empno>
      <ename>MILLER</ename>
      <job>CLERK</job>
      <mgr>7782</mgr>
      <sal>1300.0</sal>
   </employees>
   <name>Ma Ping and Friends</name>
</Corporation>

小结:实际应用中,为了能够让原始的POJO类和自动生成的同名POJO类不冲突,需要写包装类CompanyWrapper,EmpWrapper,这样所有的操作都统一针对Wrapper类的操作。

Project 下载:Java2Xml.7z

参考文献:
1. http://technology.amis.nl/2011/06/24/creating-an-xml-document-based-on-my-pojo-domain-model-how-will-jaxb-help-me/
2. http://technology.amis.nl/2011/06/25/turning-any-xml-document-into-java-object-graph-using-jaxb-2-0/
3. http://technology.amis.nl/2008/06/15/how-to-quickly-generate-an-xsd-xml-schema-definition-based-on-a-java-class-model-using-jdeveloper-11g/
4. http://www.developer.com/services/article.php/3722211/Using-JAXB-in-JDeveloper-1013.htm
5. http://biemond.blogspot.com/2008/02/jaxb-20-in-jdeveloper-10g-and-11g.html

Glassfish_004:如何修改管理员口令?

默认情况下,Glassfish的管理员是admin,口令为空。
如果你想设置管理员口令的话,可以通过命令行操作。

在Glassfish的bin目录下,运行asadmin,进入

asdmin>change-admin-password --user admin
admin password>adminadmin
Enter new admin password>newadmin
Enter new admin password again>newadmin
修改成功后,会显示如下信息:
Command change-admin-password executed successfully.

注意,Glassfish4.0后,默认的admin口令为空。

2013年9月15日星期日

JavaEE_021:一道面试题

这是曾经遇到的一道比较有意思的面试题,这是考官发给我的邮件,题目如下:

As part of the interview process, you are to write an application that will submit an interview form with your details. Here is what you need to do
1. Download and install JavaSE 6 if you have not.
2. Download and install Glassfish 3.1 application server. Make sure Glassfish is running on JavaSE 6 that you have downloaded in item 1 above.
3. Deploy interview.war to Glassfish
4. Goto http://yourserver:yourport/interview for further instructions

Good luck

你需要把 interview.war 部署到Glassfish上,然后依照提示做题。
如果你解答正确的话,考官会收到邮件,然后通知你下一步面试。
我觉得这种面试方式比较新颖,并不是问那些在Google上可以找到的现成答案的技术问题。
相反,它考察了技术人员的实际动手能力:能否在短时间内学习一门新技术、新产品,并且解决遇到的问题。

这里我也不做过多提示,因为考官当时也没有给我任何提示。
怎么样,感兴趣的话,就动手试试吧!

祝你好运!

如果你面试中也遇到过比较有意思的题目,欢迎给我发信或留言,大家彼此交流和分享一下。

2013年9月7日星期六

NetBeans_025:开发JavaEE 7 应用之八:使用Faces Flow实现预订电影票功能

开发运行环境:NetBeans7.3.1。

这里使用的是JSF 2.2的新特性:Faces Flow,关于JSF 2.2的介绍,请参考《JavaEE7 十大新特性》。

预订电影票功能包括一系列页面:
(1)显示电影列表。
(2)显示某个电影的观影时间表。
(3)确认购买。
(4)显示购买详细信息。
这里使用Faces Flow,把这些页面放到一个Flow中,这样可以达到页面功能模块化的目的。

1. Booking.java

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package org.glassfish.movieplex7.booking;

import java.util.List;
import java.util.StringTokenizer;
import javax.faces.flow.FlowScoped;
import javax.inject.Named;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.PersistenceContext;
import org.glassfish.movieplex7.entities.Movie;
import org.glassfish.movieplex7.entities.ShowTiming;

/**
 *
 * @author pmma
 */
@Named
@FlowScoped("booking")
public class Booking {

    int movieId;
    String startTime;
    int startTimeId;
    @PersistenceContext
    EntityManager em;

    public int getMovieId() {
        return movieId;
    }

    public void setMovieId(int movieId) {
        this.movieId = movieId;
    }

    public String getMovieName() {
        try {
            return em.createNamedQuery("Movie.findById",
                    Movie.class).setParameter("id", movieId).getSingleResult().getName();
        } catch (NoResultException e) {
            return "";
        }
    }

    public String getStartTime() {
        return startTime;
    }

    public void setStartTime(String startTime) {
        StringTokenizer tokens = new StringTokenizer(startTime, ",");
        startTimeId = Integer.parseInt(tokens.nextToken());
        this.startTime = tokens.nextToken();
    }

    public int getStartTimeId() {
        return startTimeId;
    }

    public String getTheater() {
// for a movie and show
        try {
// Always return the first theater
            List list =
                    em.createNamedQuery("ShowTiming.findByMovieAndTimingId",
                    ShowTiming.class)
                    .setParameter("movieId", movieId)
                    .setParameter("timingId", startTimeId)
                    .getResultList();
            if (list.isEmpty()) {
                return "none";
            }
            return list
                    .get(0)
                    .getTheaterId()
                    .getId().toString();
        } catch (NoResultException e) {
            return "none";
        }
    }
}
说明:
(1)@FlowScoped("booking")表明该类的生命周期范围是Flow Scope,"booking"对应Flow定义的id值。

2. booking-flow.xml
在booking目录下,有booking.xhtml,showtimes.xhtml,confirm.xhtml,print.xhtml。
现在在booking目录下,创建booking-flow.xml。

<?xml version="1.0" encoding="UTF-8"?>
<!--
To change this template, choose Tools | Templates
and open the template in the editor.
-->

<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">
    <flow-definition id="booking">
        <flow-return id="goHome">
            <from-outcome>/index</from-outcome>
        </flow-return>
    </flow-definition>
</faces-config>

Project 下载:9. movieplex7(JSF).7z

NetBeans_024:开发JavaEE 7 应用之七:使用JMS实现观影积分功能

开发运行环境:NetBeans7.3.1。

这里使用的是JMS 2.0的特性,关于JMS 2.0的介绍,请参考《JavaEE7 十大新特性》。

1. SendPointsBean.java
SendPointsBean.java用来模拟发送观影积分,消息会发送一个Queue中。

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package org.glassfish.movieplex7.points;

import javax.annotation.Resource;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.inject.Named;
import javax.jms.JMSContext;
import javax.jms.Queue;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;

/**
 *
 * @author pmma
 */
@Named
@RequestScoped
public class SendPointsBean {

    @NotNull
    @Pattern(regexp = "^\\d{2},\\d{2}",
            message = "Message format must be 2 digits, comma, 2 digits, e.g.12 ,12")
    private String message;
    @Inject
    JMSContext context;
    @Resource(lookup = "java:global/jms/pointsQueue")
    Queue pointsQueue;

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public void sendMessage() {
        System.out.println("Sending message: " + message);
        context.createProducer().send(pointsQueue, message);
    }
}
说明:
(1)这里使用了@Inject,依赖注入了JMSContext,JMSContext是JMS 2.0新增的Interface。
(2)Java EE 7 会创建一个默认的JMS Connection Factory:java:comp/DefaultJMSConnectionFactory。
(3)发送信息使用 context.createProducer().send(pointsQueue, message);

2. ReceivePointsBean.java
ReceivePointsBean.java用来从Queue中接收消息。

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package org.glassfish.movieplex7.points;

import java.util.Enumeration;
import javax.annotation.Resource;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.inject.Named;
import javax.jms.JMSContext;
import javax.jms.JMSDestinationDefinition;
import javax.jms.JMSException;
import javax.jms.Queue;
import javax.jms.QueueBrowser;

/**
 *
 * @author pmma
 */
@JMSDestinationDefinition(name = "java:global/jms/pointsQueue",
        interfaceName = "javax.jms.Queue")
@Named
@RequestScoped
public class ReceivePointsBean {

    @Inject
    JMSContext context;
    @Resource(lookup = "java:global/jms/pointsQueue")
    Queue pointsQueue;

    public String receiveMessage() {
        String message =
                context.createConsumer(pointsQueue).receiveBody(String.class);
        System.out.println("Received message: " + message);
        return message;
    }

    public int getQueueSize() {
        int count = 0;
        try {
            QueueBrowser browser = context.createBrowser(pointsQueue);
            Enumeration elems = browser.getEnumeration();
            while (elems.hasMoreElements()) {
                elems.nextElement();
                count++;
            }
        } catch (JMSException ex) {
            ex.printStackTrace();
        }
        return count;
    }
}
说明:
(1)接收信息使用 createConsumer或createDurableConsumer,接收方式可以是同步的,也可以是异步的。
(2)使用context.createBrowser(pointsQueue);可以查看Queue的大小,即消息的数量。

3. points.xhtml

<?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:ui="http://xmlns.jcp.org/jsf/facelets"
      xmlns:h="http://xmlns.jcp.org/jsf/html">

    <body>

        <ui:composition template="./../WEB-INF/template.xhtml">

            <ui:define name="content">
                <h1>Points</h1>
                <h:form>
                    Queue size:
                    <h:outputText value="#{receivePointsBean.queueSize}"/><p/>
                    <h:inputText value="#{sendPointsBean.message}"/>
                    <h:commandButton value="Send Message" action="points"
                                     actionListener="#{sendPointsBean.sendMessage()}"/>
                </h:form>
                <h:form>
                    <h:commandButton value="Receive Message" action="points"
                                     actionListener="#{receivePointsBean.receiveMessage()}"/>
                </h:form>
            </ui:define>

        </ui:composition>

    </body>
</html>

Project 下载:8. movieplex7(JMS).7z

NetBeans_023:开发JavaEE 7 应用之六:使用JSON实现增加功能

开发运行环境:NetBeans7.3.1。

通过定义一个JAX-RS Entity Providers,允许使用JSON格式对Movie POJO类进行读写操作。

1. MovieReader.java
MovieReader.java会根据传进来的输入流,生成Movie对象,并返回。

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package org.glassfish.movieplex7.json;

import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import javax.json.Json;
import javax.json.stream.JsonParser;
import javax.ws.rs.Consumes;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.MessageBodyReader;
import javax.ws.rs.ext.Provider;
import org.glassfish.movieplex7.entities.Movie;

/**
 *
 * @author pmma
 */
@Provider
@Consumes(MediaType.APPLICATION_JSON)
public class MovieReader implements MessageBodyReader {

    @Override
    public boolean isReadable(Class type, Type genericType, Annotation[] annotations, MediaType mediaType) {
        return Movie.class.isAssignableFrom(type);
    }

    @Override
    public Movie readFrom(Class type, Type type1, Annotation[] antns, MediaType mt, MultivaluedMap mm, InputStream in)
            throws IOException, WebApplicationException {
        Movie movie = new Movie();
        JsonParser parser = Json.createParser(in);
        while (parser.hasNext()) {
            switch (parser.next()) {
                case KEY_NAME:
                    String key = parser.getString();
                    parser.next();
                    switch (key) {
                        case "id":
                            movie.setId(parser.getInt());
                            break;
                        case "name":
                            movie.setName(parser.getString());
                            break;
                        case "actors":
                            movie.setActors(parser.getString());
                            break;
                        default:
                            break;
                    }
                    break;
                default:
                    break;
            }
        }
        return movie;
    }
}
说明:
(1)@Provider批注表明这个类是一个JAX-RS provider,运行时将会被扫描到。
(2)isReadable用于甄别对象类型,只有Movie类型才可以被处理。

2. MovieWriter.java
MovieWriter.java会根据传进来的Movie对象,生成输出流。


/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package org.glassfish.movieplex7.json;

import java.io.IOException;
import java.io.OutputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import javax.json.Json;
import javax.json.stream.JsonGenerator;
import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.MessageBodyWriter;
import javax.ws.rs.ext.Provider;
import org.glassfish.movieplex7.entities.Movie;

/**
 *
 * @author pmma
 */
@Provider
@Produces(MediaType.APPLICATION_JSON)
public class MovieWriter implements MessageBodyWriter {

    @Override
    public boolean isWriteable(Class type, Type genericType, Annotation[] annotations, MediaType mediaType) {
        return Movie.class.isAssignableFrom(type);
    }

    @Override
    public long getSize(Movie t, Class type, Type genericType, Annotation[] annotations, MediaType mediaType) {
        return -1;
    }

    @Override
    public void writeTo(Movie t, Class type, Type type1, Annotation[] antns, MediaType mt, MultivaluedMap mm, OutputStream out) throws IOException, WebApplicationException {
        JsonGenerator gen = Json.createGenerator(out);
        gen.writeStartObject()
                .write("id", t.getId())
                .write("name", t.getName())
                .write("actors", t.getActors())
                .writeEnd();
        gen.flush();
    }
}
说明:
(1)@Provider批注表明这个类是一个JAX-RS provider,运行时将会被扫描到。
(2)isWriteable用于甄别对象类型,只有Movie类型才可以被处理。

3. MovieClientBean.java中的addMovie方法
注意,这里“注册了”MovieWriter.class,也就是说当调用addMovie方法时,会根据MovieWriter的定义把Movie对象转换成JSON格式,然后以post方式调用RESTful Web Service。

public void addMovie() {
    Movie m = new Movie();
    m.setId(bean.getMovieId());
    m.setName(bean.getMovieName());
    m.setActors(bean.getActors());
    target
            .register(MovieWriter.class)
            .request()
            .post(Entity.entity(m, MediaType.APPLICATION_JSON));
}

Project 下载:7. movieplex7(Json).7z

NetBeans_022:开发JavaEE 7 应用之五:使用RESTful Web Service实现查看和删除功能

开发运行环境:NetBeans7.3.1。

这里使用的是JAX-RS 2.0的特性,关于JAX-RS 2.0的介绍,请参考《JavaEE7 十大新特性》。

1. MovieClientBean.java
MovieClientBean.java是RESTful Web Service的Client端。

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package org.glassfish.movieplex7.client;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.inject.Named;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.MediaType;
import org.glassfish.movieplex7.entities.Movie;
import org.glassfish.movieplex7.json.MovieWriter;

/**
 *
 * @author pmma
 */
@Named
@RequestScoped
public class MovieClientBean {

    Client client;
    WebTarget target;
    @Inject
    MovieBackingBean bean;

    @PostConstruct
    public void init() {
        client = ClientBuilder.newClient();
        target = client.target(
                "http://localhost:8989/movieplex7/webresources/movie/");
    }

    @PreDestroy
    public void destroy() {
        client.close();
    }

    public Movie[] getMovies() {
        return target
                .request()
                .get(Movie[].class);
    }

    public Movie getMovie() {
        Movie m = target
                .path("{movie}")
                .resolveTemplate("movie", bean.getMovieId())
                .request()
                .get(Movie.class);
        return m;
    }

    public void deleteMovie() {
        target
                .path("{movieId}")
                .resolveTemplate("movieId", bean.getMovieId())
                .request()
                .delete();
    }

    public void addMovie() {
        Movie m = new Movie();
        m.setId(bean.getMovieId());
        m.setName(bean.getMovieName());
        m.setActors(bean.getActors());
        target
                .register(MovieWriter.class)
                .request()
                .post(Entity.entity(m, MediaType.APPLICATION_JSON));
    }
}

说明:
(1)ClientBuilder是Client API的主入口,它负责调用REST endpoints。
(2)JAX-RS provider负责创建Client实例, Client实例是“客户端”的“比较重”的对象,因此只在必要的时候创建,不用的时候要关闭。
(3)使用@Inject批注,依赖注入了另一个Managed Bean:MovieBackingBean,其定义见后面。

2. MovieFacadeREST.java
MovieFacadeREST.java是RESTful Web Service的Server端。

package org.glassfish.movieplex7.rest;

import java.util.List;
import javax.ejb.Stateless;
import javax.inject.Named;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import org.glassfish.movieplex7.entities.Movie;

/**
 * @author Arun Gupta
 */
@Named
@Stateless
@Path("movie")
public class MovieFacadeREST extends AbstractFacade {
    @PersistenceContext
    protected EntityManager em;

    public MovieFacadeREST() {
        super(Movie.class);
    }

    @POST
    @Override
    @Consumes({"application/xml", "application/json"})
    public void create(Movie entity) {
        super.create(entity);
    }

    @PUT
    @Override
    @Consumes({"application/xml", "application/json"})
    public void edit(Movie entity) {
        super.edit(entity);
    }

    @DELETE
    @Path("{id}")
    public void remove(@PathParam("id") Integer id) {
        super.remove(super.find(id));
    }

    @GET
    @Path("{id}")
    @Produces({"application/xml", "application/json"})
    public Movie find(@PathParam("id") Integer id) {
        return super.find(id);
    }

    @GET
    @Override
    @Produces({"application/xml", "application/json"})
    public List getAll() {
        return super.getAll();
    }

    @GET
    @Path("{from}/{to}")
    @Produces({"application/xml", "application/json"})
    public List findRange(@PathParam("from") Integer from, @PathParam("to") Integer to) {
        return super.findRange(new int[]{from, to});
    }

    @GET
    @Path("count")
    @Produces("text/plain")
    public String countREST() {
        return String.valueOf(super.count());
    }

    @Override
    protected EntityManager getEntityManager() {
        return em;
    }
    
}

3. movies.xhtml

<?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:ui="http://xmlns.jcp.org/jsf/facelets"
      xmlns:h="http://xmlns.jcp.org/jsf/html"
      xmlns:c="http://xmlns.jcp.org/jsp/jstl/core"
      xmlns:f="http://xmlns.jcp.org/jsf/core">

    <body>

        <ui:composition template="./../WEB-INF/template.xhtml">

            <ui:define name="content">
                <h:form prependId="false">
                    <h:selectOneRadio value="#{movieBackingBean.movieId}"
                                      layout="pageDirection">
                        <c:forEach items="#{movieClientBean.movies}" var="m">
                            <f:selectItem itemValue="#{m.id}" itemLabel="#{m.name}"/>
                        </c:forEach>
                    </h:selectOneRadio>
                    <h:commandButton value="Details" action="movie" />
                    <h:commandButton value="Delete" action="movies"
                                     actionListener="#{movieClientBean.deleteMovie()}"/>
                    <h:commandButton value="New Movie" action="addmovie" />
                </h:form>
            </ui:define>

        </ui:composition>

    </body>
</html>

2. MovieBackingBean.java
为了能够把movieId从movies.xhtml传递到movie.xhtml,定义了MovieBackingBean.java。

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package org.glassfish.movieplex7.client;

import java.io.Serializable;
import javax.enterprise.context.SessionScoped;
import javax.inject.Named;

/**
 *
 * @author pmma
 */
@Named
@SessionScoped
public class MovieBackingBean implements
        Serializable {

    int movieId;
    String movieName;
    String actors;

    public String getMovieName() {
        return movieName;
    }

    public void setMovieName(String movieName) {
        this.movieName = movieName;
    }

    public String getActors() {
        return actors;
    }

    public void setActors(String actors) {
        this.actors = actors;
    }

    public int getMovieId() {
        return movieId;
    }

    public void setMovieId(int movieId) {
        this.movieId = movieId;
    }
}

Project 下载:6. movieplex7(Restful).7z