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