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