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