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 delete() {
240        return addVerb("delete", null);
241    }
242
243    public RestDefinition delete(String uri) {
244        return addVerb("delete", uri);
245    }
246
247    public RestDefinition head() {
248        return addVerb("head", null);
249    }
250
251    public RestDefinition head(String uri) {
252        return addVerb("head", uri);
253    }
254
255    @Deprecated
256    public RestDefinition options() {
257        return addVerb("options", null);
258    }
259
260    @Deprecated
261    public RestDefinition options(String uri) {
262        return addVerb("options", uri);
263    }
264
265    public RestDefinition verb(String verb) {
266        return addVerb(verb, null);
267    }
268
269    public RestDefinition verb(String verb, String uri) {
270        return addVerb(verb, uri);
271    }
272
273    @Override
274    public RestDefinition id(String id) {
275        if (getVerbs().isEmpty()) {
276            super.id(id);
277        } else {
278            // add on last verb as that is how the Java DSL works
279            VerbDefinition verb = getVerbs().get(getVerbs().size() - 1);
280            verb.id(id);
281        }
282
283        return this;
284    }
285
286    @Override
287    public RestDefinition description(String text) {
288        if (getVerbs().isEmpty()) {
289            super.description(text);
290        } else {
291            // add on last verb as that is how the Java DSL works
292            VerbDefinition verb = getVerbs().get(getVerbs().size() - 1);
293            verb.description(text);
294        }
295
296        return this;
297    }
298
299    @Override
300    public RestDefinition description(String id, String text, String lang) {
301        if (getVerbs().isEmpty()) {
302            super.description(id, text, lang);
303        } else {
304            // add on last verb as that is how the Java DSL works
305            VerbDefinition verb = getVerbs().get(getVerbs().size() - 1);
306            verb.description(id, text, lang);
307        }
308
309        return this;
310    }
311
312    public RestDefinition consumes(String mediaType) {
313        if (getVerbs().isEmpty()) {
314            this.consumes = mediaType;
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.setConsumes(mediaType);
319        }
320
321        return this;
322    }
323
324    public RestOperationParamDefinition param() {
325        if (getVerbs().isEmpty()) {
326            throw new IllegalArgumentException("Must add verb first, such as get/post/delete");
327        }
328        VerbDefinition verb = getVerbs().get(getVerbs().size() - 1);
329        return param(verb);
330    }
331
332    public RestDefinition param(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        verb.getParams().add(param);
338        return this;
339    }
340
341    public RestDefinition params(List<RestOperationParamDefinition> params) {
342        if (getVerbs().isEmpty()) {
343            throw new IllegalArgumentException("Must add verb first, such as get/post/delete");
344        }
345        VerbDefinition verb = getVerbs().get(getVerbs().size() - 1);
346        verb.getParams().addAll(params);
347        return this;
348    }
349
350    public RestOperationParamDefinition param(VerbDefinition verb) {
351        return new RestOperationParamDefinition(verb);
352    }
353
354    public RestDefinition responseMessage(RestOperationResponseMsgDefinition msg) {
355        if (getVerbs().isEmpty()) {
356            throw new IllegalArgumentException("Must add verb first, such as get/post/delete");
357        }
358        VerbDefinition verb = getVerbs().get(getVerbs().size() - 1);
359        verb.getResponseMsgs().add(msg);
360        return this;
361    }
362
363    public RestOperationResponseMsgDefinition responseMessage() {
364        if (getVerbs().isEmpty()) {
365            throw new IllegalArgumentException("Must add verb first, such as get/post/delete");
366        }
367        VerbDefinition verb = getVerbs().get(getVerbs().size() - 1);
368        return responseMessage(verb);
369    }
370
371    public RestOperationResponseMsgDefinition responseMessage(VerbDefinition verb) {
372        return new RestOperationResponseMsgDefinition(verb);
373    }
374
375    public RestDefinition responseMessages(List<RestOperationResponseMsgDefinition> msgs) {
376        if (getVerbs().isEmpty()) {
377            throw new IllegalArgumentException("Must add verb first, such as get/post/delete");
378        }
379        VerbDefinition verb = getVerbs().get(getVerbs().size() - 1);
380        verb.getResponseMsgs().addAll(msgs);
381        return this;
382    }
383
384    public RestDefinition produces(String mediaType) {
385        if (getVerbs().isEmpty()) {
386            this.produces = mediaType;
387        } else {
388            // add on last verb as that is how the Java DSL works
389            VerbDefinition verb = getVerbs().get(getVerbs().size() - 1);
390            verb.setProduces(mediaType);
391        }
392
393        return this;
394    }
395
396    public RestDefinition type(Class<?> classType) {
397        // add to last verb
398        if (getVerbs().isEmpty()) {
399            throw new IllegalArgumentException("Must add verb first, such as get/post/delete");
400        }
401
402        VerbDefinition verb = getVerbs().get(getVerbs().size() - 1);
403        verb.setType(classType.getCanonicalName());
404        return this;
405    }
406
407    public RestDefinition typeList(Class<?> classType) {
408        // add to last verb
409        if (getVerbs().isEmpty()) {
410            throw new IllegalArgumentException("Must add verb first, such as get/post/delete");
411        }
412
413        VerbDefinition verb = getVerbs().get(getVerbs().size() - 1);
414        // list should end with [] to indicate array
415        verb.setType(classType.getCanonicalName() + "[]");
416        return this;
417    }
418
419    public RestDefinition outType(Class<?> classType) {
420        // add to last verb
421        if (getVerbs().isEmpty()) {
422            throw new IllegalArgumentException("Must add verb first, such as get/post/delete");
423        }
424
425        VerbDefinition verb = getVerbs().get(getVerbs().size() - 1);
426        verb.setOutType(classType.getCanonicalName());
427        return this;
428    }
429
430    public RestDefinition outTypeList(Class<?> classType) {
431        // add to last verb
432        if (getVerbs().isEmpty()) {
433            throw new IllegalArgumentException("Must add verb first, such as get/post/delete");
434        }
435
436        VerbDefinition verb = getVerbs().get(getVerbs().size() - 1);
437        // list should end with [] to indicate array
438        verb.setOutType(classType.getCanonicalName() + "[]");
439        return this;
440    }
441
442    public RestDefinition bindingMode(RestBindingMode mode) {
443        if (getVerbs().isEmpty()) {
444            this.bindingMode = mode;
445        } else {
446            // add on last verb as that is how the Java DSL works
447            VerbDefinition verb = getVerbs().get(getVerbs().size() - 1);
448            verb.setBindingMode(mode);
449        }
450
451        return this;
452    }
453
454    public RestDefinition skipBindingOnErrorCode(boolean skipBindingOnErrorCode) {
455        if (getVerbs().isEmpty()) {
456            this.skipBindingOnErrorCode = skipBindingOnErrorCode;
457        } else {
458            // add on last verb as that is how the Java DSL works
459            VerbDefinition verb = getVerbs().get(getVerbs().size() - 1);
460            verb.setSkipBindingOnErrorCode(skipBindingOnErrorCode);
461        }
462
463        return this;
464    }
465
466    public RestDefinition enableCORS(boolean enableCORS) {
467        if (getVerbs().isEmpty()) {
468            this.enableCORS = enableCORS;
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.setEnableCORS(enableCORS);
473        }
474
475        return this;
476    }
477
478    /**
479     * Include or exclude the current Rest Definition in API documentation.
480     * <p/>
481     * The default value is true.
482     */
483    public RestDefinition apiDocs(Boolean apiDocs) {
484        if (getVerbs().isEmpty()) {
485            this.apiDocs = apiDocs;
486        } else {
487            // add on last verb as that is how the Java DSL works
488            VerbDefinition verb = getVerbs().get(getVerbs().size() - 1);
489            verb.setApiDocs(apiDocs);
490        }
491
492        return this;
493    }
494
495    /**
496     * Routes directly to the given static endpoint.
497     * <p/>
498     * If you need additional routing capabilities, then use {@link #route()} instead.
499     *
500     * @param uri the uri of the endpoint
501     * @return this builder
502     */
503    public RestDefinition to(String uri) {
504        // add to last verb
505        if (getVerbs().isEmpty()) {
506            throw new IllegalArgumentException("Must add verb first, such as get/post/delete");
507        }
508
509        ToDefinition to = new ToDefinition(uri);
510
511        VerbDefinition verb = getVerbs().get(getVerbs().size() - 1);
512        verb.setTo(to);
513        return this;
514    }
515
516    /**
517     * Routes directly to the given dynamic endpoint.
518     * <p/>
519     * If you need additional routing capabilities, then use {@link #route()} instead.
520     *
521     * @param uri the uri of the endpoint
522     * @return this builder
523     */
524    public RestDefinition toD(String uri) {
525        // add to last verb
526        if (getVerbs().isEmpty()) {
527            throw new IllegalArgumentException("Must add verb first, such as get/post/delete");
528        }
529
530        ToDynamicDefinition to = new ToDynamicDefinition(uri);
531
532        VerbDefinition verb = getVerbs().get(getVerbs().size() - 1);
533        verb.setToD(to);
534        return this;
535    }
536
537    public RouteDefinition route() {
538        // add to last verb
539        if (getVerbs().isEmpty()) {
540            throw new IllegalArgumentException("Must add verb first, such as get/post/delete");
541        }
542
543        // link them together so we can navigate using Java DSL
544        RouteDefinition route = new RouteDefinition();
545        route.setRestDefinition(this);
546        VerbDefinition verb = getVerbs().get(getVerbs().size() - 1);
547        verb.setRoute(route);
548        return route;
549    }
550
551    // Implementation
552    //-------------------------------------------------------------------------
553
554    private RestDefinition addVerb(String verb, String uri) {
555        VerbDefinition answer;
556
557        if ("get".equals(verb)) {
558            answer = new GetVerbDefinition();
559        } else if ("post".equals(verb)) {
560            answer = new PostVerbDefinition();
561        } else if ("delete".equals(verb)) {
562            answer = new DeleteVerbDefinition();
563        } else if ("head".equals(verb)) {
564            answer = new HeadVerbDefinition();
565        } else if ("put".equals(verb)) {
566            answer = new PutVerbDefinition();
567        } else if ("options".equals(verb)) {
568            answer = new OptionsVerbDefinition();
569        } else {
570            answer = new VerbDefinition();
571            answer.setMethod(verb);
572        }
573        getVerbs().add(answer);
574        answer.setRest(this);
575        answer.setUri(uri);
576        return this;
577    }
578
579    /**
580     * Transforms this REST definition into a list of {@link org.apache.camel.model.RouteDefinition} which
581     * Camel routing engine can add and run. This allows us to define REST services using this
582     * REST DSL and turn those into regular Camel routes.
583     */
584    public List<RouteDefinition> asRouteDefinition(CamelContext camelContext) {
585        // sanity check this rest definition do not have duplicates
586        validateUniquePaths();
587
588        List<RouteDefinition> answer = new ArrayList<RouteDefinition>();
589        if (camelContext.getRestConfigurations().isEmpty()) {
590            camelContext.getRestConfiguration();
591        }
592        for (RestConfiguration config : camelContext.getRestConfigurations()) {
593            addRouteDefinition(camelContext, answer, config.getComponent());
594        }
595        return answer;
596    }
597
598    protected void validateUniquePaths() {
599        Set<String> paths = new HashSet<String>();
600        for (VerbDefinition verb : verbs) {
601            String path = verb.asVerb();
602            if (verb.getUri() != null) {
603                path += ":" + verb.getUri();
604            }
605            if (!paths.add(path)) {
606                throw new IllegalArgumentException("Duplicate verb detected in rest-dsl: " + path);
607            }
608        }
609    }
610
611    /**
612     * Transforms the rest api configuration into a {@link org.apache.camel.model.RouteDefinition} which
613     * Camel routing engine uses to service the rest api docs.
614     */
615    public static RouteDefinition asRouteApiDefinition(CamelContext camelContext, RestConfiguration configuration) {
616        RouteDefinition answer = new RouteDefinition();
617
618        // create the from endpoint uri which is using the rest-api component
619        String from = "rest-api:" + configuration.getApiContextPath();
620
621        // append options
622        Map<String, Object> options = new HashMap<String, Object>();
623
624        String routeId = configuration.getApiContextRouteId();
625        if (routeId == null) {
626            routeId = answer.idOrCreate(camelContext.getNodeIdFactory());
627        }
628        options.put("routeId", routeId);
629        if (configuration.getComponent() != null && !configuration.getComponent().isEmpty()) {
630            options.put("componentName", configuration.getComponent());
631        }
632        if (configuration.getApiContextIdPattern() != null) {
633            options.put("contextIdPattern", configuration.getApiContextIdPattern());
634        }
635
636        if (!options.isEmpty()) {
637            String query;
638            try {
639                query = URISupport.createQueryString(options);
640            } catch (URISyntaxException e) {
641                throw ObjectHelper.wrapRuntimeCamelException(e);
642            }
643            from = from + "?" + query;
644        }
645
646        // we use the same uri as the producer (so we have a little route for the rest api)
647        String to = from;
648        answer.fromRest(from);
649        answer.id(routeId);
650        answer.to(to);
651
652        return answer;
653    }
654
655    private void addRouteDefinition(CamelContext camelContext, List<RouteDefinition> answer, String component) {
656        for (VerbDefinition verb : getVerbs()) {
657            // either the verb has a singular to or a embedded route
658            RouteDefinition route = verb.getRoute();
659            if (route == null) {
660                // it was a singular to, so add a new route and add the singular
661                // to as output to this route
662                route = new RouteDefinition();
663                ProcessorDefinition def = verb.getTo() != null ? verb.getTo() : verb.getToD();
664                route.getOutputs().add(def);
665            }
666
667            // add the binding
668            RestBindingDefinition binding = new RestBindingDefinition();
669            binding.setComponent(component);
670            binding.setType(verb.getType());
671            binding.setOutType(verb.getOutType());
672            // verb takes precedence over configuration on rest
673            if (verb.getConsumes() != null) {
674                binding.setConsumes(verb.getConsumes());
675            } else {
676                binding.setConsumes(getConsumes());
677            }
678            if (verb.getProduces() != null) {
679                binding.setProduces(verb.getProduces());
680            } else {
681                binding.setProduces(getProduces());
682            }
683            if (verb.getBindingMode() != null) {
684                binding.setBindingMode(verb.getBindingMode());
685            } else {
686                binding.setBindingMode(getBindingMode());
687            }
688            if (verb.getSkipBindingOnErrorCode() != null) {
689                binding.setSkipBindingOnErrorCode(verb.getSkipBindingOnErrorCode());
690            } else {
691                binding.setSkipBindingOnErrorCode(getSkipBindingOnErrorCode());
692            }
693            if (verb.getEnableCORS() != null) {
694                binding.setEnableCORS(verb.getEnableCORS());
695            } else {
696                binding.setEnableCORS(getEnableCORS());
697            }
698            // register all the default values for the query parameters
699            for (RestOperationParamDefinition param : verb.getParams()) {
700                if (RestParamType.query == param.getType() && param.getDefaultValue() != null) {
701                    binding.addDefaultValue(param.getName(), param.getDefaultValue());
702                }
703            }
704
705            route.getOutputs().add(0, binding);
706
707            // create the from endpoint uri which is using the rest component
708            String from = "rest:" + verb.asVerb() + ":" + buildUri(verb);
709
710            // append options
711            Map<String, Object> options = new HashMap<String, Object>();
712            // verb takes precedence over configuration on rest
713            if (verb.getConsumes() != null) {
714                options.put("consumes", verb.getConsumes());
715            } else if (getConsumes() != null) {
716                options.put("consumes", getConsumes());
717            }
718            if (verb.getProduces() != null) {
719                options.put("produces", verb.getProduces());
720            } else if (getProduces() != null) {
721                options.put("produces", getProduces());
722            }
723
724            // append optional type binding information
725            String inType = binding.getType();
726            if (inType != null) {
727                options.put("inType", inType);
728            }
729            String outType = binding.getOutType();
730            if (outType != null) {
731                options.put("outType", outType);
732            }
733            // if no route id has been set, then use the verb id as route id
734            if (!route.hasCustomIdAssigned()) {
735                // use id of verb as route id
736                String id = verb.getId();
737                if (id != null) {
738                    route.setId(id);
739                }
740            }
741            String routeId = route.idOrCreate(camelContext.getNodeIdFactory());
742            verb.setRouteId(routeId);
743            options.put("routeId", routeId);
744            if (component != null && !component.isEmpty()) {
745                options.put("componentName", component);
746            }
747
748            // include optional description, which we favor from 1) to/route description 2) verb description 3) rest description
749            // this allows end users to define general descriptions and override then per to/route or verb
750            String description = verb.getTo() != null ? verb.getTo().getDescriptionText() : route.getDescriptionText();
751            if (description == null) {
752                description = verb.getDescriptionText();
753            }
754            if (description == null) {
755                description = getDescriptionText();
756            }
757            if (description != null) {
758                options.put("description", description);
759            }
760
761            if (!options.isEmpty()) {
762                String query;
763                try {
764                    query = URISupport.createQueryString(options);
765                } catch (URISyntaxException e) {
766                    throw ObjectHelper.wrapRuntimeCamelException(e);
767                }
768                from = from + "?" + query;
769            }
770
771            String path = getPath();
772            String s1 = FileUtil.stripTrailingSeparator(path);
773            String s2 = FileUtil.stripLeadingSeparator(verb.getUri());
774            String allPath;
775            if (s1 != null && s2 != null) {
776                allPath = s1 + "/" + s2;
777            } else if (path != null) {
778                allPath = path;
779            } else {
780                allPath = verb.getUri();
781            }
782
783            // each {} is a parameter (url templating)
784            String[] arr = allPath.split("\\/");
785            for (String a : arr) {
786                // need to resolve property placeholders first
787                try {
788                    a = camelContext.resolvePropertyPlaceholders(a);
789                } catch (Exception e) {
790                    throw ObjectHelper.wrapRuntimeCamelException(e);
791                }
792                if (a.startsWith("{") && a.endsWith("}")) {
793                    String key = a.substring(1, a.length() - 1);
794                    //  merge if exists
795                    boolean found = false;
796                    for (RestOperationParamDefinition param : verb.getParams()) {
797                        // name is mandatory
798                        String name = param.getName();
799                        ObjectHelper.notEmpty(name, "parameter name");
800                        // need to resolve property placeholders first
801                        try {
802                            name = camelContext.resolvePropertyPlaceholders(name);
803                        } catch (Exception e) {
804                            throw ObjectHelper.wrapRuntimeCamelException(e);
805                        }
806                        if (name.equalsIgnoreCase(key)) {
807                            param.type(RestParamType.path);
808                            found = true;
809                            break;
810                        }
811                    }
812                    if (!found) {
813                        param(verb).name(key).type(RestParamType.path).endParam();
814                    }
815                }
816            }
817
818            if (verb.getType() != null) {
819                String bodyType = verb.getType();
820                if (bodyType.endsWith("[]")) {
821                    bodyType = "List[" + bodyType.substring(0, bodyType.length() - 2) + "]";
822                }
823                RestOperationParamDefinition param = findParam(verb, RestParamType.body.name());
824                if (param == null) {
825                    // must be body type and set the model class as data type
826                    param(verb).name(RestParamType.body.name()).type(RestParamType.body).dataType(bodyType).endParam();
827                } else {
828                    // must be body type and set the model class as data type
829                    param.type(RestParamType.body).dataType(bodyType);
830                }
831            }
832
833            // the route should be from this rest endpoint
834            route.fromRest(from);
835            route.id(routeId);
836            route.setRestDefinition(this);
837            answer.add(route);
838        }
839    }
840
841    private String buildUri(VerbDefinition verb) {
842        if (path != null && verb.getUri() != null) {
843            return path + ":" + verb.getUri();
844        } else if (path != null) {
845            return path;
846        } else if (verb.getUri() != null) {
847            return verb.getUri();
848        } else {
849            return "";
850        }
851    }
852
853    private RestOperationParamDefinition findParam(VerbDefinition verb, String name) {
854        for (RestOperationParamDefinition param : verb.getParams()) {
855            if (name.equals(param.getName())) {
856                return param;
857            }
858        }
859        return null;
860    }
861
862}