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.processor;
018
019import java.util.ArrayList;
020import java.util.Iterator;
021import java.util.List;
022
023import org.apache.camel.AsyncCallback;
024import org.apache.camel.AsyncProcessor;
025import org.apache.camel.Exchange;
026import org.apache.camel.Navigate;
027import org.apache.camel.Processor;
028import org.apache.camel.Traceable;
029import org.apache.camel.spi.IdAware;
030import org.apache.camel.support.ServiceSupport;
031import org.apache.camel.util.AsyncProcessorConverterHelper;
032import org.apache.camel.util.AsyncProcessorHelper;
033import org.apache.camel.util.ServiceHelper;
034import org.slf4j.Logger;
035import org.slf4j.LoggerFactory;
036
037import static org.apache.camel.processor.PipelineHelper.continueProcessing;
038
039/**
040 * Implements a Choice structure where one or more predicates are used which if
041 * they are true their processors are used, with a default otherwise clause used
042 * if none match.
043 * 
044 * @version 
045 */
046public class ChoiceProcessor extends ServiceSupport implements AsyncProcessor, Navigate<Processor>, Traceable, IdAware {
047    private static final Logger LOG = LoggerFactory.getLogger(ChoiceProcessor.class);
048    private String id;
049    private final List<FilterProcessor> filters;
050    private final Processor otherwise;
051    private transient long notFiltered;
052
053    public ChoiceProcessor(List<FilterProcessor> filters, Processor otherwise) {
054        this.filters = filters;
055        this.otherwise = otherwise;
056    }
057
058    public void process(Exchange exchange) throws Exception {
059        AsyncProcessorHelper.process(this, exchange);
060    }
061
062    public boolean process(final Exchange exchange, final AsyncCallback callback) {
063        Iterator<Processor> processors = next().iterator();
064
065        // callback to restore existing FILTER_MATCHED property on the Exchange
066        final Object existing = exchange.getProperty(Exchange.FILTER_MATCHED);
067        final AsyncCallback choiceCallback = new AsyncCallback() {
068            @Override
069            public void done(boolean doneSync) {
070                if (existing != null) {
071                    exchange.setProperty(Exchange.FILTER_MATCHED, existing);
072                } else {
073                    exchange.removeProperty(Exchange.FILTER_MATCHED);
074                }
075                callback.done(doneSync);
076            }
077        };
078
079        // as we only pick one processor to process, then no need to have async callback that has a while loop as well
080        // as this should not happen, eg we pick the first filter processor that matches, or the otherwise (if present)
081        // and if not, we just continue without using any processor
082        while (processors.hasNext()) {
083            // get the next processor
084            Processor processor = processors.next();
085
086            // evaluate the predicate on filter predicate early to be faster
087            // and avoid issues when having nested choices
088            // as we should only pick one processor
089            boolean matches = false;
090            if (processor instanceof FilterProcessor) {
091                FilterProcessor filter = (FilterProcessor) processor;
092                try {
093                    matches = filter.matches(exchange);
094                    // as we have pre evaluated the predicate then use its processor directly when routing
095                    processor = filter.getProcessor();
096                } catch (Throwable e) {
097                    exchange.setException(e);
098                }
099            } else {
100                // its the otherwise processor, so its a match
101                notFiltered++;
102                matches = true;
103            }
104
105            // check for error if so we should break out
106            if (!continueProcessing(exchange, "so breaking out of choice", LOG)) {
107                break;
108            }
109
110            // if we did not match then continue to next filter
111            if (!matches) {
112                continue;
113            }
114
115            // okay we found a filter or its the otherwise we are processing
116            AsyncProcessor async = AsyncProcessorConverterHelper.convert(processor);
117            return async.process(exchange, choiceCallback);
118        }
119
120        // when no filter matches and there is no otherwise, then just continue
121        choiceCallback.done(true);
122        return true;
123    }
124
125    @Override
126    public String toString() {
127        StringBuilder builder = new StringBuilder("choice{");
128        boolean first = true;
129        for (Processor processor : filters) {
130            if (first) {
131                first = false;
132            } else {
133                builder.append(", ");
134            }
135            builder.append("when ");
136            builder.append(processor);
137        }
138        if (otherwise != null) {
139            builder.append(", otherwise: ");
140            builder.append(otherwise);
141        }
142        builder.append("}");
143        return builder.toString();
144    }
145
146    public String getTraceLabel() {
147        return "choice";
148    }
149
150    public List<FilterProcessor> getFilters() {
151        return filters;
152    }
153
154    public Processor getOtherwise() {
155        return otherwise;
156    }
157
158    /**
159     * Gets the number of Exchanges that did not match any predicate and are routed using otherwise
160     */
161    public long getNotFilteredCount() {
162        return notFiltered;
163    }
164
165    /**
166     * Reset counters.
167     */
168    public void reset() {
169        for (FilterProcessor filter : getFilters()) {
170            filter.reset();
171        }
172        notFiltered = 0;
173    }
174
175    public List<Processor> next() {
176        if (!hasNext()) {
177            return null;
178        }
179        List<Processor> answer = new ArrayList<>();
180        if (filters != null) {
181            answer.addAll(filters);
182        }
183        if (otherwise != null) {
184            answer.add(otherwise);
185        }
186        return answer;
187    }
188
189    public boolean hasNext() {
190        return otherwise != null || (filters != null && !filters.isEmpty());
191    }
192
193    public String getId() {
194        return id;
195    }
196
197    public void setId(String id) {
198        this.id = id;
199    }
200
201    protected void doStart() throws Exception {
202        ServiceHelper.startServices(filters, otherwise);
203    }
204
205    protected void doStop() throws Exception {
206        ServiceHelper.stopServices(otherwise, filters);
207    }
208
209}