/****************************************************************
* Licensed to the Apache Software Foundation (ASF) under one *
* or more contributor license agreements. See the NOTICE file *
* distributed with this work for additional information *
* regarding copyright ownership. The ASF licenses this file *
* to you under the Apache License, Version 2.0 (the *
* "License"); you may not use this file except in compliance *
* with the License. You may obtain a copy of the License at *
* *
* http://www.apache.org/licenses/LICENSE-2.0 *
* *
* Unless required by applicable law or agreed to in writing, *
* software distributed under the License is distributed on an *
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
* KIND, either express or implied. See the License for the *
* specific language governing permissions and limitations *
* under the License. *
****************************************************************/
package org.apache.james.transport.mailets;
import org.apache.avalon.framework.service.ServiceManager;
import org.apache.avalon.framework.configuration.Configuration;
import org.apache.avalon.framework.configuration.ConfigurationException;
import org.apache.james.Constants;
import org.apache.james.services.UsersRepository;
import org.apache.james.services.UsersStore;
import org.apache.james.transport.mailets.listservcommands.ErrorCommand;
import org.apache.james.transport.mailets.listservcommands.IListServCommand;
import org.apache.james.util.XMLResources;
import org.apache.mailet.GenericMailet;
import org.apache.mailet.Mail;
import org.apache.mailet.MailAddress;
import javax.mail.MessagingException;
import java.io.File;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
/**
* CommandListservManager is the default implementation of {@link ICommandListservManager}.
* It loads all the configured {@link IListServCommand}s and delegates to them at runtime.
* <br />
*
* It isn't responsible for procesing messages sent to the main mailing list, but is responsible for
* individual commands sent by users, such as: info, subscribe, etc...
* <br />
*
* Requests sent to the CommandListservManager take the form of:
* <pre>
* <listName>-<commandName>@domain
* </pre>
*
* If the command isn't recognized an error will be sent using {@link #onError}.
* <br />
* <br />
*
* The configuration for this mailet sould be in the 'root' processor block.
* <pre>
* <mailet match="CommandListservMatcher=announce@localhost" class="CommandListservManager">
* <listName>announce</listName>
* <displayName>Announce mailing list</displayName>
* <listOwner>owner@localhost</listOwner>
* <repositoryName>list-announce</repositoryName>
* <listDomain>localhost</listDomain>
*
* <commandpackages>
* <commandpackage>org.apache.james.transport.mailets.listservcommands</commandpackage>
* </commandpackages>
*
* <commands>
* <command name="subscribe" class="Subscribe"/>
* <command name="subscribe-confirm" class="SubscribeConfirm"/>
* <command name="unsubscribe" class="UnSubscribe"/>
* <command name="unsubscribe-confirm" class="UnSubscribeConfirm"/>
* <command name="error" class="ErrorCommand"/>
* <command name="owner" class="Owner"/>
* <command name="info" class="Info"/>
* </commands>
* </mailet>
* </pre>
*
* <br />
* <br />
* Todo: refine the command matching so we can have more sophistciated commands such as:
* <pre>
* <listName>-<commandName>-<optCommandParam>@domain
* </pre>
*
* @version CVS $Revision: 430699 $ $Date: 2006-08-11 09:02:35 +0200 (Fr, 11 Aug 2006) $
* @since 2.2.0
*/
public class CommandListservManager extends GenericMailet implements ICommandListservManager {
protected Map commandMap = new HashMap();
protected List commandPackages = new ArrayList();
protected UsersRepository usersRepository;
protected String listName;
protected String displayName;
protected String listOwner;
protected String listDomain;
protected XMLResources xmlResources;
/**
* Get the name of this list specified by the config param: 'listName'.
* <br />
* eg: <pre><listName>announce</listName></pre>
*
* @param displayFormat is whether you want a display version of this or not
* @return the official display name of this list
*/
public String getListName(boolean displayFormat) {
return displayFormat ? displayName : listName;
}
/**
* Gets the owner of this list specified by the config param: 'listOwner'.
* <br />
* eg: <pre><listOwner>owner@localhost</listOwner></pre>
*
* @return this is an address like listOwner@localhost
*/
public String getListOwner() {
return listOwner;
}
/**
* Get the domain of the list specified by the config param: 'listDomain'.
* <br />
* eg: <pre><listDomain>localhost</listDomain></pre>
*
* @return a string like localhost
*/
public String getListDomain() {
return listDomain;
}
/**
* Get a specific command specified by the 'commands' configuration block.
* For instance:
* <pre>
* <commands>
* <command name="subscribe" class="Subscribe"/>
* <command name="subscribe-confirm" class="SubscribeConfirm"/>
* <command name="unsubscribe" class="UnSubscribe"/>
* <command name="unsubscribe-confirm" class="UnSubscribeConfirm"/>
* <command name="error" class="ErrorCommand"/>
* <command name="owner" class="Owner"/>
* <command name="info" class="Info"/>
* </commands>
* </pre>
* @param name case in-sensitive
* @return a {@link IListServCommand} if found, null otherwise
*/
public IListServCommand getCommand(String name) {
return (IListServCommand) commandMap.get(name.toLowerCase(Locale.US));
}
/**
* Get all the available commands
* @return a map of {@link IListServCommand}
* @see #getCommand
*/
public Map getCommands() {
return commandMap;
}
/**
* Get the current user repository for this list serv
* @return an instance of {@link UsersRepository} that is used for the member list of the list serv
*/
public UsersRepository getUsersRepository() {
return usersRepository;
}
/**
* An error occurred, send some sort of message
* @param subject the subject of the message to send
* @param mail
* @param errorMessage
*/
public void onError(Mail mail, String subject, String errorMessage) throws MessagingException {
ErrorCommand errorCommand = (ErrorCommand) getCommand("error");
errorCommand.onError(mail, subject, errorMessage);
}
/**
* @return the configuration file for the xml resources
*/
public String getResourcesFile() {
return getInitParameter("resources");
}
/**
* Use this to get standard properties for future calls to {@link org.apache.james.util.XMLResources}
* @return properties with the "LIST_NAME" and the "DOMAIN_NAME" properties
*/
public Properties getStandardProperties() {
Properties standardProperties = new Properties();
standardProperties.put("LIST_NAME", getListName(false));
standardProperties.put("DISPLAY_NAME", getListName(true));
standardProperties.put("DOMAIN_NAME", getListDomain());
return standardProperties;
}
/**
* Initializes an array of resources
* @param names such as 'header, footer' etc...
* @return an initialized array of XMLResources
* @throws ConfigurationException
*/
public XMLResources[] initXMLResources(String[] names) throws ConfigurationException {
try {
File xmlFile = new File(getResourcesFile());
Properties props = getStandardProperties();
String listName = props.getProperty("LIST_NAME");
XMLResources[] xmlResources = new XMLResources[names.length];
for (int index = 0; index < names.length; index++) {
xmlResources[index] = new XMLResources();
xmlResources[index].init(xmlFile, names[index], listName, props);
}
return xmlResources;
} catch (Exception e) {
log(e.getMessage(), e);
throw new ConfigurationException("Can't initialize:", e);
}
}
public void init() throws MessagingException {
try {
//Well, i want a more complex configuration structure
//of my mailet, so i have to cheat... and cheat i will...
Configuration configuration = (Configuration) getField(getMailetConfig(), "configuration");
//get name
listName = configuration.getChild("listName").getValue();
displayName = configuration.getChild("displayName").getValue();
listOwner = configuration.getChild("listOwner").getValue();
listDomain = configuration.getChild("listDomain").getValue();
//initialize resources
initializeResources();
//get users store
initUsersRepository();
//get command packages
loadCommandPackages(configuration);
//load commands
loadCommands(configuration);
//register w/context
getMailetContext().setAttribute(ICommandListservManager.ID + listName, this);
} catch (Exception e) {
throw new MessagingException(e.getMessage(), e);
}
}
/**
* Based on the to address get a valid or command or null
* @param mailAddress
* @return IListServCommand or null
*/
public IListServCommand getCommandTarget(MailAddress mailAddress) {
String commandName = getCommandName(mailAddress);
return getCommand(commandName);
}
/**
* <p>Called by the mailet container to allow the mailet to process a
* message.</p>
*
* <p>This method is declared abstract so subclasses must override it.</p>
*
* @param mail - the Mail object that contains the MimeMessage and
* routing information
* @throws MessagingException - if an exception occurs that interferes with the mailet's normal operation
* occurred
*/
public void service(Mail mail) throws MessagingException {
if (mail.getRecipients().size() != 1) {
getMailetContext().bounce(mail, "You can only send one command at a time to this listserv manager.");
return;
}
MailAddress mailAddress = (MailAddress) mail.getRecipients().iterator().next();
IListServCommand command = getCommandTarget(mailAddress);
if (command == null) {
//don't recognize the command
Properties props = getStandardProperties();
props.setProperty("COMMAND", getCommandName(mailAddress));
onError(mail, "unknown command", xmlResources.getString("command.not.understood", props));
} else {
command.onCommand(mail);
}
// onError or onCommand would have done the job, so regardless
// of which get rid of this e-mail. This is something that we
// should review, and decide if there is any reason to allow a
// passthrough.
mail.setState(Mail.GHOST);
}
/**
* Get the name of the command
* @param mailAddress
* @return the name of the command
*/
protected String getCommandName(MailAddress mailAddress) {
String user = mailAddress.getUser();
int index = user.indexOf('-', listName.length());
String commandName = user.substring(++index);
return commandName;
}
/**
* initialize the resources
* @throws Exception
*/
protected void initializeResources() throws Exception {
xmlResources = initXMLResources(new String[]{"List Manager"})[0];
}
/**
* Fetch the repository of users
*/
protected void initUsersRepository() {
ServiceManager compMgr = (ServiceManager) getMailetContext().getAttribute(Constants.AVALON_COMPONENT_MANAGER);
try {
UsersStore usersStore = (UsersStore) compMgr.lookup(UsersStore.ROLE);
String repName = getInitParameter("repositoryName");
usersRepository = usersStore.getRepository(repName);
} catch (Exception e) {
log("Failed to retrieve Store component:" + e.getMessage());
}
}
/**
* Load an initialize all of the available commands
* @param configuration
* @throws ConfigurationException
*/
protected void loadCommands(Configuration configuration) throws Exception {
final Configuration commandConfigurations = configuration.getChild("commands");
final Configuration[] commandConfs = commandConfigurations.getChildren("command");
for (int index = 0; index < commandConfs.length; index++) {
Configuration commandConf = commandConfs[index];
String commandName = commandConf.getAttribute("name").toLowerCase();
String className = commandConf.getAttribute("class");
loadCommand(commandName, className, commandConf);
}
}
/**
* Loads and initializes a single command
*
* @param commandName
* @param className
* @param configuration
* @throws ConfigurationException
*/
protected void loadCommand(String commandName,
String className,
Configuration configuration)
throws ConfigurationException, ClassNotFoundException, IllegalAccessException, InstantiationException {
ClassLoader theClassLoader = getClass().getClassLoader();
for (Iterator it = commandPackages.iterator(); it.hasNext();) {
String packageName = (String) it.next();
IListServCommand listServCommand = null;
try {
listServCommand = (IListServCommand) theClassLoader.loadClass(packageName + className).newInstance();
} catch (Exception e) {
//ignore
continue;
}
listServCommand.init(this, configuration);
commandMap.put(commandName, listServCommand);
return;
}
throw new ConfigurationException("Unable to load listservcommand: " + commandName);
}
/**
* loads all of the packages for the commands
*
* @param configuration
* @throws ConfigurationException
*/
protected void loadCommandPackages(Configuration configuration) throws ConfigurationException {
commandPackages.add("");
final Configuration packageConfiguration = configuration.getChild("commandpackages");
final Configuration[] pkgConfs = packageConfiguration.getChildren("commandpackage");
for (int index = 0; index < pkgConfs.length; index++) {
Configuration conf = pkgConfs[index];
String packageName = conf.getValue().trim();
if (!packageName.endsWith(".")) {
packageName += ".";
}
commandPackages.add(packageName);
}
}
/**
* Retrieves a data field, potentially defined by a super class.
* @return null if not found, the object otherwise
*/
protected static Object getField(Object instance, String name) throws IllegalAccessException {
Class clazz = instance.getClass();
Field[] fields;
while (clazz != null) {
fields = clazz.getDeclaredFields();
for (int index = 0; index < fields.length; index++) {
Field field = fields[index];
if (field.getName().equals(name)) {
field.setAccessible(true);
return field.get(instance);
}
}
clazz = clazz.getSuperclass();
}
return null;
}
}
|