blob: 27c40b4599bcb553e7b544761ed7f34598f49d14 [file] [log] [blame]
/* $Id$ */
/**
* 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.manifoldcf.crawler.connectors.email;
import org.apache.commons.lang.StringUtils;
import org.apache.manifoldcf.agents.interfaces.RepositoryDocument;
import org.apache.manifoldcf.agents.interfaces.ServiceInterruption;
import org.apache.manifoldcf.core.interfaces.*;
import org.apache.manifoldcf.crawler.interfaces.*;
import org.apache.manifoldcf.crawler.system.Logging;
import java.io.*;
import java.util.*;
import javax.mail.*;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.search.*;
/**
* This interface describes an instance of a connection between a repository and ManifoldCF's
* standard "pull" ingestion agent.
* <p/>
* Each instance of this interface is used in only one thread at a time. Connection Pooling
* on these kinds of objects is performed by the factory which instantiates repository connectors
* from symbolic names and config parameters, and is pooled by these parameters. That is, a pooled connector
* handle is used only if all the connection parameters for the handle match.
* <p/>
* Implementers of this interface should provide a default constructor which has this signature:
* <p/>
* xxx();
* <p/>
* Connectors are either configured or not. If configured, they will persist in a pool, and be
* reused multiple times. Certain methods of a connector may be called before the connector is
* configured. This includes basically all methods that permit inspection of the connector's
* capabilities. The complete list is:
* <p/>
* <p/>
* The purpose of the repository connector is to allow documents to be fetched from the repository.
* <p/>
* Each repository connector describes a set of documents that are known only to that connector.
* It therefore establishes a space of document identifiers. Each connector will only ever be
* asked to deal with identifiers that have in some way originated from the connector.
* <p/>
* Documents are fetched in three stages. First, the getDocuments() method is called in the connector
* implementation. This returns a set of document identifiers. The document identifiers are used to
* obtain the current document version strings in the second stage, using the getDocumentVersions() method.
* The last stage is processDocuments(), which queues up any additional documents needed, and also ingests.
* This method will not be called if the document version seems to indicate that no document change took
* place.
*/
public class EmailConnector extends org.apache.manifoldcf.crawler.connectors.BaseRepositoryConnector {
protected final static long SESSION_EXPIRATION_MILLISECONDS = 300000L;
// Local variables.
protected long sessionExpiration = -1L;
// Parameters for establishing a session
protected String server = null;
protected String portString = null;
protected String username = null;
protected String password = null;
protected String protocol = null;
protected Properties properties = null;
protected String urlTemplate = null;
// Local session handle
protected EmailSession session = null;
private static Map<String,String> providerMap;
static
{
providerMap = new HashMap<String,String>();
providerMap.put(EmailConfig.PROTOCOL_POP3, EmailConfig.PROTOCOL_POP3_PROVIDER);
providerMap.put(EmailConfig.PROTOCOL_POP3S, EmailConfig.PROTOCOL_POP3S_PROVIDER);
providerMap.put(EmailConfig.PROTOCOL_IMAP, EmailConfig.PROTOCOL_IMAP_PROVIDER);
providerMap.put(EmailConfig.PROTOCOL_IMAPS, EmailConfig.PROTOCOL_IMAPS_PROVIDER);
}
//////////////////////////////////Start of Basic Connector Methods/////////////////////////
/**
* Connect.
*
* @param configParameters is the set of configuration parameters, which
* in this case describe the root directory.
*/
@Override
public void connect(ConfigParams configParameters) {
super.connect(configParameters);
this.server = configParameters.getParameter(EmailConfig.SERVER_PARAM);
this.portString = configParameters.getParameter(EmailConfig.PORT_PARAM);
this.protocol = configParameters.getParameter(EmailConfig.PROTOCOL_PARAM);
this.username = configParameters.getParameter(EmailConfig.USERNAME_PARAM);
this.password = configParameters.getObfuscatedParameter(EmailConfig.PASSWORD_PARAM);
this.urlTemplate = configParameters.getParameter(EmailConfig.URL_PARAM);
this.properties = new Properties();
int i = 0;
while (i < configParameters.getChildCount()) //In post property set is added as a configuration node
{
ConfigNode cn = configParameters.getChild(i++);
if (cn.getType().equals(EmailConfig.NODE_PROPERTIES)) {
String findParameterName = cn.getAttributeValue(EmailConfig.ATTRIBUTE_NAME);
String findParameterValue = cn.getAttributeValue(EmailConfig.ATTRIBUTE_VALUE);
this.properties.setProperty(findParameterName, findParameterValue);
}
}
}
/**
* Close the connection. Call this before discarding this instance of the
* repository connector.
*/
@Override
public void disconnect()
throws ManifoldCFException {
this.urlTemplate = null;
this.server = null;
this.portString = null;
this.protocol = null;
this.username = null;
this.password = null;
this.properties = null;
finalizeConnection();
super.disconnect();
}
/**
* This method is periodically called for all connectors that are connected but not
* in active use.
*/
@Override
public void poll() throws ManifoldCFException {
if (session != null)
{
if (System.currentTimeMillis() >= sessionExpiration)
finalizeConnection();
}
}
/**
* Test the connection. Returns a string describing the connection integrity.
*
* @return the connection's status as a displayable string.
*/
@Override
public String check()
throws ManifoldCFException {
try {
checkConnection();
return super.check();
} catch (ServiceInterruption e) {
return "Connection temporarily failed: " + e.getMessage();
} catch (ManifoldCFException e) {
return "Connection failed: " + e.getMessage();
}
}
protected void checkConnection() throws ManifoldCFException, ServiceInterruption {
// Force a re-connection
finalizeConnection();
getSession();
try {
CheckConnectionThread cct = new CheckConnectionThread(session);
cct.start();
cct.finishUp();
} catch (InterruptedException e) {
throw new ManifoldCFException(e.getMessage(),ManifoldCFException.INTERRUPTED);
} catch (MessagingException e) {
handleMessagingException(e,"checking the connection");
}
}
///////////////////////////////End of Basic Connector Methods////////////////////////////////////////
//////////////////////////////Start of Repository Connector Method///////////////////////////////////
@Override
public int getConnectorModel() {
return MODEL_ADD; //Change is not applicable in context of email
}
/**
* Return the list of activities that this connector supports (i.e. writes into the log).
*
* @return the list.
*/
@Override
public String[] getActivitiesList() {
return new String[]{EmailConfig.ACTIVITY_FETCH};
}
/**
* Get the bin name strings for a document identifier. The bin name describes the queue to which the
* document will be assigned for throttling purposes. Throttling controls the rate at which items in a
* given queue are fetched; it does not say anything about the overall fetch rate, which may operate on
* multiple queues or bins.
* For example, if you implement a web crawler, a good choice of bin name would be the server name, since
* that is likely to correspond to a real resource that will need real throttle protection.
*
* @param documentIdentifier is the document identifier.
* @return the set of bin names. If an empty array is returned, it is equivalent to there being no request
* rate throttling available for this identifier.
*/
@Override
public String[] getBinNames(String documentIdentifier) {
return new String[]{server};
}
/**
* Get the maximum number of documents to amalgamate together into one batch, for this connector.
*
* @return the maximum number. 0 indicates "unlimited".
*/
@Override
public int getMaxDocumentRequest() {
return 10;
}
/**
* Queue "seed" documents. Seed documents are the starting places for crawling activity. Documents
* are seeded when this method calls appropriate methods in the passed in ISeedingActivity object.
* <p/>
* This method can choose to find repository changes that happen only during the specified time interval.
* The seeds recorded by this method will be viewed by the framework based on what the
* getConnectorModel() method returns.
* <p/>
* It is not a big problem if the connector chooses to create more seeds than are
* strictly necessary; it is merely a question of overall work required.
* <p/>
* The times passed to this method may be interpreted for greatest efficiency. The time ranges
* any given job uses with this connector will not overlap, but will proceed starting at 0 and going
* to the "current time", each time the job is run. For continuous crawling jobs, this method will
* be called once, when the job starts, and at various periodic intervals as the job executes.
* <p/>
* When a job's specification is changed, the framework automatically resets the seeding start time to 0. The
* seeding start time may also be set to 0 on each job run, depending on the connector model returned by
* getConnectorModel().
* <p/>
* Note that it is always ok to send MORE documents rather than less to this method.
*
* @param activities is the interface this method should use to perform whatever framework actions are desired.
* @param spec is a document specification (that comes from the job).
* @param startTime is the beginning of the time range to consider, inclusive.
* @param endTime is the end of the time range to consider, exclusive.
* @param jobMode is an integer describing how the job is being run, whether continuous or once-only.
*/
@Override
public void addSeedDocuments(ISeedingActivity activities,
DocumentSpecification spec, long startTime, long endTime, int jobMode)
throws ManifoldCFException, ServiceInterruption {
getSession();
int i = 0;
Map<String,String> findMap = new HashMap<String,String>();
List<String> folderNames = new ArrayList<String>();
while (i < spec.getChildCount()) {
SpecificationNode sn = spec.getChild(i++);
if (sn.getType().equals(EmailConfig.NODE_FOLDER)) {
folderNames.add(sn.getAttributeValue(EmailConfig.ATTRIBUTE_NAME));
} else if (sn.getType().equals(EmailConfig.NODE_FILTER)) {
String findParameterName, findParameterValue;
findParameterName = sn.getAttributeValue(EmailConfig.ATTRIBUTE_NAME);
findParameterValue = sn.getAttributeValue(EmailConfig.ATTRIBUTE_VALUE);
findMap.put(findParameterName, findParameterValue);
}
}
for (String folderName : folderNames)
{
try {
OpenFolderThread oft = new OpenFolderThread(session, folderName);
oft.start();
Folder folder = oft.finishUp();
try
{
Message[] messages = findMessages(folder, startTime, endTime, findMap);
for (Message message : messages) {
String emailID = ((MimeMessage) message).getMessageID();
activities.addSeedDocument(createDocumentIdentifier(folderName,emailID));
}
}
finally
{
CloseFolderThread cft = new CloseFolderThread(session, folder);
cft.start();
cft.finishUp();
}
} catch (InterruptedException e) {
throw new ManifoldCFException(e.getMessage(),ManifoldCFException.INTERRUPTED);
} catch (MessagingException e) {
handleMessagingException(e, "finding emails");
}
}
}
/*
This method will return the list of messages which matches the given criteria
*/
private Message[] findMessages(Folder folder, long startTime, long endTime, Map<String,String> findMap)
throws MessagingException, InterruptedException {
String findParameterName;
String findParameterValue;
SearchTerm searchTerm = null;
Iterator<Map.Entry<String,String>> it = findMap.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<String,String> pair = it.next();
findParameterName = pair.getKey().toLowerCase();
findParameterValue = pair.getValue();
if (Logging.connectors.isDebugEnabled())
Logging.connectors.debug("Email: Finding emails where '" + findParameterName +
"' = '" + findParameterValue + "'");
SearchTerm searchClause = null;
if (findParameterName.equals(EmailConfig.EMAIL_SUBJECT)) {
searchClause = new SubjectTerm(findParameterValue);
} else if (findParameterName.equals(EmailConfig.EMAIL_FROM)) {
searchClause = new FromStringTerm(findParameterValue);
} else if (findParameterName.equals(EmailConfig.EMAIL_TO)) {
searchClause = new RecipientStringTerm(Message.RecipientType.TO, findParameterValue);
} else if (findParameterName.equals(EmailConfig.EMAIL_BODY)) {
searchClause = new BodyTerm(findParameterValue);
}
if (searchClause != null)
{
if (searchTerm == null)
searchTerm = searchClause;
else
searchTerm = new AndTerm(searchTerm, searchClause);
}
else
{
Logging.connectors.warn("Email: Unknown filter parameter name: '"+findParameterName+"'");
}
}
Message[] result;
if (searchTerm == null)
{
GetMessagesThread gmt = new GetMessagesThread(session, folder);
gmt.start();
result = gmt.finishUp();
}
else
{
SearchMessagesThread smt = new SearchMessagesThread(session, folder, searchTerm);
smt.start();
result = smt.finishUp();
}
return result;
}
protected void getSession()
throws ManifoldCFException, ServiceInterruption {
if (session == null) {
// Check that all the required parameters are there.
if (urlTemplate == null)
throw new ManifoldCFException("Missing url parameter");
if (server == null)
throw new ManifoldCFException("Missing server parameter");
if (properties == null)
throw new ManifoldCFException("Missing server properties");
if (protocol == null)
throw new ManifoldCFException("Missing protocol parameter");
// Create a session.
int port;
if (portString != null && portString.length() > 0)
{
try
{
port = Integer.parseInt(portString);
}
catch (NumberFormatException e)
{
throw new ManifoldCFException("Port number has bad format: "+e.getMessage(),e);
}
}
else
port = -1;
try {
ConnectThread connectThread = new ConnectThread(server, port, username, password,
providerMap.get(protocol), properties);
connectThread.start();
session = connectThread.finishUp();
} catch (InterruptedException e) {
throw new ManifoldCFException(e.getMessage(),ManifoldCFException.INTERRUPTED);
} catch (MessagingException e) {
handleMessagingException(e, "connecting");
}
}
sessionExpiration = System.currentTimeMillis() + SESSION_EXPIRATION_MILLISECONDS;
}
protected void finalizeConnection() {
if (session != null) {
try {
CloseSessionThread closeSessionThread = new CloseSessionThread(session);
closeSessionThread.start();
closeSessionThread.finishUp();
} catch (InterruptedException e) {
} catch (MessagingException e) {
Logging.connectors.warn("Error while closing connection to server: " + e.getMessage(),e);
} finally {
session = null;
}
}
}
/**
* Get document versions given an array of document identifiers.
* This method is called for EVERY document that is considered. It is therefore important to perform
* as little work as possible here.
* The connector will be connected before this method can be called.
*
* @param documentIdentifiers is the array of local document identifiers, as understood by this connector.
* @param oldVersions is the corresponding array of version strings that have been saved for the document identifiers.
* A null value indicates that this is a first-time fetch, while an empty string indicates that the previous document
* had an empty version string.
* @param activities is the interface this method should use to perform whatever framework actions are desired.
* @param spec is the current document specification for the current job. If there is a dependency on this
* specification, then the version string should include the pertinent data, so that reingestion will occur
* when the specification changes. This is primarily useful for metadata.
* @param jobMode is an integer describing how the job is being run, whether continuous or once-only.
* @param usesDefaultAuthority will be true only if the authority in use for these documents is the default one.
* @return the corresponding version strings, with null in the places where the document no longer exists.
* Empty version strings indicate that there is no versioning ability for the corresponding document, and the document
* will always be processed.
*/
@Override
public String[] getDocumentVersions(String[] documentIdentifiers, String[] oldVersions, IVersionActivity activities,
DocumentSpecification spec, int jobMode, boolean usesDefaultAuthority)
throws ManifoldCFException, ServiceInterruption {
String[] result = new String[documentIdentifiers.length];
for (int i = 0; i < documentIdentifiers.length; i++)
{
result[i] = "_" + urlTemplate; // NOT empty; we need to make ManifoldCF understand that this is a document that never will change.
}
return result;
}
/**
* Process a set of documents.
* This is the method that should cause each document to be fetched, processed, and the results either added
* to the queue of documents for the current job, and/or entered into the incremental ingestion manager.
* The document specification allows this class to filter what is done based on the job.
* The connector will be connected before this method can be called.
*
* @param documentIdentifiers is the set of document identifiers to process.
* @param versions is the corresponding document versions to process, as returned by getDocumentVersions() above.
* The implementation may choose to ignore this parameter and always process the current version.
* @param activities is the interface this method should use to queue up new document references
* and ingest documents.
* @param spec is the document specification.
* @param scanOnly is an array corresponding to the document identifiers. It is set to true to indicate when the processing
* should only find other references, and should not actually call the ingestion methods.
* @param jobMode is an integer describing how the job is being run, whether continuous or once-only.
*/
@Override
public void processDocuments(String[] documentIdentifiers, String[] versions, IProcessActivity activities,
DocumentSpecification spec, boolean[] scanOnly, int jobMode)
throws ManifoldCFException, ServiceInterruption {
getSession();
int i = 0;
List<String> requiredMetadata = new ArrayList<String>();
while (i < spec.getChildCount()) {
SpecificationNode sn = spec.getChild(i++);
if (sn.getType().equals(EmailConfig.NODE_METADATA)) {
String metadataAttribute = sn.getAttributeValue(EmailConfig.ATTRIBUTE_NAME);
requiredMetadata.add(metadataAttribute);
}
}
// Keep a cached set of open folders
Map<String,Folder> openFolders = new HashMap<String,Folder>();
try {
i = 0;
while (i < documentIdentifiers.length) {
String compositeID = documentIdentifiers[i];
String version = versions[i];
String folderName = extractFolderNameFromDocumentIdentifier(compositeID);
String id = extractEmailIDFromDocumentIdentifier(compositeID);
try {
Folder folder = openFolders.get(folderName);
if (folder == null)
{
OpenFolderThread oft = new OpenFolderThread(session, folderName);
oft.start();
folder = oft.finishUp();
openFolders.put(folderName,folder);
}
long startTime = System.currentTimeMillis();
InputStream is = null;
if (Logging.connectors.isDebugEnabled())
Logging.connectors.debug("Email: Processing document identifier '"
+ compositeID + "'");
SearchTerm messageIDTerm = new MessageIDTerm(id);
SearchMessagesThread smt = new SearchMessagesThread(session, folder, messageIDTerm);
smt.start();
Message[] message = smt.finishUp();
for (Message msg : message) {
RepositoryDocument rd = new RepositoryDocument();
Date setDate = msg.getSentDate();
rd.setFileName(msg.getFileName());
is = msg.getInputStream();
rd.setBinary(is, msg.getSize());
String subject = StringUtils.EMPTY;
for (String metadata : requiredMetadata) {
if (metadata.toLowerCase().equals(EmailConfig.EMAIL_TO)) {
Address[] to = msg.getRecipients(Message.RecipientType.TO);
String[] toStr = new String[to.length];
int j = 0;
for (Address address : to) {
toStr[j] = address.toString();
}
rd.addField(EmailConfig.EMAIL_TO, toStr);
} else if (metadata.toLowerCase().equals(EmailConfig.EMAIL_FROM)) {
Address[] from = msg.getFrom();
String[] fromStr = new String[from.length];
int j = 0;
for (Address address : from) {
fromStr[j] = address.toString();
}
rd.addField(EmailConfig.EMAIL_TO, fromStr);
} else if (metadata.toLowerCase().equals(EmailConfig.EMAIL_SUBJECT)) {
subject = msg.getSubject();
rd.addField(EmailConfig.EMAIL_SUBJECT, subject);
} else if (metadata.toLowerCase().equals(EmailConfig.EMAIL_BODY)) {
Multipart mp = (Multipart) msg.getContent();
for (int j = 0, n = mp.getCount(); i < n; i++) {
Part part = mp.getBodyPart(i);
String disposition = part.getDisposition();
if ((disposition == null)) {
MimeBodyPart mbp = (MimeBodyPart) part;
if (mbp.isMimeType(EmailConfig.MIMETYPE_TEXT_PLAIN)) {
rd.addField(EmailConfig.EMAIL_BODY, mbp.getContent().toString());
} else if (mbp.isMimeType(EmailConfig.MIMETYPE_HTML)) {
rd.addField(EmailConfig.EMAIL_BODY, mbp.getContent().toString()); //handle html accordingly. Returns content with html tags
}
}
}
} else if (metadata.toLowerCase().equals(EmailConfig.EMAIL_DATE)) {
Date sentDate = msg.getSentDate();
rd.addField(EmailConfig.EMAIL_DATE, sentDate.toString());
} else if (metadata.toLowerCase().equals(EmailConfig.EMAIL_ATTACHMENT_ENCODING)) {
Multipart mp = (Multipart) msg.getContent();
if (mp != null) {
String[] encoding = new String[mp.getCount()];
for (int k = 0, n = mp.getCount(); i < n; i++) {
Part part = mp.getBodyPart(i);
String disposition = part.getDisposition();
if ((disposition != null) &&
((disposition.equals(Part.ATTACHMENT) ||
(disposition.equals(Part.INLINE))))) {
encoding[k] = part.getFileName().split("\\?")[1];
}
}
rd.addField(EmailConfig.ENCODING_FIELD, encoding);
}
} else if (metadata.toLowerCase().equals(EmailConfig.EMAIL_ATTACHMENT_MIMETYPE)) {
Multipart mp = (Multipart) msg.getContent();
String[] MIMEType = new String[mp.getCount()];
for (int k = 0, n = mp.getCount(); i < n; i++) {
Part part = mp.getBodyPart(i);
String disposition = part.getDisposition();
if ((disposition != null) &&
((disposition.equals(Part.ATTACHMENT) ||
(disposition.equals(Part.INLINE))))) {
MIMEType[k] = part.getContentType();
}
}
rd.addField(EmailConfig.MIMETYPE_FIELD, MIMEType);
}
}
String documentURI = makeDocumentURI(urlTemplate, folderName, id);
activities.ingestDocument(id, version, documentURI, rd);
}
} catch (InterruptedException e) {
throw new ManifoldCFException(e.getMessage(), ManifoldCFException.INTERRUPTED);
} catch (MessagingException e) {
handleMessagingException(e, "processing email");
} catch (IOException e) {
handleIOException(e, "processing email");
throw new ManifoldCFException(e.getMessage(), e);
}
i++;
}
}
finally
{
for (Folder f : openFolders.values())
{
try
{
CloseFolderThread cft = new CloseFolderThread(session, f);
cft.start();
cft.finishUp();
}
catch (InterruptedException e)
{
throw new ManifoldCFException(e.getMessage(),ManifoldCFException.INTERRUPTED);
}
catch (MessagingException e)
{
handleMessagingException(e, "closing folders");
}
}
}
}
//////////////////////////////End of Repository Connector Methods///////////////////////////////////
///////////////////////////////////////Start of Configuration UI/////////////////////////////////////
/**
* Output the configuration header section.
* This method is called in the head section of the connector's configuration page. Its purpose is to
* add the required tabs to the list, and to output any javascript methods that might be needed by
* the configuration editing HTML.
* The connector does not need to be connected for this method to be called.
*
* @param threadContext is the local thread context.
* @param out is the output to which any HTML should be sent.
* @param locale is the desired locale.
* @param parameters are the configuration parameters, as they currently exist, for this connection being configured.
* @param tabsArray is an array of tab names. Add to this array any tab names that are specific to the connector.
*/
@Override
public void outputConfigurationHeader(IThreadContext threadContext, IHTTPOutput out,
Locale locale, ConfigParams parameters, List<String> tabsArray)
throws ManifoldCFException, IOException {
tabsArray.add(Messages.getString(locale, "EmailConnector.Server"));
tabsArray.add(Messages.getString(locale, "EmailConnector.URL"));
// Map the parameters
Map<String, Object> paramMap = new HashMap<String, Object>();
// Fill in the parameters from each tab
fillInServerConfigurationMap(paramMap, out, parameters);
fillInURLConfigurationMap(paramMap, out, parameters);
// Output the Javascript - only one Velocity template for all tabs
Messages.outputResourceWithVelocity(out, locale, "ConfigurationHeader.js", paramMap);
}
@Override
public void outputConfigurationBody(IThreadContext threadContext, IHTTPOutput out,
Locale locale, ConfigParams parameters, String tabName)
throws ManifoldCFException, IOException {
// Output the Server tab
Map<String, Object> paramMap = new HashMap<String, Object>();
// Set the tab name
paramMap.put("TabName", tabName);
// Fill in the parameters
fillInServerConfigurationMap(paramMap, out, parameters);
fillInURLConfigurationMap(paramMap, out, parameters);
Messages.outputResourceWithVelocity(out, locale, "Configuration_Server.html", paramMap);
Messages.outputResourceWithVelocity(out, locale, "Configuration_URL.html", paramMap);
}
private static void fillInServerConfigurationMap(Map<String, Object> paramMap, IPasswordMapperActivity mapper, ConfigParams parameters) {
int i = 0;
String username = parameters.getParameter(EmailConfig.USERNAME_PARAM);
String password = parameters.getObfuscatedParameter(EmailConfig.PASSWORD_PARAM);
String protocol = parameters.getParameter(EmailConfig.PROTOCOL_PARAM);
String server = parameters.getParameter(EmailConfig.SERVER_PARAM);
String port = parameters.getParameter(EmailConfig.PORT_PARAM);
List<Map<String, String>> list = new ArrayList<Map<String, String>>();
while (i < parameters.getChildCount()) //In post property set is added as a configuration node
{
ConfigNode cn = parameters.getChild(i++);
if (cn.getType().equals(EmailConfig.NODE_PROPERTIES)) {
String findParameterName = cn.getAttributeValue(EmailConfig.ATTRIBUTE_NAME);
String findParameterValue = cn.getAttributeValue(EmailConfig.ATTRIBUTE_VALUE);
Map<String, String> row = new HashMap<String, String>();
row.put("name", findParameterName);
row.put("value", findParameterValue);
list.add(row);
}
}
if (username == null)
username = StringUtils.EMPTY;
if (password == null)
password = StringUtils.EMPTY;
else
password = mapper.mapPasswordToKey(password);
if (protocol == null)
protocol = EmailConfig.PROTOCOL_DEFAULT_VALUE;
if (server == null)
server = StringUtils.EMPTY;
if (port == null)
port = EmailConfig.PORT_DEFAULT_VALUE;
paramMap.put("USERNAME", username);
paramMap.put("PASSWORD", password);
paramMap.put("PROTOCOL", protocol);
paramMap.put("SERVER", server);
paramMap.put("PORT", port);
paramMap.put("PROPERTIES", list);
}
private static void fillInURLConfigurationMap(Map<String, Object> paramMap, IPasswordMapperActivity mapper, ConfigParams parameters) {
String urlTemplate = parameters.getParameter(EmailConfig.URL_PARAM);
if (urlTemplate == null)
urlTemplate = "http://sampleserver/$(FOLDERNAME)?id=$(MESSAGEID)";
paramMap.put("URL", urlTemplate);
}
/**
* Process a configuration post.
* This method is called at the start of the connector's configuration page, whenever there is a possibility
* that form data for a connection has been posted. Its purpose is to gather form information and modify
* the configuration parameters accordingly.
* The name of the posted form is always "editconnection".
* The connector does not need to be connected for this method to be called.
*
* @param threadContext is the local thread context.
* @param variableContext is the set of variables available from the post, including binary file post information.
* @param parameters are the configuration parameters, as they currently exist, for this connection being configured.
* @return null if all is well, or a string error message if there is an error that should prevent saving of the
* connection (and cause a redirection to an error page).
*/
@Override
public String processConfigurationPost(IThreadContext threadContext, IPostParameters variableContext,
ConfigParams parameters) throws ManifoldCFException {
String urlTemplate = variableContext.getParameter("url");
if (urlTemplate != null)
parameters.setParameter(EmailConfig.URL_PARAM, urlTemplate);
String userName = variableContext.getParameter("username");
if (userName != null)
parameters.setParameter(EmailConfig.USERNAME_PARAM, userName);
String password = variableContext.getParameter("password");
if (password != null)
parameters.setObfuscatedParameter(EmailConfig.PASSWORD_PARAM, variableContext.mapKeyToPassword(password));
String protocol = variableContext.getParameter("protocol");
if (protocol != null)
parameters.setParameter(EmailConfig.PROTOCOL_PARAM, protocol);
String server = variableContext.getParameter("server");
if (server != null)
parameters.setParameter(EmailConfig.SERVER_PARAM, server);
String port = variableContext.getParameter("port");
if (port != null)
parameters.setParameter(EmailConfig.PORT_PARAM, port);
// Remove old find parameter document specification information
removeNodes(parameters, EmailConfig.NODE_PROPERTIES);
// Parse the number of records that were posted
String findCountString = variableContext.getParameter("findcount");
if (findCountString != null) {
int findCount = Integer.parseInt(findCountString);
// Loop throught them and add new server properties
int i = 0;
while (i < findCount) {
String suffix = "_" + Integer.toString(i++);
// Only add the name/value if the item was not deleted.
String findParameterOp = variableContext.getParameter("findop" + suffix);
if (findParameterOp == null || !findParameterOp.equals("Delete")) {
String findParameterName = variableContext.getParameter("findname" + suffix);
String findParameterValue = variableContext.getParameter("findvalue" + suffix);
addFindParameterNode(parameters, findParameterName, findParameterValue);
}
}
}
// Now, look for a global "Add" operation
String operation = variableContext.getParameter("findop");
if (operation != null && operation.equals("Add")) {
// Pick up the global parameter name and value
String findParameterName = variableContext.getParameter("findname");
String findParameterValue = variableContext.getParameter("findvalue");
addFindParameterNode(parameters, findParameterName, findParameterValue);
}
return null;
}
/**
* View configuration. This method is called in the body section of the
* connector's view configuration page. Its purpose is to present the
* connection information to the user. The coder can presume that the HTML that
* is output from this configuration will be within appropriate <html> and
* <body> tags.
*
* @param threadContext is the local thread context.
* @param out is the output to which any HTML should be sent.
* @param parameters are the configuration parameters, as they currently exist, for
* this connection being configured.
*/
@Override
public void viewConfiguration(IThreadContext threadContext, IHTTPOutput out,
Locale locale, ConfigParams parameters) throws ManifoldCFException, IOException {
Map<String, Object> paramMap = new HashMap<String, Object>();
// Fill in map from each tab
fillInServerConfigurationMap(paramMap, out, parameters);
fillInURLConfigurationMap(paramMap, out, parameters);
Messages.outputResourceWithVelocity(out, locale, "ConfigurationView.html", paramMap);
}
/////////////////////////////////End of configuration UI////////////////////////////////////////////////////
/////////////////////////////////Start of Specification UI//////////////////////////////////////////////////
/**
* Output the specification header section.
* This method is called in the head section of a job page which has selected a repository connection of the
* current type. Its purpose is to add the required tabs to the list, and to output any javascript methods
* that might be needed by the job editing HTML.
* The connector will be connected before this method can be called.
*
* @param out is the output to which any HTML should be sent.
* @param locale is the desired locale.
* @param ds is the current document specification for this job.
* @param tabsArray is an array of tab names. Add to this array any tab names that are specific to the connector.
*/
@Override
public void outputSpecificationHeader(IHTTPOutput out, Locale locale,
DocumentSpecification ds, List<String> tabsArray)
throws ManifoldCFException, IOException {
// Add the tabs
tabsArray.add(Messages.getString(locale, "EmailConnector.Metadata"));
tabsArray.add(Messages.getString(locale, "EmailConnector.Filter"));
Messages.outputResourceWithVelocity(out, locale, "SpecificationHeader.js", null);
}
/**
* Output the specification body section.
* This method is called in the body section of a job page which has selected a repository connection of the
* current type. Its purpose is to present the required form elements for editing.
* The coder can presume that the HTML that is output from this configuration will be within appropriate
* <html>, <body>, and <form> tags. The name of the form is always "editjob".
* The connector will be connected before this method can be called.
*
* @param out is the output to which any HTML should be sent.
* @param locale is the desired locale.
* @param ds is the current document specification for this job.
* @param tabName is the current tab name.
*/
@Override
public void outputSpecificationBody(IHTTPOutput out, Locale locale,
DocumentSpecification ds, String tabName)
throws ManifoldCFException, IOException {
outputFilterTab(out, locale, ds, tabName);
outputMetadataTab(out, locale, ds, tabName);
}
/**
* Take care of "Metadata" tab.
*/
protected void outputMetadataTab(IHTTPOutput out, Locale locale,
DocumentSpecification ds, String tabName)
throws ManifoldCFException, IOException {
Map<String, Object> paramMap = new HashMap<String, Object>();
paramMap.put("TabName", tabName);
fillInMetadataTab(paramMap, ds);
fillInMetadataAttributes(paramMap);
Messages.outputResourceWithVelocity(out, locale, "Specification_Metadata.html", paramMap);
}
/**
* Fill in Velocity context for Metadata tab.
*/
protected static void fillInMetadataTab(Map<String, Object> paramMap,
DocumentSpecification ds) {
Set<String> metadataSelections = new HashSet<String>();
int i = 0;
while (i < ds.getChildCount()) {
SpecificationNode sn = ds.getChild(i++);
if (sn.getType().equals(EmailConfig.NODE_METADATA)) {
String metadataName = sn.getAttributeValue(EmailConfig.ATTRIBUTE_NAME);
metadataSelections.add(metadataName);
}
}
paramMap.put("metadataselections", metadataSelections);
}
/**
* Fill in Velocity context with data to permit attribute selection.
*/
protected void fillInMetadataAttributes(Map<String, Object> paramMap) {
String[] matchNames = EmailConfig.BASIC_METADATA;
paramMap.put("metadataattributes", matchNames);
}
protected void outputFilterTab(IHTTPOutput out, Locale locale,
DocumentSpecification ds, String tabName)
throws ManifoldCFException, IOException {
Map<String, Object> paramMap = new HashMap<String, Object>();
paramMap.put("TabName", tabName);
fillInFilterTab(paramMap, ds);
if (tabName.equals(Messages.getString(locale, "EmailConnector.Filter")))
fillInSearchableAttributes(paramMap);
Messages.outputResourceWithVelocity(out, locale, "Specification_Filter.html", paramMap);
}
private void fillInSearchableAttributes(Map<String, Object> paramMap)
{
String[] attributes = EmailConfig.BASIC_SEARCHABLE_ATTRIBUTES;
paramMap.put("SEARCHABLEATTRIBUTES", attributes);
try
{
String[] folderNames = getFolderNames();
paramMap.put("FOLDERNAMES", folderNames);
paramMap.put("EXCEPTION", "");
}
catch (ManifoldCFException e)
{
paramMap.put("EXCEPTION", e.getMessage());
}
catch (ServiceInterruption e)
{
paramMap.put("EXCEPTION", e.getMessage());
}
}
protected static void fillInFilterTab(Map<String, Object> paramMap,
DocumentSpecification ds) {
List<Map<String, String>> filterList = new ArrayList<Map<String, String>>();
Set<String> folders = new HashSet<String>();
int i = 0;
while (i < ds.getChildCount()) {
SpecificationNode sn = ds.getChild(i++);
if (sn.getType().equals(EmailConfig.NODE_FILTER)) {
String findParameterName = sn.getAttributeValue(EmailConfig.ATTRIBUTE_NAME);
String findParameterValue = sn.getAttributeValue(EmailConfig.ATTRIBUTE_VALUE);
Map<String, String> row = new HashMap<String, String>();
row.put("name", findParameterName);
row.put("value", findParameterValue);
filterList.add(row);
}
else if (sn.getType().equals(EmailConfig.NODE_FOLDER)) {
String folderName = sn.getAttributeValue(EmailConfig.ATTRIBUTE_NAME);
folders.add(folderName);
}
}
paramMap.put("MATCHES", filterList);
paramMap.put("FOLDERS", folders);
}
/**
* Process a specification post.
* This method is called at the start of job's edit or view page, whenever there is a possibility that form
* data for a connection has been posted. Its purpose is to gather form information and modify the
* document specification accordingly. The name of the posted form is always "editjob".
* The connector will be connected before this method can be called.
*
* @param variableContext contains the post data, including binary file-upload information.
* @param ds is the current document specification for this job.
* @return null if all is well, or a string error message if there is an error that should prevent saving of
* the job (and cause a redirection to an error page).
*/
@Override
public String processSpecificationPost(IPostParameters variableContext, DocumentSpecification ds)
throws ManifoldCFException {
String result = processFilterTab(variableContext, ds);
if (result != null)
return result;
result = processMetadataTab(variableContext, ds);
return result;
}
protected String processFilterTab(IPostParameters variableContext, DocumentSpecification ds)
throws ManifoldCFException {
String findCountString = variableContext.getParameter("findcount");
if (findCountString != null) {
int findCount = Integer.parseInt(findCountString);
// Remove old find parameter document specification information
removeNodes(ds, EmailConfig.NODE_FILTER);
int i = 0;
while (i < findCount) {
String suffix = "_" + Integer.toString(i++);
// Only add the name/value if the item was not deleted.
String findParameterOp = variableContext.getParameter("findop" + suffix);
if (findParameterOp == null || !findParameterOp.equals("Delete")) {
String findParameterName = variableContext.getParameter("findname" + suffix);
String findParameterValue = variableContext.getParameter("findvalue" + suffix);
addFindParameterNode(ds, findParameterName, findParameterValue);
}
}
String operation = variableContext.getParameter("findop");
if (operation != null && operation.equals("Add")) {
String findParameterName = variableContext.getParameter("findname");
String findParameterValue = variableContext.getParameter("findvalue");
addFindParameterNode(ds, findParameterName, findParameterValue);
}
}
String[] folders = variableContext.getParameterValues("folders");
if (folders != null)
{
removeNodes(ds, EmailConfig.NODE_FOLDER);
for (String folder : folders)
{
addFolderNode(ds, folder);
}
}
return null;
}
protected String processMetadataTab(IPostParameters variableContext, DocumentSpecification ds)
throws ManifoldCFException {
// Remove old included metadata nodes
removeNodes(ds, EmailConfig.NODE_METADATA);
// Get the posted metadata values
String[] metadataNames = variableContext.getParameterValues("metadata");
if (metadataNames != null) {
// Add each metadata name as a node to the document specification
int i = 0;
while (i < metadataNames.length) {
String metadataName = metadataNames[i++];
addIncludedMetadataNode(ds, metadataName);
}
}
return null;
}
/**
* View specification.
* This method is called in the body section of a job's view page. Its purpose is to present the document
* specification information to the user. The coder can presume that the HTML that is output from
* this configuration will be within appropriate <html> and <body> tags.
* The connector will be connected before this method can be called.
*
* @param out is the output to which any HTML should be sent.
* @param locale is the desired locale.
* @param ds is the current document specification for this job.
*/
@Override
public void viewSpecification(IHTTPOutput out, Locale locale, DocumentSpecification ds)
throws ManifoldCFException, IOException {
Map<String, Object> paramMap = new HashMap<String, Object>();
fillInFilterTab(paramMap, ds);
fillInMetadataTab(paramMap, ds);
Messages.outputResourceWithVelocity(out, locale, "SpecificationView.html", paramMap);
}
///////////////////////////////////////End of specification UI///////////////////////////////////////////////
/** Get a sorted list of folder names */
protected String[] getFolderNames()
throws ManifoldCFException, ServiceInterruption
{
getSession();
try
{
ListFoldersThread lft = new ListFoldersThread(session);
lft.start();
return lft.finishUp();
}
catch (InterruptedException e)
{
throw new ManifoldCFException(e.getMessage(),ManifoldCFException.INTERRUPTED);
}
catch (MessagingException e)
{
handleMessagingException(e,"getting folder list");
return null;
}
}
/** Create a document's URI given a template, a folder name, and a message ID */
protected static String makeDocumentURI(String urlTemplate, String folderName, String id)
{
try {
// First, URL encode folder name and id
String encodedFolderName = java.net.URLEncoder.encode(folderName, "utf-8");
String encodedId = java.net.URLEncoder.encode(id, "utf-8");
// The template is already URL encoded, except for the substitution points
Map<String,String> subsMap = new HashMap<String,String>();
subsMap.put("FOLDERNAME", encodedFolderName);
subsMap.put("MESSAGEID", encodedId);
return substitute(urlTemplate, subsMap);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("No utf-8 encoder found: "+e.getMessage(), e);
}
}
protected static String substitute(String template, Map<String,String> map)
{
StringBuilder sb = new StringBuilder();
int index = 0;
while (true)
{
int newIndex = template.indexOf("$(",index);
if (newIndex == -1)
{
sb.append(template.substring(index));
break;
}
sb.append(template.substring(index, newIndex));
int endIndex = template.indexOf(")",newIndex+2);
String varName;
if (endIndex == -1)
varName = template.substring(newIndex + 2);
else
varName = template.substring(newIndex + 2, endIndex);
String subsValue = map.get(varName);
if (subsValue == null)
subsValue = "";
sb.append(subsValue);
if (endIndex == -1)
break;
index = endIndex+1;
}
return sb.toString();
}
protected static void addFindParameterNode(ConfigParams parameters, String findParameterName, String findParameterValue) {
ConfigNode cn = new ConfigNode(EmailConfig.NODE_PROPERTIES);
cn.setAttribute(EmailConfig.ATTRIBUTE_NAME, findParameterName);
cn.setAttribute(EmailConfig.ATTRIBUTE_VALUE, findParameterValue);
// Add to the end
parameters.addChild(parameters.getChildCount(), cn);
}
protected static void removeNodes(ConfigParams parameters,
String nodeTypeName) {
int i = 0;
while (i < parameters.getChildCount()) {
ConfigNode cn = parameters.getChild(i);
if (cn.getType().equals(nodeTypeName))
parameters.removeChild(i);
else
i++;
}
}
protected static void removeNodes(DocumentSpecification ds,
String nodeTypeName) {
int i = 0;
while (i < ds.getChildCount()) {
SpecificationNode sn = ds.getChild(i);
if (sn.getType().equals(nodeTypeName))
ds.removeChild(i);
else
i++;
}
}
protected static void addIncludedMetadataNode(DocumentSpecification ds,
String metadataName) {
// Build the proper node
SpecificationNode sn = new SpecificationNode(EmailConfig.NODE_METADATA);
sn.setAttribute(EmailConfig.ATTRIBUTE_NAME, metadataName);
// Add to the end
ds.addChild(ds.getChildCount(), sn);
}
protected static void addFindParameterNode(DocumentSpecification ds, String findParameterName, String findParameterValue) {
SpecificationNode sn = new SpecificationNode(EmailConfig.NODE_FILTER);
sn.setAttribute(EmailConfig.ATTRIBUTE_NAME, findParameterName);
sn.setAttribute(EmailConfig.ATTRIBUTE_VALUE, findParameterValue);
// Add to the end
ds.addChild(ds.getChildCount(), sn);
}
protected static void addFolderNode(DocumentSpecification ds, String folderName)
{
SpecificationNode sn = new SpecificationNode(EmailConfig.NODE_FOLDER);
sn.setAttribute(EmailConfig.ATTRIBUTE_NAME, folderName);
ds.addChild(ds.getChildCount(), sn);
}
/** Create a document identifier from a folder name and an email ID */
protected static String createDocumentIdentifier(String folderName, String emailID)
{
return makeSafeFolderName(folderName) + ":" + emailID;
}
/** Find a folder name in a document identifier */
protected static String extractFolderNameFromDocumentIdentifier(String di)
{
int index = di.indexOf(":");
if (index == -1)
throw new RuntimeException("Bad document identifier: '"+di+"'");
return di.substring(0,index);
}
/** Find an email ID in a document identifier */
protected static String extractEmailIDFromDocumentIdentifier(String di)
{
int index = di.indexOf(":");
if (index == -1)
throw new RuntimeException("Bad document identifier: '"+di+"'");
return di.substring(index+1);
}
/** Create a safe folder name (which doesn't contain colons) */
protected static String makeSafeFolderName(String folderName)
{
StringBuilder sb = new StringBuilder();
for (int i = 0; i < folderName.length(); i++)
{
char x = folderName.charAt(i);
if (x == '\\')
sb.append('\\').append('\\');
else if (x == ':')
sb.append('\\').append('0');
else
sb.append(x);
}
return sb.toString();
}
/** Unpack a safe folder name */
protected static String unpackSafeFolderName(String packedFolderName)
{
StringBuilder sb = new StringBuilder();
int i = 0;
while (i < packedFolderName.length())
{
char x = packedFolderName.charAt(i++);
if (x == '\\')
{
if (i == packedFolderName.length())
throw new RuntimeException("Illegal packed folder name: '"+packedFolderName+"'");
x = packedFolderName.charAt(i++);
if (x == '\\')
sb.append('\\');
else if (x == '0')
sb.append(':');
else
throw new RuntimeException("Illegal packed folder name: '"+packedFolderName+"'");
}
else
sb.append(x);
}
return sb.toString();
}
/** Handle Messaging exceptions in a consistent global manner */
protected static void handleMessagingException(MessagingException e, String context)
throws ManifoldCFException, ServiceInterruption
{
Logging.connectors.error("Email: Error "+context+": "+e.getMessage(),e);
throw new ManifoldCFException("Error "+context+": "+e.getMessage(),e);
}
/** Handle IO Exception */
protected static void handleIOException(IOException e, String context)
throws ManifoldCFException, ServiceInterruption
{
if (e instanceof java.net.SocketTimeoutException)
{
Logging.connectors.error("Email: Socket timeout "+context+": "+e.getMessage(),e);
throw new ManifoldCFException("Socket timeout: "+e.getMessage(),e);
}
else if (e instanceof InterruptedIOException)
{
throw new ManifoldCFException("Interrupted: "+e.getMessage(),ManifoldCFException.INTERRUPTED);
}
else
{
Logging.connectors.error("Email: IO error "+context+": "+e.getMessage(),e);
throw new ManifoldCFException("IO error "+context+": "+e.getMessage(),e);
}
}
/** Class to set up connection.
*/
protected static class ConnectThread extends Thread
{
protected final String server;
protected final int port;
protected final String username;
protected final String password;
protected final String protocol;
protected final Properties properties;
// Local session handle
protected EmailSession session = null;
protected Throwable exception = null;
public ConnectThread(String server, int port, String username, String password, String protocol, Properties properties)
{
this.server = server;
this.port = port;
this.username = username;
this.password = password;
this.protocol = protocol;
this.properties = properties;
setDaemon(true);
}
public void run()
{
try
{
session = new EmailSession(server, port, username, password, protocol, properties);
}
catch (Throwable e)
{
exception = e;
}
}
public EmailSession finishUp()
throws MessagingException, InterruptedException
{
try
{
join();
if (exception != null)
{
if (exception instanceof RuntimeException)
throw (RuntimeException)exception;
else if (exception instanceof Error)
throw (Error)exception;
else if (exception instanceof MessagingException)
throw (MessagingException)exception;
else
throw new RuntimeException("Unknown exception type: "+exception.getClass().getName()+": "+exception.getMessage(),exception);
}
return session;
} catch (InterruptedException e) {
this.interrupt();
throw e;
}
}
}
/** Class to close the session.
*/
protected static class CloseSessionThread extends Thread
{
protected final EmailSession session;
protected Throwable exception = null;
public CloseSessionThread(EmailSession session)
{
this.session = session;
setDaemon(true);
}
public void run()
{
try
{
session.close();
}
catch (Throwable e)
{
exception = e;
}
}
public void finishUp()
throws MessagingException, InterruptedException
{
try
{
join();
if (exception != null)
{
if (exception instanceof RuntimeException)
throw (RuntimeException)exception;
else if (exception instanceof Error)
throw (Error)exception;
else if (exception instanceof MessagingException)
throw (MessagingException)exception;
else
throw new RuntimeException("Unknown exception type: "+exception.getClass().getName()+": "+exception.getMessage(),exception);
}
} catch (InterruptedException e) {
this.interrupt();
throw e;
}
}
}
/** Class to list all folders.
*/
protected static class ListFoldersThread extends Thread
{
protected final EmailSession session;
protected String[] rval = null;
protected Throwable exception = null;
public ListFoldersThread(EmailSession session)
{
this.session = session;
setDaemon(true);
}
public void run()
{
try
{
rval = session.listFolders();
}
catch (Throwable e)
{
exception = e;
}
}
public String[] finishUp()
throws MessagingException, InterruptedException
{
try
{
join();
if (exception != null)
{
if (exception instanceof RuntimeException)
throw (RuntimeException)exception;
else if (exception instanceof Error)
throw (Error)exception;
else if (exception instanceof MessagingException)
throw (MessagingException)exception;
else
throw new RuntimeException("Unknown exception type: "+exception.getClass().getName()+": "+exception.getMessage(),exception);
}
return rval;
} catch (InterruptedException e) {
this.interrupt();
throw e;
}
}
}
/** Class to check the connection.
*/
protected static class CheckConnectionThread extends Thread
{
protected final EmailSession session;
protected Throwable exception = null;
public CheckConnectionThread(EmailSession session)
{
this.session = session;
setDaemon(true);
}
public void run()
{
try
{
session.checkConnection();
}
catch (Throwable e)
{
exception = e;
}
}
public void finishUp()
throws MessagingException, InterruptedException
{
try
{
join();
if (exception != null)
{
if (exception instanceof RuntimeException)
throw (RuntimeException)exception;
else if (exception instanceof Error)
throw (Error)exception;
else if (exception instanceof MessagingException)
throw (MessagingException)exception;
else
throw new RuntimeException("Unknown exception type: "+exception.getClass().getName()+": "+exception.getMessage(),exception);
}
} catch (InterruptedException e) {
this.interrupt();
throw e;
}
}
}
/** Class to open a folder.
*/
protected static class OpenFolderThread extends Thread
{
protected final EmailSession session;
protected final String folderName;
// Local folder
protected Folder folder = null;
protected Throwable exception = null;
public OpenFolderThread(EmailSession session, String folderName)
{
this.session = session;
this.folderName = folderName;
setDaemon(true);
}
public void run()
{
try
{
folder = session.openFolder(folderName);
}
catch (Throwable e)
{
exception = e;
}
}
public Folder finishUp()
throws MessagingException, InterruptedException
{
try
{
join();
if (exception != null)
{
if (exception instanceof RuntimeException)
throw (RuntimeException)exception;
else if (exception instanceof Error)
throw (Error)exception;
else if (exception instanceof MessagingException)
throw (MessagingException)exception;
else
throw new RuntimeException("Unknown exception type: "+exception.getClass().getName()+": "+exception.getMessage(),exception);
}
return folder;
} catch (InterruptedException e) {
this.interrupt();
throw e;
}
}
}
/** Class to close a folder.
*/
protected static class CloseFolderThread extends Thread
{
protected final EmailSession session;
protected final Folder folder;
// Local folder
protected Throwable exception = null;
public CloseFolderThread(EmailSession session, Folder folder)
{
this.session = session;
this.folder = folder;
setDaemon(true);
}
public void run()
{
try
{
session.closeFolder(folder);
}
catch (Throwable e)
{
exception = e;
}
}
public void finishUp()
throws MessagingException, InterruptedException
{
try
{
join();
if (exception != null)
{
if (exception instanceof RuntimeException)
throw (RuntimeException)exception;
else if (exception instanceof Error)
throw (Error)exception;
else if (exception instanceof MessagingException)
throw (MessagingException)exception;
else
throw new RuntimeException("Unknown exception type: "+exception.getClass().getName()+": "+exception.getMessage(),exception);
}
} catch (InterruptedException e) {
this.interrupt();
throw e;
}
}
}
/** Class to get all messages from a folder.
*/
protected static class GetMessagesThread extends Thread
{
protected final EmailSession session;
protected final Folder folder;
// Local messages
protected Message[] messages = null;
protected Throwable exception = null;
public GetMessagesThread(EmailSession session, Folder folder)
{
this.session = session;
this.folder = folder;
setDaemon(true);
}
public void run()
{
try
{
messages = session.getMessages(folder);
}
catch (Throwable e)
{
exception = e;
}
}
public Message[] finishUp()
throws MessagingException, InterruptedException
{
try
{
join();
if (exception != null)
{
if (exception instanceof RuntimeException)
throw (RuntimeException)exception;
else if (exception instanceof Error)
throw (Error)exception;
else if (exception instanceof MessagingException)
throw (MessagingException)exception;
else
throw new RuntimeException("Unknown exception type: "+exception.getClass().getName()+": "+exception.getMessage(),exception);
}
return messages;
} catch (InterruptedException e) {
this.interrupt();
throw e;
}
}
}
/** Class to search for messages in a folder.
*/
protected static class SearchMessagesThread extends Thread
{
protected final EmailSession session;
protected final Folder folder;
protected final SearchTerm searchTerm;
// Local messages
protected Message[] messages = null;
protected Throwable exception = null;
public SearchMessagesThread(EmailSession session, Folder folder, SearchTerm searchTerm)
{
this.session = session;
this.folder = folder;
this.searchTerm = searchTerm;
setDaemon(true);
}
public void run()
{
try
{
messages = session.search(folder, searchTerm);
}
catch (Throwable e)
{
exception = e;
}
}
public Message[] finishUp()
throws MessagingException, InterruptedException
{
try
{
join();
if (exception != null)
{
if (exception instanceof RuntimeException)
throw (RuntimeException)exception;
else if (exception instanceof Error)
throw (Error)exception;
else if (exception instanceof MessagingException)
throw (MessagingException)exception;
else
throw new RuntimeException("Unknown exception type: "+exception.getClass().getName()+": "+exception.getMessage(),exception);
}
return messages;
} catch (InterruptedException e) {
this.interrupt();
throw e;
}
}
}
}