View Javadoc

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             // @deprecated #bindAndValidateに合わせた実装
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             // @deprecated #bindAndValidateに合わせた実装
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 }