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.util.component; 018 019import java.util.HashMap; 020import java.util.List; 021import java.util.Map; 022import java.util.Set; 023 024import org.apache.camel.AsyncCallback; 025import org.apache.camel.Exchange; 026import org.apache.camel.RuntimeCamelException; 027import org.apache.camel.impl.DefaultAsyncProducer; 028import org.apache.camel.util.ObjectHelper; 029import org.slf4j.Logger; 030import org.slf4j.LoggerFactory; 031 032/** 033 * Base class for API based Producers 034 */ 035public abstract class AbstractApiProducer<E extends Enum<E> & ApiName, T> 036 extends DefaultAsyncProducer implements PropertiesInterceptor, ResultInterceptor { 037 038 // API Endpoint 039 protected final AbstractApiEndpoint<E, T> endpoint; 040 041 // properties helper 042 protected final ApiMethodPropertiesHelper<T> propertiesHelper; 043 044 // method helper 045 protected final ApiMethodHelper<?> methodHelper; 046 047 // logger 048 private final transient Logger log = LoggerFactory.getLogger(getClass()); 049 050 public AbstractApiProducer(AbstractApiEndpoint<E, T> endpoint, ApiMethodPropertiesHelper<T> propertiesHelper) { 051 super(endpoint); 052 this.propertiesHelper = propertiesHelper; 053 this.endpoint = endpoint; 054 this.methodHelper = endpoint.getMethodHelper(); 055 } 056 057 @Override 058 public boolean process(final Exchange exchange, final AsyncCallback callback) { 059 // properties for method arguments 060 final Map<String, Object> properties = new HashMap<String, Object>(); 061 properties.putAll(endpoint.getEndpointProperties()); 062 propertiesHelper.getExchangeProperties(exchange, properties); 063 064 // let the endpoint and the Producer intercept properties 065 endpoint.interceptProperties(properties); 066 interceptProperties(properties); 067 068 // decide which method to invoke 069 final ApiMethod method = findMethod(exchange, properties); 070 if (method == null) { 071 // synchronous failure 072 callback.done(true); 073 return true; 074 } 075 076 // create a runnable invocation task to be submitted on a background thread pool 077 // this way we avoid blocking the current thread for long running methods 078 Runnable invocation = new Runnable() { 079 @Override 080 public void run() { 081 try { 082 if (log.isDebugEnabled()) { 083 log.debug("Invoking operation {} with {}", method.getName(), properties.keySet()); 084 } 085 086 Object result = doInvokeMethod(method, properties); 087 088 // producer returns a single response, even for methods with List return types 089 exchange.getOut().setBody(result); 090 // copy headers 091 exchange.getOut().setHeaders(exchange.getIn().getHeaders()); 092 093 interceptResult(result, exchange); 094 095 } catch (Throwable t) { 096 exchange.setException(ObjectHelper.wrapRuntimeCamelException(t)); 097 } finally { 098 callback.done(false); 099 } 100 } 101 }; 102 103 endpoint.getExecutorService().submit(invocation); 104 return false; 105 } 106 107 @Override 108 public void interceptProperties(Map<String, Object> properties) { 109 // do nothing by default 110 } 111 112 /** 113 * Invoke the API method. Derived classes can override, but MUST call super.doInvokeMethod(). 114 * @param method API method to invoke. 115 * @param properties method arguments from endpoint properties and exchange In headers. 116 * @return API method invocation result. 117 * @throws RuntimeCamelException on error. Exceptions thrown by API method are wrapped. 118 */ 119 protected Object doInvokeMethod(ApiMethod method, Map<String, Object> properties) throws RuntimeCamelException { 120 return ApiMethodHelper.invokeMethod(endpoint.getApiProxy(method, properties), method, properties); 121 } 122 123 @Override 124 public final Object splitResult(Object result) { 125 // producer never splits results 126 return result; 127 } 128 129 @Override 130 public void interceptResult(Object methodResult, Exchange resultExchange) { 131 // do nothing by default 132 } 133 134 protected ApiMethod findMethod(Exchange exchange, Map<String, Object> properties) { 135 136 ApiMethod method = null; 137 final List<ApiMethod> candidates = endpoint.getCandidates(); 138 if (processInBody(exchange, properties)) { 139 140 // filter candidates based on endpoint and exchange properties 141 final Set<String> argNames = properties.keySet(); 142 final List<ApiMethod> filteredMethods = methodHelper.filterMethods( 143 candidates, 144 ApiMethodHelper.MatchType.SUPER_SET, 145 argNames); 146 147 // get the method to call 148 if (filteredMethods.isEmpty()) { 149 throw new RuntimeCamelException(String.format("Missing properties for %s, need one or more from %s", 150 endpoint.getMethodName(), 151 methodHelper.getMissingProperties(endpoint.getMethodName(), argNames)) 152 ); 153 } else if (filteredMethods.size() == 1) { 154 // found an exact match 155 method = filteredMethods.get(0); 156 } else { 157 method = ApiMethodHelper.getHighestPriorityMethod(filteredMethods); 158 log.warn("Calling highest priority operation {} from operations {}", method, filteredMethods); 159 } 160 } 161 162 return method; 163 } 164 165 // returns false on exception, which is set in exchange 166 private boolean processInBody(Exchange exchange, Map<String, Object> properties) { 167 final String inBodyProperty = endpoint.getInBody(); 168 if (inBodyProperty != null) { 169 170 Object value = exchange.getIn().getBody(); 171 if (value != null) { 172 try { 173 value = endpoint.getCamelContext().getTypeConverter().mandatoryConvertTo( 174 endpoint.getConfiguration().getClass().getDeclaredField(inBodyProperty).getType(), 175 exchange, value); 176 } catch (Exception e) { 177 exchange.setException(new RuntimeCamelException(String.format( 178 "Error converting value %s to property %s: %s", value, inBodyProperty, e.getMessage()), e)); 179 180 return false; 181 } 182 } else { 183 // allow null values for inBody only if its a nullable option 184 if (!methodHelper.getNullableArguments().contains(inBodyProperty)) { 185 exchange.setException(new NullPointerException(inBodyProperty)); 186 187 return false; 188 } 189 } 190 191 log.debug("Property [{}] has message body value {}", inBodyProperty, value); 192 properties.put(inBodyProperty, value); 193 } 194 195 return true; 196 } 197}