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}