マイペースなプログラミング日記

DTMやプログラミングにお熱なd-kamiがマイペースに書くブログ

簡単なRSSリーダーを作ってみた その2

前回作ったやつはフィードの追加が出来なかったのでフィードが追加できるように変更した。ただし、追加したフィードの保存機能はなし。なので、別のページにいったり、ブラウザを閉じるとその内容は失われる。
できあがったもの
http://rsstest.dkami.staxapps.net/

フィードの追加は左のテキストフィールドに入力し、追加ボタンを押す。HTMLからフィード探して追加するような機能はついてないので、直接フィードのURLを入力する必要がある

ソース、まずhtmlから、ファイル名はHomePage.html

<html>
    <head>
        <title>Wicket Homepage</title>
    </head>
    <body>
        <span wicket:id="feedback"></span>
        <table style="width:100%; height: 100%;">
            <tr>
                <td style="width:30%; height:100%; vertical-align:top" wicket:id="feedParent">
                    <div wicket:id="feedList">
                        <a wicket:id="feedLink"><span wicket:id="linkText">Java Category</span></a>
                    </div>
                    <br />
                    <form wicket:id="addFeedForm">
                        <input type="text" style="width:100%", wicket:id="addFeedField" />
                        <input type="button" value="追加" wicket:id="addFeedButton" />
                    </form>
                </td>
                
                <td style="width:70%; height:100%; text-align:left; vertical-align:top" wicket:id="entryParent">
                    <dl wicket:id="entryList">
                        <dt><a href="http://test.com" wicket:id="entryLink">Entry Title</a> <span wicket:id="publishDate">2009/1/27</span></dt>
                        <dd><span wicket:id="entryDesc">Entry Description</span></dd>
                    </dl>
                </td>
            </tr>
        </table>
    </body>
</html>

次に対応するJavaのソース、ファイル名はHomePage.java

package example;

import org.apache.wicket.markup.html.WebPage;
import org.apache.wicket.markup.html.WebMarkupContainer;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.list.ListView;
import org.apache.wicket.markup.html.list.ListItem;
import org.apache.wicket.markup.html.link.ExternalLink;
import org.apache.wicket.ajax.markup.html.AjaxLink;
import org.apache.wicket.markup.html.panel.FeedbackPanel;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.form.TextField;
import org.apache.wicket.ajax.markup.html.form.AjaxButton;

import org.apache.wicket.model.IModel;
import org.apache.wicket.model.Model;
import org.apache.wicket.model.LoadableDetachableModel;

import org.apache.wicket.ajax.AjaxRequestTarget;

import com.sun.syndication.feed.synd.SyndEntry;
import com.sun.syndication.feed.synd.SyndFeed;
import com.sun.syndication.fetcher.FeedFetcher;
import com.sun.syndication.fetcher.impl.HttpURLFeedFetcher;
import com.sun.syndication.fetcher.FetcherException;
import com.sun.syndication.io.FeedException;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.ArrayList;
import java.util.Calendar;

import java.net.URL;
import java.io.IOException;

public class HomePage extends WebPage {

    private static final long serialVersionUID = 1L;

    //現在表示しているフィードのURL
    private String feedURL = "";

    private List<FeedLink> feedList;

    public HomePage() {
        
        feedList = new ArrayList<FeedLink>();
        feedList.add(new FeedLink("Javaカテゴリ", "http://k.hatena.ne.jp/keywordblog/Java?mode=rss"));
        feedList.add(new FeedLink("Wicketカテゴリ", "http://k.hatena.ne.jp/keywordblog/Wicket?mode=rss"));
        
        //エラーメッセージを表示するFeedbackPanel
        final FeedbackPanel feedback = new FeedbackPanel("feedback");
        //Ajaxで更新可能にする
        feedback.setOutputMarkupId(true);
        
        //フィードに含まれるエントリの一覧を表示するListView
        ListView entryListView = createEntryListView();
        //ListViewの親コンポーネント(AjaxでListViewを更新するのに必要)
        WebMarkupContainer entryParent = new WebMarkupContainer("entryParent");
        //Ajaxで更新可能にする
        entryParent.setOutputMarkupId(true);
        
        //ListViewの親コンポーネント
        final WebMarkupContainer feedParent = new WebMarkupContainer("feedParent");
        //Ajaxで更新可能にする
        feedParent.setOutputMarkupId(true);
        
        //フィードの一覧を表示するListView
        ListView feedListView = createFeedListView(entryParent, feedback);
        
        //フィード追加用のフォーム
        Form addFeedForm = new Form("addFeedForm");
        final TextField<String> addFeedField = new TextField<String>("addFeedField", new Model<String>());
        addFeedField.setOutputMarkupId(true);
        
        AjaxButton addFeedButton = new AjaxButton("addFeedButton"){
            @Override
            public void onSubmit(AjaxRequestTarget target, Form form){
                String url = addFeedField.getModelObject();
                
                try{
                    //フィードのURLからタイトルを取得する
                    FeedFetcher fetcher = new HttpURLFeedFetcher();
                    SyndFeed feed = fetcher.retrieveFeed(new URL(url));
                    String title = feed.getTitle();
                    
                    FeedLink feedLink = new FeedLink(title, url);
                    feedList.add(feedLink);
                    target.addComponent(feedParent);
                }catch(FetcherException e){
                    error("フィードの取得に失敗しました: " + e.getMessage());
                }catch(IOException e){
                    error("フィードの取得に失敗しました: " + e.getMessage());
                }catch(FeedException e){
                    error("フィードの取得に失敗しました: " + e.getMessage());
                }finally{
                    target.addComponent(feedback);
                    addFeedField.setModelObject("");
                    target.addComponent(addFeedField);
                }
            }
        };
        


        addFeedForm.add(addFeedField);
        addFeedForm.add(addFeedButton);
        
        feedParent.add(feedListView);
        feedParent.add(addFeedForm);

        entryParent.add(entryListView);
        
        add(feedback);
        add(feedParent);
        add(entryParent);
    }
    
    //フィードのエントリ一覧を表示するListViewを作成する
    private ListView createEntryListView(){
        IModel<List<SyndEntry>> entryListModel = new LoadableDetachableModel<List<SyndEntry>>(){
            protected List<SyndEntry> load(){
                //エントリの一覧を返す
                return getEntryList();
            }
        };
        
        ListView listView = new ListView( "entryList", entryListModel) {
            @Override
            protected void populateItem(ListItem item) {
                SyndEntry entry = (SyndEntry)item.getModelObject();
                item.add(new ExternalLink("entryLink", entry.getLink(), entry.getTitle()));
                
                Date updateDate = entry.getUpdatedDate();
                if(updateDate == null) updateDate = entry.getPublishedDate();
                
                //時間を日本時間に合わせる
                SimpleDateFormat format = new SimpleDateFormat( "yyyy-MM-dd HH:mm");
                Calendar calendar = Calendar.getInstance();
                calendar.setTime(updateDate);
                calendar.add(Calendar.HOUR_OF_DAY, 9);
                updateDate = calendar.getTime();

                item.add(new Label("publishDate", format.format( updateDate)));
                
                String description = entry.getDescription().getValue();
                
                //ディスクリプションに含まれるタグを取り除く
                description = description.replaceAll("<[^>]+>", "").replaceAll("</[^>+]>", "");
                
                item.add(new Label( "entryDesc", description));
            }
        };

        return listView;        
    }
    
    private List<SyndEntry> getEntryList(){
        if(feedURL.trim().length() == 0){
            //フィードのURLが空文字列なら空のリストを返す
            return new ArrayList<SyndEntry>();
        }
        
        try{
            //フィードのURLからフィードのエントリ一覧を取得し返す
            FeedFetcher fetcher = new HttpURLFeedFetcher();
            SyndFeed feed = fetcher.retrieveFeed(new URL(feedURL));
            return feed.getEntries();
        }catch(FetcherException e){
            error("フィードの取得に失敗しました: " + e.getMessage());
        }catch(IOException e){
            error("フィードの取得に失敗しました: " + e.getMessage());
        }catch(FeedException e){
            error("フィードの取得に失敗しました: " + e.getMessage());
        }
        
        return new ArrayList<SyndEntry>();
    }
    
    private ListView createFeedListView(final WebMarkupContainer entryParent, final FeedbackPanel feedback){
        IModel<List<FeedLink>> feedListModel  = new LoadableDetachableModel<List<FeedLink>>(){
            protected List<FeedLink> load(){
                //フィードの一覧を返す
                return getFeedList();
            }
        };
        
        ListView listView = new ListView("feedList", feedListModel){
            @Override
            protected void populateItem(ListItem item){
                final FeedLink feedLink = (FeedLink)item.getModelObject();
                Label label = new Label("linkText", feedLink.getText());
                
                AjaxLink link = new AjaxLink("feedLink"){
                    @Override
                    public void onClick(AjaxRequestTarget target){
                        //フィードのURLを書き換える
                        feedURL = feedLink.getURL();
                        //ListViewを更新
                        target.addComponent(entryParent);
                        //FeedbackPanelを更新
                        target.addComponent(feedback);
                    }
                };
                
                link.add(label);
                item.add(link);
            }
        };
        
        return listView;
    }
    
    private List<FeedLink> getFeedList(){
        return feedList;
    }
    
    static class FeedLink{
        private final String text;
        private final String url;
        
        public FeedLink(String text, String url){
            this.text = text;
            this.url = url;
        }
        
        public String getText(){
            return this.text;
        }
        
        public String getURL(){
            return this.url;
        }
    }
}

アプリケーションクラスとフィルタマッピングは省略