Skip to main content

Notice: this Wiki will be going read only early in 2024 and edits will no longer be possible. Please see: https://gitlab.eclipse.org/eclipsefdn/helpdesk/-/wikis/Wiki-shutdown-plan for the plan.

Jump to: navigation, search

Difference between revisions of "Scout/Tutorial/3.7/IMAP Step-by-Step"

< Scout‎ | Tutorial‎ | 3.7
(HowTo make a Mail-Client/Server-Application with Eclipse Scout)
(16 intermediate revisions by 9 users not shown)
Line 1: Line 1:
== HowTo make a Mail-Client/Server-Application with Eclipse Scout  ==
+
{{ScoutPage|cat=Tutorial 3.7}}
  
 +
=HowTo make a Mail-Client/Server-Application with Eclipse Scout=
 +
With this HowTo you'll get a short overview how easy it is, to create a simple client/server application. The server will connect to your imap-server and send the responses to your client. It's nothing fancy but it'll work ;-)
 +
==In the Scout-Perspective==
 +
===Create a scout project===
 
*Ctrl-N for a new Project.
 
*Ctrl-N for a new Project.
 
*In the wizard choose Scout->Scout Project
 
*In the wizard choose Scout->Scout Project
Line 10: Line 14:
 
*Click to "Finish" and wait a moment for the generation of the projects.<br>Now you have 4 projects with some source to start with.
 
*Click to "Finish" and wait a moment for the generation of the projects.<br>Now you have 4 projects with some source to start with.
 
*Change the perspective to "Scout".
 
*Change the perspective to "Scout".
 +
===Make your project JavaMail aware===
 +
 +
Download JavaMail from Oracle and put it into the endorsed directory of the JRE used by Eclipse IDE. It can be downloaded from http://www.oracle.com/technetwork/java/index-138643.html. Afterwards, in order to make use of its contained classes, you have to adapt the build path of your Eclipse projects. In the build properties, you can find a tab 'JRE System library'. Click the node 'Access rules' and add a new entry with the pattern 'javax/**' and visibility 'Accessible'.
 +
 +
Note: This change in build path is only required for development time to compile sources referring to classes in endorsed directory. When installing the product, those Jars are either already included in the application container or must be added manually to the respective endorsed directory. Please refere to the documentation of the application server in use.
 +
 +
For more background, please see [http://www.eclipse.org/forums/index.php?t=msg&th=202571&start=0&S=208ef84eb179d95846ecb91b4bb1da68 JavaMail and JAX-WS in Eclipse Scout [message #647259]].
 +
 +
===Create an outline===
 
*In the "Scout Projects" tree navigate to the yellow box "com.example.mail.client.core" -> Desktop -> Outlines and right-click "New Outline...".
 
*In the "Scout Projects" tree navigate to the yellow box "com.example.mail.client.core" -> Desktop -> Outlines and right-click "New Outline...".
 
*In the Name field enter "Mail" and hit Return. The Translation box will open. At least enter in the default field "Mail".<br>Type Name and Outline can be left to default. Hit Return or click "Finish".
 
*In the Name field enter "Mail" and hit Return. The Translation box will open. At least enter in the default field "Mail".<br>Type Name and Outline can be left to default. Hit Return or click "Finish".
Line 21: Line 34:
 
**"messageCount" / int
 
**"messageCount" / int
 
**"unreadMessageCount" / int
 
**"unreadMessageCount" / int
*Click on the MailNodePage and in the "Scout Oject Properties" view click on the last link "Get Bookmark Identifier". Accept the next Messagebox with yes.
+
*Click on the MailNodePage and in the "Scout Object Properties" view click on the last link "Get Bookmark Identifier". Accept the next Messagebox with yes.
 
*In the editor pane the cursor will jump to the newly created method and there you can overwrite the null-return with following:<br>getFolderId()
 
*In the editor pane the cursor will jump to the newly created method and there you can overwrite the null-return with following:<br>getFolderId()
*In the "Scout Oject Properties"-view click on the link "Exec Init Page". Accept the next Messagebox with yes.
+
*To show the IMAP folder names, overwrite the current code of the method getConfiguredTitle, i.e. return Texts.get("Mail"), with following:
 +
<source lang="java">
 +
if(getFolderName()!=null){
 +
  return getFolderName();
 +
}
 +
else{
 +
  return Texts.get("Mail");
 +
}
 +
</source>
 +
*In the "Scout Object Properties"-view click on the link "Exec Init Page". Accept the next Messagebox with yes.
 
*In the editor pane the cursor will jump to the newly created method and there you can enter following code:<br>setTableVisible(false);
 
*In the editor pane the cursor will jump to the newly created method and there you can enter following code:<br>setTableVisible(false);
 
*Repeat the last two steps again with following methods:
 
*Repeat the last two steps again with following methods:
Line 62: Line 84:
 
getOutline().setStatus(new ProcessingStatus(""+getMessageCount()+" message(s)",IStatus.INFO));
 
getOutline().setStatus(new ProcessingStatus(""+getMessageCount()+" message(s)",IStatus.INFO));
 
</source>
 
</source>
*With Ctrl-o you can resolve some imports. The missing Classes you can ignore for now. You'll create them in a moment.
+
*With Ctrl-Shift-O you can resolve some imports. The missing Classes you can ignore for now. You'll create them in a moment.
 +
 
 +
===Create a form===
 
*Right-Click on the folder "Forms" -> "New Form..."
 
*Right-Click on the folder "Forms" -> "New Form..."
 
*Name: Mail<br>Uncheck "Create form ID" the other fields you can leave the default values --> Next
 
*Name: Mail<br>Uncheck "Create form ID" the other fields you can leave the default values --> Next
*Now you could drag'n'drop the permissions and services if you had more the one module. In this example you have just one module (core) so you can just uncheck classes you don't need.<br>Uncheck the handlers (you'll create an other in a moment) and the permissions (you don't need permissions in this example) -> Finish
+
*Now you could drag'n'drop the permissions and services if you had more than one module. In this example you have just one module (core) so you can just uncheck classes you don't need.<br>Uncheck the handlers (you'll create an other in a moment) and the permissions (you don't need permissions in this example) -> Finish
 +
*In the "Scout Object Properties"-view change "Display Hint" to "View" and "Display View Id" to "Center"
 
*Right-Click on Variables -> "New Property Bean..."
 
*Right-Click on Variables -> "New Property Bean..."
 
*Name: "folderId" / Bean Type: String -> Finish
 
*Name: "folderId" / Bean Type: String -> Finish
Line 72: Line 97:
 
*Name: empty<br>Type Name: SplitBoxField -> Finish
 
*Name: empty<br>Type Name: SplitBoxField -> Finish
 
*Click on the newly created SplitBoxField.
 
*Click on the newly created SplitBoxField.
*In the "Scout Oject Properties"-view uncheck "Split Horizontal" and change the "Splitter Position" to 0.3.
+
*In the "Scout Object Properties"-view uncheck "Split Horizontal" and change the "Splitter Position" to 0.3.
 
*Right-Click on the SplitBoxField -> "New Form Field..."
 
*Right-Click on the SplitBoxField -> "New Form Field..."
 
*Choose the "Table Field" -> Next
 
*Choose the "Table Field" -> Next
 
*Name: empty<br>Type Name: MailTableField -> Finish
 
*Name: empty<br>Type Name: MailTableField -> Finish
 
*Right-Click on the SplitBoxField -> "New Form Field..."
 
*Right-Click on the SplitBoxField -> "New Form Field..."
*Click on the checkbox "show all fields" and enter or search "AbstractMailField". -> Next
+
*Click on the checkbox at the bottom "show all fields" and enter or search "AbstractHtmlField". -> Next
 
*Name: empty<br>Type Name: MailField -> Finish
 
*Name: empty<br>Type Name: MailField -> Finish
 
*Open the MailTableField folder and the Table folder with a click on the "+".
 
*Open the MailTableField folder and the Table folder with a click on the "+".
Line 83: Line 108:
 
*Choose "String Column" -> Next
 
*Choose "String Column" -> Next
 
*Name: empty / Type Name: FolderIdColumn -> Finish
 
*Name: empty / Type Name: FolderIdColumn -> Finish
*Repeat 36./37. with following:
+
*Repeat the last two steps with following:
 
**"Long Column" / Name: empty / Type Name: UidColumn
 
**"Long Column" / Name: empty / Type Name: UidColumn
 
**"String Column" / Name: "From" / Type Name: FromColumn
 
**"String Column" / Name: "From" / Type Name: FromColumn
Line 89: Line 114:
 
**"Date Column" / Name: "Received Date" / Type Name: ReceivedDateColumn
 
**"Date Column" / Name: "Received Date" / Type Name: ReceivedDateColumn
 
**"Integer Column" / Name: "Size" / Type Name: SizeColumn
 
**"Integer Column" / Name: "Size" / Type Name: SizeColumn
*FolderIdColumn: In the "Scout Oject Properties"-view uncheck "Displayable" and check "Primary Key".
+
*FolderIdColumn: In the "Scout Object Properties"-view uncheck "Displayable" and check "Primary Key".
*Same as 40. for the UidColumn.
+
*Same as the last point for the UidColumn.
 
*FromColumn, SubjectColumn: Change "Width" to 200.
 
*FromColumn, SubjectColumn: Change "Width" to 200.
 
*ReceivedDateColumn: Change "Width" to 100. In "Format" enter "EE dd.MM.yyyy HH:mm". Check "Has Time".
 
*ReceivedDateColumn: Change "Width" to 100. In "Format" enter "EE dd.MM.yyyy HH:mm". Check "Has Time".
 
*SizeColumn: Change "Width" to 50.
 
*SizeColumn: Change "Width" to 50.
 
*MailField: Change "Grid H" to 10.
 
*MailField: Change "Grid H" to 10.
*Change back to the Table: Click to "Exec Rows Selected" and enter following (you'll get some errors, some can be resolved with Ctrl-O, the you'll be able to resolve lateron):
+
*Change back to the Table: Click to "Exec Rows Selected" and enter following (you'll get some errors, some can be resolved with Ctrl-Shift-O, the you'll be able to resolve lateron):
 
<source lang="java">
 
<source lang="java">
 
if(rows.length==1){
 
if(rows.length==1){
   byte[] data=SERVICES.getService(IMailProcessService.class).getMessage(getFolderId()(),getUidColumn().getValue(rows[0]));
+
   byte[] data=SERVICES.getService(IMailProcessService.class).getMessage(getFolderId(),getUidColumn().getValue(rows[0]));
 
   if(data!=null){
 
   if(data!=null){
 
     try{
 
     try{
       getMailField().setValue(new MimeMessage(null,new ByteArrayInputStream(data)));
+
       Object content=new MimeMessage(null,new ByteArrayInputStream(data)).getContent();
 +
      if(content instanceof String) {
 +
        getMailField().setValue((String)content);
 +
      }
 +
      if(content instanceof javax.mail.internet.MimeMultipart) {
 +
        InputStream is=((javax.mail.internet.MimeMultipart)content).getBodyPart(0).getInputStream();
 +
        getMailField().setValue(IOUtility.getContent(new InputStreamReader(is), true));
 +
      }
 
     }
 
     }
 
     catch(javax.mail.MessagingException e){
 
     catch(javax.mail.MessagingException e){
 +
      throw new ProcessingException(e.getMessage(),e);
 +
    }
 +
    catch(IOException e){
 
       throw new ProcessingException(e.getMessage(),e);
 
       throw new ProcessingException(e.getMessage(),e);
 
     }
 
     }
Line 119: Line 154:
 
*Open the Handlers folder and click on the ViewHandler. Click to "Exec Load" and enter following:
 
*Open the Handlers folder and click on the ViewHandler. Click to "Exec Load" and enter following:
 
<source lang="java">
 
<source lang="java">
Object[][] data=SERVICES.getService(IMailProcessService.class).getMessageHeaders(getFolderId()());
+
Object[][] data=SERVICES.getService(IMailProcessService.class).getMessageHeaders(getFolderId());
 
getMailTableField().getTable().replaceRowsByMatrix(data);
 
getMailTableField().getTable().replaceRowsByMatrix(data);
 
</source>
 
</source>
*Go to: com.example.mail.service.core -> "Process Services" -> MailProcessService.
+
===Create a service===
 +
*Go to: com.example.mail.server.core -> "Process Services" -> MailProcessService.
 
*Click on the IMailProcessService and in the editor pane delete all method descriptions and enter following:
 
*Click on the IMailProcessService and in the editor pane delete all method descriptions and enter following:
 
<source lang="java">
 
<source lang="java">
Line 148: Line 184:
 
   }
 
   }
 
   if(principal != null){
 
   if(principal != null){
     ImapAdapter adapter=new ImapAdapter(principal.getHost(), 143, principal.getUsername(), principal.getPassword());
+
     ImapAdapter adapter=new ImapAdapter(principal.getHost(), principal.getPort(), principal.getUsername(), principal.getPassword());
 
     adapter.setUseSSL(true);
 
     adapter.setUseSSL(true);
 
     return adapter;
 
     return adapter;
Line 169: Line 205:
 
           if(f instanceof javax.mail.UIDFolder){
 
           if(f instanceof javax.mail.UIDFolder){
 
             if((f.getType() & javax.mail.Folder.HOLDS_MESSAGES)!=0){
 
             if((f.getType() & javax.mail.Folder.HOLDS_MESSAGES)!=0){
               Object[] row=new Object[]{f.getFullName(),f.getName(),f.getMessageCount(),f.getNewMessageCount(),f.getUnreadMessageCount()};
+
               Object[] row={f.getFullName(),f.getName(),f.getMessageCount(),f.getNewMessageCount(),f.getUnreadMessageCount()};
 
               rows.add(row);
 
               rows.add(row);
 
             }
 
             }
 
             else if((f.getType() & javax.mail.Folder.HOLDS_FOLDERS)!=0){
 
             else if((f.getType() & javax.mail.Folder.HOLDS_FOLDERS)!=0){
               Object[] row=new Object[]{f.getFullName(),f.getName(),0,0,0};
+
               Object[] row={f.getFullName(),f.getName(),0,0,0};
 
               rows.add(row);
 
               rows.add(row);
 
             }
 
             }
Line 180: Line 216:
 
       }
 
       }
 
     }
 
     }
     return rows.toArray(new Object[0][0]);
+
     return rows.toArray(new Object[0][]);
 
   }
 
   }
 
   catch(ProcessingException p){
 
   catch(ProcessingException p){
Line 212: Line 248:
 
           for(javax.mail.Message m: msgs){
 
           for(javax.mail.Message m: msgs){
 
             if(!m.isExpunged()){
 
             if(!m.isExpunged()){
               Object[] row=new Object[]{folderName, ((javax.mail.UIDFolder)f).getUID(m),firstAddress(m.getFrom()),m.getSubject(),m.getSentDate(),m.getSize()};
+
               Object[] row={folderName, ((javax.mail.UIDFolder)f).getUID(m),firstAddress(m.getFrom()),m.getSubject(),m.getSentDate(),m.getSize()};
 
               rows.add(row);
 
               rows.add(row);
 
             }
 
             }
Line 222: Line 258:
 
       }
 
       }
 
     }
 
     }
     return rows.toArray(new Object[0][0]);
+
     return rows.toArray(new Object[0][]);
 
   }
 
   }
 
   catch(ProcessingException p){
 
   catch(ProcessingException p){
Line 279: Line 315:
 
}
 
}
 
</source>
 
</source>
 +
 +
==Back to the Java-Perspective==
 +
===Some additional Code to speedup the process===
 +
*Switch to the Java perspective and add following two classes to the package com.example.mail.server.core:
 +
<source lang="java">
 +
package com.example.mail.server.core;
 +
 +
import java.io.Serializable;
 +
import java.security.Principal;
 +
 +
public class ImapPrincipal implements Principal, Serializable{
 +
  private static final long serialVersionUID=1L;
 +
 +
  private String m_host;
 +
  private int m_port;
 +
  private String m_username;
 +
  private String m_password;
 +
 +
  public ImapPrincipal(String host, int port, String username, String password) {
 +
    if(host == null) throw new IllegalArgumentException("host must not be null");
 +
    if(port == 0) port = 143;
 +
    if(username == null) throw new IllegalArgumentException("username must not be null");
 +
    if(password == null) throw new IllegalArgumentException("password must not be null");
 +
    m_host=host;
 +
    m_port=port;
 +
    m_username=username;
 +
    m_password=password;
 +
  }
 +
 +
  public String getName(){
 +
    return m_username+"@"+m_host+":"+m_port;
 +
  }
 +
 +
  public String getHost(){
 +
    return m_host;
 +
  }
 +
 +
  public int getPort(){
 +
    return m_port;
 +
  }
 +
 +
  public String getPassword(){
 +
    return m_password;
 +
  }
 +
 +
  public String getUsername(){
 +
    return m_username;
 +
  }
 +
 +
  @Override
 +
  public int hashCode(){
 +
    return getName().hashCode();
 +
  }
 +
 +
  @Override
 +
  public boolean equals(Object other){
 +
    if(other == this) return true;
 +
    if(!(other instanceof ImapPrincipal)){
 +
      return false;
 +
    }
 +
    else{
 +
      String myFullName=getName();
 +
      String otherFullName=((ImapPrincipal)other).getName();
 +
      return myFullName.equals(otherFullName);
 +
    }
 +
  }
 +
 +
  @Override
 +
  public String toString(){
 +
    return getName();
 +
  }
 +
}
 +
</source>
 +
<source lang="java">
 +
package com.example.mail.server.core;
 +
 +
import java.io.IOException;
 +
import java.security.AccessController;
 +
import java.security.Principal;
 +
import java.security.PrivilegedActionException;
 +
import java.security.PrivilegedExceptionAction;
 +
 +
import javax.security.auth.Subject;
 +
import javax.servlet.Filter;
 +
import javax.servlet.FilterChain;
 +
import javax.servlet.FilterConfig;
 +
import javax.servlet.ServletException;
 +
import javax.servlet.ServletRequest;
 +
import javax.servlet.ServletResponse;
 +
import javax.servlet.http.HttpServletRequest;
 +
import javax.servlet.http.HttpServletResponse;
 +
 +
import org.eclipse.scout.commons.Base64Utility;
 +
import org.eclipse.scout.commons.NumberUtility;
 +
import org.eclipse.scout.commons.logger.IScoutLogger;
 +
import org.eclipse.scout.commons.logger.ScoutLogManager;
 +
import org.eclipse.scout.http.servletfilter.FilterConfigInjection;
 +
import org.eclipse.scout.rt.server.services.common.imap.ImapAdapter;
 +
import org.eclipse.scout.rt.server.servlet.filter.SecureHttpServletRequestWrapper;
 +
import org.eclipse.scout.rt.shared.services.common.security.SimplePrincipal;
 +
 +
public class ImapSecurityFilter implements Filter{
 +
  private static IScoutLogger LOG=ScoutLogManager.getLogger(ImapSecurityFilter.class);
 +
 +
  private FilterConfigInjection m_injection;
 +
  private Object m_lock;
 +
  private String m_host;
 +
  private int m_port;
 +
 +
  public ImapSecurityFilter(){
 +
    m_lock=new Object();
 +
  }
 +
 +
  public void init(FilterConfig config0) throws ServletException{
 +
    m_injection=new FilterConfigInjection(config0, getClass());
 +
    FilterConfigInjection.FilterConfig config=m_injection.getAnyConfig();
 +
    String host=config.getInitParameter("host");
 +
    if(host==null) throw new ServletException("missing init-paramter 'host'");
 +
    m_host=host;
 +
    String port=config.getInitParameter("port");
 +
    if(port==null) port="143";
 +
    m_port=NumberUtility.parseInt(port);
 +
  }
 +
 +
  public void destroy(){
 +
    m_host=null;
 +
    m_port=143;
 +
    m_injection=null;
 +
  }
 +
 +
  public void doFilter(ServletRequest in, ServletResponse out, final FilterChain chain) throws IOException, ServletException{
 +
    FilterConfigInjection.FilterConfig config=m_injection.getConfig(in);
 +
    if(!config.isActive()){
 +
      chain.doFilter(in, out);
 +
      return;
 +
    }
 +
    //
 +
    final HttpServletRequest req=(HttpServletRequest)in;
 +
    final HttpServletResponse res=(HttpServletResponse)out;
 +
    // touch the session so it is effectively used
 +
    req.getSession();
 +
    // check subject on session
 +
    Subject subject;
 +
    synchronized(m_lock){
 +
      subject=findSubjectOnSession(req, res);
 +
      if(subject == null || subject.getPrincipals().size() == 0){
 +
        // try negotiate
 +
        Principal p=negotiate(req, res);
 +
        if(p==null){
 +
          res.sendError(HttpServletResponse.SC_UNAUTHORIZED);
 +
          return;
 +
        }
 +
        if(subject == null || subject.isReadOnly()){
 +
          subject=new Subject();
 +
        }
 +
        subject.getPrincipals().add(p);
 +
        subject.setReadOnly();
 +
        req.getSession().setAttribute(Subject.class.getName(), subject);
 +
      }
 +
    }
 +
    // run in subject
 +
    if(Subject.getSubject(AccessController.getContext()) != null){
 +
      doFilterInternal(req, res, chain);
 +
    }
 +
    else{
 +
      try{
 +
        Subject.doAs(subject, new PrivilegedExceptionAction(){
 +
          public Object run() throws Exception{
 +
            doFilterInternal(req, res, chain);
 +
            return null;
 +
          }
 +
        });
 +
      }
 +
      catch(PrivilegedActionException e){
 +
        Throwable t=e.getCause();
 +
        if(t instanceof IOException){
 +
          throw (IOException)t;
 +
        }
 +
        else if(t instanceof ServletException){
 +
          throw (ServletException)t;
 +
        }
 +
        else{
 +
          throw new ServletException(t);
 +
        }
 +
      }
 +
    }
 +
  }
 +
 +
  private void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws IOException, ServletException{
 +
    if(req.getRemoteUser() == null && req.getUserPrincipal() == null){
 +
      Principal principal=Subject.getSubject(AccessController.getContext()).getPrincipals().iterator().next();
 +
      chain.doFilter(new SecureHttpServletRequestWrapper(req, principal, "BASIC"), res);
 +
    }
 +
    else{
 +
      chain.doFilter(req, res);
 +
    }
 +
  }
 +
 +
  private Subject findSubjectOnSession(HttpServletRequest req, HttpServletResponse resp){
 +
    // check if we are already authenticated
 +
    Subject subject=null;
 +
    if(subject == null){
 +
      subject=Subject.getSubject(AccessController.getContext());
 +
    }
 +
    if(subject == null){
 +
      Object o=null;
 +
      o=req.getSession().getAttribute(Subject.class.getName());
 +
      if(o instanceof Subject){
 +
        subject=(Subject)o;
 +
      }
 +
    }
 +
    if(subject == null){
 +
      Principal principal=req.getUserPrincipal();
 +
      if(principal == null || principal.getName() == null || principal.getName().trim().length() == 0){
 +
        principal=null;
 +
        String name=req.getRemoteUser();
 +
        if(name != null && name.trim().length() > 0){
 +
          principal=new SimplePrincipal(name);
 +
        }
 +
      }
 +
      if(principal != null){
 +
        subject=new Subject();
 +
        subject.getPrincipals().add(principal);
 +
        subject.setReadOnly();
 +
        req.getSession().setAttribute(Subject.class.getName(), subject);
 +
      }
 +
    }
 +
    return subject;
 +
  }
 +
 +
  /**
 +
  * set the 'WWW-Authenticate' value on the response to enforce the client to
 +
  * provide login data.
 +
  *
 +
  * @param req
 +
  * @param resp
 +
  * @return
 +
  */
 +
  protected Principal negotiate(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException{
 +
    String h=req.getHeader("Authorization");
 +
    if(h != null && h.matches("Basic .*")){
 +
      String[] a=new String(Base64Utility.decode(h.substring(6))).split(":", 2);
 +
      String user=a[0].toLowerCase();
 +
      String pass=a[1];
 +
      if(user != null && pass != null){
 +
        ImapAdapter adapter=new ImapAdapter(m_host,m_port,user,pass);
 +
        adapter.setUseSSL(true);
 +
        try{
 +
          adapter.connect();
 +
          adapter.closeConnection();
 +
          return new ImapPrincipal(m_host,m_port,user,pass);
 +
        }
 +
        catch(Throwable t){
 +
          //nop, failed
 +
        }
 +
      }
 +
    }
 +
    resp.setHeader("WWW-Authenticate", "Basic realm=\""+m_host+"\"");
 +
    return null;
 +
  }
 +
}
 +
</source>
 +
===Starting Server and Client===
 +
*In the Project com.example.mail.server.core navigate to products -> development. Open the config.ini. Delete the last four lines with BasicSecurityFilter and enter following line
 +
<pre>
 +
### IMAP security filter and service
 +
com.example.mail.server.core.ImapSecurityFilter#host=<your IMAP-Server-URL>
 +
com.example.mail.server.core.ImapSecurityFilter#port=<your IMAP-Server-Port> #(optional/default:143)
 +
</pre>
 +
*Open the plugin.xml of the project com.example.mail.server.core, remove the filter "BasicSecurityFilter" from the extension point "org.eclipse.scout.http.servletfilter.filters" and add following filter:
 +
<source lang="xml">
 +
<filter
 +
      aliases="/process"
 +
      class="com.example.mail.server.core.ImapSecurityFilter"
 +
      ranking="10">
 +
  <init-param
 +
        name="host"
 +
        value="&lt;define in config.ini&gt;">
 +
  </init-param>
 +
</filter>
 +
</source>
 +
*Open the mailCore-server-dev.product, add "javax.mail.jre16" to the dependencies and Launch this product.
 +
*Open the mailCore-swt-client-dev.product, add "javax.mail.jre16" to the dependencies and Launch this product.

Revision as of 08:28, 12 April 2012

The Scout documentation has been moved to https://eclipsescout.github.io/.

HowTo make a Mail-Client/Server-Application with Eclipse Scout

With this HowTo you'll get a short overview how easy it is, to create a simple client/server application. The server will connect to your imap-server and send the responses to your client. It's nothing fancy but it'll work ;-)

In the Scout-Perspective

Create a scout project

  • Ctrl-N for a new Project.
  • In the wizard choose Scout->Scout Project
  • Choose a Project Name, Postfix and Alias.
    • Project Name: com.example.mail
    • Project Postfix: core
    • Project Alias: (automaticly set to mailCore)
  • We just want to make a swt application, so we uncheck com.example.mail.ui.swing.core.
  • Click to "Finish" and wait a moment for the generation of the projects.
    Now you have 4 projects with some source to start with.
  • Change the perspective to "Scout".

Make your project JavaMail aware

Download JavaMail from Oracle and put it into the endorsed directory of the JRE used by Eclipse IDE. It can be downloaded from http://www.oracle.com/technetwork/java/index-138643.html. Afterwards, in order to make use of its contained classes, you have to adapt the build path of your Eclipse projects. In the build properties, you can find a tab 'JRE System library'. Click the node 'Access rules' and add a new entry with the pattern 'javax/**' and visibility 'Accessible'.

Note: This change in build path is only required for development time to compile sources referring to classes in endorsed directory. When installing the product, those Jars are either already included in the application container or must be added manually to the respective endorsed directory. Please refere to the documentation of the application server in use.

For more background, please see JavaMail and JAX-WS in Eclipse Scout [message #647259].

Create an outline

  • In the "Scout Projects" tree navigate to the yellow box "com.example.mail.client.core" -> Desktop -> Outlines and right-click "New Outline...".
  • In the Name field enter "Mail" and hit Return. The Translation box will open. At least enter in the default field "Mail".
    Type Name and Outline can be left to default. Hit Return or click "Finish".
  • Open the oultine folder and right on the "Child Pages" folder "New Page...".
  • Choose "AbstractPageWithNodes" and hit return.
  • In the Name field enter "Mail" and double hit Return. The default values are ok for now.
  • Right-Click on the Variables folder under "MailNodePage" -> "New Property Bean".
  • Name: "folderId" / Bean Type: String -> return
  • Repeat the last two steps again with
    • "folderName" / String
    • "messageCount" / int
    • "unreadMessageCount" / int
  • Click on the MailNodePage and in the "Scout Object Properties" view click on the last link "Get Bookmark Identifier". Accept the next Messagebox with yes.
  • In the editor pane the cursor will jump to the newly created method and there you can overwrite the null-return with following:
    getFolderId()
  • To show the IMAP folder names, overwrite the current code of the method getConfiguredTitle, i.e. return Texts.get("Mail"), with following:
if(getFolderName()!=null){
  return getFolderName();
}
else{
  return Texts.get("Mail");
}
  • In the "Scout Object Properties"-view click on the link "Exec Init Page". Accept the next Messagebox with yes.
  • In the editor pane the cursor will jump to the newly created method and there you can enter following code:
    setTableVisible(false);
  • Repeat the last two steps again with following methods:
Exec Decorate Cell
int totalCount=getMessageCount();
if(totalCount>=0){
  cell.setTooltipText("Folder contains "+totalCount+" messages");
}
else{
  cell.setTooltipText(null);
}
Exec Page Activated
if(getFolderId()!=null){
  if(getDetailForm()==null){
    MailForm f=new MailForm();
    f.setFolderId(getFolderId());
    setDetailForm(f);
    f.startView();
  }
}
Exec Create Child Pages
for(Object[] row: SERVICES.getService(IMailProcessService.class).getChildFolders(getFolderId())){
  MailNodePage page=new MailNodePage();
  page.setFolderId((String)row[0]);
  page.setFolderName((String)row[1]);
  page.setMessageCount((Integer)row[2]);
  page.setUnreadMessageCount((Integer)row[4]);
  pageList.add(page);
}
Exec Set Page Status
getOutline().setStatus(new ProcessingStatus(""+getMessageCount()+" message(s)",IStatus.INFO));
  • With Ctrl-Shift-O you can resolve some imports. The missing Classes you can ignore for now. You'll create them in a moment.

Create a form

  • Right-Click on the folder "Forms" -> "New Form..."
  • Name: Mail
    Uncheck "Create form ID" the other fields you can leave the default values --> Next
  • Now you could drag'n'drop the permissions and services if you had more than one module. In this example you have just one module (core) so you can just uncheck classes you don't need.
    Uncheck the handlers (you'll create an other in a moment) and the permissions (you don't need permissions in this example) -> Finish
  • In the "Scout Object Properties"-view change "Display Hint" to "View" and "Display View Id" to "Center"
  • Right-Click on Variables -> "New Property Bean..."
  • Name: "folderId" / Bean Type: String -> Finish
  • Right-Click on the MainBox under the MailForm -> "New Form Field..."
  • In this dialog you get a list of common used Fields. For this example you start with an "uncommon" field. So click on the checkbox at the bottom "show all fields" and enter or search "AbstractSplitBox". -> Next
  • Name: empty
    Type Name: SplitBoxField -> Finish
  • Click on the newly created SplitBoxField.
  • In the "Scout Object Properties"-view uncheck "Split Horizontal" and change the "Splitter Position" to 0.3.
  • Right-Click on the SplitBoxField -> "New Form Field..."
  • Choose the "Table Field" -> Next
  • Name: empty
    Type Name: MailTableField -> Finish
  • Right-Click on the SplitBoxField -> "New Form Field..."
  • Click on the checkbox at the bottom "show all fields" and enter or search "AbstractHtmlField". -> Next
  • Name: empty
    Type Name: MailField -> Finish
  • Open the MailTableField folder and the Table folder with a click on the "+".
  • Right-Click on the Columns folder -> "New Column..."
  • Choose "String Column" -> Next
  • Name: empty / Type Name: FolderIdColumn -> Finish
  • Repeat the last two steps with following:
    • "Long Column" / Name: empty / Type Name: UidColumn
    • "String Column" / Name: "From" / Type Name: FromColumn
    • "String Column" / Name: "Subject" / Type Name: SubjectColumn
    • "Date Column" / Name: "Received Date" / Type Name: ReceivedDateColumn
    • "Integer Column" / Name: "Size" / Type Name: SizeColumn
  • FolderIdColumn: In the "Scout Object Properties"-view uncheck "Displayable" and check "Primary Key".
  • Same as the last point for the UidColumn.
  • FromColumn, SubjectColumn: Change "Width" to 200.
  • ReceivedDateColumn: Change "Width" to 100. In "Format" enter "EE dd.MM.yyyy HH:mm". Check "Has Time".
  • SizeColumn: Change "Width" to 50.
  • MailField: Change "Grid H" to 10.
  • Change back to the Table: Click to "Exec Rows Selected" and enter following (you'll get some errors, some can be resolved with Ctrl-Shift-O, the you'll be able to resolve lateron):
if(rows.length==1){
  byte[] data=SERVICES.getService(IMailProcessService.class).getMessage(getFolderId(),getUidColumn().getValue(rows[0]));
  if(data!=null){
    try{
      Object content=new MimeMessage(null,new ByteArrayInputStream(data)).getContent();
      if(content instanceof String) {
        getMailField().setValue((String)content);
      }
      if(content instanceof javax.mail.internet.MimeMultipart) {
        InputStream is=((javax.mail.internet.MimeMultipart)content).getBodyPart(0).getInputStream();
        getMailField().setValue(IOUtility.getContent(new InputStreamReader(is), true));
      }
    }
    catch(javax.mail.MessagingException e){
      throw new ProcessingException(e.getMessage(),e);
    }
    catch(IOException e){
      throw new ProcessingException(e.getMessage(),e);
    }
  }
  else{
    getMailField().setValue(null);
  }
}
else{
  getMailField().setValue(null);
}
  • Right-Click on the "Handlers" folder under the MailForm -> "New Handler..."
  • Choose "Form Handler" -> Next. Type Name: ViewHandler -> Finish
  • Open the Handlers folder and click on the ViewHandler. Click to "Exec Load" and enter following:
Object[][] data=SERVICES.getService(IMailProcessService.class).getMessageHeaders(getFolderId());
getMailTableField().getTable().replaceRowsByMatrix(data);

Create a service

  • Go to: com.example.mail.server.core -> "Process Services" -> MailProcessService.
  • Click on the IMailProcessService and in the editor pane delete all method descriptions and enter following:
/**
 * @return fullName (ID), name, count, newCount, unreadCount
 */
public Object[][] getChildFolders(String parentName) throws ProcessingException;
 
/**
 * @return folderId (ID), uid (ID), from, subject, date, size
 */
public Object[][] getMessageHeaders(String folderId) throws ProcessingException;
 
/**
 * @return MimeMessage
 */
public byte[] getMessage(String folderd,long uid) throws ProcessingException;
  • Click on the MailProcessService and in the editor pane delete all method descriptions and enter following:
private ImapAdapter createImapAdapter(){
  ImapPrincipal principal=null;
  for(ImapPrincipal p : Subject.getSubject(AccessController.getContext()).getPrincipals(ImapPrincipal.class)){
    principal=p;
  }
  if(principal != null){
    ImapAdapter adapter=new ImapAdapter(principal.getHost(), principal.getPort(), principal.getUsername(), principal.getPassword());
    adapter.setUseSSL(true);
    return adapter;
  }
  return null;
}
 
/**
 * @return fullName (ID), name, count, newCount, unreadCount
 */
public Object[][] getChildFolders(String parentName) throws ProcessingException{
  ImapAdapter adapter=createImapAdapter();
  try{
    ArrayList<Object[]> rows=new ArrayList<Object[]>();
    adapter.connect();
    javax.mail.Folder parent=parentName!=null ? adapter.getStore().getFolder(parentName) : adapter.getStore().getDefaultFolder();
    if(parent!=null && parent.exists()){
      for(javax.mail.Folder f : parent.list()){
        if(f.exists()){
          if(f instanceof javax.mail.UIDFolder){
            if((f.getType() & javax.mail.Folder.HOLDS_MESSAGES)!=0){
              Object[] row={f.getFullName(),f.getName(),f.getMessageCount(),f.getNewMessageCount(),f.getUnreadMessageCount()};
              rows.add(row);
            }
            else if((f.getType() & javax.mail.Folder.HOLDS_FOLDERS)!=0){
              Object[] row={f.getFullName(),f.getName(),0,0,0};
              rows.add(row);
            }
          }
        }
      }
    }
    return rows.toArray(new Object[0][]);
  }
  catch(ProcessingException p){
    throw p;
  }
  catch(Throwable t){
    throw new ProcessingException(t.getMessage(),t);
  }
  finally{
    adapter.closeConnection();
  }
}
 
/**
 * @return folderName (ID), uid (ID), from, subject, date, size
 */
public Object[][] getMessageHeaders(String folderName) throws ProcessingException{
  ImapAdapter adapter=createImapAdapter();
  try{
    ArrayList<Object[]> rows=new ArrayList<Object[]>();
    adapter.connect();
    javax.mail.Folder f=adapter.getStore().getFolder(folderName);
    if(f!=null && f.exists()){
      if(f instanceof javax.mail.UIDFolder){
        f.open(javax.mail.Folder.READ_ONLY);
        try{
          javax.mail.Message[] msgs=f.getMessages();
          javax.mail.FetchProfile fp=new javax.mail.FetchProfile();
          fp.add(javax.mail.FetchProfile.Item.ENVELOPE);
          f.fetch(msgs,fp);
          for(javax.mail.Message m: msgs){
            if(!m.isExpunged()){
              Object[] row={folderName, ((javax.mail.UIDFolder)f).getUID(m),firstAddress(m.getFrom()),m.getSubject(),m.getSentDate(),m.getSize()};
              rows.add(row);
            }
          }
        }
        finally{
          f.close(false);
        }
      }
    }
    return rows.toArray(new Object[0][]);
  }
  catch(ProcessingException p){
    throw p;
  }
  catch(Throwable t){
    throw new ProcessingException(t.getMessage(),t);
  }
  finally{
    adapter.closeConnection();
  }
}
 
public byte[] getMessage(String folderId,long uid) throws ProcessingException{
  ImapAdapter adapter=createImapAdapter();
  try{
    adapter.connect();
    javax.mail.Folder f=adapter.getStore().getFolder(folderId);
    if(f!=null && f.exists()){
      if(f instanceof javax.mail.UIDFolder){
        f.open(javax.mail.Folder.READ_ONLY);
        try{
          javax.mail.Message m=((javax.mail.UIDFolder)f).getMessageByUID(uid);
          if(m!=null && !m.isExpunged() && m instanceof javax.mail.internet.MimeMessage){
            ByteArrayOutputStream out=new ByteArrayOutputStream();
            m.writeTo(out);
            return out.toByteArray();
          }
        }
        finally{
          f.close(false);
        }
      }
    }
    return null;
  }
  catch(ProcessingException p){
    throw p;
  }
  catch(Throwable t){
    throw new ProcessingException(t.getMessage(),t);
  }
  finally{
    adapter.closeConnection();
  }
}
 
private String firstAddress(javax.mail.Address[] a){
  if(a!=null && a.length>0 && a[0] instanceof InternetAddress){
    InternetAddress ia=(InternetAddress)a[0];
    String s=ia.getPersonal();
    if(StringUtility.isNullOrEmpty(s)) s=ia.getAddress();
    return s;
  }
  return null;
}

Back to the Java-Perspective

Some additional Code to speedup the process

  • Switch to the Java perspective and add following two classes to the package com.example.mail.server.core:
package com.example.mail.server.core;
 
import java.io.Serializable;
import java.security.Principal;
 
public class ImapPrincipal implements Principal, Serializable{
  private static final long serialVersionUID=1L;
 
  private String m_host;
  private int m_port;
  private String m_username;
  private String m_password;
 
  public ImapPrincipal(String host, int port, String username, String password) {
    if(host == null) throw new IllegalArgumentException("host must not be null");
    if(port == 0) port = 143;
    if(username == null) throw new IllegalArgumentException("username must not be null");
    if(password == null) throw new IllegalArgumentException("password must not be null");
    m_host=host;
    m_port=port;
    m_username=username;
    m_password=password;
  }
 
  public String getName(){
    return m_username+"@"+m_host+":"+m_port;
  }
 
  public String getHost(){
    return m_host;
  }
 
  public int getPort(){
    return m_port;
  }
 
  public String getPassword(){
    return m_password;
  }
 
  public String getUsername(){
    return m_username;
  }
 
  @Override
  public int hashCode(){
    return getName().hashCode();
  }
 
  @Override
  public boolean equals(Object other){
    if(other == this) return true;
    if(!(other instanceof ImapPrincipal)){
      return false;
    }
    else{
      String myFullName=getName();
      String otherFullName=((ImapPrincipal)other).getName();
      return myFullName.equals(otherFullName);
    }
  }
 
  @Override
  public String toString(){
    return getName();
  }
}
package com.example.mail.server.core;
 
import java.io.IOException;
import java.security.AccessController;
import java.security.Principal;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
 
import javax.security.auth.Subject;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
 
import org.eclipse.scout.commons.Base64Utility;
import org.eclipse.scout.commons.NumberUtility;
import org.eclipse.scout.commons.logger.IScoutLogger;
import org.eclipse.scout.commons.logger.ScoutLogManager;
import org.eclipse.scout.http.servletfilter.FilterConfigInjection;
import org.eclipse.scout.rt.server.services.common.imap.ImapAdapter;
import org.eclipse.scout.rt.server.servlet.filter.SecureHttpServletRequestWrapper;
import org.eclipse.scout.rt.shared.services.common.security.SimplePrincipal;
 
public class ImapSecurityFilter implements Filter{
  private static IScoutLogger LOG=ScoutLogManager.getLogger(ImapSecurityFilter.class);
 
  private FilterConfigInjection m_injection;
  private Object m_lock;
  private String m_host;
  private int m_port;
 
  public ImapSecurityFilter(){
    m_lock=new Object();
  }
 
  public void init(FilterConfig config0) throws ServletException{
    m_injection=new FilterConfigInjection(config0, getClass());
    FilterConfigInjection.FilterConfig config=m_injection.getAnyConfig();
    String host=config.getInitParameter("host");
    if(host==null) throw new ServletException("missing init-paramter 'host'");
    m_host=host;
    String port=config.getInitParameter("port");
    if(port==null) port="143";
    m_port=NumberUtility.parseInt(port);
  }
 
  public void destroy(){
    m_host=null;
    m_port=143;
    m_injection=null;
  }
 
  public void doFilter(ServletRequest in, ServletResponse out, final FilterChain chain) throws IOException, ServletException{
    FilterConfigInjection.FilterConfig config=m_injection.getConfig(in);
    if(!config.isActive()){
      chain.doFilter(in, out);
      return;
    }
    //
    final HttpServletRequest req=(HttpServletRequest)in;
    final HttpServletResponse res=(HttpServletResponse)out;
    // touch the session so it is effectively used
    req.getSession();
    // check subject on session
    Subject subject;
    synchronized(m_lock){
      subject=findSubjectOnSession(req, res);
      if(subject == null || subject.getPrincipals().size() == 0){
        // try negotiate
        Principal p=negotiate(req, res);
        if(p==null){
          res.sendError(HttpServletResponse.SC_UNAUTHORIZED);
          return;
        }
        if(subject == null || subject.isReadOnly()){
          subject=new Subject();
        }
        subject.getPrincipals().add(p);
        subject.setReadOnly();
        req.getSession().setAttribute(Subject.class.getName(), subject);
      }
    }
    // run in subject
    if(Subject.getSubject(AccessController.getContext()) != null){
      doFilterInternal(req, res, chain);
    }
    else{
      try{
        Subject.doAs(subject, new PrivilegedExceptionAction(){
          public Object run() throws Exception{
            doFilterInternal(req, res, chain);
            return null;
          }
        });
      }
      catch(PrivilegedActionException e){
        Throwable t=e.getCause();
        if(t instanceof IOException){
          throw (IOException)t;
        }
        else if(t instanceof ServletException){
          throw (ServletException)t;
        }
        else{
          throw new ServletException(t);
        }
      }
    }
  }
 
  private void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws IOException, ServletException{
    if(req.getRemoteUser() == null && req.getUserPrincipal() == null){
      Principal principal=Subject.getSubject(AccessController.getContext()).getPrincipals().iterator().next();
      chain.doFilter(new SecureHttpServletRequestWrapper(req, principal, "BASIC"), res);
    }
    else{
      chain.doFilter(req, res);
    }
  }
 
  private Subject findSubjectOnSession(HttpServletRequest req, HttpServletResponse resp){
    // check if we are already authenticated
    Subject subject=null;
    if(subject == null){
      subject=Subject.getSubject(AccessController.getContext());
    }
    if(subject == null){
      Object o=null;
      o=req.getSession().getAttribute(Subject.class.getName());
      if(o instanceof Subject){
        subject=(Subject)o;
      }
    }
    if(subject == null){
      Principal principal=req.getUserPrincipal();
      if(principal == null || principal.getName() == null || principal.getName().trim().length() == 0){
        principal=null;
        String name=req.getRemoteUser();
        if(name != null && name.trim().length() > 0){
          principal=new SimplePrincipal(name);
        }
      }
      if(principal != null){
        subject=new Subject();
        subject.getPrincipals().add(principal);
        subject.setReadOnly();
        req.getSession().setAttribute(Subject.class.getName(), subject);
      }
    }
    return subject;
  }
 
  /**
   * set the 'WWW-Authenticate' value on the response to enforce the client to
   * provide login data.
   *
   * @param req
   * @param resp
   * @return
   */
  protected Principal negotiate(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException{
    String h=req.getHeader("Authorization");
    if(h != null && h.matches("Basic .*")){
      String[] a=new String(Base64Utility.decode(h.substring(6))).split(":", 2);
      String user=a[0].toLowerCase();
      String pass=a[1];
      if(user != null && pass != null){
        ImapAdapter adapter=new ImapAdapter(m_host,m_port,user,pass);
        adapter.setUseSSL(true);
        try{
          adapter.connect();
          adapter.closeConnection();
          return new ImapPrincipal(m_host,m_port,user,pass);
        }
        catch(Throwable t){
          //nop, failed
        }
      }
    }
    resp.setHeader("WWW-Authenticate", "Basic realm=\""+m_host+"\"");
    return null;
  }
}

Starting Server and Client

  • In the Project com.example.mail.server.core navigate to products -> development. Open the config.ini. Delete the last four lines with BasicSecurityFilter and enter following line
### IMAP security filter and service
com.example.mail.server.core.ImapSecurityFilter#host=<your IMAP-Server-URL>
com.example.mail.server.core.ImapSecurityFilter#port=<your IMAP-Server-Port> #(optional/default:143)
  • Open the plugin.xml of the project com.example.mail.server.core, remove the filter "BasicSecurityFilter" from the extension point "org.eclipse.scout.http.servletfilter.filters" and add following filter:
<filter
      aliases="/process"
      class="com.example.mail.server.core.ImapSecurityFilter"
      ranking="10">
   <init-param
         name="host"
         value="&lt;define in config.ini&gt;">
   </init-param>
</filter>
  • Open the mailCore-server-dev.product, add "javax.mail.jre16" to the dependencies and Launch this product.
  • Open the mailCore-swt-client-dev.product, add "javax.mail.jre16" to the dependencies and Launch this product.

Back to the top