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.AbstractList;
020import java.util.ArrayList;
021import java.util.List;
022import javax.xml.bind.annotation.XmlAccessType;
023import javax.xml.bind.annotation.XmlAccessorType;
024import javax.xml.bind.annotation.XmlElement;
025import javax.xml.bind.annotation.XmlElementRef;
026import javax.xml.bind.annotation.XmlRootElement;
027
028import org.apache.camel.Predicate;
029import org.apache.camel.Processor;
030import org.apache.camel.builder.ExpressionClause;
031import org.apache.camel.processor.ChoiceProcessor;
032import org.apache.camel.processor.FilterProcessor;
033import org.apache.camel.spi.AsPredicate;
034import org.apache.camel.spi.Metadata;
035import org.apache.camel.spi.RouteContext;
036import org.apache.camel.util.CollectionStringBuffer;
037import org.apache.camel.util.ObjectHelper;
038
039/**
040 * Routes messages based on a series of predicates
041 *
042 * @version
043 */
044@Metadata(label = "eip,routing")
045@XmlRootElement(name = "choice")
046@XmlAccessorType(XmlAccessType.FIELD)
047public class ChoiceDefinition extends ProcessorDefinition<ChoiceDefinition> {
048    @XmlElementRef @AsPredicate
049    private List<WhenDefinition> whenClauses = new ArrayList<WhenDefinition>();
050    @XmlElement
051    private OtherwiseDefinition otherwise;
052
053    private transient boolean onlyWhenOrOtherwise = true;
054
055    public ChoiceDefinition() {
056    }
057    
058    @Override
059    public List<ProcessorDefinition<?>> getOutputs() {
060        // wrap the outputs into a list where we can on the inside control the when/otherwise
061        // but make it appear as a list on the outside
062        return new AbstractList<ProcessorDefinition<?>>() {
063
064            public ProcessorDefinition<?> get(int index) {
065                if (index < whenClauses.size()) {
066                    return whenClauses.get(index);
067                } 
068                if (index == whenClauses.size()) {
069                    return otherwise;
070                }
071                throw new IndexOutOfBoundsException("Index " + index + " is out of bounds with size " + size());
072            }
073
074            public boolean add(ProcessorDefinition<?> def) {
075                if (def instanceof WhenDefinition) {
076                    return whenClauses.add((WhenDefinition)def);
077                } else if (def instanceof OtherwiseDefinition) {
078                    otherwise = (OtherwiseDefinition)def;
079                    return true;
080                }
081                throw new IllegalArgumentException("Expected either a WhenDefinition or OtherwiseDefinition but was "
082                        + ObjectHelper.classCanonicalName(def));
083            }
084
085            public int size() {
086                return whenClauses.size() + (otherwise == null ? 0 : 1);
087            }
088
089            public void clear() {
090                whenClauses.clear();
091                otherwise = null;
092            }
093
094            public ProcessorDefinition<?> set(int index, ProcessorDefinition<?> element) {
095                if (index < whenClauses.size()) {
096                    if (element instanceof WhenDefinition) {
097                        return whenClauses.set(index, (WhenDefinition)element);
098                    }
099                    throw new IllegalArgumentException("Expected WhenDefinition but was "
100                            + ObjectHelper.classCanonicalName(element));
101                } else if (index == whenClauses.size()) {
102                    ProcessorDefinition<?> old = otherwise;
103                    otherwise = (OtherwiseDefinition)element;
104                    return old;
105                }
106                throw new IndexOutOfBoundsException("Index " + index + " is out of bounds with size " + size());
107            }
108
109            public ProcessorDefinition<?> remove(int index) {
110                if (index < whenClauses.size()) {
111                    return whenClauses.remove(index);
112                } else if (index == whenClauses.size()) {
113                    ProcessorDefinition<?> old = otherwise;
114                    otherwise = null;
115                    return old;
116                }
117                throw new IndexOutOfBoundsException("Index " + index + " is out of bounds with size " + size());
118            }
119        };
120    }
121
122    @Override
123    public boolean isOutputSupported() {
124        return true;
125    }
126    
127    @Override
128    public String toString() {
129        return "Choice[" + getWhenClauses() + (getOtherwise() != null ? " " + getOtherwise() : "") + "]";
130    }
131
132    @Override
133    public Processor createProcessor(RouteContext routeContext) throws Exception {
134        List<FilterProcessor> filters = new ArrayList<FilterProcessor>();
135        for (WhenDefinition whenClause : whenClauses) {
136            FilterProcessor filter = (FilterProcessor) createProcessor(routeContext, whenClause);
137            filters.add(filter);
138        }
139        Processor otherwiseProcessor = null;
140        if (otherwise != null) {
141            otherwiseProcessor = createProcessor(routeContext, otherwise);
142        }
143        return new ChoiceProcessor(filters, otherwiseProcessor);
144    }
145
146    @Override
147    public void addOutput(ProcessorDefinition<?> output) {
148        if (onlyWhenOrOtherwise) {
149            if (output instanceof WhenDefinition || output instanceof OtherwiseDefinition) {
150                // okay we are adding a when or otherwise so allow any kind of output after this again
151                onlyWhenOrOtherwise = false;
152            } else {
153                throw new IllegalArgumentException("A new choice clause should start with a when() or otherwise(). "
154                    + "If you intend to end the entire choice and are using endChoice() then use end() instead.");
155            }
156        }
157        super.addOutput(output);
158    }
159
160    @Override
161    public ProcessorDefinition<?> end() {
162        // we end a block so only when or otherwise is supported
163        onlyWhenOrOtherwise = true;
164        return super.end();
165    }
166
167    @Override
168    public ChoiceDefinition endChoice() {
169        // we end a block so only when or otherwise is supported
170        onlyWhenOrOtherwise = true;
171        return super.endChoice();
172    }
173
174    // Fluent API
175    // -------------------------------------------------------------------------
176
177    /**
178     * Sets the predicate for the when node
179     *
180     * @param predicate the predicate
181     * @return the builder
182     */
183    public ChoiceDefinition when(@AsPredicate Predicate predicate) {
184        addClause(new WhenDefinition(predicate));
185        return this;
186    }
187
188    /**
189     * Creates an expression for the when node
190     *
191     * @return expression to be used as builder to configure the when node
192     */
193    @AsPredicate
194    public ExpressionClause<ChoiceDefinition> when() {
195        ExpressionClause<ChoiceDefinition> clause = new ExpressionClause<ChoiceDefinition>(this);
196        addClause(new WhenDefinition(clause));
197        return clause;
198    }
199    
200    private void addClause(ProcessorDefinition<?> when) {
201        onlyWhenOrOtherwise = true;
202        popBlock();
203        addOutput(when);
204        pushBlock(when);
205    }
206    
207    /**
208     * Sets the otherwise node
209     *
210     * @return the builder
211     */
212    public ChoiceDefinition otherwise() {
213        OtherwiseDefinition answer = new OtherwiseDefinition();
214        addClause(answer);
215        return this;
216    }
217
218    @Override
219    public void setId(String value) {
220        // when setting id, we should set it on the fine grained element, if possible
221        if (otherwise != null) {
222            otherwise.setId(value);
223        } else if (!getWhenClauses().isEmpty()) {
224            int size = getWhenClauses().size();
225            getWhenClauses().get(size - 1).setId(value);
226        } else {
227            super.setId(value);
228        }
229    }
230
231    // Properties
232    // -------------------------------------------------------------------------
233
234    @Override
235    public String getLabel() {
236        CollectionStringBuffer buffer = new CollectionStringBuffer("choice[");
237        List<WhenDefinition> list = getWhenClauses();
238        for (WhenDefinition whenType : list) {
239            buffer.append(whenType.getLabel());
240        }
241        buffer.append("]");
242        return buffer.toString();
243    }
244
245    public List<WhenDefinition> getWhenClauses() {
246        return whenClauses;
247    }
248
249    /**
250     * Sets the when clauses
251     */
252    public void setWhenClauses(List<WhenDefinition> whenClauses) {
253        this.whenClauses = whenClauses;
254    }
255
256    public OtherwiseDefinition getOtherwise() {
257        return otherwise;
258    }
259
260    public void setOtherwise(OtherwiseDefinition otherwise) {
261        this.otherwise = otherwise;
262    }
263
264    @Override
265    public void configureChild(ProcessorDefinition<?> output) {
266        if (whenClauses == null || whenClauses.isEmpty()) {
267            return;
268        }
269        for (WhenDefinition when : whenClauses) {
270            if (when.getExpression() instanceof ExpressionClause) {
271                ExpressionClause<?> clause = (ExpressionClause<?>) when.getExpression();
272                if (clause.getExpressionType() != null) {
273                    // if using the Java DSL then the expression may have been set using the
274                    // ExpressionClause which is a fancy builder to define expressions and predicates
275                    // using fluent builders in the DSL. However we need afterwards a callback to
276                    // reset the expression to the expression type the ExpressionClause did build for us
277                    when.setExpression(clause.getExpressionType());
278                }
279            }
280        }
281    }
282}