顯示具有 java 標籤的文章。 顯示所有文章
顯示具有 java 標籤的文章。 顯示所有文章

2012年9月30日 星期日

Groovy與XML的相遇(三) : 如何找到節點

這一次就不寫 Java Code了,因為用傳統的寫法會發瘋。

一樣,以剛剛那個xml為範例

<root>
  <star age='22' blood='A'>布萊德彼特1</star>
  <star age='26' blood='C'>小勞勃道尼</star>
  <star age='24' blood='B'>基努李維</star>
</root> 

(明明他們就沒那麼年輕…)

問題1:  找到年紀小於25歲的男星,我只要他們的名字

println roots.star.find{  it.@age <=25 }.text() 

下底線又用了閉包傳入。很簡單吧! 可是執行失敗說!
因為xml讀進來都是字串啊,Groovy沒有人工智慧。
好,那怎麼辦…? 你如果有從我第一篇開始看,你應該記得轉型吧。

println roots.star.find{  it.@age as int <=25 }.text() 

你很快樂的按下去,不過結果還是錯的,你不要把Groovy當神好嗎。

println roots.star.find{  (it.@age as int) <=25 }.text() 

加個括號就行了。是那裡面的東西要轉成 int ,後面再來比條件。
問題1的解法,一行結束! 不過剛剛符合條件的男星有二個耶 ?
find 只會傳出第一個 match 的。
findAll 會傳出都符合條件的。於是我們再試一次

List list = roots.star.findAll{  (it.@age as int)  <=25 } as List
 

沒錯,還順便轉了型,裡面裝好了 groovy.util.Node。
List<groovy.util.Node> 啦。

問題2 : 把這些明星的年紀由小到大排序,呃,我要大於23歲的。然後裝成 List<Integer>。

當然以Groovy來說一定要秒殺解決的。


List list2 = (roots.star.@age.findAll{ (it as int ) >=23 } as List).sort()


我把這一行一個一個說明;
  • 取得root下的所有明星節點,但是我要他們的age而已,這時候已經是單純 age 的字串集合了。
  • 從這個結果中,幫我找 age 集合中年紀大於23 歲的,並轉型成 list。
  • 然後再排序。

是不是很方便呢? 其它的進階 xml 密技,就給你自己去研究了 !



Groovy與XML的相遇(二) : 解析文件

按照傳統,還是先來一段 Java code, 31行


import org.xml.sax.SAXException;
import org.w3c.dom.*;
import javax.xml.parsers.*;
import java.io.IOException;

public class ParseXml {
  public static void main(String[] args) {
    DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
    try {
      DocumentBuilder db = dbf.newDocumentBuilder();
      Document doc = db.parse("src/languages.xml");

      //print the "type" attribute
      Element langs = doc.getDocumentElement();
      System.out.println("type = " + langs.getAttribute("type"));

      //print the "language" elements
      NodeList list = langs.getElementsByTagName("language");
      for(int i = 0 ; i < list.getLength();i++) {
        Element language = (Element) list.item(i);
        System.out.println(language.getTextContent());
      }
    }catch(ParserConfigurationException pce) {
      pce.printStackTrace();
    }catch(SAXException se) {
      se.printStackTrace();
    }catch(IOException ioe) {
      ioe.printStackTrace();
    }
  }
}

我不針對上面抄來的程式做修正了,反正大約都是一層的xml簡單文件而已。下面是 Groovy 的寫法:

def roots = new XmlParser().parse("D:\\test.xml")
roots.star.each{   
   println "age="+it.attribute("age") +" " +  it.text() 
}

處理的是這份文件

<root>
  <star age='22' blood='A'>布萊德彼特1</star>
  <star age='26' blood='C'>小勞勃道尼</star>
  <star age='24' blood='B'>基努李維</star>
</root> 

你應該已經知道 it 是預設的,如果你的閉包參數只有一個,那麼it 就是指這個物件。 唯一要注意的就是,如果要取 tag 裡的文字是用 text(),要取參數就是 it.attribute("名稱")。

( 取參數也可以用 it.@age ,如果你開始習慣之後,就可以改成Groovy提供的做法。  )

roots.star.each 這種語法或許 Java 的程序員還不是很習慣,但其實jQuery 裡也大量使用這種觀念啊。

$("#talbe1 tr)".each({

});
是否你覺得似曾相識呢? 我把它換行一下。

$("#talbe1 tr)".each(  {這裡就是一個活生生的閉包啊}   );

再注意一次相同性,都是把閉包 { } 丟到方法each 中做參數。 在這裡閉包就像是一個沒名字的方法。

你還是有一點意見對吧…

roots.star.each{   
   println "age="+it.attribute("age") +" " +  it.text() 
}

你說明明就有一點不同,和jQuery不同啊,少了圓括號與最後的分號。
那我們改一下再來跑一次。

roots.star.each({     
    println "age=" + it.@age +" " +  it.text()   
});
我連分號都加了,這樣你沒話說了吧,還是可以跑啊。 why!!
為何在Groovy 不用加 ( ) 呢? 這是Groovy 提供的一種簡潔式的寫法。好啦,我直接整理給你看三種不同寫法,都可以跑。

roots.star.each({     
     
});
roots.star.each(){     
     
};
roots.star.each{     
    
};
這三種都是一樣的意思。我很快的總結一下

  1. 真正的寫法是如此,閉包是each的參數。 
  2. 為了好看,閉包可以放在each()的後面,因為它太長了嘛! 
  3. 如果each只收一個閉包做為參數,那 ( ) 就可以省了,它是2 的再精簡寫法。

再感覺一下… Groovy 是不是沒那麼奇怪了呢? 是不是常覺得…如果jQuery的寫法可以用在 Java 多好? 如果有這種感覺的話,你才是真的是個程序員!

Groovy 與 XML 的相遇(一) : MarkupBuilder + XmlSlurper

用Groovy 來產生 xml 再簡單不過了。雖然以前的 dom4j也是用物件的方式操作,但你要做的功夫相當的多。 先貼Java要怎麼寫…大約51行

import org.w3c.dom.*;
import javax.xml.parsers.*;
import javax.xml.transform.*;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.StringWriter;

public class CreateXml {
  public static void main(String[] args) {
    DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
    try {
      DocumentBuilder db = dbf.newDocumentBuilder();
      Document doc = db.newDocument();

      Element root = doc.createElement("root");      
      doc.appendChild(root);

      Element star1 = doc.createElement("star");
      Text text1 = doc.createTextNode("布萊德彼特");
      star1.appendChild(text1);
      root.appendChild(star1);

      Element star2 = doc.createElement("star");
      Text text2 = doc.createTextNode("小勞勃道尼");
      star2.appendChild(text2);
      langs.appendChild(star2);

      Element star3 = doc.createElement("star");
      Text text3 = doc.createTextNode("基努李維");
      star3.appendChild(text3);
      langs.appendChild(star3);

      // Output the XML
      TransformerFactory tf = TransformerFactory.newInstance();
      Transformer transformer = tf.newTransformer();
      transformer.setOutputProperty(OutputKeys.INDENT, "yes");
      StringWriter sw = new StringWriter();
      StreamResult sr = new StreamResult(sw);
      DOMSource source = new DOMSource(doc);
      transformer.transform(source, sr);
      String xmlString = sw.toString();
      System.out.println(xmlString);
    }catch(ParserConfigurationException pce) {
      pce.printStackTrace();
    } catch (TransformerConfigurationException e) {
      e.printStackTrace();
    } catch (TransformerException e) {
      e.printStackTrace();
    }
  }
}
雖然非常容易理解,一個一個物件生成,但這麼單純的事,要花51行來寫,覺得你快要死了嗎? (況且他們的屬性我還沒有加進去哩……) 來一點輕鬆的吧。先寫了一個第一版,15行,很棒了吧??

import groovy.xml.MarkupBuilder
import groovy.util.XmlSlurper
def file = new File("d:\\test.xml")
def objs = [
    [ age: 22, name: "布萊德彼特", blood: "A" ],
    [ age: 26, name: "小勞勃道尼", blood: "C" ],
    [ age: 24, name: "基努李維", blood: "B" ] ]
def b = new MarkupBuilder(new FileWriter(file))
b.root {
    objs.each { o ->
        star(age: o.age , blood:o.blood ,  o.name) {          
        }
    }
}
如果不要先把資料弄成集合 ( 雖然這樣比較容易看的懂 ),要寫的行數更少…

def xml = new groovy.xml.MarkupBuilder(new FileWriter("d:\\test.xml"))
xml.root(){
  star(age: 22, blood: "A" , "布萊德彼特")
  star(age: 26, blood: "C" , "小勞勃道尼")
  star(age: 24, blood: "B" , "基努李維")
}

對,你沒看錯,就是6行。 除了行數變少之外,易讀性完全破表。 root 並不是一個死的方法,你將它改成stars,那麼產出的xml的根節點就叫做stars了。裡面所有的東西都能改,只要你結構掌握住了,xml 某一個節點要加新的屬性,易如反掌! 維護程式的人,你覺得他看上面的51行容易理解,還是下面的6行呢?

我們都知道重構的重要。若只是為了把原來就繁複的語法歸類而重構,那為何不一開始就精簡語法? 

這樣精簡的xml 語法,很大的功勞一樣是用閉包做的。程式人員只要專心在資料面,如何產生xml 的部份並非你的價值所在。除非你是開發 xml parser 的人,否則不要沉醉在自己高深的50行程式中。這裡面並沒有價值。再一次強調,就像醫生的價值在於醫好別人的病,不在於你會用幾種手術刀。若你可以空手就取出腫瘤,30秒結束回家吃飯,沒有病人會想抱怨的。

再談 Closure

上一篇講了,閉包是物件,不是方法,雖然使用上真的很像,這裡我們直接做證明…



  Closure comparator = { String a, String b ->
    a.compareToIgnoreCase(b)
  }
這樣很明白了吧,Closure的確是物件。差別在這個物件把自己當參數傳入某個方法後,這個方法會執行 Closure裡的行為。 當然在宣告的時候和傳統的「方法」不同,你要用 { } 括起來,而且要寫 = 。 參數也不是方法一樣的宣告方式,不過概念是一樣的。 只要能接受語法後,就把它想成「動態的方法」就好了。 在書上的例子寫到


Closure comparator = { String a, String b ->
   a.compareToIgnoreCase(b)
}
List fruit = [ "apple", "Orange", "Avocado", "pear", "cherry" ]
fruit.sort(comparator)
println "Sorted fruit: ${fruit}"

這是一種寫法。先定義好有名有姓的閉包,當然 sort 也要接受這樣的閉包,不然你也是白寫的。 能夠變的地方在裡面實作的「比較方式」而已。 若還記得我們上篇文章,那是一種更直接寫的寫法。就像暱名的方法一樣。

 rpt_list.sort{o1,o2->
    o1.total<=>o2.total
 }
後面還會很大量的使用 Closure,所以目前暫時還不習慣也是無所謂的。

2012年9月29日 星期六

Groovy 的一些好用語法(二) << , 動態屬性 , collect , sort ,

Groovy 是一種動態語言,可以輕易的將方法加到原來死板的 Java 類別中,這樣可以帶來很多好處,有時候太有彈性,甚至用起來有一點頭暈腦脹,所以先整理一下一些好用的方法。

先來講 << 。


List rpt_list = new ArrayList()
rpt_list << new Report(name:"as1" , m1:10 , m2:20 , total:30)
rpt_list << new Report(name:"as2" , m1:40 , m2:40 , total:80)
rpt_list << new Report(name:"as3" , m1:20 , m2:30 , total:50)

<< 看起來很奇怪,但說起來也蠻好理解的,就是「塞過去」。 看箭頭的方向也知道,我要把右邊的塞到左邊。這個是 Java 沒有的語法。 你要是看起來很不習慣,一樣還是可以用 add ,這是 Groovy 的好處,你把他想成加強版的 Java 即可。

new Report 右邊的東西,是把屬性像 map 一樣傳進去。冒號的左邊是key右邊是value。還蠻容易理解的吧。

這邊有一個有趣的地方。一般來說 Java 要動態呼叫一個方法或是屬性,有一點麻煩,也就是說我們「執行時期」才知道要呼叫誰的話,在 Java 是比較不直觀的方式處理的。再一次強調,我的觀點仍然認為,程式的價值不在這些轉換上,所以務必要很容易做到

既然你可以把 map 當建構子傳入,那你可能會想到他是怎麼動態去mapping。
假設你要自己寫也不難,在 Groovy 來說只要寫

def dynamic_prop="total"

assert rpt_list[0]."${dynamic_prop}" == rpt_list[0]."total"
assert rpt_list[0]."total" == rpt_list[0].total

下面二個斷言都會是 true。相信你已經知道 ${ } 是像jsp 裡的變數,Groovy可放在字串中。
所以 rpt_list[0]."${dynamic_prop}"  => 會變成 rpt_list[0]."total"


而造成了可以簡單的動態呼叫的效果。相對於 Java 直接又容易理解。

好了你囉嗦了這麼久,該說 collect 了吧。我們已經準備好剛剛的 List 了

List rpt_list = new ArrayList()
rpt_list << new Report(name:"as1" , m1:10 , m2:20 , total:30)
rpt_list << new Report(name:"as2" , m1:40 , m2:40 , total:80)
rpt_list << new Report(name:"as3" , m1:20 , m2:30 , total:50)

再來你 GSP/JSP 的頁面,老闆要你最上面要先列出一個一個總和。
聰明的你應該想到上一集的招式了

List rpt_total_list = rpt_list*.getTotal()


事情有這麼簡單當然最好,但是老闆改口說,業務要看的是 / 1000 的單位啦,不然值太大。
你抓了抓頭,這樣 「星點」好像不能用了,其實還有更棒的招,就是collect,一行。

List rpt_total_list = rpt_list.collect{ it.total/1000 } 

我紅色標起來的是 Closure,大陸翻閉包,很難聽,就還是叫 Closure好了。
這樣說,閉包是一個物件,這個物件裡要寫一些邏輯,包起來當參數餵給 collect。
collect 裡頭,Groovy 自己幫你實做了,你要餵它的就是閉包 ( 好吧…叫閉包也行 )
collect 收到閉包後他做了什麼事 ?
其實 collect 在上例被呼叫了三次,因為rpt_list 裡有三個物件。
這邊依序取這三個物件的 total 後除 1000 ,然後add 到List 中,有多少加多少,加到完之後結束,把 List 傳出來這樣。
如果用傳統的 Java 來寫,又要容易閱讀的話 :

private List<Report> getTotalList(){
  List rpt_total_list = new ArrayList();
  for(Report r : rpt_list){
     rpt_total_list.add(r.getTotal);
  }
  return rpt_total_list ;
}
//煩死了…一行Groovy比七行Java

閉包使用起來像一個方法,只是不用再獨立寫出來。Groovy 提供了很多對 Collection 的瘋狂技巧。再下來我們講sort。 想到要排序剛剛的sort就很頭痛,因為在 Java 來說,你得這樣寫。你要實作compare來完成這個需求,剛剛的名言又要再講一次,這種東西沒有價值,越好寫越快。價值在客戶要用什麼排序,不是在你怎麼寫出這段程式。
public void sortPeopleByGivenName(List personList) {
  Collections.sort(personList, new Comparator() {
  public int compare(Person p1, Person p2) {
  return p1.getFamilyName().compareTo(p2.getFamilyName());
  }
  });
}
//很煩吧…
用 Groovy 的話
def sortIt(list, property) {
    list.sort { p1, p2 -> 
    p1."${property}" <=> p2."${property}" 
  }
}

長的有一點奇怪,不過你呼叫 sortIt ( rpt_list , "total") 就可以得到你想要的結果了。 上面講過了,那個${}會整個被替換成你丟入的參數。你也注意到了 sort 本身就接受閉包,所以不如改成

rpt_list.sort{o1,o2->
    o1.total <=> o2.total
}

<=> 是啥啦 ! 它就是


public int compare(int i1, int i2) {
 if (i1 == i2) return 0;
 else if (i1 < i2) return -1;
 else return 1;
}

的簡寫啦 ! 所以在閉包裡如果比的是數字,除非你的比法和人家不同,否則 <=> 就行了! 這樣子 sort 你開心了嗎 ? 今天講的就是 collect 與 sort 最後,其實 sort 還可以加條件,剛剛都白寫了 ! 沒錯,Groovy的寫法真的太有彈性了,你還可以寫成

rpt_list.sort{ it.getTotal() } 
是不是很瘋狂呢? 叫你「按照裡面每一個物件的total 給我排序!」 如果你要排的是數字,最後一個方案就是你要的! 一行搞定。

想哭嗎? 很正常。
如果不是單純的排序,你有一些奇怪規則的,留給你自己,在閉包裡動動腦筋囉!

Groovy 的轉型

Groovy 的轉型寫為 as .
為什麼 Java 已經有轉型了,Groovy 還要發明一個 ? 舉個例子


String s = "this,is,a,book"
String[] arr = s.split(",");

你這個陣列用一用之後,突然後面的邏輯變了,你需要再動態 add 一些元素到這個已經拆開的陣列中,你也知道陣列在 Java 是不能隨意改變大小的,所以你只能再寫一些程式,把這個陣子轉成 List 後再處理。
我網路上隨便查了一些寫法 :

1.

List myList = new ArrayList();
String[] myStringArray = new String[] {"Java", "is", "Cool"};

Collections.addAll(myList, myStringArray);
2.
List<String> list = new ArrayList<String>(words.length);  
for (String s : words) {  
    list.add(s);  
}  
3.
List<String> ret = Arrays.asList( 裡面放字串陣列) 

好,無論怎樣,都只是在表達一件事而已,而且這些事對真正的程式邏輯來說毫不重要,不是商業邏輯的一部份。在 Groovy 來說仍舊是一行解決。

String s = "this,is,a,book"
def ret = s.split(",") as List

( 當然第一行是字串陣列不算啦 )
這樣的好處很多! 而且中間甚至可以加上型態轉換,例如我們上一集的例子

def id_array = list*.getId() as Integer[]

我們假設 list 裡是一堆 Book ,而且他的id 是字串型態,我後面的程式需要他的 id 以數字的方式,包在整數陣列裡面。

現在你看的懂了,在第一個方法呼叫後,得到的是List<String> ,但是我們要的其實是整數陣列 ( 對不起你的同事就是要整數陣列 ) ,你打幾個字便已輕舟過萬重山。

如果你的同事突然發瘋,他決定還是List<Integer> 好了,那你就把 Integer[] 槓掉改成 List 就收工。

請記住,程式的重點都是在解決一個問題,中間轉型或是換集合物件都沒有價值。這些沒有價值的事,就是要快速處理,敏捷開發,也要有敏捷的語言搭配才可以的 !




Groovy 的一些好用語法(一) *.

 Groovy 可以和Java語法混用,這一點真的是很有趣的事。
在很不熟Groovy的時候,甚至可以把 Java 的寫法套進來。但是久了之後就會發現,Java 在一件簡單的事上,往往要很重覆的不斷寫上一樣的東西。

有些人會不屑的回應說,只是語法上的甜頭,不足掛齒。
其實這只看到表面。語法上的簡化、行數減少這是事實,不能否認,但好處為何?


  • 行數少,bug 就少。100行出錯的機率一定比10行來的高。
  • 行數少,程式的「自我描述性」就高。在 Java 中的五行,其實是在說一件事,但因為這5行會消耗你閱讀上的力氣,當行數越來越多的時候,你會很難抓住程式本身的重點。行數少,程式可以集中力氣在「表達」上,也就是易讀性會大大提升。
  • 等下的例子會十分明顯。


假設我們需要得到全部的「書」,然後把他的 id 傳到 jsp 供應用…不管你程式寫幾行,或用任何的程式語言,中文來說就是這二件事,以傳統的Java 來說:

List<Book> book = session.createQuery("from Book").list(); 
//do something....
List<Integer> list_id = new ArrayList();
for(Book b : book){
   list_id.add(b.getId());
}
request.setAttribute("id_list",list_id);

這個是我們每天在寫程式的時候做的事,雖然很囉嗦但是久了也習慣了。
Groovy 怎麼處理?

def book = Book.list()
//do something
["id_list", book*.getId() ]

啥?
對,就是你眼睛看到的。
*. 是什麼 ? 其實望文生義,就是「在這個集合裡的所有物件,都都呼叫這個方法,得到的值紀錄起來用List傳出來」。
這裡的 book 如 Groovy 第一行來說,已經是List  <Book> 了
book*.getId() 在這裡就是說「 book裡全部的物件都呼叫getId() 並塞到另一個新的List 中」
於是這個繁瑣的過程就這樣結束了。如果臨時 ui 又需要 book 裡所有的作者

["author_list" , book*.getAuthor()] 

馬上一行就可以解決並傳到前端去…
再一次說明,語法上的甜頭,並不是不重要的事,語法上的精簡,可以大大增加

  • 易讀性
  • 開發速度
  • 減少出錯機率







2012年8月8日 星期三

jQuery - 如何加入自動完成 ? How to achieve auto complete function?

第一步你要先 download jQuery,並且在jsp 中宣告 :

<script type="text/javascript" src="js/jquery-1.3.2.js"></script>
<script type="text/javascript" src="js/jquery-ui-1.8.7.custom.min.js"></script>

版本號就看你下載的jQuery 來宣告。
接下來在你的 js 中,針對某一個 input 欄位,此例為料號,加入 auto complete 的功能 。

$("#material_text").autocomplete({
        source : function(request, response) {
           
            $.ajax({
                url : $("#path").val() + 'ajax.action?method=list&act=find_material_text',
                type : "POST",
                dataType : "json",
                cache : false,
                delay : 1500,
                data : {
                    term : request.term
                },
                success : function(data) {
                //    $("#material_desc").unblock();
                    response(
                        $.map(data , function(item) {
                            return {
                                label : item.label,
                                value : item.value,
                                desc : item.desc
                            };
                        })
                    );
                },
                error: function(XMLHttpRequest, textStatus, errorThrown) {
                    alert(textStatus);
                
                }
            });
        },
        select : function(event, ui) {
            $("#product_desc").attr('value', ui.item.desc);            
        }     
    });

以下針對上面的程式做簡單說明。


自動完成,主要是搭配 ajax 的技術,來動態向後端要值。此例是將回傳結果,回傳成類似下拉式的選單,讓使用者可以從這些選單做選擇,而不用記得全部的輸入值資料。可以在 SQL 中做 LIKE ,或是其它搜索方式,讓使用者不一定要從「頭」開始輸入。


delay 是輸入後趨動查詢的間隔時間,如果使用者想要馬上打馬上看,那間隔可以短一點,不過對後台的呼叫次數當然也會變多,若機器不是很強的時候可以調慢一點。


 data : {
          term : request.term
         },


這是要送到後台去的,term 可以在後面當成一個 parameter 來抓取。就是使用者目前輸入的值。


select : function(event, ui) {
            $("#product_desc").attr('value', ui.item.desc);            
        } 


這裡是指,當使用者「點選了自動完成給你的結果集」之後所觸發的事件。
若沒有要特別做什麼,這裡可以不用去實作。
這裡是將 ui.item.desc 的值設入 某個 html input element 中 。
ui.item 是固定的,就是後台回傳的一個一個物件。
desc 是你自己可以在後台加入的屬性。
desc 我是放料號的說明。等下會解釋。

這樣前端的部份大概就好了。再來後端要提供觸發回傳的結果集有哪些。
至於這一段方法怎麼去觸發,就是看你web framework 使用的是哪一種,簡單來說就是要觸發到這一段,並且要用 ajax + json 的方式吐回去。


value = p("term");
sb.append("[");
List<Object[]> list = sap.createSQLQuery("select top 500 MATERIAL,MATERIAL_DESC from  MATERIAL where 1=1  and (material like  :val  or MATERIAL_DESC like :val )")
.setParameter("val", "%" + value + "%").list();
int i = 0;
for (Object[] ob : list) {
    sb.append(String.format("{\"label\":\"%s\",\"value\":\"%s\",\"desc\":\"%s\"}", getString(ob[0]) + " " + getString(ob[1]).replaceAll("\"", ""), getString(ob[0]), getString(ob[1])));
    if (i != list.size() - 1)
        sb.append(",");
    i++;
}
sb.append("]");

首先我只撈前500筆,若太多的話前端顯示會當掉。
可以用關鍵字查詢。
 json 裡則有三個屬性 label , value 與 desc ,desc是我自己加的,要和前端的程式同名才能使用。

  1. label 放料號的 id + 說明。這個是顯示用的。
  2. value 則放料號的 id。這個值在點了之後,會真的塞到原來的 input field中。
  3. desc 則只放料號的說明。我拿這個值來塞到畫面別處,讓使用者可以看到很長的料號說明。 將此三值分開,在前端可以方便使用。

組json 字串是當初花比較多時間的部份。


getString = String.valueOf 而已。  p 是取parameter 我自己簡化過的方式。

jQuery - 如何隱藏 select 下的 option. How to hide the options in select using jQuery.

$("#sel option").hide();    ?

沒辦法耶…

因為hide 不是跨瀏覽器的,至少 IE 8 無法達到這個效果。

網路上有一些solution,但是要以plugin 的方式進行,我覺得沒有必要。

最簡單的原則就是 「把option 搬走」

先宣告變數來裝。


var from_text="" ;
var to_text="";


$("#from_year").change(function(){
            if($(this).val()==''){                                                 
                from_text = $("#from_Q").html();                
                $("#from_Q option").remove();
            }else{
                $("#from_Q").html(from_text);
            }
        });

如果年選成空白的時候,把第幾季(Q1-Q4的select)的選擇搬走。
如果年選回某一年的時候,再把第幾季的文字加回來即可。


2012年8月3日 星期五

Java - 如何透過jco 與 TimerTask 做自動資料同步

前提 :

  1. 自己的資料庫準備好對應的sap表格,欄位名稱和格式要確定好了。
  2. 在servlet 中也要設定這個程式何時要跑,這個就不多提了。
  3. 這個class 會繼承 TimerTask ,當servlet 設定的時間條件觸發時,便會執行。

說明 : 

  1. 本程式提供了成功、失敗寄信的考量。當失敗時會將java exception寄出。
  2. 若sap 的程式有變,只要在db 的表格中新增sap 多的欄位即可,不用動程式。
  3. 每100筆就會flush 出去,不會快速增加無用的暫存記憶體。



public void run() {
        Session s = null;
        JCoDestination jCoDestination = null;
        Transaction tx = null;
        Boolean trans_done = false;
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");
       
        synchronized (this) { // 一次只能有一個進程在執行
            try {    
                Boolean need_to_run = true;
                s = BaseDAO.getSession();
                tx = s.beginTransaction();
                RptLogger.logger.info("== START ==");
                RptLogger.logger.info(new Date());
                //如果沒有執行過 ,或是執行過失敗的時候會自動跑 : 有
              
                if(need_to_run){
 
                    RptLogger.logger.info("try to connect to  SAP Table ...");                                                
                    jCoDestination = ConnectSAPServer.ConnectDETest();
                    RptLogger.logger.info("connect to  SAP Table OK!");
                    JCoFunction function = jCoDestination.getRepository().getFunction("sap 提供的function name");
                    RptLogger.logger.info("get function OK!");
                    if (function == null)
                        throw new RuntimeException("function not found in SAP.");
    
                    // 傳入的參數
                    // SimpleDateFormat sdf = new SimpleDateFormat("MM/dd/yyyy");
                    Calendar ca = Calendar.getInstance();
                    int this_year =  ca.get(Calendar.YEAR);
                    int this_month = ca.get(Calendar.MONTH)+1;
                    ca.add(Calendar.MONTH, -1);
                    int pre_year  = ca.get(Calendar.YEAR);
                    int pre_month = ca.get(Calendar.MONTH)+1;
                    //這邊是視你sap 程式的參數需要而加。
                    function.getImportParameterList().setValue("SALES_YEAR", this_year);
                    function.getImportParameterList().setValue("SALES_MONTH",this_month);
                    function.getImportParameterList().setValue("SALES_YACO", "COGS");
    
                    function.execute(jCoDestination);
    
                    JCoTable returnTable = function.getTableParameterList().getTable("sap 吐出來的表格名稱");
                    RptLogger.logger.info("get SAP Table OK!");
    
                    
                    List<String> header_cols = s.createSQLQuery("select COLUMN_NAME from INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = '你自己db的表格名稱'").list();
                         
                    String sql = "Insert into 你自己db的表格名稱(  ";
                    int x = 0;
                    for (String string : header_cols) {
                        sql = sql + string;
                        if (x != header_cols.size() - 1) {
                            sql = sql + ",";
                        }
                        x++;
                    }
                    x = 0;
                    sql = sql + ") values (";
                    for (String string : header_cols) {
                        sql = sql + "?";
                        if (x != header_cols.size() - 1) {
                            sql = sql + ",";
                        }
                        x++;
                    }
                    sql = sql +" ) ";
                    Query q = s.createSQLQuery(sql);
                 
                    RptLogger.logger.info("There are " + returnTable.getNumRows() + " rows to be inserted.");
                    if (returnTable.getNumRows() > 0) {
                        
                        returnTable.firstRow();
                        for (int i = 0; i < returnTable.getNumRows(); i++, returnTable.nextRow()) { // 總共幾筆資章要塞
                            x = 0;
                            for (String col : header_cols) {
                                q.setParameter(x, returnTable.getValue(col));
                                x++;
                            }
                            q.executeUpdate();
                            if (i % 100 == 0) {
                                s.flush();
                                s.clear();
                            }
                        }
                        s.flush();
                                                                       
                        tx.commit();
                        trans_done = true;
                        RptLogger.logger.info("== YURSWEBFICS END ==");
                    }
                    if(trans_done){                       
                        sendOKEmail();
                    }
                }
            } catch (Exception ex) {
                if (tx != null)
                    tx.rollback();
                ex.printStackTrace();
                StringWriter sw = new StringWriter();
                ex.printStackTrace(new PrintWriter(sw));
                RptLogger.logger.info(sw.toString());                                  
                sendWrongEmail(sw);                                                
            } finally {
                if (s != null && s.isOpen()) {
                    s.close();
                }
             
            }
        }
    }
    
    private void sendOKEmail() {
        MessageSender msender = MessageSender.getInstance();
        //寄ok信
        
    }

    private void sendWrongEmail(StringWriter sw) {
        MessageSender msender = MessageSender.getInstance();
        //寄失敗信,把java exception 寄出去,讓it可以第一時間抓到問題點。
    }
    

Java - 如何透過jco 連接SAP 程式

這一篇主要是一個共用的類別來連結,下一篇會提供可以動態將SAP 吐出來的表格原封不動的塞入自己Database的表格,當然,你這個自己的表格內容要和SAP 的一樣,可以少,但是不可以多。詳細內容是下一篇會介紹。


package com.deu.report.util;

import java.io.File;
import java.io.FileOutputStream;
import java.util.Properties;

import com.sap.conn.jco.JCoDestination;
import com.sap.conn.jco.JCoDestinationManager;
import com.sap.conn.jco.JCoException;
import com.sap.conn.jco.ext.DestinationDataProvider;

public class ConnectSAPServer {
     //SAP服務器IP地址 
   // static  String DEI_SAP_CONN_SERVER =   ; 
    //static  String T11 =  "172.test.test.test" ; 
     
    static  String DE_SAP_CONN =  "DE_SAP_CONN" ; 
    static  String DE_TEST_SAP_CONN =  "DE_TEST_SAP_CONN" ; 
     
    static { 
        Properties connectProperties =  new  Properties(); 
        connectProperties.setProperty(DestinationDataProvider.JCO_ASHOST, "172.test.test.test");  
        connectProperties.setProperty(DestinationDataProvider.JCO_SYSNR,   "00" );         //系統編號 
        connectProperties.setProperty(DestinationDataProvider.JCO_CLIENT,  "025" );        //SAP集團 
        connectProperties.setProperty(DestinationDataProvider.JCO_USER,    "test" );   //SAP用戶名 
        connectProperties.setProperty(DestinationDataProvider.JCO_PASSWD,  "test" );      //密碼 
        connectProperties.setProperty(DestinationDataProvider.JCO_LANG,    "EN" );         //登錄語言 
        connectProperties.setProperty(DestinationDataProvider.JCO_POOL_CAPACITY,  "3" );   //最大連接數 
        connectProperties.setProperty(DestinationDataProvider.JCO_PEAK_LIMIT, "10" );      //最大連接線程 
     
        createDataFile(DEI_SAP_CONN,  "jcoDestination" , connectProperties); 
        
        connectProperties =  new  Properties(); 
        connectProperties.setProperty(DestinationDataProvider.JCO_ASHOST, "172.16.test.test");  
        connectProperties.setProperty(DestinationDataProvider.JCO_SYSNR,   "00" );         //系統編號 
        connectProperties.setProperty(DestinationDataProvider.JCO_CLIENT,  "025" );        //SAP集團 
        connectProperties.setProperty(DestinationDataProvider.JCO_USER,    "test" );   //SAP用戶名 
        connectProperties.setProperty(DestinationDataProvider.JCO_PASSWD,  "test" );      //密碼 
        connectProperties.setProperty(DestinationDataProvider.JCO_LANG,    "EN" );         //登錄語言 
        connectProperties.setProperty(DestinationDataProvider.JCO_POOL_CAPACITY,  "3" );   //最大連接數 
        connectProperties.setProperty(DestinationDataProvider.JCO_PEAK_LIMIT, "10" );      //最大連接線程 
     
        createDataFile(DE_TEST_SAP_CONN,  "jcoDestination" , connectProperties); 
        
    } 
     
    static  void  createDataFile(String name, String suffix, Properties properties){ 
        File cfg =  new  File( name+ "." +suffix); 
        if (!cfg.exists()){ 
            try { 
                FileOutputStream fos =  new  FileOutputStream(cfg,  false ); 
                properties.store(fos,  "for DEI only !" ); 
                fos.close(); 
            } catch  (Exception e){ 
                throw  new  RuntimeException( "Unable to create the destination file "  + cfg.getName(), e); 
            } 
        } 
    } 
 
    public  static  JCoDestination ConnectDE(){ 
        JCoDestination destination = null ;    
        try  { 
            destination = JCoDestinationManager.getDestination( DE_SAP_CONN ); 
        }  catch  (JCoException e) { 
            e.printStackTrace();
        } 
         
        return  destination; 
    } 
    public  static  JCoDestination ConnectDETest(){ 
        JCoDestination destination = null ;    
        try  { 
            destination = JCoDestinationManager.getDestination( DE_TEST_SAP_CONN ); 
        }  catch  (JCoException e) { 
            e.printStackTrace();
        } 
         
        return  destination; 
    } 
}

Java - jco3 如何在windows + tomcat 下使用 ( 部署篇)

SAP JCO 主要用於 Java 呼叫 Sap 程式時使用。當然sap 的程式要開放remote call 與帳號密碼。

1. 首先要下載 jco  的不同版本,windows 主要是分32位元與64位元。
( 這部份可以向貴公司的sap 管理者請求,因為下載時需要提供sap伺服器的資訊 )

2. 將 sapjco3.jar 丟到 tomcat 的共用lib 下面。

3. 將 sapjco3.dll 丟到 C:\\Windows\System32 下

4. 要安裝 Microsoft Visual C++ 2005 SP1 在 tomcat server 那台機器上! 否則無法使用。

下二篇將紀錄如何透過 jco 呼叫 SAP 的程式。