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.model;
018
019import java.util.ArrayList;
020import java.util.Arrays;
021import java.util.List;
022
023import javax.xml.bind.annotation.XmlAccessType;
024import javax.xml.bind.annotation.XmlAccessorType;
025import javax.xml.bind.annotation.XmlElement;
026import javax.xml.bind.annotation.XmlElementRef;
027import javax.xml.bind.annotation.XmlElements;
028import javax.xml.bind.annotation.XmlRootElement;
029
030import org.apache.camel.Expression;
031import org.apache.camel.Processor;
032import org.apache.camel.model.loadbalancer.CircuitBreakerLoadBalancerDefinition;
033import org.apache.camel.model.loadbalancer.CustomLoadBalancerDefinition;
034import org.apache.camel.model.loadbalancer.FailoverLoadBalancerDefinition;
035import org.apache.camel.model.loadbalancer.RandomLoadBalancerDefinition;
036import org.apache.camel.model.loadbalancer.RoundRobinLoadBalancerDefinition;
037import org.apache.camel.model.loadbalancer.StickyLoadBalancerDefinition;
038import org.apache.camel.model.loadbalancer.TopicLoadBalancerDefinition;
039import org.apache.camel.model.loadbalancer.WeightedLoadBalancerDefinition;
040import org.apache.camel.processor.loadbalancer.LoadBalancer;
041import org.apache.camel.spi.Metadata;
042import org.apache.camel.spi.RouteContext;
043import org.apache.camel.util.CollectionStringBuffer;
044
045/**
046 * Balances message processing among a number of nodes
047 */
048@Metadata(label = "eip,routing")
049@XmlRootElement(name = "loadBalance")
050@XmlAccessorType(XmlAccessType.FIELD)
051public class LoadBalanceDefinition extends ProcessorDefinition<LoadBalanceDefinition> {
052    @XmlElements({
053            @XmlElement(required = false, name = "failover", type = FailoverLoadBalancerDefinition.class),
054            @XmlElement(required = false, name = "random", type = RandomLoadBalancerDefinition.class),
055            // TODO: Camel 3.0 - Should be named customLoadBalancer to avoid naming clash with custom dataformat
056            @XmlElement(required = false, name = "custom", type = CustomLoadBalancerDefinition.class),
057            @XmlElement(required = false, name = "roundRobin", type = RoundRobinLoadBalancerDefinition.class),
058            @XmlElement(required = false, name = "sticky", type = StickyLoadBalancerDefinition.class),
059            @XmlElement(required = false, name = "topic", type = TopicLoadBalancerDefinition.class),
060            @XmlElement(required = false, name = "weighted", type = WeightedLoadBalancerDefinition.class),
061            @XmlElement(required = false, name = "circuitBreaker", type = CircuitBreakerLoadBalancerDefinition.class)}
062        )
063    private LoadBalancerDefinition loadBalancerType;
064    @XmlElementRef
065    private List<ProcessorDefinition<?>> outputs = new ArrayList<>();
066
067    public LoadBalanceDefinition() {
068    }
069
070    @Override
071    public List<ProcessorDefinition<?>> getOutputs() {
072        return outputs;
073    }
074
075    public void setOutputs(List<ProcessorDefinition<?>> outputs) {
076        this.outputs = outputs;
077        if (outputs != null) {
078            for (ProcessorDefinition<?> output : outputs) {
079                configureChild(output);
080            }
081        }
082    }
083
084    public boolean isOutputSupported() {
085        return true;
086    }
087
088    public LoadBalancerDefinition getLoadBalancerType() {
089        return loadBalancerType;
090    }
091
092    /**
093     * The load balancer to be used
094     */
095    public void setLoadBalancerType(LoadBalancerDefinition loadbalancer) {
096        if (loadBalancerType != null) {
097            throw new IllegalArgumentException("Loadbalancer already configured to: " + loadBalancerType + ". Cannot set it to: " + loadbalancer);
098        }
099        loadBalancerType = loadbalancer;
100    }
101
102    @Override
103    public Processor createProcessor(RouteContext routeContext) throws Exception {
104        // the load balancer is stateful so we should only create it once in case its used from a context scoped error handler
105
106        LoadBalancer loadBalancer = loadBalancerType.getLoadBalancer(routeContext);
107        if (loadBalancer == null) {
108            // then create it and reuse it
109            loadBalancer = loadBalancerType.createLoadBalancer(routeContext);
110            loadBalancerType.setLoadBalancer(loadBalancer);
111
112            // some load balancer can only support a fixed number of outputs
113            int max = loadBalancerType.getMaximumNumberOfOutputs();
114            int size = getOutputs().size();
115            if (size > max) {
116                throw new IllegalArgumentException("To many outputs configured on " + loadBalancerType + ": " + size + " > " + max);
117            }
118
119            for (ProcessorDefinition<?> processorType : getOutputs()) {
120                // output must not be another load balancer
121                // check for instanceof as the code below as there is compilation errors on earlier versions of JDK6
122                // on Windows boxes or with IBM JDKs etc.
123                if (LoadBalanceDefinition.class.isInstance(processorType)) {
124                    throw new IllegalArgumentException("Loadbalancer already configured to: " + loadBalancerType + ". Cannot set it to: " + processorType);
125                }
126                Processor processor = createProcessor(routeContext, processorType);
127                processor = wrapChannel(routeContext, processor, processorType);
128                loadBalancer.addProcessor(processor);
129            }
130        }
131
132        Boolean inherit = inheritErrorHandler;
133        if (loadBalancerType instanceof FailoverLoadBalancerDefinition) {
134            // special for failover load balancer where you can configure it to not inherit error handler for its children
135            // but the load balancer itself should inherit so Camels error handler can react afterwards
136            inherit = true;
137        }
138        Processor target = wrapChannel(routeContext, loadBalancer, this, inherit);
139        return target;
140    }
141    
142    // Fluent API
143    // -------------------------------------------------------------------------
144
145    /**
146     * Uses a custom load balancer
147     *
148     * @param loadBalancer  the load balancer
149     * @return the builder
150     */
151    public LoadBalanceDefinition loadBalance(LoadBalancer loadBalancer) {
152        CustomLoadBalancerDefinition def = new CustomLoadBalancerDefinition();
153        def.setLoadBalancer(loadBalancer);
154        setLoadBalancerType(def);
155        return this;
156    }
157    
158    /**
159     * Uses fail over load balancer
160     * <p/>
161     * Will not round robin and inherit the error handler.
162     *
163     * @return the builder
164     */
165    public LoadBalanceDefinition failover() {
166        return failover(-1, true, false);
167    }
168    
169    /**
170     * Uses fail over load balancer
171     * <p/>
172     * Will not round robin and inherit the error handler.
173     *
174     * @param exceptions exception classes which we want to failover if one of them was thrown
175     * @return the builder
176     */
177    public LoadBalanceDefinition failover(Class<?>... exceptions) {
178        return failover(-1, true, false, exceptions);
179    }
180
181    /**
182     * Uses fail over load balancer
183     *
184     * @param maximumFailoverAttempts  maximum number of failover attempts before exhausting.
185     *                                 Use -1 to newer exhaust when round robin is also enabled.
186     *                                 If round robin is disabled then it will exhaust when there are no more endpoints to failover
187     * @param inheritErrorHandler      whether or not to inherit error handler.
188     *                                 If <tt>false</tt> then it will failover immediately in case of an exception
189     * @param roundRobin               whether or not to use round robin (which keeps state)
190     * @param exceptions               exception classes which we want to failover if one of them was thrown
191     * @return the builder
192     */
193    public LoadBalanceDefinition failover(int maximumFailoverAttempts, boolean inheritErrorHandler, boolean roundRobin, Class<?>... exceptions) {
194        return failover(maximumFailoverAttempts, inheritErrorHandler, roundRobin, false, exceptions);
195    }
196
197    /**
198     * Uses fail over load balancer
199     *
200     * @param maximumFailoverAttempts  maximum number of failover attempts before exhausting.
201     *                                 Use -1 to newer exhaust when round robin is also enabled.
202     *                                 If round robin is disabled then it will exhaust when there are no more endpoints to failover
203     * @param inheritErrorHandler      whether or not to inherit error handler.
204     *                                 If <tt>false</tt> then it will failover immediately in case of an exception
205     * @param roundRobin               whether or not to use round robin (which keeps state)
206     * @param sticky                   whether or not to use sticky (which keeps state)
207     * @param exceptions               exception classes which we want to failover if one of them was thrown
208     * @return the builder
209     */
210    public LoadBalanceDefinition failover(int maximumFailoverAttempts, boolean inheritErrorHandler, boolean roundRobin, boolean sticky, Class<?>... exceptions) {
211        FailoverLoadBalancerDefinition def = new FailoverLoadBalancerDefinition();
212        def.setExceptionTypes(Arrays.asList(exceptions));
213        def.setMaximumFailoverAttempts(maximumFailoverAttempts);
214        def.setRoundRobin(roundRobin);
215        def.setSticky(sticky);
216        setLoadBalancerType(def);
217        this.setInheritErrorHandler(inheritErrorHandler);
218        return this;
219    }
220
221    /**
222     * Uses weighted load balancer
223     *
224     * @param roundRobin                   used to set the processor selection algorithm.
225     * @param distributionRatio            String of weighted ratios for distribution of messages.
226     * @return the builder
227     */
228    public LoadBalanceDefinition weighted(boolean roundRobin, String distributionRatio) {
229        return weighted(roundRobin, distributionRatio, ",");
230    }
231
232    /**
233     * Uses circuitBreaker load balancer
234     *
235     * @param threshold         number of errors before failure.
236     * @param halfOpenAfter     time interval in milliseconds for half open state.
237     * @param exceptions        exception classes which we want to break if one of them was thrown
238     * @return the builder
239     * @deprecated use Hystrix EIP instead which is the popular Netflix implementation of circuit breaker
240     */
241    @Deprecated
242    public LoadBalanceDefinition circuitBreaker(int threshold, long halfOpenAfter, Class<?>... exceptions) {
243        CircuitBreakerLoadBalancerDefinition def = new CircuitBreakerLoadBalancerDefinition();
244        def.setExceptionTypes(Arrays.asList(exceptions));
245        def.setThreshold(threshold);
246        def.setHalfOpenAfter(halfOpenAfter);
247        setLoadBalancerType(def);
248        return this;
249    }
250    
251    /**
252     * Uses weighted load balancer
253     *
254     * @param roundRobin                   used to set the processor selection algorithm.
255     * @param distributionRatio            String of weighted ratios for distribution of messages.
256     * @param distributionRatioDelimiter   String containing delimiter to be used for ratios
257     * @return the builder
258     */
259    public LoadBalanceDefinition weighted(boolean roundRobin, String distributionRatio, String distributionRatioDelimiter) {
260        WeightedLoadBalancerDefinition def = new WeightedLoadBalancerDefinition();
261        def.setRoundRobin(roundRobin);
262        def.setDistributionRatio(distributionRatio);
263        def.setDistributionRatioDelimiter(distributionRatioDelimiter);
264        setLoadBalancerType(def);
265        return this;
266    }
267    
268    /**
269     * Uses round robin load balancer
270     *
271     * @return the builder
272     */
273    public LoadBalanceDefinition roundRobin() {
274        setLoadBalancerType(new RoundRobinLoadBalancerDefinition());
275        return this;
276    }
277
278    /**
279     * Uses random load balancer
280     *
281     * @return the builder
282     */
283    public LoadBalanceDefinition random() {
284        setLoadBalancerType(new RandomLoadBalancerDefinition());
285        return this;
286    }
287
288    /**
289     * Uses the custom load balancer
290     *
291     * @param ref reference to lookup a custom load balancer from the {@link org.apache.camel.spi.Registry} to be used.
292     * @return the builder
293     */
294    public LoadBalanceDefinition custom(String ref) {
295        CustomLoadBalancerDefinition balancer = new CustomLoadBalancerDefinition();
296        balancer.setRef(ref);
297        setLoadBalancerType(balancer);
298        return this;
299    }
300
301    /**
302     * Uses sticky load balancer
303     *
304     * @param correlationExpression  the expression for correlation
305     * @return  the builder
306     */
307    public LoadBalanceDefinition sticky(Expression correlationExpression) {
308        StickyLoadBalancerDefinition def = new StickyLoadBalancerDefinition();
309        def.setCorrelationExpression(correlationExpression);
310        setLoadBalancerType(def);
311        return this;
312    }
313
314    /**
315     * Uses topic load balancer
316     * 
317     * @return the builder
318     */
319    public LoadBalanceDefinition topic() {
320        setLoadBalancerType(new TopicLoadBalancerDefinition());
321        return this;
322    }
323
324    @Override
325    public String getShortName() {
326        return "loadBalance";
327    }
328
329    @Override
330    public String getLabel() {
331        CollectionStringBuffer buffer = new CollectionStringBuffer("loadBalance[");
332        List<ProcessorDefinition<?>> list = getOutputs();
333        for (ProcessorDefinition<?> processorType : list) {
334            buffer.append(processorType.getLabel());
335        }
336        buffer.append("]");
337        return buffer.toString();
338    }
339
340    @Override
341    public String toString() {
342        return "LoadBalanceType[" + loadBalancerType + ", " + getOutputs() + "]";
343    }
344}