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