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