转自:http://www.ibm.com/developerworks/cn/web/wa-dojotree/index.html?ca=drs-
REST 是一种用于分布式超媒体系统(比如 World Wide Web)的软件架构。资源,或者特定信息来源是 REST 的一个重要组成,每个资源都可以通过一个全局标识符引用。例如,在 HTTP 中 URI 是一个标识符。要对资源进行操作,网络组件(用户代理和源服务器)之间使用一个标准化接口通信,比如 HTTP,并交换资源的表示(传递信息的实际文件)。
Wink 是一个可用于 REST 开发的框架。Wink 为 REST web 服务的开发和使用提供便利,根据 REST 架构风格为服务建模提供方法。Wink 也为定义和实现资源、表示以及构成一个服务的统一方法提供了必要的基础架构。它干净地将低级协议和应用程序分离开来。要实现和使用 REST web 服务,您只需要关注应用程序业务逻辑,而不需要关注低级技术细节。但是,Wink 目前还不支持 Dojo 小部件,比如 Dojo Grid 和 Dojo Tree。
在本文中,了解如何编写新的服务提供方程序以及如何将它们插入 Wink。探究使用含基于 Dojo 的 GUI 的 Wink 的优势。您可以 下载 本文所使用的样例 Dojo 服务提供方程序。
网格(或数据格栏)在客户端/服务器开发模式中是很常见的。网格 本质上是一种电子表格,通常用于显示主从结构( master-detail)表单上的详情。在 HTML 中,一个网格是一个 “超级表”,拥有自己的可滚动视图端口。一个结构 是一系列视图,而一个视图 则是一系列单元格。清单 1 展示了 Dojo 网格的一个样例格式。
清单 1. 网格格式
{ items: [ {"id":0, "name": "Alex", "manager": "T", "sex": "M", "age":30}, {"id":1, "name": "Jack", "manager": "F", "sex": "M", "age":26}, {"id":2, "name": "Rose", "manager": "F", "sex": "M", "age":31}, {"id":3, name: "Wang","manager": "T", "sex": "M", "age":36 }, ..], "endRecordIndex"=0, "totalRecords"=0, "startRecordIndex"=0 } |
要实现一个表示,您应该创建一个 REST 资源。清单 2 中的示例创建了一个 Java™ 类 DojoList
。
清单 2. Dojo 网格资源
public class DojoList<T>{ protected ArrayList<T>items; private int endRecordIndex ; private int totalRecords ; private int startRecordIndex ; @XmlElement(name = "items") public ArrayList<T> getItems() { if (items == null) { items = new ArrayList<T>(); } return items; } public void setItems(ArrayList<T> newList) { items = newList; } @XmlElement(name = "endRecordIndex") public void setEndRecordIndex(int endRecordIndex) { this.endRecordIndex = endRecordIndex; } public int getEndRecordIndex() { return endRecordIndex; } @XmlElement(name = "totalRecords") public void setTotalRecords(int totalRecords) { this.totalRecords = totalRecords; } public int getTotalRecords() { return totalRecords; } @XmlElement(name = "startRecordIndex") public void setStartRecordIndex(int startRecordIndex) { this.startRecordIndex = startRecordIndex; } public int getStartRecordIndex() { return startRecordIndex; } } |
树 是一个定义了 “父子” 关系的层次结构。例如,就一个管理者和一个员工之间的实体关系来说,多个员工是被一个管理者管理的。他们之间的关系也可以认为是一种 “父子” 关系。这类 “父子” 关系可以在一个 Dojo 树中显示出来。Dojo 树的一个样例网格格式如清单 3 所示。
清单 3. 网格树格式
{ items: [ {"id":0, "name": "Alex", manager: "T", "sex" : "M", "age" : 30, "children" : {"child":[ {"id":1,"name":"Jack",sex: "M", "age" : 26}, {"id":2, "name" : "Rose", "sex" : "F", "age" : 26}, {"id":3,"name":"Phil",sex: "M", "age" : 27} ]},.... …..], } |
要实现这个表示,您要创建一个 REST 资源。清单 4 中的示例创建了一个 Java 类 DojoTree
。
清单 4. Dojo 树资源
@XmlType(propOrder = {"items"}) @XmlAccessorType(XmlAccessType.PROPERTY) @XmlRootElement(name = "dojoItems") public class DojoTree<T> { protected ArrayList<T> items; @XmlElement(name = "items") public ArrayList<T> getItems() { if (items == null) { items = new ArrayList<T>(); } return items; } public void setItems(ArrayList<T> newList) { items = newList; |
Wink 框架支持默认服务提供方程序,比如 JavaScript Object Notation (JSON) 和 XML。Dojo 小部件接受网格和树格式的数据,但是它们按不能这种格式使用,因为 Wink 框架生成的输出不能匹配 Dojo 标准。要使用 Wink,您需要通过编写自定义服务提供方程序以及将它们插入框架来扩展框架。默认情况下,Apache Wink 支持以下表示:
- JSON
- RSS
- MultiPart
- APP
- CSV
- OpenSearch
- Atom
- HTML
要支持上述之外的表示,您需要编写自定义服务提供方程序。下一节介绍如何编写一个自定义服务提供方程序来支持网格表示,以便于被 Dojo 小部件所理解。
要出创建一个自定义服务提供方程序 JSONGridProvider
,从清单 5 中的代码开始。
清单 5. JSON 网格自定义服务提供方程序
@Provider @Produces(value = { "application/json_grid" }) public class JsonGridProvider implements MessageBodyWriter<DojoList> { ... } |
@Provider
注解将该类声明为服务提供方程序类。@Produces
定义输出内容类型,用于生成输出。通过为其属性(在本例中是 application/json_grid
)值提供一个自定义内容类型,默认 MIME 类型被替代。对于 JSONGridProvider
,创建一个新的自定义 MIME 类型,比如 application/json_grid
。对于要以网格格式生成 JSON 的 JSONGridProvider
,MessageBodyWriter 接口必须被重写。
一个自定义服务提供方程序必须重写 MessageBodyWriter 接口的两个方法:isWritable()
和 writeTo()
。清单 6 显示 JsonGridProvider
重写的一个示例。
清单 6. 为 JSON 网格服务提供方程序重写
isWriteable
public boolean isWriteable(Class<?> inputClass, Type type, Annotation[] annotations, MediaType mediaType) { return inputClass == DojoList.class; } |
在清单 6 的代码片段中,当 DojoList
类被传入时,将返回 true
。Wink 框架然后选择自定义的 JsonGridProvider
来进行 XML 编组(marshalling)。
清单 7 中的代码将 DojoList
(列表对象)的实例编组成 JSON。
清单 7. 为 JSON 网格服务提供方程序重写
isWriteTo
//Create a new instance JAXbContext on given context path. jc = JAXBContext.newInstance("rest.resource"); OutputStream tempOs = new ByteArrayOutputStream(); Marshaller ms = jc.createMarshaller(); HashMap<String, String> namespaceMap = new HashMap<String, String>(); namespaceMap.put("http://www.w3.org/2001/XMLSchema-instance", “xmlns”); namespaceMap.put("http://www.w3.org/2001/XMLSchema-instance", "xmlns.type"); System.out.println("Size is "+namespaceMap.size()); XMLOutputFactory factory = new MappedXMLOutputFactory(namespaceMap); XMLStreamWriter xsw = factory.createXMLStreamWriter(tempOs); ms.marshal(list, xsw); |
清单 8 显示编组程序生成的 JSON
清单 8. JSON 网格服务提供方程序的 JSON 输出
{"dojoItems":{"items":[{"@xmlns.type.type":"employee","id":"001", "name":"Manager","manager":true,"sex":"male","age":35}, {"@xmlns.type.type":"employee","id":"002", "name":"developer","manager":false,"sex":"male","age":25}], "endRecordIndex":2, "totalRecords":2, "startRecordIndex":1}} |
清单 8 中的输出有 @xmlns.type.type
属性。您需要通过删除标记来格式化输出,清单 9 中的代码片段取代所有出现 @xmlns.type.type
属性的地方,生成格式化输出。
清单 9. 格式化 JSON 输出
private String replaceAll(String jsonString) { String replacePattern = "\"@xmlns.type[a-z,A-Z,:,.,\"]*?,”; Pattern pattern = Pattern.compile(replacePattern); Matcher matcher = pattern.matcher(jsonString); return matcher.replaceAll(“”); } |
该函数是在 writeTo
方法中调用,因此,JSON 输出不使用 @xmlns.type.type
属性及其值来进行格式化。调用清单 9 中的函数之后生成的 JSON 输出在清单 10 显示。
清单 10. 格式化的 JSON 输出
{"dojoItems":{"items":[{"id":"001","name":"Manager","manager":true,"sex":"male","age":35}, {"id":"002","name":"developer","manager":false,"sex":"male","age":25}], "endRecordIndex":2, "totalRecords":2, "startRecordIndex":1}} |
从清单 10 中生成的 JSON 输出提取 items
属性及其值,如清单 11 所示。
清单 11. 提取
items
标签
int index = replacedJsonString.indexOf(":"); int end = replacedJson-String.lastIndexOf("}"); if (replacedJsonString.indexOf("items") > 0) { if (index > 0) { /*If size is equal to 1, then square brackets will not appear, so add square brackets */ if(list.getItems().size() == 1) { /*extracting json grid format*/ String itemsString = replacedJsonString.substring(index + 1, end); debug(methodName, "jsonString itemsString=" + itemsString); os.write(replaceWithParenthesisForSingleItem(itemsString,list).getBytes()); } else { /*extracting json grid format and writing to outputStream*/ os.write(replacedJsonString.substring(index + 1, end) .getBytes()); } } else { /*writing the replacedJsonString into outputStream*/ os.write(replacedJsonString.getBytes()); } } else { // For an empty list, items tag won’t be present. // But dojoWidget Requires it so adding it here. String emptJsonString = “{items:[],startRecordIndex:" + "0" • ", endRecordIndex:" + "0" + ",totalRecords:" + "0" + "}"; os.write(emptJsonString.getBytes()); } os.close(); xsw.close(); tempOs.close(); |
如果条目列表中只有一项,那么生成的 JSON 将没有方括号([]
),而您必须对其做一些特殊处理。除此之外,其他情况下生成的带有方括号的 JSON 输出都在清单中标出了。清单 12 显示了只有一个条目时如何添加方括号。
清单 12. 如果条目数为 1 添加 []
private String replaceWithParenthesisForSingleItem(String jsonString,DojoList list) { final String methodName = "replaceWithParenthesisForSingleItem"; int firstColonIndex = jsonString.indexOf(":"); int endRecordIndex = jsonString.indexOf("endRecordIndex"); int flowerBracketBeforeEndRecordIndexString = jsonString .lastIndexOf("}",endRecordIndex); String jsonObject = jsonString.substring(firstColonIndex + 1, flowerBracketBeforeEndRecordIndexString + 1); String replaceString = "{items:[" + jsonObject + "],startRecordIndex:" • list.getStartRecordIndex() + ", endRecordIndex:" + list.getEndRecordIndex() + ", totalRecords:" + list.getTotalRecords()+ "}"; System.out.println("jsonString after replace=" + replaceString); return replaceString; } |
清单 13 显示了 JSON 网格服务提供方程序的最终格式化输出,它可被发送到任何 Dojo 网格客户端。Dojo 网格客户端可直接向用户显示生成的输出。
清单 13. 最终 JSON 网格输出
{"items":[{"id":"001","name":"Manager","manager":true,"sex":"male","age":35}, {"id":"002","name":"developer","manager":false,"sex":"male","age":25}], "endRecordIndex":2,"totalRecords":2,"startRecordIndex":1} |
您可以实现一个 Dojo 树,类似 Dojo Grid 服务提供方程序。在这一节,您将要编写一个自定义服务提供方程序来支持树型表示,以便于它能被 Dojo 小部件所理解。
首先调用服务提供方程序 JSONDojoTreeProvider
,如清单 14 代码片段中的注解所示。您必须重写 isWritable
和 writeTo
方法。另外提供了一个新的 MIME 类型,application/json_dojo_tree
。
清单 14. Dojo Tree 自定义服务提供方程序
@Provider @Produces(value = { “application/json_dojo_tree”}) public class JsonDojoTreeProvider implements MessageBody-Writer<DojoTree> { ... } |
您需要为树定义一个新内容类型,这样 Dojo 客户端就可以区分和显示不同小部件上的数据,如清单 15 所示。
清单 15. 为 Dojo Tree 服务提供方程序重写
isWriteable
public boolean isWriteable(Class<?> inputClass, Type type, Annotation[] annotations, MediaType mediaType) { return inputClass == == DojoTree.class; } |
当输入类为 DojoTree
时,该方法返回 true
。然后 Wink 框架将选择自定义 JsonDojoTreeProvider
来执行 XML 编组。
清单 16. 为 JSON Tree 服务提供方程序重写
writeTo
public void writeTo(DojoTree list, Class<?> inputClass, Type type, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, Object> map, OutputStream os) throws IOException, WebApplicationException { JAXBContext jc; final String methodName = “writeTo”; try { JAXBContext jc = JAXBContext.newInstance("rest.resource"); OutputStream tempOs = new ByteArrayOutputStream(); Marshaller ms = jc.createMarshaller(); HashMap<String, String> namespaceMap = new HashMap<String, String>(); namespaceMap.put("http://www.w3.org/2001/XMLSchema-instance", "xmlns"); namespaceMap.put("http://www.w3.org/2001/XMLSchema-instance", "xmlns.type"); XMLOutputFactory factory = new MappedXMLOutputFactory(namespaceMap); XMLStreamWriter xsw = factory.createXMLStreamWriter(tempOs); ms.marshal(list, xsw); String replacedJsonString = replaceAll(tempOs.toString()); // remove dojoItems tag int index = replacedJsonString.indexOf(":"); int end = replacedJsonString.lastIndexOf("}"); // For an empty list items tag won't be present, But Dojo //client requires it if (replacedJsonString.indexOf("items") > 0) { if (index > 0) { if (list.getItems().size() == 1) { String itemsString = replacedJsonString.substring(index + 1, end); os.write(replaceWithParenthesisForSingleItem(itemsString).getBytes()); } else { os.write(replacedJsonString.substring(index + 1, end).getBytes()); } } else { os.write(replacedJsonString.getBytes()); } } else { String emptJsonString = "{items:[],startRecordIndex:" + "0" + ", endRecordIndex:" + "0" + ",totalRecords:" + "0" + "}"; os.write(emptJsonString.getBytes()); } os.close(); xsw.close(); tempOs.close(); } catch (JAXBException e) { e.printStackTrace(); } catch (XMLStreamException e) { e.printStackTrace(); } } private String replaceWithParenthesisForSingleItem(String jsonString) { int firstColonIndex = jsonString.indexOf(“:”); int firstFlowerBracketIndex = jsonString.indexOf(“}”); String jsonObject = jsonString.substring(firstColonIndex + 1, firstFlowerBracketIndex + 1); |
通过重写 writeTo()
方法,代码将 DojoTree
对象转换成一个树格式然后写入一个 HTTP Response 输出流。JsonDojoTreeProvider
的 writeTo()
函数非常类似于 JsonGridProvider
。Dojo Tree 格式没有 startRecordIndex
、endRecordIndex
或 totalRecords
。在 writeTo()
函数中主要的区别是当 DojoTree
对象的一个条目列表为空时,以及 DojoTree
对象只有一个条目列表时。
必须使用 Wink 框架注册自定义服务提供方程序,需要从 Wink 框架中继承 javax.ws.rs.core.application。清单 17 是一个示例。
清单 17. 注册自定义服务提供方程序
public Set<Object> getSingletons() { Set<Object> singletons = new HashSet<Object>(); singletons.add(new JsonGridProvider()); singletons.add(new JsonDojoTreeProvider()); return singletons; } |
本小节将介绍如何建模一个样例资源,并显示如何使用该样例资源调用自定义服务提供方程序(JsonGridProvider
和 JsonDojoTreeProvider
)来生成 JSON 网格输出以及 JSON 树输出。
假设您有一个样例类 Employee
。它是一个有 JAXB 注解的简单 Java 类,如清单 18 所示。
清单 18. 样例 Employee 资源
// Employee.java /** @XmlAccessorType Controls whether fields or Javabean properties are serialized by default XmlAccessType.FIELD: Every non static, non transient attribute in a JAXB-bound class will be automatically bound to XML, unless annotated by XmlTransient. Getter/setter pairs are bound to XML only when they are explicitly annotated by some of the JAXB annotations * */ @XmlRootElement @XmlAccessorType(XmlAccessType.FIELD) public class Employee { @XmlElement private String id; @XmlElement private String name; @XmlElement private Boolean manager; @XmlElement private String sex; @XmlElement private String age; /** • @return the id */ public String getId() { return id; } /** • @param id the id to set */ public void setId(String id) { this.id = id; } /** • @return the name */ public String getName() { return name; } /** • @param name the name to set */ public void setName(String name) { this.name = name; } /** • @return the manager */ public Boolean getManager() { return manager; } /** • @param manager the manager to set */ public void setManager(Boolean manager) { this.manager = manager; } /** • @return the sex */ public String getSex() { return sex; } /** • @param sex the sex to set */ public void setSex(String sex) { this.sex = sex; } /** • @return the age */ public String getAge() { return age; } /** • @param age the age to set */ public void setAge(String age) { this.age = age; } |
您还有一个保存员工列表的样例类 EmployeeView
。此类是一个含有 JAXB 注解的简单 Java 类,如清单 19 所示。
清单 19. 样例 Employee View 资源
@XmlRootElement @XmlAccessorType(XmlAccessType.FIELD) public class EmployeeView extends Employee { @XmlElementWrapper(name = "children") @XmlElement(name = "child") Set<EmployeeView> employeeSet ; public Set<EmployeeView> getChildrenRepresentation(){ return employeeSet; } public void setChildrenRepresentation(Set<EmployeeView> employeeSet){ this.employeeSet=employeeSet; } |
将含 Wink 注解的类称为 Action
类。每个类都有将获取关于一个资源的信息的所有方法。所有必须实现的 REST 调用在该类中定义。该类的每个方法的调用都是用一个 @Path
注解标注的。Action
类将处理所有 HTTP 请求。清单 20 显示了 EmployeeAction
类的一个代码片段。
清单 20. 样例 EmployeeAction 类
@Path(value = "/employee") public class EmployeeAction { @GET @Path("/list") @Produces(value ={"application/json_grid"}) public Response getEmployeeDojoList(){ DojoList<Employee> employeeDojoList = new DojoList<Employee>(); /* can create any number of employees; here creating two employees and adding to Dojo list */ Employee manager = new Employee(); manager.setId("001"); manager.setName("Manager"); manager.setManager(true); manager.setSex("male"); manager.setAge("35"); employeeDojoList.getItems().add(manager); return Response.ok(employeeDojoList).build(); } @GET @Path("/tree") @Produces(value ={"application/json_dojo_tree"}) public Response getEmployeeDojoTree(){ DojoTree<EmployeeView> employeeDojoList = new DojoTree<EmployeeView>(); //can create any number of employees; // here creating two employees and adding to Dojo list EmployeeView manager = new EmployeeView(); manager.setId("001"); manager.setName("Manager"); manager.setManager(true); manager.setSex("male"); manager.setAge("35"); EmployeeView developer1 = new EmployeeView(); developer1.setId("002"); developer1.setName("John"); developer1.setManager(false); developer1.setSex("M"); developer1.setAge("25"); EmployeeView developer2 = new EmployeeView(); developer2.setId("002"); developer2.setName("Alice"); developer2.setManager(false); developer2.setSex("F"); developer2.setAge("21"); Set<EmployeeView> employeeSet = new HashSet<EmployeeView>(); employeeSet.add(developer1); employeeSet.add(developer2); manager.setChildrenRepresentation(employeeSet); //making developer as child of manager employeeDojoList.getItems().add(manager); employeeDojoList.getItems().add(developer1); employeeDojoList.getItems().add(developer2); return Response.ok(employeeDojoList).build(); } } |
当使用 /employee/list
值调用一个 URI 时,接受的头部是 application/json_grid
,然后 getEmployeeList()
被触发并返回一个 DojoGrid 格式的响应。
要在 Dojo Tree 格式中查看员工信息,有一个独立的 URI /employee/tree,含有接受头部 application/json_dojo_tree
。Wink REST Servlet 调用 EmployeeAction
的 getEmployeeDojoTree
方法,返回一个 DojoTree 格式的响应。
JAXB 技术要求类上下文要向 XML 反编组/编组程序公开。要做到这一点,您必须实现一个 ObjectFactory
类,这个类将充当工厂的角色,创建不同资源对象。在 清单 20 的示例中,有 4 个资源类:Employee
、EmployeeView
、 DojoList
和 DojoTree
。清单 21 展示了如何通过创建该方法将这个实例公布到 JAXB 上下文。
清单 21. 样例
ObjectFactory
类
@XmlRegistry public class ObjectFactory { public DojoList createDojoList() { return new DojoList(); } public DojoTree createDojoTree() { return new DojoTree(); } public Employee createEmployee(){ return new Employee(); } public EmployeeView createEmployeeView(){ return new EmployeeView(); } |
在本文中,您学习了如何编写服务提供方程序来满足特定需求。新服务提供方程序可以插入到 Wink 框架,并能增强该框架的功能。您现在可以从含有一个基于 Dojo 的 GUI 的 Wink 的使用中获益了。