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