Fork me on GitHub

Programming Design Notes

Java 使用 Bottom Up 和 JAX-WS 來部署 Web Service Provider

| Comments

網路上關於 Web Service 的資訊不是很多,中文的 Web Service 資源更是少之有少,可能是因為部署 Web Service 是一件麻煩的事,而且性能又不是太好,但 Web Service 也有很多好處。不用同一種程式語言也可以互相交換數據,執行程序,有標準的格式,大多數語言都提供了 Library 去操作。Web Service 大多數用於對外的對像,例如: 銀行和銀行之間的數據交換亦可以通過 Web Service 做到。

有 2 種主要的方式去設計一個 Web Service,就是 Top DownBottom Up,究竟有什麼分別呢?

Top Down 是由定義 Object (物件) 和 Interface (介面) 做起的,Object (物件) 你可以想像為傳遞數據的一個容器,而這個物件是由 XML Schema (XSD) 去描述的。Interface (介面) 則是描述這個 Web Service 提供什麼服務,服務需要傳入一個什麼 Object (物件) 和 返回一個什麼 Object (物件)等等描述,這些都是由 Web Services Description Language (WSDL) 去描述的。做好這 2 項工作後就需要製作和 XML Schema 描述一樣的 Object Class,亦要製作和 Web Services Description Language 描述一樣的 Functional Class。最後當然要寫程式式碼和放上 Application Server

Bottom Up 就比較簡單了,是由 Object ClassFunctional Class 先做起,做起後放上 Application ServerApplication Server 自動產生出 XML Schema (XSD)Web Services Description Language (WSDL)。不懂得 XML SchemaWeb Services Description Language 也一樣可以製作出一個 Web Service

最好當然是對 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

首先當然講解一下最簡單的 Bottom Up 方法,首先要在 IDE 建立一個 WAR project,加入一個 Java Class:
package sample.ws;

import javax.jws.WebMethod;
import javax.jws.WebService;

@WebService(
name = "CalculaterService",
serviceName = "CalculaterService",
portName = "CalculaterServicePort",
targetNamespace = "http://pro.ctlok.com/CalculaterService/")
public class CalculaterService {

@WebMethod
public int add(int i, int k) {
return i + k;
}

@WebMethod
public int subtract(int i, int k) {
return i - k;
}

}

就這樣完成了一個最簡單的 Web Service

@WebService(
name = "CalculaterService",
serviceName = "CalculaterService",
portName = "CalculaterServicePort",
targetNamespace = "http://pro.ctlok.com/CalculaterService/")

用於通知 Java EE 容器識別這個 Class 是一個 Web Service

  • name: 是在 Application Server 上的名稱
  • serviceName: 是在 URLWSDL 上顯示的名稱
  • portName: 是在 WSDL 上顯示的名稱
  • targetNamespace : 是在 WSDL 上顯示的命名空間,令 XML 內的名稱不會相同

@WebMethod

是指定這個 MethodWeb Service 的其中一個 Function

CalculaterService 亦可以是一個 EJB Session Bean:

package sample.ws;

@Local
public interface CalculaterService{

public int add(int i, int k);
public int subtract(int i, int k);

}

package sample.ws;

import javax.jws.WebMethod;
import javax.jws.WebService;

@Stateless
@WebService(
name = "CalculaterService",
serviceName = "CalculaterService",
portName = "CalculaterServicePort",
targetNamespace = "http://pro.ctlok.com/CalculaterService/")
public class CalculaterServiceBean implements CalculaterService{

@WebMethod
public int add(int i, int k) {
return i + k;
}

@WebMethod
public int subtract(int i, int k) {
return i - k;
}

}

放上 Application Server 後使用瀏覽器開啟: http://localhost/CalculaterService?wsdl 會顯示出以下的 WSDL
<?xml version="1.0" encoding="UTF-8"?>
<definitions name="CalculaterService" targetNamespace="http://pro.ctlok.com/CalculaterService/"
xmlns="http://schemas.xmlsoap.org/wsdl/" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:tns="http://pro.ctlok.com/CalculaterService/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/">

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

<message name="addResponse">
<part name="parameters" element="tns:addResponse">
</part>
</message>

<message name="add">
<part name="parameters" element="tns:add">
</part>
</message>

<message name="subtractResponse">
<part name="parameters" element="tns:subtractResponse">
</part>
</message>

<message name="subtract">
<part name="parameters" element="tns:subtract">
</part>
</message>

<portType name="CalculaterService">
<operation name="add">
<input message="tns:add">
</input>
<output message="tns:addResponse">
</output>
</operation>

<operation name="subtract">
<input message="tns:subtract">
</input>
<output message="tns:subtractResponse">
</output>
</operation>
</portType>

<binding name="CalculaterServicePortBinding" type="tns:CalculaterService">
<soap:binding style="document"
transport="http://schemas.xmlsoap.org/soap/http" />
<operation name="add">
<soap:operation soapAction="" />
<input>
<soap:body use="literal" />
</input>
<output>
<soap:body use="literal" />
</output>
</operation>
<operation name="subtract">
<soap:operation soapAction="" />
<input>
<soap:body use="literal" />
</input>
<output>
<soap:body use="literal" />
</output>
</operation>
</binding>

<service name="CalculaterService">
<port name="CalculaterServicePort" binding="tns:CalculaterServicePortBinding">
<soap:address
location="http://localhost/CalculaterService" />
</port>
</service>
</definitions>

Application Server 除了自動產生出 WSDL 外,亦會產生出 XML Schema

使用瀏覽器開啟: http://localhost/CalculaterService/CalculaterService_schema1.xsd 會顯示出以下的 XML Schema:
<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:tns="http://pro.ctlok.com/CalculaterService/" version="1.0"
targetNamespace="http://pro.ctlok.com/CalculaterService/">

<xs:element name="add" type="tns:add"></xs:element>
<xs:element name="addResponse" type="tns:addResponse"></xs:element>
<xs:element name="subtract" type="tns:subtract"></xs:element>
<xs:element name="subtractResponse" type="tns:subtractResponse"></xs:element>

<xs:complexType name="add">
<xs:sequence>
<xs:element name="arg0" type="xs:int"></xs:element>
<xs:element name="arg1" type="xs:int"></xs:element>
</xs:sequence>
</xs:complexType>

<xs:complexType name="addResponse">
<xs:sequence>
<xs:element name="return" type="xs:int"></xs:element>
</xs:sequence>
</xs:complexType>

<xs:complexType name="subtract">
<xs:sequence>
<xs:element name="arg0" type="xs:int"></xs:element>
<xs:element name="arg1" type="xs:int"></xs:element>
</xs:sequence>
</xs:complexType>

<xs:complexType name="subtractResponse">
<xs:sequence>
<xs:element name="return" type="xs:int"></xs:element>
</xs:sequence>
</xs:complexType>
</xs:schema>

看到這是資料即是成功部署到 Web Service,接下來使用 soapUI 來模擬 Web Service Client

soapUI 下載地址: http://sourceforge.net/projects/soapui/files/

請選擇安裝程式去下載,完成安裝後打開 soapUI:
  1. File -> New soapUI Project
  2. Project name 輸入 localhost 後按 OK
  3. Project 上按滑鼠加鍵 -> Add WSDL
  4. WSDL Location 輸入 http://localhost/CalculaterService/CalculaterService.wsdl

現在應該看得到 2 個 Method 名稱,分別為 addsubtract
打開 addRequest 1 左邊顯示以下資料:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:ws="http://pro.ctlok.com/CalculaterService/">
<soapenv:Header />
<soapenv:Body>
<ws:add>
<arg0>?</arg0>
<arg1>?</arg1>
</ws:add>
</soapenv:Body>
</soapenv:Envelope>

改為:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:ws="http://pro.ctlok.com/CalculaterService/">
<soapenv:Header />
<soapenv:Body>
<ws:add>
<arg0>1</arg0>
<arg1>2</arg1>
</ws:add>
</soapenv:Body>
</soapenv:Envelope>

然後傳送出去,右邊會顯示出以下 XML 即代表成功:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Body>
<dlwmin:addResponse xmlns:dlwmin="http://pro.ctlok.com/CalculaterService/">
<return>3</return>
</dlwmin:addResponse>
</soapenv:Body>
</soapenv:Envelope>

其實在 Web ServiceFunction 不一定要傳入或返回基本型態 (short, int, long, float, double, char, byte, boolean),可以傳入或返回 Object (物件),例如:

Object Class:
package sample.ws;

public class Info{

private String firstName;
private String lastName;

public String getFirstName(){
return firstName;
}

public String getLastName(){
return lastName;
}

public void setFirstName(String firstName){
this.firstName = firstName;
}

public void setLastName(String lastName){
this.lastName = lastName;
}

}

Web Service:
@WebMethod
public String sayHello(Info info) {
return "Hello! " + info.getFirstName()
+ " " + info.getLastName();
}

@WebMethod
public Info getInfo(int id){
return findInfo(id);
}

有時間會再講解 Top Down 的做法。

相關書籍: Java Web Services: Up and RunningJava Soa CookbookBuilding Web Services with Java: Making Sense of XML, SOAP, WSDL, and UDDI (2nd Edition)

Seam 2 in Websphere 7 EJB JNDI Setting

| Comments

SeamJBoss Application Server 上將 EJB session bean 設為 Seam Component 根本不需要特地去設定 JNDISeam 自然地找到相對應的 Session bean

但在 Websphere 7 Application Server 上就不是這回事了,需要自行設定 JNDISeam 找得到 (將 EJB session bean 設為 Seam Component 才需要更改 JNDI,純 EJB session bean 不需要作出任何更改)

這篇文章只使用我認為最好的方法。

首先到 web.xml 加上以下設定:

<ejb-local-ref>
<ejb-ref-name>ejblocal:EjbSynchronizations</ejb-ref-name>
<ejb-ref-type>Session</ejb-ref-type>
<local-home></local-home>
<local>org.jboss.seam.transaction.LocalEjbSynchronizations</local>
</ejb-local-ref>

然後在 WEB-INF/classes 加入一個 seam-jndi.prpperties:
com.ibm.websphere.naming.hostname.normalizer=com.ibm.ws.naming.util.DefaultHostnameNormalizer
java.naming.factory.initial=com.ibm.websphere.naming.WsnInitialContextFactory
com.ibm.websphere.naming.name.syntax=jndi
com.ibm.websphere.naming.namespace.connection=lazy
com.ibm.ws.naming.ldap.ldapinitctxfactory=com.sun.jndi.ldap.LdapCtxFactory
com.ibm.websphere.naming.jndicache.cacheobject=populated
com.ibm.websphere.naming.namespaceroot=defaultroot
com.ibm.ws.naming.wsn.factory.initial=com.ibm.ws.naming.util.WsnInitCtxFactory
com.ibm.websphere.naming.jndicache.maxcachelife=0
com.ibm.websphere.naming.jndicache.maxentrylife=0
com.ibm.websphere.naming.jndicache.cachename=providerURL
java.naming.provider.url=corbaloc:rir:/NameServiceServerRoot
java.naming.factory.url.pkgs=com.ibm.ws.runtime\:com.ibm.ws.naming

再將 WEB-INF/components.xml 內的 jndi-pattern 更改為:
<core:init 
jndi-pattern="ejblocal:#{ejbName}"
debug="false" />

最後就是設定 WebsphereEJB session bean JNDI,在 EJB Project 內的 META-INF 加入 ibm-ejb-jar-bnd.xml:
<?xml version="1.0" encoding="UTF-8"?>
<ejb-jar-bnd xmlns="http://websphere.ibm.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://websphere.ibm.com/xml/ns/javaee
http://websphere.ibm.com/xml/ns/javaee/ibm-ejb-jar-bnd_1_0.xsd"
version="1.0">

<session name="MyBean" simple-binding-name="MyBean" />

</ejb-jar-bnd>

simple-binding-name 就是 JNDI,這樣 Seam 會照著 components.xml 內的 jndi-pattern 去使用以下的 JNDI 找到 EJB session bean:
ejblocal:MyBean

如果 components.xml 內的 jndi-pattern 改變了,亦要去更改 simple-binding-name
例如:
<core:init 
jndi-pattern="ejblocal:myModule/#{ejbName}"
debug="false" />

simple-binding-name 亦要更改為:
<session name="MyBean" 
simple-binding-name="MyModule/myBean" />

相關書籍: Seam in ActionSeam Framework: Experience the Evolution of Java EE (2nd Edition)Pro IBM WebSphere Application Server 7 Internals (Books for Professionals by Professionals)

使用 Xstream 程式庫去深層複製 Java 物件

| Comments

在某些時候,為免改寫原本 Object 的數據,而需要複製一個一模一樣的 Object 出來,例如記下當時的 Object 狀態,然後備份等等。如果使用 GetterSetter 一個一個數據去複製,一定會大大增加程式碼行數,亦只能對已知的 Object 去複製。當這個 Object 新增一項數據時,便要再重寫複製方法,所以這方法是很沒有效率的。

Xstream 原本是 Java 操作 XML 的一個 Library,除此之外亦可以使用 Xstream 去複製 Object (將 Java Object 轉換成 XML 再轉換為 Java Object)。

Xstream 官方網址: http://xstream.codehaus.org/

下載後將 .jar 加到 class path

假設現在有 ParentInfo 2 個 Object,這 2 個 Object 都是作為傳遞數據的 Object

Parent:
package xstream.sample;

public class Parent {

private String firstName;
private String lastName;

public String getFirstName() {
return firstName;
}

public void setFirstName(String firstName) {
this.firstName = firstName;
}

public String getLastName() {
return lastName;
}

public void setLastName(String lastName) {
this.lastName = lastName;
}

@Override
public String toString() {
return "Parent [firstName=" + firstName + ", lastName=" + lastName
+ "]";
}

}

Info:
package xstream.sample;

import java.util.LinkedList;
import java.util.List;

public class Info {

private String firstName;
private String lastName;
private int age;
private List<String> hobbies;
private Parent parent;

public String getFirstName() {
return firstName;
}

public void setFirstName(String firstName) {
this.firstName = firstName;
}

public String getLastName() {
return lastName;
}

public void setLastName(String lastName) {
this.lastName = lastName;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

public List<String> getHobbies() {
return hobbies;
}

public void setHobbies(List<String> hobbies) {
this.hobbies = hobbies;
}

public void addHobby(String hobby) {
if (hobbies == null)
hobbies = new LinkedList<String>();
hobbies.add(hobby);
}

public Parent getParent() {
return parent;
}

public void setParent(Parent parent) {
this.parent = parent;
}

@Override
public String toString() {
return "Info [firstName=" + firstName + ", lastName=" + lastName
+ ", age=" + age + ", hobbies=" + hobbies + ", parent="
+ parent + "]";
}

}

付責複製的 Function:

package xstream.sample;

import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.xml.DomDriver;

public class Main {

private static final XStream XSTREAM = new XStream(new DomDriver());

@SuppressWarnings("unchecked")
public static <T> T cloneObject(T src){
return (T) XSTREAM.fromXML(XSTREAM.toXML(src));
}

}

測試的程式:
package xstream.sample;

import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.xml.DomDriver;

public class Main {

private static final XStream XSTREAM = new XStream(new DomDriver());

public static void main(String[] args) {
//Create info object
Info info = new Info();
Parent parent = new Parent();

//Set data
info.setFirstName("Lawrence");
info.setFirstName("Cheung");
info.setAge(24);
info.addHobby("Sports");
info.addHobby("Reading");

parent.setFirstName("Tom");
parent.setLastName("Cheung");
info.setParent(parent);

//Print my info
System.out.println("Original object: " + info);

System.out.println("Now, we are going to clone info object");
System.out.println();
Info clonedInfo = cloneObject(info);

//Print cloned info
System.out.println("Cloned object: " + clonedInfo);
System.out.println();

//Modify original info
System.out.println("Change original info age to 25 and parent first name set to John.");
info.setAge(25);
info.getParent().setFirstName("John");

System.out.println();
System.out.println("Original object: " + info);
System.out.println("Cloned object: " + clonedInfo);
}

@SuppressWarnings("unchecked")
public static <T> T cloneObject(T src){
return (T) XSTREAM.fromXML(XSTREAM.toXML(src));
}

}


結果顯示如下:
Original object: Info [firstName=Cheung, lastName=null, age=24, hobbies=[Sports, Reading], parent=Parent [firstName=Tom, lastName=Cheung]]
Now, we are going to clone info object

Cloned object: Info [firstName=Cheung, lastName=null, age=24, hobbies=[Sports, Reading], parent=Parent [firstName=Tom, lastName=Cheung]]

Change original info age to 25 and parent first name set to John.

Original object: Info [firstName=Cheung, lastName=null, age=25, hobbies=[Sports, Reading], parent=Parent [firstName=John, lastName=Cheung]]
Cloned object: Info [firstName=Cheung, lastName=null, age=24, hobbies=[Sports, Reading], parent=Parent [firstName=Tom, lastName=Cheung]]

可以看出連 Parent 在內也一起複製了,就算更改了原本的 Info Object 也不會改動到複製出來的 Object,即是 2 個變數的 Pointer 不是指向同一個位置。

相關書籍: Head First Java, 2nd EditionEffective Java (2nd Edition)Sams Teach Yourself Java in 24 Hours (5th Edition)

使用 Apache Commons VFS 來操作 SFTP

| Comments

Java 使用 FTP 去上載檔案,下載檔案或去做其他操作也是很簡單,因為 Java API 已經包括了對 FTP 的支援。如果不是使用 FTP,而是使用 SFTP: SSH File Transfer Protocol,使用Java API 實作便有點麻煩了。

幸好 Apache Commons VFS 替我們實作了 SFTP 的操作,Apache Commons VFS 配合 JSch 使我們可以很簡單地使用 SSH 連接 Server 和上載檔案,下載檔案等等操作。

廢話不多說,首先要準備 3 個 Library:

下載好後將 .jar 加到 class path 令程式找到這些 Library

再來就是寫程式,為了方便好看,我會將所有程式碼放到 Main class 內。

第一步是製作一個 Function 去將 ProtocolHost NameUsernamePasswordServer 上的檔案位置連接起來 (Factory Method)。令到 Apache Commons VFS 能讀得懂。
public static String createConnectionString(String hostName,
String username, String password, String remoteFilePath) {
// result: "sftp://user:[email protected]/resume.pdf
return "sftp://" + username + ":" + password + "@" + hostName + "/"
+ remoteFilePath;
}

第二步是製作一個 Function 去製造出一個 SFTP 預設的設定 (Factory Method):
public static FileSystemOptions createDefaultOptions()
throws FileSystemException {
// Create SFTP options
FileSystemOptions opts = new FileSystemOptions();

// SSH Key checking
SftpFileSystemConfigBuilder.getInstance().setStrictHostKeyChecking(
opts, "no");

// Root directory set to user home
SftpFileSystemConfigBuilder.getInstance().setUserDirIsRoot(opts, true);

// Timeout is count by Milliseconds
SftpFileSystemConfigBuilder.getInstance().setTimeout(opts, 10000);

return opts;
}

第三步是製作一個上載檔案的 Function:
public static void upload(String hostName, String username,
String password, String localFilePath, String remoteFilePath) {

File f = new File(localFilePath);
if (!f.exists())
throw new RuntimeException("Error. Local file not found");

StandardFileSystemManager manager = new StandardFileSystemManager();

try {
manager.init();

// Create local file object
FileObject localFile = manager.resolveFile(f.getAbsolutePath());

// Create remote file object
FileObject remoteFile = manager.resolveFile(
createConnectionString(hostName, username, password,
remoteFilePath), createDefaultOptions());

// Copy local file to sftp server
remoteFile.copyFrom(localFile, Selectors.SELECT_SELF);

System.out.println("File upload success");
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
manager.close();
}
}

在這裡可以測試一下上載檔案的 Function 是否沒有問題:
/*
* args[0]: hostName
* args[1]: username
* args[2]: password
* args[3]:localFilePath
* args[4]: remoteFilePath
*/
public static void main(String[] args) {
if (args.length < 5)
throw new RuntimeException(
"Error. Please enter "
+ "args[0]: hostName, args[1]: username, args[2]: password, "
+ "args[3]: localFilePath, args[4]: remoteFilePath.");

upload(args[0], args[1], args[2], args[3], args[4]);
}

成功後 Console 會打印出 File upload success

再來是加下載檔案的 Function:
public static void download(String hostName, String username,
String password, String localFilePath, String remoteFilePath) {

StandardFileSystemManager manager = new StandardFileSystemManager();

try {
manager.init();

String downloadFilePath = localFilePath.substring(0,
localFilePath.lastIndexOf("."))
+ "_downlaod_from_sftp"
+ localFilePath.substring(localFilePath.lastIndexOf("."),
localFilePath.length());

// Create local file object
FileObject localFile = manager.resolveFile(downloadFilePath);

// Create remote file object
FileObject remoteFile = manager.resolveFile(
createConnectionString(hostName, username, password,
remoteFilePath), createDefaultOptions());

// Copy local file to sftp server
localFile.copyFrom(remoteFile, Selectors.SELECT_SELF);

System.out.println("File download success");
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
manager.close();
}
}

測試檔案是否存在的 Function:
public static boolean exist(String hostName, String username,
String password, String remoteFilePath) {
StandardFileSystemManager manager = new StandardFileSystemManager();

try {
manager.init();

// Create remote object
FileObject remoteFile = manager.resolveFile(
createConnectionString(hostName, username, password,
remoteFilePath), createDefaultOptions());

System.out.println("File exist: " + remoteFile.exists());

return remoteFile.exists();
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
manager.close();
}
}

刪除 Server 檔案的 Function:
public static void delete(String hostName, String username,
String password, String remoteFilePath) {
StandardFileSystemManager manager = new StandardFileSystemManager();

try {
manager.init();

// Create remote object
FileObject remoteFile = manager.resolveFile(
createConnectionString(hostName, username, password,
remoteFilePath), createDefaultOptions());

if (remoteFile.exists()) {
remoteFile.delete();
System.out.println("Delete remote file success");
}
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
manager.close();
}
}

完整的程式:
package sftp.sample;

import java.io.File;

import org.apache.commons.vfs.FileObject;
import org.apache.commons.vfs.FileSystemException;
import org.apache.commons.vfs.FileSystemOptions;
import org.apache.commons.vfs.Selectors;
import org.apache.commons.vfs.impl.StandardFileSystemManager;
import org.apache.commons.vfs.provider.sftp.SftpFileSystemConfigBuilder;

public class Main {

/*
* args[0]: hostName
* args[1]: username
* args[2]: password
* args[3]: localFilePath
* args[4]: remoteFilePath
*/
public static void main(String[] args) {
if (args.length < 5)
throw new RuntimeException(
"Error. Please enter "
+ "args[0]: hostName, args[1]: username, args[2]: password, "
+ "args[3]: localFilePath, args[4]: remoteFilePath.");

upload(args[0], args[1], args[2], args[3], args[4]);
exist(args[0], args[1], args[2], args[4]);
download(args[0], args[1], args[2], args[3], args[4]);
delete(args[0], args[1], args[2], args[4]);
}

public static void upload(String hostName, String username,
String password, String localFilePath, String remoteFilePath) {

File f = new File(localFilePath);
if (!f.exists())
throw new RuntimeException("Error. Local file not found");

StandardFileSystemManager manager = new StandardFileSystemManager();

try {
manager.init();

// Create local file object
FileObject localFile = manager.resolveFile(f.getAbsolutePath());

// Create remote file object
FileObject remoteFile = manager.resolveFile(
createConnectionString(hostName, username, password,
remoteFilePath), createDefaultOptions());

// Copy local file to sftp server
remoteFile.copyFrom(localFile, Selectors.SELECT_SELF);

System.out.println("File upload success");
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
manager.close();
}
}

public static void download(String hostName, String username,
String password, String localFilePath, String remoteFilePath) {

StandardFileSystemManager manager = new StandardFileSystemManager();

try {
manager.init();

String downloadFilePath = localFilePath.substring(0,
localFilePath.lastIndexOf("."))
+ "_downlaod_from_sftp"
+ localFilePath.substring(localFilePath.lastIndexOf("."),
localFilePath.length());

// Create local file object
FileObject localFile = manager.resolveFile(downloadFilePath);

// Create remote file object
FileObject remoteFile = manager.resolveFile(
createConnectionString(hostName, username, password,
remoteFilePath), createDefaultOptions());

// Copy local file to sftp server
localFile.copyFrom(remoteFile, Selectors.SELECT_SELF);

System.out.println("File download success");
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
manager.close();
}
}

public static void delete(String hostName, String username,
String password, String remoteFilePath) {
StandardFileSystemManager manager = new StandardFileSystemManager();

try {
manager.init();

// Create remote object
FileObject remoteFile = manager.resolveFile(
createConnectionString(hostName, username, password,
remoteFilePath), createDefaultOptions());

if (remoteFile.exists()) {
remoteFile.delete();
System.out.println("Delete remote file success");
}
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
manager.close();
}
}

public static boolean exist(String hostName, String username,
String password, String remoteFilePath) {
StandardFileSystemManager manager = new StandardFileSystemManager();

try {
manager.init();

// Create remote object
FileObject remoteFile = manager.resolveFile(
createConnectionString(hostName, username, password,
remoteFilePath), createDefaultOptions());

System.out.println("File exist: " + remoteFile.exists());

return remoteFile.exists();
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
manager.close();
}
}

public static String createConnectionString(String hostName,
String username, String password, String remoteFilePath) {
// result: "sftp://user:[email protected]/resume.pdf
return "sftp://" + username + ":" + password + "@" + hostName + "/"
+ remoteFilePath;
}

public static FileSystemOptions createDefaultOptions()
throws FileSystemException {
// Create SFTP options
FileSystemOptions opts = new FileSystemOptions();

// SSH Key checking
SftpFileSystemConfigBuilder.getInstance().setStrictHostKeyChecking(
opts, "no");

// Root directory set to user home
SftpFileSystemConfigBuilder.getInstance().setUserDirIsRoot(opts, true);

// Timeout is count by Milliseconds
SftpFileSystemConfigBuilder.getInstance().setTimeout(opts, 10000);

return opts;
}

}

相關書籍: Jakarta Commons CookbookPro Jakarta CommonsSSH, The Secure Shell: The Definitive Guide

Eclipse Web Development Plugin - Aptana Studio

| Comments

介紹一款非常好用的 Web 開發工具 - Aptana Studio。(其實我用了很久,以為已經發過文章介紹…)

這款工具對編寫 HTMLCSSJavascript 都非常有幫助,而且對 Javascript Framework (例如 jQueryPrototype 等等) 有題示功能(需要再安裝 Plugin),比起 Eclipse 預設的工具好用多了。

除了 HTMLCSSJavascript 外。Aptana Studio 亦支援 RubyPythonPHP 的開發 (PHP 支援已經停止更新)。

官方網址: http://www.aptana.com/

下載頁面: http://www.aptana.com/products/studio2/download

進入下載頁面後只需選擇 Eclipse Plugin,然後按 Download,就會出現 EclipseUpdate Site URL

如果不喜歡作為 Eclipse Plugin 去使用,亦可以下載安裝版本去使用 (我覺得安裝版沒有那麼方便…)。

亦可以試用開發版本的 Aptana Studio 3。現在的版本已經很穩定,我用的時候亦沒發現什麼 Bug 了。
下載頁面: http://www.aptana.com/products/studio3/download

相關書籍: Dynamic Web Programming: A Beginner's Guide (Beginner's Guide (Osborne Mcgraw Hill))Pro Javascript RIA Techniques: Best Practices, Performance and PresentationAptana RadRails: An IDE for Rails Development: A comprehensive guide to using RadRails to develop your Ruby on Rails projects in a professional and productive manner