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.util.ArrayList;
020import java.util.HashMap;
021import java.util.List;
022import java.util.Map;
023import javax.xml.bind.annotation.XmlAccessType;
024import javax.xml.bind.annotation.XmlAccessorType;
025import javax.xml.bind.annotation.XmlAttribute;
026import javax.xml.bind.annotation.XmlElement;
027import javax.xml.bind.annotation.XmlRootElement;
028
029import org.apache.camel.CamelContext;
030import org.apache.camel.spi.Metadata;
031import org.apache.camel.spi.RestConfiguration;
032import org.apache.camel.util.CamelContextHelper;
033
034/**
035 * To configure rest
036 */
037@Metadata(label = "rest")
038@XmlRootElement(name = "restConfiguration")
039@XmlAccessorType(XmlAccessType.FIELD)
040public class RestConfigurationDefinition {
041
042    @XmlAttribute
043    private String component;
044
045    @XmlAttribute @Metadata(defaultValue = "swagger")
046    private String apiComponent;
047
048    @XmlAttribute
049    private String scheme;
050
051    @XmlAttribute
052    private String host;
053
054    @XmlAttribute
055    private String port;
056
057    @XmlAttribute
058    private String contextPath;
059
060    @XmlAttribute
061    private String apiContextPath;
062
063    @XmlAttribute
064    private String apiContextRouteId;
065
066    @XmlAttribute
067    private String apiContextIdPattern;
068
069    @XmlAttribute
070    private Boolean apiContextListing;
071
072    @XmlAttribute
073    private RestHostNameResolver hostNameResolver;
074
075    @XmlAttribute @Metadata(defaultValue = "off")
076    private RestBindingMode bindingMode;
077
078    @XmlAttribute
079    private Boolean skipBindingOnErrorCode;
080
081    @XmlAttribute
082    private Boolean enableCORS;
083
084    @XmlAttribute
085    private String jsonDataFormat;
086
087    @XmlAttribute
088    private String xmlDataFormat;
089
090    @XmlElement(name = "componentProperty")
091    private List<RestPropertyDefinition> componentProperties = new ArrayList<RestPropertyDefinition>();
092
093    @XmlElement(name = "endpointProperty")
094    private List<RestPropertyDefinition> endpointProperties = new ArrayList<RestPropertyDefinition>();
095
096    @XmlElement(name = "consumerProperty")
097    private List<RestPropertyDefinition> consumerProperties = new ArrayList<RestPropertyDefinition>();
098
099    @XmlElement(name = "dataFormatProperty")
100    private List<RestPropertyDefinition> dataFormatProperties = new ArrayList<RestPropertyDefinition>();
101
102    @XmlElement(name = "apiProperty")
103    private List<RestPropertyDefinition> apiProperties = new ArrayList<RestPropertyDefinition>();
104
105    @XmlElement(name = "corsHeaders")
106    private List<RestPropertyDefinition> corsHeaders = new ArrayList<RestPropertyDefinition>();
107
108    public String getComponent() {
109        return component;
110    }
111
112    /**
113     * The Camel Rest component to use for the REST transport, such as restlet, spark-rest.
114     * If no component has been explicit configured, then Camel will lookup if there is a Camel component
115     * that integrates with the Rest DSL, or if a org.apache.camel.spi.RestConsumerFactory is registered in the registry.
116     * If either one is found, then that is being used.
117     */
118    public void setComponent(String component) {
119        this.component = component;
120    }
121
122    public String getApiComponent() {
123        return apiComponent;
124    }
125
126    /**
127     * The name of the Camel component to use as the REST API (such as swagger)
128     */
129    public void setApiComponent(String apiComponent) {
130        this.apiComponent = apiComponent;
131    }
132
133    public String getScheme() {
134        return scheme;
135    }
136
137    /**
138     * The scheme to use for exposing the REST service. Usually http or https is supported.
139     * <p/>
140     * The default value is http
141     */
142    public void setScheme(String scheme) {
143        this.scheme = scheme;
144    }
145
146    public String getHost() {
147        return host;
148    }
149
150    /**
151     * The hostname to use for exposing the REST service.
152     */
153    public void setHost(String host) {
154        this.host = host;
155    }
156
157    public String getPort() {
158        return port;
159    }
160
161    /**
162     * The port number to use for exposing the REST service.
163     * Notice if you use servlet component then the port number configured here does not apply,
164     * as the port number in use is the actual port number the servlet component is using.
165     * eg if using Apache Tomcat its the tomcat http port, if using Apache Karaf its the HTTP service in Karaf
166     * that uses port 8181 by default etc. Though in those situations setting the port number here,
167     * allows tooling and JMX to know the port number, so its recommended to set the port number
168     * to the number that the servlet engine uses.
169     */
170    public void setPort(String port) {
171        this.port = port;
172    }
173
174    public String getContextPath() {
175        return contextPath;
176    }
177
178    /**
179     * Sets a leading context-path the REST services will be using.
180     * <p/>
181     * This can be used when using components such as <tt>camel-servlet</tt> where the deployed web application
182     * is deployed using a context-path. Or for components such as <tt>camel-jetty</tt> or <tt>camel-netty4-http</tt>
183     * that includes a HTTP server.
184     */
185    public void setContextPath(String contextPath) {
186        this.contextPath = contextPath;
187    }
188
189    public String getApiContextPath() {
190        return apiContextPath;
191    }
192
193    /**
194     * Sets a leading API context-path the REST API services will be using.
195     * <p/>
196     * This can be used when using components such as <tt>camel-servlet</tt> where the deployed web application
197     * is deployed using a context-path.
198     *
199     * @param contextPath the API context path
200     */
201    public void setApiContextPath(String contextPath) {
202        this.apiContextPath = contextPath;
203    }
204
205    public String getApiContextRouteId() {
206        return apiContextRouteId;
207    }
208
209    /**
210     * Sets the route id to use for the route that services the REST API.
211     * <p/>
212     * The route will by default use an auto assigned route id.
213     *
214     * @param apiContextRouteId  the route id
215     */
216    public void setApiContextRouteId(String apiContextRouteId) {
217        this.apiContextRouteId = apiContextRouteId;
218    }
219
220    public String getApiContextIdPattern() {
221        return apiContextIdPattern;
222    }
223
224    /**
225     * Sets an CamelContext id pattern to only allow Rest APIs from rest services within CamelContext's which name matches the pattern.
226     * <p/>
227     * The pattern <tt>#name#</tt> refers to the CamelContext name, to match on the current CamelContext only.
228     * For any other value, the pattern uses the rules from {@link org.apache.camel.util.EndpointHelper#matchPattern(String, String)}
229     *
230     * @param apiContextIdPattern  the pattern
231     */
232    public void setApiContextIdPattern(String apiContextIdPattern) {
233        this.apiContextIdPattern = apiContextIdPattern;
234    }
235
236    public Boolean getApiContextListing() {
237        return apiContextListing;
238    }
239
240    /**
241     * Sets whether listing of all available CamelContext's with REST services in the JVM is enabled. If enabled it allows to discover
242     * these contexts, if <tt>false</tt> then only the current CamelContext is in use.
243     */
244    public void setApiContextListing(Boolean apiContextListing) {
245        this.apiContextListing = apiContextListing;
246    }
247
248    public RestHostNameResolver getHostNameResolver() {
249        return hostNameResolver;
250    }
251
252    /**
253     * If no hostname has been explicit configured, then this resolver is used to compute the hostname the REST service will be using.
254     */
255    public void setHostNameResolver(RestHostNameResolver hostNameResolver) {
256        this.hostNameResolver = hostNameResolver;
257    }
258
259    public RestBindingMode getBindingMode() {
260        return bindingMode;
261    }
262
263    /**
264     * Sets the binding mode to use.
265     * <p/>
266     * The default value is off
267     */
268    public void setBindingMode(RestBindingMode bindingMode) {
269        this.bindingMode = bindingMode;
270    }
271
272    public Boolean getSkipBindingOnErrorCode() {
273        return skipBindingOnErrorCode;
274    }
275
276    /**
277     * Whether to skip binding on output if there is a custom HTTP error code header.
278     * This allows to build custom error messages that do not bind to json / xml etc, as success messages otherwise will do.
279     */
280    public void setSkipBindingOnErrorCode(Boolean skipBindingOnErrorCode) {
281        this.skipBindingOnErrorCode = skipBindingOnErrorCode;
282    }
283
284    public Boolean getEnableCORS() {
285        return enableCORS;
286    }
287
288    /**
289     * Whether to enable CORS headers in the HTTP response.
290     * <p/>
291     * The default value is false.
292     */
293    public void setEnableCORS(Boolean enableCORS) {
294        this.enableCORS = enableCORS;
295    }
296
297    public String getJsonDataFormat() {
298        return jsonDataFormat;
299    }
300
301    /**
302     * Name of specific json data format to use.
303     * By default json-jackson will be used.
304     * Important: This option is only for setting a custom name of the data format, not to refer to an existing data format instance.
305     */
306    public void setJsonDataFormat(String jsonDataFormat) {
307        this.jsonDataFormat = jsonDataFormat;
308    }
309
310    public String getXmlDataFormat() {
311        return xmlDataFormat;
312    }
313
314    /**
315     * Name of specific XML data format to use.
316     * By default jaxb will be used.
317     * Important: This option is only for setting a custom name of the data format, not to refer to an existing data format instance.
318     */
319    public void setXmlDataFormat(String xmlDataFormat) {
320        this.xmlDataFormat = xmlDataFormat;
321    }
322
323    public List<RestPropertyDefinition> getComponentProperties() {
324        return componentProperties;
325    }
326
327    /**
328     * Allows to configure as many additional properties for the rest component in use.
329     */
330    public void setComponentProperties(List<RestPropertyDefinition> componentProperties) {
331        this.componentProperties = componentProperties;
332    }
333
334    public List<RestPropertyDefinition> getEndpointProperties() {
335        return endpointProperties;
336    }
337
338    /**
339     * Allows to configure as many additional properties for the rest endpoint in use.
340     */
341    public void setEndpointProperties(List<RestPropertyDefinition> endpointProperties) {
342        this.endpointProperties = endpointProperties;
343    }
344
345    public List<RestPropertyDefinition> getConsumerProperties() {
346        return consumerProperties;
347    }
348
349    /**
350     * Allows to configure as many additional properties for the rest consumer in use.
351     */
352    public void setConsumerProperties(List<RestPropertyDefinition> consumerProperties) {
353        this.consumerProperties = consumerProperties;
354    }
355
356    public List<RestPropertyDefinition> getDataFormatProperties() {
357        return dataFormatProperties;
358    }
359
360    /**
361     * Allows to configure as many additional properties for the data formats in use.
362     * For example set property prettyPrint to true to have json outputted in pretty mode.
363     * The properties can be prefixed to denote the option is only for either JSON or XML and for either the IN or the OUT.
364     * The prefixes are:
365     * <ul>
366     *     <li>json.in.</li>
367     *     <li>json.out.</li>
368     *     <li>xml.in.</li>
369     *     <li>xml.out.</li>
370     * </ul>
371     * For example a key with value "xml.out.mustBeJAXBElement" is only for the XML data format for the outgoing.
372     * A key without a prefix is a common key for all situations.
373     */
374    public void setDataFormatProperties(List<RestPropertyDefinition> dataFormatProperties) {
375        this.dataFormatProperties = dataFormatProperties;
376    }
377
378    public List<RestPropertyDefinition> getApiProperties() {
379        return apiProperties;
380    }
381
382    /**
383     * Allows to configure as many additional properties for the api documentation (swagger).
384     * For example set property api.title to my cool stuff
385     */
386    public void setApiProperties(List<RestPropertyDefinition> apiProperties) {
387        this.apiProperties = apiProperties;
388    }
389
390    public List<RestPropertyDefinition> getCorsHeaders() {
391        return corsHeaders;
392    }
393
394    /**
395     * Allows to configure custom CORS headers.
396     */
397    public void setCorsHeaders(List<RestPropertyDefinition> corsHeaders) {
398        this.corsHeaders = corsHeaders;
399    }
400
401    // Fluent API
402    //-------------------------------------------------------------------------
403
404    /**
405     * To use a specific Camel rest component
406     */
407    public RestConfigurationDefinition component(String componentId) {
408        setComponent(componentId);
409        return this;
410    }
411
412    /**
413     * To use a specific Camel rest API component
414     */
415    public RestConfigurationDefinition apiComponent(String componentId) {
416        setApiComponent(componentId);
417        return this;
418    }
419
420    /**
421     * To use a specific scheme such as http/https
422     */
423    public RestConfigurationDefinition scheme(String scheme) {
424        setScheme(scheme);
425        return this;
426    }
427
428    /**
429     * To define the host to use, such as 0.0.0.0 or localhost
430     */
431    public RestConfigurationDefinition host(String host) {
432        setHost(host);
433        return this;
434    }
435
436    /**
437     * To specify the port number to use for the REST service
438     */
439    public RestConfigurationDefinition port(int port) {
440        setPort("" + port);
441        return this;
442    }
443
444    /**
445     * To specify the port number to use for the REST service
446     */
447    public RestConfigurationDefinition port(String port) {
448        setPort(port);
449        return this;
450    }
451
452    /**
453     * Sets a leading context-path the REST services will be using.
454     * <p/>
455     * This can be used when using components such as <tt>camel-servlet</tt> where the deployed web application
456     * is deployed using a context-path. Or for components such as <tt>camel-jetty</tt> or <tt>camel-netty4-http</tt>
457     * that includes a HTTP server.
458     */
459    public RestConfigurationDefinition apiContextPath(String contextPath) {
460        setApiContextPath(contextPath);
461        return this;
462    }
463
464    /**
465     * Sets the route id to use for the route that services the REST API.
466     */
467    public RestConfigurationDefinition apiContextRouteId(String routeId) {
468        setApiContextRouteId(routeId);
469        return this;
470    }
471
472    /**
473     * Sets an CamelContext id pattern to only allow Rest APIs from rest services within CamelContext's which name matches the pattern.
474     * <p/>
475     * The pattern uses the rules from {@link org.apache.camel.util.EndpointHelper#matchPattern(String, String)}
476     */
477    public RestConfigurationDefinition apiContextIdPattern(String pattern) {
478        setApiContextIdPattern(pattern);
479        return this;
480    }
481
482    /**
483     * Sets whether listing of all available CamelContext's with REST services in the JVM is enabled. If enabled it allows to discover
484     * these contexts, if <tt>false</tt> then only the current CamelContext is in use.
485     */
486    public RestConfigurationDefinition apiContextListing(boolean listing) {
487        setApiContextListing(listing);
488        return this;
489    }
490
491    /**
492     * Sets a leading context-path the REST services will be using.
493     * <p/>
494     * This can be used when using components such as <tt>camel-servlet</tt> where the deployed web application
495     * is deployed using a context-path.
496     */
497    public RestConfigurationDefinition contextPath(String contextPath) {
498        setContextPath(contextPath);
499        return this;
500    }
501
502    /**
503     * To specify the hostname resolver
504     */
505    public RestConfigurationDefinition hostNameResolver(RestHostNameResolver hostNameResolver) {
506        setHostNameResolver(hostNameResolver);
507        return this;
508    }
509
510    /**
511     * To specify the binding mode
512     */
513    public RestConfigurationDefinition bindingMode(RestBindingMode bindingMode) {
514        setBindingMode(bindingMode);
515        return this;
516    }
517
518    /**
519     * To specify whether to skip binding output if there is a custom HTTP error code
520     */
521    public RestConfigurationDefinition skipBindingOnErrorCode(boolean skipBindingOnErrorCode) {
522        setSkipBindingOnErrorCode(skipBindingOnErrorCode);
523        return this;
524    }
525
526    /**
527     * To specify whether to enable CORS which means Camel will automatic include CORS in the HTTP headers in the response.
528     */
529    public RestConfigurationDefinition enableCORS(boolean enableCORS) {
530        setEnableCORS(enableCORS);
531        return this;
532    }
533
534    /**
535     * To use a specific json data format
536     * <p/>
537     * <b>Important:</b> This option is only for setting a custom name of the data format, not to refer to an existing data format instance.
538     *
539     * @param name  name of the data format to {@link org.apache.camel.CamelContext#resolveDataFormat(java.lang.String) resolve}
540     */
541    public RestConfigurationDefinition jsonDataFormat(String name) {
542        setJsonDataFormat(name);
543        return this;
544    }
545
546    /**
547     * To use a specific XML data format
548     * <p/>
549     * <b>Important:</b> This option is only for setting a custom name of the data format, not to refer to an existing data format instance.
550     *
551     * @param name  name of the data format to {@link org.apache.camel.CamelContext#resolveDataFormat(java.lang.String) resolve}
552     */
553    public RestConfigurationDefinition xmlDataFormat(String name) {
554        setXmlDataFormat(name);
555        return this;
556    }
557
558    /**
559     * For additional configuration options on component level
560     * <p/>
561     * The value can use <tt>#</tt> to refer to a bean to lookup in the registry.
562     */
563    public RestConfigurationDefinition componentProperty(String key, String value) {
564        RestPropertyDefinition prop = new RestPropertyDefinition();
565        prop.setKey(key);
566        prop.setValue(value);
567        getComponentProperties().add(prop);
568        return this;
569    }
570
571    /**
572     * For additional configuration options on endpoint level
573     * <p/>
574     * The value can use <tt>#</tt> to refer to a bean to lookup in the registry.
575     */
576    public RestConfigurationDefinition endpointProperty(String key, String value) {
577        RestPropertyDefinition prop = new RestPropertyDefinition();
578        prop.setKey(key);
579        prop.setValue(value);
580        getEndpointProperties().add(prop);
581        return this;
582    }
583
584    /**
585     * For additional configuration options on consumer level
586     * <p/>
587     * The value can use <tt>#</tt> to refer to a bean to lookup in the registry.
588     */
589    public RestConfigurationDefinition consumerProperty(String key, String value) {
590        RestPropertyDefinition prop = new RestPropertyDefinition();
591        prop.setKey(key);
592        prop.setValue(value);
593        getConsumerProperties().add(prop);
594        return this;
595    }
596
597    /**
598     * For additional configuration options on data format level
599     * <p/>
600     * The value can use <tt>#</tt> to refer to a bean to lookup in the registry.
601     */
602    public RestConfigurationDefinition dataFormatProperty(String key, String value) {
603        RestPropertyDefinition prop = new RestPropertyDefinition();
604        prop.setKey(key);
605        prop.setValue(value);
606        getDataFormatProperties().add(prop);
607        return this;
608    }
609
610    /**
611     * For configuring an api property, such as <tt>api.title</tt>, or <tt>api.version</tt>.
612     */
613    public RestConfigurationDefinition apiProperty(String key, String value) {
614        RestPropertyDefinition prop = new RestPropertyDefinition();
615        prop.setKey(key);
616        prop.setValue(value);
617        getApiProperties().add(prop);
618        return this;
619    }
620
621    /**
622     * For configuring CORS headers
623     */
624    public RestConfigurationDefinition corsHeaderProperty(String key, String value) {
625        RestPropertyDefinition prop = new RestPropertyDefinition();
626        prop.setKey(key);
627        prop.setValue(value);
628        getCorsHeaders().add(prop);
629        return this;
630    }
631
632    // Implementation
633    //-------------------------------------------------------------------------
634
635    /**
636     * Creates a {@link org.apache.camel.spi.RestConfiguration} instance based on the definition
637     *
638     * @param context     the camel context
639     * @return the configuration
640     * @throws Exception is thrown if error creating the configuration
641     */
642    public RestConfiguration asRestConfiguration(CamelContext context) throws Exception {
643        RestConfiguration answer = new RestConfiguration();
644        if (component != null) {
645            answer.setComponent(CamelContextHelper.parseText(context, component));
646        }
647        if (apiComponent != null) {
648            answer.setApiComponent(CamelContextHelper.parseText(context, apiComponent));
649        }
650        if (scheme != null) {
651            answer.setScheme(CamelContextHelper.parseText(context, scheme));
652        }
653        if (host != null) {
654            answer.setHost(CamelContextHelper.parseText(context, host));
655        }
656        if (port != null) {
657            answer.setPort(CamelContextHelper.parseInteger(context, port));
658        }
659        if (apiContextPath != null) {
660            answer.setApiContextPath(CamelContextHelper.parseText(context, apiContextPath));
661        }
662        if (apiContextRouteId != null) {
663            answer.setApiContextRouteId(CamelContextHelper.parseText(context, apiContextRouteId));
664        }
665        if (apiContextIdPattern != null) {
666            // special to allow #name# to refer to itself
667            if ("#name#".equals(apiComponent)) {
668                answer.setApiContextIdPattern(context.getName());
669            } else {
670                answer.setApiContextIdPattern(CamelContextHelper.parseText(context, apiContextIdPattern));
671            }
672        }
673        if (apiContextListing != null) {
674            answer.setApiContextListing(apiContextListing);
675        }
676        if (contextPath != null) {
677            answer.setContextPath(CamelContextHelper.parseText(context, contextPath));
678        }
679        if (hostNameResolver != null) {
680            answer.setRestHostNameResolver(hostNameResolver.name());
681        }
682        if (bindingMode != null) {
683            answer.setBindingMode(bindingMode.name());
684        }
685        if (skipBindingOnErrorCode != null) {
686            answer.setSkipBindingOnErrorCode(skipBindingOnErrorCode);
687        }
688        if (enableCORS != null) {
689            answer.setEnableCORS(enableCORS);
690        }
691        if (jsonDataFormat != null) {
692            answer.setJsonDataFormat(jsonDataFormat);
693        }
694        if (xmlDataFormat != null) {
695            answer.setXmlDataFormat(xmlDataFormat);
696        }
697        if (!componentProperties.isEmpty()) {
698            Map<String, Object> props = new HashMap<String, Object>();
699            for (RestPropertyDefinition prop : componentProperties) {
700                String key = prop.getKey();
701                String value = CamelContextHelper.parseText(context, prop.getValue());
702                props.put(key, value);
703            }
704            answer.setComponentProperties(props);
705        }
706        if (!endpointProperties.isEmpty()) {
707            Map<String, Object> props = new HashMap<String, Object>();
708            for (RestPropertyDefinition prop : endpointProperties) {
709                String key = prop.getKey();
710                String value = CamelContextHelper.parseText(context, prop.getValue());
711                props.put(key, value);
712            }
713            answer.setEndpointProperties(props);
714        }
715        if (!consumerProperties.isEmpty()) {
716            Map<String, Object> props = new HashMap<String, Object>();
717            for (RestPropertyDefinition prop : consumerProperties) {
718                String key = prop.getKey();
719                String value = CamelContextHelper.parseText(context, prop.getValue());
720                props.put(key, value);
721            }
722            answer.setConsumerProperties(props);
723        }
724        if (!dataFormatProperties.isEmpty()) {
725            Map<String, Object> props = new HashMap<String, Object>();
726            for (RestPropertyDefinition prop : dataFormatProperties) {
727                String key = prop.getKey();
728                String value = CamelContextHelper.parseText(context, prop.getValue());
729                props.put(key, value);
730            }
731            answer.setDataFormatProperties(props);
732        }
733        if (!apiProperties.isEmpty()) {
734            Map<String, Object> props = new HashMap<String, Object>();
735            for (RestPropertyDefinition prop : apiProperties) {
736                String key = prop.getKey();
737                String value = CamelContextHelper.parseText(context, prop.getValue());
738                props.put(key, value);
739            }
740            answer.setApiProperties(props);
741        }
742        if (!corsHeaders.isEmpty()) {
743            Map<String, String> props = new HashMap<String, String>();
744            for (RestPropertyDefinition prop : corsHeaders) {
745                String key = prop.getKey();
746                String value = CamelContextHelper.parseText(context, prop.getValue());
747                props.put(key, value);
748            }
749            answer.setCorsHeaders(props);
750        }
751        return answer;
752    }
753
754}