วันอังคารที่ 26 เมษายน พ.ศ. 2554

Spring Framework and Web Service JAX-WS on Annotation

หลังจากที่เราได้รู้เรื่องการติดต่อ Database กับไปแล้วจากบทนี้

Spring Framework and Hibernate on Annotation

ตอนนี้เราก็มารู้เรื่องเกี่ยวกับ Web Service กันต่อเลย แต่จะย่อหน่อย เป็นตัวอย่าง
เพราะจริง ๆ ก็เขียนอยู่แค่นี้เอง

ProjectCheckListWS.java
package th.go.nhso.projecttracking.webservice.service;

import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebResult;
import javax.jws.WebService;
import th.go.nhso.projecttracking.webservice.model.ProjectPlanCodeResult;
import th.go.nhso.projecttracking.webservice.model.ProjectPlanCodeRequest;
import th.go.nhso.projecttracking.webservice.model.ProjectCheckInRequest;
import th.go.nhso.projecttracking.webservice.model.ProjectCheckInResult;
import th.go.nhso.projecttracking.webservice.model.ProjectCheckOutRequest;
import th.go.nhso.projecttracking.webservice.model.ProjectCheckOutResult;

@WebService
public interface ProjectCheckListWS {
    public ProjectPlanCodeResult getPlanCode(ProjectPlanCodeRequest planCodeReq);
    public ProjectCheckInResult checkIn(ProjectCheckInRequest checkInReq);
    public ProjectCheckOutResult checkOut(ProjectCheckOutRequest checkOutReq);
}


ProjectCheckListWSImpl
package th.go.nhso.projecttracking.webservice.service;

import javax.jws.WebMethod;
import javax.jws.WebService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.context.support.SpringBeanAutowiringSupport;
import th.go.nhso.projecttracking.service.ProjectCheckListService;
import th.go.nhso.projecttracking.webservice.model.ProjectPlanCodeRequest;
import th.go.nhso.projecttracking.webservice.model.ProjectPlanCodeResult;
import th.go.nhso.projecttracking.webservice.model.ProjectCheckInRequest;
import th.go.nhso.projecttracking.webservice.model.ProjectCheckInResult;
import th.go.nhso.projecttracking.webservice.model.ProjectCheckOutRequest;
import th.go.nhso.projecttracking.webservice.model.ProjectCheckOutResult;

@WebService(serviceName = "ProjectCheckListWebService")
public class ProjectCheckListWSImpl extends SpringBeanAutowiringSupport implements ProjectCheckListWS {

    @Autowired
    private ProjectCheckListService service;

    @Override
    @WebMethod
    public ProjectCheckInResult checkIn(ProjectCheckInRequest req) {
        return service.checkIn(req);
    }

    @Override
    @WebMethod
    public ProjectCheckOutResult checkOut(ProjectCheckOutRequest req) {
        return service.checkOut(req);
    }

    @Override
    @WebMethod
    public ProjectPlanCodeResult getPlanCode(ProjectPlanCodeRequest req) {
        return service.getPlanCode(req);
    }

}


หรือในอีกกรณีคือ ประกาศ endpointInterface ให้กับ ตัว implement แต่วิธีนี้ spring จะสามารถ ผูก service (autowired) ได้ และต้อง extends SpringBeanAutowiringSupport ด้วย มิฉะนั้น service จะ null ได้
ตอนสร้าง service ครั้งแรกไปดูสลับกับ CXF Web Service เลยก็งง ๆ อยู่ เหมือนกันมาก

model ที่ใช้ ส่งค่า parameter นั้นต้องมีการประกาศอะไรเพิ่มเติมนิดหน่อย ตัวอย่าง

BaseProjectCheckRequest.java
package th.go.nhso.projecttracking.webservice.model;

import java.io.Serializable;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;

@XmlAccessorType(XmlAccessType.FIELD)
public abstract class BaseProjectCheckRequest implements Serializable {
    
    public String projectCode;
    
    public String projectPlanCode;
    
    public String processCode;
    
    public String processSubCode;
    
    private String postBy;
    
    private String postLocation;

    public String getPostBy() {
        return postBy;
    }

    public void setPostBy(String postBy) {
        this.postBy = postBy;
    }

    public String getPostLocation() {
        return postLocation;
    }

    public void setPostLocation(String postLocation) {
        this.postLocation = postLocation;
    }

    public String getProcessCode() {
        return processCode;
    }

    public void setProcessCode(String processCode) {
        this.processCode = processCode;
    }

    public String getProcessSubCode() {
        return processSubCode;
    }

    public void setProcessSubCode(String processSubCode) {
        this.processSubCode = processSubCode;
    }

    public String getProjectCode() {
        return projectCode;
    }

    public void setProjectCode(String projectCode) {
        this.projectCode = projectCode;
    }

    public String getProjectPlanCode() {
        return projectPlanCode;
    }

    public void setProjectPlanCode(String projectPlanCode) {
        this.projectPlanCode = projectPlanCode;
    }
}

ProjectCheckInRequest.java
package th.go.nhso.projecttracking.webservice.model;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class ProjectCheckInRequest extends BaseProjectCheckRequest {

}

ก็คือ มีการกำหนด xml type เข้าไปด้วย ไม่อย่างนั้นมันจะมีข้อผิดพลาดเกิดขึ้น
@XmlAccessorType(XmlAccessType.FIELD) << ถ้าไม่ใส่ จะเกิดการเรียกสองครั้ง คือ ครั้งแรกจะไปเรียก attribute ก่อน แล้ว มันก็จะไปเรียก get method อีกที แล้วก็ฟ้องว่ามีการประกาศ attribute สองตัวเหมือนกัน ใน class เดียว -_-" อ่าว โง่หนิ !!!
@XmlRootElement << เพื่อบอกว่า ให้เอา attribute ของ root มาเป็นของตัวเองด้วย ไม่งั้นมันจะว่างเปล่า ไม่มี attribute เลย เพราะ class ลูกเราก็ไม่ค่อยได้เขียนอะไรอยู่แล้ว

ส่วนละเอียด service ก็จะเป็นประมาณนี้

ProjectCheckListService.java
package th.go.nhso.projecttracking.service;

import th.go.nhso.projecttracking.webservice.model.ProjectCheckInRequest;
import th.go.nhso.projecttracking.webservice.model.ProjectCheckInResult;
import th.go.nhso.projecttracking.webservice.model.ProjectCheckOutRequest;
import th.go.nhso.projecttracking.webservice.model.ProjectCheckOutResult;
import th.go.nhso.projecttracking.webservice.model.ProjectPlanCodeRequest;
import th.go.nhso.projecttracking.webservice.model.ProjectPlanCodeResult;

public interface ProjectCheckListService {
    public ProjectCheckInResult checkIn(ProjectCheckInRequest request);    
    public ProjectCheckOutResult checkOut(ProjectCheckOutRequest request);
    public ProjectPlanCodeResult getPlanCode(ProjectPlanCodeRequest request);
}


ProjectCheckListService.java
package th.go.nhso.projecttracking.service;

import java.util.Date;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import th.go.nhso.projecttracking.dao.ProcessTranDao;
import th.go.nhso.projecttracking.model.ProcessTran;
import th.go.nhso.projecttracking.webservice.model.ProjectCheckInRequest;
import th.go.nhso.projecttracking.webservice.model.ProjectCheckInResult;
import th.go.nhso.projecttracking.webservice.model.ProjectCheckOutRequest;
import th.go.nhso.projecttracking.webservice.model.ProjectCheckOutResult;
import th.go.nhso.projecttracking.webservice.model.ProjectPlanCodeRequest;
import th.go.nhso.projecttracking.webservice.model.ProjectPlanCodeResult;

@Service
public class ProjectCheckListServiceImpl implements ProjectCheckListService {
    private static String STEP_IN = "START";
    private static String STEP_OUT = "FINISH";
    
    @Autowired
    private ProcessTranDao processTranDao;
    
    @Override
    public ProjectCheckInResult checkIn(ProjectCheckInRequest request) {
        ProcessTran t = new ProcessTran();
        this.setReqeq2Tran(t, request);
        processTranDao.insert(t);
        
        ProjectCheckInResult result = new ProjectCheckInResult();
        result.setCode("200");
        result.setDescription("SUCCESS");
        return result;
    }

    @Override
    public ProjectCheckOutResult checkOut(ProjectCheckOutRequest request) {
        ProcessTran t = new ProcessTran();
        this.setReqeq2Tran(t, request);
        processTranDao.insert(t);
        
        
        ProjectCheckOutResult result = new ProjectCheckOutResult();
        result.setCode("200");
        result.setDescription("SUCCESS");
        return result;
    }

    private void setReqeq2Tran(ProcessTran t, ProjectCheckInRequest r) {
        t.setProjectId(Long.MIN_VALUE);
        t.setProjectPlanId(Long.MIN_VALUE);
        t.setProcessId(Long.MIN_VALUE);
        t.setProcessSubId(Long.MIN_VALUE);
        t.setCreateDate(new Date());
        t.setCreateBy(r.getPostLocation());
        t.setStep(STEP_IN);
    }
    
    
    private void setReqeq2Tran(ProcessTran t, ProjectCheckOutRequest r) {
        t.setProjectId(Long.MIN_VALUE);
        t.setProjectPlanId(Long.MIN_VALUE);
        t.setProcessId(Long.MIN_VALUE);
        t.setProcessSubId(Long.MIN_VALUE);
        t.setCreateDate(new Date());
        t.setCreateBy(r.getPostLocation());
        t.setStep(STEP_OUT);
    }

    @Override
    public ProjectPlanCodeResult getPlanCode(ProjectPlanCodeRequest request) {
        throw new UnsupportedOperationException("Not supported yet.");
    }
    
}

ก็เป็น service ธรรมดาที่เราผูกไว้ กับ spring นั่นเอง

Spring Framework and Hibernate on Annotation

แปลกใจตัวเองไม่ได้เขียนอันนี้ขึ้นมา ทั้งที่น่าจะใช้บ่อย Spring Framework และ Hibernate config on Annotation
แต่เอาเหอ ถึงจะขี้เกียจนิดหน่อย แต่มาเริ่มกันดีกว่า ว่าจะทำอะไรก่อนดี บทนี้น่าจะต่อจาก

Java Spring framework 3 Quickstart

แต่ในบทนี้จะไม่พูดถึงเรื่อง coltroller จะพูดเรื่อง ผูก service hibernate กับ spring เป็นหลัก
อันดับแรก ให้สร้าง Web Project หรือ Java Web Application ด้วย Netbean หรือ eclipse(jee) ตามแต่ถนัดนะ อันนี้คงไม่ลง detail ไม่อยากมีภาคสอง(จะยากเกินเดี๋ยวไม่จบ)

จากนั้นก็ไปที่ file : web/WEB-INF/web.xml เริ่มแก้ไข xml ให้เป็นเช่นนี้

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
    
    <!-- Spring Framework Config -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/applicationContext.xml</param-value>
    </context-param>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>2</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>*.htm</url-pattern>
    </servlet-mapping>
    
    <session-config>
        <session-timeout>30</session-timeout>
    </session-config>
    <welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
    </welcome-file-list>

</web-app>

จากนั้น ก็ config application file กับต่อเลย file : web/WEB-INF/applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans 
       http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context-3.0.xsd
       http://www.springframework.org/schema/aop 
       http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
       http://www.springframework.org/schema/tx 
       http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">
    
    <import resource="/datasource.xml" />
    <import resource="/hibernate.xml" />
    
    <context:annotation-config/>
    <context:component-scan base-package="th.go.nhso.projecttracking" />
    
</beans>



ตามด้วย config database datasource config file : web/WEB-INF/datasource.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
  
    <bean id="dataSource" 
         class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver" />
        <property name="url" value="jdbc:mysql://localhost:3306/projecttracking" />
        <property name="username" value="develop" />
        <property name="password" value="password" />
    </bean>

</beans>


ต่อจากนั้น ก็ hibernate config file : web/WEB-INF/hibernate.xml
ในส่วนนี้จะทำการ create และ update table ใน database ให้เอง 

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:aop="http://www.springframework.org/schema/aop"
        xmlns:tx="http://www.springframework.org/schema/tx"
        xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">

    
    <bean id="hibernateTemplate" class="org.springframework.orm.hibernate3.HibernateTemplate">
        <constructor-arg ref="sessionFactory" name="sessionFactory" />
    </bean>
    
    <!-- Hibernate session factory -->
    <bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <property name="hibernateProperties">
            <props>
                <prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
                <prop key="hibernate.show_sql">true</prop>
            </props>
        </property>
        <property name="schemaUpdate" value="true"/>
        <property name="packagesToScan" value="th.go.nhso.projecttracking.model"/>
    </bean>
    
</beans>



อันดับต่อไป อันนี้ไม่ค่อยสำคัญมากในหัวเรื่องนี้ view file : dispatcher-servlet.xml
ถ้าใช้ netbean สร้าง web project ดึง spring framework เป็น plugin ก็จะมี file นี้โดยไม่ต้องเขียนเอง ส่วนใหญ่เราจะไม่ค่อยได้ยุ่งกับ file นี้มากนัก ใช้เป็นแค่ทางผ่านไป controller เฉย ๆ เพราะ concept web เดิม เราต้องเขียน serverlet config ผูกกับชื่อ (ถ้าไม่เข้าใจกลับไปดู servlet พื้นฐาน) แต่ในที่นี้เหมือนเราทำรอบเดียว

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
       http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
       http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">

    <bean class="org.springframework.web.servlet.mvc.support.ControllerClassNameHandlerMapping"/>
    
    <!--
    Most controllers will use the ControllerClassNameHandlerMapping above, but
    for the index controller we are using ParameterizableViewController, so we must
    define an explicit mapping for it.
    -->
    <bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
        <property name="mappings">
            <props>
                <prop key="index.htm">indexController</prop>
            </props>
        </property>
    </bean>
    
    <bean id="viewResolver"
          class="org.springframework.web.servlet.view.InternalResourceViewResolver"
          p:prefix="/WEB-INF/jsp/"
          p:suffix=".jsp" />
    
    <!--
    The index controller.
    -->
    <bean name="indexController"
          class="org.springframework.web.servlet.mvc.ParameterizableViewController"
          p:viewName="index" />
    
</beans>

นี่ขนาดใช้ annotation ช่วยแระ ยังมี xml file config เหลือขนาดนี้ -_-"

ต่อมาก็คงเป็น DAO class ที่ทำการติดต่อ database เริ่มจาก

BaseDao.java
package th.go.nhso.projecttracking.dao;

public interface BaseDao {
    public void insert(Object... models);
    public Long insert(Object model);
    public void update(Object... models);
    public void delete(Class clazz, Long... ids);
    public Object getById(Class clazz, Long id);
}


ProcessDao.java
package th.go.nhso.projecttracking.dao;

public interface ProcessDao extends BaseDao {
}

BaseDaoImpl.java
package th.go.nhso.projecttracking.dao;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.orm.hibernate3.HibernateTemplate;

public abstract class BaseDaoImpl implements BaseDao {

    @Autowired
    private HibernateTemplate hibernateTemplate;

    @Override
    public Object getById(Class clazz, Long id) {
        return (Object) hibernateTemplate.get(clazz, id);
    }

    @Override
    public void insert(Object... models) {
        for (Object model : models) {
            hibernateTemplate.save(model);
        }
    }

    @Override
    public Long insert(Object model) {
        return (Long) hibernateTemplate.save(model);
    }

    @Override
    public void update(Object... models) {
        for (Object model : models) {
            hibernateTemplate.update(model);
        }
    }

    @Override
    public void delete(Class clazz, Long... ids) {
        for (Long id : ids) {
            Object o = hibernateTemplate.get(clazz, id);
            if (null != o) {
                hibernateTemplate.delete(o);
            }
        }
    }

    public HibernateTemplate getHibernateTemplate() {
        return hibernateTemplate;
    }

    public void setHibernateTemplate(HibernateTemplate hibernateTemplate) {
        this.hibernateTemplate = hibernateTemplate;
    }
}



ProcessDaoImpl.java
package th.go.nhso.projecttracking.dao;

public interface ProcessDao extends BaseDao {
    
}

BaseEntity.java
package th.go.nhso.projecttracking.model;

import java.io.Serializable;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.MappedSuperclass;
import javax.persistence.Temporal;

@MappedSuperclass
public class BaseEntity implements Serializable {
    
    @Column
    private String createBy;
    
    @Column
    @Temporal(javax.persistence.TemporalType.TIMESTAMP)
    private Date createDate;
    
    @Column
    private String updateBy;
    
    @Column
    @Temporal(javax.persistence.TemporalType.TIMESTAMP)
    private Date updateDate;

    
    public String getCreateBy() {
        return createBy;
    }

    public void setCreateBy(String createBy) {
        this.createBy = createBy;
    }

    public Date getCreateDate() {
        return createDate;
    }

    public void setCreateDate(Date createDate) {
        this.createDate = createDate;
    }

    public String getUpdateBy() {
        return updateBy;
    }

    public void setUpdateBy(String updateBy) {
        this.updateBy = updateBy;
    }

    public Date getUpdateDate() {
        return updateDate;
    }

    public void setUpdateDate(Date updateDate) {
        this.updateDate = updateDate;
    }
    
}

Process.java
package th.go.nhso.projecttracking.model;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name="PROCESS")
public class Process extends BaseEntity {
    
    public Process(){}
    
    @Id
    @GeneratedValue
    private Long id;
    
    @Column
    private String code;
    
    @Column
    private String description;

    @Column
    private Long projectId;

    @Column
    private Long projectPlanId;

    public Long getProjectId() {
        return projectId;
    }

    public void setProjectId(Long projectId) {
        this.projectId = projectId;
    }

    public Long getProjectPlanId() {
        return projectPlanId;
    }

    public void setProjectPlanId(Long projectPlanId) {
        this.projectPlanId = projectPlanId;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }
    
    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }
    
}

เราไปดู test กันดีกว่า ทำแล้วก็ต้อง test ได้
จริง ๆ ก็จะมี service รองรับ ก่อนถึง dao แต่ตอนนี้จะ test ว่าที่ทำขึ้นมาสามารถ insert ได้หรือเปล่าไปก่อน
ในส่วน config location อาจต้องแก้ path สำหรับ เรียกดู applicationContext เอง แต่ในที่นี้ ใช้วิธี copy มาที่ source path ก่อน
BaseDaoTest.java
package th.go.nhso.projecttracking.dao.test;

import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath*:applicationContext.xml" })
public abstract class BaseDaoTest {
    
}

ProcessDaoImplTest
package th.go.nhso.projecttracking.dao.test;

import java.util.Date;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import th.go.nhso.projecttracking.model.Process;
import th.go.nhso.projecttracking.dao.ProcessDao;

public class ProcessDaoImplTest extends BaseDaoTest {
    
    @Autowired
    private ProcessDao processDao;

    public ProcessDao getProcessDao() {
        return processDao;
    }

    public void setProcessDao(ProcessDao processDao) {
        this.processDao = processDao;
    }

    @Test
    public void testSomeMethod() {
        Process p = new Process();
        p.setCode("001");
        p.setProjectId(new Long(1));
        p.setProjectPlanId(new Long(1));
        p.setDescription("detail001");
        p.setCreateBy("staff");
        p.setCreateDate(new Date());
        processDao.insert(p);
    }
}

run test file ถ้าไม่มี error ก็ ok ผ่าน คร่าว ๆ ก็น่าจะมีแค่นี้ก่อนครับ