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}