/* * 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 javax.mail; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.PrintStream; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.net.InetAddress; import java.net.URL; import java.util.ArrayList; import java.util.Collection; import java.util.Enumeration; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.StringTokenizer; import java.util.WeakHashMap; import org.apache.geronimo.osgi.locator.ProviderLocator; import org.apache.geronimo.mail.MailProviderRegistry; /** * OK, so we have a final class in the API with a heck of a lot of implementation required... * let's try and figure out what it is meant to do. *
* It is supposed to collect together properties and defaults so that they can be * shared by multiple applications on a desktop; with process isolation and no * real concept of shared memory, this seems challenging. These properties and * defaults rely on system properties, making management in a app server harder, * and on resources loaded from "mail.jar" which may lead to skew between * differnet independent implementations of this API. * * @version $Rev$ $Date$ */ public final class Session { private static final Class[] PARAM_TYPES = {Session.class, URLName.class}; private static final WeakHashMap addressMapsByClassLoader = new WeakHashMap(); private static Session DEFAULT_SESSION; private Map passwordAuthentications = new HashMap(); private final Properties properties; private final Authenticator authenticator; private boolean debug; private PrintStream debugOut = System.out; private static final WeakHashMap providersByClassLoader = new WeakHashMap(); /** * No public constrcutor allowed. */ private Session(Properties properties, Authenticator authenticator) { this.properties = properties; this.authenticator = authenticator; debug = Boolean.valueOf(properties.getProperty("mail.debug")).booleanValue(); } /** * Create a new session initialized with the supplied properties which uses the supplied authenticator. * Clients should ensure the properties listed in Appendix A of the JavaMail specification are * set as the defaults are unlikey to work in most scenarios; particular attention should be given * to: *mail.transport.protocol
property.
*
* @return a Transport
* @throws NoSuchProviderException
*/
public Transport getTransport() throws NoSuchProviderException {
String protocol = properties.getProperty("mail.transport.protocol");
if (protocol == null) {
throw new NoSuchProviderException("mail.transport.protocol property is not set");
}
return getTransport(protocol);
}
/**
* Return a Transport for the specified protocol.
*
* @param protocol the protocol to use
* @return a Transport
* @throws NoSuchProviderException
*/
public Transport getTransport(String protocol) throws NoSuchProviderException {
Provider provider = getProvider(protocol);
return getTransport(provider);
}
/**
* Return a transport for the protocol specified in the URL.
*
* @param name the URL whose scheme specifies the protocol
* @return a Transport
* @throws NoSuchProviderException
*/
public Transport getTransport(URLName name) throws NoSuchProviderException {
return (Transport) getService(getProvider(name.getProtocol()), name);
}
/**
* Return a transport for the protocol associated with the type of this address.
*
* @param address the address we are trying to deliver to
* @return a Transport
* @throws NoSuchProviderException
*/
public Transport getTransport(Address address) throws NoSuchProviderException {
String type = address.getType();
// load the address map from the resource files.
Map addressMap = getAddressMap();
String protocolName = (String)addressMap.get(type);
if (protocolName == null) {
throw new NoSuchProviderException("No provider for address type " + type);
}
return getTransport(protocolName);
}
/**
* Return the Transport specified by a Provider
*
* @param provider the defining Provider
* @return a Transport
* @throws NoSuchProviderException
*/
public Transport getTransport(Provider provider) throws NoSuchProviderException {
return (Transport) getService(provider, null);
}
/**
* Set the password authentication associated with a URL.
*
* @param name the url
* @param authenticator the authenticator
*/
public void setPasswordAuthentication(URLName name, PasswordAuthentication authenticator) {
if (authenticator == null) {
passwordAuthentications.remove(name);
} else {
passwordAuthentications.put(name, authenticator);
}
}
/**
* Get the password authentication associated with a URL
*
* @param name the URL
* @return any authenticator for that url, or null if none
*/
public PasswordAuthentication getPasswordAuthentication(URLName name) {
return (PasswordAuthentication) passwordAuthentications.get(name);
}
/**
* Call back to the application supplied authenticator to get the needed username add password.
*
* @param host the host we are trying to connect to, may be null
* @param port the port on that host
* @param protocol the protocol trying to be used
* @param prompt a String to show as part of the prompt, may be null
* @param defaultUserName the default username, may be null
* @return the authentication information collected by the authenticator; may be null
*/
public PasswordAuthentication requestPasswordAuthentication(InetAddress host, int port, String protocol, String prompt, String defaultUserName) {
if (authenticator == null) {
return null;
}
return authenticator.authenticate(host, port, protocol, prompt, defaultUserName);
}
/**
* Return the properties object for this Session; this is a live collection.
*
* @return the properties for the Session
*/
public Properties getProperties() {
return properties;
}
/**
* Return the specified property.
*
* @param property the property to get
* @return its value, or null if not present
*/
public String getProperty(String property) {
return getProperties().getProperty(property);
}
/**
* Add a provider to the Session managed provider list.
*
* @param provider The new provider to add.
*/
public synchronized void addProvider(Provider provider) {
ProviderInfo info = getProviderInfo();
info.addProvider(provider);
}
/**
* Add a mapping between an address type and a protocol used
* to process that address type.
*
* @param addressType
* The address type identifier.
* @param protocol The protocol name mapping.
*/
public void setProtocolForAddress(String addressType, String protocol) {
Map addressMap = getAddressMap();
// no protocol specified is a removal
if (protocol == null) {
addressMap.remove(addressType);
}
else {
addressMap.put(addressType, protocol);
}
}
private Service getService(Provider provider, URLName name) throws NoSuchProviderException {
try {
if (name == null) {
name = new URLName(provider.getProtocol(), null, -1, null, null, null);
}
ClassLoader cl = getClassLoader();
Class clazz = null;
try {
clazz = ProviderLocator.loadClass(provider.getClassName(), this.getClass(), cl);
} catch (ClassNotFoundException e) {
throw (NoSuchProviderException) new NoSuchProviderException("Unable to load class for provider: " + provider).initCause(e);
}
Constructor ctr = clazz.getConstructor(PARAM_TYPES);
return(Service) ctr.newInstance(new Object[]{this, name});
} catch (NoSuchMethodException e) {
throw (NoSuchProviderException) new NoSuchProviderException("Provider class does not have a constructor(Session, URLName): " + provider).initCause(e);
} catch (InstantiationException e) {
throw (NoSuchProviderException) new NoSuchProviderException("Unable to instantiate provider class: " + provider).initCause(e);
} catch (IllegalAccessException e) {
throw (NoSuchProviderException) new NoSuchProviderException("Unable to instantiate provider class: " + provider).initCause(e);
} catch (InvocationTargetException e) {
throw (NoSuchProviderException) new NoSuchProviderException("Exception from constructor of provider class: " + provider).initCause(e.getCause());
}
}
private ProviderInfo getProviderInfo() {
ClassLoader cl = getClassLoader();
synchronized (providersByClassLoader) {
ProviderInfo info = (ProviderInfo) providersByClassLoader.get(cl);
if (info == null) {
info = loadProviders(cl);
}
return info;
}
}
private Map getAddressMap() {
ClassLoader cl = getClassLoader();
Map addressMap = (Map)addressMapsByClassLoader.get(cl);
if (addressMap == null) {
addressMap = loadAddressMap(cl);
}
return addressMap;
}
/**
* Resolve a class loader used to resolve context resources. The
* class loader used is either a current thread context class
* loader (if set), the class loader used to load an authenticator
* we've been initialized with, or the class loader used to load
* this class instance (which may be a subclass of Session).
*
* @return The class loader used to load resources.
*/
private ClassLoader getClassLoader() {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
if (cl == null) {
if (authenticator != null) {
cl = authenticator.getClass().getClassLoader();
}
else {
cl = this.getClass().getClassLoader();
}
}
return cl;
}
private ProviderInfo loadProviders(ClassLoader cl) {
// we create a merged map from reading all of the potential address map entries. The locations
// searched are:
// 1. java.home/lib/javamail.address.map
// 2. META-INF/javamail.address.map
// 3. META-INF/javamail.default.address.map
//
ProviderInfo info = new ProviderInfo();
// NOTE: Unlike the addressMap, we process these in the defined order. The loading routine
// will not overwrite entries if they already exist in the map.
try {
File file = new File(System.getProperty("java.home"), "lib/javamail.providers");
InputStream is = new FileInputStream(file);
try {
loadProviders(info, is);
if (debug) {
writeDebug("Loaded lib/javamail.providers from " + file.toString());
}
} finally{
is.close();
}
} catch (SecurityException e) {
// ignore
} catch (IOException e) {
// ignore
}
try {
Enumeration e = cl.getResources("META-INF/javamail.providers");
while (e.hasMoreElements()) {
URL url = (URL) e.nextElement();
if (debug) {
writeDebug("Loading META-INF/javamail.providers from " + url.toString());
}
InputStream is = url.openStream();
try {
loadProviders(info, is);
} finally{
is.close();
}
}
} catch (SecurityException e) {
// ignore
} catch (IOException e) {
// ignore
}
// we could be running in an OSGi environment, so there might be some globally defined
// providers
try {
Collection