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