RotationShardShuffler is initialized with a random seed, then the seed will be increased by one (round-robin) on every certain action.
2015年5月26日 星期二
Elasticsearch Class Diagram for Shard Routing and Preference
RotationShardShuffler is initialized with a random seed, then the seed will be increased by one (round-robin) on every certain action.
2015年5月25日 星期一
2015年5月8日 星期五
2015年5月5日 星期二
Interview with LZ4 (Extremely Fast Compression algorithm)
Why is LZ4 so fast?
- Fast scan strategy
- xxHash (Extremely fast non-cryptographic hash algorithm)
- Multi-threading
- Reduced memory usage fits into Intel x86 L1 cache
- #define LZ4_MEMORY_USAGE 14 (default 16 KB, see lz4.h)
Reference:
- LZ4 - Improved performance
- Multi-threading compression available
- LZ4 explained
- Lucene source code to learn LZ4 compression algorithm in Lucene
Official site:
- LZ4 - Extremely fast compression
- LZ4 Java
- JNI (fastest)
- Pure Java
- Java uses sun.misc.Unsafe API
2015年5月4日 星期一
2015年4月23日 星期四
Performance tuning for Elasticsearch
Some important environment variables about performance tuning for Elasticsearch.
- Linux
- max_file_descriptors: 65536
- vm.max_map_count: 262144 (per process)
- elasticsearch.yml
- bootstrap.mlockall: true
- discovery.zen.minimum_master_nodes
- gateway.recover_after_nodes
- gateway.expected_nodes
- gateway.recover_after_time
- index.number_of_shards
- index.number_of_replicas
- index.refresh_interval
- indices.fielddata.cache.size: 50%
- indices.breaker.fielddata.limit: 50%
- Create index
- index.number_of_shards
- index.number_of_replicas
- index.refresh_interval: 30s
- #index.merge.policy.type: tiered
- #index.translog.flush_threshold_size
- #index.search.slowlog.threshold.query
- #index.search.slowlog.threshold.fetch
- #index.routing.allocation.include.box_type
- #indices.memory.index_buffer_size
- #indices.memory.min_index_buffer_size
- #indices.memory.min_shard_index_buffer_size
- #indices.ttl.interval
- Search time tips
- _optimize?max_num_segments=1 (less segments more efficiency)
- Index per Time Frame
- Faking Index per User with Aliases
- shard_size & size, by default, shard_size = size * shards
- Index Warmer (suffer refresh time)
- collect_mode: breadth_first
- Fielddata
- enable doc_values, it will use mmapfs by default
- Fielddata Filtering
- Eagerly Loading Fielddata (suffer merge time)
- Global ordinals
- Eager Global ordinals (suffer refresh time)
You could google above terms for more information.
2014年12月19日 星期五
JVM Monitoring using SNMP in Cacti
Cacti 是個監控工具,可以透過 SNMP 監控主機的運行狀態,預設可以監控 OS 的 CPU、Memory、Disk、Network usage,這裡我們說明透過 JVM-MANAGEMENT-MIB 讓 JVM 也能被 Cacti 監控。
步驟:
步驟:
- 在被監控端安裝 snmpd,參考:Monitoring Performance with Net-SNMP。
- 在監控端安裝 Cacti,CentOS / Fedora 用戶直接執行 yum install cacti。
- 啟動 JVM SNMP 功能,在執行 java command 加上:
- -Dcom.sun.management.snmp.port=1610
- -Dcom.sun.management.snmp.acl.file=snmp.acl
- snmp.acl 從 $JAVA_HOME/jre/lib/management/snmp.acl.template 複製,將最後的 acl、trap 區塊 uncomment。
- 修改 snmpd.conf,加上 proxy -v 2c -c public localhost:1610 .1.3.6.1.4.1.42。
- 複製 jvm_mem_pool.xml、jvm_gc.xml 到 Cacti 的 snmp_queries 目錄下。
- 到 Cacti 的管理介面匯入 cacti_host_template_jvm_snmp_host.xml。
- 成功匯入後會看到 Host Templates 多了一個名為 JVM SNMP Host 的 template,其下有:
- Associated Graph Templates
- JVM - Classes Count
- JVM - Heap Usage
- JVM - NonHeap Usage
- JVM - Thread Usage
- Associated Data Queries
- JVM - Garbage Collection Query
- JVM - Memory Pool Query
- SNMP - Get Mounted Partitions
- SNMP - Get Processor Information
- SNMP - Interface Statistics
- 在 Cacti 新增 Device 時,Host Template 選取 JVM SNMP Host。
- 相關檔案可在 https://github.com/linuschien/Cacti 取得。
- 範例 JVM Heap Memory Usage 圖:
2014年12月18日 星期四
2014年11月27日 星期四
自訂 Docker image 範例
這個範例從CentOS 6 image上安裝sshd、supervisord、JDK 1.7、Tomcat 7、MySQL,並且預設啟動supervisord,再由supervisord自動帶起sshd、MySQL。
Dockerfile
FROM centos:centos6
MAINTAINER Linus Chien <linus_chien@mail.gss.com.tw>
RUN yum install -y openssh-server java-1.7.0-openjdk wget tar python-setuptools mysql-server
# setup root's password
RUN echo 'root:12345678' | chpasswd
# SSH login fix. Otherwise user is kicked off after login
RUN sed 's@session\s*required\s*pam_loginuid.so@session optional pam_loginuid.so@g' -i /etc/pam.d/sshd
# init SSH keys
RUN /sbin/service sshd start && /sbin/service sshd stop
# init MySQL database
RUN /sbin/service mysqld start && /sbin/service mysqld stop
ENV TOMCAT_SITE https://archive.apache.org/dist/tomcat
ENV TOMCAT_MAJOR_VERSION 7
ENV TOMCAT_MINOR_VERSION 7.0.57
RUN wget -q ${TOMCAT_SITE}/tomcat-${TOMCAT_MAJOR_VERSION}/v${TOMCAT_MINOR_VERSION}/bin/apache-tomcat-${TOMCAT_MINOR_VERSION}.tar.gz && \
wget -qO- ${TOMCAT_SITE}/tomcat-${TOMCAT_MAJOR_VERSION}/v${TOMCAT_MINOR_VERSION}/bin/apache-tomcat-${TOMCAT_MINOR_VERSION}.tar.gz.md5 | md5sum -c - && \
tar zxf apache-tomcat-*.tar.gz && \
rm apache-tomcat-*.tar.gz && \
mv apache-tomcat* /opt/
RUN easy_install supervisor
ADD supervisord.conf /etc/supervisord.conf
EXPOSE 22 3306 8080
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisord.conf"]
MAINTAINER Linus Chien <linus_chien@mail.gss.com.tw>
RUN yum install -y openssh-server java-1.7.0-openjdk wget tar python-setuptools mysql-server
# setup root's password
RUN echo 'root:12345678' | chpasswd
# SSH login fix. Otherwise user is kicked off after login
RUN sed 's@session\s*required\s*pam_loginuid.so@session optional pam_loginuid.so@g' -i /etc/pam.d/sshd
# init SSH keys
RUN /sbin/service sshd start && /sbin/service sshd stop
# init MySQL database
RUN /sbin/service mysqld start && /sbin/service mysqld stop
ENV TOMCAT_SITE https://archive.apache.org/dist/tomcat
ENV TOMCAT_MAJOR_VERSION 7
ENV TOMCAT_MINOR_VERSION 7.0.57
RUN wget -q ${TOMCAT_SITE}/tomcat-${TOMCAT_MAJOR_VERSION}/v${TOMCAT_MINOR_VERSION}/bin/apache-tomcat-${TOMCAT_MINOR_VERSION}.tar.gz && \
wget -qO- ${TOMCAT_SITE}/tomcat-${TOMCAT_MAJOR_VERSION}/v${TOMCAT_MINOR_VERSION}/bin/apache-tomcat-${TOMCAT_MINOR_VERSION}.tar.gz.md5 | md5sum -c - && \
tar zxf apache-tomcat-*.tar.gz && \
rm apache-tomcat-*.tar.gz && \
mv apache-tomcat* /opt/
RUN easy_install supervisor
ADD supervisord.conf /etc/supervisord.conf
EXPOSE 22 3306 8080
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisord.conf"]
supervisord.conf
[supervisord]
logfile=/tmp/supervisord.log ; (main log file;default $CWD/supervisord.log)
logfile_maxbytes=10MB ; (max main logfile bytes b4 rotation;default 50MB)
logfile_backups=10 ; (num of main logfile rotation backups;default 10)
loglevel=info ; (log level;default info; others: debug,warn,trace)
pidfile=/tmp/supervisord.pid ; (supervisord pidfile;default supervisord.pid)
nodaemon=true ; (start in foreground if true;default false)
minfds=1024 ; (min. avail startup file descriptors;default 1024)
minprocs=200 ; (min. avail process descriptors;default 200)
;[program:apache2]
;command = /usr/sbin/httpd -D FOREGROUND
;priority = 30
;[program:tomcat7]
;command = /opt/apache-tomcat-7.0.57/bin/catalina.sh run
;priority = 20
[program:mysql5]
command = /usr/bin/pidproxy /var/run/mysqld/mysqld.pid /usr/bin/mysqld_safe
priority = 10
[program:sshd]
command = /usr/sbin/sshd -D
priority = 40
步驟:
logfile=/tmp/supervisord.log ; (main log file;default $CWD/supervisord.log)
logfile_maxbytes=10MB ; (max main logfile bytes b4 rotation;default 50MB)
logfile_backups=10 ; (num of main logfile rotation backups;default 10)
loglevel=info ; (log level;default info; others: debug,warn,trace)
pidfile=/tmp/supervisord.pid ; (supervisord pidfile;default supervisord.pid)
nodaemon=true ; (start in foreground if true;default false)
minfds=1024 ; (min. avail startup file descriptors;default 1024)
minprocs=200 ; (min. avail process descriptors;default 200)
;[program:apache2]
;command = /usr/sbin/httpd -D FOREGROUND
;priority = 30
;[program:tomcat7]
;command = /opt/apache-tomcat-7.0.57/bin/catalina.sh run
;priority = 20
[program:mysql5]
command = /usr/bin/pidproxy /var/run/mysqld/mysqld.pid /usr/bin/mysqld_safe
priority = 10
[program:sshd]
command = /usr/sbin/sshd -D
priority = 40
- 安裝docker,並啟動,細節請自行參閱官方文件。
- 準備好上述兩個檔案:Dockerfile、supervisord.conf
- 執行
- docker build -t cao:tomcat7 . # use your own tag name, don't miss the dot
- docker run -d --name="tomcat" cao:tomcat7 # assign a unique name to container for convenience
- docker inspect tomcat # you can find container's IP address
- ssh {IP} # default root's password setup in Dockerfile
Docker 常用指令
- docker search {keyword} # 搜尋雲端images
- docker pull {image} # 將雲端image拉回主機
- docker build -t {tag} {path} # 使用Dockerfile建立image
- docker run {options} {image} {command} # 從image執行指令(建立container)
- docker run -i -t {image} /bin/bash # 進入container shell,(ctrl+p) + (ctrl+q)可丟背景離開shell
- docker inspect --format='{{.NetworkSettings.IPAddress}}' {container} # 查看container IP
- docker images # 查看local所有的images
- docker ps -a # 查看container包含已結束的
- docker rm {container} # 移除已結束的container
- docker rm $(docker ps -a -q) # 移除所有container
- docker rmi {image} # 移除image
- docker commit {comtainer} {image} # 將container異動過的內容寫入image
- docker start {container} # 重新啟動已結束的container
- docker attach {container} # 重新接入正在執行container
- docker save {image} > {tarball} # 備份image
- docker load < {tarball} # 還原image
2014年1月22日 星期三
Install Tomcat 7 as Linux Service
基本上在Tomcat 7裡面已經自帶了安裝成service的必要source code和shell script,但安裝到Linux上,例如:Fedora、CentOS還是需要動一點手腳。
如何compile daemon出來請參考:http://tomcat.apache.org/tomcat-7.0-doc/setup.html#Unix_daemon
只要做完第一個步驟,將compile出來的jsvc執行檔複製到$CATALINA_HOME/bin底下即可,此時在bin目錄下除了jsvc之外,還有一個重要的daemon.sh。
編輯daemon.sh增加兩行comment,如下方所示紅色部分:
接下來切換到root身分,到/etc/init.d底下執行:
如何compile daemon出來請參考:http://tomcat.apache.org/tomcat-7.0-doc/setup.html#Unix_daemon
只要做完第一個步驟,將compile出來的jsvc執行檔複製到$CATALINA_HOME/bin底下即可,此時在bin目錄下除了jsvc之外,還有一個重要的daemon.sh。
編輯daemon.sh增加兩行comment,如下方所示紅色部分:
daemon.sh
#!/bin/sh
#
# chkconfig: 35 90 10
# description: Start/Stop the Tomcat daemon.
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
35代表level 3和5會啟動這個service,90代表啟動順序,10代表關閉順序。
#
# chkconfig: 35 90 10
# description: Start/Stop the Tomcat daemon.
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
接下來切換到root身分,到/etc/init.d底下執行:
- ln -s $CATALINA_HOME/bin/daemon.sh tomcat
- chkconfig -add tomcat
這樣就已經完成service安裝,可以執行以下指令試驗看看開關Tomcat:
- /sbin/service tomcat start
- /sbin/service tomcat stop
預設service會用tomcat這個user啟動,如果沒有這個user可以先建一個,或是在$CATALINA_HOME/bin/setenv.sh裡面加入TOMCAT_USER=user name就可以了。
2013年4月23日 星期二
Send mail by Spring Integration gateway through Rabbit MQ
Maven dependency
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-mail</artifactId>
<version>2.2.3.RELEASE</version>
</dependency>
<dependency>
<groupId>javax.mail</groupId>
<artifactId>mail</artifactId>
<version>1.4.6</version>
</dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-amqp</artifactId>
<version>2.2.3.RELEASE</version>
</dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-mail</artifactId>
<version>2.2.3.RELEASE</version>
</dependency>
<dependency>
<groupId>javax.mail</groupId>
<artifactId>mail</artifactId>
<version>1.4.6</version>
</dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-amqp</artifactId>
<version>2.2.3.RELEASE</version>
</dependency>
Gateway interface
import org.springframework.integration.annotation.Gateway;
import org.springframework.integration.annotation.Header;
import org.springframework.integration.annotation.Payload;
import org.springframework.integration.mail.MailHeaders;
public interface SendMail {
@Gateway(requestChannel = "input")
void send(@Header(MailHeaders.FROM) String from, @Header(MailHeaders.TO) String to, @Header(MailHeaders.SUBJECT) String subject, @Payload String message);
}
import org.springframework.integration.annotation.Header;
import org.springframework.integration.annotation.Payload;
import org.springframework.integration.mail.MailHeaders;
public interface SendMail {
@Gateway(requestChannel = "input")
void send(@Header(MailHeaders.FROM) String from, @Header(MailHeaders.TO) String to, @Header(MailHeaders.SUBJECT) String subject, @Payload String message);
}
Spring configuration
<?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:int="http://www.springframework.org/schema/integration" xmlns:int-mail="http://www.springframework.org/schema/integration/mail"
xmlns:int-amqp="http://www.springframework.org/schema/integration/amqp" xmlns:rabbit="http://www.springframework.org/schema/rabbit"
xsi:schemaLocation="http://www.springframework.org/schema/integration/amqp http://www.springframework.org/schema/integration/amqp/spring-integration-amqp.xsd
http://www.springframework.org/schema/integration/mail http://www.springframework.org/schema/integration/mail/spring-integration-mail.xsd
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd
http://www.springframework.org/schema/rabbit http://www.springframework.org/schema/rabbit/spring-rabbit.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<rabbit:connection-factory id="rabbitConnectionFactory" host="xxx.xxx.xxx.xxx" virtual-host="/linus" username="linus_chien" password="xxx" />
<int-amqp:channel id="input" connection-factory="rabbitConnectionFactory" queue-name="linus.test" />
<int-mail:outbound-channel-adapter id="mailAdaptor" channel="input" host="xxx.xxx.xxx.xxx" />
<int:gateway id="sendMail" service-interface="SendMail" />
</beans>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:int="http://www.springframework.org/schema/integration" xmlns:int-mail="http://www.springframework.org/schema/integration/mail"
xmlns:int-amqp="http://www.springframework.org/schema/integration/amqp" xmlns:rabbit="http://www.springframework.org/schema/rabbit"
xsi:schemaLocation="http://www.springframework.org/schema/integration/amqp http://www.springframework.org/schema/integration/amqp/spring-integration-amqp.xsd
http://www.springframework.org/schema/integration/mail http://www.springframework.org/schema/integration/mail/spring-integration-mail.xsd
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd
http://www.springframework.org/schema/rabbit http://www.springframework.org/schema/rabbit/spring-rabbit.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<rabbit:connection-factory id="rabbitConnectionFactory" host="xxx.xxx.xxx.xxx" virtual-host="/linus" username="linus_chien" password="xxx" />
<int-amqp:channel id="input" connection-factory="rabbitConnectionFactory" queue-name="linus.test" />
<int-mail:outbound-channel-adapter id="mailAdaptor" channel="input" host="xxx.xxx.xxx.xxx" />
<int:gateway id="sendMail" service-interface="SendMail" />
</beans>
Test Case
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class SpringIntegrationTest {
@Autowired
private SendMail sendMail;
@Test
public void test() {
sendMail.send("linus_chien@xxx.xxx.xxx.xxx", "linus_chien@xxx.xxx.xxx.xxx", "Test", "This is a gateway annotation test.");
}
}
就這樣沒寫什麼程式就可以發信了。
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class SpringIntegrationTest {
@Autowired
private SendMail sendMail;
@Test
public void test() {
sendMail.send("linus_chien@xxx.xxx.xxx.xxx", "linus_chien@xxx.xxx.xxx.xxx", "Test", "This is a gateway annotation test.");
}
}
2013年2月20日 星期三
Online Picture Resizer
http://www.picresize.com/線上提供了轉換圖檔的功能,支援的格式有:
- JPG
- PNG
- GIF
- BMP
可以截圖、旋轉、變更大小以及加上特殊效果,從自己的電腦上傳或直接給一個URL也行。
Online escape tools
HtmlEscape.net線上提供了以下幾個功能,還蠻方便的,可以試試看:
- Html escape tool
- Html un-escape tool
- Java/.NET String escaping
- JavaScript String escaping
2013年2月7日 星期四
Android + Maven + M2Eclipse All Together
若是想要在Eclipse裡面使用Maven順利開發Android程式,還是需要動一點手腳的,必要工具有以下三項:
以上就是全部的設定步驟。
建議先裝好Spring Tool Suite會比較方便,以下是安裝與使用步驟:
- 安裝m2e-android,從Eclipse Marketplace裡面搜尋Android可以找到。
- 如果預先裝好STS,那麼只要選第一項就可以了,如果ADT還沒裝,可以在這裡勾選。
- 裝好plugin之後重新啟動Eclipse,然後新增一個Maven project,依照以下畫面新增archetype,然後下一步。
- 依照自己的需要輸入必要欄位,platform的API就看ADT裡面安裝了哪些版本,預設是16。
- 新專案建立之後會有點小問題,一是src/test/java目錄沒開,二是JDK compile版本是1.5,我改成1.6。
- 直接Run As Android Application就會看到執行畫面了。
- 如果要deploy apk到device上,則需要下maven指令install android:deploy以及設定環境變數。
- 設定環境變數ANDROID_HOME,以及在%PATH%中加入%ANDROID_HOME%\tools和%ANDROID_HOME%\platform-tools。
2013年2月5日 星期二
Speedup Android Testing
在開發Android App時,若是使用官方的emulator會非常慢,原因是程式會經過兩次指令的轉換,一次是Java class轉換成dex,接著再把手機CPU的RISC指令轉成一般x86的CISC指令,這就是為什麼會這麼慢的原因。
為了加速開發,有人會直接用真的手機做測試,這裡是介紹AndroVM做為開發的模擬環境,AndroVM是直接用x86重新compile一次Android,執行時不需要做RISC轉CISC指令的動作,所以速度上快非常多,以下說明安裝步驟。
為了加速開發,有人會直接用真的手機做測試,這裡是介紹AndroVM做為開發的模擬環境,AndroVM是直接用x86重新compile一次Android,執行時不需要做RISC轉CISC指令的動作,所以速度上快非常多,以下說明安裝步驟。
- 安裝Oracle VM VirtualBox。
- 下載AndroVM OVA檔,p代表phone,t代表tablet,我下載了androVM_vbox86p_4.1.1_r4-20121119-gapps-houdini-flash.ova。
- 直接double click ova檔案之後開始匯入VM image,會看到以下畫面。
- 匯入完畢後修改網路介面卡設定,第一張網路卡的設定如下圖。
- 第二張網路卡是用來模擬WiFi,設定如下圖。
- 設定完畢後啟動VM,經過幾個步驟設定完之後會看到下面的畫面。
- 打開AndroVM Configuration會看到第一張網卡的IP Address,這是用來讓Eclipse連線用的。
- 記住IP之後打開命令提示字元,執行adb connect <IP>指令,成功會看到以下畫面。
- 回到Eclipse執行Android專案,就會看到App的執行結果直接打在VM的畫面上,這樣就可以進行測試了。
- 在程式集的畫面也會看到開發的App出現在桌面上。
Why RabbitMQ
選擇RabbitMQ有以下幾點理由:
- Advanced Message Queuing Protocol (AMQP) implementation
- Open source and commercially supported
- Supports a huge number of developer platforms
- Spring AMQP的支援,同時有Java和.NET的版本
- Spring Integration的支援
- 為Cloud Foundry內建的messaging service
2013年2月4日 星期一
QUnit: A JavaScript Unit Testing framework.
以下是一個使用QUnit撰寫的test case,延續ISO8601.js的測試,主要有5個檔案:
- qunit-1.11.0.js
- qunit-1.11.0.css
- ISO8601.js
- testcases.js
- qunit.html
前兩項從QUnit官方網站下載。
ISO8601.js
Date.prototype.setISO8601 = function(string) {
var regexp = "([0-9]{4})(-([0-9]{2})(-([0-9]{2})"
+ "(T([0-9]{2}):([0-9]{2})(:([0-9]{2})(\.([0-9]+))?)?"
+ "(Z|(([-+])([0-9]{2}):([0-9]{2})))?)?)?)?";
var d = string.match(new RegExp(regexp));
var offset = 0;
var date = new Date(d[1], 0, 1);
if (d[3]) {
date.setMonth(d[3] - 1);
}
if (d[5]) {
date.setDate(d[5]);
}
if (d[7]) {
date.setHours(d[7]);
}
if (d[8]) {
date.setMinutes(d[8]);
}
if (d[10]) {
date.setSeconds(d[10]);
}
if (d[12]) {
date.setMilliseconds(Number("0." + d[12]) * 1000);
}
if (d[14]) {
offset = (Number(d[16]) * 60) + Number(d[17]);
offset *= ((d[15] == '-') ? 1 : -1);
}
offset -= date.getTimezoneOffset();
time = (Number(date) + (offset * 60 * 1000));
this.setTime(Number(time));
};
Date.prototype.getISO8601 = function() {
return this.getFullYear() + '-' + (this.getMonth() + 1).format(2) + '-'
+ this.getDate().format(2) + 'T' + this.getHours().format(2) + ':'
+ this.getMinutes().format(2) + ':' + this.getSeconds().format(2)
+ '.' + this.getMilliseconds().format(3) + this.getTimezone();
};
Date.prototype.getTimezone = function() {
var offset = -this.getTimezoneOffset();
if (offset == 0) {
return "Z";
}
var hour = parseInt(Math.abs(offset / 60)).format(2);
var minute = Math.abs(offset % 60).format(2)
var sign = offset > 0 ? "+" : "-";
return sign + hour + ":" + minute;
};
Number.prototype.format = function(length) {
var str = "" + this;
while (str.length < length) {
str = '0' + str;
}
return str;
};
var regexp = "([0-9]{4})(-([0-9]{2})(-([0-9]{2})"
+ "(T([0-9]{2}):([0-9]{2})(:([0-9]{2})(\.([0-9]+))?)?"
+ "(Z|(([-+])([0-9]{2}):([0-9]{2})))?)?)?)?";
var d = string.match(new RegExp(regexp));
var offset = 0;
var date = new Date(d[1], 0, 1);
if (d[3]) {
date.setMonth(d[3] - 1);
}
if (d[5]) {
date.setDate(d[5]);
}
if (d[7]) {
date.setHours(d[7]);
}
if (d[8]) {
date.setMinutes(d[8]);
}
if (d[10]) {
date.setSeconds(d[10]);
}
if (d[12]) {
date.setMilliseconds(Number("0." + d[12]) * 1000);
}
if (d[14]) {
offset = (Number(d[16]) * 60) + Number(d[17]);
offset *= ((d[15] == '-') ? 1 : -1);
}
offset -= date.getTimezoneOffset();
time = (Number(date) + (offset * 60 * 1000));
this.setTime(Number(time));
};
Date.prototype.getISO8601 = function() {
return this.getFullYear() + '-' + (this.getMonth() + 1).format(2) + '-'
+ this.getDate().format(2) + 'T' + this.getHours().format(2) + ':'
+ this.getMinutes().format(2) + ':' + this.getSeconds().format(2)
+ '.' + this.getMilliseconds().format(3) + this.getTimezone();
};
Date.prototype.getTimezone = function() {
var offset = -this.getTimezoneOffset();
if (offset == 0) {
return "Z";
}
var hour = parseInt(Math.abs(offset / 60)).format(2);
var minute = Math.abs(offset % 60).format(2)
var sign = offset > 0 ? "+" : "-";
return sign + hour + ":" + minute;
};
Number.prototype.format = function(length) {
var str = "" + this;
while (str.length < length) {
str = '0' + str;
}
return str;
};
testcases.js
test( "iso8601 test1", function() {
var date = new Date();
date.setISO8601('2012-01-30T16:07:03.007Z');
equal(date.getISO8601(), '2012-01-31T00:07:03.007+08:00');
});
test( "iso8601 test2", function() {
var date = new Date();
date.setISO8601('2012-01-30T16:07:03.012+08:00');
equal(date.getISO8601(), '2012-01-30T16:07:03.012+08:00');
});
test( "iso8601 test3", function() {
var today = new Date();
var milliseconds = today.valueOf();
today.setISO8601(today.getISO8601());
equal(today.valueOf() == milliseconds, true);
});
var date = new Date();
date.setISO8601('2012-01-30T16:07:03.007Z');
equal(date.getISO8601(), '2012-01-31T00:07:03.007+08:00');
});
test( "iso8601 test2", function() {
var date = new Date();
date.setISO8601('2012-01-30T16:07:03.012+08:00');
equal(date.getISO8601(), '2012-01-30T16:07:03.012+08:00');
});
test( "iso8601 test3", function() {
var today = new Date();
var milliseconds = today.valueOf();
today.setISO8601(today.getISO8601());
equal(today.valueOf() == milliseconds, true);
});
qunit.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>QUnit Example</title>
<link rel="stylesheet" href="qunit-1.11.0.css">
</head>
<body>
<div id="qunit"></div>
<div id="qunit-fixture"></div>
<script src="ISO8601.js"></script>
<script src="qunit-1.11.0.js"></script>
<script src="testcases.js"></script>
</body>
</html>
<html>
<head>
<meta charset="utf-8">
<title>QUnit Example</title>
<link rel="stylesheet" href="qunit-1.11.0.css">
</head>
<body>
<div id="qunit"></div>
<div id="qunit-fixture"></div>
<script src="ISO8601.js"></script>
<script src="qunit-1.11.0.js"></script>
<script src="testcases.js"></script>
</body>
</html>
Test Result
Enterprise Organization Data Model
這個data model主要是想要能夠應付千變萬化的組織型態,並且能夠符合BPM的應用,主要構思我參考了:
- The Data Model Resource Book, Vol. 1: A Library of Universal Data Models for All Enterprises
- A Universal Person and Organization Data Model
過程中對於BPM的Unit Role是不是Party Role這件事有過深切的考慮,最後以矛盾法證明出Unit Role並不是Party Role而決定將Party Role從data model中拿掉,證明過程如下:
- 假設Unit Role是Party Role而不是Party。
- 那麼必須有Party Role Type來記錄哪些Unit Role是屬於同一種Party Role。
- 假設Unit Role的Party Role Type為Unit Role Type。
- 但是Unit Role Type必須和Unit有relation,所以Unit Role Type其實是一種Party,與第1點假設產生矛盾。
- 故Unit Role是一種Party。
Struts2 Type Conversion
在Struts1使用Commons BeanUtils做為type conversion的工具,在Struts2則改用OGNL,細節可參考:Type Conversion。
不管用哪種技術,它們的目的都是為了在model中直接宣告資料正確的型別,然後透過type conversion在data binding時直接處理資料的型別轉換,如果型別轉換失敗,錯誤訊息會透過ConversionErrorInterceptor塞入field error裡面,不過程式開發的原則應該是盡量避開conversion error的產生才對。
以下我們自訂一個PercentFormatConverter,專門處理具有%符號的數字轉換。
不管用哪種技術,它們的目的都是為了在model中直接宣告資料正確的型別,然後透過type conversion在data binding時直接處理資料的型別轉換,如果型別轉換失敗,錯誤訊息會透過ConversionErrorInterceptor塞入field error裡面,不過程式開發的原則應該是盡量避開conversion error的產生才對。
以下我們自訂一個PercentFormatConverter,專門處理具有%符號的數字轉換。
NumberFormatConverter
package com.gss.gmo.cao.struts2.conversion;
import static java.text.NumberFormat.getNumberInstance;
import static org.apache.commons.lang.StringUtils.isBlank;
import static org.apache.commons.lang3.ArrayUtils.isEmpty;
import java.text.NumberFormat;
import java.text.ParseException;
import java.util.Map;
import org.apache.struts2.util.StrutsTypeConverter;
public class NumberFormatConverter extends StrutsTypeConverter {
private NumberFormat numberFormat = getNumberInstance();
public void setNumberFormat(NumberFormat numberFormat) {
this.numberFormat = numberFormat;
}
@SuppressWarnings("rawtypes")
@Override
public Object convertFromString(Map context, String[] values, Class toClass) {
if (isEmpty(values)) {
return null;
}
if (!toClass.isArray() && values.length == 1) {
return convertFromString(context, values[0], toClass);
}
String[] numbers = new String[values.length];
for (int i = 0; i < values.length; i++) {
try {
numbers[i] = numberFormat.parse(values[i]).toString();
} catch (ParseException e) {
numbers[i] = null;
}
}
return performFallbackConversion(context, numbers, toClass);
}
@SuppressWarnings("rawtypes")
private Object convertFromString(Map context, String value, Class toClass) {
if (isBlank(value)) {
return null;
}
String number;
try {
number = numberFormat.parse(value).toString();
} catch (ParseException e) {
number = null;
}
return performFallbackConversion(context, number, toClass);
}
@SuppressWarnings("rawtypes")
@Override
public String convertToString(Map context, Object object) {
if (object == null) {
return null;
}
if (object instanceof String) {
return (String) object;
}
return numberFormat.format(object);
}
}
import static java.text.NumberFormat.getNumberInstance;
import static org.apache.commons.lang.StringUtils.isBlank;
import static org.apache.commons.lang3.ArrayUtils.isEmpty;
import java.text.NumberFormat;
import java.text.ParseException;
import java.util.Map;
import org.apache.struts2.util.StrutsTypeConverter;
public class NumberFormatConverter extends StrutsTypeConverter {
private NumberFormat numberFormat = getNumberInstance();
public void setNumberFormat(NumberFormat numberFormat) {
this.numberFormat = numberFormat;
}
@SuppressWarnings("rawtypes")
@Override
public Object convertFromString(Map context, String[] values, Class toClass) {
if (isEmpty(values)) {
return null;
}
if (!toClass.isArray() && values.length == 1) {
return convertFromString(context, values[0], toClass);
}
String[] numbers = new String[values.length];
for (int i = 0; i < values.length; i++) {
try {
numbers[i] = numberFormat.parse(values[i]).toString();
} catch (ParseException e) {
numbers[i] = null;
}
}
return performFallbackConversion(context, numbers, toClass);
}
@SuppressWarnings("rawtypes")
private Object convertFromString(Map context, String value, Class toClass) {
if (isBlank(value)) {
return null;
}
String number;
try {
number = numberFormat.parse(value).toString();
} catch (ParseException e) {
number = null;
}
return performFallbackConversion(context, number, toClass);
}
@SuppressWarnings("rawtypes")
@Override
public String convertToString(Map context, Object object) {
if (object == null) {
return null;
}
if (object instanceof String) {
return (String) object;
}
return numberFormat.format(object);
}
}
PercentFormatConverter
package com.gss.gmo.cao.struts2.conversion;
import static java.text.NumberFormat.getPercentInstance;
public class PercentFormatConverter extends NumberFormatConverter {
public PercentFormatConverter() {
setNumberFormat(getPercentInstance());
}
}
設定方式就詳閱官方文件,這裡不多做贅述。
import static java.text.NumberFormat.getPercentInstance;
public class PercentFormatConverter extends NumberFormatConverter {
public PercentFormatConverter() {
setNumberFormat(getPercentInstance());
}
}
訂閱:
文章 (Atom)






















