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 private final long offerTimeout; 045 046 /** 047 * @deprecated Use {@link #SedaProducer(SedaEndpoint, WaitForTaskToComplete, long, boolean) the other constructor}. 048 */ 049 @Deprecated 050 public SedaProducer(SedaEndpoint endpoint, BlockingQueue<Exchange> queue, WaitForTaskToComplete waitForTaskToComplete, long timeout) { 051 this(endpoint, waitForTaskToComplete, timeout, false, 0); 052 } 053 054 /** 055 * @deprecated Use {@link #SedaProducer(SedaEndpoint, WaitForTaskToComplete, long, boolean) the other constructor}. 056 */ 057 @Deprecated 058 public SedaProducer(SedaEndpoint endpoint, BlockingQueue<Exchange> queue, WaitForTaskToComplete waitForTaskToComplete, long timeout, boolean blockWhenFull, long offerTimeout) { 059 this(endpoint, waitForTaskToComplete, timeout, blockWhenFull, offerTimeout); 060 } 061 062 public SedaProducer(SedaEndpoint endpoint, WaitForTaskToComplete waitForTaskToComplete, long timeout, boolean blockWhenFull, long offerTimeout) { 063 super(endpoint); 064 this.queue = endpoint.getQueue(); 065 this.endpoint = endpoint; 066 this.waitForTaskToComplete = waitForTaskToComplete; 067 this.timeout = timeout; 068 this.blockWhenFull = blockWhenFull; 069 this.offerTimeout = offerTimeout; 070 } 071 072 @Override 073 public boolean process(final Exchange exchange, final AsyncCallback callback) { 074 WaitForTaskToComplete wait = waitForTaskToComplete; 075 if (exchange.getProperty(Exchange.ASYNC_WAIT) != null) { 076 wait = exchange.getProperty(Exchange.ASYNC_WAIT, WaitForTaskToComplete.class); 077 } 078 079 if (wait == WaitForTaskToComplete.Always 080 || (wait == WaitForTaskToComplete.IfReplyExpected && ExchangeHelper.isOutCapable(exchange))) { 081 082 // do not handover the completion as we wait for the copy to complete, and copy its result back when it done 083 Exchange copy = prepareCopy(exchange, false); 084 085 // latch that waits until we are complete 086 final CountDownLatch latch = new CountDownLatch(1); 087 088 // we should wait for the reply so install a on completion so we know when its complete 089 copy.addOnCompletion(new SynchronizationAdapter() { 090 @Override 091 public void onDone(Exchange response) { 092 // check for timeout, which then already would have invoked the latch 093 if (latch.getCount() == 0) { 094 if (log.isTraceEnabled()) { 095 log.trace("{}. Timeout occurred so response will be ignored: {}", this, response.hasOut() ? response.getOut() : response.getIn()); 096 } 097 return; 098 } else { 099 if (log.isTraceEnabled()) { 100 log.trace("{} with response: {}", this, response.hasOut() ? response.getOut() : response.getIn()); 101 } 102 try { 103 ExchangeHelper.copyResults(exchange, response); 104 } finally { 105 // always ensure latch is triggered 106 latch.countDown(); 107 } 108 } 109 } 110 111 @Override 112 public boolean allowHandover() { 113 // do not allow handover as we want to seda producer to have its completion triggered 114 // at this point in the routing (at this leg), instead of at the very last (this ensure timeout is honored) 115 return false; 116 } 117 118 @Override 119 public String toString() { 120 return "onDone at endpoint: " + endpoint; 121 } 122 }); 123 124 log.trace("Adding Exchange to queue: {}", copy); 125 try { 126 // do not copy as we already did the copy 127 addToQueue(copy, false); 128 } catch (SedaConsumerNotAvailableException e) { 129 exchange.setException(e); 130 callback.done(true); 131 return true; 132 } 133 134 if (timeout > 0) { 135 if (log.isTraceEnabled()) { 136 log.trace("Waiting for task to complete using timeout (ms): {} at [{}]", timeout, endpoint.getEndpointUri()); 137 } 138 // lets see if we can get the task done before the timeout 139 boolean done = false; 140 try { 141 done = latch.await(timeout, TimeUnit.MILLISECONDS); 142 } catch (InterruptedException e) { 143 // ignore 144 } 145 if (!done) { 146 exchange.setException(new ExchangeTimedOutException(exchange, timeout)); 147 // remove timed out Exchange from queue 148 endpoint.getQueue().remove(copy); 149 // count down to indicate timeout 150 latch.countDown(); 151 } 152 } else { 153 if (log.isTraceEnabled()) { 154 log.trace("Waiting for task to complete (blocking) at [{}]", endpoint.getEndpointUri()); 155 } 156 // no timeout then wait until its done 157 try { 158 latch.await(); 159 } catch (InterruptedException e) { 160 // ignore 161 } 162 } 163 } else { 164 // no wait, eg its a InOnly then just add to queue and return 165 try { 166 addToQueue(exchange, true); 167 } catch (SedaConsumerNotAvailableException e) { 168 exchange.setException(e); 169 callback.done(true); 170 return true; 171 } 172 } 173 174 // we use OnCompletion on the Exchange to callback and wait for the Exchange to be done 175 // so we should just signal the callback we are done synchronously 176 callback.done(true); 177 return true; 178 } 179 180 protected Exchange prepareCopy(Exchange exchange, boolean handover) { 181 // use a new copy of the exchange to route async (and use same message id) 182 183 // if handover we need to do special handover to avoid handing over 184 // RestBindingMarshalOnCompletion as it should not be handed over with SEDA 185 Exchange copy = ExchangeHelper.createCorrelatedCopy(exchange, handover, true, 186 synchronization -> !synchronization.getClass().getName().contains("RestBindingMarshalOnCompletion")); 187 // set a new from endpoint to be the seda queue 188 copy.setFromEndpoint(endpoint); 189 return copy; 190 } 191 192 @Override 193 protected void doStart() throws Exception { 194 super.doStart(); 195 endpoint.onStarted(this); 196 } 197 198 @Override 199 protected void doStop() throws Exception { 200 endpoint.onStopped(this); 201 super.doStop(); 202 } 203 204 /** 205 * Strategy method for adding the exchange to the queue. 206 * <p> 207 * Will perform a blocking "put" if blockWhenFull is true, otherwise it will 208 * simply add which will throw exception if the queue is full 209 * 210 * @param exchange the exchange to add to the queue 211 * @param copy whether to create a copy of the exchange to use for adding to the queue 212 */ 213 protected void addToQueue(Exchange exchange, boolean copy) throws SedaConsumerNotAvailableException { 214 boolean offerTime; 215 BlockingQueue<Exchange> queue = null; 216 QueueReference queueReference = endpoint.getQueueReference(); 217 if (queueReference != null) { 218 queue = queueReference.getQueue(); 219 } 220 if (queue == null) { 221 throw new SedaConsumerNotAvailableException("No queue available on endpoint: " + endpoint, exchange); 222 } 223 224 boolean empty = !queueReference.hasConsumers(); 225 if (empty) { 226 if (endpoint.isFailIfNoConsumers()) { 227 throw new SedaConsumerNotAvailableException("No consumers available on endpoint: " + endpoint, exchange); 228 } else if (endpoint.isDiscardIfNoConsumers()) { 229 log.debug("Discard message as no active consumers on endpoint: {}", endpoint); 230 return; 231 } 232 } 233 234 Exchange target = exchange; 235 236 // handover the completion so its the copy which performs that, as we do not wait 237 if (copy) { 238 target = prepareCopy(exchange, true); 239 } 240 241 log.trace("Adding Exchange to queue: {}", target); 242 if (blockWhenFull && offerTimeout == 0) { 243 try { 244 queue.put(target); 245 } catch (InterruptedException e) { 246 // ignore 247 log.debug("Put interrupted, are we stopping? {}", isStopping() || isStopped()); 248 } 249 } else if (blockWhenFull && offerTimeout > 0) { 250 try { 251 offerTime = queue.offer(target, offerTimeout, TimeUnit.MILLISECONDS); 252 if (!offerTime) { 253 throw new IllegalStateException("Fails to insert element into queue, " 254 + "after timeout of" + offerTimeout + "milliseconds"); 255 } 256 } catch (InterruptedException e) { 257 // ignore 258 log.debug("Offer interrupted, are we stopping? {}", isStopping() || isStopped()); 259 } 260 } else { 261 queue.add(target); 262 } 263 } 264 265}