Total Pageviews

2017/02/05

[Testing Tool] Postman

I am using Redmine Rest API to implement my web application. For example,  here has developer guide to teach us how to use rest API to find, create, update and delete project: https://www.redmine.org/projects/redmine/wiki/Rest_Projects

In order to make sure the JSON format for each action (i.e. CRUD) in request or response, we can make good use of Postman to assist us.

Installation


How-to
Firstly, we need to configure user name and password for logging into Redmine:


Secondly, we need to set content-type to application/json in Headers:


If I would like to test:

  • List projects: GET /projects.json
    • Select GET HTTP method, fill in URL and click Send button, then we can get the execution result
  • Show a project: GET /projects/[id].json
    • Select GET HTTP method, fill in URL and click Send button, then we can get the execution result
  • Create a project: POST /projects.json
    • Select POST HTTP method, fill in URL, prepare JSON string for creating a new project and click Send button, then we can get the execution result
  • Update a project: PUT /projects/[id].json
    • Select PUT HTTP method, fill in URL, prepare JSON string for updating  project and click Send button, then we can get the execution result
  • Delete a project: DELETE /projects/[id].json
    • Select DELETE HTTP method, fill in URL and click Send button, then we can get the execution result




Reference
[1] https://www.getpostman.com/
[2] https://dzone.com/articles/12-great-web-service-testing-tools?utm_content=bufferba092&utm_medium=social&utm_source=twitter.com&utm_campaign=buffer

2017/02/04

[Design Pattern] Strategy Pattern

Assume I have a data exporter which can export data to PDF, XLS or HTML format. It can export data to different format depends on your demand.

Firstly, creating a strategy interface:
package albert.practice.designpattern.strategy;

public interface ExportStrategy {

    void exportData(String data);
    
}


Secondly, creating concrete classes implements strategy interface.
package albert.practice.designpattern.strategy;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class ExcelExporter implements ExportStrategy {

    @Override
    public void exportData(String data) {
        log.debug("exporting " + data + " into excel file.");
    }

}



package albert.practice.designpattern.strategy;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class HtmlExporter implements ExportStrategy {

    @Override
    public void exportData(String data) {
        log.debug("exporting " + data + " into html file.");
    }

}



package albert.practice.designpattern.strategy;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class PdfExporter implements ExportStrategy {

    @Override
    public void exportData(String data) {
        log.debug("exporting " + data + " into pdf file.");
    }

}


The DataExport class will export data depends on client's demands.

package albert.practice.designpattern.strategy;

public class DataExport {

    public static void main(String[] args) {
        String data = "[test data]";
        
        DataExport exporter = new DataExport();
        exporter.exportData(FileTypeEnum.XLS, data);
        exporter.exportData(FileTypeEnum.PDF, data);
        exporter.exportData(FileTypeEnum.HTML, data);
    }

    public void exportData(FileTypeEnum fileType, String data) {
        ExportStrategy exporter = null;
        if (FileTypeEnum.XLS == fileType) {
            exporter = new ExcelExporter();
        } else if (FileTypeEnum.PDF == fileType) {
            exporter = new PdfExporter();
        } else if (FileTypeEnum.HTML == fileType) {
            exporter = new HtmlExporter();
        }
        exporter.exportData(data);
    }

}




We create objects which represent various export strategies, but we still expose if-else logic in client. Therefore, we create an ExportStrategy class which will change the executing algorithm of the context object (i.e. PdfExporter, HtmlExporter, and ExcelExporter).
package albert.practice.designpattern.strategy;

import lombok.Data;

@Data
public class ExporterContext {
    
    private String data;
    
    public void doExport(ExportStrategy strategy) {
        strategy.exportData(this.data);
    }
    
}



And the DataExport will be modified as bellows:

package albert.practice.designpattern.strategy;

public class DataExport {

    public static void main(String[] args) {
        String data = "[test data]";
        
        ExporterContext context = new ExporterContext();
        context.setData(data);
        context.doExport(new HtmlExporter());
        context.doExport(new ExcelExporter());
        context.doExport(new PdfExporter());
    }

}



To sum up, we create a ExportStrategy interface which define an exportData action and concrete strategy classes (i.e. PdfExporter, ExcelExporter, HtmlExporter) implementing the ExportStrategy interface. ExporterContext is a class which uses a Strategy.
DataExport, our demo class, use Context and strategy objects to demonstrate change in Context behaviour based on strategy  (i.e. PdfExporter, ExcelExporter, HtmlExporter) it uses.


2017/02/03

[Git] Commit undo process

Problem
If we would like to clean up my commits, i.e. remove some unnecessary commits, how to do it?



How-to


If we would like to revert to this commit which number is ead950a


The commands are as bellows:


The result is as bellows:


2017/02/02

2017/02/01

[閱讀筆記] The World Is Flat


  1. 雖然很多美國的會計師的工作都外包到印度了,但是想留在美國繼續做這一行的會計師必須要懂得設計具有創意的複雜策略,才有辦法繼續生存,例如如何避稅、節稅、經營客戶關係。這類會計師會對客戶說:「我把旁枝末節的報稅工作外包到國外去了,我們現在來討論,如何料理你的不動產,你打算為客戶做哪些安排,你準備把一些前留在信託基金裡嗎?」
  2. 任何工作,只要能夠把內容數位化,把流程切割,靈活調動每一個部分,就可能被包到海外。我無法幫你端上一客牛排,可是我可以為你在全世界的任何餐廳訂位
  3. 何處可以把工作做的最符合效益又有效率,工作就會移往何處。如此一來,釋放了人力與資金去做更高層次的工作,而且我們能以更低的成本做生產,使消費者與企業互蒙其利
  4. 市場經濟的規則是,哪裡有最豐沛的人力資源、最低廉的勞工,企業自然就會往那兒去
  5. 景氣過熱和泡沫可能誘發經濟危機,會使很多人、很多企業虧損甚至破產,不過也能驅使科技的創新越來越快。所造成的過剩生產力不論是發生在鐵路舖設或汽車生產上,最後都能帶來意想不到的正面效果
  6. 標準化並不會阻礙創新,只會讓人更專注,專注於真正價值所在。價值通常可以附加在標準之上或其周邊
  7. 軟體是生菜不是黃金,是容易朽壞的商品,如果一段時間不改善,就會腐爛
  8. 很多產品是互補的,有了A 如果也有 B,A 的價值就會更高。有紙很好,有鉛筆也很好,有了更多紙就會有更多鉛筆;再來紙的品質變好了,鉛筆也變好,結果生產力就會提昇。這就是互補性產品同步改進的現象
  9. 只是引進新科技是絕對不夠的,新科技一定要結合新作法,產能才會升級
  10. 資訊科技的出現並沒有馬上提高產能,因為有了新電腦還不夠,還需要新程序核心技能。新作法會增加資訊科技的價值,更新更好的資訊科技則會增加新作法的可能
  11. 網路泡沫化,並沒有終結全球化,反而逼得許多企業盡可能把工作外包出去,以節省資金,等於加速全球化
  12. 當創造價值模式從垂直變水平,企業從計畫與控制變成連結與合作
  13. 價值通常是同一家公司由上而下創造的,很容易看出誰在上誰在下,誰剝削誰。但是當世界開始抹平,價值是水平創造的,合作的方式有好多種,小人物的力量比以前大很多,誰在上誰在下,誰封鎖誰,一切變得很複雜
  14. 假如每一國只生產自己擁有成本優勢的產品,再與他國交易他國所擁有優勢的商品,每個貿易國都會受益,國民所得全會提升
  15. 出售勞力的人,一次只能賣給一家工廠或因為消費者,不像軟體或新藥的發明者,同時可以賣給全球市場的所有人
  16. 當印度或中國往價值鏈的上端移動,開始生產美國以往擅長的知識產品,美國在某些領域的比較優勢就會減少。某些領域的工資會下跌,有些工作甚至會永遠移往海外。所以某些知識工作者必須水平移動。但是變大的全球大餅一定只會創造出目前無法預測的新專業,留待工作者去填滿
  17. 在抹平的世界,你必須太特殊、太專業、太懂得深耕、太會調適,你的工作才不會外包出去
  18. 工程師出身的領導者,聽你說話馬上就能抓住重點;律師出身的領導者,都聽不到重點
  19. 科技與貿易不僅把餅越做越大,還把餅的配額由低階工作轉到高階。把高中教育變成義務,社會就多出許多技術較高的工作者,可以在變大、變複雜的經濟大餅中,吃到更大的一塊
  20. 專才通常技術有深度,卻視野窄;通才視野廣,技術卻沒深度,需要反應的時候行動快,卻常得不到夥伴或客戶的信任。相形之下,博才則能在日益寬廣的狀況與經驗中應用深度技術,一直能學新技能,建立新關係,擔當新角色。公司已不能把人當專門工具,員工也不能再繼續當專門工具,必須改當瑞士刀。瑞士刀便是博才
  21. 在抹平世界的過程,一定會創造贏家與輸家,要思索的是讓贏家補償輸家的機制,尤其是原本高薪專業,再就業薪水卻少很多的輸家。若你不處理,就是在自惹政治反彈。抹平的過程一定會有很多受害者,只要經濟長期衰退,反彈就可能會變成動亂
  22. 假如社會無法處理世界抹平所產生的緊張,就會有反彈,會有政治勢力想把推土機所剷除的障礙與藩籬重新豎起,他們會以保護弱者為名,到頭來大家的生活水平都要因此降低
  23. 我們應該善待弱者及落後的人,要挺抹平的唯一方式,就是做個有愛心的抹平主義者。即想過著共和黨人那種日子,投票就要像民主黨人(註:通常共和黨選民比較富有,民主黨選民比較重視社會福利)
  24. 保障就業是沒辦法讓國家脫貧的。問題只是就業就好解決,國營企業可以吸收一切需要就業的人口。問題不只是就業,而是可以提升生活水平,增加生產力的那種就業
  25. 政府不只要由上而下負責把財政及貨幣政策管好,還要創造一個易於創業、籌資、經商的民間環境,並容許部分外國競爭,因為有競爭,創新總是較多較快

2017/01/31

2017/01 Travel

農禪寺
DSC01084

DSC01087


Taipei 101
DSC01123

DSC01154

DSC01194



鶯歌
IMG_5909



IMG_5911


2017/01/11

[Oracle VirtualBox] VT-x is not available

Problem
當我們參加教育訓練,從講師的硬碟 copy 上課用的 VirtualBox 到自己的電腦後,在啟動的時候發生錯誤,顯示 VT-x is not available 的錯誤訊息


How-to
解決步驟如下:
1. 重新啟動電腦 (restart your computer)
2. 按下 Esc,到 BIOS 進行設定 (press Esc during restarting computer process, then enter your BIOS configuation)
3. 將 Virtualization Technology (VTx) 的選項選取,重新啟動電腦即可 (Check Virtualization Technology (VTx) and Save, then restart your computer)




2017/01/10

[Oracle VirtualBox] 在 VirtualBox 中無法連上網路

Problem
當我啟動 VirtualBox 後,發現在 VirtualBox 中無法連上網路,但是在本機是可以連上網路的,此 VirtualBox 的網路設定如下:


How-To
網路要設定成橋接介面卡,並指定本機使用到的網路介面卡即可




2017/01/09

[Design Pattern] Factory Pattern

Assume I have a juice store, which provide apple juice, orange juice and kiwi juice. As I accept customer's order, I will make juice and deliver juice to customer.

Therefore, I will do make juice and deliver juice no matter which juices are ordered.
package albert.practice.designpattern.factory;

public interface Juice {
    void makeJuice();
    void deliverJuice();
}


Each juice will implement Juice interface, and implement its own logic in each class.
Here is apple juice:

package albert.practice.designpattern.factory;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class AppleJuice implements Juice {

    @Override
    public void makeJuice() {
      log.debug("making APPLE juice.");
    }

    @Override
    public void deliverJuice() {
        log.debug("deliver APPLE juice to customer.");
    }

}



Here is orange juice:

package albert.practice.designpattern.factory;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class OrangeJuice implements Juice {

    @Override
    public void makeJuice() {
        log.debug("making ORANGE juice.");
    }

    @Override
    public void deliverJuice() {
        log.debug("deliver ORANGE juice to customer.");        
    }

}



Here is kiwi juice:
package albert.practice.designpattern.factory;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class KiwiJuice implements Juice {

    @Override
    public void makeJuice() {
        log.debug("making KIWI juice.");
    }

    @Override
    public void deliverJuice() {
        log.debug("deliver KIWI juice to customer.");        
    }

}


I also create a fruit enumeration to tell which juice I will make:
package albert.practice.designpattern.factory;

public enum FruitEnum {
    APPLE, ORANGE, KIWI;
}


And I create a JuiceStore to process customers' order. The processOrder method is used to create apple, orange or kiwi object depends on customer's order.
package albert.practice.designpattern.factory;

public class JuiceStore {

    public static void main(String[] args) {
        JuiceStore test = new JuiceStore();
        test.processOrder(FruitEnum.KIWI);
        test.processOrder(FruitEnum.APPLE);
        test.processOrder(FruitEnum.ORANGE);
    }

    public void processOrder(FruitEnum fruit) {
        Juice juice = null;
        if (FruitEnum.APPLE == fruit) {
            juice = new AppleJuice();
        } else if (FruitEnum.ORANGE == fruit) {
            juice = new OrangeJuice();
        } else if (FruitEnum.KIWI == fruit) {
            juice = new KiwiJuice();
        }
        juice.makeJuice();
        juice.deliverJuice();
    }

}


It seems good so far. But if we would like to create object without exposing the if-else creating logic to client, we need to apply factory pattern to refactor the existing architecture.


Therefore, we need to create a JuiceFactory to generate object of concrete class based on given fruit.
package albert.practice.designpattern.factory;

public class JuiceFactory {

    public Juice getJuice(FruitEnum fruitEnum) {
        Juice juice = null;
        if (FruitEnum.APPLE == fruitEnum) {
            juice = new AppleJuice();
        } else if (FruitEnum.ORANGE == fruitEnum) {
            juice = new OrangeJuice();
        } else if (FruitEnum.KIWI == fruitEnum) {
            juice = new KiwiJuice();
        }
        return juice;
    }

}



We can see the updated JuiceStore class, the creating logic had been hide in JuiceFactory, we only need to tell JuiceFactory which fruit I would like to make:

package albert.practice.designpattern.factory;

public class JuiceStore {

    public static void main(String[] args) {
        JuiceFactory factory = new JuiceFactory();
        
        Juice apple = factory.getJuice(FruitEnum.APPLE);
        apple.makeJuice();
        apple.deliverJuice();
        
        Juice orange = factory.getJuice(FruitEnum.ORANGE);
        orange.makeJuice();
        orange.deliverJuice();
        
        Juice kiwi = factory.getJuice(FruitEnum.KIWI);
        kiwi.makeJuice();
        kiwi.deliverJuice();
    }
}


We can see JuiceStore asks JuiceFactory to create object based on its demands, and JuiceStore will use its object to make juice and deliver juice.




2017/01/08

[Redmine] Fail to Create Project via Rest API again!

Problem
I am using Redmine rest api to create project, and had checked the project identifier only allowed lower letter (a-z), numbers, dashes and underscores. But it still go wrong when I only provide numbers in project identifier:
1
2
3
4
5
org.springframework.web.client.HttpClientErrorException: 422 Unprocessable Entity
 at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:91) ~[spring-web-4.3.2.RELEASE.jar:4.3.2.RELEASE]
 at org.springframework.web.client.RestTemplate.handleResponse(RestTemplate.java:667) ~[spring-web-4.3.2.RELEASE.jar:4.3.2.RELEASE]
 at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:620) ~[spring-web-4.3.2.RELEASE.jar:4.3.2.RELEASE]
 at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:580) ~[spring-web-4.3.2.RELEASE.jar:4.3.2.RELEASE]


How-to
According to this issue, the project identifier cannot accept number only although the document does not say it.

Therefore, we need to do one more check as we create project (you need to import org.apache.commons.lang.StringUtils class):
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
    private void checkProjectIdentifier(String identifier) {
        Pattern pattern = Pattern.compile("^[a-z0-9_=]*$");
        if (!pattern.matcher(identifier).matches()) {
            throw new RuntimeException("專案代碼僅允許使用小寫英文字母 (a-z), 阿拉伯數字, 虛線與底線");
        }
        
        if(StringUtils.isNumeric(identifier)) {
            throw new RuntimeException("專案代碼不可僅有數字");
        }
    }


Reference
[1] https://www.redmine.org/issues/5877

2017/01/07

[Redmine] Fail to Create Project via Rest API

Problem
I am using Redmine Rest API to create project, but I get this error message:
1
2
3
4
5
6
org.springframework.web.client.HttpClientErrorException: 422 Unprocessable Entity
 at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:91) ~[spring-web-4.3.2.RELEASE.jar:4.3.2.RELEASE]
 at org.springframework.web.client.RestTemplate.handleResponse(RestTemplate.java:667) ~[spring-web-4.3.2.RELEASE.jar:4.3.2.RELEASE]
 at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:620) ~[spring-web-4.3.2.RELEASE.jar:4.3.2.RELEASE]
 at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:580) ~[spring-web-4.3.2.RELEASE.jar:4.3.2.RELEASE]
 at com.cht.commons.web.client.RestRequest.post(RestRequest.java:175) ~[cht-commons-web-client-0.3.0-SNAPSHOT.jar:0.3.0-SNAPSHOT]




How-To
This error results from the project identifier's limitation. 
I provide an illegal project identifier, so I fail to create project.



Owing to the Redmine Rest API cannot provide meaningful information for user, so I need to utilize regular expression to provide meaningful information when the project identifier is not legal.
1
2
3
4
5
6
    private void checkProjectIdentifier(String identifier) {
        Pattern pattern = Pattern.compile("^[a-z0-9_=]*$");
        if (!pattern.matcher(identifier).matches()) {
            throw new RuntimeException("僅允許使用小寫英文字母 (a-z), 阿拉伯數字, 虛線與底線");
        }
    }

    
    
Reference
[1] https://www.redmine.org/projects/redmine/wiki/Rest_Projects    


2017/01/06

[Angular2] Services

Requirement
It's very common that multiple components may need access to the same code and we don't want to copy and paste the same code over and over. 
Therefore, we need create a single reusable data service and inject it in the components that need it.

How-To
(1) Create a service class looks like:
1
2
3
4
5
6
7
8
9
import { Injectable } from '@angular/core';
import { Http, Response, Headers } from '@angular/http';
import { Observable } from 'rxjs/Observable';

@Injectable()
export class ProjectService {

  constructor(private http: Http) { }
}

(2) Import, register and initialize the service class in component class
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import { Component, Pipe, PipeTransform } from '@angular/core';
import { CORE_DIRECTIVES } from '@angular/common';
import { TAB_DIRECTIVES } from 'ng2-bootstrap/ng2-bootstrap';
import { Router } from '@angular/router';
// import ProjectService
import { ProjectService } from './project.service';
import { MODAL_DIRECTIVES, ModalComponent } from 'ng2-bs3-modal/ng2-bs3-modal';
import { PAGINATION_DIRECTIVES } from 'ng2-bootstrap/ng2-bootstrap';
import { StatusPipe } from './project.pipe';

@Component({
  selector: 'project-list',
  templateUrl: 'project.list.html',
  directives: [MODAL_DIRECTIVES, PAGINATION_DIRECTIVES],
  providers: [ProjectService], // register ProjectService to providers
  pipes: [StatusPipe]
})

export class ProjectComponent {
  
  // initialize ProjectService in constructor
  constructor(private projectService: ProjectService) {

  }
}

(3) If I add a search method in ProjectService:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import { Injectable } from '@angular/core';
import { Http, Response, Headers } from '@angular/http';
import { Observable } from 'rxjs/Observable';

@Injectable()
export class ProjectService {

  constructor(private http: Http) { }
  
  search(searchProject: Project, page: number) {
    let name: string = searchProject.name;
    let status: number = searchProject.status;
    return this.http.get('/api/projects/search?name=' + name + '&status=' + status + '&page=' + page)
                    .map(res => <Page<Project>>res.json());
  }
}

(4) The component class code snippet looks like:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import { Component, Pipe, PipeTransform } from '@angular/core';
import { CORE_DIRECTIVES } from '@angular/common';
import { TAB_DIRECTIVES } from 'ng2-bootstrap/ng2-bootstrap';
import { Router } from '@angular/router';
// import ProjectService
import { ProjectService } from './project.service';
import { MODAL_DIRECTIVES, ModalComponent } from 'ng2-bs3-modal/ng2-bs3-modal';
import { PAGINATION_DIRECTIVES } from 'ng2-bootstrap/ng2-bootstrap';
import { StatusPipe } from './project.pipe';
import { Project, Page } from '../model/model';

@Component({
  selector: 'project-list',
  templateUrl: 'project.list.html',
  directives: [MODAL_DIRECTIVES, PAGINATION_DIRECTIVES],
  providers: [ProjectService], // register ProjectService to providers
  pipes: [StatusPipe]
})

export class ProjectComponent {
  
  projects: Page<Project>;
  
  // initialize ProjectService in constructor
  constructor(private projectService: ProjectService) {  }
  
  search() {
    this.projectService.search(this.searchProject, this.currentPage).subscribe(
          res => {
            this.projects = res;
          }
    );
  }

}