http://git-wip-us.apache.org/repos/asf/james-project/blob/3dddd7c5/mailet/test/src/main/java/org/apache/mailet/base/test/FakeMail.java ---------------------------------------------------------------------- diff --git a/mailet/test/src/main/java/org/apache/mailet/base/test/FakeMail.java b/mailet/test/src/main/java/org/apache/mailet/base/test/FakeMail.java new file mode 100644 index 0000000..6791a1f --- /dev/null +++ b/mailet/test/src/main/java/org/apache/mailet/base/test/FakeMail.java @@ -0,0 +1,464 @@ +/**************************************************************** + * 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.mailet.base.test; + +import java.io.Serializable; +import java.io.UnsupportedEncodingException; +import java.util.Arrays; +import java.util.Collection; +import java.util.Date; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Properties; + +import javax.mail.MessagingException; +import javax.mail.internet.AddressException; +import javax.mail.internet.InternetAddress; +import javax.mail.internet.MimeMessage; + +import org.apache.james.core.MailAddress; +import org.apache.james.core.builder.MimeMessageBuilder; +import org.apache.james.util.MimeMessageUtil; +import org.apache.mailet.Mail; +import org.apache.mailet.PerRecipientHeaders; +import org.apache.mailet.PerRecipientHeaders.Header; + +import com.github.fge.lambdas.Throwing; +import com.github.steveash.guavate.Guavate; +import com.google.common.base.MoreObjects; +import com.google.common.base.Objects; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; + +public class FakeMail implements Mail, Serializable { + + private static final String DEFAULT_REMOTE_HOST = "111.222.333.444"; + public static final String DEFAULT_REMOTE_ADDRESS = "127.0.0.1"; + + public static FakeMail fromMessage(MimeMessageBuilder message) throws MessagingException { + return FakeMail.builder() + .mimeMessage(message) + .build(); + } + + public static FakeMail fromMime(String text, String javaEncodingCharset, String javamailDefaultEncodingCharset) throws MessagingException, UnsupportedEncodingException { + Properties javamailProperties = new Properties(); + javamailProperties.setProperty("mail.mime.charset", javamailDefaultEncodingCharset); + return FakeMail.builder() + .mimeMessage(MimeMessageUtil.mimeMessageFromBytes((text.getBytes(javaEncodingCharset)))) + .build(); + } + + public static FakeMail fromMail(Mail mail) throws MessagingException { + return new FakeMail(mail.getMessage(), + Lists.newArrayList(mail.getRecipients()), + mail.getName(), + mail.getSender(), + mail.getState(), + mail.getErrorMessage(), + mail.getLastUpdated(), + attributes(mail), + mail.getMessageSize(), + mail.getRemoteAddr(), + mail.getRemoteHost(), + mail.getPerRecipientSpecificHeaders()); + } + + public static FakeMail from(MimeMessage message) throws MessagingException { + return builder() + .mimeMessage(message) + .build(); + } + + public static FakeMail from(MimeMessageBuilder message) throws MessagingException { + return from(message.build()); + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + + private Optional<String> fileName; + private Optional<MimeMessage> mimeMessage; + private List<MailAddress> recipients; + private Optional<String> name; + private Optional<MailAddress> sender; + private Optional<String> state; + private Optional<String> errorMessage; + private Optional<Date> lastUpdated; + private Map<String, Serializable> attributes; + private Optional<Long> size; + private Optional<String> remoteAddr; + private Optional<String> remoteHost; + private PerRecipientHeaders perRecipientHeaders; + + private Builder() { + fileName = Optional.empty(); + mimeMessage = Optional.empty(); + recipients = Lists.newArrayList(); + name = Optional.empty(); + sender = Optional.empty(); + state = Optional.empty(); + errorMessage = Optional.empty(); + lastUpdated = Optional.empty(); + attributes = Maps.newHashMap(); + size = Optional.empty(); + remoteAddr = Optional.empty(); + remoteHost = Optional.empty(); + perRecipientHeaders = new PerRecipientHeaders(); + } + + public Builder size(long size) { + this.size = Optional.of(size); + return this; + } + + public Builder fileName(String fileName) { + this.fileName = Optional.of(fileName); + return this; + } + + public Builder mimeMessage(MimeMessage mimeMessage) { + this.mimeMessage = Optional.of(mimeMessage); + return this; + } + + public Builder mimeMessage(MimeMessageBuilder mimeMessage) throws MessagingException { + this.mimeMessage = Optional.of(mimeMessage.build()); + return this; + } + + public Builder recipients() { + return this; + } + + public Builder recipients(List<MailAddress> recipients) { + this.recipients.addAll(recipients); + return this; + } + + public Builder recipients(MailAddress... recipients) { + return recipients(ImmutableList.copyOf(recipients)); + } + + public Builder recipients(String... recipients) { + return recipients(Arrays.stream(recipients) + .map(Throwing.function(MailAddress::new)) + .collect(Guavate.toImmutableList())); + } + + public Builder recipient(MailAddress recipient) { + return recipients(recipient); + } + + public Builder recipient(String recipient) throws AddressException { + return recipients(recipient); + } + + public Builder name(String name) { + this.name = Optional.of(name); + return this; + } + + public Builder sender(MailAddress sender) { + this.sender = Optional.of(sender); + return this; + } + + public Builder sender(String sender) throws AddressException { + return sender(new MailAddress(sender)); + } + + public Builder state(String state) { + this.state = Optional.of(state); + return this; + } + + public Builder errorMessage(String errorMessage) { + this.errorMessage = Optional.of(errorMessage); + return this; + } + + public Builder lastUpdated(Date lastUpdated) { + this.lastUpdated = Optional.of(lastUpdated); + return this; + } + + public Builder attribute(String name, Serializable object) { + this.attributes.put(name, object); + return this; + } + + public Builder attributes(Map<String, Serializable> attributes) { + this.attributes.putAll(attributes); + return this; + } + + public Builder remoteAddr(String remoteAddr) { + this.remoteAddr = Optional.of(remoteAddr); + return this; + } + + public Builder remoteHost(String remoteHost) { + this.remoteHost = Optional.of(remoteHost); + return this; + } + + public Builder addHeaderForRecipient(Header header, MailAddress recipient) { + this.perRecipientHeaders.addHeaderForRecipient(header, recipient); + return this; + } + + public FakeMail build() throws MessagingException { + return new FakeMail(getMimeMessage(), recipients, name.orElse(null), sender.orElse(null), state.orElse(null), errorMessage.orElse(null), lastUpdated.orElse(null), + attributes, size.orElse(0L), remoteAddr.orElse(DEFAULT_REMOTE_ADDRESS), remoteHost.orElse(DEFAULT_REMOTE_HOST), perRecipientHeaders); + } + + private MimeMessage getMimeMessage() throws MessagingException { + Preconditions.checkState(!(fileName.isPresent() && mimeMessage.isPresent()), "You can not specify a MimeMessage object when you alredy set Content from a file"); + if (fileName.isPresent()) { + return MimeMessageUtil.mimeMessageFromStream(ClassLoader.getSystemResourceAsStream(fileName.get())); + } + return mimeMessage.orElse(null); + } + } + + public static FakeMail defaultFakeMail() throws MessagingException { + return FakeMail.builder().build(); + } + + private static Map<String, Serializable> attributes(Mail mail) { + ImmutableMap.Builder<String, Serializable> builder = ImmutableMap.builder(); + for (String attributeName: ImmutableList.copyOf(mail.getAttributeNames())) { + builder.put(attributeName, mail.getAttribute(attributeName)); + } + return builder.build(); + } + + private transient MimeMessage msg; + private Collection<MailAddress> recipients; + private String name; + private MailAddress sender; + private String state; + private String errorMessage; + private Date lastUpdated; + private Map<String, Serializable> attributes; + private long size; + private String remoteAddr; + private String remoteHost; + private PerRecipientHeaders perRecipientHeaders; + + public FakeMail(MimeMessage msg, List<MailAddress> recipients, String name, MailAddress sender, String state, String errorMessage, Date lastUpdated, + Map<String, Serializable> attributes, long size, String remoteAddr, String remoteHost, PerRecipientHeaders perRecipientHeaders) { + this.msg = msg; + this.recipients = recipients; + this.name = name; + this.sender = sender; + this.state = state; + this.errorMessage = errorMessage; + this.lastUpdated = lastUpdated; + this.attributes = attributes; + this.size = size; + this.remoteAddr = remoteAddr; + this.perRecipientHeaders = perRecipientHeaders; + this.remoteHost = remoteHost; + } + + @Override + public String getName() { + return name; + } + + @Override + public void setName(String newName) { + this.name = newName; + } + + @Override + public MimeMessage getMessage() throws MessagingException { + return msg; + } + + @Override + public Collection<MailAddress> getRecipients() { + return recipients; + } + + @Override + public void setRecipients(Collection<MailAddress> recipients) { + this.recipients = recipients; + } + + @Override + public MailAddress getSender() { + return sender; + } + + @Override + public String getState() { + return state; + } + + @Override + public String getRemoteHost() { + return remoteHost; + } + + @Override + public String getRemoteAddr() { + return remoteAddr; + } + + @Override + public String getErrorMessage() { + return errorMessage; + } + + @Override + public void setErrorMessage(String msg) { + this.errorMessage = msg; + } + + @Override + public void setMessage(MimeMessage message) { + this.msg = message; + try { + if (message != null && message.getSender() != null) { + this.sender = new MailAddress((InternetAddress) message.getSender()); + } + } catch (MessagingException e) { + throw new RuntimeException("Exception caught", e); + } + } + + @Override + public void setState(String state) { + this.state = state; + } + + @Override + public Serializable getAttribute(String name) { + return attributes.get(name); + } + + @Override + public Iterator<String> getAttributeNames() { + return attributes.keySet().iterator(); + } + + @Override + public boolean hasAttributes() { + return !attributes.isEmpty(); + } + + @Override + public Serializable removeAttribute(String name) { + return attributes.remove(name); + + } + + @Override + public void removeAllAttributes() { + attributes.clear(); + } + + @Override + public Serializable setAttribute(String name, Serializable object) { + return attributes.put(name, object); + } + + @Override + public long getMessageSize() throws MessagingException { + return size; + } + + @Override + public Date getLastUpdated() { + return lastUpdated; + } + + @Override + public void setLastUpdated(Date lastUpdated) { + this.lastUpdated = lastUpdated; + } + + public void setMessageSize(long size) { + this.size = size; + } + + @Override + public final boolean equals(Object o) { + if (o instanceof FakeMail) { + FakeMail that = (FakeMail) o; + + return Objects.equal(this.size, that.size) + && Objects.equal(this.recipients, that.recipients) + && Objects.equal(this.name, that.name) + && Objects.equal(this.sender, that.sender) + && Objects.equal(this.state, that.state) + && Objects.equal(this.errorMessage, that.errorMessage) + && Objects.equal(this.lastUpdated, that.lastUpdated) + && Objects.equal(this.attributes, that.attributes) + && Objects.equal(this.remoteHost, that.remoteHost) + && Objects.equal(this.perRecipientHeaders, that.perRecipientHeaders) + && Objects.equal(this.remoteAddr, that.remoteAddr); + } + return false; + } + + @Override + public final int hashCode() { + return Objects.hashCode(name, sender, recipients, state, errorMessage, lastUpdated, attributes, size, remoteAddr, remoteHost, perRecipientHeaders); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("msg", msg) + .add("recipients", recipients) + .add("name", name) + .add("sender", sender) + .add("state", state) + .add("errorMessage", errorMessage) + .add("lastUpdated", lastUpdated) + .add("attributes", attributes) + .add("size", size) + .add("remoteAddr", remoteAddr) + .toString(); + } + + @Override + public PerRecipientHeaders getPerRecipientSpecificHeaders() { + return perRecipientHeaders; + } + + @Override + public void addSpecificHeaderForRecipient(Header header, MailAddress recipient) { + perRecipientHeaders.addHeaderForRecipient(header, recipient); + } +}
http://git-wip-us.apache.org/repos/asf/james-project/blob/3dddd7c5/mailet/test/src/main/java/org/apache/mailet/base/test/FakeMailContext.java ---------------------------------------------------------------------- diff --git a/mailet/test/src/main/java/org/apache/mailet/base/test/FakeMailContext.java b/mailet/test/src/main/java/org/apache/mailet/base/test/FakeMailContext.java new file mode 100644 index 0000000..eff41c9 --- /dev/null +++ b/mailet/test/src/main/java/org/apache/mailet/base/test/FakeMailContext.java @@ -0,0 +1,608 @@ +/**************************************************************** + * 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.mailet.base.test; + +import java.io.Serializable; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.TimeUnit; + +import javax.mail.MessagingException; +import javax.mail.internet.AddressException; +import javax.mail.internet.MimeMessage; + +import org.apache.james.core.Domain; +import org.apache.james.core.MailAddress; +import org.apache.james.core.builder.MimeMessageWrapper; +import org.apache.mailet.HostAddress; +import org.apache.mailet.LookupException; +import org.apache.mailet.Mail; +import org.apache.mailet.MailetContext; +import org.slf4j.Logger; + +import com.github.fge.lambdas.Throwing; +import com.github.fge.lambdas.functions.ThrowingFunction; +import com.google.common.base.MoreObjects; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Lists; + +@SuppressWarnings("deprecation") +public class FakeMailContext implements MailetContext { + + public static Builder builder() { + return new Builder(); + } + + public static SentMail.Builder sentMailBuilder() { + return new SentMail.Builder(); + } + + public static SentMail.Builder fromMail(Mail mail) throws MessagingException { + return sentMailBuilder() + .sender(mail.getSender()) + .recipients(mail.getRecipients()) + .message(mail.getMessage()) + .state(mail.getState()) + .attributes(buildAttributesMap(mail)) + .fromMailet(); + } + + private static ImmutableMap<String, Serializable> buildAttributesMap(Mail mail) { + Map<String, Serializable> result = new HashMap<>(); + List<String> attributesNames = Lists.newArrayList(mail.getAttributeNames()); + for (String attributeName: attributesNames) { + result.put(attributeName, mail.getAttribute(attributeName)); + } + return ImmutableMap.copyOf(result); + } + + public static FakeMailContext defaultContext() { + return builder().build(); + } + + public static class Builder { + + private Logger logger; + private Optional<MailAddress> postmaster; + + private Builder() { + postmaster = Optional.empty(); + } + + public Builder logger(Logger logger) { + this.logger = logger; + return this; + } + + public Builder postmaster(MailAddress postmaster) { + this.postmaster = Optional.of(postmaster); + return this; + } + + public FakeMailContext build() { + return new FakeMailContext(Optional.ofNullable(logger), postmaster.orElse(null)); + } + } + + public static class SentMail { + + private static MimeMessage tryCopyMimeMessage(MimeMessage msg) throws MessagingException { + ThrowingFunction<MimeMessage, MimeMessage> throwingFunction = MimeMessageWrapper::wrap; + + return Optional.ofNullable(msg) + .map(Throwing.function(throwingFunction).sneakyThrow()) + .orElse(null); + } + + public static class Builder { + private MailAddress sender; + private Optional<Collection<MailAddress>> recipients = Optional.empty(); + private MimeMessage msg; + private Map<String, Serializable> attributes = new HashMap<>(); + private Optional<String> state = Optional.empty(); + private Optional<Boolean> fromMailet = Optional.empty(); + private Optional<Delay> delay = Optional.empty(); + + public Builder sender(MailAddress sender) { + this.sender = sender; + return this; + } + + public Builder sender(String sender) throws AddressException { + return sender(new MailAddress(sender)); + } + + public Builder recipients(Collection<MailAddress> recipients) { + this.recipients = Optional.of(recipients); + return this; + } + + public Builder fromMailet() { + this.fromMailet = Optional.of(true); + return this; + } + + public Builder recipients(MailAddress... recipients) { + this.recipients = Optional.<Collection<MailAddress>>of(ImmutableList.copyOf(recipients)); + return this; + } + + public Builder recipient(MailAddress recipient) { + Preconditions.checkNotNull(recipient); + return recipients(ImmutableList.of(recipient)); + } + + public Builder recipient(String recipient) throws AddressException { + Preconditions.checkNotNull(recipient); + return recipients(new MailAddress(recipient)); + } + + public Builder message(MimeMessage mimeMessage) { + this.msg = mimeMessage; + return this; + } + + public Builder attributes(Map<String, Serializable> attributes) { + this.attributes.putAll(attributes); + return this; + } + + public Builder attribute(String key, Serializable value) { + this.attributes.put(key, value); + return this; + } + + public Builder state(String state) { + this.state = Optional.of(state); + return this; + } + + public Builder delay(Delay delay) { + this.delay = Optional.of(delay); + return this; + } + + public SentMail build() throws MessagingException { + if (fromMailet.orElse(false)) { + this.attribute(Mail.SENT_BY_MAILET, "true"); + } + return new SentMail(sender, recipients.orElse(ImmutableList.<MailAddress>of()), msg, + ImmutableMap.copyOf(attributes), state.orElse(Mail.DEFAULT), delay); + } + } + + private final MailAddress sender; + private final Collection<MailAddress> recipients; + private final MimeMessage msg; + private final Optional<String> subject; + private final Map<String, Serializable> attributes; + private final String state; + private final Optional<Delay> delay; + + private SentMail(MailAddress sender, Collection<MailAddress> recipients, MimeMessage msg, Map<String, Serializable> attributes, String state, Optional<Delay> delay) throws MessagingException { + this.sender = sender; + this.recipients = ImmutableList.copyOf(recipients); + this.msg = tryCopyMimeMessage(msg); + this.subject = getSubject(msg); + this.attributes = ImmutableMap.copyOf(attributes); + this.state = state; + this.delay = delay; + } + + private Optional<String> getSubject(MimeMessage msg) { + try { + return Optional.ofNullable(msg.getSubject()); + } catch (Exception e) { + return Optional.empty(); + } + } + + public MailAddress getSender() { + return sender; + } + + public Collection<MailAddress> getRecipients() { + return recipients; + } + + public MimeMessage getMsg() { + return msg; + } + + public String getState() { + return state; + } + + public Optional<String> getSubject() { + return subject; + } + + + public Optional<Delay> getDelay() { + return delay; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof SentMail)) { + return false; + } + + SentMail sentMail = (SentMail) o; + + return Objects.equals(this.sender, sentMail.sender) + && Objects.equals(this.recipients, sentMail.recipients) + && Objects.equals(this.attributes, sentMail.attributes) + && Objects.equals(this.state, sentMail.state); + } + + @Override + public int hashCode() { + return Objects.hash(sender, recipients, attributes, state); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("recipients", recipients) + .add("sender", sender) + .add("attributeNames", attributes) + .add("state", state) + .toString(); + } + } + + public static class Delay { + private final long duration; + private final TimeUnit timeUnit; + + public Delay(long duration, TimeUnit timeUnit) { + this.duration = duration; + this.timeUnit = timeUnit; + } + + public long getDuration() { + return duration; + } + + public TimeUnit getTimeUnit() { + return timeUnit; + } + + @Override + public final boolean equals(Object o) { + if (o instanceof Delay) { + Delay delay = (Delay) o; + + return Objects.equals(this.duration, delay.duration) + && Objects.equals(this.timeUnit, delay.timeUnit); + } + return false; + } + + @Override + public final int hashCode() { + return Objects.hash(duration, timeUnit); + } + } + + public static class BouncedMail { + private final SentMail sentMail; + private final String message; + private final Optional<MailAddress> bouncer; + + public BouncedMail(SentMail sentMail, String message, Optional<MailAddress> bouncer) { + this.sentMail = sentMail; + this.message = message; + this.bouncer = bouncer; + } + + public BouncedMail(SentMail.Builder sentMail, String message, Optional<MailAddress> bouncer) throws MessagingException { + this(sentMail.build(), message, bouncer); + } + + public SentMail getSentMail() { + return sentMail; + } + + public String getMessage() { + return message; + } + + public Optional<MailAddress> getBouncer() { + return bouncer; + } + + @Override + public boolean equals(Object o) { + if (o instanceof BouncedMail) { + BouncedMail that = (BouncedMail) o; + return Objects.equals(this.sentMail, that.sentMail) + && Objects.equals(this.message, that.message) + && Objects.equals(this.bouncer, that.bouncer); + } + return false; + } + + @Override + public int hashCode() { + return Objects.hash(sentMail, message, bouncer); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("sentMail", sentMail) + .add("message", message) + .add("bouncer", bouncer) + .toString(); + } + } + + private final HashMap<String, Object> attributes; + private final Collection<SentMail> sentMails; + private final Collection<BouncedMail> bouncedMails; + private final Optional<Logger> logger; + private final MailAddress postmaster; + + private FakeMailContext(Optional<Logger> logger, MailAddress postmaster) { + attributes = new HashMap<>(); + sentMails = new ConcurrentLinkedQueue<>(); + bouncedMails = new ConcurrentLinkedQueue<>(); + this.logger = logger; + this.postmaster = postmaster; + } + + @Override + public void bounce(Mail mail, String message) throws MessagingException { + bouncedMails.add(new BouncedMail(fromMail(mail), message, Optional.empty())); + } + + @Override + public void bounce(Mail mail, String message, MailAddress bouncer) throws MessagingException { + bouncedMails.add(new BouncedMail(fromMail(mail), message, Optional.ofNullable(bouncer))); + } + + /** + * @deprecated use the generic dnsLookup method + */ + @Override + public Collection<String> getMailServers(Domain host) { + return null; // trivial implementation + } + + @Override + public MailAddress getPostmaster() { + return postmaster; + } + + @Override + public Object getAttribute(String name) { + return attributes.get(name); + } + + @Override + public Iterator<String> getAttributeNames() { + return attributes.keySet().iterator(); + } + + @Override + public int getMajorVersion() { + return 0; // trivial implementation + } + + @Override + public int getMinorVersion() { + return 0; // trivial implementation + } + + @Override + public String getServerInfo() { + return "Mock Server"; + } + + @Override + public boolean isLocalServer(Domain domain) { + return domain.equals(Domain.LOCALHOST); + } + + /** + * @deprecated use {@link #isLocalEmail(MailAddress)} instead + */ + @Override + public boolean isLocalUser(String userAccount) { + return false; // trivial implementation + } + + @Override + public boolean isLocalEmail(MailAddress mailAddress) { + return false; // trivial implementation + } + + /** + * @deprecated use {@link #log(LogLevel level, String message)} + */ + @Override + public void log(String message) { + System.out.println(message); + } + + /** + * @deprecated use {@link #log(LogLevel level, String message, Throwable t)} + */ + @Override + public void log(String message, Throwable t) { + System.out.println(message); + t.printStackTrace(System.out); + } + + @Override + public void removeAttribute(String name) { + // trivial implementation + } + + @Override + public void sendMail(MimeMessage mimemessage) throws MessagingException { + sentMails.add(sentMailBuilder() + .message(mimemessage) + .fromMailet() + .build()); + } + + @Override + public void sendMail(MailAddress sender, Collection<MailAddress> recipients, MimeMessage msg) throws MessagingException { + sentMails.add(sentMailBuilder() + .recipients(recipients) + .sender(sender) + .message(msg) + .fromMailet() + .build()); + } + + @Override + public void sendMail(MailAddress sender, Collection<MailAddress> recipients, MimeMessage msg, String state) throws MessagingException { + sentMails.add(sentMailBuilder() + .recipients(recipients) + .message(msg) + .state(state) + .sender(sender) + .fromMailet() + .build()); + } + + @Override + public void sendMail(Mail mail) throws MessagingException { + sendMail(mail, Mail.DEFAULT); + } + + @Override + public void sendMail(Mail mail, long delay, TimeUnit unit) throws MessagingException { + sendMail(mail, Mail.DEFAULT, delay, unit); + } + + @Override + public void sendMail(Mail mail, String state) throws MessagingException { + mail.setState(state); + sentMails.add(fromMail(mail).build()); + } + + @Override + public void sendMail(Mail mail, String state, long delay, TimeUnit unit) throws MessagingException { + mail.setState(state); + sentMails.add( + fromMail(mail) + .delay(new Delay(delay, unit)) + .build()); + } + + public void setAttribute(String name, Serializable object) { + attributes.put(name,object); + } + + public void storeMail(MailAddress sender, MailAddress recipient, MimeMessage msg) throws MessagingException { + // trivial implementation + } + + /** + * @deprecated use the generic dnsLookup method + */ + @Override + public Iterator<HostAddress> getSMTPHostAddresses(Domain domainName) { + return null; // trivial implementation + } + + @Override + public void setAttribute(String name, Object value) { + throw new UnsupportedOperationException("MOCKed method"); + } + + @Override + public void log(LogLevel level, String message) { + if (logger.isPresent()) { + switch (level) { + case INFO: + logger.get().info(message); + break; + case WARN: + logger.get().warn(message); + break; + case ERROR: + logger.get().error(message); + break; + default: + logger.get().debug(message); + } + } else { + System.out.println("[" + level + "]" + message); + } + } + + @Override + public void log(LogLevel level, String message, Throwable t) { + if (logger.isPresent()) { + switch (level) { + case INFO: + logger.get().info(message, t); + break; + case WARN: + logger.get().warn(message, t); + break; + case ERROR: + logger.get().error(message, t); + break; + default: + logger.get().debug(message, t); + } + } else { + System.out.println("[" + level + "]" + message); + t.printStackTrace(System.out); + } + } + + @Override + public List<String> dnsLookup(String name, RecordType type) throws LookupException { + return null; // trivial implementation + } + + public List<SentMail> getSentMails() { + return ImmutableList.copyOf(sentMails); + } + + public void resetSentMails() { + sentMails.clear(); + } + + public List<BouncedMail> getBouncedMails() { + return ImmutableList.copyOf(bouncedMails); + } + + @Override + public Logger getLogger() { + return logger.orElse(null); + } +} http://git-wip-us.apache.org/repos/asf/james-project/blob/3dddd7c5/mailet/test/src/main/java/org/apache/mailet/base/test/FakeMailetConfig.java ---------------------------------------------------------------------- diff --git a/mailet/test/src/main/java/org/apache/mailet/base/test/FakeMailetConfig.java b/mailet/test/src/main/java/org/apache/mailet/base/test/FakeMailetConfig.java new file mode 100644 index 0000000..73289d7 --- /dev/null +++ b/mailet/test/src/main/java/org/apache/mailet/base/test/FakeMailetConfig.java @@ -0,0 +1,106 @@ +/**************************************************************** + * 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.mailet.base.test; + +import java.util.Iterator; +import java.util.Optional; +import java.util.Properties; + +import org.apache.mailet.MailetConfig; +import org.apache.mailet.MailetContext; + +/** + * MailetConfig over Properties + */ +public class FakeMailetConfig implements MailetConfig { + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + + private static final String DEFAULT_MAILET_NAME = "A Mailet"; + private Optional<String> mailetName; + private Optional<MailetContext> mailetContext; + private Properties properties; + + private Builder() { + mailetName = Optional.empty(); + mailetContext = Optional.empty(); + properties = new Properties(); + } + + public Builder mailetName(String mailetName) { + this.mailetName = Optional.ofNullable(mailetName); + return this; + } + + public Builder mailetContext(MailetContext mailetContext) { + this.mailetContext = Optional.ofNullable(mailetContext); + return this; + } + + public Builder mailetContext(FakeMailContext.Builder mailetContext) { + return mailetContext(mailetContext.build()); + } + + public Builder setProperty(String key, String value) { + this.properties.setProperty(key, value); + return this; + } + + public FakeMailetConfig build() { + return new FakeMailetConfig(mailetName.orElse(DEFAULT_MAILET_NAME), + mailetContext.orElse(FakeMailContext.defaultContext()), + properties); + } + } + + private final String mailetName; + private final MailetContext mailetContext; + private final Properties properties; + + private FakeMailetConfig(String mailetName, MailetContext mailetContext, Properties properties) { + this.mailetName = mailetName; + this.mailetContext = mailetContext; + this.properties = properties; + } + + @Override + public String getInitParameter(String name) { + return properties.getProperty(name); + } + + @Override + public Iterator<String> getInitParameterNames() { + return properties.stringPropertyNames().iterator(); + } + + @Override + public MailetContext getMailetContext() { + return mailetContext; + } + + @Override + public String getMailetName() { + return mailetName; + } +} http://git-wip-us.apache.org/repos/asf/james-project/blob/3dddd7c5/mailet/test/src/main/java/org/apache/mailet/base/test/FakeMatcherConfig.java ---------------------------------------------------------------------- diff --git a/mailet/test/src/main/java/org/apache/mailet/base/test/FakeMatcherConfig.java b/mailet/test/src/main/java/org/apache/mailet/base/test/FakeMatcherConfig.java new file mode 100644 index 0000000..1917c3d --- /dev/null +++ b/mailet/test/src/main/java/org/apache/mailet/base/test/FakeMatcherConfig.java @@ -0,0 +1,95 @@ +/**************************************************************** + * 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.mailet.base.test; + +import java.util.Optional; + +import org.apache.mailet.MailetContext; +import org.apache.mailet.MatcherConfig; + +import com.google.common.base.Preconditions; + +/** + * MatcherConfig + */ +public class FakeMatcherConfig implements MatcherConfig { + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + + private String matcherName; + private Optional<MailetContext> mailetContext; + private Optional<String> condition; + + private Builder() { + condition = Optional.empty(); + mailetContext = Optional.empty(); + } + + public Builder matcherName(String matcherName) { + this.matcherName = matcherName; + return this; + } + + public Builder mailetContext(MailetContext mailetContext) { + Preconditions.checkNotNull(mailetContext); + this.mailetContext = Optional.of(mailetContext); + return this; + } + + public Builder condition(String condition) { + this.condition = Optional.ofNullable(condition); + return this; + } + + public FakeMatcherConfig build() { + Preconditions.checkNotNull(matcherName, "'matcherName' is mandatory"); + return new FakeMatcherConfig(matcherName, mailetContext.orElse(FakeMailContext.defaultContext()), condition); + } + } + + private final String matcherName; + private final MailetContext mailetContext; + private final Optional<String> condition; + + private FakeMatcherConfig(String matcherName, MailetContext mailetContext, Optional<String> condition) { + this.matcherName = matcherName; + this.mailetContext = mailetContext; + this.condition = condition; + } + + @Override + public String getMatcherName() { + return matcherName; + } + + @Override + public MailetContext getMailetContext() { + return mailetContext; + } + + @Override + public String getCondition() { + return condition.orElse(null); + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/james-project/blob/3dddd7c5/mailet/test/src/main/java/org/apache/mailet/base/test/MailUtil.java ---------------------------------------------------------------------- diff --git a/mailet/test/src/main/java/org/apache/mailet/base/test/MailUtil.java b/mailet/test/src/main/java/org/apache/mailet/base/test/MailUtil.java new file mode 100644 index 0000000..ecbee4f --- /dev/null +++ b/mailet/test/src/main/java/org/apache/mailet/base/test/MailUtil.java @@ -0,0 +1,92 @@ +/**************************************************************** + * 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.mailet.base.test; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +import javax.mail.MessagingException; +import javax.mail.internet.MimeMessage; + +import org.apache.james.core.MailAddress; +import org.apache.james.core.builder.MimeMessageBuilder; +import org.apache.mailet.Mail; + +/** + * some utilities for James unit testing + */ +public class MailUtil { + + public static final String SENDER = "t...@james.apache.org"; + public static final String RECIPIENT = "te...@james.apache.org"; + private static int m_counter = 0; + + public static String newId() { + m_counter++; + return "MockMailUtil-ID-" + m_counter; + } + + public static FakeMail createMockMail2Recipients() throws MessagingException { + return FakeMail.builder() + .name(newId()) + .recipients(new MailAddress("t...@james.apache.org"), new MailAddress("te...@james.apache.org")) + .build(); + } + + public static FakeMail createMockMail2Recipients(MimeMessage message) throws MessagingException { + return FakeMail.builder() + .name(newId()) + .mimeMessage(message) + .recipients(new MailAddress("t...@james.apache.org"), new MailAddress("te...@james.apache.org")) + .build(); + } + + public static MimeMessage createMimeMessage() throws MessagingException { + return createMimeMessage(null, null); + } + + public static MimeMessage createMimeMessageWithSubject(String subject) throws MessagingException { + return createMimeMessage(null, null, subject); + } + + public static MimeMessage createMimeMessage(String headerName, String headerValue) throws MessagingException { + return createMimeMessage(headerName, headerValue, "testmail"); + } + + private static MimeMessage createMimeMessage(String headerName, String headerValue, String subject) throws MessagingException { + MimeMessageBuilder mimeMessageBuilder = MimeMessageBuilder.mimeMessageBuilder() + .addToRecipient(RECIPIENT) + .addFrom(SENDER) + .setSubject(subject); + if (headerName != null) { + mimeMessageBuilder.addHeader(headerName, headerValue); + } + return mimeMessageBuilder.build(); + } + + public static String toString(Mail mail, String charset) throws IOException, MessagingException { + ByteArrayOutputStream rawMessage = new ByteArrayOutputStream(); + mail.getMessage().writeTo( + rawMessage, + new String[] { "Bcc", "Content-Length", "Message-ID" }); + return rawMessage.toString(charset); + } + +} http://git-wip-us.apache.org/repos/asf/james-project/blob/3dddd7c5/mailet/test/src/test/java/org/apache/mailet/base/test/FakeMailTest.java ---------------------------------------------------------------------- diff --git a/mailet/test/src/test/java/org/apache/mailet/base/test/FakeMailTest.java b/mailet/test/src/test/java/org/apache/mailet/base/test/FakeMailTest.java new file mode 100644 index 0000000..fc0c03c --- /dev/null +++ b/mailet/test/src/test/java/org/apache/mailet/base/test/FakeMailTest.java @@ -0,0 +1,41 @@ +/**************************************************************** + * 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.mailet.base.test; + +import static org.mockito.Mockito.mock; + +import javax.mail.internet.MimeMessage; + +import org.junit.Test; + +import nl.jqno.equalsverifier.EqualsVerifier; +import nl.jqno.equalsverifier.Warning; + +public class FakeMailTest { + + @Test + public void beanShouldRespectBeanContract() { + EqualsVerifier.forClass(FakeMail.class) + .suppress(Warning.NONFINAL_FIELDS) + .withPrefabValues(MimeMessage.class, mock(MimeMessage.class), mock(MimeMessage.class)) + .verify(); + } + +} http://git-wip-us.apache.org/repos/asf/james-project/blob/3dddd7c5/mpt/impl/imap-mailbox/core/pom.xml ---------------------------------------------------------------------- diff --git a/mpt/impl/imap-mailbox/core/pom.xml b/mpt/impl/imap-mailbox/core/pom.xml index 83d17d4..b8075ff 100644 --- a/mpt/impl/imap-mailbox/core/pom.xml +++ b/mpt/impl/imap-mailbox/core/pom.xml @@ -85,11 +85,6 @@ <artifactId>protocols-imap</artifactId> </dependency> <dependency> - <groupId>${james.protocols.groupId}</groupId> - <artifactId>protocols-imap</artifactId> - <type>test-jar</type> - </dependency> - <dependency> <groupId>org.jmock</groupId> <artifactId>jmock</artifactId> </dependency> http://git-wip-us.apache.org/repos/asf/james-project/blob/3dddd7c5/pom.xml ---------------------------------------------------------------------- diff --git a/pom.xml b/pom.xml index 0164ebe..669d927 100644 --- a/pom.xml +++ b/pom.xml @@ -982,6 +982,11 @@ </dependency> <dependency> <groupId>${james.groupId}</groupId> + <artifactId>apache-mailet-test</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>${james.groupId}</groupId> <artifactId>apache-mime4j-core</artifactId> <version>${apache-mime4j.version}</version> <exclusions> http://git-wip-us.apache.org/repos/asf/james-project/blob/3dddd7c5/protocols/imap/src/main/java/org/apache/james/imap/decode/ImapRequestStreamLineReader.java ---------------------------------------------------------------------- diff --git a/protocols/imap/src/main/java/org/apache/james/imap/decode/ImapRequestStreamLineReader.java b/protocols/imap/src/main/java/org/apache/james/imap/decode/ImapRequestStreamLineReader.java new file mode 100644 index 0000000..4fedc02 --- /dev/null +++ b/protocols/imap/src/main/java/org/apache/james/imap/decode/ImapRequestStreamLineReader.java @@ -0,0 +1,105 @@ +/**************************************************************** + * 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.imap.decode; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import org.apache.james.imap.api.display.HumanReadableText; +import org.apache.james.protocols.imap.DecodingException; +import org.apache.james.protocols.imap.utils.EolInputStream; +import org.apache.james.protocols.imap.utils.FixedLengthInputStream; + +/** + * {@link ImapRequestLineReader} which use normal IO Streaming + */ +public class ImapRequestStreamLineReader extends ImapRequestLineReader { + private final InputStream input; + + private final OutputStream output; + + public ImapRequestStreamLineReader(InputStream input, OutputStream output) { + this.input = input; + this.output = output; + } + + /** + * Reads the next character in the current line. This method will continue + * to return the same character until the {@link #consume()} method is + * called. + * + * @return The next character TODO: character encoding is variable and + * cannot be determine at the token level; this char is not accurate + * reported; should be an octet + * @throws DecodingException + * If the end-of-stream is reached. + */ + @Override + public char nextChar() throws DecodingException { + if (!nextSeen) { + int next = -1; + + try { + next = input.read(); + } catch (IOException e) { + throw new DecodingException(HumanReadableText.SOCKET_IO_FAILURE, "Error reading from stream.", e); + } + if (next == -1) { + throw new DecodingException(HumanReadableText.ILLEGAL_ARGUMENTS, "Unexpected end of stream."); + } + + nextSeen = true; + nextChar = (char) next; + } + return nextChar; + } + + @Override + public InputStream read(int size, boolean extraCRLF) throws DecodingException { + + // Unset the next char. + nextSeen = false; + nextChar = 0; + FixedLengthInputStream in = new FixedLengthInputStream(input, size); + if (extraCRLF) { + return new EolInputStream(this, in); + } else { + return in; + } + } + + /** + * Sends a server command continuation request '+' back to the client, + * requesting more data to be sent. + */ + @Override + protected void commandContinuationRequest() throws DecodingException { + try { + output.write('+'); + output.write('\r'); + output.write('\n'); + output.flush(); + } catch (IOException e) { + throw new DecodingException(HumanReadableText.SOCKET_IO_FAILURE, "Unexpected exception in sending command continuation request.", e); + } + } + +} http://git-wip-us.apache.org/repos/asf/james-project/blob/3dddd7c5/protocols/imap/src/main/java/org/apache/james/imap/decode/main/ImapRequestStreamHandler.java ---------------------------------------------------------------------- diff --git a/protocols/imap/src/main/java/org/apache/james/imap/decode/main/ImapRequestStreamHandler.java b/protocols/imap/src/main/java/org/apache/james/imap/decode/main/ImapRequestStreamHandler.java new file mode 100644 index 0000000..f720a6a --- /dev/null +++ b/protocols/imap/src/main/java/org/apache/james/imap/decode/main/ImapRequestStreamHandler.java @@ -0,0 +1,129 @@ +/**************************************************************** + * 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.imap.decode.main; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import org.apache.james.imap.api.ImapSessionState; +import org.apache.james.imap.api.process.ImapProcessor; +import org.apache.james.imap.api.process.ImapSession; +import org.apache.james.imap.decode.ImapDecoder; +import org.apache.james.imap.decode.ImapRequestLineReader; +import org.apache.james.imap.decode.ImapRequestStreamLineReader; +import org.apache.james.imap.encode.ImapEncoder; +import org.apache.james.imap.encode.base.ImapResponseComposerImpl; +import org.apache.james.imap.main.AbstractImapRequestHandler; +import org.apache.james.imap.message.request.SystemMessage; +import org.apache.james.protocols.imap.DecodingException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * + */ +public final class ImapRequestStreamHandler extends AbstractImapRequestHandler { + private static final Logger LOGGER = LoggerFactory.getLogger(ImapRequestStreamHandler.class); + + public ImapRequestStreamHandler(ImapDecoder decoder, ImapProcessor processor, ImapEncoder encoder) { + super(decoder, processor, encoder); + } + + /** + * This method parses IMAP commands read off the wire in handleConnection. + * Actual processing of the command (possibly including additional back and + * forth communication with the client) is delegated to one of a number of + * command specific handler methods. The primary purpose of this method is + * to parse the raw command string to determine exactly which handler should + * be called. It returns true if expecting additional commands, false + * otherwise. + * + * @return whether additional commands are expected. + */ + public boolean handleRequest(InputStream input, OutputStream output, ImapSession session) { + final boolean result; + if (isSelectedMailboxDeleted(session)) { + writeSignoff(output, session); + result = false; + } else { + ImapRequestLineReader request = new ImapRequestStreamLineReader(input, output); + + try { + request.nextChar(); + } catch (DecodingException e) { + LOGGER.debug("Unexpected end of line. Cannot handle request: ", e); + abandon(output, session); + return false; + } + + ImapResponseComposerImpl response = new ImapResponseComposerImpl(new OutputStreamImapResponseWriter(output)); + + if (doProcessRequest(request, response, session)) { + + try { + // Consume the rest of the line, throwing away any extras. + // This allows us to clean up after a protocol error. + request.consumeLine(); + } catch (DecodingException e) { + // Cannot clean up. No recovery is therefore possible. + // Abandon connection. + LOGGER.info("Fault during clean up: {}", e.getMessage()); + LOGGER.debug("Abandoning after fault in clean up", e); + abandon(output, session); + return false; + } + + result = !(ImapSessionState.LOGOUT == session.getState()); + } else { + LOGGER.debug("Connection was abandoned after request processing failed."); + result = false; + abandon(output, session); + } + } + return result; + } + + private void writeSignoff(OutputStream output, ImapSession session) { + try { + output.write(MAILBOX_DELETED_SIGNOFF); + } catch (IOException e) { + LOGGER.warn("Failed to write signoff"); + LOGGER.debug("Failed to write signoff:", e); + } + } + + private void abandon(OutputStream out, ImapSession session) { + if (session != null) { + try { + session.logout(); + } catch (Throwable t) { + LOGGER.warn("Session logout failed. Resources may not be correctly recycled."); + } + } + try { + out.write(ABANDON_SIGNOFF); + } catch (Throwable t) { + LOGGER.debug("Failed to write ABANDON_SIGNOFF", t); + } + processor.process(SystemMessage.FORCE_LOGOUT, new SilentResponder(), session); + } + +} http://git-wip-us.apache.org/repos/asf/james-project/blob/3dddd7c5/protocols/imap/src/main/java/org/apache/james/imap/decode/main/OutputStreamImapResponseWriter.java ---------------------------------------------------------------------- diff --git a/protocols/imap/src/main/java/org/apache/james/imap/decode/main/OutputStreamImapResponseWriter.java b/protocols/imap/src/main/java/org/apache/james/imap/decode/main/OutputStreamImapResponseWriter.java new file mode 100644 index 0000000..4ceaad6 --- /dev/null +++ b/protocols/imap/src/main/java/org/apache/james/imap/decode/main/OutputStreamImapResponseWriter.java @@ -0,0 +1,70 @@ +/**************************************************************** + * 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.imap.decode.main; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import org.apache.james.imap.encode.ImapResponseWriter; +import org.apache.james.imap.message.response.Literal; + +/** + * Class providing methods to send response messages from the server to the + * client. + */ +public class OutputStreamImapResponseWriter implements ImapResponseWriter { + + private final OutputStream output; + + public OutputStreamImapResponseWriter(OutputStream output) { + this.output = output; + } + + public void flush() throws IOException { + output.flush(); + } + + + + @Override + public void write(Literal literal) throws IOException { + InputStream in = null; + try { + in = literal.getInputStream(); + + byte[] buffer = new byte[1024]; + for (int len; (len = in.read(buffer)) != -1;) { + output.write(buffer, 0, len); + } + } finally { + if (in != null) { + in.close(); + } + } + + } + + @Override + public void write(byte[] buffer) throws IOException { + output.write(buffer); + } + +} http://git-wip-us.apache.org/repos/asf/james-project/blob/3dddd7c5/protocols/imap/src/main/java/org/apache/james/imap/encode/FakeImapSession.java ---------------------------------------------------------------------- diff --git a/protocols/imap/src/main/java/org/apache/james/imap/encode/FakeImapSession.java b/protocols/imap/src/main/java/org/apache/james/imap/encode/FakeImapSession.java new file mode 100644 index 0000000..bd7d053 --- /dev/null +++ b/protocols/imap/src/main/java/org/apache/james/imap/encode/FakeImapSession.java @@ -0,0 +1,145 @@ +/**************************************************************** + * 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.imap.encode; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.apache.james.imap.api.ImapSessionState; +import org.apache.james.imap.api.process.ImapLineHandler; +import org.apache.james.imap.api.process.ImapSession; +import org.apache.james.imap.api.process.SelectedMailbox; + +public class FakeImapSession implements ImapSession { + + private ImapSessionState state = ImapSessionState.NON_AUTHENTICATED; + + private SelectedMailbox selectedMailbox = null; + + private final Map<String, Object> attributesByKey; + + public FakeImapSession() { + this.attributesByKey = new ConcurrentHashMap<>(); + } + + @Override + public void logout() { + closeMailbox(); + state = ImapSessionState.LOGOUT; + } + + @Override + public void authenticated() { + this.state = ImapSessionState.AUTHENTICATED; + } + + @Override + public void deselect() { + this.state = ImapSessionState.AUTHENTICATED; + closeMailbox(); + } + + @Override + public void selected(SelectedMailbox mailbox) { + this.state = ImapSessionState.SELECTED; + closeMailbox(); + this.selectedMailbox = mailbox; + } + + @Override + public SelectedMailbox getSelected() { + return this.selectedMailbox; + } + + @Override + public ImapSessionState getState() { + return this.state; + } + + public void closeMailbox() { + if (selectedMailbox != null) { + selectedMailbox.deselect(); + selectedMailbox = null; + } + } + + @Override + public Object getAttribute(String key) { + return attributesByKey.get(key); + } + + @Override + public void setAttribute(String key, Object value) { + if (value == null) { + attributesByKey.remove(key); + } else { + attributesByKey.put(key, value); + } + } + + @Override + public boolean startTLS() { + return false; + } + + @Override + public boolean supportStartTLS() { + return false; + } + + @Override + public boolean isCompressionSupported() { + return false; + } + + @Override + public boolean startCompression() { + return false; + } + + @Override + public void pushLineHandler(ImapLineHandler lineHandler) { + } + + @Override + public void popLineHandler() { + + } + + @Override + public boolean isPlainAuthDisallowed() { + return false; + } + + @Override + public boolean isTLSActive() { + return false; + } + + @Override + public boolean supportMultipleNamespaces() { + return false; + } + + @Override + public boolean isCompressionActive() { + return false; + } + +} http://git-wip-us.apache.org/repos/asf/james-project/blob/3dddd7c5/protocols/imap/src/test/java/org/apache/james/imap/decode/ImapRequestStreamLineReader.java ---------------------------------------------------------------------- diff --git a/protocols/imap/src/test/java/org/apache/james/imap/decode/ImapRequestStreamLineReader.java b/protocols/imap/src/test/java/org/apache/james/imap/decode/ImapRequestStreamLineReader.java deleted file mode 100644 index 4fedc02..0000000 --- a/protocols/imap/src/test/java/org/apache/james/imap/decode/ImapRequestStreamLineReader.java +++ /dev/null @@ -1,105 +0,0 @@ -/**************************************************************** - * 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.imap.decode; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -import org.apache.james.imap.api.display.HumanReadableText; -import org.apache.james.protocols.imap.DecodingException; -import org.apache.james.protocols.imap.utils.EolInputStream; -import org.apache.james.protocols.imap.utils.FixedLengthInputStream; - -/** - * {@link ImapRequestLineReader} which use normal IO Streaming - */ -public class ImapRequestStreamLineReader extends ImapRequestLineReader { - private final InputStream input; - - private final OutputStream output; - - public ImapRequestStreamLineReader(InputStream input, OutputStream output) { - this.input = input; - this.output = output; - } - - /** - * Reads the next character in the current line. This method will continue - * to return the same character until the {@link #consume()} method is - * called. - * - * @return The next character TODO: character encoding is variable and - * cannot be determine at the token level; this char is not accurate - * reported; should be an octet - * @throws DecodingException - * If the end-of-stream is reached. - */ - @Override - public char nextChar() throws DecodingException { - if (!nextSeen) { - int next = -1; - - try { - next = input.read(); - } catch (IOException e) { - throw new DecodingException(HumanReadableText.SOCKET_IO_FAILURE, "Error reading from stream.", e); - } - if (next == -1) { - throw new DecodingException(HumanReadableText.ILLEGAL_ARGUMENTS, "Unexpected end of stream."); - } - - nextSeen = true; - nextChar = (char) next; - } - return nextChar; - } - - @Override - public InputStream read(int size, boolean extraCRLF) throws DecodingException { - - // Unset the next char. - nextSeen = false; - nextChar = 0; - FixedLengthInputStream in = new FixedLengthInputStream(input, size); - if (extraCRLF) { - return new EolInputStream(this, in); - } else { - return in; - } - } - - /** - * Sends a server command continuation request '+' back to the client, - * requesting more data to be sent. - */ - @Override - protected void commandContinuationRequest() throws DecodingException { - try { - output.write('+'); - output.write('\r'); - output.write('\n'); - output.flush(); - } catch (IOException e) { - throw new DecodingException(HumanReadableText.SOCKET_IO_FAILURE, "Unexpected exception in sending command continuation request.", e); - } - } - -} http://git-wip-us.apache.org/repos/asf/james-project/blob/3dddd7c5/protocols/imap/src/test/java/org/apache/james/imap/decode/main/ImapRequestStreamHandler.java ---------------------------------------------------------------------- diff --git a/protocols/imap/src/test/java/org/apache/james/imap/decode/main/ImapRequestStreamHandler.java b/protocols/imap/src/test/java/org/apache/james/imap/decode/main/ImapRequestStreamHandler.java deleted file mode 100644 index f720a6a..0000000 --- a/protocols/imap/src/test/java/org/apache/james/imap/decode/main/ImapRequestStreamHandler.java +++ /dev/null @@ -1,129 +0,0 @@ -/**************************************************************** - * 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.imap.decode.main; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -import org.apache.james.imap.api.ImapSessionState; -import org.apache.james.imap.api.process.ImapProcessor; -import org.apache.james.imap.api.process.ImapSession; -import org.apache.james.imap.decode.ImapDecoder; -import org.apache.james.imap.decode.ImapRequestLineReader; -import org.apache.james.imap.decode.ImapRequestStreamLineReader; -import org.apache.james.imap.encode.ImapEncoder; -import org.apache.james.imap.encode.base.ImapResponseComposerImpl; -import org.apache.james.imap.main.AbstractImapRequestHandler; -import org.apache.james.imap.message.request.SystemMessage; -import org.apache.james.protocols.imap.DecodingException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * - */ -public final class ImapRequestStreamHandler extends AbstractImapRequestHandler { - private static final Logger LOGGER = LoggerFactory.getLogger(ImapRequestStreamHandler.class); - - public ImapRequestStreamHandler(ImapDecoder decoder, ImapProcessor processor, ImapEncoder encoder) { - super(decoder, processor, encoder); - } - - /** - * This method parses IMAP commands read off the wire in handleConnection. - * Actual processing of the command (possibly including additional back and - * forth communication with the client) is delegated to one of a number of - * command specific handler methods. The primary purpose of this method is - * to parse the raw command string to determine exactly which handler should - * be called. It returns true if expecting additional commands, false - * otherwise. - * - * @return whether additional commands are expected. - */ - public boolean handleRequest(InputStream input, OutputStream output, ImapSession session) { - final boolean result; - if (isSelectedMailboxDeleted(session)) { - writeSignoff(output, session); - result = false; - } else { - ImapRequestLineReader request = new ImapRequestStreamLineReader(input, output); - - try { - request.nextChar(); - } catch (DecodingException e) { - LOGGER.debug("Unexpected end of line. Cannot handle request: ", e); - abandon(output, session); - return false; - } - - ImapResponseComposerImpl response = new ImapResponseComposerImpl(new OutputStreamImapResponseWriter(output)); - - if (doProcessRequest(request, response, session)) { - - try { - // Consume the rest of the line, throwing away any extras. - // This allows us to clean up after a protocol error. - request.consumeLine(); - } catch (DecodingException e) { - // Cannot clean up. No recovery is therefore possible. - // Abandon connection. - LOGGER.info("Fault during clean up: {}", e.getMessage()); - LOGGER.debug("Abandoning after fault in clean up", e); - abandon(output, session); - return false; - } - - result = !(ImapSessionState.LOGOUT == session.getState()); - } else { - LOGGER.debug("Connection was abandoned after request processing failed."); - result = false; - abandon(output, session); - } - } - return result; - } - - private void writeSignoff(OutputStream output, ImapSession session) { - try { - output.write(MAILBOX_DELETED_SIGNOFF); - } catch (IOException e) { - LOGGER.warn("Failed to write signoff"); - LOGGER.debug("Failed to write signoff:", e); - } - } - - private void abandon(OutputStream out, ImapSession session) { - if (session != null) { - try { - session.logout(); - } catch (Throwable t) { - LOGGER.warn("Session logout failed. Resources may not be correctly recycled."); - } - } - try { - out.write(ABANDON_SIGNOFF); - } catch (Throwable t) { - LOGGER.debug("Failed to write ABANDON_SIGNOFF", t); - } - processor.process(SystemMessage.FORCE_LOGOUT, new SilentResponder(), session); - } - -} http://git-wip-us.apache.org/repos/asf/james-project/blob/3dddd7c5/protocols/imap/src/test/java/org/apache/james/imap/decode/main/OutputStreamImapResponseWriter.java ---------------------------------------------------------------------- diff --git a/protocols/imap/src/test/java/org/apache/james/imap/decode/main/OutputStreamImapResponseWriter.java b/protocols/imap/src/test/java/org/apache/james/imap/decode/main/OutputStreamImapResponseWriter.java deleted file mode 100644 index 4ceaad6..0000000 --- a/protocols/imap/src/test/java/org/apache/james/imap/decode/main/OutputStreamImapResponseWriter.java +++ /dev/null @@ -1,70 +0,0 @@ -/**************************************************************** - * 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.imap.decode.main; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -import org.apache.james.imap.encode.ImapResponseWriter; -import org.apache.james.imap.message.response.Literal; - -/** - * Class providing methods to send response messages from the server to the - * client. - */ -public class OutputStreamImapResponseWriter implements ImapResponseWriter { - - private final OutputStream output; - - public OutputStreamImapResponseWriter(OutputStream output) { - this.output = output; - } - - public void flush() throws IOException { - output.flush(); - } - - - - @Override - public void write(Literal literal) throws IOException { - InputStream in = null; - try { - in = literal.getInputStream(); - - byte[] buffer = new byte[1024]; - for (int len; (len = in.read(buffer)) != -1;) { - output.write(buffer, 0, len); - } - } finally { - if (in != null) { - in.close(); - } - } - - } - - @Override - public void write(byte[] buffer) throws IOException { - output.write(buffer); - } - -} --------------------------------------------------------------------- To unsubscribe, e-mail: server-dev-unsubscr...@james.apache.org For additional commands, e-mail: server-dev-h...@james.apache.org