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}