View Javadoc

1   /*
2    * Copyright 2004-2009 the Seasar Foundation and the Others.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
13   * either express or implied. See the License for the specific language
14   * governing permissions and limitations under the License.
15   */
16  package org.seasar.cubby.action;
17  
18  import java.io.Writer;
19  import java.util.Collection;
20  import java.util.Map;
21  
22  import javax.servlet.http.HttpServletRequest;
23  import javax.servlet.http.HttpServletResponse;
24  
25  import org.seasar.cubby.internal.util.StringUtils;
26  import org.seasar.cubby.spi.JsonProvider;
27  import org.seasar.cubby.spi.ProviderFactory;
28  
29  /**
30   * JSON 形式の応答を返す {@link ActionResult} です。
31   * <p>
32   * アクションメソッドの戻り値としてこのインスタンスを指定することで、指定された JavaBean を JSON/JSONP 形式に変換して応答を返します。
33   * ブラウザの JavaScript から発行された要求を処理する場合等に使用してください。 JavaBean/ {@link Map}/配列/
34   * {@link Collection}などがコンストラクタに渡すことができます。
35   * </p>
36   * <p>
37   * 使用例1 : JSON 形式の応答を返す
38   * 
39   * <pre>
40   * MyBean bean = ...;
41   * return new Json(bean);
42   * </pre>
43   * 
44   * </p>
45   * <p>
46   * 使用例2 : コールバック関数名を指定して JSONP 形式の応答を返す
47   * 
48   * <pre>
49   * MyBean bean = ...;
50   * return new Json(bean, &quot;callback&quot;);
51   * </pre>
52   * 
53   * </p>
54   * <p>
55   * 使用例3 : MIME タイプと文字コードを指定して JSON 形式の応答を返す。<br>
56   * 設定される MIME タイプは"text/javascript+json; charset=Shift_JIS"になります。
57   * 
58   * <pre>
59   * MyBean bean = ...;
60   * return new Json(bean).contentType(&quot;text/javascript+json&quot;).encoding(&quot;Shift_JIS&quot;);
61   * </pre>
62   * 
63   * </p>
64   * 
65   * @see <a
66   *      href="http://www.json.org/">JSON(JavaScript&nbsp;Object&nbsp;Notation)</a>
67   * @see <a
68   *      href="http://ajaxian.com/archives/jsonp-json-with-padding">JSONP(JSON&nbsp;with&nbsp;Padding)</a>
69   * @see JsonProvider#toJson(Object)
70   * @author baba
71   * @author agata
72   */
73  public class Json implements ActionResult {
74  
75  	/** 変換対象のオブジェクト。 */
76  	private final Object bean;
77  
78  	/** 変換に使用する {@link JsonProvider}。 */
79  	private final JsonProvider jsonProvider;
80  
81  	/** コールバック関数名。 */
82  	private final String calllback;
83  
84  	/** MIME タイプ。 */
85  	private String contentType = "text/javascript";
86  
87  	/** エンコーディング。 */
88  	private String encoding = "utf-8";
89  
90  	/** X-JSON 応答ヘッダを使用するか。 */
91  	private boolean xjson = false;
92  
93  	/**
94  	 * JSON 形式で応答を返すインスタンスを生成します。
95  	 * 
96  	 * @param bean
97  	 *            JSON 形式に変換するオブジェクト
98  	 */
99  	public Json(final Object bean) {
100 		this(bean, null, ProviderFactory.get(JsonProvider.class));
101 	}
102 
103 	/**
104 	 * JSON 形式で応答を返すインスタンスを生成します。
105 	 * 
106 	 * @param bean
107 	 *            JSON 形式に変換するオブジェクト
108 	 * @param jsonProvider
109 	 *            JSON のプロバイダ
110 	 */
111 	public Json(final Object bean, final JsonProvider jsonProvider) {
112 		this(bean, null, jsonProvider);
113 	}
114 
115 	/**
116 	 * JSONP 形式で応答を返すインスタンスを生成します。
117 	 * 
118 	 * @param bean
119 	 *            JSONP 形式に変換するオブジェクト
120 	 * @param callback
121 	 *            コールバック関数名
122 	 */
123 	public Json(final Object bean, final String callback) {
124 		this(bean, callback, ProviderFactory.get(JsonProvider.class));
125 	}
126 
127 	/**
128 	 * JSONP 形式で応答を返すインスタンスを生成します。
129 	 * 
130 	 * @param bean
131 	 *            JSONP 形式に変換するオブジェクト
132 	 * @param callback
133 	 *            コールバック関数名
134 	 * @param jsonProvider
135 	 *            JSON のプロバイダ
136 	 */
137 	public Json(final Object bean, final String callback,
138 			final JsonProvider jsonProvider) {
139 		if (jsonProvider == null) {
140 			throw new NullPointerException("jsonProvider");
141 		}
142 		this.bean = bean;
143 		this.calllback = callback;
144 		this.jsonProvider = jsonProvider;
145 	}
146 
147 	/**
148 	 * JSON 形式に変換する JavaBeanを取得します。
149 	 * 
150 	 * @return JSON 形式に変換する JavaBean
151 	 */
152 	public Object getBean() {
153 		return this.bean;
154 	}
155 
156 	/**
157 	 * コールバック関数名を取得します。
158 	 * 
159 	 * @return コールバック関数名
160 	 */
161 	public String getCallback() {
162 		return this.calllback;
163 	}
164 
165 	/**
166 	 * MIME タイプを設定します。
167 	 * 
168 	 * @param contentType
169 	 *            MIME タイプ (例:"text/javascript+json")
170 	 * @return {@link Json}
171 	 */
172 	public Json contentType(final String contentType) {
173 		this.contentType = contentType;
174 		return this;
175 	}
176 
177 	/**
178 	 * MIME タイプを取得します。
179 	 * 
180 	 * @return MIME タイプ
181 	 */
182 	public String getContentType() {
183 		return this.contentType;
184 	}
185 
186 	/**
187 	 * エンコーディングを設定します。
188 	 * <p>
189 	 * 設定されたエンコーディングは MIME タイプの charset として使用されます。
190 	 * </p>
191 	 * 
192 	 * @param encoding
193 	 *            エンコーディング (例:"Shift_JIS")
194 	 * @return {@link Json}
195 	 */
196 	public Json encoding(final String encoding) {
197 		this.encoding = encoding;
198 		return this;
199 	}
200 
201 	/**
202 	 * エンコーディングを取得します。
203 	 * 
204 	 * @return エンコーディング
205 	 */
206 	public String getEncoding() {
207 		return this.encoding;
208 	}
209 
210 	/**
211 	 * JSON 文字列を応答ボディではなく X-JSON 応答ヘッダに設定することを指定します。
212 	 * <p>
213 	 * prototype.js の <code>Ajax.Request</code> を使うときに使用してください。
214 	 * </p>
215 	 * 
216 	 * @see <a
217 	 *      href="http://www.prototypejs.org/api/ajax/options">www.prototypejs.org&nbsp;-&nbsp;Ajax&nbsp;Options</a>
218 	 */
219 	public void xjson() {
220 		this.xjson = true;
221 	}
222 
223 	/**
224 	 * JSON 文字列を X-JOSN 応答ヘッダに設定するかを示します。
225 	 * 
226 	 * @return JSON 文字列を X-JOSN 応答ヘッダに設定する場合は <code>true</code>、そうでない場合は
227 	 *         <code>false</code>
228 	 */
229 	public boolean isXjson() {
230 		return xjson;
231 	}
232 
233 	/**
234 	 * {@inheritDoc}
235 	 */
236 	public void execute(final ActionContext actionContext,
237 			final HttpServletRequest request, final HttpServletResponse response)
238 			throws Exception {
239 		response.setCharacterEncoding(this.encoding);
240 		response
241 				.setContentType(this.contentType + "; charset=" + this.encoding);
242 		response.setHeader("Cache-Control", "no-cache");
243 		response.setHeader("Pragma", "no-cache");
244 
245 		final String script;
246 		if (isJsonp()) {
247 			script = appendCallbackFunction(jsonProvider.toJson(bean),
248 					calllback);
249 		} else {
250 			script = jsonProvider.toJson(bean);
251 		}
252 
253 		if (xjson) {
254 			response.setHeader("X-JSON", script);
255 		} else {
256 			final Writer writer = response.getWriter();
257 			writer.write(script);
258 			writer.flush();
259 		}
260 	}
261 
262 	/**
263 	 * JSONP 形式に変換するかどうかを示します。
264 	 * 
265 	 * @return JSONP 形式に変換する場合は <code>true</code>、そうでない場合は <code>false</code>
266 	 */
267 	private boolean isJsonp() {
268 		return !StringUtils.isEmpty(calllback);
269 	}
270 
271 	/**
272 	 * JSON 形式のスクリプトに指定されたコールバック関数を付加します。
273 	 * 
274 	 * @param script
275 	 *            JSON 形式のスクリプト
276 	 * @param callback
277 	 *            コールバック関数名
278 	 * @return コールバック関数が追加された JSON 形式のスクリプト
279 	 */
280 	private static String appendCallbackFunction(final String script,
281 			final String callback) {
282 		final StringBuilder builder = new StringBuilder(script.length()
283 				+ callback.length() + 10);
284 		builder.append(callback);
285 		builder.append("(");
286 		builder.append(script);
287 		builder.append(");");
288 		return builder.toString();
289 	}
290 
291 }