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