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.component.rest; 018 019import java.io.InputStream; 020import java.util.Locale; 021 022import org.apache.camel.AsyncCallback; 023import org.apache.camel.AsyncProcessor; 024import org.apache.camel.CamelContext; 025import org.apache.camel.CamelContextAware; 026import org.apache.camel.Exchange; 027import org.apache.camel.processor.DelegateAsyncProcessor; 028import org.apache.camel.processor.MarshalProcessor; 029import org.apache.camel.processor.UnmarshalProcessor; 030import org.apache.camel.processor.binding.BindingException; 031import org.apache.camel.spi.DataFormat; 032import org.apache.camel.util.ExchangeHelper; 033import org.apache.camel.util.ObjectHelper; 034import org.apache.camel.util.ServiceHelper; 035 036/** 037 * A {@link org.apache.camel.Processor} that binds the REST producer request and reply messages 038 * from sources of json or xml to Java Objects. 039 * <p/> 040 * The binding uses {@link org.apache.camel.spi.DataFormat} for the actual work to transform 041 * from xml/json to Java Objects and reverse again. 042 * <p/> 043 * The rest-dsl consumer side is implemented in {@link org.apache.camel.processor.RestBindingAdvice} 044 */ 045public class RestProducerBindingProcessor extends DelegateAsyncProcessor { 046 047 private final CamelContext camelContext; 048 private final AsyncProcessor jsonUnmarshal; 049 private final AsyncProcessor xmlUnmarshal; 050 private final AsyncProcessor jsonMarshal; 051 private final AsyncProcessor xmlMarshal; 052 private final String bindingMode; 053 private final boolean skipBindingOnErrorCode; 054 private final String outType; 055 056 public RestProducerBindingProcessor(AsyncProcessor processor, CamelContext camelContext, 057 DataFormat jsonDataFormat, DataFormat xmlDataFormat, 058 DataFormat outJsonDataFormat, DataFormat outXmlDataFormat, 059 String bindingMode, boolean skipBindingOnErrorCode, 060 String outType) { 061 062 super(processor); 063 064 this.camelContext = camelContext; 065 066 if (outJsonDataFormat != null) { 067 this.jsonUnmarshal = new UnmarshalProcessor(outJsonDataFormat); 068 } else { 069 this.jsonUnmarshal = null; 070 } 071 if (jsonDataFormat != null) { 072 this.jsonMarshal = new MarshalProcessor(jsonDataFormat); 073 } else { 074 this.jsonMarshal = null; 075 } 076 077 if (outXmlDataFormat != null) { 078 this.xmlUnmarshal = new UnmarshalProcessor(outXmlDataFormat); 079 } else { 080 this.xmlUnmarshal = null; 081 } 082 if (xmlDataFormat != null) { 083 this.xmlMarshal = new MarshalProcessor(xmlDataFormat); 084 } else { 085 this.xmlMarshal = null; 086 } 087 088 this.bindingMode = bindingMode; 089 this.skipBindingOnErrorCode = skipBindingOnErrorCode; 090 this.outType = outType; 091 } 092 093 @Override 094 public String toString() { 095 return "RestProducerBindingProcessor"; 096 } 097 098 @Override 099 protected void doStart() throws Exception { 100 // inject CamelContext before starting 101 if (jsonMarshal instanceof CamelContextAware) { 102 ((CamelContextAware) jsonMarshal).setCamelContext(camelContext); 103 } 104 if (jsonUnmarshal instanceof CamelContextAware) { 105 ((CamelContextAware) jsonUnmarshal).setCamelContext(camelContext); 106 } 107 if (xmlMarshal instanceof CamelContextAware) { 108 ((CamelContextAware) xmlMarshal).setCamelContext(camelContext); 109 } 110 if (xmlUnmarshal instanceof CamelContextAware) { 111 ((CamelContextAware) xmlUnmarshal).setCamelContext(camelContext); 112 } 113 ServiceHelper.startServices(jsonMarshal, jsonUnmarshal, xmlMarshal, xmlUnmarshal); 114 } 115 116 @Override 117 protected void doStop() throws Exception { 118 ServiceHelper.stopServices(jsonMarshal, jsonUnmarshal, xmlMarshal, xmlUnmarshal); 119 } 120 121 @Override 122 public boolean process(Exchange exchange, AsyncCallback callback) { 123 boolean isXml = false; 124 boolean isJson = false; 125 126 // skip before binding for empty/null body 127 Object body = exchange.getIn().getBody(); 128 if (ObjectHelper.isEmpty(body)) { 129 if (outType != null) { 130 // wrap callback to add reverse operation if we know the output type from the REST service 131 callback = new RestProducerBindingUnmarshalCallback(exchange, callback, jsonMarshal, xmlMarshal, false); 132 } 133 // okay now we can continue routing to the producer 134 return getProcessor().process(exchange, callback); 135 } 136 137 // we only need to perform before binding if the message body is POJO based 138 if (body instanceof String || body instanceof byte[]) { 139 // the body is text based and thus not POJO so no binding needed 140 if (outType != null) { 141 // wrap callback to add reverse operation if we know the output type from the REST service 142 callback = new RestProducerBindingUnmarshalCallback(exchange, callback, jsonMarshal, xmlMarshal, false); 143 } 144 // okay now we can continue routing to the producer 145 return getProcessor().process(exchange, callback); 146 } else { 147 // if its convertable to stream based then its not POJO based 148 InputStream is = camelContext.getTypeConverter().tryConvertTo(InputStream.class, exchange, body); 149 if (is != null) { 150 exchange.getIn().setBody(is); 151 if (outType != null) { 152 // wrap callback to add reverse operation if we know the output type from the REST service 153 callback = new RestProducerBindingUnmarshalCallback(exchange, callback, jsonMarshal, xmlMarshal, false); 154 } 155 // okay now we can continue routing to the producer 156 return getProcessor().process(exchange, callback); 157 } 158 } 159 160 // assume body is POJO based and binding needed 161 162 String contentType = ExchangeHelper.getContentType(exchange); 163 if (contentType != null) { 164 isXml = contentType.toLowerCase(Locale.ENGLISH).contains("xml"); 165 isJson = contentType.toLowerCase(Locale.ENGLISH).contains("json"); 166 } 167 168 // only allow xml/json if the binding mode allows that 169 isXml &= bindingMode.equals("auto") || bindingMode.contains("xml"); 170 isJson &= bindingMode.equals("auto") || bindingMode.contains("json"); 171 172 // if we do not yet know if its xml or json, then use the binding mode to know the mode 173 if (!isJson && !isXml) { 174 isXml = bindingMode.equals("auto") || bindingMode.contains("xml"); 175 isJson = bindingMode.equals("auto") || bindingMode.contains("json"); 176 } 177 178 // favor json over xml 179 if (isJson && jsonMarshal != null) { 180 try { 181 jsonMarshal.process(exchange); 182 } catch (Exception e) { 183 // we failed so cannot call producer 184 exchange.setException(e); 185 callback.done(true); 186 return true; 187 } 188 // need to prepare exchange first 189 ExchangeHelper.prepareOutToIn(exchange); 190 if (outType != null) { 191 // wrap callback to add reverse operation if we know the output type from the REST service 192 callback = new RestProducerBindingUnmarshalCallback(exchange, callback, jsonMarshal, xmlMarshal, false); 193 } 194 // okay now we can continue routing to the producer 195 return getProcessor().process(exchange, callback); 196 } else if (isXml && xmlMarshal != null) { 197 try { 198 xmlMarshal.process(exchange); 199 } catch (Exception e) { 200 // we failed so cannot call producer 201 exchange.setException(e); 202 callback.done(true); 203 return true; 204 } 205 // need to prepare exchange first 206 ExchangeHelper.prepareOutToIn(exchange); 207 if (outType != null) { 208 // wrap callback to add reverse operation if we know the output type from the REST service 209 callback = new RestProducerBindingUnmarshalCallback(exchange, callback, jsonMarshal, xmlMarshal, true); 210 } 211 // okay now we can continue routing to the producer 212 return getProcessor().process(exchange, callback); 213 } 214 215 // we could not bind 216 if ("off".equals(bindingMode) || bindingMode.equals("auto")) { 217 if (outType != null) { 218 // wrap callback to add reverse operation if we know the output type from the REST service 219 callback = new RestProducerBindingUnmarshalCallback(exchange, callback, jsonMarshal, xmlMarshal, false); 220 } 221 // okay now we can continue routing to the producer 222 return getProcessor().process(exchange, callback); 223 } else { 224 if (bindingMode.contains("xml")) { 225 exchange.setException(new BindingException("Cannot bind to xml as message body is not xml compatible", exchange)); 226 } else { 227 exchange.setException(new BindingException("Cannot bind to json as message body is not json compatible", exchange)); 228 } 229 // we failed so cannot call producer 230 callback.done(true); 231 return true; 232 } 233 } 234 235 private final class RestProducerBindingUnmarshalCallback implements AsyncCallback { 236 237 private final Exchange exchange; 238 private final AsyncCallback callback; 239 private final AsyncProcessor jsonMarshal; 240 private final AsyncProcessor xmlMarshal; 241 private boolean wasXml; 242 243 private RestProducerBindingUnmarshalCallback(Exchange exchange, AsyncCallback callback, 244 AsyncProcessor jsonMarshal, AsyncProcessor xmlMarshal, boolean wasXml) { 245 this.exchange = exchange; 246 this.callback = callback; 247 this.jsonMarshal = jsonMarshal; 248 this.xmlMarshal = xmlMarshal; 249 this.wasXml = wasXml; 250 } 251 252 @Override 253 public void done(boolean doneSync) { 254 try { 255 doDone(); 256 } catch (Throwable e) { 257 exchange.setException(e); 258 } finally { 259 // ensure callback is called 260 callback.done(doneSync); 261 } 262 } 263 264 private void doDone() { 265 // only unmarshal if there was no exception 266 if (exchange.getException() != null) { 267 return; 268 } 269 270 if (skipBindingOnErrorCode) { 271 Integer code = exchange.hasOut() ? exchange.getOut().getHeader(Exchange.HTTP_RESPONSE_CODE, Integer.class) : exchange.getIn().getHeader(Exchange.HTTP_RESPONSE_CODE, Integer.class); 272 // if there is a custom http error code then skip binding 273 if (code != null && code >= 300) { 274 return; 275 } 276 } 277 278 boolean isXml = false; 279 boolean isJson = false; 280 281 // check the content-type if its json or xml 282 String contentType = ExchangeHelper.getContentType(exchange); 283 if (contentType != null) { 284 isXml = contentType.toLowerCase(Locale.ENGLISH).contains("xml"); 285 isJson = contentType.toLowerCase(Locale.ENGLISH).contains("json"); 286 } 287 288 // only allow xml/json if the binding mode allows that (when off we still want to know if its xml or json) 289 if (bindingMode != null) { 290 isXml &= bindingMode.equals("off") || bindingMode.equals("auto") || bindingMode.contains("xml"); 291 isJson &= bindingMode.equals("off") || bindingMode.equals("auto") || bindingMode.contains("json"); 292 293 // if we do not yet know if its xml or json, then use the binding mode to know the mode 294 if (!isJson && !isXml) { 295 isXml = bindingMode.equals("auto") || bindingMode.contains("xml"); 296 isJson = bindingMode.equals("auto") || bindingMode.contains("json"); 297 } 298 } 299 300 // in case we have not yet been able to determine if xml or json, then use the same as in the unmarshaller 301 if (isXml && isJson) { 302 isXml = wasXml; 303 isJson = !wasXml; 304 } 305 306 // need to prepare exchange first 307 ExchangeHelper.prepareOutToIn(exchange); 308 309 // ensure there is a content type header (even if binding is off) 310 ensureHeaderContentType(isXml, isJson, exchange); 311 312 if (bindingMode == null || "off".equals(bindingMode)) { 313 // binding is off, so no message body binding 314 return; 315 } 316 317 // is there any unmarshaller at all 318 if (jsonUnmarshal == null && xmlUnmarshal == null) { 319 return; 320 } 321 322 // is the body empty 323 if ((exchange.hasOut() && exchange.getOut().getBody() == null) || (!exchange.hasOut() && exchange.getIn().getBody() == null)) { 324 return; 325 } 326 327 contentType = exchange.getIn().getHeader(Exchange.CONTENT_TYPE, String.class); 328 // need to lower-case so the contains check below can match if using upper case 329 contentType = contentType.toLowerCase(Locale.US); 330 try { 331 // favor json over xml 332 if (isJson && jsonUnmarshal != null) { 333 // only marshal if its json content type 334 if (contentType.contains("json")) { 335 jsonUnmarshal.process(exchange); 336 } 337 } else if (isXml && xmlUnmarshal != null) { 338 // only marshal if its xml content type 339 if (contentType.contains("xml")) { 340 xmlUnmarshal.process(exchange); 341 } 342 } else { 343 // we could not bind 344 if (bindingMode.equals("auto")) { 345 // okay for auto we do not mind if we could not bind 346 } else { 347 if (bindingMode.contains("xml")) { 348 exchange.setException(new BindingException("Cannot bind from xml as message body is not xml compatible", exchange)); 349 } else { 350 exchange.setException(new BindingException("Cannot bind from json as message body is not json compatible", exchange)); 351 } 352 } 353 } 354 } catch (Throwable e) { 355 exchange.setException(e); 356 } 357 } 358 359 private void ensureHeaderContentType(boolean isXml, boolean isJson, Exchange exchange) { 360 // favor json over xml 361 if (isJson) { 362 // make sure there is a content-type with json 363 String type = ExchangeHelper.getContentType(exchange); 364 if (type == null) { 365 exchange.getIn().setHeader(Exchange.CONTENT_TYPE, "application/json"); 366 } 367 } else if (isXml) { 368 // make sure there is a content-type with xml 369 String type = ExchangeHelper.getContentType(exchange); 370 if (type == null) { 371 exchange.getIn().setHeader(Exchange.CONTENT_TYPE, "application/xml"); 372 } 373 } 374 } 375 376 @Override 377 public String toString() { 378 return "RestProducerBindingUnmarshalCallback"; 379 } 380 } 381 382}