001/** 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.camel.component.seda; 018 019import java.util.concurrent.BlockingQueue; 020import java.util.concurrent.CountDownLatch; 021import java.util.concurrent.TimeUnit; 022import org.apache.camel.AsyncCallback; 023import org.apache.camel.Exchange; 024import org.apache.camel.ExchangeTimedOutException; 025import org.apache.camel.WaitForTaskToComplete; 026import org.apache.camel.impl.DefaultAsyncProducer; 027import org.apache.camel.support.SynchronizationAdapter; 028import org.apache.camel.util.ExchangeHelper; 029 030/** 031 * @version 032 */ 033public class SedaProducer extends DefaultAsyncProducer { 034 035 /** 036 * @deprecated Better make use of the {@link SedaEndpoint#getQueue()} API which delivers the accurate reference to the queue currently being used. 037 */ 038 @Deprecated 039 protected final BlockingQueue<Exchange> queue; 040 private final SedaEndpoint endpoint; 041 private final WaitForTaskToComplete waitForTaskToComplete; 042 private final long timeout; 043 private final boolean blockWhenFull; 044 045 /** 046 * @deprecated Use {@link #SedaProducer(SedaEndpoint, WaitForTaskToComplete, long, boolean) the other constructor}. 047 */ 048 @Deprecated 049 public SedaProducer(SedaEndpoint endpoint, BlockingQueue<Exchange> queue, WaitForTaskToComplete waitForTaskToComplete, long timeout) { 050 this(endpoint, waitForTaskToComplete, timeout, false); 051 } 052 053 /** 054 * @deprecated Use {@link #SedaProducer(SedaEndpoint, WaitForTaskToComplete, long, boolean) the other constructor}. 055 */ 056 @Deprecated 057 public SedaProducer(SedaEndpoint endpoint, BlockingQueue<Exchange> queue, WaitForTaskToComplete waitForTaskToComplete, long timeout, boolean blockWhenFull) { 058 this(endpoint, waitForTaskToComplete, timeout, blockWhenFull); 059 } 060 061 public SedaProducer(SedaEndpoint endpoint, WaitForTaskToComplete waitForTaskToComplete, long timeout, boolean blockWhenFull) { 062 super(endpoint); 063 this.queue = endpoint.getQueue(); 064 this.endpoint = endpoint; 065 this.waitForTaskToComplete = waitForTaskToComplete; 066 this.timeout = timeout; 067 this.blockWhenFull = blockWhenFull; 068 } 069 070 @Override 071 public boolean process(final Exchange exchange, final AsyncCallback callback) { 072 WaitForTaskToComplete wait = waitForTaskToComplete; 073 if (exchange.getProperty(Exchange.ASYNC_WAIT) != null) { 074 wait = exchange.getProperty(Exchange.ASYNC_WAIT, WaitForTaskToComplete.class); 075 } 076 077 if (wait == WaitForTaskToComplete.Always 078 || (wait == WaitForTaskToComplete.IfReplyExpected && ExchangeHelper.isOutCapable(exchange))) { 079 080 // do not handover the completion as we wait for the copy to complete, and copy its result back when it done 081 Exchange copy = prepareCopy(exchange, false); 082 083 // latch that waits until we are complete 084 final CountDownLatch latch = new CountDownLatch(1); 085 086 // we should wait for the reply so install a on completion so we know when its complete 087 copy.addOnCompletion(new SynchronizationAdapter() { 088 @Override 089 public void onDone(Exchange response) { 090 // check for timeout, which then already would have invoked the latch 091 if (latch.getCount() == 0) { 092 if (log.isTraceEnabled()) { 093 log.trace("{}. Timeout occurred so response will be ignored: {}", this, response.hasOut() ? response.getOut() : response.getIn()); 094 } 095 return; 096 } else { 097 if (log.isTraceEnabled()) { 098 log.trace("{} with response: {}", this, response.hasOut() ? response.getOut() : response.getIn()); 099 } 100 try { 101 ExchangeHelper.copyResults(exchange, response); 102 } finally { 103 // always ensure latch is triggered 104 latch.countDown(); 105 } 106 } 107 } 108 109 @Override 110 public boolean allowHandover() { 111 // do not allow handover as we want to seda producer to have its completion triggered 112 // at this point in the routing (at this leg), instead of at the very last (this ensure timeout is honored) 113 return false; 114 } 115 116 @Override 117 public String toString() { 118 return "onDone at endpoint: " + endpoint; 119 } 120 }); 121 122 log.trace("Adding Exchange to queue: {}", copy); 123 try { 124 // do not copy as we already did the copy 125 addToQueue(copy, false); 126 } catch (SedaConsumerNotAvailableException e) { 127 exchange.setException(e); 128 callback.done(true); 129 return true; 130 } 131 132 if (timeout > 0) { 133 if (log.isTraceEnabled()) { 134 log.trace("Waiting for task to complete using timeout (ms): {} at [{}]", timeout, endpoint.getEndpointUri()); 135 } 136 // lets see if we can get the task done before the timeout 137 boolean done = false; 138 try { 139 done = latch.await(timeout, TimeUnit.MILLISECONDS); 140 } catch (InterruptedException e) { 141 // ignore 142 } 143 if (!done) { 144 exchange.setException(new ExchangeTimedOutException(exchange, timeout)); 145 // remove timed out Exchange from queue 146 endpoint.getQueue().remove(copy); 147 // count down to indicate timeout 148 latch.countDown(); 149 } 150 } else { 151 if (log.isTraceEnabled()) { 152 log.trace("Waiting for task to complete (blocking) at [{}]", endpoint.getEndpointUri()); 153 } 154 // no timeout then wait until its done 155 try { 156 latch.await(); 157 } catch (InterruptedException e) { 158 // ignore 159 } 160 } 161 } else { 162 // no wait, eg its a InOnly then just add to queue and return 163 try { 164 addToQueue(exchange, true); 165 } catch (SedaConsumerNotAvailableException e) { 166 exchange.setException(e); 167 callback.done(true); 168 return true; 169 } 170 } 171 172 // we use OnCompletion on the Exchange to callback and wait for the Exchange to be done 173 // so we should just signal the callback we are done synchronously 174 callback.done(true); 175 return true; 176 } 177 178 protected Exchange prepareCopy(Exchange exchange, boolean handover) { 179 // use a new copy of the exchange to route async (and use same message id) 180 181 // if handover we need to do special handover to avoid handing over 182 // RestBindingMarshalOnCompletion as it should not be handed over with SEDA 183 Exchange copy = ExchangeHelper.createCorrelatedCopy(exchange, handover, true, 184 synchronization -> !synchronization.getClass().getName().contains("RestBindingMarshalOnCompletion")); 185 // set a new from endpoint to be the seda queue 186 copy.setFromEndpoint(endpoint); 187 return copy; 188 } 189 190 @Override 191 protected void doStart() throws Exception { 192 super.doStart(); 193 endpoint.onStarted(this); 194 } 195 196 @Override 197 protected void doStop() throws Exception { 198 endpoint.onStopped(this); 199 super.doStop(); 200 } 201 202 /** 203 * Strategy method for adding the exchange to the queue. 204 * <p> 205 * Will perform a blocking "put" if blockWhenFull is true, otherwise it will 206 * simply add which will throw exception if the queue is full 207 * 208 * @param exchange the exchange to add to the queue 209 * @param copy whether to create a copy of the exchange to use for adding to the queue 210 */ 211 protected void addToQueue(Exchange exchange, boolean copy) throws SedaConsumerNotAvailableException { 212 BlockingQueue<Exchange> queue = null; 213 QueueReference queueReference = endpoint.getQueueReference(); 214 if (queueReference != null) { 215 queue = queueReference.getQueue(); 216 } 217 if (queue == null) { 218 throw new SedaConsumerNotAvailableException("No queue available on endpoint: " + endpoint, exchange); 219 } 220 221 boolean empty = !queueReference.hasConsumers(); 222 if (empty) { 223 if (endpoint.isFailIfNoConsumers()) { 224 throw new SedaConsumerNotAvailableException("No consumers available on endpoint: " + endpoint, exchange); 225 } else if (endpoint.isDiscardIfNoConsumers()) { 226 log.debug("Discard message as no active consumers on endpoint: " + endpoint); 227 return; 228 } 229 } 230 231 Exchange target = exchange; 232 233 // handover the completion so its the copy which performs that, as we do not wait 234 if (copy) { 235 target = prepareCopy(exchange, true); 236 } 237 238 log.trace("Adding Exchange to queue: {}", target); 239 if (blockWhenFull) { 240 try { 241 queue.put(target); 242 } catch (InterruptedException e) { 243 // ignore 244 log.debug("Put interrupted, are we stopping? {}", isStopping() || isStopped()); 245 } 246 } else { 247 queue.add(target); 248 } 249 } 250 251}