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