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}