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