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