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 }