View Javadoc

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