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
064    public ToDynamicDefinition() {
065    }
066
067    public ToDynamicDefinition(String uri) {
068        this.uri = uri;
069    }
070
071    @Override
072    public Processor createProcessor(RouteContext routeContext) throws Exception {
073        ObjectHelper.notEmpty(uri, "uri", this);
074
075        Expression exp = createExpression(routeContext);
076
077        SendDynamicProcessor processor = new SendDynamicProcessor(uri, exp);
078        processor.setCamelContext(routeContext.getCamelContext());
079        processor.setPattern(pattern);
080        if (cacheSize != null) {
081            processor.setCacheSize(cacheSize);
082        }
083        if (ignoreInvalidEndpoint != null) {
084            processor.setIgnoreInvalidEndpoint(ignoreInvalidEndpoint);
085        }
086        return processor;
087    }
088
089    protected Expression createExpression(RouteContext routeContext) {
090        List<Expression> list = new ArrayList<Expression>();
091
092        String[] parts = safeSplitRaw(uri);
093        for (String part : parts) {
094            // the part may have optional language to use, so you can mix languages
095            String value = ObjectHelper.after(part, "language:");
096            if (value != null) {
097                String before = ObjectHelper.before(value, ":");
098                String after = ObjectHelper.after(value, ":");
099                if (before != null && after != null) {
100                    // maybe its a language, must have language: as prefix
101                    try {
102                        Language partLanguage = routeContext.getCamelContext().resolveLanguage(before);
103                        if (partLanguage != null) {
104                            Expression exp = partLanguage.createExpression(after);
105                            list.add(exp);
106                            continue;
107                        }
108                    } catch (NoSuchLanguageException e) {
109                        // ignore
110                    }
111                }
112            }
113            // fallback and use simple language
114            Language lan = routeContext.getCamelContext().resolveLanguage("simple");
115            Expression exp = lan.createExpression(part);
116            list.add(exp);
117        }
118
119        Expression exp;
120        if (list.size() == 1) {
121            exp = list.get(0);
122        } else {
123            exp = ExpressionBuilder.concatExpression(list);
124        }
125
126        return exp;
127    }
128
129    @Override
130    public String toString() {
131        return "DynamicTo[" + getLabel() + "]";
132    }
133
134    // Fluent API
135    // -------------------------------------------------------------------------
136
137    /**
138     * Sets the optional {@link ExchangePattern} used to invoke this endpoint
139     */
140    public ToDynamicDefinition pattern(ExchangePattern pattern) {
141        setPattern(pattern);
142        return this;
143    }
144
145    /**
146     * Sets the maximum size used by the {@link org.apache.camel.impl.ConsumerCache} which is used to cache and reuse producers.
147     *
148     * @param cacheSize  the cache size, use <tt>0</tt> for default cache size, or <tt>-1</tt> to turn cache off.
149     * @return the builder
150     */
151    public ToDynamicDefinition cacheSize(int cacheSize) {
152        setCacheSize(cacheSize);
153        return this;
154    }
155
156    /**
157     * Ignore the invalidate endpoint exception when try to create a producer with that endpoint
158     *
159     * @return the builder
160     */
161    public ToDynamicDefinition ignoreInvalidEndpoint() {
162        setIgnoreInvalidEndpoint(true);
163        return this;
164    }
165
166    // Properties
167    // -------------------------------------------------------------------------
168
169    public String getUri() {
170        return uri;
171    }
172
173    /**
174     * The uri of the endpoint to send to. The uri can be dynamic computed using the {@link org.apache.camel.language.simple.SimpleLanguage} expression.
175     */
176    public void setUri(String uri) {
177        this.uri = uri;
178    }
179
180    public ExchangePattern getPattern() {
181        return pattern;
182    }
183
184    public void setPattern(ExchangePattern pattern) {
185        this.pattern = pattern;
186    }
187
188    public Integer getCacheSize() {
189        return cacheSize;
190    }
191
192    public void setCacheSize(Integer cacheSize) {
193        this.cacheSize = cacheSize;
194    }
195
196    public Boolean getIgnoreInvalidEndpoint() {
197        return ignoreInvalidEndpoint;
198    }
199
200    public void setIgnoreInvalidEndpoint(Boolean ignoreInvalidEndpoint) {
201        this.ignoreInvalidEndpoint = ignoreInvalidEndpoint;
202    }
203
204    // Utilities
205    // -------------------------------------------------------------------------
206
207    private static class Pair {
208        int left;
209        int right;
210        Pair(int left, int right) {
211            this.left = left;
212            this.right = right;
213        }
214    }
215
216    private static List<Pair> checkRAW(String s) {
217        Matcher matcher = RAW_PATTERN.matcher(s);
218        List<Pair> answer = new ArrayList<Pair>();
219        // Check all occurrences
220        while (matcher.find()) {
221            answer.add(new Pair(matcher.start(), matcher.end() - 1));
222        }
223        return answer;
224    }
225
226    private static boolean isRaw(int index, List<Pair>pairs) {
227        for (Pair pair : pairs) {
228            if (index < pair.left) {
229                return false;
230            } else {
231                if (index >= pair.left) {
232                    if (index <= pair.right) {
233                        return true;
234                    } else {
235                        continue;
236                    }
237                }
238            }
239        }
240        return false;
241    }
242
243    /**
244     * We need to split the string safely for each + sign, but avoid splitting within RAW(...).
245     */
246    private static String[] safeSplitRaw(String s) {
247        List<String> list = new ArrayList<>();
248
249        if (!s.contains("+")) {
250            // no plus sign so there is only one part, so no need to split
251            list.add(s);
252        } else {
253            // there is a plus sign so we need to split in a safe manner
254            List<Pair> rawPairs = checkRAW(s);
255            StringBuilder sb = new StringBuilder();
256            char chars[] = s.toCharArray();
257            for (int i = 0; i < chars.length; i++) {
258                char ch = chars[i];
259                if (ch != '+' || isRaw(i, rawPairs)) {
260                    sb.append(ch);
261                } else {
262                    list.add(sb.toString());
263                    sb.setLength(0);
264                }
265            }
266            // any leftover?
267            if (sb.length() > 0) {
268                list.add(sb.toString());
269                sb.setLength(0);
270            }
271        }
272
273        return list.toArray(new String[list.size()]);
274    }
275
276}