View Javadoc

1   /************************************************************************
2    * Copyright (c) 2000-2006 The Apache Software Foundation.             *
3    * All rights reserved.                                                *
4    * ------------------------------------------------------------------- *
5    * Licensed under the Apache License, Version 2.0 (the "License"); you *
6    * may not use this file except in compliance with the License. You    *
7    * may obtain a copy of the License at:                                *
8    *                                                                     *
9    *     http://www.apache.org/licenses/LICENSE-2.0                      *
10   *                                                                     *
11   * Unless required by applicable law or agreed to in writing, software *
12   * distributed under the License is distributed on an "AS IS" BASIS,   *
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or     *
14   * implied.  See the License for the specific language governing       *
15   * permissions and limitations under the License.                      *
16   ***********************************************************************/
17  
18  package org.apache.james.transport.mailets;
19  
20  import org.apache.avalon.cornerstone.services.datasources.DataSourceSelector;
21  import org.apache.avalon.excalibur.datasource.DataSourceComponent;
22  import org.apache.avalon.framework.service.ServiceManager;
23  import org.apache.james.Constants;
24  import org.apache.james.util.JDBCUtil;
25  import org.apache.mailet.MailAddress;
26  import org.apache.mailet.MailetException;
27  
28  import javax.mail.MessagingException;
29  
30  import java.sql.Connection;
31  import java.sql.DatabaseMetaData;
32  import java.sql.PreparedStatement;
33  import java.sql.ResultSet;
34  import java.sql.SQLException;
35  import java.util.Collection;
36  import java.util.Iterator;
37  import java.util.Map;
38  
39  /***
40   * Implements a Virtual User Table for JAMES.  Derived from the
41   * JDBCAlias mailet, but whereas that mailet uses a simple map from a
42   * source address to a destination address, this handles simple
43   * wildcard selection, verifies that a catchall address is for a domain
44   * in the Virtual User Table, and handles forwarding.
45   *
46   * JDBCVirtualUserTable does not provide any administation tools.
47   * You'll have to create the VirtualUserTable yourself.  The standard
48   * configuration is as follows:
49   *
50   * CREATE TABLE VirtualUserTable
51   * (
52   *  user varchar(64) NOT NULL default '',
53   *  domain varchar(255) NOT NULL default '',
54   *  target_address varchar(255) NOT NULL default '',
55   *  PRIMARY KEY (user,domain)
56   * );
57   *
58   * The user column specifies the username of the virtual recipient, the domain
59   * column the domain of the virtual recipient, and the target_address column
60   * the email address of the real recipient. The target_address column can contain
61   * just the username in the case of a local user, and multiple recipients can be
62   * specified in a list separated by commas, semi-colons or colons.
63   *
64   * The standard query used with VirtualUserTable is:
65   *
66   * select VirtualUserTable.target_address from VirtualUserTable, VirtualUserTable as VUTDomains
67   * where (VirtualUserTable.user like ? or VirtualUserTable.user like "\%")
68   * and (VirtualUserTable.domain like ?
69   * or (VirtualUserTable.domain like "\%" and VUTDomains.domain like ?))
70   * order by concat(VirtualUserTable.user,'@',VirtualUserTable.domain) desc limit 1
71   *
72   * For a given [user, domain, domain] used with the query, this will
73   * match as follows (in precedence order):
74   *
75   * 1. user@domain    - explicit mapping for user@domain
76   * 2. user@%         - catchall mapping for user anywhere
77   * 3. %@domain       - catchall mapping for anyone at domain
78   * 4. null           - no valid mapping
79   *
80   * You need to set the connection.  At the moment, there is a limit to
81   * what you can change regarding the SQL Query, because there isn't a
82   * means to specify where in the query to replace parameters. [TODO]
83   *
84   * <mailet match="All" class="JDBCVirtualUserTable">
85   *   <table>db://maildb/VirtualUserTable</table>
86   *   <sqlquery>sqlquery</sqlquery>
87   * </mailet>
88   */
89  public class JDBCVirtualUserTable extends AbstractVirtualUserTable
90  {
91      protected DataSourceComponent datasource;
92  
93      /***
94       * The query used by the mailet to get the alias mapping
95       */
96      protected String query = null;
97  
98      /***
99       * The JDBCUtil helper class
100      */
101     private final JDBCUtil theJDBCUtil = new JDBCUtil() {
102         protected void delegatedLog(String logString) {
103             log("JDBCVirtualUserTable: " + logString);
104         }
105     };
106 
107     /***
108      * Initialize the mailet
109      */
110     public void init() throws MessagingException {
111         if (getInitParameter("table") == null) {
112             throw new MailetException("Table location not specified for JDBCVirtualUserTable");
113         }
114 
115         String tableURL = getInitParameter("table");
116 
117         String datasourceName = tableURL.substring(5);
118         int pos = datasourceName.indexOf("/");
119         String tableName = datasourceName.substring(pos + 1);
120         datasourceName = datasourceName.substring(0, pos);
121         Connection conn = null;
122 
123         try {
124             ServiceManager componentManager = (ServiceManager)getMailetContext().getAttribute(Constants.AVALON_COMPONENT_MANAGER);
125             // Get the DataSourceSelector service
126             DataSourceSelector datasources = (DataSourceSelector)componentManager.lookup(DataSourceSelector.ROLE);
127             // Get the data-source required.
128             datasource = (DataSourceComponent)datasources.select(datasourceName);
129 
130             conn = datasource.getConnection();
131 
132             // Check if the required table exists. If not, complain.
133             DatabaseMetaData dbMetaData = conn.getMetaData();
134             // Need to ask in the case that identifiers are stored, ask the DatabaseMetaInfo.
135             // Try UPPER, lower, and MixedCase, to see if the table is there.
136             if (!(theJDBCUtil.tableExists(dbMetaData, tableName))) {
137                 StringBuffer exceptionBuffer =
138                                               new StringBuffer(128)
139                                               .append("Could not find table '")
140                                               .append(tableName)
141                                               .append("' in datasource '")
142                                               .append(datasourceName)
143                                               .append("'");
144                 throw new MailetException(exceptionBuffer.toString());
145             }
146 
147             //Build the query
148             query = getInitParameter("sqlquery","select VirtualUserTable.target_address from VirtualUserTable, VirtualUserTable as VUTDomains where (VirtualUserTable.user like ? or VirtualUserTable.user like '//%') and (VirtualUserTable.domain like ? or (VirtualUserTable.domain like '//%' and VUTDomains.domain like ?)) order by concat(VirtualUserTable.user,'@',VirtualUserTable.domain) desc limit 1");
149         } catch (MailetException me) {
150             throw me;
151         } catch (Exception e) {
152             throw new MessagingException("Error initializing JDBCVirtualUserTable", e);
153         } finally {
154             theJDBCUtil.closeJDBCConnection(conn);
155         }
156     }
157 
158     /***
159      * Map any virtual recipients to real recipients using the configured
160      * JDBC connection, table and query.
161      * 
162      * @param recipientsMap the mapping of virtual to real recipients
163      */
164     protected void mapRecipients(Map recipientsMap) throws MessagingException {
165         Connection conn = null;
166         PreparedStatement mappingStmt = null;
167 
168         Collection recipients = recipientsMap.keySet();
169 
170         try {
171             conn = datasource.getConnection();
172             mappingStmt = conn.prepareStatement(query);
173 
174             for (Iterator i = recipients.iterator(); i.hasNext(); ) {
175                 ResultSet mappingRS = null;
176                 try {
177                     MailAddress source = (MailAddress)i.next();
178                     mappingStmt.setString(1, source.getUser());
179                     mappingStmt.setString(2, source.getHost());
180                     mappingStmt.setString(3, source.getHost());
181                     mappingRS = mappingStmt.executeQuery();
182                     if (mappingRS.next()) {
183                         String targetString = mappingRS.getString(1);
184                         recipientsMap.put(source, targetString);
185                     }
186                 } finally {
187                     theJDBCUtil.closeJDBCResultSet(mappingRS);
188                 }
189             }
190         } catch (SQLException sqle) {
191             throw new MessagingException("Error accessing database", sqle);
192         } finally {
193             theJDBCUtil.closeJDBCStatement(mappingStmt);
194             theJDBCUtil.closeJDBCConnection(conn);
195         }
196     }
197 
198     public String getMailetInfo() {
199         return "JDBC Virtual User Table mailet";
200     }
201 }