Fork me on GitHub

Programming Design Notes

試用 Google Cloud SQL

| Comments


Google 剛剛出的新服務 - Google Cloud SQL,可能有太多人抱怨認為 Google App EngineDatastore 太難用,而且現有的 App 移植到 Google App Engine 最大問題也是 Datastore。傳統的應用程式大多數使用 Relational Database Management System (RDBMS) 關聯式資料庫管理系統作為儲存資料的系統,但 RDBMS 是比較難去擴展整個系統,起初可以靠升級系統硬件去加強效能,但硬件升級愈高,性價比愈低。而 Google App EngineDatastoreNoSQL Database,擴展系統是一件很容易的事,可以買 5 台中級機器去組成整個系統,不夠可以再加上去,可以無限地伸延 (理論上)。NoSQL Database 是沒有 Join Table 之類的語法,你只能靠應用程式層面實現 Join Table,這會提高應用程式開發難度。

Google Cloud SQL 是用來解決傳統的應用程式移植或對使用 RDBMS 有開發經驗的程式員有多一個選擇。而 Google Cloud SQL 底層是使用 MySQL 實作的。

打開 Google Cloud SQLConsole 是這個樣子:

而容量方面有三個選擇,分別是 1GB, 5GB10GB

試試新增一個 MySQL Instance (暫時只有 Google App Engine 可以存取到,只需加入 GAEID 就可以了):

處理中:

完成後看一看 Log:

再到 Prompt 看看:

新增一個 Database,命名為 mydb:

再新增一個 Table 命名為 User:

我發現每一次只可以輸入一行指令,有分號隔著也不行,希望遲點會改善。
當然 Google Cloud SQL 有提供 Import 的功能,但檔案一定要存放在 Google Cloud Storage 上。




Import 成功後就試試旗選取一埋資料:

再試試 Join Table:

一樣沒有問題。

SQL 檔案: sampledatabase.sql

要申請 Google Cloud SQL 可到 https://code.google.com/apis/console/,然後選取 Service 再在 Google Cloud SQL 按一下 Request access,填好表格後就等 Google 回覆就可以用了 (我是等了兩天才有回覆)。
遲一點再在 GAE 上試用。

Guice + Jersey 打造 RESTful 應用程式

| Comments

以前我喜歡使用 Spring + Spring Web MVC 來打造 RESTful 應用程式,但 Spring 的起動有點慢,如果放上 Google App Engine 上經常令用戶等待很久才載入到頁面。

今次選用了 Guice + Jersey 的組合。Guice 是一個輕量級的容器,設定上比起 Spring 更簡單,起動或注入速度亦比 Spring 快。而 Jersey 則是一個為 RESTful web service 而設的一個 Framework

Gucie 官方網址: http://code.google.com/p/google-guice/
Jersey 官方網址: http://jersey.java.net/

因為手上的電腦沒有安裝 Application Server,只好拿 GAEDevelopment Server 來測試。

首先下載 Guice : http://code.google.com/p/google-guice/downloads/detail?name=guice-3.0.zip&can=2&q= (現時為止最新版本為 3.0)

打開後將 aopalliance.jar, guice-3.0.jar, guice-servlet-3.0.jarjavax.inject.jar 加到 WEB-INF/lib 中並加到 Class path

然後下載 Jersey 所需的 Jar (現時為止最新版本為 1.9):
  1. jersey-server.jar
  2. jersey-core.jar
  3. asm.jar
  4. jersey-guice-1.9-SNAPSHOT.jar

將以上的檔案一樣是放到 WEB-INF/lib 並加到 Class path

首先建立一個 class,每一次連接 http://www.xyzdomainname.com 都會顯示 I am Index page:
package com.ctlok.pro.controller;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

@Path("/")
public class CommonController {

@GET
@Produces(MediaType.TEXT_PLAIN)
public String index(){
return "I am Index page";
}

}

@Path 是設定這個 class 會處理的 URL,可以放在 ClassMethod。而 Jersey 會搜尋出在 Class 上有 @Path 的類別然後處理。
@GET 是設定這個 Method 處理那一種請求,一共有 5 種方式: GET, POST, HEAD, PUT, DELETE
@Produces 則是回傳資料的類型,可以是 Text, XML, JSON, HTML 等等。

然後再去建立一個 GuiceModule class 並設定 Jersey:
package com.ctlok.pro;

import java.util.HashMap;
import java.util.Map;

import com.ctlok.pro.controller.CommonController;
import com.google.inject.servlet.RequestScoped;
import com.google.inject.servlet.ServletModule;
import com.sun.jersey.guice.spi.container.servlet.GuiceContainer;

public class WebModule extends ServletModule {

@Override
protected void configureServlets(){
bind(CommonController.class).in(RequestScoped.class);

Map<String, String> parameters = new HashMap<String, String>();
parameters.put("com.sun.jersey.config.property.packages", "com.ctlok.pro.controller");
serve("/*").with(GuiceContainer.class, parameters);
}

}

然後設定 Guice 起動時的注入器:
package com.ctlok.pro;

import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.servlet.GuiceServletContextListener;

public class AppConfig extends GuiceServletContextListener{

@Override
protected Injector getInjector() {
return Guice.createInjector(new WebModule());
}

}

在 web.xml 設定好 Guice FilterListener:
<?xml version="1.0" encoding="utf-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5">
<listener>
<listener-class>com.ctlok.pro.AppConfig</listener-class>
</listener>
<filter>
<filter-name>guiceFilter</filter-name>
<filter-class>com.google.inject.servlet.GuiceFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>guiceFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>

打開 http://localhost:8888 後即可看到 “I am Index page”.

如要增加 URL,可以在 CommonController 加入:
@GET
@Path("user/{userId}/{userName}")
@Produces(MediaType.TEXT_PLAIN)
public String getUser(@PathParam("userId") String userId, @PathParam("userName") String userName){
return "User ID: " + userId + ", user name: " + userName;
}

你可能有點疑問,@Path 在 Class 已經設家了,現在又有一個 @Pathmethod 上,那 Jersey 會怎麼決定。
其實 Jersey 會將 class@Path value 加上 method 的 @Path value,即是 “/” + “user/{userId}/{userName}”。

打開 http://localhost:8888/user/123/lawrence 顯示:User ID: 123, user name: lawrence

其實 JerseyMethod 不一定返回 String 可以是一個 ObjectJersey 定義 的 Response。以下例字示範如何顯示一個 JSP 頁面,並由 ControllerModel 傳給 View (JSP):

先在 WEB-INF 新增一個 views 資料夾並新增一個 example.jspJSP 內容如下:
<?xml version="1.0" encoding="utf-8"?>
<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!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">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Guice + Jersey + JSP</title>
</head>
<body>
<c:out value="${it.msg}" />
</body>
</html>

Controller 內也增加一個 Method:
@GET
@Path("jsp")
@Produces(MediaType.TEXT_HTML)
public Response getJsp(){
Map<String, Object> model = new HashMap<String, Object>();
model.put("msg", "Hello World!");
return Response.ok(new Viewable("/WEB-INF/views/example.jsp", model)).build();
}

打開 http://localhost:8888/jsp 顯示: Hello World!

完整的 CommonController:
package com.ctlok.pro.controller;

import java.util.HashMap;
import java.util.Map;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

import com.sun.jersey.api.view.Viewable;

@Path("/")
public class CommonController {

@GET
@Produces(MediaType.TEXT_PLAIN)
public String index() {
return "I am Index page";
}

@GET
@Path("user/{userId}/{userName}")
@Produces(MediaType.TEXT_PLAIN)
public String getUser(@PathParam("userId") String userId,
@PathParam("userName") String userName) {
return "User ID: " + userId + ", user name: " + userName;
}

@GET
@Path("jsp")
@Produces(MediaType.TEXT_HTML)
public Response getJsp() {
Map<String, Object> model = new HashMap<String, Object>();
model.put("msg", "Hello World!");
return Response.ok(new Viewable("/WEB-INF/views/example.jsp", model))
.build();
}

}

範例: Guice-Jersey.zip
密碼: pro.ctlok.com 相關書籍: Dependency InjectionGoogle Guice: Agile Lightweight Dependency Injection FrameworkRESTful Java with Jax-RS (Animal Guide)

JSF + RichFaces 做成 Session Memory Leak

| Comments

在公司有個項目是使用 JSF 作為 View,感覺十分差。JSF 給我的感覺: 運行慢,吃資源,限制又多。實在不太喜歡 JSF

在進行 Street Test 時,發覺 Server 上的 Memory 用得異常地多。經調查後發現 RichFaces 其中一個 Object - AjaxStateHolder 佔用大量 Memory。如果是使用 3.2.0.SR1 之前的版本更加嚴重,Session 隨時佔用 1 GigabytesMemory

解決方法就是在 web.xml 加入以下設定:
<context-param>
<param-name>com.sun.faces.numberOfViewsInSession</param-name>
<param-value>1</param-value>
</context-param>

<context-param>
<param-name>com.sun.faces.numberOfLogicalViews</param-name>
<param-value>1</param-value>
</context-param>

因為每運行一次 JSFView 時都會將這個 View 放到 Session 內,而且同一個 View 亦會再儲存多一次在 Session,因為這些 Session 仍然在使用中,JVM GC 無法將不用的 View 回收,做成 Memory 使用量不斷增加。

限制 View 數量後就可以解決這個問題。

RichFaces bug: https://issues.jboss.org/browse/RF-3878

相關書籍: Core JavaServer Faces (3rd Edition)JavaServer Faces 2.0, The Complete ReferenceBeginning JSP, JSF and Tomcat Web Development: From Novice to Professional

Java 使用 Top Down 和 JAX-WS 來部署 Web Service Provider

| Comments

上一篇已經介紹過使用 Bottom Up 的方法來開發 Web Service: Java 使用 Bottom Up 和 JAX-WS 來部署 Web Service。今次當然是使用 Top Down 的方法,個人認為 Top Down 其實比 Bottom Up 方法更好,原因是自動產生出來的 XML SchemaWSDL 不太好。雖然是方便,但有很多細節地方不能自己操作到,所以我是比較喜歡 Top Down 的。


使用 Top Down 一定要懂 XML SchemaWeb Services Description Language

XML Schema: http://www.w3schools.com/schema/default.asp
Web Services Description Language: http://www.w3schools.com/WSDL/default.asp

這次的 Web Service 功能是客戶端 (Web service client) 送出一個 Employee Id list 到 服務器端 (Web service provider)。

首先建立一個 WAR project,然後在 web root 目錄 加入 company.xsdcompany.wsdl

以下是 company.xsd:
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://pro.ctlok.com/company" xmlns:tns="http://pro.ctlok.com/company"
elementFormDefault="qualified">

<xs:simpleType name="EmployeeId">
<xs:restriction base="xs:string">
<xs:length value="7" />
<xs:pattern value="E[0-9]{7}" />
</xs:restriction>
</xs:simpleType>

<xs:simpleType name="EmployeeTitle">
<xs:restriction base="xs:string">
<xs:enumeration value="CEO" />
<xs:enumeration value="Manger" />
<xs:enumeration value="Supervisor" />
<xs:enumeration value="Clerk" />
</xs:restriction>
</xs:simpleType>

<xs:complexType name="EmployeeInfo">
<xs:sequence>
<xs:element name="eid" type="tns:EmployeeId"
minOccurs="0" nillable="false" />
<xs:element name="firstName" type="xs:string"
minOccurs="0" nillable="false" />
<xs:element name="lastName" type="xs:string"
minOccurs="0" nillable="false" />
<xs:element name="age" type="xs:unsignedShort"
minOccurs="0" nillable="false" />
<xs:element name="title" type="tns:EmployeeTitle"
minOccurs="0" nillable="false" />
</xs:sequence>
</xs:complexType>

<xs:complexType name="EmployeeInfoWrapper">
<xs:sequence>
<xs:element name="employeeInfo" type="tns:EmployeeInfo"
minOccurs="0" maxOccurs="unbounded" nillable="false" />
</xs:sequence>
</xs:complexType>

<xs:complexType name="EmployeeIdWrapper">
<xs:sequence>
<xs:element name="eid" type="tns:EmployeeId"
minOccurs="0" maxOccurs="unbounded" nillable="false" />
</xs:sequence>
</xs:complexType>

<xs:element name="EmployeeIdList" type="tns:EmployeeIdWrapper" />
<xs:element name="EmployeeInfoList" type="tns:EmployeeInfoWrapper" />

</xs:schema>

以下則是 company.wsdl:
<?xml version="1.0" encoding="UTF-8"?>
<wsdl:definitions name="Company" targetNamespace="http://pro.ctlok.com/company"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tns="http://pro.ctlok.com/company"
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:xsd="http://www.w3.org/2001/XMLSchema">

<wsdl:types>
<xsd:schema>
<xsd:import namespace="http://pro.ctlok.com/company"
schemaLocation="company.xsd" />
</xsd:schema>
</wsdl:types>

<wsdl:message name="employeeLookupRequest">
<wsdl:part element="tns:EmployeeIdList" name="employeeIdList" />
</wsdl:message>

<wsdl:message name="employeeLookupResponse">
<wsdl:part element="tns:EmployeeInfoList" name="employeeInfoList" />
</wsdl:message>

<wsdl:portType name="employeeLookupService">
<wsdl:operation name="employeeLookup">
<wsdl:input message="tns:employeeLookupRequest" />
<wsdl:output message="tns:employeeLookupResponse" />
</wsdl:operation>
</wsdl:portType>

<wsdl:binding name="employeeLookupBinding" type="tns:employeeLookupService">
<soap:binding style="document"
transport="http://schemas.xmlsoap.org/soap/http" />
<wsdl:operation name="employeeLookup">
<soap:operation
soapAction="http://pro.ctlok.com/company/employeeLookup" />
<wsdl:input>
<soap:body use="literal" />
</wsdl:input>
<wsdl:output>
<soap:body use="literal" />
</wsdl:output>
</wsdl:operation>
</wsdl:binding>

<wsdl:service name="employeeLookupService">
<wsdl:port binding="tns:employeeLookupBinding" name="employeeLookupPort">
<soap:address location="http://localhost:9080/WebService" />
</wsdl:port>
</wsdl:service>

</wsdl:definitions>

現在完成了 Web Service 的介面了,但要將 Web ServiceObject 轉為 Java Class 才可以。幸好有工具可以替我們完成這項工作,不用一個一個手動建立。使用 JAXB compiler 幫我們將 WSDL 內用到的 Object 轉為 Java Class。在 Windows 打開 CMD 輸入以下指令:
xjc -wsdl company.wsdl -p com.ctlok.pro.ws.model

*** 如果你不能執行 xjc,請查看 System Path 有沒有加入 %JAVA_HOME%/bin 。 ***

  • -wsdlwsdl 檔案存放的位置
  • -p 是產生出來的 Java class 所在 package

想知更多指令可參考: JAXB Using

執行指令後會看到有 6 個 .java 檔案,分別是:
  1. EmployeeIdWrapper
  2. EmployeeInfo
  3. EmployeeInfoWrapper
  4. EmployeeTitle
  5. ObjectFactory
  6. package-info

EmployeeIdWrapper:
//
// This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference Implementation, vJAXB 2.1.3 in JDK 1.6
// See <a href="http://java.sun.com/xml/jaxb">http://java.sun.com/xml/jaxb</a>
// Any modifications to this file will be lost upon recompilation of the source schema.
// Generated on: 2011.06.23 at 04:28:35 PM CST
//


package com.ctlok.pro.ws.model;

import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlType;


/**
* <p>Java class for EmployeeIdWrapper complex type.
*
* <p>The following schema fragment specifies the expected content contained within this class.
*
* <pre>
* &lt;complexType name="EmployeeIdWrapper">
* &lt;complexContent>
* &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
* &lt;sequence>
* &lt;element name="eid" type="{http://pro.ctlok.com/company}EmployeeId" maxOccurs="unbounded" minOccurs="0"/>
* &lt;/sequence>
* &lt;/restriction>
* &lt;/complexContent>
* &lt;/complexType>
* </pre>
*
*
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "EmployeeIdWrapper", propOrder = {
"eid"
})
public class EmployeeIdWrapper {

protected List<String> eid;

/**
* Gets the value of the eid property.
*
* <p>
* This accessor method returns a reference to the live list,
* not a snapshot. Therefore any modification you make to the
* returned list will be present inside the JAXB object.
* This is why there is not a <CODE>set</CODE> method for the eid property.
*
* <p>
* For example, to add a new item, do as follows:
* <pre>
* getEid().add(newItem);
* </pre>
*
*
* <p>
* Objects of the following type(s) are allowed in the list
* {@link String }
*
*
*/
public List<String> getEid() {
if (eid == null) {
eid = new ArrayList<String>();
}
return this.eid;
}

}

EmployeeInfo:
//
// This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference Implementation, vJAXB 2.1.3 in JDK 1.6
// See <a href="http://java.sun.com/xml/jaxb">http://java.sun.com/xml/jaxb</a>
// Any modifications to this file will be lost upon recompilation of the source schema.
// Generated on: 2011.06.23 at 04:28:35 PM CST
//


package com.ctlok.pro.ws.model;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlSchemaType;
import javax.xml.bind.annotation.XmlType;


/**
* <p>Java class for EmployeeInfo complex type.
*
* <p>The following schema fragment specifies the expected content contained within this class.
*
* <pre>
* &lt;complexType name="EmployeeInfo">
* &lt;complexContent>
* &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
* &lt;sequence>
* &lt;element name="eid" type="{http://pro.ctlok.com/company}EmployeeId" minOccurs="0"/>
* &lt;element name="firstName" type="{http://www.w3.org/2001/XMLSchema}string" minOccurs="0"/>
* &lt;element name="lastName" type="{http://www.w3.org/2001/XMLSchema}string" minOccurs="0"/>
* &lt;element name="age" type="{http://www.w3.org/2001/XMLSchema}unsignedShort" minOccurs="0"/>
* &lt;element name="title" type="{http://pro.ctlok.com/company}EmployeeTitle" minOccurs="0"/>
* &lt;/sequence>
* &lt;/restriction>
* &lt;/complexContent>
* &lt;/complexType>
* </pre>
*
*
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "EmployeeInfo", propOrder = {
"eid",
"firstName",
"lastName",
"age",
"title"
})
public class EmployeeInfo {

protected String eid;
protected String firstName;
protected String lastName;
@XmlSchemaType(name = "unsignedShort")
protected Integer age;
protected EmployeeTitle title;

/**
* Gets the value of the eid property.
*
* @return
* possible object is
* {@link String }
*
*/
public String getEid() {
return eid;
}

/**
* Sets the value of the eid property.
*
* @param value
* allowed object is
* {@link String }
*
*/
public void setEid(String value) {
this.eid = value;
}

/**
* Gets the value of the firstName property.
*
* @return
* possible object is
* {@link String }
*
*/
public String getFirstName() {
return firstName;
}

/**
* Sets the value of the firstName property.
*
* @param value
* allowed object is
* {@link String }
*
*/
public void setFirstName(String value) {
this.firstName = value;
}

/**
* Gets the value of the lastName property.
*
* @return
* possible object is
* {@link String }
*
*/
public String getLastName() {
return lastName;
}

/**
* Sets the value of the lastName property.
*
* @param value
* allowed object is
* {@link String }
*
*/
public void setLastName(String value) {
this.lastName = value;
}

/**
* Gets the value of the age property.
*
* @return
* possible object is
* {@link Integer }
*
*/
public Integer getAge() {
return age;
}

/**
* Sets the value of the age property.
*
* @param value
* allowed object is
* {@link Integer }
*
*/
public void setAge(Integer value) {
this.age = value;
}

/**
* Gets the value of the title property.
*
* @return
* possible object is
* {@link EmployeeTitle }
*
*/
public EmployeeTitle getTitle() {
return title;
}

/**
* Sets the value of the title property.
*
* @param value
* allowed object is
* {@link EmployeeTitle }
*
*/
public void setTitle(EmployeeTitle value) {
this.title = value;
}

}

EmployeeInfoWrapper:
//
// This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference Implementation, vJAXB 2.1.3 in JDK 1.6
// See <a href="http://java.sun.com/xml/jaxb">http://java.sun.com/xml/jaxb</a>
// Any modifications to this file will be lost upon recompilation of the source schema.
// Generated on: 2011.06.23 at 04:28:35 PM CST
//


package com.ctlok.pro.ws.model;

import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlType;


/**
* <p>Java class for EmployeeInfoWrapper complex type.
*
* <p>The following schema fragment specifies the expected content contained within this class.
*
* <pre>
* &lt;complexType name="EmployeeInfoWrapper">
* &lt;complexContent>
* &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
* &lt;sequence>
* &lt;element name="employeeInfo" type="{http://pro.ctlok.com/company}EmployeeInfo" maxOccurs="unbounded" minOccurs="0"/>
* &lt;/sequence>
* &lt;/restriction>
* &lt;/complexContent>
* &lt;/complexType>
* </pre>
*
*
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "EmployeeInfoWrapper", propOrder = {
"employeeInfo"
})
public class EmployeeInfoWrapper {

protected List<EmployeeInfo> employeeInfo;

/**
* Gets the value of the employeeInfo property.
*
* <p>
* This accessor method returns a reference to the live list,
* not a snapshot. Therefore any modification you make to the
* returned list will be present inside the JAXB object.
* This is why there is not a <CODE>set</CODE> method for the employeeInfo property.
*
* <p>
* For example, to add a new item, do as follows:
* <pre>
* getEmployeeInfo().add(newItem);
* </pre>
*
*
* <p>
* Objects of the following type(s) are allowed in the list
* {@link EmployeeInfo }
*
*
*/
public List<EmployeeInfo> getEmployeeInfo() {
if (employeeInfo == null) {
employeeInfo = new ArrayList<EmployeeInfo>();
}
return this.employeeInfo;
}

}

EmployeeTitle:
//
// This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference Implementation, vJAXB 2.1.3 in JDK 1.6
// See <a href="http://java.sun.com/xml/jaxb">http://java.sun.com/xml/jaxb</a>
// Any modifications to this file will be lost upon recompilation of the source schema.
// Generated on: 2011.06.23 at 04:28:35 PM CST
//


package com.ctlok.pro.ws.model;

import javax.xml.bind.annotation.XmlEnum;
import javax.xml.bind.annotation.XmlEnumValue;
import javax.xml.bind.annotation.XmlType;


/**
* <p>Java class for EmployeeTitle.
*
* <p>The following schema fragment specifies the expected content contained within this class.
* <p>
* <pre>
* &lt;simpleType name="EmployeeTitle">
* &lt;restriction base="{http://www.w3.org/2001/XMLSchema}string">
* &lt;enumeration value="CEO"/>
* &lt;enumeration value="Manger"/>
* &lt;enumeration value="Supervisor"/>
* &lt;enumeration value="Clerk"/>
* &lt;/restriction>
* &lt;/simpleType>
* </pre>
*
*/
@XmlType(name = "EmployeeTitle")
@XmlEnum
public enum EmployeeTitle {

CEO("CEO"),
@XmlEnumValue("Manger")
MANGER("Manger"),
@XmlEnumValue("Supervisor")
SUPERVISOR("Supervisor"),
@XmlEnumValue("Clerk")
CLERK("Clerk");
private final String value;

EmployeeTitle(String v) {
value = v;
}

public String value() {
return value;
}

public static EmployeeTitle fromValue(String v) {
for (EmployeeTitle c: EmployeeTitle.values()) {
if (c.value.equals(v)) {
return c;
}
}
throw new IllegalArgumentException(v);
}

}

ObjectFactory:
//
// This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference Implementation, vJAXB 2.1.3 in JDK 1.6
// See <a href="http://java.sun.com/xml/jaxb">http://java.sun.com/xml/jaxb</a>
// Any modifications to this file will be lost upon recompilation of the source schema.
// Generated on: 2011.06.23 at 04:28:35 PM CST
//


package com.ctlok.pro.ws.model;

import javax.xml.bind.JAXBElement;
import javax.xml.bind.annotation.XmlElementDecl;
import javax.xml.bind.annotation.XmlRegistry;
import javax.xml.namespace.QName;


/**
* This object contains factory methods for each
* Java content interface and Java element interface
* generated in the com.ctlok.pro.ws.model package.
* <p>An ObjectFactory allows you to programatically
* construct new instances of the Java representation
* for XML content. The Java representation of XML
* content can consist of schema derived interfaces
* and classes representing the binding of schema
* type definitions, element declarations and model
* groups. Factory methods for each of these are
* provided in this class.
*
*/
@XmlRegistry
public class ObjectFactory {

private final static QName _EmployeeIdList_QNAME = new QName("http://pro.ctlok.com/company", "EmployeeIdList");
private final static QName _EmployeeInfoList_QNAME = new QName("http://pro.ctlok.com/company", "EmployeeInfoList");

/**
* Create a new ObjectFactory that can be used to create new instances of schema derived classes for package: com.ctlok.pro.ws.model
*
*/
public ObjectFactory() {
}

/**
* Create an instance of {@link EmployeeIdWrapper }
*
*/
public EmployeeIdWrapper createEmployeeIdWrapper() {
return new EmployeeIdWrapper();
}

/**
* Create an instance of {@link EmployeeInfoWrapper }
*
*/
public EmployeeInfoWrapper createEmployeeInfoWrapper() {
return new EmployeeInfoWrapper();
}

/**
* Create an instance of {@link EmployeeInfo }
*
*/
public EmployeeInfo createEmployeeInfo() {
return new EmployeeInfo();
}

/**
* Create an instance of {@link JAXBElement }{@code <}{@link EmployeeIdWrapper }{@code >}}
*
*/
@XmlElementDecl(namespace = "http://pro.ctlok.com/company", name = "EmployeeIdList")
public JAXBElement<EmployeeIdWrapper> createEmployeeIdList(EmployeeIdWrapper value) {
return new JAXBElement<EmployeeIdWrapper>(_EmployeeIdList_QNAME, EmployeeIdWrapper.class, null, value);
}

/**
* Create an instance of {@link JAXBElement }{@code <}{@link EmployeeInfoWrapper }{@code >}}
*
*/
@XmlElementDecl(namespace = "http://pro.ctlok.com/company", name = "EmployeeInfoList")
public JAXBElement<EmployeeInfoWrapper> createEmployeeInfoList(EmployeeInfoWrapper value) {
return new JAXBElement<EmployeeInfoWrapper>(_EmployeeInfoList_QNAME, EmployeeInfoWrapper.class, null, value);
}

}

package-info:
//
// This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference Implementation, vJAXB 2.1.3 in JDK 1.6
// See <a href="http://java.sun.com/xml/jaxb">http://java.sun.com/xml/jaxb</a>
// Any modifications to this file will be lost upon recompilation of the source schema.
// Generated on: 2011.06.23 at 04:28:35 PM CST
//

@javax.xml.bind.annotation.XmlSchema(namespace = "http://pro.ctlok.com/company", elementFormDefault = javax.xml.bind.annotation.XmlNsForm.QUALIFIED)
package com.ctlok.pro.ws.model;

將這些 Java Class 複製到 WAR project 內。

再建立一個 Java Class - EmployeeLookupService:
package com.ctlok.pro.ws;

import java.util.HashMap;
import java.util.Map;

import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebResult;
import javax.jws.WebService;
import javax.jws.soap.SOAPBinding;
import javax.xml.bind.annotation.XmlSeeAlso;

import com.ctlok.pro.ws.model.EmployeeIdWrapper;
import com.ctlok.pro.ws.model.EmployeeInfo;
import com.ctlok.pro.ws.model.EmployeeInfoWrapper;
import com.ctlok.pro.ws.model.EmployeeTitle;
import com.ctlok.pro.ws.model.ObjectFactory;

@WebService(
name = "employeeLookupService",
serviceName = "employeeLookupService",
portName = "employeeLookupPort",
targetNamespace= "http://pro.ctlok.com/company",
wsdlLocation = "company.wsdl")
@SOAPBinding(parameterStyle = SOAPBinding.ParameterStyle.BARE)
@XmlSeeAlso({ObjectFactory.class})
public class EmployeeLookupService {

private Map<String, EmployeeInfo> infoMap;

public EmployeeLookupService(){
infoMap = new HashMap<String, EmployeeInfo>();

EmployeeInfo info1 = new EmployeeInfo();
info1.setEid("E1000000");
info1.setFirstName("Lawrence");
info1.setLastName("Cheung");
info1.setAge(24);
info1.setTitle(EmployeeTitle.CEO);

EmployeeInfo info2 = new EmployeeInfo();
info2.setEid("E1524125");
info2.setFirstName("Tom");
info2.setLastName("Wong");
info2.setAge(22);
info2.setTitle(EmployeeTitle.CLERK);

EmployeeInfo info3 = new EmployeeInfo();
info3.setEid("E7452145");
info3.setFirstName("John");
info3.setLastName("Lee");
info3.setAge(29);
info3.setTitle(EmployeeTitle.MANGER);

EmployeeInfo info4 = new EmployeeInfo();
info4.setEid("E6523547");
info4.setFirstName("Katty");
info4.setLastName("Choi");
info4.setAge(24);
info4.setTitle(EmployeeTitle.SUPERVISOR);


infoMap.put(info1.getEid(), info1);
infoMap.put(info2.getEid(), info2);
infoMap.put(info3.getEid(), info3);
infoMap.put(info4.getEid(), info4);
}

@WebMethod(operationName="employeeLookup")
@WebResult(name = "EmployeeInfoList", partName = "employeeInfoList")
public EmployeeInfoWrapper employeeLookup(
@WebParam(name = "EmployeeIdList", partName = "employeeIdList")
EmployeeIdWrapper employeeIdWrapper){

EmployeeInfoWrapper employeeInfoWrapper = new EmployeeInfoWrapper();

for (String eid: employeeIdWrapper.getEid()){
EmployeeInfo info = infoMap.get(eid);

if (info == null)
info = new EmployeeInfo();

employeeInfoWrapper.getEmployeeInfo().add(info);
}

return employeeInfoWrapper;

}

}

完成了 Web Service Provider

@SOAPBinding(parameterStyle = SOAPBinding.ParameterStyle.BARE)

以上這句注解很重要,預設是 WRAPPED,即是 JAX-WS 會替你自動包裝好 List Object,但我們在 XML Schema 已經設定了 Wrapper,所以不用設定為 WRAPPED

————– 分隔線 ————–

@XmlSeeAlso({ObjectFactory.class})

以上的注解則是確保 JAXB 正常運作,由 XML 轉為 ObjectObject 轉為 XML

————– 分隔線 ————–

@WebResult(name = "EmployeeInfoList", partName = "employeeInfoList")

以上的注解對應 WSDL 內的:

<wsdl:message name="employeeLookupRequest">
<wsdl:part element="tns:EmployeeIdList" name="employeeIdList" />
</wsdl:message>

————– 分隔線 ————–

@WebResult(name = "EmployeeInfoList", partName = "employeeInfoList")

以上的注解則對應 WSDL 內的:

<wsdl:message name="employeeLookupResponse">
<wsdl:part element="tns:EmployeeInfoList" name="employeeInfoList" />
</wsdl:message>


現在將 WAR project 放上 Application Server 並運行 http://localhost/employeeLookupService?wsdl。如果發現找不到 wsdl 檔案即是 wsdl 放的位置不正確。

成功找到後可以使用 soapUI 去測試一下,傳送以下 SOAP XMLServer:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:com="http://pro.ctlok.com/company">
<soapenv:Header/>
<soapenv:Body>
<com:EmployeeIdList>
<!--Zero or more repetitions:-->
<com:eid>E1000000</com:eid>
<com:eid>E1524125</com:eid>
</com:EmployeeIdList>
</soapenv:Body>
</soapenv:Envelope>

Server 回傳以下 XML 即代表成功設定:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Body>
<EmployeeInfoList xmlns="http://pro.ctlok.com/company">
<employeeInfo>
<eid>E1000000</eid>
<firstName>Lawrence</firstName>
<lastName>Cheung</lastName>
<age>24</age>
<title>CEO</title>
</employeeInfo>
<employeeInfo>
<eid>E1524125</eid>
<firstName>Tom</firstName>
<lastName>Wong</lastName>
<age>22</age>
<title>Clerk</title>
</employeeInfo>
</EmployeeInfoList>
</soapenv:Body>
</soapenv:Envelope>

有時間再講解一下 Java Web Service Client 如何設定。

相閞書籍: Java Web Services: Up and RunningJava Soa CookbookJava Web Services: Up and Running

更新 Blogger 模板

| Comments

舊模板加了太多東西,令頁面載入的時間比較長。今次換了一個比較簡潔的模板,看上去的感覺比較舒服一點,再加上停用一些載入時間較長的 Fancy boxSexy Bookmark 等等,並使用新的方法去載入 Syntax Highlighter, 希望令載入時間更加快。


更新 Log:

  1. Add popular posts widget
  2. Update jQuery to 1.6.1
  3. Update Syntax Highlighter
  4. Update Facebook javascript API
  5. Update Facebook Like box (iframe -> XFBML)
  6. Update Facebook Like box style
  7. Update Facebook Like (iframe -> XFBML)
  8. Update Google Adsense style
  9. Update Google Adsense custom search
  10. Remove Fancy Box
  11. Remove BloggerAds banner

相關書籍: Beginning Google BloggerPublishing a Blog with Blogger: Visual QuickProject Guide (2nd Edition)Blogger: Beyond the Basics: Customize and promote your blog with original templates, analytics, advertising, and SEO (From Technologies to Solutions)