001: /****************************************************************
002: * Licensed to the Apache Software Foundation (ASF) under one *
003: * or more contributor license agreements. See the NOTICE file *
004: * distributed with this work for additional information *
005: * regarding copyright ownership. The ASF licenses this file *
006: * to you under the Apache License, Version 2.0 (the *
007: * "License"); you may not use this file except in compliance *
008: * with the License. You may obtain a copy of the License at *
009: * *
010: * http://www.apache.org/licenses/LICENSE-2.0 *
011: * *
012: * Unless required by applicable law or agreed to in writing, *
013: * software distributed under the License is distributed on an *
014: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
015: * KIND, either express or implied. See the License for the *
016: * specific language governing permissions and limitations *
017: * under the License. *
018: ****************************************************************/package org.apache.james.transport.mailets;
019:
020: import org.apache.avalon.framework.service.ServiceManager;
021: import org.apache.avalon.framework.configuration.Configuration;
022: import org.apache.avalon.framework.configuration.ConfigurationException;
023: import org.apache.james.Constants;
024: import org.apache.james.services.UsersRepository;
025: import org.apache.james.services.UsersStore;
026: import org.apache.james.transport.mailets.listservcommands.ErrorCommand;
027: import org.apache.james.transport.mailets.listservcommands.IListServCommand;
028: import org.apache.james.util.XMLResources;
029: import org.apache.mailet.GenericMailet;
030: import org.apache.mailet.Mail;
031: import org.apache.mailet.MailAddress;
032:
033: import javax.mail.MessagingException;
034: import java.io.File;
035: import java.lang.reflect.Field;
036: import java.util.ArrayList;
037: import java.util.HashMap;
038: import java.util.Iterator;
039: import java.util.List;
040: import java.util.Locale;
041: import java.util.Map;
042: import java.util.Properties;
043:
044: /**
045: * CommandListservManager is the default implementation of {@link ICommandListservManager}.
046: * It loads all the configured {@link IListServCommand}s and delegates to them at runtime.
047: * <br />
048: *
049: * It isn't responsible for procesing messages sent to the main mailing list, but is responsible for
050: * individual commands sent by users, such as: info, subscribe, etc...
051: * <br />
052: *
053: * Requests sent to the CommandListservManager take the form of:
054: * <pre>
055: * <listName>-<commandName>@domain
056: * </pre>
057: *
058: * If the command isn't recognized an error will be sent using {@link #onError}.
059: * <br />
060: * <br />
061: *
062: * The configuration for this mailet sould be in the 'root' processor block.
063: * <pre>
064: * <mailet match="CommandListservMatcher=announce@localhost" class="CommandListservManager">
065: * <listName>announce</listName>
066: * <displayName>Announce mailing list</displayName>
067: * <listOwner>owner@localhost</listOwner>
068: * <repositoryName>list-announce</repositoryName>
069: * <listDomain>localhost</listDomain>
070: *
071: * <commandpackages>
072: * <commandpackage>org.apache.james.transport.mailets.listservcommands</commandpackage>
073: * </commandpackages>
074: *
075: * <commands>
076: * <command name="subscribe" class="Subscribe"/>
077: * <command name="subscribe-confirm" class="SubscribeConfirm"/>
078: * <command name="unsubscribe" class="UnSubscribe"/>
079: * <command name="unsubscribe-confirm" class="UnSubscribeConfirm"/>
080: * <command name="error" class="ErrorCommand"/>
081: * <command name="owner" class="Owner"/>
082: * <command name="info" class="Info"/>
083: * </commands>
084: * </mailet>
085: * </pre>
086: *
087: * <br />
088: * <br />
089: * Todo: refine the command matching so we can have more sophistciated commands such as:
090: * <pre>
091: * <listName>-<commandName>-<optCommandParam>@domain
092: * </pre>
093: *
094: * @version CVS $Revision: 430699 $ $Date: 2006-08-11 09:02:35 +0200 (Fr, 11 Aug 2006) $
095: * @since 2.2.0
096: */
097: public class CommandListservManager extends GenericMailet implements
098: ICommandListservManager {
099:
100: protected Map commandMap = new HashMap();
101: protected List commandPackages = new ArrayList();
102: protected UsersRepository usersRepository;
103: protected String listName;
104: protected String displayName;
105: protected String listOwner;
106: protected String listDomain;
107: protected XMLResources xmlResources;
108:
109: /**
110: * Get the name of this list specified by the config param: 'listName'.
111: * <br />
112: * eg: <pre><listName>announce</listName></pre>
113: *
114: * @param displayFormat is whether you want a display version of this or not
115: * @return the official display name of this list
116: */
117: public String getListName(boolean displayFormat) {
118: return displayFormat ? displayName : listName;
119: }
120:
121: /**
122: * Gets the owner of this list specified by the config param: 'listOwner'.
123: * <br />
124: * eg: <pre><listOwner>owner@localhost</listOwner></pre>
125: *
126: * @return this is an address like listOwner@localhost
127: */
128: public String getListOwner() {
129: return listOwner;
130: }
131:
132: /**
133: * Get the domain of the list specified by the config param: 'listDomain'.
134: * <br />
135: * eg: <pre><listDomain>localhost</listDomain></pre>
136: *
137: * @return a string like localhost
138: */
139: public String getListDomain() {
140: return listDomain;
141: }
142:
143: /**
144: * Get a specific command specified by the 'commands' configuration block.
145: * For instance:
146: * <pre>
147: * <commands>
148: * <command name="subscribe" class="Subscribe"/>
149: * <command name="subscribe-confirm" class="SubscribeConfirm"/>
150: * <command name="unsubscribe" class="UnSubscribe"/>
151: * <command name="unsubscribe-confirm" class="UnSubscribeConfirm"/>
152: * <command name="error" class="ErrorCommand"/>
153: * <command name="owner" class="Owner"/>
154: * <command name="info" class="Info"/>
155: * </commands>
156: * </pre>
157: * @param name case in-sensitive
158: * @return a {@link IListServCommand} if found, null otherwise
159: */
160: public IListServCommand getCommand(String name) {
161: return (IListServCommand) commandMap.get(name
162: .toLowerCase(Locale.US));
163: }
164:
165: /**
166: * Get all the available commands
167: * @return a map of {@link IListServCommand}
168: * @see #getCommand
169: */
170: public Map getCommands() {
171: return commandMap;
172: }
173:
174: /**
175: * Get the current user repository for this list serv
176: * @return an instance of {@link UsersRepository} that is used for the member list of the list serv
177: */
178: public UsersRepository getUsersRepository() {
179: return usersRepository;
180: }
181:
182: /**
183: * An error occurred, send some sort of message
184: * @param subject the subject of the message to send
185: * @param mail
186: * @param errorMessage
187: */
188: public void onError(Mail mail, String subject, String errorMessage)
189: throws MessagingException {
190: ErrorCommand errorCommand = (ErrorCommand) getCommand("error");
191: errorCommand.onError(mail, subject, errorMessage);
192: }
193:
194: /**
195: * @return the configuration file for the xml resources
196: */
197: public String getResourcesFile() {
198: return getInitParameter("resources");
199: }
200:
201: /**
202: * Use this to get standard properties for future calls to {@link org.apache.james.util.XMLResources}
203: * @return properties with the "LIST_NAME" and the "DOMAIN_NAME" properties
204: */
205: public Properties getStandardProperties() {
206: Properties standardProperties = new Properties();
207: standardProperties.put("LIST_NAME", getListName(false));
208: standardProperties.put("DISPLAY_NAME", getListName(true));
209: standardProperties.put("DOMAIN_NAME", getListDomain());
210: return standardProperties;
211: }
212:
213: /**
214: * Initializes an array of resources
215: * @param names such as 'header, footer' etc...
216: * @return an initialized array of XMLResources
217: * @throws ConfigurationException
218: */
219: public XMLResources[] initXMLResources(String[] names)
220: 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],
231: listName, props);
232: }
233: return xmlResources;
234: } catch (Exception e) {
235: log(e.getMessage(), e);
236: throw new ConfigurationException("Can't initialize:", e);
237: }
238: }
239:
240: public void init() throws MessagingException {
241:
242: try {
243: //Well, i want a more complex configuration structure
244: //of my mailet, so i have to cheat... and cheat i will...
245: Configuration configuration = (Configuration) getField(
246: getMailetConfig(), "configuration");
247:
248: //get name
249: listName = configuration.getChild("listName").getValue();
250: displayName = configuration.getChild("displayName")
251: .getValue();
252: listOwner = configuration.getChild("listOwner").getValue();
253: listDomain = configuration.getChild("listDomain")
254: .getValue();
255:
256: //initialize resources
257: initializeResources();
258:
259: //get users store
260: initUsersRepository();
261:
262: //get command packages
263: loadCommandPackages(configuration);
264:
265: //load commands
266: loadCommands(configuration);
267:
268: //register w/context
269: getMailetContext().setAttribute(
270: ICommandListservManager.ID + listName, this );
271: } catch (Exception e) {
272: throw new MessagingException(e.getMessage(), e);
273: }
274: }
275:
276: /**
277: * Based on the to address get a valid or command or null
278: * @param mailAddress
279: * @return IListServCommand or null
280: */
281: public IListServCommand getCommandTarget(MailAddress mailAddress) {
282: String commandName = getCommandName(mailAddress);
283: return getCommand(commandName);
284: }
285:
286: /**
287: * <p>Called by the mailet container to allow the mailet to process a
288: * message.</p>
289: *
290: * <p>This method is declared abstract so subclasses must override it.</p>
291: *
292: * @param mail - the Mail object that contains the MimeMessage and
293: * routing information
294: * @throws MessagingException - if an exception occurs that interferes with the mailet's normal operation
295: * occurred
296: */
297: public void service(Mail mail) throws MessagingException {
298: if (mail.getRecipients().size() != 1) {
299: getMailetContext()
300: .bounce(mail,
301: "You can only send one command at a time to this listserv manager.");
302: return;
303: }
304: MailAddress mailAddress = (MailAddress) mail.getRecipients()
305: .iterator().next();
306: IListServCommand command = getCommandTarget(mailAddress);
307:
308: if (command == null) {
309: //don't recognize the command
310: Properties props = getStandardProperties();
311: props.setProperty("COMMAND", getCommandName(mailAddress));
312: onError(mail, "unknown command", xmlResources.getString(
313: "command.not.understood", props));
314: } else {
315: command.onCommand(mail);
316: }
317:
318: // onError or onCommand would have done the job, so regardless
319: // of which get rid of this e-mail. This is something that we
320: // should review, and decide if there is any reason to allow a
321: // passthrough.
322: mail.setState(Mail.GHOST);
323: }
324:
325: /**
326: * Get the name of the command
327: * @param mailAddress
328: * @return the name of the command
329: */
330: protected String getCommandName(MailAddress mailAddress) {
331: String user = mailAddress.getUser();
332: int index = user.indexOf('-', listName.length());
333: String commandName = user.substring(++index);
334: return commandName;
335: }
336:
337: /**
338: * initialize the resources
339: * @throws Exception
340: */
341: protected void initializeResources() throws Exception {
342: xmlResources = initXMLResources(new String[] { "List Manager" })[0];
343: }
344:
345: /**
346: * Fetch the repository of users
347: */
348: protected void initUsersRepository() {
349: ServiceManager compMgr = (ServiceManager) getMailetContext()
350: .getAttribute(Constants.AVALON_COMPONENT_MANAGER);
351: try {
352: UsersStore usersStore = (UsersStore) compMgr
353: .lookup(UsersStore.ROLE);
354: String repName = getInitParameter("repositoryName");
355:
356: usersRepository = usersStore.getRepository(repName);
357: } catch (Exception e) {
358: log("Failed to retrieve Store component:" + e.getMessage());
359: }
360: }
361:
362: /**
363: * Load an initialize all of the available commands
364: * @param configuration
365: * @throws ConfigurationException
366: */
367: protected void loadCommands(Configuration configuration)
368: throws Exception {
369: final Configuration commandConfigurations = configuration
370: .getChild("commands");
371: final Configuration[] commandConfs = commandConfigurations
372: .getChildren("command");
373: for (int index = 0; index < commandConfs.length; index++) {
374: Configuration commandConf = commandConfs[index];
375: String commandName = commandConf.getAttribute("name")
376: .toLowerCase();
377: String className = commandConf.getAttribute("class");
378: loadCommand(commandName, className, commandConf);
379: }
380: }
381:
382: /**
383: * Loads and initializes a single command
384: *
385: * @param commandName
386: * @param className
387: * @param configuration
388: * @throws ConfigurationException
389: */
390: protected void loadCommand(String commandName, String className,
391: Configuration configuration) throws ConfigurationException,
392: ClassNotFoundException, IllegalAccessException,
393: InstantiationException {
394: ClassLoader theClassLoader = getClass().getClassLoader();
395: for (Iterator it = commandPackages.iterator(); it.hasNext();) {
396: String packageName = (String) it.next();
397:
398: IListServCommand listServCommand = null;
399: try {
400: listServCommand = (IListServCommand) theClassLoader
401: .loadClass(packageName + className)
402: .newInstance();
403: } catch (Exception e) {
404: //ignore
405: continue;
406: }
407: listServCommand.init(this , configuration);
408: commandMap.put(commandName, listServCommand);
409: return;
410: }
411:
412: throw new ConfigurationException(
413: "Unable to load listservcommand: " + commandName);
414: }
415:
416: /**
417: * loads all of the packages for the commands
418: *
419: * @param configuration
420: * @throws ConfigurationException
421: */
422: protected void loadCommandPackages(Configuration configuration)
423: throws ConfigurationException {
424: commandPackages.add("");
425: final Configuration packageConfiguration = configuration
426: .getChild("commandpackages");
427: final Configuration[] pkgConfs = packageConfiguration
428: .getChildren("commandpackage");
429: for (int index = 0; index < pkgConfs.length; index++) {
430: Configuration conf = pkgConfs[index];
431: String packageName = conf.getValue().trim();
432: if (!packageName.endsWith(".")) {
433: packageName += ".";
434: }
435: commandPackages.add(packageName);
436: }
437: }
438:
439: /**
440: * Retrieves a data field, potentially defined by a super class.
441: * @return null if not found, the object otherwise
442: */
443: protected static Object getField(Object instance, String name)
444: throws IllegalAccessException {
445: Class clazz = instance.getClass();
446: Field[] fields;
447: while (clazz != null) {
448: fields = clazz.getDeclaredFields();
449: for (int index = 0; index < fields.length; index++) {
450: Field field = fields[index];
451: if (field.getName().equals(name)) {
452: field.setAccessible(true);
453: return field.get(instance);
454: }
455: }
456: clazz = clazz.getSuperclass();
457: }
458:
459: return null;
460: }
461: }
|