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