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