Announcement

Collapse
No announcement yet.
X
  • Filter
  • Time
Clear All
new posts

    Issue with Velocity substitution when using Mail messageTemplate

    Hi,

    I have an issue when trying to do Velocity substitutions using the Mail interface's messageTemplate parameter. The problem is that the variable substitution is not performed as expected.

    When I switch to using the templateFile approach, everything works fine. Is this the intended behavior? I would like to use the messageTemplate approach instead of templateFile, as my messageTemplates are stored in a SQL database.

    Here's my datasource definition:
    Code:
    <DataSource 
    	schema="bemailer_test"
    	dbName="v3_sandbox"
    	tableName="mail_messages_queue"
    	ID="mail_messages_queue"
    	serverType="sql"
    >  
        <fields> 
            <field name="message_id" title="Message ID" primaryKey="true" type="integer"/>   
            <field name="message_to" title="To" type="text"/>
            <field name="message_from" title="From" type="text" detail="true"/>
            <field name="message_replyto" title="ReplyTo" type="text" detail="true"/>            
            <field name="message_subject" title="Subject" type="text" detail="true"/>   
            <field name="message_body" title="Body" type="text" length="8192" detail="true"/>                   
        </fields> 
    
        <operationBindings>   
            <operationBinding operationType="fetch"> 
                <mail contentType="text/html">  
                    <to>$message_to</to>  
                    <from>$message_from</from>     
                    <replyTo>$message_replyto</replyTo>
                    <subject>$message_subject</subject>
                    <templateFile>mail_template.txt</templateFile> 
                    <!--
                    <messageTemplate>
                        <html>
                            <body>
                                $message_body                            
                            </body>                    
                        </html>
                    </messageTemplate>
                    -->                       
                </mail>     
            </operationBinding>
        </operationBindings>    
        
    </DataSource>
    Here's the template text (same for both the file and the messageTemplate string):

    Code:
    Dear user, <br/>
    <br/>
    This is an attempt at dereferencing a Velocity variable:
    <br/>
    <br/>
    $message_subject
    <br/>
    <br/>
    The resulting message when using the messageTemplate contains the literal string '$message_subject' while the message generated using the templateFile has the properly substituted string.

    As a side issue, I wonder if it is also possible to support this 'nested' variable substitution in the other fields of the Mail interface. Specifically, my application requires creating a subject line based on a few extra columns from the database, and if I could do similar substitution in that field it would be ideal. Does this seem like a reasonable request/use case?

    Any insight would be greatly appreciated.

    Thanks for your time,
    Mike

    SmartClient Version: SNAPSHOT_v8.3d_2012-09-04/PowerEdition Deployment (built 2012-09-04)

    Browsers: FF15,IE9

    #2
    Your variable $message_body goes through variable substitution and becomes the message_body you've made available in the context. But you seem to expect variable substitution will happen a *second time* on the result of the first variable substitution - this won't happen.

    Comment


      #3
      Hi,

      Thanks for your response. Naturally, it makes me wonder - why doesn't this second substitution happen? It does in fact happen when the body text comes from a file associated with templateFile rather than the string bound to messageTemplate. It seems strange that one behaves differently from the other.


      Also, the javadocs hint that this should work (or am I mis-reading them?):

      If you have the Transaction Chaining feature, all of the variables normally accessible in other Velocity contexts are available in the message template as well, for example, $responseData.last('order').orderId. See transaction chaining for more details.

      You can provide the body of the email directly in the messageTemplate tag, or you can provide a filename in the templateFile attribute, which tells Smart GWT to use the contents of that file as the body of the email.
      Supporting this second substitution would make this already useful interface even more powerful. In any case, it is not clear to me why it works when using templateFile (verfied personally, and documented in these forums) and not messageTemplate.

      Thanks for your consideration,
      Mike

      Comment


        #4
        templateFile is interpreted as a Velocity template, then the template is loaded and interpreted as a Velocity template.

        No second substitution even happens on *the same setting or bit of text*, which is what you're hoping for. The docs do not indicate that this would happen, and we would not want to add in the behavior of doing 2+ round of substitutions, which just seems odd (why 2? why not 4? why not N?).

        Basically this is only occurring to you because you happen to want to store parts of your mail templates in the database. You could use the server-side TemplatedMailMessage class directly for this use case.

        Comment


          #5
          templateFile is interpreted as a Velocity template, then the template is loaded and interpreted as a Velocity template.
          The docs do not make this distinction clear in the summary, but it does appear in the text description of templateFile (and is omitted in the description of messageTemplate), so my bad for missing that subtle difference. I don't understand the rationale for why they are implemented differently, but I'll get over it.

          Ultimately, treating a field's contents as a Velocity template is exactly the behavior I desire. I do not want or need multiple substitution passes to occur, I just want to be able to specify that the substituted contents of a given mail field should be interpreted as a Velocity template and that it gets processed once by Velocity. "Multiple passes" don't make sense to me either, and that isn't what I need.

          In my use case, it would be great to be able to treat messageTemplate as a Velocity template, and also the subject field. I can think of use cases where you might want to do this with the other fields like to, cc, bcc, and replyTo.

          A setting to toggle this behavior would basically solve all of my problems. Part of the beauty of this interface is that it can be leveraged using no server-side code. Having this extra functionality would provide a lot of flexibility to cover some of these corner cases. Does it seem like a reasonable idea, or is it just wishful thinking?

          Of course, I am not opposed to writing code to workaround this, but I am not sure how I would go about implementing this by using TemplatedMailMessage. I will look into it. Any pointers would be greatly appreciated.

          Regards,
          Mike

          Comment


            #6
            Well, I tried creating a DMI fetch operation where I could plug-in my own mail sending action via TemplatedMailMessage (which I interpreted as your suggestion), but I cannot get it to work because TemplatedMailMessage.send() gives me an NPE without telling me what caused the NPE.

            I can use the vanilla MailMessage class in my DMI without any problems, so I am at a loss as to why TemplatedMailMessage fails. There isn't much usage information for this class in the Docs so I am just guessing at how to configure things before invoking send().

            Here's my modified ds.xml:

            Code:
                <fields> 
                    <field name="message_id" title="Message ID" primaryKey="true" type="integer"/>   
                    <field name="message_to" title="To" type="text"/>
                    <field name="message_from" title="From" type="text" detail="true"/>
                    <field name="message_replyto" title="ReplyTo" type="text" detail="true"/>            
                    <field name="message_subject" title="Subject" type="text" detail="true"/>   
                    <field name="message_body" title="Body" type="text" length="8192" detail="true"/>                   
                </fields> 
            
                <operationBindings>   
                    <operationBinding operationType="fetch"> 
                        <serverObject className="edu.isi.misd.beari.bemailer.server.SendMailMessage"/>
                    </operationBinding>
                </operationBindings>
            Here's the source for my DMI method:

            Code:
            /*
            
             */
            package edu.isi.misd.beari.bemailer.server;
            
            import com.isomorphic.datasource.DSRequest;
            import com.isomorphic.datasource.DSResponse;
            import com.isomorphic.log.Logger;
            import com.isomorphic.mail.TemplatedMailMessage;
            import java.util.Map;
            
            public class SendMailMessage 
            {
                static Logger log = new Logger(SendMailMessage.class.getName());
                
                public DSResponse fetch(DSRequest req) throws Exception 
                {          
                    DSResponse resp = req.execute();
                    Map[] records = (Map[])resp.getRecords().toArray(new Map[0]);
                    
                    for (Map record : records) 
                    {
                        TemplatedMailMessage message = new TemplatedMailMessage();
                        message.setRecipient((String)record.get("message_to"));
                        message.setFrom((String)record.get("message_from"));
                        message.setReplyTo((String)record.get("message_replyto"));           
                        message.setMessageTemplate((String)record.get("message_body"));            
                        message.setContentType("text/html");     
                        message.setEncoding("UTF-8");  // is this necessary? is there a default set
                        message.setContextMap(record);
                        message.buildMessage();  // is this needed, or what? how do I trigger velocity substitution against the messageTemplate?                      
                        message.send();
                    }
                    return resp;  
                }      
            }
            Here's the stack trace:

            Code:
            === 2012-09-16 17:19:17,112 [c-16] INFO  SQLDriver - [builtinApplication.mail_messages_queue_fetch] Executing SQL query on 'v3_sandbox': SELECT mail_messages_queue.message_body, mail_messages_queue.message_from, mail_messages_queue.message_id, mail_messages_queue.message_replyto, mail_messages_queue.message_subject, mail_messages_queue.message_to FROM bemailer_test.mail_messages_queue WHERE ('1'='1')
            === 2012-09-16 17:19:17,117 [c-16] INFO  DSResponse - [builtinApplication.mail_messages_queue_fetch] DSResponse: List with 1 items
            === 2012-09-16 17:19:17,176 [c-16] WARN  RequestContext - dsRequest.execute() failed: 
            java.lang.NullPointerException
            	at com.isomorphic.mail.MailMessage.send(MailMessage.java:502)
            	at edu.isi.misd.beari.bemailer.server.SendMailMessage.fetch(SendMailMessage.java:34)
            	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
            	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
            	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
            	at java.lang.reflect.Method.invoke(Method.java:597)
            	at com.isomorphic.base.Reflection.adaptArgsAndInvoke(Reflection.java:972)
            	at com.isomorphic.datasource.DataSourceDMI.execute(DataSourceDMI.java:416)
            	at com.isomorphic.datasource.DataSourceDMI.execute(DataSourceDMI.java:64)
            	at com.isomorphic.datasource.DSRequest.execute(DSRequest.java:1997)
            	at com.isomorphic.servlet.IDACall.handleDSRequest(IDACall.java:216)
            	at com.isomorphic.servlet.IDACall.processRPCTransaction(IDACall.java:173)
            	at com.isomorphic.servlet.IDACall.processRequest(IDACall.java:138)
            	at com.isomorphic.servlet.IDACall.doPost(IDACall.java:74)
            	at javax.servlet.http.HttpServlet.service(HttpServlet.java:641)
            	at com.isomorphic.servlet.BaseServlet.service(BaseServlet.java:152)
            	at javax.servlet.http.HttpServlet.service(HttpServlet.java:722)
            	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:304)
            	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
            	at com.isomorphic.servlet.CompressionFilter.doFilter(CompressionFilter.java:259)
            	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243)
            	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
            	at org.netbeans.modules.web.monitor.server.MonitorFilter.doFilter(MonitorFilter.java:393)
            	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243)
            	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
            	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:240)
            	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:164)
            	at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:462)
            	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:164)
            	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:100)
            	at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:563)
            	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:118)
            	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:399)
            	at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:317)
            	at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:204)
            	at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:311)
            	at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
            	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
            	at java.lang.Thread.run(Thread.java:662)
            Is this the right way to approach this? Do you have any insight to what is causing the NPE?

            I hate to sound like a broken record but this workaround would be unnecessary if there was a way that the data associated with messageTemplate could be treated as a Velocity template like the contents of templateFile...

            Thanks,
            Mike
            Last edited by mdarcy; 16 Sep 2012, 22:12.

            Comment


              #7
              I finally got this working so that it would meet my requirements.

              In the end, I could not get TemplatedMailMessage to work because of the NPE and I had to fall back to using MailMessage.

              For posterity, here's how I did it:

              Code:
              package edu.isi.misd.beari.bemailer.server;
              
              import com.isomorphic.datasource.DSRequest;
              import com.isomorphic.datasource.DSResponse;
              import com.isomorphic.log.Logger;
              import com.isomorphic.mail.MailMessage;
              import com.isomorphic.velocity.Velocity;
              import java.util.Map;
              
              public class SendMailMessage 
              {
                  static Logger log = new Logger(SendMailMessage.class.getName());
                  
                  public DSResponse fetch(DSRequest req) throws Exception 
                  {          
                      DSResponse resp = req.execute();
                      Map[] records = (Map[])resp.getRecords().toArray(new Map[0]);
                      
                      for (Map record : records) 
                      {
                          MailMessage message = new MailMessage();
                          message.addHeader("Content-Type", "text/html; charset=utf-8");
                          message.setRecipient((String)record.get("message_to"));
                          message.setFrom((String)record.get("message_from"));
                          message.setReplyTo((String)record.get("message_replyto"));
                          String subject = 
                              Velocity.evaluateAsString(
                                  (String)record.get("message_subject"), record); 
                          message.setSubject(subject);
                          String body = 
                              Velocity.evaluateAsString(
                                  (String)record.get("message_body"), record);
                          message.setBody(body);
                          message.send();
                      }
                      return resp;  
                  }      
              }
              I still don't understand the rationale for the framework not treating the dereferenced content of messageTemplate as the same as the content of a read-in templateFile and simply making the same call to Velocity.evaluateAsString() as I did, but at this point I am just glad I found a workaround.

              Thanks for your assistance,
              Mike
              Last edited by mdarcy; 16 Sep 2012, 23:00.

              Comment


                #8
                Just to wrap this up:

                The docs do not make this distinction clear in the summary, but it does appear in the text description of templateFile (and is omitted in the description of messageTemplate), so my bad for missing that subtle difference. I don't understand the rationale for why they are implemented differently, but I'll get over it.
                They are not implemented differently, it is one-pass substitution in all cases.

                Ultimately, treating a field's contents as a Velocity template is exactly the behavior I desire. I do not want or need multiple substitution passes to occur,
                Yes, you actually are asking for 2+ pass substitution. You are asking that $message_body be interpolated into some straight HTML (<html><body>) then you want the result interpolated again.

                Comment


                  #9
                  Thanks for your reply. I seem to have found a workaround that is satisfactory, so all is good.

                  I would only add one final comment. Perhaps it is a misnomer to label the messageTemplate field "Template". Template in this context made me think it was a Velocity template, like templateFile. That was really the source of my confusion and subsequent expectations that it work like templateFile. Maybe it should just be called "messageBody" or "body", since that is really all it is.

                  Cheers,
                  Mike

                  Comment


                    #10
                    For everyone finding this thread via search - this is also related.

                    Best regards
                    Blama

                    Comment

                    Working...
                    X