View Javadoc

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