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