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.ArrayList; 020import java.util.List; 021import java.util.regex.Matcher; 022import java.util.regex.Pattern; 023import javax.xml.bind.annotation.XmlAccessType; 024import javax.xml.bind.annotation.XmlAccessorType; 025import javax.xml.bind.annotation.XmlAttribute; 026import javax.xml.bind.annotation.XmlRootElement; 027 028import org.apache.camel.ExchangePattern; 029import org.apache.camel.Expression; 030import org.apache.camel.NoSuchLanguageException; 031import org.apache.camel.Processor; 032import org.apache.camel.builder.ExpressionBuilder; 033import org.apache.camel.processor.SendDynamicProcessor; 034import org.apache.camel.spi.Language; 035import org.apache.camel.spi.Metadata; 036import org.apache.camel.spi.RouteContext; 037import org.apache.camel.util.ObjectHelper; 038 039/** 040 * Sends the message to a dynamic endpoint 041 * <p/> 042 * You can specify multiple languages in the uri separated by the plus sign, such as <tt>mock:+language:xpath:/order/@uri</tt> 043 * where <tt>mock:</tt> would be a prefix to a xpath expression. 044 * <p/> 045 * For more dynamic behavior use <a href="http://camel.apache.org/recipient-list.html">Recipient List</a> or 046 * <a href="http://camel.apache.org/dynamic-router.html">Dynamic Router</a> EIP instead. 047 */ 048@Metadata(label = "eip,endpoint,routing") 049@XmlRootElement(name = "toD") 050@XmlAccessorType(XmlAccessType.FIELD) 051public class ToDynamicDefinition extends NoOutputDefinition<ToDynamicDefinition> { 052 053 private static final Pattern RAW_PATTERN = Pattern.compile("RAW\\([^\\)]+\\)"); 054 055 @XmlAttribute @Metadata(required = "true") 056 private String uri; 057 @XmlAttribute 058 private ExchangePattern pattern; 059 @XmlAttribute 060 private Integer cacheSize; 061 @XmlAttribute 062 private Boolean ignoreInvalidEndpoint; 063 @XmlAttribute @Metadata(defaultValue = "true") 064 private Boolean allowOptimisedComponents; 065 066 public ToDynamicDefinition() { 067 } 068 069 public ToDynamicDefinition(String uri) { 070 this.uri = uri; 071 } 072 073 @Override 074 public Processor createProcessor(RouteContext routeContext) throws Exception { 075 ObjectHelper.notEmpty(uri, "uri", this); 076 077 Expression exp = createExpression(routeContext); 078 079 SendDynamicProcessor processor = new SendDynamicProcessor(uri, exp); 080 processor.setCamelContext(routeContext.getCamelContext()); 081 processor.setPattern(pattern); 082 if (cacheSize != null) { 083 processor.setCacheSize(cacheSize); 084 } 085 if (ignoreInvalidEndpoint != null) { 086 processor.setIgnoreInvalidEndpoint(ignoreInvalidEndpoint); 087 } 088 return processor; 089 } 090 091 protected Expression createExpression(RouteContext routeContext) { 092 List<Expression> list = new ArrayList<>(); 093 094 String[] parts = safeSplitRaw(uri); 095 for (String part : parts) { 096 // the part may have optional language to use, so you can mix languages 097 String value = ObjectHelper.after(part, "language:"); 098 if (value != null) { 099 String before = ObjectHelper.before(value, ":"); 100 String after = ObjectHelper.after(value, ":"); 101 if (before != null && after != null) { 102 // maybe its a language, must have language: as prefix 103 try { 104 Language partLanguage = routeContext.getCamelContext().resolveLanguage(before); 105 if (partLanguage != null) { 106 Expression exp = partLanguage.createExpression(after); 107 list.add(exp); 108 continue; 109 } 110 } catch (NoSuchLanguageException e) { 111 // ignore 112 } 113 } 114 } 115 // fallback and use simple language 116 Language lan = routeContext.getCamelContext().resolveLanguage("simple"); 117 Expression exp = lan.createExpression(part); 118 list.add(exp); 119 } 120 121 Expression exp; 122 if (list.size() == 1) { 123 exp = list.get(0); 124 } else { 125 exp = ExpressionBuilder.concatExpression(list); 126 } 127 128 return exp; 129 } 130 131 @Override 132 public String toString() { 133 return "DynamicTo[" + getLabel() + "]"; 134 } 135 136 // Fluent API 137 // ------------------------------------------------------------------------- 138 139 /** 140 * Sets the optional {@link ExchangePattern} used to invoke this endpoint 141 */ 142 public ToDynamicDefinition pattern(ExchangePattern pattern) { 143 setPattern(pattern); 144 return this; 145 } 146 147 /** 148 * Sets the maximum size used by the {@link org.apache.camel.impl.ConsumerCache} which is used to cache and reuse producers. 149 * 150 * @param cacheSize the cache size, use <tt>0</tt> for default cache size, or <tt>-1</tt> to turn cache off. 151 * @return the builder 152 */ 153 public ToDynamicDefinition cacheSize(int cacheSize) { 154 setCacheSize(cacheSize); 155 return this; 156 } 157 158 /** 159 * Ignore the invalidate endpoint exception when try to create a producer with that endpoint 160 * 161 * @return the builder 162 */ 163 public ToDynamicDefinition ignoreInvalidEndpoint() { 164 setIgnoreInvalidEndpoint(true); 165 return this; 166 } 167 168 /** 169 * Whether to allow components to optimise toD if they are {@link org.apache.camel.spi.SendDynamicAware}. 170 * 171 * @return the builder 172 */ 173 public ToDynamicDefinition allowOptimisedComponents(boolean allowOptimisedComponents) { 174 setAllowOptimisedComponents(allowOptimisedComponents); 175 return this; 176 } 177 178 // Properties 179 // ------------------------------------------------------------------------- 180 181 public String getUri() { 182 return uri; 183 } 184 185 /** 186 * The uri of the endpoint to send to. The uri can be dynamic computed using the {@link org.apache.camel.language.simple.SimpleLanguage} expression. 187 */ 188 public void setUri(String uri) { 189 this.uri = uri; 190 } 191 192 public ExchangePattern getPattern() { 193 return pattern; 194 } 195 196 public void setPattern(ExchangePattern pattern) { 197 this.pattern = pattern; 198 } 199 200 public Integer getCacheSize() { 201 return cacheSize; 202 } 203 204 public void setCacheSize(Integer cacheSize) { 205 this.cacheSize = cacheSize; 206 } 207 208 public Boolean getIgnoreInvalidEndpoint() { 209 return ignoreInvalidEndpoint; 210 } 211 212 public void setIgnoreInvalidEndpoint(Boolean ignoreInvalidEndpoint) { 213 this.ignoreInvalidEndpoint = ignoreInvalidEndpoint; 214 } 215 216 public Boolean getAllowOptimisedComponents() { 217 return allowOptimisedComponents; 218 } 219 220 public void setAllowOptimisedComponents(Boolean allowOptimisedComponents) { 221 this.allowOptimisedComponents = allowOptimisedComponents; 222 } 223 224 // Utilities 225 // ------------------------------------------------------------------------- 226 227 private static class Pair { 228 int left; 229 int right; 230 Pair(int left, int right) { 231 this.left = left; 232 this.right = right; 233 } 234 } 235 236 private static List<Pair> checkRAW(String s) { 237 Matcher matcher = RAW_PATTERN.matcher(s); 238 List<Pair> answer = new ArrayList<>(); 239 // Check all occurrences 240 while (matcher.find()) { 241 answer.add(new Pair(matcher.start(), matcher.end() - 1)); 242 } 243 return answer; 244 } 245 246 private static boolean isRaw(int index, List<Pair>pairs) { 247 for (Pair pair : pairs) { 248 if (index < pair.left) { 249 return false; 250 } else { 251 if (index >= pair.left) { 252 if (index <= pair.right) { 253 return true; 254 } else { 255 continue; 256 } 257 } 258 } 259 } 260 return false; 261 } 262 263 /** 264 * We need to split the string safely for each + sign, but avoid splitting within RAW(...). 265 */ 266 private static String[] safeSplitRaw(String s) { 267 List<String> list = new ArrayList<>(); 268 269 if (!s.contains("+")) { 270 // no plus sign so there is only one part, so no need to split 271 list.add(s); 272 } else { 273 // there is a plus sign so we need to split in a safe manner 274 List<Pair> rawPairs = checkRAW(s); 275 StringBuilder sb = new StringBuilder(); 276 char chars[] = s.toCharArray(); 277 for (int i = 0; i < chars.length; i++) { 278 char ch = chars[i]; 279 if (ch != '+' || isRaw(i, rawPairs)) { 280 sb.append(ch); 281 } else { 282 list.add(sb.toString()); 283 sb.setLength(0); 284 } 285 } 286 // any leftover? 287 if (sb.length() > 0) { 288 list.add(sb.toString()); 289 sb.setLength(0); 290 } 291 } 292 293 return list.toArray(new String[list.size()]); 294 } 295 296}