반응형

Visual Studio.net 버전에만 가능한걸로 알고 있구요...

Visual Studio.net에서는 메뉴에서

도구 - 옵션에 들어가셔서

'모든언어' 노드를 선택하시고 오른쪽 다이얼로그에서 '줄번호' 체크박스를 키시면 됩니다.
반응형

애플리케이션에서 사용할 데이터 모델 만들기

모델-뷰-컨트롤(MVC) 아키텍처에 익숙하다면 지금까지 만든 것이 애플리케이션의 뷰에 해당한다는 것을 알아차렸을 것이다. 이 부분이야 말로 Jigloo가 진가를 발휘하는 부분이다. 이제는 모델을 만들어야 한다. 다시 말하면 시스템에서 사용할 데이터를 가져오고 사용할 자바 코드를 만들어야 함을 의미한다. Jigloo는 여기서도 역시 빛을 발한다. Jigloo는 단순한 이클립스 플러그인으로 Jigloo를 사용할 때도 직접 이클립스의 강력한 기능들을 힘들이지 않고 바로 사용할 수 있다. 이클립스는 자바 코드 작성과 데이터를 다루는 데 매우 훌륭한 도구이기 때문에 Jigloo 역시 이런 작업을 하기에 적합하다.

스키마 만들기

언급했다시피, XML 형식으로 데이터를 저장하고 JAXB를 사용하여 XML을 읽고 쓰는 작업을 할 것이다. 따라서 스키마를 작성할 필요가 있다.


Listing 1. 워크플로우 XML 스키마
                    
<?xml version="1.0" encoding="UTF-8"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema" 
  targetNamespace="org:developerworks:workflow" 
  xmlns:dw="org:developerworks:workflow">

     <element name="workflow" type="dw:workflow"/>
     <complexType name="workflow">
          <sequence>
               <element name="user" type="dw:user" minOccurs="0"
maxOccurs="unbounded"/>
               <element name="po" type="dw:purchaseOrder"
minOccurs="0" maxOccurs="unbounded"/>
          </sequence>
     </complexType>
     
     <complexType name="user">
          <sequence>
               <element name="username" type="string"/>
               <element name="role" type="dw:role"/>
          </sequence>
          <attribute name="id" type="integer" use="required"/>
     </complexType>
     <simpleType name="role">
          <restriction base="string">
               <enumeration value="worker"/>
               <enumeration value="manager"/>
          </restriction>
     </simpleType>
     
     <complexType name="purchaseOrder">
          <sequence>
               <element name="priority" type="dw:priority"/>
               <element name="dateRequested" type="date"/>
               <element name="dateNeeded" type="date" minOccurs="0"/>
               <element name="itemName" type="string"/>
               <element name="itemDescription" type="string" minOccurs="0"/>
               <element name="quantityRequested" type="integer"/>
               <element name="url" type="anyURI" minOccurs="0"/>
               <element name="price" type="decimal"/>
               <element name="status" type="dw:orderStatus"/>
               <element name="submittedBy" type="integer"/>
               <element name="processedBy" type="integer" minOccurs="0"/>
          </sequence>
          <attribute name="id" type="integer" use="required"/>
     </complexType>
     
     <simpleType name="priority">
          <restriction base="string">
               <enumeration value="normal"/>
               <enumeration value="high"/>
          </restriction>
     </simpleType>
     
     <simpleType name="orderStatus">
          <restriction base="string">
               <enumeration value="pending"/>
               <enumeration value="approved"/>
               <enumeration value="rejected"/>
          </restriction>
     </simpleType>
</schema>

스키마에 해당하는 XML 파일에 바인딩할 자바 클래스를 만들기 위해 JAXB를 사용할 수 있다. 자바 6을 사용한다면 JAXB가 내장되어 있다. 자바 5를 사용한다면 썬에서 JAXB를 다운로드해야 한다. 여러분은 명령행 도구인 xjc를 사용하길 원할 수도 있다. 예를 들어 xjc workflow.xsd와 같이 사용할 수 있다. 이 명령어를 사용하여 workflow.xsd를 스키마 컴파일러가 파싱하고 클래스를 만들어낼 것이다. 그런 다음 클래스 파일들을 프로젝트에 복사하여 사용할 수 있다. 프로젝트에 XML 디렉터리를 만들고 그 안에 스키마 파일도 복사한다. 이제 예제로 사용할 XML 파일을 만들자.


Listing 2. 초기 XML 데이터
                    
<?xml version="1.0" encoding="UTF-8"?>
<dw:workflow xmlns:dw="org:developerworks:workflow"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="org:developerworks:workflow
workflow.xsd ">
  <user id="0">
    <username>homer</username>
    <role>worker</role>
  </user>
  <user id="1">
    <username>bart</username>
    <role>manager</role>
  </user>
  <po id="0">
    <priority>normal</priority>
    <dateRequested>2001-01-01</dateRequested>
    <dateNeeded>2001-01-01</dateNeeded>
    <itemName>stapler</itemName>
    <itemDescription>A great stapler</itemDescription>
    <quantityRequested>2</quantityRequested>
    <url>http://www.thinkgeek.com/homeoffice/gear/61b7/</url>
    <price>21.99</price>
    <status>pending</status>
    <submittedBy>0</submittedBy>
  </po>
</dw:workflow>

모든 요소들을 추가한 뒤 Package Explorer는 그림 26처럼 보일 것이다(필요하다면 JAXB jar 파일들을 자바 빌드 패스에 추가해야 한다).


그림 26. JAXB 클래스와 XML 파일이 추가된 Package explorer
JAXB 클래스와 XML 파일이 추가된 Package explorer




위로


데이터 접근

Package Explorer에 몇 가지 파일들이 추가된 것을 확인할 수 있을 것이다. 바로 WorkflowDao와 XmlWorkflow다. WorkflowDao는 데이터를 사용할 때 필요로 하는 작업들을 정의한 인터페이스다(Listing 3).


Listing 3. WorkflowDao 인터페이스
                    
package org.developerworks.workflow;

import java.util.List;

public interface WorkflowDao {
     public List<User> getUsers();
     public List<PurchaseOrder> getAllOrders();
     public List<PurchaseOrder> getAllPendingOrders();
     public List<PurchaseOrder> getOrdersForUser(int userId);
     public void saveOrder(PurchaseOrder order);
     public void setOrderStatus(int orderId, OrderStatus status);
}

고전적인 데이터 접근 객체(Data Access Object) 패턴을 사용하고 있다. 간단하게 인터페이스를 정의하고 이 인터페이스에 대한 애플리케이션 코드를 작성한다. XML을 사용하여 JAXB 기반 구현을 할 것이다. 하지만 이 디자인을 사용하여 손쉽게 데이터베이스 기반 구현 같은 기타 구현으로 변경할 수 있다. 이 인터페이스의 구현체가 XmlWorkFlow다.


Listing 4. XmlWorkflow 인터페이스 구현
                    
package org.developerworks.workflow;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;

public class XmlWorkflow implements WorkflowDao {
     private static final String DATA_FILE = "data.xml";
     private static XmlWorkflow instance;
     
     private Workflow workflow;
     
     private XmlWorkflow() {
          try {
               JAXBContext ctx = this.getContext();
               Unmarshaller unm = ctx.createUnmarshaller();
               File dataFile = this.getDataFile();
               InputStream inputStream;
               if (dataFile.exists() && dataFile.length() > 0){
                    inputStream = new FileInputStream(dataFile);
               } else {
                    inputStream = 
Thread.currentThread().getContextClassLoader().getResourceAsStream("xml/"+DATA_FILE);
               }
               JAXBElement element = (JAXBElement) unm.unmarshal(inputStream);
               this.workflow = (Workflow) element.getValue();
          } catch (JAXBException e) {
               e.printStackTrace();
               throw new RuntimeException("Failed to read data file",e);
          } catch (FileNotFoundException e) {
               e.printStackTrace();
               throw new RuntimeException("Could not open data file", e);
          }
     }
     
     public static XmlWorkflow getInstance(){
          if (instance == null){
               instance = new XmlWorkflow();
          }
          return instance;
     }

     public List<PurchaseOrder> getAllOrders() {
          return this.workflow.getPo();
     }

     public List<PurchaseOrder> getAllPendingOrders() {
          List<PurchaseOrder> allOrders = this.getAllOrders();
          List<PurchaseOrder> pending = new ArrayList<PurchaseOrder>();
          for (PurchaseOrder order : allOrders){
               if (order.getStatus().equals(OrderStatus.PENDING)){
                    pending.add(order);
               }
          }
          return pending;
     }

     public List<PurchaseOrder> getOrdersForUser(int userId) {
          List<PurchaseOrder> allOrders = this.getAllOrders();
          List<PurchaseOrder> userOrders = new ArrayList<PurchaseOrder>();
          for (PurchaseOrder order : allOrders){
               if (order.getSubmittedBy().intValue() == userId){
                    userOrders.add(order);
               }
          }
          return userOrders;
     }

     public List<User> getUsers() {
          return this.workflow.getUser();
     }

     public void saveOrder(PurchaseOrder order) {
          int index = 0;
          for (PurchaseOrder po : this.workflow.getPo()){
               if (po.getId().intValue() == order.getId().intValue()){
                    this.workflow.getPo().set(index, order);
                    this.saveData();
                    return;
               }
               index++;
          }
          // add new order
          order.setId(new BigInteger(Integer.toString(this.workflow.getPo().size())));
          this.workflow.getPo().add(order);
          this.saveData();
     }
     
     public void setOrderStatus(int orderId, OrderStatus status) {
          for (PurchaseOrder po : this.workflow.getPo()){
               if (po.getId().intValue() == orderId){
                    po.setStatus(status);
                    this.saveData();
                    return;
               }
          }
     }

     private void saveData(){
          File dataFile = this.getDataFile();
          try {
               JAXBContext ctx = this.getContext();
               Marshaller marshaller = ctx.createMarshaller();
               FileOutputStream stream = new FileOutputStream(dataFile);
               marshaller.marshal(this.workflow, stream);
          } catch (JAXBException e) {
               e.printStackTrace();
               throw new RuntimeException("Exception serializing data file",e);
          } catch (FileNotFoundException e) {
               e.printStackTrace();
               throw new RuntimeException("Exception opening data file");
          }
     }
     
     private File getDataFile() {
          String tempDir = System.getProperty("java.io.tmpdir");
          File dataFile = new File(tempDir + File.separatorChar + DATA_FILE);
          return dataFile;
     }

     private JAXBContext getContext() throws JAXBException {
          JAXBContext ctx = JAXBContext.newInstance("org.developerworks.workflow");
          return ctx;
     }
     
     public static void main(String[] args){
          XmlWorkflow dao = XmlWorkflow.getInstance();
          List<User> users = dao.getUsers();
          assert(users.size() == 2);
          for (User user : users){
               System.out.println("User: " + user.getUsername() + " ID:" + user.getId());
          }
          List<PurchaseOrder> orders = dao.getAllOrders();
          assert(orders.size() == 1);
          for (PurchaseOrder order : orders){
               System.out.println("Order:" + order.getItemName() + "
ID:" + order.getId() + " Status:" + order.getStatus());
          }
          PurchaseOrder order = orders.get(0);
          order.setStatus(OrderStatus.APPROVED);
          order.setProcessedBy(new BigInteger("1"));
          dao.saveOrder(order);
     }
}

위에서 만들었던 예제 파일을 초기에 읽어 들이는 것을 확인할 수 있지만 시스템 임시 폴더에 data.xml로 변경사항을 저장한다. 데이터를 저장하기에 가장 안전한 장소는 아니지만 예제 애플리케이션에 사용하기에는 적절하다. 이 클래스 파일 안에 간단한 main 메서드가 있는 것 또한 볼 수 있다. JAXB가 동작하는 것을 간단하게 단위 테스트하기 위한 용도로 만들었다. 만약에 자바 5를 사용한다면 JAXB jar 파일을 프로젝트의 클래스패스에 추가해야 한다. 계속해서 작업하려면 그 파일들을 복사하여 프로젝트에 추가하거나 프로젝트 외부에 그 파일들이 위치한 장소를 참조할 수 있도록 해야 한다.




위로


애플리케이션 초기화하기

애플리케이션과 인터랙트하기 전에 모든 요소를 초기화해야 한다. 먼저 애플리케이션에서 사용할 모델 객체를 몇 개 선언해야 한다. Listing 5에 있는 코드를 추가하여 WorkflowMain 멤버 변수를 추가한다.


Listing 5. 모델 객체 선언
                    
     // Data Model Objects
     private java.util.List<User> users;
     private User user;
     
     // Service Object
     private WorkflowDao dao = XmlWorkflow.getInstance();

코드에 접근하려면 Workflow.java 파일을 마우스 오른쪽 클릭을 하고 Open With > Java Editor를 선택한다.

애플리케이션의 initGUI() 메서드 코드를 수정한다. 사용자 명단을 초기화하기 위해 private 메서드를 만들겠다.


Listing 6. 사용자 메서드 만들기
                    
     private void initUserList(){
          this.users = dao.getUsers();
          for (User u : users){
               this.userListCombo.add(u.getUsername());
          }
     }

userListCombo를 정의한 후 initGUI()에서 이 메서드를 호출한다.


Listing 7. 사용자 메서드 호출
                    
                {
                    userListCombo = new Combo(this, SWT.NONE);
                    userListCombo.setText("Users");
                    userListCombo.setBounds(28, 35, 105, 21);
                    this.initUserList();
               }




위로


이벤트를 사용하여 뷰와 모델을 엮기

뷰와 모델을 만들었다. 이제 그 둘을 엮어 보자. 컨트롤러가 필요하다. SWT(와 스윙)는 모든 UI 프레임워크에서 사용하는 간단한 기술을 사용한다. 바로 이벤트 주도 시스템이라는 것이다. 이벤트를 사용하여 언제 모델에 있는 작업을 호출하고 뷰를 수정할지 알려줄 수 있다.

이제 다시 비주얼 디자이너로 돌아가자. 모델과 엮기를 원하는 첫 번째 UI 이벤트는 사용자 콤보 리스트에서 사용자를 선택했을 때다. 콤보 컨트롤을 선택하고 GUI 속성 뷰에서 Event 탭으로 변경하면 그림 27에 보이는 것과 같은 화면을 확인할 수 있을 것이다.


그림 27. 콤보 컨트롤 이벤트에 접근하기
콤보 컨트롤 이벤트에 접근하기

몇몇 리스너들을 콤보 컨트롤에서 볼 수 있다. SelectionListener를 선택한다. 이 리스너는 콤보 컨트롤에서 무언가를 선택할 때마다 SelectionEvent를 발생시킨다. 이 이벤트를 다룰 것을 익명 메서드로 그것을 인라인에서 다룰 수도 있고이 이벤트를 다룰 메서드를 정의할 수도 있다. 후자를 선택하겠다. 이렇게 하면 userListComboWidgetSelected라는 메서드가 생성된다. 이벤트를 다루기 위한 코드는 Listing 8에 나와있다.


Listing 8. 사용자 콤보 리스트 선택 코드
                    
     private void userListComboWidgetSelected(SelectionEvent evt) {
          int index = this.userListCombo.getSelectionIndex();
          if (index >= 0){
               this.user = this.users.get(index);
               System.out.println("User selected="+this.user.getUsername());
               purchaseOrderTable.removeAll();
               java.util.List<PurchaseOrder> orders;
               boolean isManager = this.user.getRole().equals(Role.MANAGER);
               if (isManager){
                    orders = dao.getAllPendingOrders();
               } else {
                    orders = dao.getOrdersForUser(this.user.getId().intValue());
               }
               this.approveButton.setVisible(isManager);
               this.rejectButton.setVisible(isManager);
               for (PurchaseOrder order : orders){
                    displayPurchaseOrder(order);
               }
          }
     }

여기서 살펴봐야 할 것들이 많다. 먼저 사용자가 관리자인지 확인한다. 관리자가 아니면 사용자의 모든 구매 주문 목록을 보여준다. 관리자가 맞다면 대기중인 주문 목록만을 보여준다. 다음으로 관리자가 아니면 승인/취소 버튼이 보이지 않도록 한다. 마지막으로 purchaseOrderTable에 있는 모든 주문 데이터들을 데이터 접근 객체를 사용해 가져와 보여준다.

이제 Approve 버튼에 이벤트를 추가한다. 비주얼 디자이너에 보이는 Approve 버튼을 선택하고 GUI 속성 창에 있는 이벤트 탭으로 이동한다. 버튼을 선택했을 때 실행되어야 하기 때문에 그림 28에 보이는 것처럼 여기서도 selection event를 사용할 것이다.


그림 28. Approve 버튼 선택 이벤트 설정
Approve 버튼 선택 이벤트 설정

그리고 나서 이 이벤트를 다룰 코드를 추가한다.


Listing 9. Approve 버튼 선택 이벤트 코드
                    
     private void approveButtonWidgetSelected(SelectionEvent evt) {
          TableItem[] selected = this.purchaseOrderTable.getSelection();
          if (selected != null){
               for (TableItem item : selected){
	this.dao.setOrderStatus(Integer.parseInt(item.getText(4)), OrderStatus.APPROVED);
                    item.setText(3, OrderStatus.APPROVED.toString());
               }
          }
     }

reject 버튼도 이와 매우 비슷하게 할 수 있다. 선택 이벤트 리스너를 추가하고 비슷한 코드를 실행한다. 유일한 차이가 있다면 주문 상태를 APPROVED가 아닌 REJECTED로 바꾼다는 것이다.


Listing 10. Reject 버튼 코드
                    
     private void rejectButtonWidgetSelected(SelectionEvent evt) {
          TableItem[] selected = this.purchaseOrderTable.getSelection();
          if (selected != null){
               for (TableItem item : selected){
	this.dao.setOrderStatus(Integer.parseInt(item.getText(4)), OrderStatus.REJECTED);
                    item.setText(3, OrderStatus.REJECTED.toString());
               }
          }
     }

이제 남은 건 Submit 버튼이다. 이 버튼을 사용하여 새로운 구매 주문을 추가할 수 있어야 한다. 이 버튼 역시 다른 버튼들과 유사하다. 선택 이벤트에 대한 이벤트 핸들러를 그림 29처럼 추가한다.


그림 29. PO 버튼 선택 리스너 추가
PO 버튼 선택 리스너 추가

이벤트를 다룰 코드를 추가한다.


Listing 11. PO 버튼 이벤트 핸들러 코드 추가
                    
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.GregorianCalendar;

import javax.xml.datatype.DatatypeFactory;
import javax.xml.datatype.XMLGregorianCalendar;

...

     private void addButtonWidgetSelected(SelectionEvent evt) {
          try {
               this.addPurchaseOrder();
          } catch (Exception e) {
               throw new RuntimeException("Exception adding purchase order",e);
          }
          this.formItemText.clearSelection();
          this.formPriceText.clearSelection();
          this.formQuantityText.clearSelection();
     }
     
     private void addPurchaseOrder() throws Exception{
          String item = this.formItemText.getText();
          String priceString = this.formPriceText.getText();
          String quantityString = this.formQuantityText.getText();
          BigDecimal price = new BigDecimal(priceString);
          BigInteger quantity = new BigInteger(quantityString);
          PurchaseOrder po = new PurchaseOrder();
          int num = this.dao.getAllOrders().size();
          String numString = Integer.toString(num);
          BigInteger newId = new BigInteger(numString);
          po.setId(newId);
          po.setItemName(item);
          po.setPrice(price);
          po.setQuantityRequested(quantity);
          po.setPriority(Priority.NORMAL);
          po.setStatus(OrderStatus.PENDING);
          po.setSubmittedBy(this.user.getId());
          GregorianCalendar cal = (GregorianCalendar) GregorianCalendar.getInstance();
          DatatypeFactory factory = DatatypeFactory.newInstance();
          XMLGregorianCalendar now = factory.newXMLGregorianCalendar(cal);
          po.setDateRequested(now);
          this.dao.saveOrder(po);
          this.displayPurchaseOrder(po);
     }

	private void displayPurchaseOrder(PurchaseOrder order) {
		String[] row = new String[] 
		         {order.getItemName(), order.getPrice().toString(), 
				order.getQuantityRequested().toString(),
order.getStatus().toString(), order.getId().toString()};
		TableItem tableItem = new TableItem(purchaseOrderTable,0);
		  tableItem.setText(row);
		  this.purchaseOrderTable.showItem(tableItem);
	}




위로


GUI 테스트

GUI를 테스트해야 할 시간이다. 그림 30처럼 클래스를 오른쪽 클릭을 한 후 Run As > SWT Application을 선택한다.


그림 30. 애플리케이션 실행
애플리케이션 실행

이렇게 하면 애플리케이션이 실행된다.


그림 31. 워크플로우 애플리케이션
워크플로우 애플리케이션

기본 데이터로 대기중인 구매 주문을 넣어뒀기 때문에 사용자 bart를 선택한 뒤 구매 주문을 승인할 수 있다.


그림 32. 주문 승인
주문 승인

사용자를 homer로 변경한 뒤 구매 주문의 상태를 확인해 보자.


그림 33. 사용자가 자기 PO 보기
사용자가 자기 PO 보기

새로운 주문을 추가할 수도 있다.


그림 34. PO 추가
PO 추가

Add PO를 클릭하여 테이블에 구매 주문을 추가할 수 있다.


그림 35. 새 PO 추가
새 PO 추가

다시 bart로 변경하여 승인 또는 취소할 수 있다.


그림 36. 업데이트된 관리자 화면
업데이트된 관리자 화면 
반응형
:: 2007년 7월 13일 ::


로보코드 코리아컵 2007

자바 프로그램 경진대회인 로보코드 코리아컵 2007의 접수 마감이 얼마 남지 않았습니다. 7월 22일(금) 로보코드의 예선 접수가 마감되오니 아직 제작 중이거나 준비 중이신 분들은 서둘러 접수를 해주시기 바랍니다. 재미있는 게임을 통해 자바 실력도 늘리고 푸짐한 상품도 꼭 받아가세요. 참가만 하셔도 참가상을 드리니 여러분의 많은 성원 바랍니다.



제2기 대학생 모니터요원 모집

IBM developerWorks에서 2007년 하반기 동안 활동할 "제2기 developerWorks 대학생 모니터 요원" 모집이 7월 20일 마감됩니다. 선정된 모니터 요원에게는 임명장과 함께 각종 행사에 우선 참여할 수 있는 기회가 주어집니다. 우수한 활동을 펼친 분께는 IBM의 추천서도 제공되오니 재기 넘치는 대학생 여러분의 많은 참여 바랍니다.



오픈 소스 세미나

IBM이 후원하는 기묘 세미나, 성공적인 오픈소스 프로젝트 방법론이 오는 19일(목),역삼동 포스틸 타워 3층 이벤트홀에서 열립니다!  이번 세미나에서는 오픈소스 프로젝트를 성공으로 이끄는 방법론을 중심으로 오픈소스의 커스터마이징과 확장 전략을 세워볼 수 있는 계기가 될 것입니다. 선착순으로 사전등록이 마감되오니 관심 있으신 분들은 서둘러 주세요.

+ Recent posts