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.framework.service.ServiceManager;
25 import org.apache.avalon.framework.configuration.Configuration;
26 import org.apache.avalon.framework.configuration.ConfigurationException;
27 import org.apache.james.Constants;
28 import org.apache.james.services.UsersRepository;
29 import org.apache.james.services.UsersStore;
30 import org.apache.james.transport.mailets.listservcommands.ErrorCommand;
31 import org.apache.james.transport.mailets.listservcommands.IListServCommand;
32 import org.apache.james.util.XMLResources;
33 import org.apache.mailet.GenericMailet;
34 import org.apache.mailet.Mail;
35 import org.apache.mailet.MailAddress;
36
37 import javax.mail.MessagingException;
38 import java.io.File;
39 import java.lang.reflect.Field;
40 import java.util.ArrayList;
41 import java.util.HashMap;
42 import java.util.Iterator;
43 import java.util.List;
44 import java.util.Locale;
45 import java.util.Map;
46 import java.util.Properties;
47
48 /***
49 * CommandListservManager is the default implementation of {@link ICommandListservManager}.
50 * It loads all the configured {@link IListServCommand}s and delegates to them at runtime.
51 * <br />
52 *
53 * It isn't responsible for procesing messages sent to the main mailing list, but is responsible for
54 * individual commands sent by users, such as: info, subscribe, etc...
55 * <br />
56 *
57 * Requests sent to the CommandListservManager take the form of:
58 * <pre>
59 * <listName>-<commandName>@domain
60 * </pre>
61 *
62 * If the command isn't recognized an error will be sent using {@link #onError}.
63 * <br />
64 * <br />
65 *
66 * The configuration for this mailet sould be in the 'root' processor block.
67 * <pre>
68 * <mailet match="CommandListservMatcher=announce@localhost" class="CommandListservManager">
69 * <listName>announce</listName>
70 * <displayName>Announce mailing list</displayName>
71 * <listOwner>owner@localhost</listOwner>
72 * <repositoryName>list-announce</repositoryName>
73 * <listDomain>localhost</listDomain>
74 *
75 * <commandpackages>
76 * <commandpackage>org.apache.james.transport.mailets.listservcommands</commandpackage>
77 * </commandpackages>
78 *
79 * <commands>
80 * <command name="subscribe" class="Subscribe"/>
81 * <command name="subscribe-confirm" class="SubscribeConfirm"/>
82 * <command name="unsubscribe" class="UnSubscribe"/>
83 * <command name="unsubscribe-confirm" class="UnSubscribeConfirm"/>
84 * <command name="error" class="ErrorCommand"/>
85 * <command name="owner" class="Owner"/>
86 * <command name="info" class="Info"/>
87 * </commands>
88 * </mailet>
89 * </pre>
90 *
91 * <br />
92 * <br />
93 * Todo: refine the command matching so we can have more sophistciated commands such as:
94 * <pre>
95 * <listName>-<commandName>-<optCommandParam>@domain
96 * </pre>
97 *
98 * @version CVS $Revision: 430699 $ $Date: 2006-08-11 08:02:35 +0100 (Fri, 11 Aug 2006) $
99 * @since 2.2.0
100 */
101 public class CommandListservManager extends GenericMailet implements ICommandListservManager {
102
103 protected Map commandMap = new HashMap();
104 protected List commandPackages = new ArrayList();
105 protected UsersRepository usersRepository;
106 protected String listName;
107 protected String displayName;
108 protected String listOwner;
109 protected String listDomain;
110 protected XMLResources xmlResources;
111
112 /***
113 * Get the name of this list specified by the config param: 'listName'.
114 * <br />
115 * eg: <pre><listName>announce</listName></pre>
116 *
117 * @param displayFormat is whether you want a display version of this or not
118 * @return the official display name of this list
119 */
120 public String getListName(boolean displayFormat) {
121 return displayFormat ? displayName : listName;
122 }
123
124 /***
125 * Gets the owner of this list specified by the config param: 'listOwner'.
126 * <br />
127 * eg: <pre><listOwner>owner@localhost</listOwner></pre>
128 *
129 * @return this is an address like listOwner@localhost
130 */
131 public String getListOwner() {
132 return listOwner;
133 }
134
135 /***
136 * Get the domain of the list specified by the config param: 'listDomain'.
137 * <br />
138 * eg: <pre><listDomain>localhost</listDomain></pre>
139 *
140 * @return a string like localhost
141 */
142 public String getListDomain() {
143 return listDomain;
144 }
145
146 /***
147 * Get a specific command specified by the 'commands' configuration block.
148 * For instance:
149 * <pre>
150 * <commands>
151 * <command name="subscribe" class="Subscribe"/>
152 * <command name="subscribe-confirm" class="SubscribeConfirm"/>
153 * <command name="unsubscribe" class="UnSubscribe"/>
154 * <command name="unsubscribe-confirm" class="UnSubscribeConfirm"/>
155 * <command name="error" class="ErrorCommand"/>
156 * <command name="owner" class="Owner"/>
157 * <command name="info" class="Info"/>
158 * </commands>
159 * </pre>
160 * @param name case in-sensitive
161 * @return a {@link IListServCommand} if found, null otherwise
162 */
163 public IListServCommand getCommand(String name) {
164 return (IListServCommand) commandMap.get(name.toLowerCase(Locale.US));
165 }
166
167 /***
168 * Get all the available commands
169 * @return a map of {@link IListServCommand}
170 * @see #getCommand
171 */
172 public Map getCommands() {
173 return commandMap;
174 }
175
176 /***
177 * Get the current user repository for this list serv
178 * @return an instance of {@link UsersRepository} that is used for the member list of the list serv
179 */
180 public UsersRepository getUsersRepository() {
181 return usersRepository;
182 }
183
184 /***
185 * An error occurred, send some sort of message
186 * @param subject the subject of the message to send
187 * @param mail
188 * @param errorMessage
189 */
190 public void onError(Mail mail, String subject, String errorMessage) throws MessagingException {
191 ErrorCommand errorCommand = (ErrorCommand) getCommand("error");
192 errorCommand.onError(mail, subject, errorMessage);
193 }
194
195 /***
196 * @return the configuration file for the xml resources
197 */
198 public String getResourcesFile() {
199 return getInitParameter("resources");
200 }
201
202 /***
203 * Use this to get standard properties for future calls to {@link org.apache.james.util.XMLResources}
204 * @return properties with the "LIST_NAME" and the "DOMAIN_NAME" properties
205 */
206 public Properties getStandardProperties() {
207 Properties standardProperties = new Properties();
208 standardProperties.put("LIST_NAME", getListName(false));
209 standardProperties.put("DISPLAY_NAME", getListName(true));
210 standardProperties.put("DOMAIN_NAME", getListDomain());
211 return standardProperties;
212 }
213
214 /***
215 * Initializes an array of resources
216 * @param names such as 'header, footer' etc...
217 * @return an initialized array of XMLResources
218 * @throws ConfigurationException
219 */
220 public XMLResources[] initXMLResources(String[] names) throws ConfigurationException {
221 try {
222 File xmlFile = new File(getResourcesFile());
223
224 Properties props = getStandardProperties();
225 String listName = props.getProperty("LIST_NAME");
226
227 XMLResources[] xmlResources = new XMLResources[names.length];
228 for (int index = 0; index < names.length; index++) {
229 xmlResources[index] = new XMLResources();
230 xmlResources[index].init(xmlFile, names[index], listName, props);
231 }
232 return xmlResources;
233 } catch (Exception e) {
234 log(e.getMessage(), e);
235 throw new ConfigurationException("Can't initialize:", e);
236 }
237 }
238
239 public void init() throws MessagingException {
240
241 try {
242
243
244 Configuration configuration = (Configuration) getField(getMailetConfig(), "configuration");
245
246
247 listName = configuration.getChild("listName").getValue();
248 displayName = configuration.getChild("displayName").getValue();
249 listOwner = configuration.getChild("listOwner").getValue();
250 listDomain = configuration.getChild("listDomain").getValue();
251
252
253 initializeResources();
254
255
256 initUsersRepository();
257
258
259 loadCommandPackages(configuration);
260
261
262 loadCommands(configuration);
263
264
265 getMailetContext().setAttribute(ICommandListservManager.ID + listName, this);
266 } catch (Exception e) {
267 throw new MessagingException(e.getMessage(), e);
268 }
269 }
270
271 /***
272 * Based on the to address get a valid or command or null
273 * @param mailAddress
274 * @return IListServCommand or null
275 */
276 public IListServCommand getCommandTarget(MailAddress mailAddress) {
277 String commandName = getCommandName(mailAddress);
278 return getCommand(commandName);
279 }
280
281 /***
282 * <p>Called by the mailet container to allow the mailet to process a
283 * message.</p>
284 *
285 * <p>This method is declared abstract so subclasses must override it.</p>
286 *
287 * @param mail - the Mail object that contains the MimeMessage and
288 * routing information
289 * @throws MessagingException - if an exception occurs that interferes with the mailet's normal operation
290 * occurred
291 */
292 public void service(Mail mail) throws MessagingException {
293 if (mail.getRecipients().size() != 1) {
294 getMailetContext().bounce(mail, "You can only send one command at a time to this listserv manager.");
295 return;
296 }
297 MailAddress mailAddress = (MailAddress) mail.getRecipients().iterator().next();
298 IListServCommand command = getCommandTarget(mailAddress);
299
300 if (command == null) {
301
302 Properties props = getStandardProperties();
303 props.setProperty("COMMAND", getCommandName(mailAddress));
304 onError(mail, "unknown command", xmlResources.getString("command.not.understood", props));
305 } else {
306 command.onCommand(mail);
307 }
308
309
310
311
312
313 mail.setState(Mail.GHOST);
314 }
315
316 /***
317 * Get the name of the command
318 * @param mailAddress
319 * @return the name of the command
320 */
321 protected String getCommandName(MailAddress mailAddress) {
322 String user = mailAddress.getUser();
323 int index = user.indexOf('-', listName.length());
324 String commandName = user.substring(++index);
325 return commandName;
326 }
327
328 /***
329 * initialize the resources
330 * @throws Exception
331 */
332 protected void initializeResources() throws Exception {
333 xmlResources = initXMLResources(new String[]{"List Manager"})[0];
334 }
335
336 /***
337 * Fetch the repository of users
338 */
339 protected void initUsersRepository() {
340 ServiceManager compMgr = (ServiceManager) getMailetContext().getAttribute(Constants.AVALON_COMPONENT_MANAGER);
341 try {
342 UsersStore usersStore = (UsersStore) compMgr.lookup(UsersStore.ROLE);
343 String repName = getInitParameter("repositoryName");
344
345 usersRepository = usersStore.getRepository(repName);
346 } catch (Exception e) {
347 log("Failed to retrieve Store component:" + e.getMessage());
348 }
349 }
350
351 /***
352 * Load an initialize all of the available commands
353 * @param configuration
354 * @throws ConfigurationException
355 */
356 protected void loadCommands(Configuration configuration) throws Exception {
357 final Configuration commandConfigurations = configuration.getChild("commands");
358 final Configuration[] commandConfs = commandConfigurations.getChildren("command");
359 for (int index = 0; index < commandConfs.length; index++) {
360 Configuration commandConf = commandConfs[index];
361 String commandName = commandConf.getAttribute("name").toLowerCase();
362 String className = commandConf.getAttribute("class");
363 loadCommand(commandName, className, commandConf);
364 }
365 }
366
367 /***
368 * Loads and initializes a single command
369 *
370 * @param commandName
371 * @param className
372 * @param configuration
373 * @throws ConfigurationException
374 */
375 protected void loadCommand(String commandName,
376 String className,
377 Configuration configuration)
378 throws ConfigurationException, ClassNotFoundException, IllegalAccessException, InstantiationException {
379 ClassLoader theClassLoader = getClass().getClassLoader();
380 for (Iterator it = commandPackages.iterator(); it.hasNext();) {
381 String packageName = (String) it.next();
382
383 IListServCommand listServCommand = null;
384 try {
385 listServCommand = (IListServCommand) theClassLoader.loadClass(packageName + className).newInstance();
386 } catch (Exception e) {
387
388 continue;
389 }
390 listServCommand.init(this, configuration);
391 commandMap.put(commandName, listServCommand);
392 return;
393 }
394
395 throw new ConfigurationException("Unable to load listservcommand: " + commandName);
396 }
397
398 /***
399 * loads all of the packages for the commands
400 *
401 * @param configuration
402 * @throws ConfigurationException
403 */
404 protected void loadCommandPackages(Configuration configuration) throws ConfigurationException {
405 commandPackages.add("");
406 final Configuration packageConfiguration = configuration.getChild("commandpackages");
407 final Configuration[] pkgConfs = packageConfiguration.getChildren("commandpackage");
408 for (int index = 0; index < pkgConfs.length; index++) {
409 Configuration conf = pkgConfs[index];
410 String packageName = conf.getValue().trim();
411 if (!packageName.endsWith(".")) {
412 packageName += ".";
413 }
414 commandPackages.add(packageName);
415 }
416 }
417
418 /***
419 * Retrieves a data field, potentially defined by a super class.
420 * @return null if not found, the object otherwise
421 */
422 protected static Object getField(Object instance, String name) throws IllegalAccessException {
423 Class clazz = instance.getClass();
424 Field[] fields;
425 while (clazz != null) {
426 fields = clazz.getDeclaredFields();
427 for (int index = 0; index < fields.length; index++) {
428 Field field = fields[index];
429 if (field.getName().equals(name)) {
430 field.setAccessible(true);
431 return field.get(instance);
432 }
433 }
434 clazz = clazz.getSuperclass();
435 }
436
437 return null;
438 }
439 }