JSP - Custom Tag

<?xml version="1.0" encoding="UTF-8"?>
<taglib version="2.0" xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee 
  http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd ">
  <tlib-version>1.2</tlib-version>
  <short-name>NMTOKEN</short-name>

  <uri>DiceFunction</uri>

  <!-- สำหรับ Custom Tag -->
  <tag>
    <description>random advice</description>
    <name>advice</name>
    <tag-class>package.AdvisorTagHandler</tag-class>
    <body-content> empty | scriptlet | tagdependent </body-content>

    <attribute>
      <name>user</name>
      <required> true | false </required>
      <rtexprvalue> true | false </rtexprvalue>
      <type>int | java.lang.String | etc. </type>
    </attribute>
    [<attribute> ... </attribute>]
    <dynamic-attributes> true | false </dynamic-attributes>
  </tag>

  <!-- สำหรับ tag file -->
  <tag-file>
    <name>Header</name>
    <path>/META-INF/tags/Header.tag</path>
  </tag-file>

  <!-- สำหรับ function -->
  <function>
    <name>rollIt</name>
    <function-class>com.amadmonster.scwcd.el.function.DiceRoller</function-class>
    <function-signature>int rollDice()</function-signature>
  </function>

  <!-- สำหรับ function ที่รับตัวแปร -->
  <function>
    <name>rollWith</name>
    <function-class>com.amadmonster.scwcd.el.function.DiceRoller</function-class>
    <function-signature>int rollDice(int)</function-signature>
  </function>

</taglib>

Custom Tag

<name> ของ <tag> จะเป็นชื่อที่ใช้อ้างใน JSP
<body-content> ของ <tag> สามารถเป็น
  • empty - ไม่มี body
  • scriptless - สามารถเป็น static text, EL expressions, standard action และ custom tags ได้ แต่จะใส่ JSP scriptlet () ไม่ได้
  • tagdependent - ค่า EL จะไม่ถูกแปล และถูกส่งให้ Custom Tag เสมือน plain-text เพื่อให้แปลด้วย Custom Tag เอง

<tag-class> เป็นชื่อ Package และ Class ของ Tag Handler
<attribute> จะบรรยายลักษณะของแต่ละ attribute ของ Custom Tag
<rtexprvalue> จะบอกว่า attribute ตัวนี้สามารถรับเป็น อย่างอื่นที่ไม่ใช่ text ได้หรือไม่ (เช่น EL expression, custom tag, scriptlet) (default: falue)
<type>
เป็นชนิดของ attribute ซึ่งใส่เป็น fully-qualified class name ยกเว้น primitive data type ซึ่งใส่ได้ตรงๆ เช่น int, char ในกรณีที่ไม่ใส่ ค่าอะไรเลย default จะเป็น java.lang.String
<dynamic-attributes> ใช้ config ว่า tag นี้จะรับ attribute แบบ dynamic หรือไม่ ซึ่งถ้าเป็น true ตัว tag handler จะต้อง implement DynamicAttributes


สำหรับ Custom Tag สามารถ implement ได้ 2 แบบ แบบแรกคือการ Simple Tag ซึ่งจะ extend SimpleTagSupport และมี life cycle ดังนี้




แบบที่ 2 คือ แบบ Classic Tag ซึ่งจะ extend TagSupport หรือ BodyTagSupport ซึ่งสามารถแก้ไข body ของ tag ที่จะแสดงออกไปได้ ซึ่งมี life cycle ดังนี้




และ State Diagram ของ TagSupport และ BodyTagSupport ดังนี้



Reference:
Tag Library Descriptor

JSTL - JSP Standard Tag Library


TagAttributeUsage
c:outvalueString ของค่าที่ต้องการจะแสดงผล
escapeXmlไม่ render XML หรือ HTML แ่ต่จะแสดงผลเหมือนกับ String ปกติ
default: true
defaultค่า default เมื่อ value เป็น null ซึ่งสามารถใช้ได้เช่นเดียวกับ
<c:out value="some string">default value</c:out>
c:forEachvarชื่อของตัวแปรที่ไว้รับ element ในแต่ละ loop
itemsชื่อของตัวแปรตัวแปร array, Collection, Map หรือ comma-delimited String ซึ่งเก็บแต่ละ element สำหรับการวนลูป
varStatueชื่อของตัวแปร javax.servlet.jsp.jstl.core.LoopTagStatus ซึ่งสามารถนำมาหาค่าต่างๆ เช่น count ต่อไป เช่น
<c:forEach var="movie" items="${movieList}"
varStatus="movieLoopCount">
 <tr><td>Count: ${movieLoopCount.count} </td></tr>
</c:forEach>

c:forTokensvarชื่อของตัวแปรที่ไว้รับ element ในแต่ละ loop
itemsชื่อของตัวแปรตัวแปร array, Collection, Map หรือ comma-delimited String ซึ่งเก็บแต่ละ element สำหรับการวนลูป
varStatueชื่อของตัวแปร javax.servlet.jsp.jstl.core.LoopTagStatus ซึ่งสามารถใช้ได้เช่นเดียวกับ varStatus ของ c:forEach
delimsString ที่เป็นตัวคั่นในการแบ่งแต่ละ token เช่น
<c:forTokens items="${dataString}" delims="," var="item">
Next item - ${item}
</c:forTokens>
beginตัวแรกที่จะเริ่มใช้ใน loop
endตัวสุดท้ายที่จะใช้ใน loop
stepตัวถัดที่จะใช้ใน loop
c:iftestค่าที่เป็น boolean เพื่อใช้ทดสอบว่าจะทำคำสั่งใน c:if หรือไม่
c:choose
ตัวนี้ไม่มี attribute จะใช้ ร่วมกับ c:when และ c:otherwise
<c:choose>
 <c:when test="${userPref == 'performance'}">
   ...do something...
 </c:when>
 <c:when test="${userPref == 'safety'}">
   ...do something...
 </c:when>
 <c:otherwise>
   ...do something...
 </c:otherwise>
</c:choose>
c:whentestเหมือนกับ attribute test ของ c:if คือใช้ทดสอบเงื่อนไขในการทำงาน
c:otherwise
ตัวนี้ไม่มี attribute ใช้ร่วมกับ c:when ดังนี้
c:setvarตัวแปรที่ต้องการ set ค่า ในกรณีที่ไม่มีตัวแปรชื่อนี้ ตัวแปรนี้ก็จะถูกสร้างขึ้นมา
scopescope ของตัวแปร
  • page (default)
  • request
  • session
  • application
default: page
valueค่าที่ต้องการจะ set หรือสามารถใส่ค่านี้เป็น body ของ tag ก็ได้ เช่น
c:settargetbean หรือ Map ที่ต้องการจะ set ค่า
propertyชื่อ property ที่ต้องการจะ set ค่า
valueค่าที่ต้องการจะ set หรือสามารถใส่ค่านี้เป็น body ของ tag ก็ได้ เช่น
<c:set target="${person}" property="name">new name</c:set>
c:removevarตัวแปรที่ต้องการลบออก
scopescope ของตัวแปรที่ต้องการจะลบ
  • page (default)
  • request
  • session
  • application
default: page
c:importurlc:import จะใช้ได้เหมือน jsp:include แต่ว่าสามารถอ้างถึงไฟล์ได้ทั้งใน application และนอก web application โดยระบุ URL ของไฟล์ที่ต้องการจะใส่เข้ามา
c:paramnamec:param ใช้ในการส่งตัวแปรข้ามไปยัง included page เหมือนกับ jsp:param โดยระบุชื่อของตัวแปรที่ต้องการจะส่งไป
valueค่าของตัวแปรที่จะส่งไป ใช้ร่วมกับ c:import หรือ c:url ซึ่งค่าของตัวแปรก็จะ encodeUrl แล้ว และสามารถส่งได้อย่างถูกต้อง ตัวอย่างเช่น
<c:import url="Header.jsp">
 <c:param name="subTitle" value="any sub-title for this page" />
<c:import>
c:urlvaluepath และ file ที่ต้องการจะ link ไป โดย c:url จะจัดการเรื่อง session ให้อัตโนมัติ เช่นในกรณีที่ client ไม่รับ cookies มันจะใส่ session id ต่อท้าย url ให้อัตโนมัติ แต่มันจะไม่ได้ทำ encodeUrl ให้ดังนั้นถ้าใน URL มีค่าตัวแปรที่เป็นอักขระพิเศษสำหรับ HTML เช่น space ก็จะทำให้ error ได้
c:redirecturlurl ที่จะ redirect ไป ซึ่งจะมีการ rewrite URL เพื่อจัดการ session ให้อัตโนมัติ
c:catchvarสำหรับใส่ code ที่เสี่ยงจะ throw exception ถ้ามี exception เกิดขึ้น มันจะข้าม block ของ c:catch ไปทำคำสั่งต่อจากนั้นเลย และ property var เป็นชื่อของตัวแปร exception ซึ่งจะใช้หลังจาก c:catch ได้ เช่น
<c:catch var="ex">
 <% int x = 1/0 %>
</c:catch>
If error occurs with ${ex}
*ที่เขียน c:set แยกออกมาเพราะว่ามันสามารถ set ได้ทั้ง ตัวแปร และ Bean หรือ Map ซึ่งจะใช้ property ที่ต่างกันจึงเขียนแยกกันเพื่อความเข้าใจง่าย


Reference:
JSTL Reference Documentation
Setup Jstl for Jsp2

Java App: Send Keystoke to Windows

สำหรับการส่งการกด keyboard โดยผ่านทาง java นะค่ะ ง่ายๆ เราใช้ java.awt.Robot เป็นตัวช่วยในการส่ง keystoke ตัวอย่างด้านล่างเป็นแค่ส่วนหนึ่งของ program ที่ดึงมาให้ดูเฉพาะส่วนที่ต้องการส่ง keystoke นะค่ะ



import java.awt.AWTException;
import java.awt.Robot;
import java.awt.event.KeyEvent;

...
try {
Robot robot = new Robot();
// ส่ง key A ไป
robot.keyPress(KeyEvent.VK_A);
} catch (AWTException e) {
e.printStackTrace();
}
...



Reference:
Sending keystrokes to GUI applications

Java App: Send SMTP Mail Example

การส่งเมล์โดยใช้ java สามารถใช้ class ใน javax.mail ได้ ซึ่งตัวอย่างข้างล่างเป็น utility ที่สามารถเรียกใช้ได้เลย

MailUtility.java

package com.amadmonster.util.mail;

import java.util.Date;
import java.util.Properties;

import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;

import org.apache.log4j.Logger;

public class MailUtility {
/** Get logger. */
private static Logger logger = Logger.getLogger(MailUtility.class);
Properties props;

public MailUtility(String smtpServer) {
props = System.getProperties();
props.put("mail.smtp.host", smtpServer);
}

public void sendMail(String to, String from, String subject, String body) throws MailUtilityException {
Session session = Session.getDefaultInstance(props);
session.setDebug(false);

Message msg = new MimeMessage(session);
try {
msg.setFrom(new InternetAddress(from));
msg.setRecipients(Message.RecipientType.TO, InternetAddress.parse(to, true));
msg.setSubject(subject);
// check mime type from Web: Content Type
msg.setContent(body, "text/plain");
msg.setText(body);
msg.setSentDate(new Date());
// -- Send the message --
Transport.send(msg);
logger.info("Message sent OK.");

} catch (AddressException e) {
throw new MailUtilityException("Incorrrect address format", e);
} catch (MessagingException e) {
throw new MailUtilityException("Error occurs when sending mail", e);
}

}
}

MailUtilityException.java


package com.amadmonster.util.mail;

public class MailUtilityException extends Exception {

public MailUtilityException(String message, Throwable cause) {
super(message, cause);
}

public MailUtilityException(String message) {
super(message);
}

public MailUtilityException(Throwable cause) {
super(cause);
}

}


Reference:
JavaMail quick start

Java App: Windows Command Line Example

สำหรับการ run command line ผ่านทาง Java นั่นสามารถใช้ method Runtime.getRuntime().exec() ได้ แต่ว่าจะ run ตรงๆ ไม่ได้ จะต้องส่ง C:\WINDOWS\system32\cmd.exe /y /c ไปก่อน ตัวอย่างข้างล่างเป็นการเขียน utility class เพื่อที่จะนำไปใช้ต่อไปได้

CommandUtility.java


package com.amadmonster.util.command;

import java.io.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.apache.log4j.Logger;

public class CommandUtility {
static Logger logger = Logger.getLogger(CommandUtility.class);
private int status; // return command status, 0 - normal exit

/**
* Run Window command on specific working directory.
* @param command Array of command string and option.
* @param workDir String of working directory.
* @throws CommandUtilityException
*/
public void runCommand(String[] command, String workDir) throws CommandUtilityException {
String[] cmd = { "C:\\WINDOWS\\system32\\cmd.exe", "/y", "/c" };
List cmdList = new ArrayList();
cmdList.addAll(Arrays.asList(cmd));
cmdList.addAll(Arrays.asList(command));

Process p;
try {
p = Runtime.getRuntime().exec(cmdList.toArray(new String[0]), null,
new File(workDir));

logCommandInputStream(p);
logCommandErrorStream(p);
setStatus(p.exitValue());

if (getStatus() != 0) {
throw new CommandUtilityException("Fail to run command.");
}

} catch (IOException e) {
// TODO Auto-generated catch block
throw new CommandUtilityException("Fail to run command.", e);
}

}

private void logCommandInputStream(Process p) throws IOException {
InputStream is = p.getInputStream();
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
String line;
while ((line = br.readLine()) != null) {
logger.info(line);
}

}

private void logCommandErrorStream(Process p) throws IOException, CommandUtilityException {
InputStream is = p.getErrorStream();
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
String line;
while ((line = br.readLine()) != null) {
logger.error(line);
}

}

/**
* Run Window command on specific working directory.
* @param command Command string.
* @param workDir String of working directory.
* @throws CommandUtilityException
*/
public void runCommand(String command, String workDir) throws CommandUtilityException {
String[] cmd = { command };
runCommand(cmd, workDir);
}

/**
*
* @return int Status of running command, 0 - normal
*/
public int getStatus() {
return status;
}

/**
*
* @param status Exit command status
*/
private void setStatus(int status) {
this.status = status;
}

}


CommandUtilityException.java

package com.amadmonster.util.command;

public class CommandUtilityException extends Exception {

public CommandUtilityException(String message, Throwable cause) {
super(message, cause);
}

public CommandUtilityException(String message) {
super(message);
}

public CommandUtilityException(Throwable cause) {
super(cause);
}

}


Reference:
When Runtime.exec() won't