package com.intrbiz.bergamot.queue.impl; import java.io.IOException; import java.util.UUID; import com.intrbiz.Util; import com.intrbiz.bergamot.io.BergamotTranscoder; import com.intrbiz.bergamot.model.message.check.ExecuteCheck; import com.intrbiz.bergamot.model.message.reading.ReadingParcelMO; import com.intrbiz.bergamot.model.message.result.ResultMO; import com.intrbiz.bergamot.queue.WorkerQueue; import com.intrbiz.bergamot.queue.key.AdhocResultKey; import com.intrbiz.bergamot.queue.key.ReadingKey; import com.intrbiz.bergamot.queue.key.ResultKey; import com.intrbiz.bergamot.queue.key.WorkerKey; import com.intrbiz.gerald.source.IntelligenceSource; import com.intrbiz.gerald.witchcraft.Witchcraft; import com.intrbiz.queue.Consumer; import com.intrbiz.queue.DeliveryHandler; import com.intrbiz.queue.QueueBrokerPool; import com.intrbiz.queue.QueueManager; import com.intrbiz.queue.RoutedProducer; import com.intrbiz.queue.name.NullKey; import com.intrbiz.queue.rabbit.RabbitConsumer; import com.intrbiz.queue.rabbit.RabbitProducer; import com.rabbitmq.client.Channel; public class RabbitWorkerQueue extends WorkerQueue { public static final void register() { QueueManager.getInstance().registerQueueAdapter(WorkerQueue.class, RabbitWorkerQueue::new); } private final BergamotTranscoder transcoder = new BergamotTranscoder(); private final QueueBrokerPool<Channel> broker; private final IntelligenceSource source = Witchcraft.get().source("com.intrbiz.bergamot.queue"); @SuppressWarnings("unchecked") public RabbitWorkerQueue(QueueBrokerPool<?> broker) { this.broker = (QueueBrokerPool<Channel>) broker; } public String getName() { return "worker-queue"; } @Override public RoutedProducer<ExecuteCheck, WorkerKey> publishChecks(WorkerKey defaultKey) { return new RabbitProducer<ExecuteCheck, WorkerKey>(this.broker, this.transcoder.asQueueEventTranscoder(ExecuteCheck.class), defaultKey, this.source.getRegistry().timer("publish-check")) { protected String setupExchange(Channel on) throws IOException { on.exchangeDeclare("bergamot.check", "topic", true, false, args("alternate-exchange", "bergamot.check.site.default")); return "bergamot.check"; } }; } @Override public Consumer<ExecuteCheck, WorkerKey> consumeChecks(DeliveryHandler<ExecuteCheck> handler, UUID site, String theWorkerPool, String engine, boolean agentRouting, UUID workerId) { // validate arguments final String workerPool = Util.isEmpty(theWorkerPool) ? null : theWorkerPool; if (Util.isEmpty(engine)) throw new IllegalArgumentException("Engine name must be given!"); if (agentRouting && workerId == null) throw new IllegalArgumentException("For agent routing an worker id must be given"); // the engine exchange final String engineExchangeName = "bergamot.check.engine." + Util.coalesce(site, "default") + "." + Util.coalesce(workerPool, "any") + "." + engine; // create the consumer return new RabbitConsumer<ExecuteCheck, WorkerKey>(this.broker, this.transcoder.asQueueEventTranscoder(ExecuteCheck.class), handler, this.source.getRegistry().timer("consume-check")) { public String setupQueue(Channel on) throws IOException { // the dead check queue on.queueDeclare("bergamot.dead_check_queue", true, false, false, null); on.exchangeDeclare("bergamot.dead_check", "fanout", true); on.queueBind("bergamot.dead_check_queue", "bergamot.dead_check", ""); // the dead agent check queue on.queueDeclare("bergamot.dead_agent_check_queue", true, false, false, null); on.exchangeDeclare("bergamot.dead_agent_check", "fanout", true); on.queueBind("bergamot.dead_agent_check_queue", "bergamot.dead_agent_check", ""); // setup our worker queue String queueName = null; if (agentRouting) { // for agent routed check, we use a transient queue queueName = "bergamot.check." + Util.coalesce(site, "default") + "." + Util.coalesceEmpty(workerPool, "any") + "." + engine + "." + workerId; on.queueDeclare(queueName, false, true, true, args("x-dead-letter-exchange", "bergamot.dead_check")); } else { // for non agent routed checks we use a shared queue queueName = "bergamot.check." + Util.coalesce(site, "default") + "." + Util.coalesceEmpty(workerPool, "any") + "." + engine; on.queueDeclare(queueName, true, false, false, args("x-dead-letter-exchange", "bergamot.dead_check")); } // common exchanges on.exchangeDeclare("bergamot.check", "topic", true, false, args("alternate-exchange", "bergamot.check.site.default")); // default exchanges on.exchangeDeclare("bergamot.check.site.default", "topic", true, false, args("alternate-exchange", "bergamot.check.worker_pool.default.any")); on.exchangeDeclare("bergamot.check.worker_pool.default.any", "topic", true, false, args("alternate-exchange", "bergamot.dead_check")); // site default exchanges if (site != null) { on.exchangeDeclare("bergamot.check.site." + site, "topic", true, false, args("alternate-exchange", "bergamot.check.worker_pool." + site + ".any")); on.exchangeDeclare("bergamot.check.worker_pool." + site + ".any", "topic", true, false, args("alternate-exchange", "bergamot.dead_check")); // bind the site to check exchange on.exchangeBind("bergamot.check.site." + site, "bergamot.check", site + ".*.*.*"); } // worker pool default exchanges if (workerPool != null) { on.exchangeDeclare("bergamot.check.worker_pool.default." + workerPool, "topic", true, false, args("alternate-exchange", "bergamot.dead_check")); // bind the worker pool to default site exchange on.exchangeBind("bergamot.check.site.default", "bergamot.check.worker_pool.default." + workerPool, "*." + workerPool + ".*.*"); } // specific exchanges if (site != null && workerPool != null) { on.exchangeDeclare("bergamot.check.worker_pool." + site + "." + workerPool, "topic", true, false, args("alternate-exchange", "bergamot.dead_check")); // bind the worker pool exchange to the site exchange on.exchangeBind("bergamot.check.worker_pool." + site + "." + workerPool, "bergamot.check.site." + site, "*." + workerPool + ".*.*"); } // declare the engine exchange // the engine exchange dead checks to the dead agent check exchange on.exchangeDeclare(engineExchangeName, "topic", true, false, args("alternate-exchange", "bergamot.dead_agent_check")); // bind the engine exchange to our worker pool on.exchangeBind(engineExchangeName, "bergamot.check.worker_pool." + Util.coalesce(site, "default") + "." + Util.coalesce(workerPool, "any"), "*.*." + engine + ".*"); // bind our queue to the engine exchange if (! agentRouting) { // for agent routing we bind when an agent connects rather than now on.queueBind(queueName, engineExchangeName, "*.*.*.*"); } return queueName; } @Override protected void addQueueBinding(Channel on, String binding) throws IOException { on.queueBind(this.queue, engineExchangeName, binding); } @Override protected void removeQueueBinding(Channel on, String binding) throws IOException { on.queueUnbind(this.queue, engineExchangeName, binding); } }; } @Override public Consumer<ExecuteCheck, NullKey> consumeDeadChecks(DeliveryHandler<ExecuteCheck> handler) { return new RabbitConsumer<ExecuteCheck, NullKey>(this.broker, this.transcoder.asQueueEventTranscoder(ExecuteCheck.class), handler, this.source.getRegistry().timer("consume-dead-check")) { public String setupQueue(Channel on) throws IOException { on.queueDeclare("bergamot.dead_check_queue", true, false, false, null); on.exchangeDeclare("bergamot.dead_check", "fanout", true); on.queueBind("bergamot.dead_check_queue", "bergamot.dead_check", ""); return "bergamot.dead_check_queue"; } }; } @Override public Consumer<ExecuteCheck, NullKey> consumeDeadAgentChecks(DeliveryHandler<ExecuteCheck> handler) { return new RabbitConsumer<ExecuteCheck, NullKey>(this.broker, this.transcoder.asQueueEventTranscoder(ExecuteCheck.class), handler, this.source.getRegistry().timer("consume-dead-check")) { public String setupQueue(Channel on) throws IOException { on.queueDeclare("bergamot.dead_agent_check_queue", true, false, false, null); on.exchangeDeclare("bergamot.dead_agent_check", "fanout", true); on.queueBind("bergamot.dead_agent_check_queue", "bergamot.dead_agent_check", ""); return "bergamot.dead_agent_check_queue"; } }; } // results @Override public RoutedProducer<ResultMO, ResultKey> publishResults(ResultKey defaultKey) { return new RabbitProducer<ResultMO, ResultKey>(this.broker, this.transcoder.asQueueEventTranscoder(ResultMO.class), defaultKey, this.source.getRegistry().timer("publish-result")) { protected String setupExchange(Channel on) throws IOException { // the fallback queue on.queueDeclare("bergamot.result.fallback_queue", true, false, false, null); on.exchangeDeclare("bergamot.result.fallback", "fanout", true, false, null); on.queueBind("bergamot.result.fallback_queue", "bergamot.result.fallback", ""); // the result exchange on.exchangeDeclare("bergamot.result", "topic", true, false, args("alternate-exchange", "bergamot.result.fallback")); return "bergamot.result"; } }; } @Override public Consumer<ResultMO, ResultKey> consumeResults(DeliveryHandler<ResultMO> handler, String instance) { return new RabbitConsumer<ResultMO, ResultKey>(this.broker, this.transcoder.asQueueEventTranscoder(ResultMO.class), handler, this.source.getRegistry().timer("consume-result")) { public String setupQueue(Channel on) throws IOException { // the fallback queue on.queueDeclare("bergamot.result.fallback_queue", true, false, false, null); on.exchangeDeclare("bergamot.result.fallback", "fanout", true, false, null); on.queueBind("bergamot.result.fallback_queue", "bergamot.result.fallback", ""); // Use a transient queue since // processing duties could be moved at any time String queueName = "bergamot.result.processor." + instance; on.queueDeclare(queueName, false, true, true, null); // the result exchange on.exchangeDeclare("bergamot.result", "topic", true, false, args("alternate-exchange", "bergamot.result.fallback")); return queueName; } @Override protected void addQueueBinding(Channel on, String binding) throws IOException { on.queueBind(this.queue, "bergamot.result", binding); } @Override protected void removeQueueBinding(Channel on, String binding) throws IOException { // seems odd, but unbind is non-idempotent, binding then unbinding is a poor workaround on.queueBind(this.queue, "bergamot.result", binding); on.queueUnbind(this.queue, "bergamot.result", binding); } }; } @Override public Consumer<ResultMO, ResultKey> consumeFallbackResults(DeliveryHandler<ResultMO> handler) { return new RabbitConsumer<ResultMO, ResultKey>(this.broker, this.transcoder.asQueueEventTranscoder(ResultMO.class), handler, this.source.getRegistry().timer("consume-fallback-result")) { public String setupQueue(Channel on) throws IOException { on.queueDeclare("bergamot.result.fallback_queue", true, false, false, null); on.exchangeDeclare("bergamot.result.fallback", "fanout", true, false, null); on.queueBind("bergamot.result.fallback_queue", "bergamot.result.fallback", ""); return "bergamot.result.fallback_queue"; } }; } // readings @Override public RoutedProducer<ReadingParcelMO, ReadingKey> publishReadings(ReadingKey defaultKey) { return new RabbitProducer<ReadingParcelMO, ReadingKey>(this.broker, this.transcoder.asQueueEventTranscoder(ReadingParcelMO.class), defaultKey, this.source.getRegistry().timer("publish-reading")) { protected String setupExchange(Channel on) throws IOException { // the fallback queue on.queueDeclare("bergamot.reading.fallback_queue", true, false, false, null); on.exchangeDeclare("bergamot.reading.fallback", "fanout", true, false, null); on.queueBind("bergamot.reading.fallback_queue", "bergamot.reading.fallback", ""); // the result exchange on.exchangeDeclare("bergamot.reading", "topic", true, false, args("alternate-exchange", "bergamot.reading.fallback")); return "bergamot.reading"; } }; } @Override public Consumer<ReadingParcelMO, ReadingKey> consumeReadings(DeliveryHandler<ReadingParcelMO> handler, String instance) { return new RabbitConsumer<ReadingParcelMO, ReadingKey>(this.broker, this.transcoder.asQueueEventTranscoder(ReadingParcelMO.class), handler, this.source.getRegistry().timer("consume-reading")) { public String setupQueue(Channel on) throws IOException { // the fallback queue on.queueDeclare("bergamot.reading.fallback_queue", true, false, false, null); on.exchangeDeclare("bergamot.reading.fallback", "fanout", true, false, null); on.queueBind("bergamot.reading.fallback_queue", "bergamot.reading.fallback", ""); // Use a transient queue since // processing duties could be moved at any time String queueName = "bergamot.reading.processor." + instance; on.queueDeclare(queueName, false, true, true, null); // the result exchange on.exchangeDeclare("bergamot.reading", "topic", true, false, args("alternate-exchange", "bergamot.reading.fallback")); return queueName; } @Override protected void addQueueBinding(Channel on, String binding) throws IOException { on.queueBind(this.queue, "bergamot.reading", binding); } @Override protected void removeQueueBinding(Channel on, String binding) throws IOException { // seems odd, but unbind is non-idempotent, binding then unbinding is a poor workaround on.queueBind(this.queue, "bergamot.reading", binding); on.queueUnbind(this.queue, "bergamot.reading", binding); } }; } @Override public Consumer<ReadingParcelMO, ReadingKey> consumeFallbackReadings(DeliveryHandler<ReadingParcelMO> handler) { return new RabbitConsumer<ReadingParcelMO, ReadingKey>(this.broker, this.transcoder.asQueueEventTranscoder(ReadingParcelMO.class), handler, this.source.getRegistry().timer("consume-fallback-reading")) { public String setupQueue(Channel on) throws IOException { on.queueDeclare("bergamot.reading.fallback_queue", true, false, false, null); on.exchangeDeclare("bergamot.reading.fallback", "fanout", true, false, null); on.queueBind("bergamot.reading.fallback_queue", "bergamot.reading.fallback", ""); return "bergamot.reading.fallback_queue"; } }; } // adhoc @Override public RoutedProducer<ResultMO, AdhocResultKey> publishAdhocResults() { return new RabbitProducer<ResultMO, AdhocResultKey>(this.broker, this.transcoder.asQueueEventTranscoder(ResultMO.class), null, this.source.getRegistry().timer("publish-result")) { protected String setupExchange(Channel on) throws IOException { // the result exchange on.exchangeDeclare("bergamot.adhocresult", "topic", true, false, null); return "bergamot.adhocresult"; } }; } @Override public Consumer<ResultMO, AdhocResultKey> consumeAdhocResults(UUID adhocId, DeliveryHandler<ResultMO> handler) { return new RabbitConsumer<ResultMO, AdhocResultKey>(this.broker, this.transcoder.asQueueEventTranscoder(ResultMO.class), handler, this.source.getRegistry().timer("consume-result")) { public String setupQueue(Channel on) throws IOException { String queueName = on.queueDeclare().getQueue(); // the result exchange on.exchangeDeclare("bergamot.adhocresult", "topic", true, false, null); // bind on.queueBind(queueName, "bergamot.adhocresult", new AdhocResultKey(adhocId).toString()); return queueName; } }; } @Override public void close() { } }