起風前的相遇
Mac, Music and Programming
04 2月 2015
04 3月 2013
buildnumber-maven-plugin
專案開始進入測試階段時,每次提供給測試人員或客戶測試時,若有問題要回報時,我比較希望除了問題的描述之外,還能有目前測試的版本版次的資訊,因為用不同的版本,不一定能測出相同的問題。
當然每次release時加上tag是個好習慣,但在專案後半期會大量進行build->testing的循環,每次build都要加上tag可能不是很容易。
所以我們簡單一點想,如果在build完產生的war或jar這類的檔案,能自行包含這些資訊是不是比較好些?
buildnumber-maven-plugin提供了三個變數讓我們取得這些資訊
- buildNumber : repository的版本,SVN就是目前的版本,像是22013,而Git就是Head的revision.
- scmBranch : repository的Branch名稱,SVN的值會像是brunch/developer,而Git則不支援,會顯示UNKNOWN
- timestamp : 此次Build的時間
我們可以在pom.xml中利用上列三個變數,加諸在檔名或是MANIFEST.MF中,以便識別這個檔案的相關版本資料。
再來就簡單示範如何使用這個plugin
首先一定要加入scm的設定,讓plugin辨別是要怎麼讀取Buildnumber與Branch.
<scm> <connection>scm:git:git://github.com/ElliotChen/buildDemo.git</connection> </scm>
第二步就是加入buildnumber-maven-plugin,並要求在validate這個phase時執行create這個command,也就是在validate這個phase之後,我們就可以使用上列三個變數了。
<plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>buildnumber-maven-plugin</artifactId> <version>1.2</version> <executions> <execution> <phase>validate</phase> <goals> <goal>create</goal> </goals> </execution> </executions> </plugin>
再來可以在MANIFEST.MF中加入相關的資訊,下面是在war檔案裡加入相關資訊的方法。
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-war-plugin</artifactId> <version>2.3</version> <configuration> <archive> <manifestEntries> <buildNumber>${buildNumber}</buildNumber> <branch>${scmBranch}</branch> <buildTime>${timestamp}</buildTime> </manifestEntries> </archive> </configuration> </plugin>
這樣輸出的MANIFEST.MF中就會有包含有下列的資訊
buildTime: 1362382356069 branch: UNKNOWN buildNumber: 7b9aa85d
這樣可能還不夠,因為包在war檔裡沒人看得見,所以系統裡應該有個簡單的讀取MANIFEST.MF的功能,然後以JSP,或是其他方式做為顯示的媒介。
提供一個簡單的Servlet給大家參考
public class SystemInfoServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { HashMap<Object, Object> map = new HashMap<Object, Object>(); ServletContext application = getServletConfig().getServletContext(); Manifest manifest = new Manifest(application.getResourceAsStream("/META-INF/MANIFEST.MF")); Attributes attr = manifest.getMainAttributes(); for (Object key : attr.keySet()) { map.put(key, attr.get(key)); } request.setAttribute("infoMap", map); request.getRequestDispatcher("/SystemInfo.jsp").forward(request,response); } }
JSP裡只要用下列的程式就能呈現MANIFEST.MF資料
<body> <c:forEach items="${infoMap}" var="info"> ${info.key} : ${info.value} <br/> </c:forEach> </body>
另外,若是在Eclipse中有安裝Maven Plugin,那它會在每次pom.xml修改時自動產生MANIFEST.MF,但是因為沒有執行validate的關係,所以上列三項參數不會正常顯示在MANIFEST.MF裡;但是在Build完成時是會正確崁入war檔裡的,所以如果看到Eclipse Project中的MANIFEST.MF與你想像不同時,別擔心,這是正常的。
21 1月 2013
那些我被放生的專案
由於最近被問了幾次,我不確定大家聽到什麼,所以整理一下先前的情形囉。
先請看一看我2012年六月的行事曆
再看看我五月底到六月底兩個專案git的歷史資料
不清楚我要表達什麼?這很正常,讓我稍微整理一下
1.三義Yulon,Zyxel, MXIC這三個公司地點都不在台北,也分別代表三個案子
2.我Git的commit log自5月24日起到6月22日,只有5月26日,5月30日,6月1日這三天沒有Commit log,其他都都有喔,而且這三天裡,只有5月26日是假日,其他都還是工作日
3.再看看commit的時間,在晚上八點之後到隔日八點之間的至少有15天,凌晨2~4的日子也不少
這代表我在這個月內,獨自面對兩個看來是高強度的遠距離案子,當然強度如何可能要看一下Commit的程式多寡與難度,所以只好說〞看來是〞。
那這些又能代表什麼?嗯,當然只能看下去囉
我先說明這三個案子
1.三義是個維護案,就是原來系統的功能再修修改改,量不多,壓力也不大
2.Zyxel則是某個廠商案子的第二階段,這案子自3月開始接觸,預計開發時程為4、5兩個月 。以下簡稱A案。
3.MXIC則是個導入案,我們只負責其中資料交換的部份。類似ETL,但沒有邏輯的部份。4月中談下來的,預計時間大約是5月~7月初要結束。以下簡稱B案。
再來我用時間的順序來看一下狀況
三月:
A案自3月起開始與客戶討論,初步大約是九個功能,中間廠商會做SA與PM的角色,討論時僅能說個大概,詳細部份要等4月份才能提供;所以我評估這個案子可能需要2個人,時間2個月應該風險不高,最後的報價大概支付2個月帳面薪水還能有一半的收益。
不過因為預計要有兩個人,所以我當然會去要人,我希望不要是新手,而且至少在4月前能有1~2個星期先磨合,不要到了專案開始才給我人,這樣風險會很高,我得到的回答是要人沒有問題,到時候一定找得到人,不過再提了2次後,我想應該不會有人了,所以我只好提,這個案子開始後,我應該沒有時間去做別的東西,面試或其他的工做大概都幫不上忙。
四月:
3號左右收到了文件,稍微放下了心,功能的要求比我想像中要來得容易,很多我認為要做後台管理的東西都可以免了,我跟廠商的PM談了一下各階段的東西,預計是2星期左右做Prototype,再來就是開發的階段,中間客戶提了項技術性的要求,我必需去改某OpenSource的Source Code加入AJAX的支援以便配合我慣用的頁面模式,多要求了3天做Prototype,不過最後還是在2星期內提給客戶一個全部頁面都能點擊操作的系統,只是內容資料都是假的;
再來後的的就比較輕鬆,當時認為如果客戶該提供的東西都沒有Delay的話,還可能會提早1~2個星期完成。所以當提出要去談另一個案子,我覺得還可以,由其客戶預計是五月才開始,七月初要上線,那依規劃的情形下,我六月份可以有很多的時間來處理,應該完全不用擔心。
五月:
中間持續有些討論,廠商的權限設計很有趣....,不過在5月18日我也把東西給A案廠商測,測試大約一個星期左右也都符合他們內部份的規格後,我開始在看B案要怎麼做比較好。
我當時認為,A案廠商都驗收過了,再來應該不會有什麼大問題.....
六月:
A案廠商交給客戶看了,提了很多問題,不過裡面有3/4我認為那是規格上沒有註明也沒有說明的部份,這情形當然要算CR,廠商的PM也承認很多不是我們的問題;第一個假日,我將他們提的東西改完後分為2部份,一部份是我該修的,一部份是我不該修的,各自Package一個版本,交給公司內部的人去運用;另一方面,B案的PM也與我討論什麼時間要到客戶那,初步訂了6月18日要到客戶那進行我那部份的Demo;
A案的客戶在修正之後,又持續提了很多東西,幾乎佔用了我大多的時間,特別又一直要我待在新竹,但如果在新竹,我B案無法做任何事,再來後面提的東西基本都是CR,我不認為我們需要為這些負責,CR本來就該另估時程並計價,在沒完成計價估時的情形下,開發人員不該擅自處理。
不過只要我沒去新竹,A案廠商就會一直打電話來,說我一定要去新竹不然客戶不高興等等,我堅持的是如果是我們的BUG,那當然我們負責到底,但是後面提的都不在規格裡,為什麼我們一定要配合他們給客戶看,當然沒提的是,我還在忙另一個案子。
中間雖然公司的人被電話煩到一度曾氣說案子不做了,但最後還是讓執行的人討論,所以我還是做了下去。
就這樣撐著撐到6月12日,當時給的9個功能在客戶那也都過了,不過另一個晴天霹靂出現,A案的客戶提出兩個全新的功能,一個是較複雜的查詢,另一個是要與舊系統整合,13號我到新竹看了實際的需求,初步的評估可能需要4~6個工作日。
這下慘了,我很需要後面幾個完整的工作日來進行我B案的部份,要不B案會沒辦法如期交付,而且還訂好時間要到客戶那....
6月14日,我上午先到三義瞭解客戶的需求,下午我還是進台北辦公室跟公司的人談A案與B案的事情,我認為A案是CR,而不知道還有多少洞要補,AB兩個案子要救應該要先救B案,至少A案在當時給的東西是完全不在當時簽約的規格內。
不過公司的人接電話接到煩了,A案廠商會晚上很晚還打電話罵為什麼我不去新竹,所以看來公司的人看來是不知道怎麼擋了,也不說話了,
〞那公司有沒有其他人可以先幫助B案?〞,我想有人先頂一下吧?不過沒有回應。
我最後想想,〞那是不是幫忙打電話給B案的客戶,說明因其他專案的問題所以可能延後交付?〞
公司的人還是沈默沒回應,我笑了笑,〞算了,我自己處理〞,我很明白,我完全被放生了。
所以接下來就一連串的加班工作,我把B案完全放下,利用假日加上三個左右的工作天完全在加班趕A案的東西,還好B案他們自己延到20號,不過那天是颱風天,所以又延到22號,我在沒有出天窗的情形下過關了。。。。
總結一下
1。我在專案裡要不到人,急的時候完全沒有支援。
2。CR的部份,公司的人完全不會處理,我不只一次說,不在規格內的東西,時程應該是可以討論的,但這是我最失敗的地方,應該所有東西都要抓回來讓我自己談,我把公司的人想得太好了。
對了,我完全沒有加班費,開車去新竹也沒有交通補助,大家說我是不是佛心來的呢?
24 12月 2012
JSDT jQuery - Eclipse Plugin
在Eclipse中要編寫jQuery的程式通常要另開Browser以便查詢API,之前有jqueryWTP可以協助editor裡的autocomplete,但久未更新,換了新版的Eclipse也就無法使用了。
今天終於又看到了一個新的Plugin,而且還支援jQuery 1.8!!所以記錄一下,主要是安裝完後的設定不是那麼直覺。
JSDT jQuery,請參考http://code.google.com/a/eclipselabs.org/p/jsdt-jquery/
第一步,在Eclipse Marketplace中以jquery尋找plugin,就可以找到JSDT jQuery
第二步,安裝後選取要使用的project的properties,在JavaScript -> Include Path裡按下 "Add JavaScript Library"
第三步,選取jQuery Library,這選項是Plugin加上去的
第四步,就是選取jQuery的版本,最新的1.8已經有支援了!
接下來在Properties裡就能看到多了一個jQuery的Library
無論是在JSP或是JS檔案裡,都可以輕鬆看到jQuery的API囉!
26 7月 2012
Query By Example In Spring-Data-JPA
Query By Example (QBE) 是個常用的查詢模式,Hibernate有Example Query來實踐,但JPA沒有,這對我常用的開發模式而言是個不小的困擾,所以基本上我都把JPA放在一邊,直接用Hibernate。
其實QBE的概念並不難實現,只要分析傳入的Domain Object,哪些property有值,加入查詢條件即可, 趁著覆習Spring Data的同時,就做個簡單的實作好了。
先建個ExpressionParam來儲存分析Domain Object Class後的結果,readMethod就是用來取值,而attribue則是用來取得Criteria的Path
public class ExpressionParam<T> { private String name; private Method readMethod; private SingularAttribute<T, ?> attribute; public ExpressionParam(String name, Method readMethod, SingularAttribute<T, ?> attribute) { super(); this.name = name; this.readMethod = readMethod; this.attribute = attribute; } ..... }
再來就是配合Spring-Data的Specification Query,實作一個ExampleSpecification,其中作個簡單的Cache機制,以免同樣的Domain Object Class要一直重覆分析有哪些ReadMethod。
public class ExampleSpecification<T> implements Specification<T> { private static final Logger logger = LoggerFactory.getLogger(ExampleSpecification.class); protected static final Map<Class<?>, List<ExpressionParam<?>>> classCache = Collections.synchronizedMap(new WeakHashMap<Class<?>, List<ExpressionParam<?>>>()); EntityManager entityManager; T example; public ExampleSpecification(final EntityManager entityManager, final T example) { this.entityManager = entityManager; this.example = example; } @Override public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb) { List<Predicate> predicates = new ArrayList<Predicate>(); EntityType<T> entity = entityManager.getMetamodel().entity((Class<T>)example.getClass()); List<ExpressionParam<?>> params = parseReadMethod(entity); for (ExpressionParam<?> param : params) { try { Object value = param.getReadMethod().invoke(example); if (null != value && StringUtils.isNotEmpty(value.toString())) { predicates.add(cb.equal(root.get((SingularAttribute<T, ?>)param.getAttribute()), value)); } } catch (Exception e) { e.printStackTrace(); } } return predicates.isEmpty()?cb.conjunction() : cb.and(predicates.toArray(new Predicate[predicates.size()])); } protected List<ExpressionParam<?>> parseReadMethod(EntityType<T> entityType) { Class<T> clazz = (Class<T>) entityType.getClass(); if (classCache.containsKey(clazz)) { return classCache.get(clazz); } logger.info("First Parsing Read Method for Class[{}]", clazz); List<ExpressionParam<?>> methods = new ArrayList<ExpressionParam<?>>(); classCache.put(clazz, methods); PropertyDescriptor[] pds = BeanUtils.getPropertyDescriptors(example.getClass()); Set<SingularAttribute<T, ?>> atts = entityType.getDeclaredSingularAttributes(); for (SingularAttribute<T,?> sat : atts) { if (PersistentAttributeType.MANY_TO_ONE == sat.getPersistentAttributeType() || PersistentAttributeType.ONE_TO_ONE == sat.getPersistentAttributeType()) { continue; } String name = sat.getName(); Method readMethod = null; for (PropertyDescriptor pd : pds) { if (pd.getName().equals(name)) { readMethod = pd.getReadMethod(); break; } } logger.debug("Property {} - Method {}", name, readMethod); if (null != readMethod) { methods.add(new ExpressionParam<T>(name, readMethod, sat)); } } return methods; } }
由於Spring Data的Repository產生機制不容易修改(其實是我還沒找到好的切入點....)所以只好在Service Layer來達到QBE的作用,可以參考下面UserService的做法。
@Service @Transactional(readOnly=true) public class UserService { @PersistenceContext private EntityManager entityManager; @Autowired private UserJpaDao userDao; public List findByExample(User example) { ExampleSpecification es = new ExampleSpecification(entityManager, example); return userDao.findAll(es); } }
再來看一下Test的實際應用
public class UserJpaDaoTest { @Autowired private UserService userService; @Test public void test() { User example = new User(); example.setName("Bob"); this.userService.findByExample(example); } }
順便提一下,不才小弟又要找新工作了,若有覺得適合小弟的可以聯絡交流一下。