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}