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.rest;
018
019import java.net.URISyntaxException;
020import java.util.ArrayList;
021import java.util.HashMap;
022import java.util.HashSet;
023import java.util.List;
024import java.util.Map;
025import java.util.Set;
026import javax.xml.bind.annotation.XmlAccessType;
027import javax.xml.bind.annotation.XmlAccessorType;
028import javax.xml.bind.annotation.XmlAttribute;
029import javax.xml.bind.annotation.XmlElement;
030import javax.xml.bind.annotation.XmlElementRef;
031import javax.xml.bind.annotation.XmlRootElement;
032
033import org.apache.camel.CamelContext;
034import org.apache.camel.model.OptionalIdentifiedDefinition;
035import org.apache.camel.model.ProcessorDefinition;
036import org.apache.camel.model.ProcessorDefinitionHelper;
037import org.apache.camel.model.RouteDefinition;
038import org.apache.camel.model.ToDefinition;
039import org.apache.camel.model.ToDynamicDefinition;
040import org.apache.camel.spi.Metadata;
041import org.apache.camel.spi.RestConfiguration;
042import org.apache.camel.util.CamelContextHelper;
043import org.apache.camel.util.FileUtil;
044import org.apache.camel.util.ObjectHelper;
045import org.apache.camel.util.URISupport;
046
047/**
048 * Defines a rest service using the rest-dsl
049 */
050@Metadata(label = "rest")
051@XmlRootElement(name = "rest")
052@XmlAccessorType(XmlAccessType.FIELD)
053public class RestDefinition extends OptionalIdentifiedDefinition<RestDefinition> {
054
055    @XmlAttribute
056    private String path;
057
058    @XmlAttribute
059    private String tag;
060
061    @XmlAttribute
062    private String consumes;
063
064    @XmlAttribute
065    private String produces;
066
067    @XmlAttribute @Metadata(defaultValue = "auto")
068    private RestBindingMode bindingMode;
069
070    @XmlAttribute
071    private Boolean skipBindingOnErrorCode;
072
073    @XmlAttribute
074    private Boolean enableCORS;
075
076    @XmlAttribute
077    private Boolean apiDocs;
078
079    @XmlElement(name = "securityDefinitions") // use the name swagger uses
080    private RestSecuritiesDefinition securityDefinitions;
081
082    @XmlElementRef
083    private List<VerbDefinition> verbs = new ArrayList<VerbDefinition>();
084
085    @Override
086    public String getLabel() {
087        return "rest";
088    }
089
090    public String getPath() {
091        return path;
092    }
093
094    /**
095     * Path of the rest service, such as "/foo"
096     */
097    public void setPath(String path) {
098        this.path = path;
099    }
100
101    public String getTag() {
102        return tag;
103    }
104
105    /**
106     * To configure a special tag for the operations within this rest definition.
107     */
108    public void setTag(String tag) {
109        this.tag = tag;
110    }
111
112    public String getConsumes() {
113        return consumes;
114    }
115
116    /**
117     * To define the content type what the REST service consumes (accept as input), such as application/xml or application/json.
118     * This option will override what may be configured on a parent level
119     */
120    public void setConsumes(String consumes) {
121        this.consumes = consumes;
122    }
123
124    public String getProduces() {
125        return produces;
126    }
127
128    /**
129     * To define the content type what the REST service produces (uses for output), such as application/xml or application/json
130     * This option will override what may be configured on a parent level
131     */
132    public void setProduces(String produces) {
133        this.produces = produces;
134    }
135
136    public RestBindingMode getBindingMode() {
137        return bindingMode;
138    }
139
140    /**
141     * Sets the binding mode to use.
142     * This option will override what may be configured on a parent level
143     * <p/>
144     * The default value is auto
145     */
146    public void setBindingMode(RestBindingMode bindingMode) {
147        this.bindingMode = bindingMode;
148    }
149
150    public List<VerbDefinition> getVerbs() {
151        return verbs;
152    }
153
154    public RestSecuritiesDefinition getSecurityDefinitions() {
155        return securityDefinitions;
156    }
157
158    /**
159     * Sets the security definitions such as Basic, OAuth2 etc.
160     */
161    public void setSecurityDefinitions(RestSecuritiesDefinition securityDefinitions) {
162        this.securityDefinitions = securityDefinitions;
163    }
164
165    /**
166     * The HTTP verbs this REST service accepts and uses
167     */
168    public void setVerbs(List<VerbDefinition> verbs) {
169        this.verbs = verbs;
170    }
171
172    public Boolean getSkipBindingOnErrorCode() {
173        return skipBindingOnErrorCode;
174    }
175
176    /**
177     * Whether to skip binding on output if there is a custom HTTP error code header.
178     * This allows to build custom error messages that do not bind to json / xml etc, as success messages otherwise will do.
179     * This option will override what may be configured on a parent level
180     */
181    public void setSkipBindingOnErrorCode(Boolean skipBindingOnErrorCode) {
182        this.skipBindingOnErrorCode = skipBindingOnErrorCode;
183    }
184
185    public Boolean getEnableCORS() {
186        return enableCORS;
187    }
188
189    /**
190     * Whether to enable CORS headers in the HTTP response.
191     * This option will override what may be configured on a parent level
192     * <p/>
193     * The default value is false.
194     */
195    public void setEnableCORS(Boolean enableCORS) {
196        this.enableCORS = enableCORS;
197    }
198
199    public Boolean getApiDocs() {
200        return apiDocs;
201    }
202
203    /**
204     * Whether to include or exclude the VerbDefinition in API documentation.
205     * This option will override what may be configured on a parent level
206     * <p/>
207     * The default value is true.
208     */
209    public void setApiDocs(Boolean apiDocs) {
210        this.apiDocs = apiDocs;
211    }
212
213    // Fluent API
214    //-------------------------------------------------------------------------
215
216    /**
217     * To set the base path of this REST service
218     */
219    public RestDefinition path(String path) {
220        setPath(path);
221        return this;
222    }
223
224    /**
225     * To set the tag to use of this REST service
226     */
227    public RestDefinition tag(String tag) {
228        setTag(tag);
229        return this;
230    }
231
232    public RestDefinition get() {
233        return addVerb("get", null);
234    }
235
236    public RestDefinition get(String uri) {
237        return addVerb("get", uri);
238    }
239
240    public RestDefinition post() {
241        return addVerb("post", null);
242    }
243
244    public RestDefinition post(String uri) {
245        return addVerb("post", uri);
246    }
247
248    public RestDefinition put() {
249        return addVerb("put", null);
250    }
251
252    public RestDefinition put(String uri) {
253        return addVerb("put", uri);
254    }
255
256    public RestDefinition patch() {
257        return addVerb("patch", null);
258    }
259
260    public RestDefinition patch(String uri) {
261        return addVerb("patch", uri);
262    }
263
264    public RestDefinition delete() {
265        return addVerb("delete", null);
266    }
267
268    public RestDefinition delete(String uri) {
269        return addVerb("delete", uri);
270    }
271
272    public RestDefinition head() {
273        return addVerb("head", null);
274    }
275
276    public RestDefinition head(String uri) {
277        return addVerb("head", uri);
278    }
279
280    @Deprecated
281    public RestDefinition options() {
282        return addVerb("options", null);
283    }
284
285    @Deprecated
286    public RestDefinition options(String uri) {
287        return addVerb("options", uri);
288    }
289
290    public RestDefinition verb(String verb) {
291        return addVerb(verb, null);
292    }
293
294    public RestDefinition verb(String verb, String uri) {
295        return addVerb(verb, uri);
296    }
297
298    @Override
299    public RestDefinition id(String id) {
300        if (getVerbs().isEmpty()) {
301            super.id(id);
302        } else {
303            // add on last verb as that is how the Java DSL works
304            VerbDefinition verb = getVerbs().get(getVerbs().size() - 1);
305            verb.id(id);
306        }
307
308        return this;
309    }
310
311    @Override
312    public RestDefinition description(String text) {
313        if (getVerbs().isEmpty()) {
314            super.description(text);
315        } else {
316            // add on last verb as that is how the Java DSL works
317            VerbDefinition verb = getVerbs().get(getVerbs().size() - 1);
318            verb.description(text);
319        }
320
321        return this;
322    }
323
324    @Override
325    public RestDefinition description(String id, String text, String lang) {
326        if (getVerbs().isEmpty()) {
327            super.description(id, text, lang);
328        } else {
329            // add on last verb as that is how the Java DSL works
330            VerbDefinition verb = getVerbs().get(getVerbs().size() - 1);
331            verb.description(id, text, lang);
332        }
333
334        return this;
335    }
336
337    public RestDefinition consumes(String mediaType) {
338        if (getVerbs().isEmpty()) {
339            this.consumes = mediaType;
340        } else {
341            // add on last verb as that is how the Java DSL works
342            VerbDefinition verb = getVerbs().get(getVerbs().size() - 1);
343            verb.setConsumes(mediaType);
344        }
345
346        return this;
347    }
348
349    public RestOperationParamDefinition param() {
350        if (getVerbs().isEmpty()) {
351            throw new IllegalArgumentException("Must add verb first, such as get/post/delete");
352        }
353        VerbDefinition verb = getVerbs().get(getVerbs().size() - 1);
354        return param(verb);
355    }
356
357    public RestDefinition param(RestOperationParamDefinition param) {
358        if (getVerbs().isEmpty()) {
359            throw new IllegalArgumentException("Must add verb first, such as get/post/delete");
360        }
361        VerbDefinition verb = getVerbs().get(getVerbs().size() - 1);
362        verb.getParams().add(param);
363        return this;
364    }
365
366    public RestDefinition params(List<RestOperationParamDefinition> params) {
367        if (getVerbs().isEmpty()) {
368            throw new IllegalArgumentException("Must add verb first, such as get/post/delete");
369        }
370        VerbDefinition verb = getVerbs().get(getVerbs().size() - 1);
371        verb.getParams().addAll(params);
372        return this;
373    }
374
375    public RestOperationParamDefinition param(VerbDefinition verb) {
376        return new RestOperationParamDefinition(verb);
377    }
378
379    public RestDefinition responseMessage(RestOperationResponseMsgDefinition msg) {
380        if (getVerbs().isEmpty()) {
381            throw new IllegalArgumentException("Must add verb first, such as get/post/delete");
382        }
383        VerbDefinition verb = getVerbs().get(getVerbs().size() - 1);
384        verb.getResponseMsgs().add(msg);
385        return this;
386    }
387
388    public RestOperationResponseMsgDefinition responseMessage() {
389        if (getVerbs().isEmpty()) {
390            throw new IllegalArgumentException("Must add verb first, such as get/post/delete");
391        }
392        VerbDefinition verb = getVerbs().get(getVerbs().size() - 1);
393        return responseMessage(verb);
394    }
395
396    public RestOperationResponseMsgDefinition responseMessage(VerbDefinition verb) {
397        return new RestOperationResponseMsgDefinition(verb);
398    }
399
400    public RestDefinition responseMessages(List<RestOperationResponseMsgDefinition> msgs) {
401        if (getVerbs().isEmpty()) {
402            throw new IllegalArgumentException("Must add verb first, such as get/post/delete");
403        }
404        VerbDefinition verb = getVerbs().get(getVerbs().size() - 1);
405        verb.getResponseMsgs().addAll(msgs);
406        return this;
407    }
408
409    /**
410     * To configure security definitions.
411     */
412    public RestSecuritiesDefinition securityDefinitions() {
413        if (securityDefinitions == null) {
414            securityDefinitions = new RestSecuritiesDefinition(this);
415        }
416        return securityDefinitions;
417    }
418
419    public RestDefinition produces(String mediaType) {
420        if (getVerbs().isEmpty()) {
421            this.produces = mediaType;
422        } else {
423            // add on last verb as that is how the Java DSL works
424            VerbDefinition verb = getVerbs().get(getVerbs().size() - 1);
425            verb.setProduces(mediaType);
426        }
427
428        return this;
429    }
430
431    public RestDefinition type(Class<?> classType) {
432        // add to last verb
433        if (getVerbs().isEmpty()) {
434            throw new IllegalArgumentException("Must add verb first, such as get/post/delete");
435        }
436
437        VerbDefinition verb = getVerbs().get(getVerbs().size() - 1);
438        verb.setType(classType.getCanonicalName());
439        return this;
440    }
441
442    /**
443     * @param classType the canonical class name for the array passed as input
444     *
445     * @deprecated as of 2.19.0. Replaced wtih {@link #type(Class)} with {@code []} appended to canonical class name
446     * , e.g. {@code type(MyClass[].class}
447     */
448    @Deprecated
449    public RestDefinition typeList(Class<?> classType) {
450        // add to last verb
451        if (getVerbs().isEmpty()) {
452            throw new IllegalArgumentException("Must add verb first, such as get/post/delete");
453        }
454
455        VerbDefinition verb = getVerbs().get(getVerbs().size() - 1);
456        // list should end with [] to indicate array
457        verb.setType(classType.getCanonicalName() + "[]");
458        return this;
459    }
460
461    public RestDefinition outType(Class<?> classType) {
462        // add to last verb
463        if (getVerbs().isEmpty()) {
464            throw new IllegalArgumentException("Must add verb first, such as get/post/delete");
465        }
466
467        VerbDefinition verb = getVerbs().get(getVerbs().size() - 1);
468        verb.setOutType(classType.getCanonicalName());
469        return this;
470    }
471
472    /**
473     * @param classType the canonical class name for the array passed as output
474     *
475     * @deprecated as of 2.19.0. Replaced wtih {@link #outType(Class)} with {@code []} appended to canonical class name
476     * , e.g. {@code outType(MyClass[].class}
477     */
478    @Deprecated
479    public RestDefinition outTypeList(Class<?> classType) {
480        // add to last verb
481        if (getVerbs().isEmpty()) {
482            throw new IllegalArgumentException("Must add verb first, such as get/post/delete");
483        }
484
485        VerbDefinition verb = getVerbs().get(getVerbs().size() - 1);
486        // list should end with [] to indicate array
487        verb.setOutType(classType.getCanonicalName() + "[]");
488        return this;
489    }
490
491    public RestDefinition bindingMode(RestBindingMode mode) {
492        if (getVerbs().isEmpty()) {
493            this.bindingMode = mode;
494        } else {
495            // add on last verb as that is how the Java DSL works
496            VerbDefinition verb = getVerbs().get(getVerbs().size() - 1);
497            verb.setBindingMode(mode);
498        }
499
500        return this;
501    }
502
503    public RestDefinition skipBindingOnErrorCode(boolean skipBindingOnErrorCode) {
504        if (getVerbs().isEmpty()) {
505            this.skipBindingOnErrorCode = skipBindingOnErrorCode;
506        } else {
507            // add on last verb as that is how the Java DSL works
508            VerbDefinition verb = getVerbs().get(getVerbs().size() - 1);
509            verb.setSkipBindingOnErrorCode(skipBindingOnErrorCode);
510        }
511
512        return this;
513    }
514
515    public RestDefinition enableCORS(boolean enableCORS) {
516        if (getVerbs().isEmpty()) {
517            this.enableCORS = enableCORS;
518        } else {
519            // add on last verb as that is how the Java DSL works
520            VerbDefinition verb = getVerbs().get(getVerbs().size() - 1);
521            verb.setEnableCORS(enableCORS);
522        }
523
524        return this;
525    }
526
527    /**
528     * Include or exclude the current Rest Definition in API documentation.
529     * <p/>
530     * The default value is true.
531     */
532    public RestDefinition apiDocs(Boolean apiDocs) {
533        if (getVerbs().isEmpty()) {
534            this.apiDocs = apiDocs;
535        } else {
536            // add on last verb as that is how the Java DSL works
537            VerbDefinition verb = getVerbs().get(getVerbs().size() - 1);
538            verb.setApiDocs(apiDocs);
539        }
540
541        return this;
542    }
543
544    /**
545     * Sets the security setting for this verb.
546     */
547    public RestDefinition security(String key) {
548        return security(key, null);
549    }
550
551    /**
552     * Sets the security setting for this verb.
553     */
554    public RestDefinition security(String key, String scopes) {
555        // add to last verb
556        if (getVerbs().isEmpty()) {
557            throw new IllegalArgumentException("Must add verb first, such as get/post/delete");
558        }
559
560        VerbDefinition verb = getVerbs().get(getVerbs().size() - 1);
561        SecurityDefinition sd = new SecurityDefinition();
562        sd.setKey(key);
563        sd.setScopes(scopes);
564        verb.getSecurity().add(sd);
565        return this;
566    }
567
568    /**
569     * Routes directly to the given static endpoint.
570     * <p/>
571     * If you need additional routing capabilities, then use {@link #route()} instead.
572     *
573     * @param uri the uri of the endpoint
574     * @return this builder
575     */
576    public RestDefinition to(String uri) {
577        // add to last verb
578        if (getVerbs().isEmpty()) {
579            throw new IllegalArgumentException("Must add verb first, such as get/post/delete");
580        }
581
582        ToDefinition to = new ToDefinition(uri);
583
584        VerbDefinition verb = getVerbs().get(getVerbs().size() - 1);
585        verb.setTo(to);
586        return this;
587    }
588
589    /**
590     * Routes directly to the given dynamic endpoint.
591     * <p/>
592     * If you need additional routing capabilities, then use {@link #route()} instead.
593     *
594     * @param uri the uri of the endpoint
595     * @return this builder
596     */
597    public RestDefinition toD(String uri) {
598        // add to last verb
599        if (getVerbs().isEmpty()) {
600            throw new IllegalArgumentException("Must add verb first, such as get/post/delete");
601        }
602
603        ToDynamicDefinition to = new ToDynamicDefinition(uri);
604
605        VerbDefinition verb = getVerbs().get(getVerbs().size() - 1);
606        verb.setToD(to);
607        return this;
608    }
609
610    public RouteDefinition route() {
611        // add to last verb
612        if (getVerbs().isEmpty()) {
613            throw new IllegalArgumentException("Must add verb first, such as get/post/delete");
614        }
615
616        // link them together so we can navigate using Java DSL
617        RouteDefinition route = new RouteDefinition();
618        route.setRestDefinition(this);
619        VerbDefinition verb = getVerbs().get(getVerbs().size() - 1);
620        verb.setRoute(route);
621        return route;
622    }
623
624    // Implementation
625    //-------------------------------------------------------------------------
626
627    private RestDefinition addVerb(String verb, String uri) {
628        VerbDefinition answer;
629
630        if ("get".equals(verb)) {
631            answer = new GetVerbDefinition();
632        } else if ("post".equals(verb)) {
633            answer = new PostVerbDefinition();
634        } else if ("delete".equals(verb)) {
635            answer = new DeleteVerbDefinition();
636        } else if ("head".equals(verb)) {
637            answer = new HeadVerbDefinition();
638        } else if ("put".equals(verb)) {
639            answer = new PutVerbDefinition();
640        } else if ("patch".equals(verb)) {
641            answer = new PatchVerbDefinition();
642        } else if ("options".equals(verb)) {
643            answer = new OptionsVerbDefinition();
644        } else {
645            answer = new VerbDefinition();
646            answer.setMethod(verb);
647        }
648        getVerbs().add(answer);
649        answer.setRest(this);
650        answer.setUri(uri);
651        return this;
652    }
653
654    /**
655     * Transforms this REST definition into a list of {@link org.apache.camel.model.RouteDefinition} which
656     * Camel routing engine can add and run. This allows us to define REST services using this
657     * REST DSL and turn those into regular Camel routes.
658     *
659     * @param camelContext The Camel context
660     */
661    public List<RouteDefinition> asRouteDefinition(CamelContext camelContext) {
662        ObjectHelper.notNull(camelContext, "CamelContext");
663
664        // sanity check this rest definition do not have duplicates
665        validateUniquePaths();
666
667        List<RouteDefinition> answer = new ArrayList<RouteDefinition>();
668        if (camelContext.getRestConfigurations().isEmpty()) {
669            // make sure to initialize a rest configuration when its empty
670            // lookup a global which may have been setup via camel-spring-boot etc
671            RestConfiguration conf = CamelContextHelper.lookup(camelContext, RestConstants.DEFAULT_REST_CONFIGURATION_ID, RestConfiguration.class);
672            if (conf == null) {
673                conf = CamelContextHelper.findByType(camelContext, RestConfiguration.class);
674            }
675            if (conf != null) {
676                camelContext.setRestConfiguration(conf);
677            } else {
678                camelContext.setRestConfiguration(new RestConfiguration());
679            }
680        }
681        for (RestConfiguration config : camelContext.getRestConfigurations()) {
682            addRouteDefinition(camelContext, answer, config.getComponent());
683        }
684        return answer;
685    }
686
687    /**
688     * Transforms this REST definition into a list of {@link org.apache.camel.model.RouteDefinition} which
689     * Camel routing engine can add and run. This allows us to define REST services using this
690     * REST DSL and turn those into regular Camel routes.
691     *
692     * @param camelContext        The Camel context
693     * @param restConfiguration   The rest configuration to use
694     */
695    public List<RouteDefinition> asRouteDefinition(CamelContext camelContext, RestConfiguration restConfiguration) {
696        ObjectHelper.notNull(camelContext, "CamelContext");
697        ObjectHelper.notNull(restConfiguration, "RestConfiguration");
698
699        // sanity check this rest definition do not have duplicates
700        validateUniquePaths();
701
702        List<RouteDefinition> answer = new ArrayList<RouteDefinition>();
703        addRouteDefinition(camelContext, answer, restConfiguration.getComponent());
704        return answer;
705    }
706
707    protected void validateUniquePaths() {
708        Set<String> paths = new HashSet<String>();
709        for (VerbDefinition verb : verbs) {
710            String path = verb.asVerb();
711            if (verb.getUri() != null) {
712                path += ":" + verb.getUri();
713            }
714            if (!paths.add(path)) {
715                throw new IllegalArgumentException("Duplicate verb detected in rest-dsl: " + path);
716            }
717        }
718    }
719
720    /**
721     * Transforms the rest api configuration into a {@link org.apache.camel.model.RouteDefinition} which
722     * Camel routing engine uses to service the rest api docs.
723     */
724    public static RouteDefinition asRouteApiDefinition(CamelContext camelContext, RestConfiguration configuration) {
725        RouteDefinition answer = new RouteDefinition();
726
727        // create the from endpoint uri which is using the rest-api component
728        String from = "rest-api:" + configuration.getApiContextPath();
729
730        // append options
731        Map<String, Object> options = new HashMap<String, Object>();
732
733        String routeId = configuration.getApiContextRouteId();
734        if (routeId == null) {
735            routeId = answer.idOrCreate(camelContext.getNodeIdFactory());
736        }
737        options.put("routeId", routeId);
738        if (configuration.getComponent() != null && !configuration.getComponent().isEmpty()) {
739            options.put("componentName", configuration.getComponent());
740        }
741        if (configuration.getApiContextIdPattern() != null) {
742            options.put("contextIdPattern", configuration.getApiContextIdPattern());
743        }
744
745        if (!options.isEmpty()) {
746            String query;
747            try {
748                query = URISupport.createQueryString(options);
749            } catch (URISyntaxException e) {
750                throw ObjectHelper.wrapRuntimeCamelException(e);
751            }
752            from = from + "?" + query;
753        }
754
755        // we use the same uri as the producer (so we have a little route for the rest api)
756        String to = from;
757        answer.fromRest(from);
758        answer.id(routeId);
759        answer.to(to);
760
761        return answer;
762    }
763
764    private void addRouteDefinition(CamelContext camelContext, List<RouteDefinition> answer, String component) {
765        for (VerbDefinition verb : getVerbs()) {
766            // either the verb has a singular to or a embedded route
767            RouteDefinition route = verb.getRoute();
768            if (route == null) {
769                // it was a singular to, so add a new route and add the singular
770                // to as output to this route
771                route = new RouteDefinition();
772                ProcessorDefinition def = verb.getTo() != null ? verb.getTo() : verb.getToD();
773                route.getOutputs().add(def);
774            }
775
776            // ensure property placeholders is resolved on the verb
777            try {
778                ProcessorDefinitionHelper.resolvePropertyPlaceholders(camelContext, verb);
779                for (RestOperationParamDefinition param : verb.getParams()) {
780                    ProcessorDefinitionHelper.resolvePropertyPlaceholders(camelContext, param);
781                }
782            } catch (Exception e) {
783                throw ObjectHelper.wrapRuntimeCamelException(e);
784            }
785
786            // add the binding
787            RestBindingDefinition binding = new RestBindingDefinition();
788            binding.setComponent(component);
789            binding.setType(verb.getType());
790            binding.setOutType(verb.getOutType());
791            // verb takes precedence over configuration on rest
792            if (verb.getConsumes() != null) {
793                binding.setConsumes(verb.getConsumes());
794            } else {
795                binding.setConsumes(getConsumes());
796            }
797            if (verb.getProduces() != null) {
798                binding.setProduces(verb.getProduces());
799            } else {
800                binding.setProduces(getProduces());
801            }
802            if (verb.getBindingMode() != null) {
803                binding.setBindingMode(verb.getBindingMode());
804            } else {
805                binding.setBindingMode(getBindingMode());
806            }
807            if (verb.getSkipBindingOnErrorCode() != null) {
808                binding.setSkipBindingOnErrorCode(verb.getSkipBindingOnErrorCode());
809            } else {
810                binding.setSkipBindingOnErrorCode(getSkipBindingOnErrorCode());
811            }
812            if (verb.getEnableCORS() != null) {
813                binding.setEnableCORS(verb.getEnableCORS());
814            } else {
815                binding.setEnableCORS(getEnableCORS());
816            }
817            // register all the default values for the query parameters
818            for (RestOperationParamDefinition param : verb.getParams()) {
819                if (RestParamType.query == param.getType() && ObjectHelper.isNotEmpty(param.getDefaultValue())) {
820                    binding.addDefaultValue(param.getName(), param.getDefaultValue());
821                }
822            }
823
824            route.setRestBindingDefinition(binding);
825
826            // create the from endpoint uri which is using the rest component
827            String from = "rest:" + verb.asVerb() + ":" + buildUri(verb);
828
829            // append options
830            Map<String, Object> options = new HashMap<String, Object>();
831            // verb takes precedence over configuration on rest
832            if (verb.getConsumes() != null) {
833                options.put("consumes", verb.getConsumes());
834            } else if (getConsumes() != null) {
835                options.put("consumes", getConsumes());
836            }
837            if (verb.getProduces() != null) {
838                options.put("produces", verb.getProduces());
839            } else if (getProduces() != null) {
840                options.put("produces", getProduces());
841            }
842
843            // append optional type binding information
844            String inType = binding.getType();
845            if (inType != null) {
846                options.put("inType", inType);
847            }
848            String outType = binding.getOutType();
849            if (outType != null) {
850                options.put("outType", outType);
851            }
852            // if no route id has been set, then use the verb id as route id
853            if (!route.hasCustomIdAssigned()) {
854                // use id of verb as route id
855                String id = verb.getId();
856                if (id != null) {
857                    route.setId(id);
858                }
859            }
860
861            String routeId = verb.idOrCreate(camelContext.getNodeIdFactory());
862
863            if (!verb.getUsedForGeneratingNodeId()) {
864                routeId = route.idOrCreate(camelContext.getNodeIdFactory());
865            }
866
867            verb.setRouteId(routeId);
868            options.put("routeId", routeId);
869            if (component != null && !component.isEmpty()) {
870                options.put("componentName", component);
871            }
872
873            // include optional description, which we favor from 1) to/route description 2) verb description 3) rest description
874            // this allows end users to define general descriptions and override then per to/route or verb
875            String description = verb.getTo() != null ? verb.getTo().getDescriptionText() : route.getDescriptionText();
876            if (description == null) {
877                description = verb.getDescriptionText();
878            }
879            if (description == null) {
880                description = getDescriptionText();
881            }
882            if (description != null) {
883                options.put("description", description);
884            }
885
886            if (!options.isEmpty()) {
887                String query;
888                try {
889                    query = URISupport.createQueryString(options);
890                } catch (URISyntaxException e) {
891                    throw ObjectHelper.wrapRuntimeCamelException(e);
892                }
893                from = from + "?" + query;
894            }
895
896            String path = getPath();
897            String s1 = FileUtil.stripTrailingSeparator(path);
898            String s2 = FileUtil.stripLeadingSeparator(verb.getUri());
899            String allPath;
900            if (s1 != null && s2 != null) {
901                allPath = s1 + "/" + s2;
902            } else if (path != null) {
903                allPath = path;
904            } else {
905                allPath = verb.getUri();
906            }
907
908            // each {} is a parameter (url templating)
909            if (allPath != null) {
910                String[] arr = allPath.split("\\/");
911                for (String a : arr) {
912                    // need to resolve property placeholders first
913                    try {
914                        a = camelContext.resolvePropertyPlaceholders(a);
915                    } catch (Exception e) {
916                        throw ObjectHelper.wrapRuntimeCamelException(e);
917                    }
918                    if (a.startsWith("{") && a.endsWith("}")) {
919                        String key = a.substring(1, a.length() - 1);
920                        //  merge if exists
921                        boolean found = false;
922                        for (RestOperationParamDefinition param : verb.getParams()) {
923                            // name is mandatory
924                            String name = param.getName();
925                            ObjectHelper.notEmpty(name, "parameter name");
926                            // need to resolve property placeholders first
927                            try {
928                                name = camelContext.resolvePropertyPlaceholders(name);
929                            } catch (Exception e) {
930                                throw ObjectHelper.wrapRuntimeCamelException(e);
931                            }
932                            if (name.equalsIgnoreCase(key)) {
933                                param.type(RestParamType.path);
934                                found = true;
935                                break;
936                            }
937                        }
938                        if (!found) {
939                            param(verb).name(key).type(RestParamType.path).endParam();
940                        }
941                    }
942                }
943            }
944
945            if (verb.getType() != null) {
946                String bodyType = verb.getType();
947                if (bodyType.endsWith("[]")) {
948                    bodyType = "List[" + bodyType.substring(0, bodyType.length() - 2) + "]";
949                }
950                RestOperationParamDefinition param = findParam(verb, RestParamType.body.name());
951                if (param == null) {
952                    // must be body type and set the model class as data type
953                    param(verb).name(RestParamType.body.name()).type(RestParamType.body).dataType(bodyType).endParam();
954                } else {
955                    // must be body type and set the model class as data type
956                    param.type(RestParamType.body).dataType(bodyType);
957                }
958            }
959
960            // the route should be from this rest endpoint
961            route.fromRest(from);
962            route.routeId(routeId);
963            route.setRestDefinition(this);
964            answer.add(route);
965        }
966    }
967
968    private String buildUri(VerbDefinition verb) {
969        if (path != null && verb.getUri() != null) {
970            return path + ":" + verb.getUri();
971        } else if (path != null) {
972            return path;
973        } else if (verb.getUri() != null) {
974            return verb.getUri();
975        } else {
976            return "";
977        }
978    }
979
980    private RestOperationParamDefinition findParam(VerbDefinition verb, String name) {
981        for (RestOperationParamDefinition param : verb.getParams()) {
982            if (name.equals(param.getName())) {
983                return param;
984            }
985        }
986        return null;
987    }
988
989}