ดึงภาพจาก Flickr ด้วย Spring RestTemplate

October 16th, 2009 by roofimon Leave a reply »

ต้นฉบับ http://blog.springsource.com/2009/03/27/rest-in-spring-3-resttemplate/
source code สามารถ checkout ได้ที่ https://spring-rest-template.googlecode.com/svn/trunk/
RESTFul ครองโลก ดังนั้นเพื่อไม่ให้เป็นการตกเทรนด์สปริงสามจึงเตรียม RestTemplate เป็นคลาสหลักสำหรับการเรียกใช้งาน REST เซอร์วิสเหล่านั้น โดยเราสามารถมอง RESTTemplate ให้เหมือนกับ JdbcTemplate หรือ JmsTemplate หรือ tempate อื่นๆที่สปริงเตรียมไว้ให้ ดังนั้น Template นี้จึงเป็น thread-safe เช่นกันเมื่อมันถูกสร้างขึ้นมาแล้วเราสามารถใช้ Callback เมธอดเพื่อกำหนดการทำงานโดยละเอียดตามใจเราภายในของมันได้อีกเช่นกัน
RestTemplate เมธอดมีให้เรียกใช้งานได้ 6 เมธอดตาม HTTP เมธอด:

DELETE delete(String, String…)
GET getForObject(String, Class, String…)
HEAD headForHeaders(String, String…)
OPTIONS optionsForAllow(String, String…)
POST postForLocation(String, Object, String…)
PUT put(String, Object, String…)

ชื่อเมธอดจะขึ้นต้นด้วยมาตรฐาน HTTP เมธอดตามด้วยผลที่เราจะได้รับจากการเรียกใช้ ( getForObject = get (ชื่อ HTTP เมธอด)+ Object(ผลที่ได้รับกลับ) )
URI Templates
อยากเอกสารอ้างอิงเราจะเห็นได้ว่าทุกๆเมธอดจะรับพารามิเตอร์ตัวแรกเป็น URI เสมอซึ่งเราสามารถเตรียม URI เป็น Template ได้เพื่อเตรียมตัวแปรที่ต้องการใช้ไว้ก่อนหลังจากนั้นเราสามารถกำหนดค่าของตัวแปรที่ต้องการใช้ผ่านกระบวนการได้สองแบบคือ กำหนดค่าเป็นอาร์เรย์ของ String หรือ ใช้ Map ดังตัวอย่างด้านล่าง

String result = restTemplate.getForObject("http://example.com/hotels/{hotel}/bookings/{booking}", String.class, "42", "21");

ตัวอย่างนี้เราจะทำการเรียก GET และส่ง URI ที่มีรูปแบบดังนี้ http://example.com/hotels/42/bookings/21 วิธีการนี้เป็นการกำหนดตัวแปรในรูปแบบของอาร์เรย์ของ String ส่วนต่อไปเป็นการกำหนดตัวแปรด้วย Map

Map<String, String> vars = new HashMap<String, String>();
vars.put("hotel", "42");
vars.put("booking", "21");
String result = restTemplate.getForObject("http://example.com/hotels/{hotel}/bookings/{booking}", String.class, vars);

HttpMessageConverters
Objects ที่ถูกส่งผ่านกลับมาจากการเรียกใช้เซอร์วิสเช่น getForObject(), postForLocation(), และ put() จะถูกแปลงเป็น HTTP requests และผลที่ได้จาก HTTP response จะถูกแปลงต่อจาก MIME types ไปเป็น Java Types ได้อยู่แล้วแต่ในกาณีที่เราต้องการเขียนวิธีการแปลงเองก็สามารถทำได้ดังนี้
ตัวอย่างการใช้งาน Spring RESTTemplate และการเขียน HttpMessageConverter เอง
ต่อไปจะเป็นตัวอย่างการใช้งาน RestTemplate ด้วยการดึงภาพมาจาก Flickr ด้วยคำค้นที่เราต้องการแต่ตัวอย่างนี้เราจะแสดงภาพที่ได้จาก Flickr ลงไปที่ Swing UI เพื่อจำลองการทำงานของ HTTP client แต่กก่อนอื่นเราต้องมี API key สำหรับ Flickr ก่อน

การค้นหาภาพ
Flickr เตรียม เพื่อให้เราทำงานกับภาพไว้มากมายยกตัวอย่างเช่น flickr.photos.search เมธอดนั้นเราสามารถค้นหาภาพได้จากการใช้ GET Request http://www.flickr.com/services/rest?method=flickr.photos.search&api+key=xxx&tags=penguins, โดยเราจำเป็นต้องป้อน API key ลงไปและหลังจากส่ง request ไปเราจะได้รับผลที่เป็น XML กลับมา

<photos page="2" pages="89" perpage="10" total="881">
	<photo id="2636" owner="47058503995@N01"
		secret="a123456" server="2" title="test_04"
		ispublic="1" isfriend="0" isfamily="0" />
	<photo id="2635" owner="47058503995@N01"
		secret="b123456" server="2" title="test_03"
		ispublic="0" isfriend="1" isfamily="1" />
	<photo id="2633" owner="47058503995@N01"
		secret="c123456" server="2" title="test_01"
		ispublic="1" isfriend="0" isfamily="0" />
	<photo id="2610" owner="12037949754@N01"
		secret="d123456" server="2" title="00_tall"
		ispublic="1" isfriend="0" isfamily="0" />
</photos>

ซึ่งเราสามารถส่ง request ไปด้วย restTemplate ได้ดังนี้

final String photoSearchUrl =  "http://www.flickr.com/services/rest?method=flickr.photos.search&amp;amp;amp;amp;amp;amp;api+key={api-key}&amp;amp;amp;amp;amp;amp;tags={tag}&amp;amp;amp;amp;amp;amp;per_page=10";
Source photos = restTemplate.getForObject(photoSearchUrl, Source.class, apiKey, searchTerm);

โดยที่ apiKey กับ searchTerm เป็นพารามิเตอร์ที่ต้องรับมาจากภายนอก ส่วน Source.class ทำหน้าที่เป็น SourceHttpMessageConverter เพื่อแปลงผลที่ได้จาก HTTP XML ไปเป็น javax.xml.transform.Source

การดึงภาพ
หลังจากเราได้ XML ที่ถูกแปลงออกมาเป็น javax.xml.transform.Source แล้วเราจะต้องทำการดึงภาพจริงมาที่ฝั่ง client ด้วย XPatn expression โดยเราจะใช้ XPathTemplate ในโปรเจค Spring Web Services เราจะเริ่มจากการดึงอิลิเมนท์ //photo ทุกๆตัวออกมา ต่อไปเราจะอิมพลีเมนท์รายละเอียดเมธอด mapNode() ของ NodeMapper อินเทอร์เฟสเพื่อทำการดึงรายละเอียดต่างๆเช่น server, id และ secret และนำไปใส่ใน Map และเราจะใช้ RestTemplate อีกครั้งเพื่อดึงเอาภาพตัวจริงมาใส่ใน java.awt.image.BufferedImage

List<BufferedImage> imageList = xpathTemplate.evaluate("//photo", photos, new NodeMapper() {
    public Object mapNode(Node node, int i) throws DOMException {
        Element photo = (Element) node;

        Map<String, String> variables = new HashMap<String, String>(3);
        variables.put("server", photo.getAttribute("server"));
        variables.put("id", photo.getAttribute("id"));
        variables.put("secret", photo.getAttribute("secret"));

        String photoUrl = "http://static.flickr.com/{server}/{id}_{secret}_m.jpg";
        return restTemplate.getForObject(photoUrl, BufferedImage.class, variables);
    }
});

ดังนั้นผลที่ได้ใน imageList จะได้ภาพสี่ภาพและ URL ที่เจาจะไปดึงภาพมาคือ http://static.flickr.com/2/2636_ a123456_m.jpg, และ http://static.flickr.com/2/2635_ b123456_m.jpg, …

การแปลงภาพ
ส่วนต่อไปคือเราต้องเขียนรายละเอียดของ HttpMessageConverter ที่จะสามารถอ่านได้จาก HTTP response และสร้าง BufferedImage

public class BufferedImageHttpMessageConverter implements HttpMessageConverter<BufferedImage> {

    public List<MediaType> getSupportedMediaTypes() {
        return Collections.singletonList(new MediaType("image", "jpeg"));
    }

    public boolean supports(Class<? extends BufferedImage> clazz) {
        return BufferedImage.class.equals(clazz);
    }

    public BufferedImage read(Class<BufferedImage> clazz, HttpInputMessage inputMessage) throws IOException {
        return ImageIO.read(inputMessage.getBody());
    }

    public void write(BufferedImage image, HttpOutputMessage message) throws IOException {
        throw new UnsupportedOperationException("Not implemented");
    }

}

หลังจากนั้นเราต้องไปใส่รายละเอียดให้กับ applicationContext.xml

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="flickrClient" class="com.springsource.samples.resttemplate.FlickrClient">
        <constructor-arg ref="restTemplate"/>
        <constructor-arg ref="xpathTemplate"/>
    </bean>

    <bean id="restTemplate" class="org.springframework.web.client.RestTemplate">
        <property name="messageConverters">
            <list>
                <bean class="org.springframework.http.converter.xml.SourceHttpMessageConverter"/>
                <bean class="com.springsource.samples.resttemplate.BufferedImageHttpMessageConverter"/>
            </list>
        </property>
    </bean>

    <bean id="xpathTemplate" class="org.springframework.xml.xpath.Jaxp13XPathTemplate"/>

</beans>

ส่วนของการแสดงผล
เราจะนำผลที่ได้ไปแสดงใน Swing UI

JFrame frame = new JFrame(searchTerm + " photos");
frame.setLayout(new GridLayout(2, imageList.size() / 2));
for (BufferedImage image : imageList) {
    frame.add(new JLabel(new ImageIcon(image)));
}
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setVisible(true);
Advertisement

Leave a Reply

You must be logged in to post a comment.