Category Archives: Java

Mayaaで動的なidを作るw

Mayaa的には非推奨のはずで、マニュアルにも全然記載がないのだけど、どうしてもと顧客から要望があったので、次のように実装しました。

テンプレートにこのように書いたとします。

<span m:id="HOGEHOGE_NAME_OF_HOGEHOGE_ID" m:HOGEHOGE_ID="1">dummy</span>

で、実はHOGEHOGE_NAMEは、Map hogehoge に、HOGEHOGE_IDをキーとして与えられているとします。

public static Map<String, String> hogehoge = new HashMap<String, String>();
static {
hogehoge.put("1", "ほげほげ");
hogehoge.put("2", "ふがふが");
}

この場合、このようにすれば、m:HOGEHOGE_IDをキーにして「ほげほげ」等を返すidが作成できます。

<m:write m:id="HOGEHOGE_NAME_OF_HOGEHOGE_ID" value="${hogehoge.get(originalNode.getAttribute(
  Packages.org.seasar.mayaa.impl.engine.specification.SpecificationUtil.createQName("HOGEHOGE_ID")
).value)}" />

参考にさせていただいたのは次のML投稿です。
http://ml.seasar.org/archives/mayaa-user/2009-May/000793.html

本当は、Namespaceにmayaaの物を使わず自分でちゃんと定義するべきでしょう。

この使い方はMayaa作者が意図した範囲から外れていると思うので、今後のMayaaバージョンによってはサポートされなくなるリスクは0ではないでしょう。

プログラマーとデザイナーの境界が縮まってきた気がする

今日のひがさんのエントリを読んで、appengineとは全然関係ないけど思ったことを書きます。
http://d.hatena.ne.jp/higayasuo/20101109/1289290143

ひがさんのエントリでは、appengine/Javaのスピンアップ問題を解決するために、静的HTMLをレスポンスして、JavaScriptで後から動的なデータを取得するパターンが提案されています。

別にこれ、appengineに限らず最近僕の近辺ではトレンドだなと思っていました。
SEOがあるので、サーバーサイドテンプレートもまだ必要だとは思いますが。

それで、思ったのがプログラマーの仕事についてです。僕が見てきたことを書きます。

むかしはJSPを書くだけのプログラマーがいました。

むかしむかし、ウェブシステムの画面はJSPという難解な呪文を駆使しなければ書けない、とても高級なものでした。
しかし、世の中には、「JSPが書ける」プログラマーが沢山いました。少しコツを教えればすぐにやり方を覚えてくれるので、派遣さんや学生のアルバイトが多く担当しました。

彼らは、デザイナーさんが書いたHTMLを「HTMLきもーい」とか言いながら、一生懸命コピペしていました。

この仕事は誰でもできる反面、誰がやっても同じくらいのコストがかかる仕事でした。優秀なプログラマーなら、普通のプログラマーの20倍は生産するのが普通な現場ですが、みんな一律に同じ程度の時間がかかるのです。それに、デザインを組み込むために何週間もかかるため、お客様にもご迷惑がかかりました。

それを解決したのが、(弊社の場合は)Mayaaでした。最初の導入は大変でしたが、導入後は、デザイナーの人が一人でサイト全体のデザインをしてくれるようになりました。もう、JSPを書けるだけのプログラマーは必要なくなり、プログラマーはJavaに専念し、デザイナーはHTMLに専念することができるようになりました。時々、中間言語のmayaaファイル(XML)を書かなければいけないのですが、こういう時、プログラマーは腕の見せ所でカッコ良く対応してあげるとデザイナーさんからの株が上がります。大いにがんばりましょう。

ところが、これを導入してたった半年で時代は変わりました。

みんな、JavaScriptでなんとかしたくなっていたのです。
MayaaはJavaScriptの変数を置換することはできません。強引にやることもできますが、やるべきではありません。セキュリティー的に。
それではどうするのかというと、一旦mayaaでspanタグにデータを落として、スタイルシートで非表示にし、JavaScriptから、getElementByIdしてinnerHTMLなどで値を取得するという方法が良さそうです。

これくらいのことなら、デザイナーさんにレクチャーすればやってもらえるのですが、

デザイナーといえど、JavaScriptを知らなければやっていられない時代なんだなと思います。

このことにとどまらず、今、毎日のように、デザイナーさんがいろんなJavaScriptをWEBから見つけては、「動かない」と悩んで僕に質問をします。その都度、「これはjQueryのバージョンが合わない」とか「このJavaScriptわかってない人が書いてるから使わない方が良い」とか答えるのですが、時には「わかった。僕がやっとくよ」ということもあります。

今までは、
Java, JSP, HTML(CSS)
と、それぞれの言語で話していたことが、今は
HTML/JavaScript
という共通の言語でプログラマーとデザイナーが会話をしているのです。

本当はUIプログラマーという職種が、橋渡しを担当するのでしょうか?でも、なんだかむしろ、今時のWebはUIを取ってしまったらあとは何も残らないくらいUIに依存しているので、UIプログラミングのできないプログラマーも、JavaScriptの書けないデザイナーもあんまり活躍できないのではないかと思います。

その後の追記

ついに、デザイナーさんから、「私ができそうなところは私がmayaa書いていいですか?」と言われました。もはや最強!

プログラマーに取ってmayaaファイルを書く事はXMLプログラミングですので、割と苦痛なのですが、デザイナーさんにとっては、JavaScriptよりもフレンドリーのようです。

この記事は以前多くのブックマーク・コメントを頂きました。

これからブックマークされる方はこちら↓

俺タイムラインメールアプリ

前回(といっても、半年近く前なんですね) http://d.hatena.ne.jp/s-ishigami/20100224 で、TwitterのTimeLineや、Replyをメール配信するGAE/Java アプリを公開させていただきました。

その後、構造化されていないとか、一部からご反響をいただきましたが、あくまでサンプルコードのつもりで気楽に書いているので、メソッド一個でがんばりました。
Slim3とかJDOとか使っていません。

さて、この度
appengine ja hack-a-thon #6 (http://atnd.org/events/5932)
に参加させていただき、多くのすばらしい先生方を尻目に初心者の私ですが、上記アプリを拡張させていただいた次第でございます。

今回実装したのは以下のとおりです。
・OAuth対応
・DirectMessage対応

それではコードです。

package s_ishigami.ore_timeline_mail;
import java.io.IOException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import twitter4j.DirectMessage;
import twitter4j.Paging;
import twitter4j.ResponseList;
import twitter4j.Status;
import twitter4j.Twitter;
import twitter4j.TwitterException;
import twitter4j.TwitterFactory;
import twitter4j.http.AccessToken;
import twitter4j.http.RequestToken;
import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.DatastoreServiceFactory;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.EntityNotFoundException;
import com.google.appengine.api.datastore.KeyFactory;
import com.google.appengine.api.mail.MailServiceFactory;
import com.google.appengine.api.mail.MailService.Message;
import com.google.appengine.api.memcache.Expiration;
import com.google.appengine.api.memcache.MemcacheService;
import com.google.appengine.api.memcache.MemcacheServiceFactory;
@SuppressWarnings("serial")
public class OreTimelineMailServlet extends HttpServlet {
// 設定情報
private static final String SYSTEM_ADMIN_MAIL = "xxxx@xxx.xx";
private static final String YOUR_MAIL = "xxxx@xxxx.xx";
private static final String CONSUMER_KEY = "XXXXXXX";
private static final String CONSUMER_SECRET = "XXXXXXXXXXXXXXXXXXXXXX";
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
String path = request.getRequestURI().substring(request.getContextPath().length());
DatastoreService dataService = DatastoreServiceFactory.getDatastoreService();
MemcacheService memcacheService = MemcacheServiceFactory.getMemcacheService();
try {
Twitter twitter = new TwitterFactory().getOAuthAuthorizedInstance(CONSUMER_KEY, CONSUMER_SECRET);
// OAuth認証コールバック
if (path.equals("/oauth_recieve")) {
RequestToken requestToken = (RequestToken) memcacheService.get("oauth_request_token");
if (requestToken == null) {
response.setContentType("text/plain");
response.setCharacterEncoding("UTF-8");
response.getWriter().println("多分timeout");
return;
}
AccessToken accessToken = twitter.getOAuthAccessToken(requestToken);
Entity entity = new Entity("twitter", "oauth_access_token");
entity.setProperty("token", accessToken.getToken());
entity.setProperty("tokenSecret", accessToken.getTokenSecret());
dataService.put(entity);
response.setContentType("text/plain");
response.setCharacterEncoding("UTF-8");
response.getWriter().println("OAuth 認証を登録しました。");
return;
}
// OAuth認証
AccessToken accessToken = null;
try {
Entity entity = dataService.get(KeyFactory.createKey("twitter", "oauth_access_token"));
accessToken = new AccessToken((String) entity.getProperty("token"), (String) entity.getProperty("tokenSecret"));
} catch (EntityNotFoundException e) {}
if (accessToken == null) {
// 認証済みでない場合は、リクエストトークンを発行してキャッシュに保持・リダイレクト
RequestToken requestToken = twitter.getOAuthRequestToken();
memcacheService.put("oauth_request_token", requestToken, Expiration.byDeltaSeconds(600)); // 10分有効
response.sendRedirect(requestToken.getAuthorizationURL());
return;
}
twitter.setOAuthAccessToken(accessToken);
// 以下アプリ
StringBuilder text = new StringBuilder();
if (path.equals("/clear")) {
Entity entity = new Entity("twitter", "timeline");
entity.setProperty("lastId", -1);
dataService.put(entity);
entity = new Entity("twitter", "reply");
entity.setProperty("lastId", -1);
dataService.put(entity);
entity = new Entity("twitter", "direct");
entity.setProperty("lastId", -1);
dataService.put(entity);
}
if (path.equals("/tl")) {
// 保存された最新のIDを取得
long lastId = -1;
try {
Object id = dataService.get(KeyFactory.createKey("twitter", "timeline")).getProperty("lastId");
lastId = (id == null ? lastId : ((Long) id).longValue());
} catch (EntityNotFoundException e) {}
ResponseList<Status> timeline = twitter.getHomeTimeline();
for (Status status : timeline) {
if (lastId == status.getId()) { break; }
text.append(status.getUser().getScreenName() + ":" + status.getText() + "\n");
}
if (!"true".equals(request.getParameter("not_save"))) {
// 最新のIDを保存
long theLastId = timeline.get(0).getId();
Entity entity = new Entity("twitter", "timeline");
entity.setProperty("lastId", theLastId);
dataService.put(entity);
}
} else if (path.equals("/reply")) {
// 保存された最新のIDを取得
long lastId = -1;
try {
Object id = dataService.get(KeyFactory.createKey("twitter", "reply")).getProperty("lastId");
lastId = (id == null ? lastId : ((Long) id).longValue());
} catch (EntityNotFoundException e) {}
ResponseList<Status> mentions = twitter.getMentions(new Paging(1, 8));
for (Status status : mentions) {
if (lastId == status.getId()) {
break;
}
text.append(status.getUser().getScreenName() + ":" + status.getText() + "\n");
}
if (!"true".equals(request.getParameter("not_save"))) {
// 最新のIDを保存
long theLastId = mentions.get(0).getId();
Entity entity = new Entity("twitter", "reply");
entity.setProperty("lastId", theLastId);
dataService.put(entity);
}
} else if (path.equals("/direct")) {
// 保存された最新のIDを取得
long lastId = -1;
try {
Object id = dataService.get(KeyFactory.createKey("twitter", "direct")).getProperty("lastId");
lastId = (id == null ? lastId : ((Long) id).longValue());
} catch (EntityNotFoundException e) {}
ResponseList<DirectMessage> messages = twitter.getDirectMessages(new Paging(1, 8));
for (DirectMessage message : messages) {
if (lastId == message.getId()) {
break;
}
text.append(message.getSender().getScreenName() + ":" + message.getText() + "\n");
}
if (!"true".equals(request.getParameter("not_save"))) {
// 最新のIDを保存
long theLastId = messages.get(0).getId();
Entity entity = new Entity("twitter", "direct");
entity.setProperty("lastId", theLastId);
dataService.put(entity);
}
}
if ("true".equals(request.getParameter("ispc"))) {
response.setContentType("text/plain");
response.setCharacterEncoding("UTF-8");
response.getWriter().println(text.toString());
} else {
if (text.length() > 0) {
Message message = new Message();
message.setSubject("Twitter Timeline");
message.setTo(YOUR_MAIL);
message.setSender(SYSTEM_ADMIN_MAIL);
message.setTextBody(text.toString());
MailServiceFactory.getMailService().send(message);
}
}
} catch (TwitterException e) {
response.setContentType("text/plain");
response.setCharacterEncoding("UTF-8");
e.printStackTrace(response.getWriter());
}
}
}

使い方
・自分用のApplicationIDを取得
・事前に http://twitter.com/oauth_clients/new でアプリを登録
  ・この際、戻りURLは、"http://[取得したAppID].appspot.com/oauth_recieve" を設定してください。
   あとは環境に合わせて。
・出力された、CONSUMER_KEYと、CONSUMER_SECRETをソースにコピペ
・メール送信元アドレス(開発者メールアドレス)と、送信先アドレス(携帯アドレス)をソースに記入
・デプロイ

PCでも携帯でもいいので、/以外にアクセス/hogeとかでも可
あとは画面に従えば認証が行われます。