Using Windwos Azure Storage Services with Java

在Windows Azure平台中,應用程式除了可選擇完整的資料庫平台SQL Azure儲存資料外,也可選擇較為簡單的Windows Azure Storage Services來儲存資料,

Windows Azure Storage Services共提供三種儲存體

Using Windwos Azure Storage Services with Java

 

/黃忠成

 

 

雲端儲存體

 

   在Windows Azure平台中,應用程式除了可選擇完整的資料庫平台SQL Azure儲存資料外,也可選擇較為簡單的Windows Azure Storage Services來儲存資料,

Windows Azure Storage Services共提供三種儲存體:

 

  • Table Storage Service儲存row/column(Table)類型的資料

 

  • Blob Service儲存大型的二進位資料

 

  • Queue Service佇列

 

接下來本文將探討如何於Java/Tomcat中與這三種儲存體互動。

 

Windows Azure SDK Eclipse Plug-In中的Storage Explorer

 

  Windows Azure SDK的Eclipse Plug-In提供了一個相當方便的Storage Explorer工具,可以讓開發者於Eclipse直接連到Windows Azure Storage Services來

查看或變更資料,開Storage Explorer的方法很簡單,請點選Windows|Show Views|Other選單,就可開出圖1的畫面。

圖1

圖2

點選OK按鈕後,Storage Explorer就出現在右方,開發者可依據喜好來移動她。

圖3

進行下一步動作前,請記得確認Storage Emulator是在執行中的,如果沒有,可透過程式集來啟動。

圖4

圖5

一切正常的話,就可以點選Open按鈕來連結到Storage Emulator。

圖6

開發者可透過Storage Explorer來取得或是管理Table、Blob或是Queue等儲存體中的資料。

圖7

圖8

圖9

圖10

圖11

當然,開發者也可以直接連結到真正位於Windows Azure平台中的Storage Services,只要點選Windows|Perferences選單來添加Account即可。

圖12

圖13 

圖14

 

使用Windows Azure SDK for Java Client Library

 

  要在應用程式中存取Windows Azure Storage Services,開發者還需要至以下網址下載Windows Azure Libraries for Java。

http://www.windowsazure.com/en-us/develop/java/java-home/download-the-windows-azure-sdk-for-java/

圖15

紅框框起來的部分都要一一下載,慣用Maven的開發者也可於此網頁中找到取得的方法(這個較為快速且簡單)。

在取得這個Library後,就可以著手進行開發,本文以一個Web Project為例,第一步驟就是要引用Windows Azure Libraries for Java中的所有.jar。

圖16

圖17

圖18

 

使用Table Storage Service

 

  Table Storage Servcie可以用來儲存Table型態的資料,是一種完全針對雲端運算環境所設計的儲存體。在Java中使用的方式很簡單,先新增一個StorageClient.java。


package com.azure.stroage.demo;


import java.net.URISyntaxException;

import java.security.InvalidKeyException;


import com.microsoft.windowsazure.serviceruntime.RoleEnvironment;

import com.microsoft.windowsazure.services.blob.client.CloudBlobClient;

import com.microsoft.windowsazure.services.core.storage.CloudStorageAccount;

import com.microsoft.windowsazure.services.queue.client.CloudQueueClient;

import com.microsoft.windowsazure.services.table.client.CloudTableClient;


public class StorageClient {

          

           //for running outside worker role.

           static final String storageConnectionString =

                         "UseDevelopmentStorage=true;" +

                         "AccountName=devstoreaccount1;" +

                         "AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==";

          

           private static CloudStorageAccount Account = null;

           private static CloudTableClient _tableClient = null;

           private static CloudBlobClient _blobClient = null;

           private static CloudQueueClient _queueClient = null;

          

           public static CloudTableClient getCloudTableClient() {

                     if(_tableClient == null)

                                _tableClient = Account.createCloudTableClient();

                     return _tableClient;

           }

          

           public static CloudBlobClient getCloudBlobClient() {

                     if(_blobClient == null)

                                _blobClient = Account.createCloudBlobClient();

                     return _blobClient;

           }

          

           public static CloudQueueClient getCloudQueueClient() {

                     if(_queueClient == null)

                                _queueClient = Account.createCloudQueueClient();

                     return _queueClient;

           }

          

           static {

                     try {

                                Account = CloudStorageAccount.parse(RoleEnvironment.getConfigurationSettings().get("DataConnectionString"));

                                //Account = CloudStorageAccount.parse(storageConnectionString);

                     } catch (InvalidKeyException e) {

                                // TODO Auto-generated catch block

                                e.printStackTrace();

                     } catch (URISyntaxException e) {

                                // TODO Auto-generated catch block

                                e.printStackTrace();

                     }

                    

           }

}

StorageClient類別負責提供Table Storage、Blob、Queue的初始化程式碼,這裡有一段需特別注意。


Account = CloudStorageAccount.parse(RoleEnvironment.getConfigurationSettings().get("DataConnectionString"));

這一段是透過Windows Azure Worker Role的組態檔來取得Storage Services的連線參數,但這只能夠在Windows Azure Worker Role環境中執行,如果你想直接執行Web Project的話(比較快),需修改成下面這樣子。


Account = CloudStorageAccount.parse(storageConnectionString);

接著新增一個Customer.java,其是對應成Table Storage中的一筆資料。

 


package com.azure.stroage.demo;

import com.microsoft.windowsazure.services.table.client.TableServiceEntity;

public class Customer extends TableServiceEntity {
	
	int _age;
    
    public String getName() {
    	return this.partitionKey;
    }
    
    public void setName(String name) {
    	this.partitionKey = name;
    }
    
    public String getCustomerID() {
    	return this.rowKey;
    }
    
    public void setCustomerID(String id) {
    	this.rowKey = id;
    }
    
    public int getAge() {
    	return _age;
    }
    
    public void setAge(int age) {
    	_age = age;
    }
    
    public Customer(String id, String name) {
        setName(name);
        setCustomerID(id);
    }
    
    public Customer() {
    	
    }
}

為了方便使用,我把操作Table的程式碼封裝在Customers類別中。

 


package com.azure.stroage.demo;


import com.microsoft.windowsazure.services.core.storage.StorageException;

import com.microsoft.windowsazure.services.table.client.TableBatchOperation;

import com.microsoft.windowsazure.services.table.client.TableConstants;

import com.microsoft.windowsazure.services.table.client.TableOperation;

import com.microsoft.windowsazure.services.table.client.TableQuery;

import com.microsoft.windowsazure.services.table.client.TableQuery.Operators;

import com.microsoft.windowsazure.services.table.client.TableQuery.QueryComparisons;


public class Customers {

          

           public static final void createIfNotExists() throws StorageException {

                     StorageClient.getCloudTableClient().createTableIfNotExists("Customers");

           }

          

           public static final void insert(Customer entity) throws StorageException {

                     TableOperation operation = TableOperation.insert(entity);

                     StorageClient.getCloudTableClient().execute("Customers", operation);

           }

          

           public static final void insertEntitys(Customer[] entitys) throws StorageException {

                     TableBatchOperation batch = new TableBatchOperation();

                     for(Customer c : entitys)

                                batch.insert(c);

                     StorageClient.getCloudTableClient().execute("Customers", batch);

           }

          

           public static final void delete(Customer entity) throws StorageException {

                     TableOperation operation = TableOperation.delete(entity);

                     StorageClient.getCloudTableClient().execute("Customers", operation);

           }

          

           public static final void update(Customer entity) throws StorageException {

                     TableOperation operation = TableOperation.replace(entity);

                     StorageClient.getCloudTableClient().execute("Customers", operation);

           }

          

           public static final Iterable retrieveAll() {

                     TableQuery myQuery = TableQuery.from("Customers", Customer.class)

                                     .take(999);

                     return StorageClient.getCloudTableClient().execute(myQuery);

           }

          

           public static final Iterable retrieveWithName(String name) {

                     String queryString = TableQuery.generateFilterCondition(TableConstants.PARTITION_KEY, QueryComparisons.EQUAL, name);

                     TableQuery myQuery = TableQuery.from("Customers", Customer.class).where(queryString);

                     return StorageClient.getCloudTableClient().execute(myQuery);

           }

          

           public static final Iterable retrieveWithAge(int age) {

                     String queryString = TableQuery.generateFilterCondition("Age", QueryComparisons.EQUAL, age);

                     TableQuery myQuery = TableQuery.from("Customers", Customer.class).where(queryString);

                     return StorageClient.getCloudTableClient().execute(myQuery);

           }

          

           public static final Customer retrieveWithID(String id) {

                     String queryString = TableQuery.generateFilterCondition(TableConstants.ROW_KEY, QueryComparisons.EQUAL, id);

                     TableQuery myQuery = TableQuery.from("Customers", Customer.class).where(queryString);

                     for(Customer c : StorageClient.getCloudTableClient().execute(myQuery))

                                return c;

                     return null;

           }

          

           public static final Iterable retrieveWithNameAndAge(String name,int age) {

                     String queryString = TableQuery.generateFilterCondition(TableConstants.PARTITION_KEY, QueryComparisons.EQUAL, name);

                     queryString = TableQuery.combineFilters(queryString, Operators.AND,

                                           TableQuery.generateFilterCondition("Age", QueryComparisons.EQUAL, age));

                     TableQuery myQuery = TableQuery.from("Customers", Customer.class).where(queryString);

                     return StorageClient.getCloudTableClient().execute(myQuery);

           }

}

程式碼應該不難理解,只是新增刪動作而已。完成後即可建立.jsp頁面來測試。

 


<%@pagelanguage="java"contentType="text/html; charset=BIG5"

    pageEncoding="BIG5"%>

<%@pageimport="com.azure.stroage.demo.*"%>

<%@pageimport="com.microsoft.windowsazure.services.table.client.*"%> 

<%@pageimport="com.microsoft.windowsazure.services.core.storage.*"%>

<!DOCTYPEhtmlPUBLIC"-//W3C//DTD HTML 4.01 Transitional//EN""http://www.w3.org/TR/html4/loose.dtd">

<html>

<head>

<metahttp-equiv="Content-Type"content="text/html; charset=BIG5">

<title>Customers</title>

</head>

<body>

<%

   if(request.getParameter("btnGenerate") != null) {

       Customers.createIfNotExists();

       Customer c1 = newCustomer("A00001","Jeffery");

       c1.setAge(18);

      

       Customer c2 = newCustomer("A00002","Tom");

       c2.setAge(28);

      

       Customers.insert(c1);

       Customers.insert(c2);

       response.sendRedirect("Customers.jsp");

   }

%>

<formname="form1"method="POST">           

   <table>

      <tr>

         <td>Customer ID</td>

         <td>Name</td>

         <td>Age</td>

      </tr>

      <%

         Customers.createIfNotExists();

         for(Customer c : Customers.retrieveAll()) {%>

         <tr>

         <td><%=c.getCustomerID()%></td>

         <td><%=c.getName()%></td>

         <td><%=c.getAge()%></td>

         </tr>

        <%} %>

        <tr>

        <tdcolspan="3">

          <inputtype="submit"name="btnGenerate"value="Generate"/>

        </td>

        </tr>

   </table>

</form>

</body>

</html>

圖19為執行畫面。

圖19

 

使用Blob Service

 

   當瞭解Table Storage Service的大概用法後,Blob就不難了。

 


package com.azure.stroage.demo;


import java.io.ByteArrayInputStream;

import java.io.IOException;

import java.net.URISyntaxException;


import com.microsoft.windowsazure.services.blob.client.BlobContainerPermissions;

import com.microsoft.windowsazure.services.blob.client.BlobContainerPublicAccessType;

import com.microsoft.windowsazure.services.blob.client.CloudBlobContainer;

import com.microsoft.windowsazure.services.blob.client.CloudBlockBlob;

import com.microsoft.windowsazure.services.blob.client.ListBlobItem;

import com.microsoft.windowsazure.services.core.storage.StorageException;


public class MyPictureBlob {

           private static CloudBlobContainer _container = null;

          

           public static final void insert(String name, byte[] data) throws URISyntaxException, StorageException, IOException {

                     CloudBlockBlob blob = _container.getBlockBlobReference(name);

                     ByteArrayInputStream stream = new ByteArrayInputStream(data);

                     blob.upload(stream, data.length);

           }

          

           public static final Iterable getBlobs() {

                     return _container.listBlobs();

           }

          

           public static final void delete(String name) throws URISyntaxException, StorageException {

                     CloudBlockBlob blob = _container.getBlockBlobReference(name);

                     blob.delete();

           }

          

           static {

                     try {

                                _container = StorageClient.getCloudBlobClient().getContainerReference("pics");

                                _container.createIfNotExist();                        

                                BlobContainerPermissions ps = new BlobContainerPermissions();

                                ps.setPublicAccess(BlobContainerPublicAccessType.CONTAINER);

                                _container.uploadPermissions(ps);

                     } catch (URISyntaxException | StorageException e1) {

                                // TODO Auto-generated catch block

                                e1.printStackTrace();

                     }

           }

}

在本文中,測試Blob的是一個供使用者瀏覽及上傳圖片的頁面。

 


<%@page language="java"contentType="text/html; charset=BIG5"

    pageEncoding="BIG5"%>

<%@page import="com.azure.stroage.demo.*"%>

<%@page import="com.microsoft.windowsazure.services.blob.client.*"%>

<!DOCTYPE htmlPUBLIC"-//W3C//DTD HTML 4.01 Transitional//EN""http://www.w3.org/TR/html4/loose.dtd">

<html>

<head>

<title>Blobs</title>

<meta http-equiv="Content-Type" content="text/html; charset=big5"/>

</head>

<body>

<form name="upload" enctype="multipart/form-data" method="post" action="uploadHandler.jsp">

<p>上傳圖片:<input type="file" name="file" size="20 "maxlength="20"/></p>

<p><input type="submit"value="上傳"/><input type="reset"value="清除"/></p>

</form>

<table>

  <%

     for(ListBlobItem item : MyPictureBlob.getBlobs()) { %>

     <tr><td><img src='<%=item.getUri().toString() %>'/></td></tr>         

  <%} %>

</table>

</body>

</html>

下面是uploadHandler.jsp的程式碼。

 


<%@ page language="java" %>
<%@ page import="java.io.*"%>
<%@ page import="java.util.Iterator"%>
<%@ page import="java.util.List"%>
<%@ page import="com.azure.stroage.demo.*"%>
<%@ page import="com.microsoft.windowsazure.services.blob.client.*" %>
<%
     String contentType = request.getContentType();        
     if ((contentType != null) && (contentType.indexOf("multipart/form-data") >= 0))  {                
    	 DataInputStream in = new DataInputStream(request.getInputStream());  
    	 int formDataLength = request.getContentLength();
    	 byte dataBytes[] = new byte[formDataLength];
    	 int byteRead = 0;
    	 int totalBytesRead = 0;
    	 while (totalBytesRead < formDataLength) {
    		 byteRead = in.read(dataBytes, totalBytesRead, formDataLength);
    		 totalBytesRead += byteRead;
         }                                        
    	 String file = new String(dataBytes);                
    	 String saveFile = file.substring(file.indexOf("filename=\"") + 10);                
    	 saveFile = saveFile.substring(0, saveFile.indexOf("\n"));                
    	 saveFile = saveFile.substring(saveFile.lastIndexOf("\\") + 1,saveFile.indexOf("\""));                
    	 int lastIndex = contentType.lastIndexOf("=");                
    	 String boundary = contentType.substring(lastIndex + 1,contentType.length());                
    	 int pos;
    	 pos = file.indexOf("filename=\"");                
    	 pos = file.indexOf("\n", pos) + 1;                
    	 pos = file.indexOf("\n", pos) + 1;                
    	 pos = file.indexOf("\n", pos) + 1;                
    	 int boundaryLocation = file.indexOf(boundary, pos) - 4;                
    	 int startPos = ((file.substring(0, pos)).getBytes()).length;                
    	 int endPos = ((file.substring(0, boundaryLocation)).getBytes()).length;
    	 ByteArrayOutputStream stream = new ByteArrayOutputStream();
    	 stream.write(dataBytes, startPos, dataBytes.length - startPos - boundary.length() - 8);    	 
    	 MyPictureBlob.insert(saveFile, stream.toByteArray());
    	 stream.close();
    	 response.sendRedirect("Blobs.jsp");
     }
%>

圖20

 

使用Queue Service

 

  Queue Service就更簡單了。

 


package com.azure.stroage.demo;

import java.net.URISyntaxException;

import com.microsoft.windowsazure.services.core.storage.StorageException;
import com.microsoft.windowsazure.services.queue.client.CloudQueue;
import com.microsoft.windowsazure.services.queue.client.CloudQueueMessage;

public class MyQueue {
	
	private static CloudQueue _queue =  null;
	
	public static void enqueue(String message) throws URISyntaxException, StorageException {		
		_queue.addMessage(new CloudQueueMessage(message));
	}
	
	public static String dequeue() throws URISyntaxException, StorageException {
		return _queue.peekMessage().getMessageContentAsString();
	}
	
	static {
		try {
			_queue = StorageClient.getCloudQueueClient().getQueueReference("messages");
			_queue.createIfNotExist();
		} catch (URISyntaxException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (StorageException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}		
	}
}

下面是測試的頁面。

 


<%@ page language="java" contentType="text/html; charset=BIG5"
    pageEncoding="BIG5"%>
<%@ page import="com.azure.stroage.demo.*" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=BIG5">
<title>Insert title here</title>
</head>
<body>
   <%
      MyQueue.enqueue("Hello World");
   %>
   <%= MyQueue.dequeue() %>
</body>
</html>

 

 

佈署到真正的Windows Azure環境

 

   第一個步驟就是修改StorageClient.java,讓他由Worker Role組態中取得Storage Services的連線參數。

 


Account = CloudStorageAccount.parse(RoleEnvironment.getConfigurationSettings().get("DataConnectionString"));

接著修改Azure Project中的ServiceDefinitions.csdef檔案。

 


<?xml version="1.0" encoding="utf-8" standalone="no"?>
<ServiceDefinition xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceDefinition" name="WindowsAzureProject">
  <WorkerRole name="WorkerRole1" vmsize="Small">
    <Startup>
      <!-- Sample startup task calling startup.cmd from the role's approot folder -->
      <Task commandLine="util/.start.cmd startup.cmd" executionContext="elevated" taskType="simple">
        <Environment>
          <Variable name="_JAVA_OPTIONS" value="-agentlib:jdwp=transport=dt_socket,server=y,address=8090,suspend=n"/>
        </Environment>
      </Task>
    </Startup>
    <Runtime executionContext="elevated">
    	<EntryPoint>
	  		<!-- Sample entry point calling run.cmd from the role's approot folder -->
    		<ProgramEntryPoint commandLine="run.cmd" setReadyOnProcessStart="true"/>
    	</EntryPoint>
    </Runtime> 
    <Imports>
  	  <Import moduleName="RemoteAccess"/>
  	  <Import moduleName="RemoteForwarder"/>
    </Imports>
    <Endpoints>
      <InputEndpoint localPort="8080" name="http" port="80" protocol="tcp"/>
    <InputEndpoint localPort="8090" name="Debugging" port="8090" protocol="tcp"/>
    </Endpoints>
    <ConfigurationSettings>
      <Setting name="DataConnectionString"/>
    </ConfigurationSettings>    
  </WorkerRole>
</ServiceDefinition>

再修改ServiceDefinitions.cscfg檔案,加入連線參數。

 


<?xml version="1.0" encoding="utf-8" standalone="no"?>
<ServiceConfiguration xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceConfiguration" osFamily="2" osVersion="*" serviceName="WindowsAzureProject">
  <Role name="WorkerRole1">
    <Instances count="1"/>
    <ConfigurationSettings>
      <Setting name="Microsoft.WindowsAzure.Plugins.RemoteAccess.Enabled" value="true"/>
      <Setting name="Microsoft.WindowsAzure.Plugins.RemoteForwarder.Enabled" value="true"/>
      <!-- NOTE: replace the following settings with your own -->
      <Setting name="Microsoft.WindowsAzure.Plugins.RemoteAccess.AccountUsername" value="test"/>
      <Setting name="Microsoft.WindowsAzure.Plugins.RemoteAccess.AccountEncryptedPassword" value="......."/>
      <Setting name="Microsoft.WindowsAzure.Plugins.RemoteAccess.AccountExpiration" value="2039-12-31T23:59:59.0000000-08:00"/>      
      <!-- <Setting name="DataConnectionString" value="UseDevelopmentStorage=true"/>  -->
      <Setting name="DataConnectionString" value="DefaultEndpointsProtocol=http;AccountName=<your account name>;AccountKey=<your account key> "/>      
    </ConfigurationSettings>
    <Certificates>
      
      <Certificate name="Microsoft.WindowsAzure.Plugins.RemoteAccess.PasswordEncryption" thumbprint=".........." thumbprintAlgorithm="sha1"/>
    </Certificates>
  </Role>
</ServiceConfiguration>

最後進行Build All後,將產生的.cspkg、.cscfg上傳至Azure平台即可。