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 = "auto")
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 auto
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     */
561    public RestConfigurationDefinition componentProperty(String key, String value) {
562        RestPropertyDefinition prop = new RestPropertyDefinition();
563        prop.setKey(key);
564        prop.setValue(value);
565        getComponentProperties().add(prop);
566        return this;
567    }
568
569    /**
570     * For additional configuration options on endpoint level
571     */
572    public RestConfigurationDefinition endpointProperty(String key, String value) {
573        RestPropertyDefinition prop = new RestPropertyDefinition();
574        prop.setKey(key);
575        prop.setValue(value);
576        getEndpointProperties().add(prop);
577        return this;
578    }
579
580    /**
581     * For additional configuration options on consumer level
582     */
583    public RestConfigurationDefinition consumerProperty(String key, String value) {
584        RestPropertyDefinition prop = new RestPropertyDefinition();
585        prop.setKey(key);
586        prop.setValue(value);
587        getConsumerProperties().add(prop);
588        return this;
589    }
590
591    /**
592     * For additional configuration options on data format level
593     */
594    public RestConfigurationDefinition dataFormatProperty(String key, String value) {
595        RestPropertyDefinition prop = new RestPropertyDefinition();
596        prop.setKey(key);
597        prop.setValue(value);
598        getDataFormatProperties().add(prop);
599        return this;
600    }
601
602    /**
603     * For configuring an api property, such as <tt>api.title</tt>, or <tt>api.version</tt>.
604     */
605    public RestConfigurationDefinition apiProperty(String key, String value) {
606        RestPropertyDefinition prop = new RestPropertyDefinition();
607        prop.setKey(key);
608        prop.setValue(value);
609        getApiProperties().add(prop);
610        return this;
611    }
612
613    /**
614     * For configuring CORS headers
615     */
616    public RestConfigurationDefinition corsHeaderProperty(String key, String value) {
617        RestPropertyDefinition prop = new RestPropertyDefinition();
618        prop.setKey(key);
619        prop.setValue(value);
620        getCorsHeaders().add(prop);
621        return this;
622    }
623
624    // Implementation
625    //-------------------------------------------------------------------------
626
627    /**
628     * Creates a {@link org.apache.camel.spi.RestConfiguration} instance based on the definition
629     *
630     * @param context     the camel context
631     * @return the configuration
632     * @throws Exception is thrown if error creating the configuration
633     */
634    public RestConfiguration asRestConfiguration(CamelContext context) throws Exception {
635        RestConfiguration answer = new RestConfiguration();
636        if (component != null) {
637            answer.setComponent(CamelContextHelper.parseText(context, component));
638        }
639        if (apiComponent != null) {
640            answer.setApiComponent(CamelContextHelper.parseText(context, apiComponent));
641        }
642        if (scheme != null) {
643            answer.setScheme(CamelContextHelper.parseText(context, scheme));
644        }
645        if (host != null) {
646            answer.setHost(CamelContextHelper.parseText(context, host));
647        }
648        if (port != null) {
649            answer.setPort(CamelContextHelper.parseInteger(context, port));
650        }
651        if (apiContextPath != null) {
652            answer.setApiContextPath(CamelContextHelper.parseText(context, apiContextPath));
653        }
654        if (apiContextRouteId != null) {
655            answer.setApiContextRouteId(CamelContextHelper.parseText(context, apiContextRouteId));
656        }
657        if (apiContextIdPattern != null) {
658            // special to allow #name# to refer to itself
659            if ("#name#".equals(apiComponent)) {
660                answer.setApiContextIdPattern(context.getName());
661            } else {
662                answer.setApiContextIdPattern(CamelContextHelper.parseText(context, apiContextIdPattern));
663            }
664        }
665        if (apiContextListing != null) {
666            answer.setApiContextListing(apiContextListing);
667        }
668        if (contextPath != null) {
669            answer.setContextPath(CamelContextHelper.parseText(context, contextPath));
670        }
671        if (hostNameResolver != null) {
672            answer.setRestHostNameResolver(hostNameResolver.name());
673        }
674        if (bindingMode != null) {
675            answer.setBindingMode(bindingMode.name());
676        }
677        if (skipBindingOnErrorCode != null) {
678            answer.setSkipBindingOnErrorCode(skipBindingOnErrorCode);
679        }
680        if (enableCORS != null) {
681            answer.setEnableCORS(enableCORS);
682        }
683        if (jsonDataFormat != null) {
684            answer.setJsonDataFormat(jsonDataFormat);
685        }
686        if (xmlDataFormat != null) {
687            answer.setXmlDataFormat(xmlDataFormat);
688        }
689        if (!componentProperties.isEmpty()) {
690            Map<String, Object> props = new HashMap<String, Object>();
691            for (RestPropertyDefinition prop : componentProperties) {
692                String key = prop.getKey();
693                String value = CamelContextHelper.parseText(context, prop.getValue());
694                props.put(key, value);
695            }
696            answer.setComponentProperties(props);
697        }
698        if (!endpointProperties.isEmpty()) {
699            Map<String, Object> props = new HashMap<String, Object>();
700            for (RestPropertyDefinition prop : endpointProperties) {
701                String key = prop.getKey();
702                String value = CamelContextHelper.parseText(context, prop.getValue());
703                props.put(key, value);
704            }
705            answer.setEndpointProperties(props);
706        }
707        if (!consumerProperties.isEmpty()) {
708            Map<String, Object> props = new HashMap<String, Object>();
709            for (RestPropertyDefinition prop : consumerProperties) {
710                String key = prop.getKey();
711                String value = CamelContextHelper.parseText(context, prop.getValue());
712                props.put(key, value);
713            }
714            answer.setConsumerProperties(props);
715        }
716        if (!dataFormatProperties.isEmpty()) {
717            Map<String, Object> props = new HashMap<String, Object>();
718            for (RestPropertyDefinition prop : dataFormatProperties) {
719                String key = prop.getKey();
720                String value = CamelContextHelper.parseText(context, prop.getValue());
721                props.put(key, value);
722            }
723            answer.setDataFormatProperties(props);
724        }
725        if (!apiProperties.isEmpty()) {
726            Map<String, Object> props = new HashMap<String, Object>();
727            for (RestPropertyDefinition prop : apiProperties) {
728                String key = prop.getKey();
729                String value = CamelContextHelper.parseText(context, prop.getValue());
730                props.put(key, value);
731            }
732            answer.setApiProperties(props);
733        }
734        if (!corsHeaders.isEmpty()) {
735            Map<String, String> props = new HashMap<String, String>();
736            for (RestPropertyDefinition prop : corsHeaders) {
737                String key = prop.getKey();
738                String value = CamelContextHelper.parseText(context, prop.getValue());
739                props.put(key, value);
740            }
741            answer.setCorsHeaders(props);
742        }
743        return answer;
744    }
745
746}