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.HashMap;
020import java.util.Map;
021import javax.xml.bind.JAXBContext;
022import javax.xml.bind.annotation.XmlAccessType;
023import javax.xml.bind.annotation.XmlAccessorType;
024import javax.xml.bind.annotation.XmlAttribute;
025import javax.xml.bind.annotation.XmlRootElement;
026
027import org.apache.camel.CamelContext;
028import org.apache.camel.Processor;
029import org.apache.camel.model.NoOutputDefinition;
030import org.apache.camel.processor.binding.RestBindingProcessor;
031import org.apache.camel.spi.DataFormat;
032import org.apache.camel.spi.RouteContext;
033import org.apache.camel.util.IntrospectionSupport;
034
035@XmlRootElement(name = "restBinding")
036@XmlAccessorType(XmlAccessType.FIELD)
037public class RestBindingDefinition extends NoOutputDefinition<RestBindingDefinition> {
038
039    @XmlAttribute
040    private String consumes;
041
042    @XmlAttribute
043    private String produces;
044
045    @XmlAttribute
046    private RestBindingMode bindingMode;
047
048    @XmlAttribute
049    private String type;
050
051    @XmlAttribute
052    private String outType;
053
054    @XmlAttribute
055    private Boolean skipBindingOnErrorCode;
056
057    @XmlAttribute
058    private Boolean enableCORS;
059
060    @Override
061    public String toString() {
062        return "RestBinding";
063    }
064
065    @Override
066    public String getShortName() {
067        return "restBinding";
068    }
069
070    @Override
071    public Processor createProcessor(RouteContext routeContext) throws Exception {
072
073        CamelContext context = routeContext.getCamelContext();
074
075        // these options can be overriden per rest verb
076        String mode = context.getRestConfiguration().getBindingMode().name();
077        if (bindingMode != null) {
078            mode = bindingMode.name();
079        }
080        boolean cors = context.getRestConfiguration().isEnableCORS();
081        if (enableCORS != null) {
082            cors = enableCORS;
083        }
084        boolean skip = context.getRestConfiguration().isSkipBindingOnErrorCode();
085        if (skipBindingOnErrorCode != null) {
086            skip = skipBindingOnErrorCode;
087        }
088
089        // cors headers
090        Map<String, String> corsHeaders = context.getRestConfiguration().getCorsHeaders();
091
092        if (mode == null || "off".equals(mode)) {
093            // binding mode is off, so create a off mode binding processor
094            return new RestBindingProcessor(null, null, null, null, consumes, produces, mode, skip, cors, corsHeaders);
095        }
096
097        // setup json data format
098        String name = context.getRestConfiguration().getJsonDataFormat();
099        if (name != null) {
100            // must only be a name, not refer to an existing instance
101            Object instance = context.getRegistry().lookupByName(name);
102            if (instance != null) {
103                throw new IllegalArgumentException("JsonDataFormat name: " + name + " must not be an existing bean instance from the registry");
104            }
105        } else {
106            name = "json-jackson";
107        }
108        // this will create a new instance as the name was not already pre-created
109        DataFormat json = context.resolveDataFormat(name);
110        DataFormat outJson = context.resolveDataFormat(name);
111
112        // is json binding required?
113        if (mode.contains("json") && json == null) {
114            throw new IllegalArgumentException("JSon DataFormat " + name + " not found.");
115        }
116
117        if (json != null) {
118            Class<?> clazz = null;
119            if (type != null) {
120                String typeName = type.endsWith("[]") ? type.substring(0, type.length() - 2) : type;
121                clazz = context.getClassResolver().resolveMandatoryClass(typeName);
122            }
123            if (clazz != null) {
124                IntrospectionSupport.setProperty(context.getTypeConverter(), json, "unmarshalType", clazz);
125                IntrospectionSupport.setProperty(context.getTypeConverter(), json, "useList", type.endsWith("[]"));
126            }
127            setAdditionalConfiguration(context, json, "json.in.");
128            context.addService(json);
129
130            Class<?> outClazz = null;
131            if (outType != null) {
132                String typeName = outType.endsWith("[]") ? outType.substring(0, outType.length() - 2) : outType;
133                outClazz = context.getClassResolver().resolveMandatoryClass(typeName);
134            }
135            if (outClazz != null) {
136                IntrospectionSupport.setProperty(context.getTypeConverter(), outJson, "unmarshalType", outClazz);
137                IntrospectionSupport.setProperty(context.getTypeConverter(), outJson, "useList", outType.endsWith("[]"));
138            }
139            setAdditionalConfiguration(context, outJson, "json.out.");
140            context.addService(outJson);
141        }
142
143        // setup xml data format
144        name = context.getRestConfiguration().getXmlDataFormat();
145        if (name != null) {
146            // must only be a name, not refer to an existing instance
147            Object instance = context.getRegistry().lookupByName(name);
148            if (instance != null) {
149                throw new IllegalArgumentException("XmlDataFormat name: " + name + " must not be an existing bean instance from the registry");
150            }
151        } else {
152            name = "jaxb";
153        }
154        // this will create a new instance as the name was not already pre-created
155        DataFormat jaxb = context.resolveDataFormat(name);
156        DataFormat outJaxb = context.resolveDataFormat(name);
157
158        // is xml binding required?
159        if (mode.contains("xml") && jaxb == null) {
160            throw new IllegalArgumentException("XML DataFormat " + name + " not found.");
161        }
162
163        if (jaxb != null) {
164            Class<?> clazz = null;
165            if (type != null) {
166                String typeName = type.endsWith("[]") ? type.substring(0, type.length() - 2) : type;
167                clazz = context.getClassResolver().resolveMandatoryClass(typeName);
168            }
169            if (clazz != null) {
170                JAXBContext jc = JAXBContext.newInstance(clazz);
171                IntrospectionSupport.setProperty(context.getTypeConverter(), jaxb, "context", jc);
172            }
173            setAdditionalConfiguration(context, jaxb, "xml.in.");
174            context.addService(jaxb);
175
176            Class<?> outClazz = null;
177            if (outType != null) {
178                String typeName = outType.endsWith("[]") ? outType.substring(0, outType.length() - 2) : outType;
179                outClazz = context.getClassResolver().resolveMandatoryClass(typeName);
180            }
181            if (outClazz != null) {
182                JAXBContext jc = JAXBContext.newInstance(outClazz);
183                IntrospectionSupport.setProperty(context.getTypeConverter(), outJaxb, "context", jc);
184            } else if (clazz != null) {
185                // fallback and use the context from the input
186                JAXBContext jc = JAXBContext.newInstance(clazz);
187                IntrospectionSupport.setProperty(context.getTypeConverter(), outJaxb, "context", jc);
188            }
189            setAdditionalConfiguration(context, outJaxb, "xml.out.");
190            context.addService(outJaxb);
191        }
192
193        return new RestBindingProcessor(json, jaxb, outJson, outJaxb, consumes, produces, mode, skip, cors, corsHeaders);
194    }
195
196    private void setAdditionalConfiguration(CamelContext context, DataFormat dataFormat, String prefix) throws Exception {
197        if (context.getRestConfiguration().getDataFormatProperties() != null && !context.getRestConfiguration().getDataFormatProperties().isEmpty()) {
198            // must use a copy as otherwise the options gets removed during introspection setProperties
199            Map<String, Object> copy = new HashMap<String, Object>();
200
201            // filter keys on prefix
202            // - either its a known prefix and must match the prefix parameter
203            // - or its a common configuration that we should always use
204            for (Map.Entry<String, Object> entry : context.getRestConfiguration().getDataFormatProperties().entrySet()) {
205                String key = entry.getKey();
206                String copyKey;
207                boolean known = isKeyKnownPrefix(key);
208                if (known) {
209                    // remove the prefix from the key to use
210                    copyKey = key.substring(prefix.length());
211                } else {
212                    // use the key as is
213                    copyKey = key;
214                }
215                if (!known || key.startsWith(prefix)) {
216                    copy.put(copyKey, entry.getValue());
217                }
218            }
219
220            IntrospectionSupport.setProperties(context.getTypeConverter(), dataFormat, copy);
221        }
222    }
223
224    private boolean isKeyKnownPrefix(String key) {
225        return key.startsWith("json.in.") || key.startsWith("json.out.") || key.startsWith("xml.in.") || key.startsWith("xml.out.");
226    }
227
228    public String getConsumes() {
229        return consumes;
230    }
231
232    public void setConsumes(String consumes) {
233        this.consumes = consumes;
234    }
235
236    public String getProduces() {
237        return produces;
238    }
239
240    public void setProduces(String produces) {
241        this.produces = produces;
242    }
243
244    public RestBindingMode getBindingMode() {
245        return bindingMode;
246    }
247
248    public void setBindingMode(RestBindingMode bindingMode) {
249        this.bindingMode = bindingMode;
250    }
251
252    public String getType() {
253        return type;
254    }
255
256    public void setType(String type) {
257        this.type = type;
258    }
259
260    public String getOutType() {
261        return outType;
262    }
263
264    public void setOutType(String outType) {
265        this.outType = outType;
266    }
267
268    public Boolean getSkipBindingOnErrorCode() {
269        return skipBindingOnErrorCode;
270    }
271
272    public void setSkipBindingOnErrorCode(Boolean skipBindingOnErrorCode) {
273        this.skipBindingOnErrorCode = skipBindingOnErrorCode;
274    }
275
276    public Boolean getEnableCORS() {
277        return enableCORS;
278    }
279
280    public void setEnableCORS(Boolean enableCORS) {
281        this.enableCORS = enableCORS;
282    }
283}