Java tutorial
/**************************************************************** * 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.queue.rabbitmq; import static org.apache.james.queue.api.MailQueue.DEQUEUED_METRIC_NAME_PREFIX; import java.io.IOException; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; import java.util.function.Consumer; import java.util.function.Function; import org.apache.james.metrics.api.Metric; import org.apache.james.metrics.api.MetricFactory; import org.apache.james.queue.api.MailQueue; import org.apache.james.queue.rabbitmq.view.api.DeleteCondition; import org.apache.james.queue.rabbitmq.view.api.MailQueueView; import org.apache.james.util.concurrent.NamedThreadFactory; import org.apache.mailet.Mail; import com.github.fge.lambdas.Throwing; import com.github.fge.lambdas.consumers.ThrowingConsumer; import com.nurkiewicz.asyncretry.AsyncRetryExecutor; import com.rabbitmq.client.GetResponse; class Dequeuer { private static class NoMailYetException extends RuntimeException { } private static class RabbitMQMailQueueItem implements MailQueue.MailQueueItem { private final Consumer<Boolean> ack; private final Mail mail; private RabbitMQMailQueueItem(Consumer<Boolean> ack, Mail mail) { this.ack = ack; this.mail = mail; } @Override public Mail getMail() { return mail; } @Override public void done(boolean success) { ack.accept(success); } } private static final int TEN_MS = 10; private final MailQueueName name; private final RabbitClient rabbitClient; private final Function<MailReferenceDTO, Mail> mailLoader; private final Metric dequeueMetric; private final MailReferenceSerializer mailReferenceSerializer; private final MailQueueView mailQueueView; Dequeuer(MailQueueName name, RabbitClient rabbitClient, Function<MailReferenceDTO, Mail> mailLoader, MailReferenceSerializer serializer, MetricFactory metricFactory, MailQueueView mailQueueView) { this.name = name; this.rabbitClient = rabbitClient; this.mailLoader = mailLoader; this.mailReferenceSerializer = serializer; this.mailQueueView = mailQueueView; this.dequeueMetric = metricFactory.generate(DEQUEUED_METRIC_NAME_PREFIX + name.asString()); } MailQueue.MailQueueItem deQueue() { return pollChannel().thenApply(Throwing.function(this::loadItem).sneakyThrow()).join(); } private RabbitMQMailQueueItem loadItem(GetResponse response) throws MailQueue.MailQueueException { Mail mail = loadMail(response); ThrowingConsumer<Boolean> ack = ack(response.getEnvelope().getDeliveryTag(), mail); return new RabbitMQMailQueueItem(ack, mail); } private ThrowingConsumer<Boolean> ack(long deliveryTag, Mail mail) { return success -> { try { if (success) { dequeueMetric.increment(); rabbitClient.ack(deliveryTag); mailQueueView.delete(DeleteCondition.withName(mail.getName())); } else { rabbitClient.nack(deliveryTag); } } catch (IOException e) { throw new MailQueue.MailQueueException( "Failed to ACK " + mail.getName() + " with delivery tag " + deliveryTag, e); } }; } private Mail loadMail(GetResponse response) throws MailQueue.MailQueueException { MailReferenceDTO mailDTO = toMailReference(response); return mailLoader.apply(mailDTO); } private MailReferenceDTO toMailReference(GetResponse getResponse) throws MailQueue.MailQueueException { try { return mailReferenceSerializer.read(getResponse.getBody()); } catch (IOException e) { throw new MailQueue.MailQueueException("Failed to parse DTO", e); } } private CompletableFuture<GetResponse> pollChannel() { ThreadFactory threadFactory = NamedThreadFactory.withClassName(getClass()); return new AsyncRetryExecutor(Executors.newSingleThreadScheduledExecutor(threadFactory)).withFixedRate() .withMinDelay(TEN_MS).retryOn(NoMailYetException.class).getWithRetry(this::singleChannelRead); } private GetResponse singleChannelRead() throws IOException { return rabbitClient.poll(name).filter(getResponse -> getResponse.getBody() != null) .orElseThrow(NoMailYetException::new); } }