1 package org.unitedfront2.web.controller;
2
3 import java.util.ArrayList;
4 import java.util.List;
5 import java.util.Locale;
6 import java.util.Map;
7 import java.util.Properties;
8
9 import javax.annotation.Resource;
10 import javax.servlet.ServletContext;
11
12 import org.springframework.beans.factory.annotation.Autowired;
13 import org.springframework.validation.DataBinder;
14 import org.springframework.validation.Errors;
15 import org.springframework.validation.Validator;
16 import org.springframework.web.bind.WebDataBinder;
17 import org.springframework.web.context.ServletContextAware;
18 import org.springframework.webflow.core.collection.MutableAttributeMap;
19 import org.springframework.webflow.execution.Event;
20 import org.springframework.webflow.execution.RequestContext;
21 import org.springframework.webflow.execution.ScopeType;
22 import org.unitedfront2.domain.Domain;
23 import org.unitedfront2.domain.DomainFactory;
24 import org.unitedfront2.validation.SpringValidator;
25 import org.unitedfront2.web.WebUtils;
26 import org.unitedfront2.web.mail.MailSendException;
27 import org.unitedfront2.web.mail.MailSender;
28 import org.unitedfront2.web.mail.SimpleMailMessageValidator;
29 import org.unitedfront2.web.mail.WebXmlTemplateSpringMailSender;
30 import org.unitedfront2.web.mail.XmlTemplateSpringMailSender;
31
32 /**
33 * 入力フォーム用のアクションクラスです。ドメインファクトリ <code>{@link DomainFactory}</code> を
34 * 使ったフォームオブジェクトの設定やメールセンダ <code>{@link MailSender}</code> を使ったメール
35 * 送信の支援、検証クラス {@link SpringValidator} を使った検証クラスのビューへの転送などを提供して
36 * います。ドメインファクトリとメールセンダの設定は任意です。ドメインファクトリが設定されていてプロパティ変数
37 * <code>formObjectClass</code> に値が設定されていない場合、
38 * <code>formObjectClass</code> にはドメインファクトリで生成されるクラスが設定されます。<p>
39 *
40 * メールの送信には、メールテンプレートエンジン {@link XmlTemplateSpringMailSender} を利用
41 * します。メールテンプレートでは、システムのメールアドレスと URL を引数として渡すことができます。
42 *
43 * @author kurokkie
44 */
45 public class FormAction extends org.springframework.webflow.action.FormAction
46 implements ServletContextAware {
47
48 /** 検証クラスの変数名 (validator) */
49 public static final String VALIDATOR_PARAM_NAME = "validator";
50
51 /** システムメールアドレスの変数名 (_systemMailAddr) */
52 public static final String SYSYSTEM_MAIL_ADDR_PARAM_NAME
53 = "_systemMailAddr";
54
55 /** システム URL の変数名 (_systemUrl) */
56 public static final String SYSYSTEM_URL_PARAM_NAME = "_systemUrl";
57
58 /** メール送信フラグの変数名 (sendMail) */
59 public static final String SEND_MAIL_PARAM_NAME = "sendMail";
60
61 /** ローカル設定ファイルの Bean 名 (localConfiguration) */
62 public static final String LOCAL_CONFIGURATION_BEAN_NAME
63 = "localConfiguration";
64
65 /** ドメインファクトリ */
66 @Autowired
67 protected DomainFactory domainFactory;
68
69 /** メール送信エンジン */
70 @Resource(name = "mailSender")
71 private org.springframework.mail.MailSender mailSenderTarget;
72
73 /** メールメッセージ検証クラス */
74 @Resource(name = "simpleMailMessageValidator")
75 private SimpleMailMessageValidator simpleMailMessageValidator;
76
77 /** メール送信テンプレートエンジンリスト */
78 private final List<XmlTemplateSpringMailSender> mailSenders
79 = new ArrayList<XmlTemplateSpringMailSender>();
80
81 /**
82 * このクラスを設定するためのプロパティです。
83 *
84 * <table>
85 * <tr>
86 * <th>キー</th>
87 * <th>説明</th>
88 * </tr>
89 * <tr>
90 * <td>mail.sendMail</td>
91 * <td>
92 * メール送信を有効にする場合は <code>true</code> 、無効にする場合は
93 * <code>false</code> 。デフォルトは <code>false</code> 。
94 * </td>
95 * </tr>
96 * <tr>
97 * <td>mail.systemMailAddr</td>
98 * <td>システムのメールアドレス。メールテンプレート内の変数として利用されます。</td>
99 * </tr>
100 * <tr>
101 * <td>mail.systemUrl</td>
102 * <td>システムの URL 。メールテンプレート内の変数として利用されます。</td>
103 * </tr>
104 * <tr>
105 * <td>mail.subjectPrefix</td>
106 * <td>メールの件名の前に付与する接頭辞。デフォルトは空文字。</td>
107 * </tr>
108 * <tr>
109 * <td>mail.signature</td>
110 * <td>メールの本文の最後につける署名。デフォルトは空文字。</td>
111 * </tr>
112 * </table>
113 */
114 @Resource(name = "formActionConfig")
115 private Properties config;
116
117 /**
118 * メール送信を行うなら true 、そうでなければ false
119 *
120 * @see #putSendMail(RequestContext)
121 * @see #isSendMail()
122 */
123 private boolean sendMail;
124
125 /** システムメールアドレス */
126 private String systemMailAddr;
127
128 /** システム URL */
129 private String systemUrl;
130
131 /** 件名の接頭辞 */
132 private String subjectPrefix;
133
134 /** 署名 */
135 private String signature;
136
137 /** {@link ServletContext} */
138 private ServletContext servletContext;
139
140 /**
141 * プロパティ <code>formErrorsScope</code> の値を {@link ScopeType#FLOW} に設定しま
142 * す。親クラスではデフォルトが {@link ScopeType#FLASH} になっています。
143 */
144 public FormAction() {
145 super();
146 setFormErrorsScope(ScopeType.FLOW);
147 }
148
149 /**
150 * ドメインファクトリが設定されていてプロパティ変数 <code>formObjectClass</code> に値が設定さ
151 * れていない場合、<code>formObjectClass</code> にはドメインファクトリで生成されるクラスが設定
152 * されます。メール送信テンプレートの生成はこのメソッドで行われます。
153 */
154 @Override
155 protected void initAction() {
156 super.initAction();
157
158
159 this.sendMail = Boolean.valueOf(config.getProperty("mail.sendMail",
160 "false"));
161 this.systemMailAddr = config.getProperty("mail.systemMailAddr");
162 this.systemUrl = config.getProperty("mail.systemUrl");
163 this.subjectPrefix = config.getProperty("mail.subjectPrefix", "");
164 this.signature = config.getProperty("mail.signature", "");
165
166
167 for (XmlTemplateSpringMailSender mailSender : mailSenders) {
168 initMailSender(mailSender);
169 }
170 }
171
172 private void initMailSender(XmlTemplateSpringMailSender mailSender) {
173 mailSender.setMailSender(mailSenderTarget);
174 mailSender.setSubjectPrefix(subjectPrefix);
175 mailSender.setSignature(signature);
176 mailSender.setSimpleMailMessageValidator(simpleMailMessageValidator);
177 }
178
179 /**
180 * {@link org.springframework.webflow.action.FormAction#setupForm(
181 * RequestContext)} を呼び出した後、検証クラスを設定します。このメソッドはフォームを表示する度に呼
182 * び出されます。初回アクセス時はもちろん、検証エラーによって戻ってきた際にも呼び出されます。
183 *
184 * @param context {@link RequestContext}
185 * @return {@link Event}
186 * @throws Exception {@link Exception}
187 * @see SpringValidator
188 */
189 @Override
190 public Event setupForm(RequestContext context) throws Exception {
191 Event event = super.setupForm(context);
192 MutableAttributeMap requestScope = context.getRequestScope();
193 Validator validator = getValidator();
194 if (validator instanceof SpringValidator) {
195 requestScope.put(VALIDATOR_PARAM_NAME,
196 ((SpringValidator<?>) validator).getOriginalValidator());
197 if (logger.isDebugEnabled()) {
198 logger.debug("Set the validator [" + ((SpringValidator<?>)
199 validator).getOriginalValidator() + "]");
200 }
201 } else {
202 requestScope.put(VALIDATOR_PARAM_NAME, validator);
203 if (logger.isDebugEnabled()) {
204 logger.debug("Set the validator [" + validator + "]");
205 }
206 }
207 return event;
208 }
209
210 /**
211 * {@link org.springframework.webflow.action.FormAction} の getFormObject
212 * のシグネチャが {@link Exception} を発生する仕様になっており使いにくいため、拡張しています。
213 *
214 * @param context リクエストコンテキスト
215 * @return フォームオブジェクト
216 * @throws IllegalArgumentException 引数が不正
217 */
218 @Override
219 protected Object getFormObject(RequestContext context)
220 throws IllegalArgumentException {
221 try {
222 return super.getFormObject(context);
223 } catch (Exception e) {
224 logger.error(e);
225 throw new IllegalArgumentException(e);
226 }
227 }
228
229 /**
230 * フォームオブジェクトを生成します。ドメインファクトリが設定されていればその <code>prototype
231 * </code> メソッドを用いてドメインオブジェクトを生成し、設定されていなければ
232 * {@link org.springframework.webflow.action.FormAction#createFormObject}
233 * を実行して得られる値を返します。
234 *
235 * @param context リクエストコンテキスト
236 * @return フォームオブジェクト
237 * @throws Exception 例外
238 * @see org.springframework.webflow.action.FormAction#createFormObject
239 */
240 @Override
241 protected Object createFormObject(RequestContext context) throws Exception {
242 Class<?> formObjectClass = getFormObjectClass();
243 if (domainFactory != null
244 && Domain.class.isAssignableFrom(formObjectClass)) {
245 return domainFactory.prototype(getFormObjectClass());
246 } else {
247 return super.createFormObject(context);
248 }
249 }
250
251 @Override
252 public Event bindAndValidate(RequestContext context) throws Exception {
253 Event e = super.bindAndValidate(context);
254 if (logger.isDebugEnabled()) {
255 Errors errors = getFormErrors(context);
256 if (errors.hasErrors()) {
257 logger.debug(errors);
258 }
259 }
260 return e;
261 }
262
263 /**
264 * 初期化処理を行います。
265 *
266 * @param context {@link RequestContext}
267 * @return 成功イベント
268 * @ensure フロースコープに、変数 {@link #SEND_MAIL_PARAM_NAME} の値が設定される
269 */
270 public Event init(RequestContext context) {
271 context.getFlowScope().put(SEND_MAIL_PARAM_NAME, sendMail);
272 return success();
273 }
274
275 /**
276 * メールを送信するアクションです。{@link #doSendMail(RequestContext)} を呼び出した後、成
277 * 功イベントを返します。
278 *
279 * @param context リクエストコンテキスト
280 * @return 成功イベント
281 * @throws Exception {@link Exception}
282 */
283 public Event sendMail(RequestContext context) throws Exception {
284 doSendMail(context);
285 return success();
286 }
287
288 /**
289 * メールの送信を行います。このメソッドは、メールテンプレートをただ一つ設定した場合にのみ有効です。複数
290 * のメールテンプレートを設定している場合は、{@link #doSendMail(RequestContext, String)}
291 * を利用してください。
292 *
293 * @param context {@link RequestContext}
294 * @throws MailSendException メール送信に失敗
295 */
296 protected void doSendMail(RequestContext context) throws MailSendException {
297 if (mailSenders.size() != 1) {
298 String message = "The 'one' mailTemplateName must be set.";
299 throw new IllegalStateException(message);
300 }
301 doSendMail(context, mailSenders.get(0).getMailTemplateName());
302 }
303
304 /**
305 * メールの送信を行います。メール送信フラグが有効になっている必要があります。リクエストスコープに保存し
306 * てあるモデルと、システムのメールアドレスと URL をメール送信テンプレートエンジンに引き渡します。<p>
307 *
308 * メール送信機能をただ一つ持つアクションクラスでは、{@link #sendMail(RequestContext)} を
309 * 利用できます。複数のメール送信機能を持たせる場合、このメソッドをサブクラスのメール送信機能でで再利
310 * 用してください。
311 *
312 * @param context {@link RequestContext}
313 * @param mailTemplateName 設定済みのメールテンプレート名
314 * @throws MailSendException メール送信に失敗
315 * @see #setMailTemplateNames(List)
316 * @see #SYSYSTEM_MAIL_ADDR_PARAM_NAME
317 * @see #SYSYSTEM_URL_PARAM_NAME
318 */
319 protected void doSendMail(RequestContext context, String mailTemplateName)
320 throws MailSendException {
321 if (!sendMail) {
322 String message = "This action has been set No send mail.";
323 logger.error(message);
324 throw new IllegalStateException(message);
325 }
326 if (logger.isInfoEnabled()) {
327 logger.info("Sending mail.");
328 }
329 Map<String, Object> model = context.getRequestScope().asMap();
330 model.put(SYSYSTEM_MAIL_ADDR_PARAM_NAME, systemMailAddr);
331 model.put(SYSYSTEM_URL_PARAM_NAME, systemUrl);
332 Locale locale = WebUtils.getLocale(context);
333 MailSender mailSender = selectMailSender(mailTemplateName);
334 if (mailSender == null) {
335 String message = "The mailTemplateName '" + mailTemplateName
336 + "' is null.";
337 logger.error(message);
338 throw new IllegalArgumentException(message);
339 }
340 mailSender.send(model, locale);
341 if (logger.isInfoEnabled()) {
342 logger.info("Send mail successfully.");
343 }
344 }
345
346 private MailSender selectMailSender(String mailTemplateName) {
347 for (XmlTemplateSpringMailSender sender : mailSenders) {
348 if (sender.supports(mailTemplateName)) {
349 return sender;
350 }
351 }
352 return null;
353 }
354
355 /**
356 * バインド中のエラーを返します。フォームオブジェクト名には、{@link #getFormObjectName()} で取
357 * 得する値が使用されます。エラーは {@link #getFormErrorsScope()} で取得したスコープに設定
358 * します。
359 *
360 * @param context {@link RequestContext}
361 * @param formObject フォームオブジェクト
362 * @return {@link Errors}
363 */
364 protected Errors getBindingErrors(RequestContext context,
365 Object formObject) {
366
367 try {
368
369 Errors errors = createBinder(context, formObject).getErrors();
370 putFormErrors(context, errors);
371 return errors;
372 } catch (Exception e) {
373 logger.error(e);
374 throw new IllegalStateException(e);
375 }
376 }
377
378 /**
379 * バインド中のエラーを返します。フォームオブジェクト名を明示的に指定できます。エラーは
380 * {@link #getFormErrorsScope()} で取得したスコープに設定します。
381 *
382 * @param context {@link RequestContext}
383 * @param formObject フォームオブジェクト
384 * @param formObjectName フォームオブジェクト名
385 * @return {@link Errors}
386 */
387 protected Errors getBindingErrors(RequestContext context, Object formObject,
388 String formObjectName) {
389
390 try {
391
392 Errors errors = createBinder(context, formObject, formObjectName)
393 .getErrors();
394 putFormErrors(context, errors);
395 return errors;
396 } catch (Exception e) {
397 logger.error(e);
398 throw new IllegalStateException(e);
399 }
400 }
401
402 /**
403 * Put given errors instance in the configured scope of given context.
404 *
405 * 親クラスにも存在する private メソッドです。
406 * {@link #getBindingErrors(RequestContext, Object)} メソッドで呼び出すようにしている
407 * ため、サブクラスには公開していません。
408 *
409 * @param context {@link RequestContext}
410 * @param errors {@link Errors}
411 */
412 private void putFormErrors(RequestContext context, Errors errors) {
413 if (logger.isDebugEnabled()) {
414 logger.debug("Putting form errors instance in scope "
415 + getFormErrorsScope());
416 }
417 getFormObjectAccessor(context).putFormErrors(errors,
418 getFormErrorsScope());
419 }
420
421 /**
422 * コンテキスト内にフォームオブジェクトを再設定します。
423 *
424 * @param context {@link RequestContext}
425 * @param formObject フォームオブジェクト
426 */
427 protected void putFormObject(RequestContext context, Object formObject) {
428 if (logger.isDebugEnabled()) {
429 logger.debug("Putting form object '" + formObject
430 + "' in scope " + getFormObjectScope()
431 + " with name '" + getFormObjectName() + "'");
432 }
433 getFormObjectAccessor(context).putFormObject(formObject,
434 getFormObjectName(), getFormObjectScope());
435 }
436
437 /**
438 * Create a new binder instance for the given form object and request
439 * context. Can be overridden to plug in custom DataBinder subclasses.
440 * <p>
441 * Default implementation creates a standard WebDataBinder, and invokes
442 * {@link #initBinder(RequestContext, DataBinder)} and
443 * {@link #registerPropertyEditors(
444 * org.springframework.beans.PropertyEditorRegistry)}.
445 * @param context the action execution context, for accessing and setting
446 * data in "flow scope" or "request scope"
447 * @param formObject the form object to bind onto
448 * @param formObjectName the form object name
449 * @return the new binder instance
450 * @throws Exception when an unrecoverable exception occurs
451 * @see WebDataBinder
452 * @see #initBinder(RequestContext, DataBinder)
453 * @see #setMessageCodesResolver(
454 * org.springframework.validation.MessageCodesResolver)
455 */
456 protected DataBinder createBinder(RequestContext context, Object formObject,
457 String formObjectName) throws Exception {
458
459 DataBinder binder = new WebDataBinder(formObject, formObjectName);
460 if (getMessageCodesResolver() != null) {
461 binder.setMessageCodesResolver(getMessageCodesResolver());
462 }
463 initBinder(context, binder);
464 registerPropertyEditors(context, binder);
465 return binder;
466 }
467
468 @Override
469 public void setServletContext(ServletContext servletContext) {
470 this.servletContext = servletContext;
471 }
472
473 protected boolean isSendMail() {
474 return sendMail;
475 }
476
477 /**
478 * メール送信テンプレートエンジン {@link XmlTemplateSpringMailSender} のためのメール
479 * テンプレート名リストを設定します。複数のテンプレートを使用する場合に使います。
480 *
481 * @param mailTemplateNames メールテンプレート名リスト
482 * @see #setMailTemplateName(String)
483 */
484 protected void setMailTemplateNames(List<String> mailTemplateNames) {
485 setMailTemplateNames(mailTemplateNames.toArray(
486 new String[mailTemplateNames.size()]));
487 }
488
489 /**
490 * メール送信テンプレートエンジン {@link XmlTemplateSpringMailSender} のためのメール
491 * テンプレート名リストを設定します。複数のテンプレートを使用する場合に使います。
492 *
493 * @param mailTemplateNames メールテンプレート名配列
494 * @see #setMailTemplateName(String)
495 */
496 protected void setMailTemplateNames(String[] mailTemplateNames) {
497 this.mailSenders.clear();
498 for (String mailTemplateName : mailTemplateNames) {
499 XmlTemplateSpringMailSender mailSender
500 = new WebXmlTemplateSpringMailSender(mailTemplateName,
501 servletContext);
502 initMailSender(mailSender);
503 this.mailSenders.add(mailSender);
504 }
505 }
506
507 /**
508 * メール送信テンプレートエンジン {@link XmlTemplateSpringMailSender} のためのメール
509 * テンプレート名を設定します。単一のテンプレートを使用する場合に使います。
510 *
511 * @param mailTemplateName メールテンプレート名
512 * @see #setMailTemplateNames(List)
513 */
514 protected void setMailTemplateName(String mailTemplateName) {
515 setMailTemplateNames(new String[] {mailTemplateName});
516 }
517
518 protected DomainFactory getDomainFactory() {
519 return domainFactory;
520 }
521 }