Notice: This Wiki is now read only and edits are no longer possible. Please see: https://gitlab.eclipse.org/eclipsefdn/helpdesk/-/wikis/Wiki-shutdown-plan for the plan.
Difference between revisions of "Scout/Tutorial/4.0/IMAP Step-by-Step"
(Created page with "{{ScoutPage|cat=Tutorial 4.0}} =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 ...") |
(→Create a form) |
||
(25 intermediate revisions by the same user not shown) | |||
Line 6: | Line 6: | ||
*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 | ||
− | * | + | *Modify to the following: |
**Project Name: com.example.mail | **Project Name: com.example.mail | ||
**Project Postfix: core | **Project Postfix: core | ||
− | * | + | **We just want to make a swt application, so we uncheck com.example.mail.ui.swing.core and com.example.mail.ui.rap.core. |
− | *We just want to make a swt application, so we uncheck com.example.mail.ui.swing.core and com.example.mail.ui.rap.core . | + | **Leave all other settings on the defaults. |
*Click "Next" to go to the application template selection page | *Click "Next" to go to the application template selection page | ||
− | *Select "Outline Tree and Table Form" as application template. | + | *Select "Outline Tree and Table Form" as application template. |
− | *Click | + | *Click "Finish" and wait a moment for the generation of the projects.<br>Now you have 5 projects with some source to start with. |
− | + | ||
===Make your project JavaMail aware=== | ===Make your project JavaMail aware=== | ||
− | + | Due to problems with JAX-WS, JavaMail and JMS (see [http://www.eclipse.org/forums/index.php?t=msg&th=202571&start=0&S=208ef84eb179d95846ecb91b4bb1da68]) an endorsed directory needs to be provided for the JRE. Create the following file structure within the JRE you are using: | |
− | 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. | + | jre\lib\endorsed\javax.mail.jar |
+ | jre\lib\endorsed\javax.jms.jar | ||
+ | |||
+ | The javax.mail.jar can be downloaded from java.net: [http://java.net/projects/javamail/downloads/download/javax.mail.jar]. | ||
+ | |||
+ | The javax.jms.jar can be downloaded from oracle.com: [http://download.oracle.com/otndocs/jcp/7542-jms-1.1-fr-doc-oth-JSpec/]. The jar can be found within the zip file: /jms1.1/lib/javax.jms.jar | ||
+ | |||
+ | The two jar files should appear in the list of system libraries in the Eclipse JRE Definition (open Window->Preferences->Java->Installed JREs, Edit...). If not, add them manually or restart Eclipse. | ||
+ | |||
+ | Afterwards, in order to make use of its contained classes, you have to adapt the build path of your Eclipse projects. Switch to the Package Explorer and right click on your com.example.mail.client.core plugin, then click on Properties. Go to Java Build Path->Libraries Tab and expand your JRE System Library. Select "Access Rules" and press "Edit...". Add a new rule with Rule pattern "javax/**" and resolution "Accessible". | ||
+ | |||
+ | [[File:Scout_build_path_accessible.png]] | ||
+ | |||
+ | The same must now also be modified for the plugin com.example.mail.server.core. | ||
+ | |||
+ | Note: This change in build path is only required for development time to compile sources referring to classes in the 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]]. | 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=== | ===Create an outline=== | ||
− | *In the "Scout Projects" | + | *Switch back to the Scout Explorer view |
− | * | + | *In the "Scout Projects" folder navigate to the orange box "com.example.mail.client.core" and then Desktop -> Outlines -> StandardOutline -> Child Pages. |
− | + | *Right click on the "Child Pages" folder "New Page...". | |
*Choose "AbstractPageWithNodes" and hit return. | *Choose "AbstractPageWithNodes" and hit return. | ||
*In the Name field enter "Mail" and double hit Return. The default values are ok for now. | *In the Name field enter "Mail" and double hit Return. The default values are ok for now. | ||
+ | *Expand the Child Pages folder if not yet expanded. | ||
*Right-Click on the Variables folder under "MailNodePage" -> "New Property Bean". | *Right-Click on the Variables folder under "MailNodePage" -> "New Property Bean". | ||
*Name: "folderId" / Bean Type: String -> return | *Name: "folderId" / Bean Type: String -> return | ||
Line 35: | Line 51: | ||
**"messageCount" / int | **"messageCount" / int | ||
**"unreadMessageCount" / int | **"unreadMessageCount" / int | ||
− | + | *To show the IMAP folder names, overwrite the current code of the method getConfiguredTitle in the MailNodePage with following: | |
− | + | ||
− | *To show the IMAP folder names, overwrite the current code of the method getConfiguredTitle | + | |
<source lang="java"> | <source lang="java"> | ||
if(getFolderName()!=null){ | if(getFolderName()!=null){ | ||
Line 47: | Line 61: | ||
</source> | </source> | ||
*In the "Scout Object Properties"-view click on the link "Exec Init Page". | *In the "Scout Object Properties"-view click on the link "Exec Init Page". | ||
− | *In the editor pane the cursor will jump to the newly created method and there you can enter following code:< | + | *In the editor pane the cursor will jump to the newly created method and there you can enter following code: |
+ | <source lang="java"> | ||
+ | setTableVisible(false); | ||
+ | </source> | ||
*Repeat the last two steps again with following methods: | *Repeat the last two steps again with following methods: | ||
::Exec Decorate Cell | ::Exec Decorate Cell | ||
Line 81: | Line 98: | ||
} | } | ||
</source> | </source> | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
*With Ctrl-Shift-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. | ||
Line 91: | Line 105: | ||
*Name: Mail<br> The other fields you can leave the default values --> Next | *Name: Mail<br> 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.<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" | + | *Select the just created Form in the Scout Explorer (expand "Forms" if necessary). |
− | *Right-Click on Variables -> "New Property Bean..." | + | *In the "Scout Object Properties" View change "Display Hint" to "View" and "Display View Id" to "Center" and uncheck "Ask If Need Save". |
+ | *Right-Click on Variables below the MailForm -> "New Property Bean..." | ||
*Name: "folderId" / Bean Type: String -> Finish | *Name: "folderId" / Bean Type: String -> Finish | ||
*Right-Click on the MainBox under the MailForm -> "New Form Field..." | *Right-Click on the MainBox under the MailForm -> "New Form Field..." | ||
− | * | + | *Search for "SplitBox" -> Next |
*Name: empty<br>Class Name: SplitBoxField -> Finish | *Name: empty<br>Class Name: SplitBoxField -> Finish | ||
*Click on the newly created SplitBoxField. | *Click on the newly created SplitBoxField. | ||
*In the "Scout Object 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 " | + | *Choose the "TableField" -> Next |
*Name: empty<br>Class Name: MailTableField -> Finish | *Name: empty<br>Class Name: MailTableField -> Finish | ||
*Right-Click on the SplitBoxField -> "New Form Field..." | *Right-Click on the SplitBoxField -> "New Form Field..." | ||
− | * | + | *Search for "HtmlField". -> Next |
*Name: empty<br>Type Name: MailField -> Finish | *Name: empty<br>Type Name: MailField -> Finish | ||
− | * | + | *Expand the MailTableField folder and the Table folder. |
*Right-Click on the Columns folder -> "New Column..." | *Right-Click on the Columns folder -> "New Column..." | ||
− | *Choose " | + | *Choose "StringColumn" -> Next |
*Name: empty / Class Name: FolderIdColumn -> Finish | *Name: empty / Class Name: FolderIdColumn -> Finish | ||
− | *Repeat the last | + | *Repeat the last three steps with following: |
− | **" | + | **"LongColumn" / Name: empty / Class Name: UidColumn |
− | **" | + | **"StringColumn" / Name: "From" / Class Name: FromColumn |
− | **" | + | **"StringColumn" / Name: "Subject" / Class Name: SubjectColumn |
− | **" | + | **"DateColumn" / Name: "Received Date" / Class Name: ReceivedDateColumn |
− | **" | + | **"IntegerColumn" / Name: "Size" / Class Name: SizeColumn |
*FolderIdColumn: In the "Scout Object Properties"-view uncheck "Displayable" and check "Primary Key". | *FolderIdColumn: In the "Scout Object Properties"-view uncheck "Displayable" and check "Primary Key". | ||
*UidColumn: In the "Scout Object Properties"-view uncheck "Displayable". | *UidColumn: In the "Scout Object Properties"-view uncheck "Displayable". | ||
*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 | + | *ReceivedDateColumn: Change "Width" to 100. In "Format" enter "EE dd.MM.yyyy HH:mm". |
*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-Shift-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 others you'll be able to resolve lateron): |
<source lang="java"> | <source lang="java"> | ||
− | if(rows. | + | if(rows.size()==1){ |
− | byte[] data=SERVICES.getService(IMailService.class).getMessage(getFolderId(),getUidColumn().getValue(rows | + | byte[] data=SERVICES.getService(IMailService.class).getMessage(getFolderId(),getUidColumn().getValue(rows.get(0))); |
if(data!=null){ | if(data!=null){ | ||
try{ | try{ | ||
Line 160: | Line 175: | ||
===Create a service=== | ===Create a service=== | ||
− | *Go to: com.example.mail.server.core -> " | + | *Go to: com.example.mail.server.core -> "Services" -> MailService. |
*Click on the IMailService and in the editor pane delete all method descriptions and enter following: | *Click on the IMailService and in the editor pane delete all method descriptions and enter following: | ||
<source lang="java"> | <source lang="java"> | ||
Line 166: | Line 181: | ||
* @return fullName (ID), name, count, newCount, unreadCount | * @return fullName (ID), name, count, newCount, unreadCount | ||
*/ | */ | ||
− | + | Object[][] getChildFolders(String parentName) throws ProcessingException; | |
/** | /** | ||
* @return folderId (ID), uid (ID), from, subject, date, size | * @return folderId (ID), uid (ID), from, subject, date, size | ||
*/ | */ | ||
− | + | Object[][] getMessageHeaders(String folderId) throws ProcessingException; | |
/** | /** | ||
* @return MimeMessage | * @return MimeMessage | ||
*/ | */ | ||
− | + | byte[] getMessage(String folderd,long uid) throws ProcessingException; | |
</source> | </source> | ||
− | *Click on the MailService and in the editor pane delete all method descriptions and enter following: | + | |
+ | *Click on the MailService and in the editor pane delete all method descriptions and enter the following: | ||
<source lang="java"> | <source lang="java"> | ||
− | private ImapAdapter createImapAdapter(){ | + | 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{ | + | @Override |
− | + | 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{ | + | @Override |
− | + | 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{ | + | @Override |
− | + | 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){ | + | 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; | ||
} | } | ||
− | |||
− | |||
</source> | </source> | ||
− | ==Back to the | + | ==Back to the Package Explorer== |
===Some additional Code to speedup the process=== | ===Some additional Code to speedup the process=== | ||
− | + | ||
+ | Switch to the Package Explorer and add the following two classes to the package com.example.mail.server.core: | ||
+ | |||
<source lang="java"> | <source lang="java"> | ||
package com.example.mail.server.core; | package com.example.mail.server.core; | ||
− | + | ||
import java.io.Serializable; | import java.io.Serializable; | ||
import java.security.Principal; | import java.security.Principal; | ||
− | + | ||
public class ImapPrincipal implements Principal, Serializable{ | public class ImapPrincipal implements Principal, Serializable{ | ||
private static final long serialVersionUID=1L; | private static final long serialVersionUID=1L; | ||
− | + | ||
private String m_host; | private String m_host; | ||
private int m_port; | private int m_port; | ||
private String m_username; | private String m_username; | ||
private String m_password; | private String m_password; | ||
− | + | ||
public ImapPrincipal(String host, int port, String username, String password) { | public ImapPrincipal(String host, int port, String username, String password) { | ||
if(host == null) throw new IllegalArgumentException("host must not be null"); | if(host == null) throw new IllegalArgumentException("host must not be null"); | ||
Line 345: | Line 366: | ||
m_password=password; | m_password=password; | ||
} | } | ||
− | + | ||
+ | @Override | ||
public String getName(){ | public String getName(){ | ||
return m_username+"@"+m_host+":"+m_port; | return m_username+"@"+m_host+":"+m_port; | ||
} | } | ||
− | + | ||
public String getHost(){ | public String getHost(){ | ||
return m_host; | return m_host; | ||
} | } | ||
− | + | ||
public int getPort(){ | public int getPort(){ | ||
return m_port; | return m_port; | ||
} | } | ||
− | + | ||
public String getPassword(){ | public String getPassword(){ | ||
return m_password; | return m_password; | ||
} | } | ||
− | + | ||
public String getUsername(){ | public String getUsername(){ | ||
return m_username; | return m_username; | ||
} | } | ||
− | + | ||
@Override | @Override | ||
public int hashCode(){ | public int hashCode(){ | ||
return getName().hashCode(); | return getName().hashCode(); | ||
} | } | ||
− | + | ||
@Override | @Override | ||
public boolean equals(Object other){ | public boolean equals(Object other){ | ||
Line 383: | Line 405: | ||
} | } | ||
} | } | ||
− | + | ||
@Override | @Override | ||
public String toString(){ | public String toString(){ | ||
Line 390: | Line 412: | ||
} | } | ||
</source> | </source> | ||
+ | |||
+ | Create a new security filter for authentication with imap server: | ||
+ | |||
<source lang="java"> | <source lang="java"> | ||
package com.example.mail.server.core; | package com.example.mail.server.core; | ||
Line 413: | Line 438: | ||
import org.eclipse.scout.commons.logger.IScoutLogger; | import org.eclipse.scout.commons.logger.IScoutLogger; | ||
import org.eclipse.scout.commons.logger.ScoutLogManager; | import org.eclipse.scout.commons.logger.ScoutLogManager; | ||
− | import org.eclipse.scout. | + | import org.eclipse.scout.commons.security.SimplePrincipal; |
+ | import org.eclipse.scout.rt.server.commons.servletfilter.FilterConfigInjection; | ||
+ | import org.eclipse.scout.rt.server.commons.servletfilter.security.SecureHttpServletRequestWrapper; | ||
import org.eclipse.scout.rt.server.services.common.imap.ImapAdapter; | import org.eclipse.scout.rt.server.services.common.imap.ImapAdapter; | ||
− | |||
− | |||
− | public class ImapSecurityFilter implements Filter{ | + | public class ImapSecurityFilter implements Filter { |
− | private static IScoutLogger LOG=ScoutLogManager.getLogger(ImapSecurityFilter.class); | + | private static IScoutLogger LOG = ScoutLogManager.getLogger(ImapSecurityFilter.class); |
private FilterConfigInjection m_injection; | private FilterConfigInjection m_injection; | ||
Line 426: | Line 451: | ||
private int m_port; | private int m_port; | ||
− | public ImapSecurityFilter(){ | + | public ImapSecurityFilter() { |
− | m_lock=new Object(); | + | m_lock = new Object(); |
} | } | ||
− | public void init(FilterConfig config0) throws ServletException{ | + | @Override |
− | m_injection=new FilterConfigInjection(config0, getClass()); | + | public void init(FilterConfig config0) throws ServletException { |
− | FilterConfigInjection.FilterConfig config=m_injection.getAnyConfig(); | + | m_injection = new FilterConfigInjection(config0, getClass()); |
− | String host=config.getInitParameter("host"); | + | FilterConfigInjection.FilterConfig config = m_injection.getAnyConfig(); |
− | if(host==null) throw new ServletException("missing init-paramter 'host'"); | + | String host = config.getInitParameter("host"); |
− | m_host=host; | + | if (host == null) throw new ServletException("missing init-paramter 'host'"); |
− | String port=config.getInitParameter("port"); | + | m_host = host; |
− | if(port==null) port="143"; | + | String port = config.getInitParameter("port"); |
− | m_port=NumberUtility.parseInt(port); | + | if (port == null) port = "143"; |
+ | m_port = NumberUtility.parseInt(port); | ||
} | } | ||
− | public void destroy(){ | + | @Override |
− | m_host=null; | + | public void destroy() { |
− | m_port=143; | + | m_host = null; |
− | m_injection=null; | + | m_port = 143; |
+ | m_injection = null; | ||
} | } | ||
− | public void doFilter(ServletRequest in, ServletResponse out, final FilterChain chain) throws IOException, ServletException{ | + | @Override |
− | FilterConfigInjection.FilterConfig config=m_injection.getConfig(in); | + | @SuppressWarnings("unchecked") |
− | if(!config.isActive()){ | + | 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); | chain.doFilter(in, out); | ||
return; | return; | ||
} | } | ||
// | // | ||
− | final HttpServletRequest req=(HttpServletRequest)in; | + | final HttpServletRequest req = (HttpServletRequest) in; |
− | final HttpServletResponse res=(HttpServletResponse)out; | + | final HttpServletResponse res = (HttpServletResponse) out; |
// touch the session so it is effectively used | // touch the session so it is effectively used | ||
req.getSession(); | req.getSession(); | ||
// check subject on session | // check subject on session | ||
Subject subject; | Subject subject; | ||
− | synchronized(m_lock){ | + | synchronized (m_lock) { |
− | subject=findSubjectOnSession(req, res); | + | subject = findSubjectOnSession(req, res); |
− | if(subject == null || subject.getPrincipals().size() == 0){ | + | if (subject == null || subject.getPrincipals().size() == 0) { |
// try negotiate | // try negotiate | ||
− | Principal p=negotiate(req, res); | + | Principal p = negotiate(req, res); |
− | if(p==null){ | + | if (p == null) { |
res.sendError(HttpServletResponse.SC_UNAUTHORIZED); | res.sendError(HttpServletResponse.SC_UNAUTHORIZED); | ||
return; | return; | ||
} | } | ||
− | if(subject == null || subject.isReadOnly()){ | + | if (subject == null || subject.isReadOnly()) { |
− | subject=new Subject(); | + | subject = new Subject(); |
} | } | ||
subject.getPrincipals().add(p); | subject.getPrincipals().add(p); | ||
Line 478: | Line 507: | ||
} | } | ||
// run in subject | // run in subject | ||
− | if(Subject.getSubject(AccessController.getContext()) != null){ | + | if (Subject.getSubject(AccessController.getContext()) != null) { |
doFilterInternal(req, res, chain); | doFilterInternal(req, res, chain); | ||
} | } | ||
− | else{ | + | else { |
− | try{ | + | try { |
− | Subject.doAs(subject, new PrivilegedExceptionAction(){ | + | Subject.doAs(subject, new PrivilegedExceptionAction() { |
− | public Object run() throws Exception{ | + | @Override |
+ | public Object run() throws Exception { | ||
doFilterInternal(req, res, chain); | doFilterInternal(req, res, chain); | ||
return null; | return null; | ||
Line 490: | Line 520: | ||
}); | }); | ||
} | } | ||
− | catch(PrivilegedActionException e){ | + | catch (PrivilegedActionException e) { |
− | Throwable t=e.getCause(); | + | Throwable t = e.getCause(); |
− | if(t instanceof IOException){ | + | if (t instanceof IOException) { |
− | throw (IOException)t; | + | throw (IOException) t; |
} | } | ||
− | else if(t instanceof ServletException){ | + | else if (t instanceof ServletException) { |
− | throw (ServletException)t; | + | throw (ServletException) t; |
} | } | ||
− | else{ | + | else { |
throw new ServletException(t); | throw new ServletException(t); | ||
} | } | ||
Line 505: | Line 535: | ||
} | } | ||
− | private void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws IOException, ServletException{ | + | private void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws IOException, ServletException { |
− | if(req.getRemoteUser() == null && req.getUserPrincipal() == null){ | + | if (req.getRemoteUser() == null && req.getUserPrincipal() == null) { |
− | Principal principal=Subject.getSubject(AccessController.getContext()).getPrincipals().iterator().next(); | + | Principal principal = Subject.getSubject(AccessController.getContext()).getPrincipals().iterator().next(); |
chain.doFilter(new SecureHttpServletRequestWrapper(req, principal, "BASIC"), res); | chain.doFilter(new SecureHttpServletRequestWrapper(req, principal, "BASIC"), res); | ||
} | } | ||
− | else{ | + | else { |
chain.doFilter(req, res); | chain.doFilter(req, res); | ||
} | } | ||
} | } | ||
− | private Subject findSubjectOnSession(HttpServletRequest req, HttpServletResponse resp){ | + | private Subject findSubjectOnSession(HttpServletRequest req, HttpServletResponse resp) { |
// check if we are already authenticated | // check if we are already authenticated | ||
− | Subject subject=null; | + | Subject subject = null; |
− | if(subject == null){ | + | if (subject == null) { |
− | subject=Subject.getSubject(AccessController.getContext()); | + | subject = Subject.getSubject(AccessController.getContext()); |
} | } | ||
− | if(subject == null){ | + | if (subject == null) { |
− | Object o=null; | + | Object o = null; |
− | o=req.getSession().getAttribute(Subject.class.getName()); | + | o = req.getSession().getAttribute(Subject.class.getName()); |
− | if(o instanceof Subject){ | + | if (o instanceof Subject) { |
− | subject=(Subject)o; | + | subject = (Subject) o; |
} | } | ||
} | } | ||
− | if(subject == null){ | + | if (subject == null) { |
− | Principal principal=req.getUserPrincipal(); | + | Principal principal = req.getUserPrincipal(); |
− | if(principal == null || principal.getName() == null || principal.getName().trim().length() == 0){ | + | if (principal == null || principal.getName() == null || principal.getName().trim().length() == 0) { |
− | principal=null; | + | principal = null; |
− | String name=req.getRemoteUser(); | + | String name = req.getRemoteUser(); |
− | if(name != null && name.trim().length() > 0){ | + | if (name != null && name.trim().length() > 0) { |
− | principal=new SimplePrincipal(name); | + | principal = new SimplePrincipal(name); |
} | } | ||
} | } | ||
− | if(principal != null){ | + | if (principal != null) { |
− | subject=new Subject(); | + | subject = new Subject(); |
subject.getPrincipals().add(principal); | subject.getPrincipals().add(principal); | ||
subject.setReadOnly(); | subject.setReadOnly(); | ||
Line 555: | Line 585: | ||
* @return | * @return | ||
*/ | */ | ||
− | protected Principal negotiate(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException{ | + | protected Principal negotiate(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { |
− | String h=req.getHeader("Authorization"); | + | String h = req.getHeader("Authorization"); |
− | if(h != null && h.matches("Basic .*")){ | + | if (h != null && h.matches("Basic .*")) { |
− | String[] a=new String(Base64Utility.decode(h.substring(6))).split(":", 2); | + | String[] a = new String(Base64Utility.decode(h.substring(6))).split(":", 2); |
− | String user=a[0].toLowerCase(); | + | String user = a[0].toLowerCase(); |
− | String pass=a[1]; | + | String pass = a[1]; |
− | if(user != null && pass != null){ | + | if (user != null && pass != null) { |
− | ImapAdapter adapter=new ImapAdapter(m_host,m_port,user,pass); | + | ImapAdapter adapter = new ImapAdapter(m_host, m_port, user, pass); |
adapter.setUseSSL(true); | adapter.setUseSSL(true); | ||
− | try{ | + | try { |
adapter.connect(); | adapter.connect(); | ||
adapter.closeConnection(); | adapter.closeConnection(); | ||
− | return new ImapPrincipal(m_host,m_port,user,pass); | + | return new ImapPrincipal(m_host, m_port, user, pass); |
} | } | ||
− | catch(Throwable t){ | + | catch (Throwable t) { |
//nop, failed | //nop, failed | ||
} | } | ||
} | } | ||
} | } | ||
− | resp.setHeader("WWW-Authenticate", "Basic realm=\""+m_host+"\""); | + | resp.setHeader("WWW-Authenticate", "Basic realm=\"" + m_host + "\""); |
return null; | return null; | ||
} | } | ||
} | } | ||
</source> | </source> | ||
+ | |||
===Starting Server and Client=== | ===Starting Server and Client=== | ||
− | *In the Project com.example.mail.server.core navigate to products -> development. Open the config.ini. Delete the last | + | *In the Project com.example.mail.server.core navigate to products -> development. Open the config.ini. Delete the last section "Servlet Filter Runtime Configuration" and add the following configuration: |
<pre> | <pre> | ||
### IMAP security filter and service | ### IMAP security filter and service | ||
com.example.mail.server.core.ImapSecurityFilter#host=<your IMAP-Server-URL> | com.example.mail.server.core.ImapSecurityFilter#host=<your IMAP-Server-URL> | ||
− | com.example.mail.server.core.ImapSecurityFilter#port=<your IMAP-Server-Port> | + | #Optional Port, default with SSL is 993 |
+ | com.example.mail.server.core.ImapSecurityFilter#port=<your IMAP-Server-Port> | ||
</pre> | </pre> | ||
− | *Open the plugin.xml of the project com.example.mail.server.core, remove | + | *Open the plugin.xml of the project com.example.mail.server.core, remove all filters from the extension point "org.eclipse.scout.rt.server.commons.filters" and add following filter: |
<source lang="xml"> | <source lang="xml"> | ||
− | <filter | + | <filter |
− | + | aliases="/process" | |
− | + | class="com.example.mail.server.core.ImapSecurityFilter" | |
− | + | ranking="10"> | |
− | + | </filter> | |
− | + | ||
− | + | ||
− | + | ||
− | </filter> | + | |
</source> | </source> | ||
− | |||
− |
Latest revision as of 08:45, 27 May 2014
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
- Modify to the following:
- Project Name: com.example.mail
- Project Postfix: core
- We just want to make a swt application, so we uncheck com.example.mail.ui.swing.core and com.example.mail.ui.rap.core.
- Leave all other settings on the defaults.
- Click "Next" to go to the application template selection page
- Select "Outline Tree and Table Form" as application template.
- Click "Finish" and wait a moment for the generation of the projects.
Now you have 5 projects with some source to start with.
Make your project JavaMail aware
Due to problems with JAX-WS, JavaMail and JMS (see [1]) an endorsed directory needs to be provided for the JRE. Create the following file structure within the JRE you are using:
jre\lib\endorsed\javax.mail.jar jre\lib\endorsed\javax.jms.jar
The javax.mail.jar can be downloaded from java.net: [2].
The javax.jms.jar can be downloaded from oracle.com: [3]. The jar can be found within the zip file: /jms1.1/lib/javax.jms.jar
The two jar files should appear in the list of system libraries in the Eclipse JRE Definition (open Window->Preferences->Java->Installed JREs, Edit...). If not, add them manually or restart Eclipse.
Afterwards, in order to make use of its contained classes, you have to adapt the build path of your Eclipse projects. Switch to the Package Explorer and right click on your com.example.mail.client.core plugin, then click on Properties. Go to Java Build Path->Libraries Tab and expand your JRE System Library. Select "Access Rules" and press "Edit...". Add a new rule with Rule pattern "javax/**" and resolution "Accessible".
The same must now also be modified for the plugin com.example.mail.server.core.
Note: This change in build path is only required for development time to compile sources referring to classes in the 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
- Switch back to the Scout Explorer view
- In the "Scout Projects" folder navigate to the orange box "com.example.mail.client.core" and then Desktop -> Outlines -> StandardOutline -> Child Pages.
- Right click 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.
- Expand the Child Pages folder if not yet expanded.
- 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
- To show the IMAP folder names, overwrite the current code of the method getConfiguredTitle in the MailNodePage 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".
- 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(IMailService.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); }
- 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
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 - Select the just created Form in the Scout Explorer (expand "Forms" if necessary).
- In the "Scout Object Properties" View change "Display Hint" to "View" and "Display View Id" to "Center" and uncheck "Ask If Need Save".
- Right-Click on Variables below the MailForm -> "New Property Bean..."
- Name: "folderId" / Bean Type: String -> Finish
- Right-Click on the MainBox under the MailForm -> "New Form Field..."
- Search for "SplitBox" -> Next
- Name: empty
Class 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 "TableField" -> Next
- Name: empty
Class Name: MailTableField -> Finish - Right-Click on the SplitBoxField -> "New Form Field..."
- Search for "HtmlField". -> Next
- Name: empty
Type Name: MailField -> Finish - Expand the MailTableField folder and the Table folder.
- Right-Click on the Columns folder -> "New Column..."
- Choose "StringColumn" -> Next
- Name: empty / Class Name: FolderIdColumn -> Finish
- Repeat the last three steps with following:
- "LongColumn" / Name: empty / Class Name: UidColumn
- "StringColumn" / Name: "From" / Class Name: FromColumn
- "StringColumn" / Name: "Subject" / Class Name: SubjectColumn
- "DateColumn" / Name: "Received Date" / Class Name: ReceivedDateColumn
- "IntegerColumn" / Name: "Size" / Class Name: SizeColumn
- FolderIdColumn: In the "Scout Object Properties"-view uncheck "Displayable" and check "Primary Key".
- UidColumn: In the "Scout Object Properties"-view uncheck "Displayable".
- FromColumn, SubjectColumn: Change "Width" to 200.
- ReceivedDateColumn: Change "Width" to 100. In "Format" enter "EE dd.MM.yyyy HH:mm".
- 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 others you'll be able to resolve lateron):
if(rows.size()==1){ byte[] data=SERVICES.getService(IMailService.class).getMessage(getFolderId(),getUidColumn().getValue(rows.get(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(IMailService.class).getMessageHeaders(getFolderId()); getMailTableField().getTable().replaceRowsByMatrix(data);
Create a service
- Go to: com.example.mail.server.core -> "Services" -> MailService.
- Click on the IMailService and in the editor pane delete all method descriptions and enter following:
/** * @return fullName (ID), name, count, newCount, unreadCount */ Object[][] getChildFolders(String parentName) throws ProcessingException; /** * @return folderId (ID), uid (ID), from, subject, date, size */ Object[][] getMessageHeaders(String folderId) throws ProcessingException; /** * @return MimeMessage */ byte[] getMessage(String folderd,long uid) throws ProcessingException;
- Click on the MailService and in the editor pane delete all method descriptions and enter the 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 */ @Override 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 */ @Override 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(); } } @Override 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 Package Explorer
Some additional Code to speedup the process
Switch to the Package Explorer and add the 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; } @Override 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(); } }
Create a new security filter for authentication with imap server:
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.commons.security.SimplePrincipal; import org.eclipse.scout.rt.server.commons.servletfilter.FilterConfigInjection; import org.eclipse.scout.rt.server.commons.servletfilter.security.SecureHttpServletRequestWrapper; import org.eclipse.scout.rt.server.services.common.imap.ImapAdapter; 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(); } @Override 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); } @Override public void destroy() { m_host = null; m_port = 143; m_injection = null; } @Override @SuppressWarnings("unchecked") 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() { @Override 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 section "Servlet Filter Runtime Configuration" and add the following configuration:
### IMAP security filter and service com.example.mail.server.core.ImapSecurityFilter#host=<your IMAP-Server-URL> #Optional Port, default with SSL is 993 com.example.mail.server.core.ImapSecurityFilter#port=<your IMAP-Server-Port>
- Open the plugin.xml of the project com.example.mail.server.core, remove all filters from the extension point "org.eclipse.scout.rt.server.commons.filters" and add following filter:
<filter aliases="/process" class="com.example.mail.server.core.ImapSecurityFilter" ranking="10"> </filter>