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