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.cloud; 018 019import java.util.ArrayList; 020import java.util.Collections; 021import java.util.HashMap; 022import java.util.List; 023import java.util.Map; 024 025import javax.xml.bind.annotation.XmlAccessType; 026import javax.xml.bind.annotation.XmlAccessorType; 027import javax.xml.bind.annotation.XmlAttribute; 028import javax.xml.bind.annotation.XmlElement; 029import javax.xml.bind.annotation.XmlElementRef; 030import javax.xml.bind.annotation.XmlRootElement; 031import javax.xml.bind.annotation.XmlTransient; 032 033import org.apache.camel.CamelContext; 034import org.apache.camel.Expression; 035import org.apache.camel.ExtendedCamelContext; 036import org.apache.camel.NoFactoryAvailableException; 037import org.apache.camel.cloud.ServiceCallConstants; 038import org.apache.camel.cloud.ServiceExpressionFactory; 039import org.apache.camel.model.IdentifiedType; 040import org.apache.camel.model.ProcessorDefinition; 041import org.apache.camel.model.PropertyDefinition; 042import org.apache.camel.model.language.ExpressionDefinition; 043import org.apache.camel.spi.Metadata; 044import org.apache.camel.support.CamelContextHelper; 045import org.apache.camel.support.PropertyBindingSupport; 046 047@Metadata(label = "routing,cloud") 048@XmlRootElement(name = "serviceExpression") 049@XmlAccessorType(XmlAccessType.FIELD) 050public class ServiceCallExpressionConfiguration extends IdentifiedType implements ServiceExpressionFactory { 051 @XmlTransient 052 private final ServiceCallDefinition parent; 053 @XmlTransient 054 private final String factoryKey; 055 @XmlElement(name = "properties") 056 @Metadata(label = "advanced") 057 private List<PropertyDefinition> properties; 058 @XmlAttribute 059 @Metadata(defaultValue = ServiceCallConstants.SERVICE_HOST) 060 private String hostHeader = ServiceCallConstants.SERVICE_HOST; 061 @XmlAttribute 062 @Metadata(defaultValue = ServiceCallConstants.SERVICE_PORT) 063 private String portHeader = ServiceCallConstants.SERVICE_PORT; 064 @XmlElementRef(required = false) 065 private ExpressionDefinition expressionType; 066 @XmlTransient 067 private Expression expression; 068 069 public ServiceCallExpressionConfiguration() { 070 this(null, null); 071 } 072 073 public ServiceCallExpressionConfiguration(ServiceCallDefinition parent, String factoryKey) { 074 this.parent = parent; 075 this.factoryKey = factoryKey; 076 } 077 078 public ServiceCallDefinition end() { 079 return this.parent; 080 } 081 082 public ProcessorDefinition<?> endParent() { 083 return this.parent.end(); 084 } 085 086 // ************************************************************************* 087 // 088 // ************************************************************************* 089 090 public List<PropertyDefinition> getProperties() { 091 return properties; 092 } 093 094 /** 095 * Set client properties to use. 096 * <p/> 097 * These properties are specific to what service call implementation are in 098 * use. For example if using ribbon, then the client properties are define 099 * in com.netflix.client.config.CommonClientConfigKey. 100 */ 101 public void setProperties(List<PropertyDefinition> properties) { 102 this.properties = properties; 103 } 104 105 /** 106 * Adds a custom property to use. 107 * <p/> 108 * These properties are specific to what service call implementation are in 109 * use. For example if using ribbon, then the client properties are define 110 * in com.netflix.client.config.CommonClientConfigKey. 111 */ 112 public ServiceCallExpressionConfiguration property(String key, String value) { 113 if (properties == null) { 114 properties = new ArrayList<>(); 115 } 116 PropertyDefinition prop = new PropertyDefinition(); 117 prop.setKey(key); 118 prop.setValue(value); 119 properties.add(prop); 120 return this; 121 } 122 123 protected Map<String, String> getPropertiesAsMap(CamelContext camelContext) throws Exception { 124 Map<String, String> answer; 125 126 if (properties == null || properties.isEmpty()) { 127 answer = Collections.emptyMap(); 128 } else { 129 answer = new HashMap<>(); 130 for (PropertyDefinition prop : properties) { 131 // support property placeholders 132 String key = CamelContextHelper.parseText(camelContext, prop.getKey()); 133 String value = CamelContextHelper.parseText(camelContext, prop.getValue()); 134 answer.put(key, value); 135 } 136 } 137 138 return answer; 139 } 140 141 public String getHostHeader() { 142 return hostHeader; 143 } 144 145 /** 146 * The header that holds the service host information, default 147 * ServiceCallConstants.SERVICE_HOST 148 */ 149 public void setHostHeader(String hostHeader) { 150 this.hostHeader = hostHeader; 151 } 152 153 public String getPortHeader() { 154 return portHeader; 155 } 156 157 /** 158 * The header that holds the service port information, default 159 * ServiceCallConstants.SERVICE_PORT 160 */ 161 public void setPortHeader(String portHeader) { 162 this.portHeader = portHeader; 163 } 164 165 public ExpressionDefinition getExpressionType() { 166 return expressionType; 167 } 168 169 public void setExpressionType(ExpressionDefinition expressionType) { 170 this.expressionType = expressionType; 171 } 172 173 public Expression getExpression() { 174 return expression; 175 } 176 177 public void setExpression(Expression expression) { 178 this.expression = expression; 179 } 180 181 /** 182 * The header that holds the service host information, default 183 * ServiceCallConstants.SERVICE_HOST 184 */ 185 public ServiceCallExpressionConfiguration hostHeader(String hostHeader) { 186 setHostHeader(hostHeader); 187 return this; 188 } 189 190 /** 191 * The header that holds the service port information, default 192 * ServiceCallConstants.SERVICE_PORT 193 */ 194 public ServiceCallExpressionConfiguration portHeader(String portHeader) { 195 setPortHeader(portHeader); 196 return this; 197 } 198 199 public ServiceCallExpressionConfiguration expressionType(ExpressionDefinition expressionType) { 200 setExpressionType(expressionType); 201 return this; 202 } 203 204 public ServiceCallExpressionConfiguration expression(Expression expression) { 205 setExpression(expression); 206 return this; 207 } 208 209 // ************************************************************************* 210 // Factory 211 // ************************************************************************* 212 213 @Override 214 public Expression newInstance(CamelContext camelContext) throws Exception { 215 Expression answer = getExpression(); 216 if (answer != null) { 217 return answer; 218 } 219 220 ExpressionDefinition expressionType = getExpressionType(); 221 if (expressionType != null && answer == null) { 222 return expressionType.createExpression(camelContext); 223 } 224 225 if (factoryKey != null) { 226 // First try to find the factory from the registry. 227 ServiceExpressionFactory factory = CamelContextHelper.lookup(camelContext, factoryKey, ServiceExpressionFactory.class); 228 if (factory != null) { 229 // If a factory is found in the registry do not re-configure it 230 // as 231 // it should be pre-configured. 232 answer = factory.newInstance(camelContext); 233 } else { 234 235 Class<?> type; 236 try { 237 // Then use Service factory. 238 type = camelContext.adapt(ExtendedCamelContext.class).getFactoryFinder(ServiceCallDefinitionConstants.RESOURCE_PATH).findClass(factoryKey).orElse(null); 239 } catch (Exception e) { 240 throw new NoFactoryAvailableException(ServiceCallDefinitionConstants.RESOURCE_PATH + factoryKey, e); 241 } 242 243 if (type != null) { 244 if (ServiceExpressionFactory.class.isAssignableFrom(type)) { 245 factory = (ServiceExpressionFactory)camelContext.getInjector().newInstance(type, false); 246 } else { 247 throw new IllegalArgumentException("Resolving Expression: " + factoryKey + " detected type conflict: Not a ExpressionFactory implementation. Found: " 248 + type.getName()); 249 } 250 } 251 252 try { 253 Map<String, Object> parameters = new HashMap<>(); 254 camelContext.adapt(ExtendedCamelContext.class).getBeanIntrospection().getProperties(this, parameters, null, false); 255 256 parameters.replaceAll((k, v) -> { 257 if (v instanceof String) { 258 try { 259 v = camelContext.resolvePropertyPlaceholders((String)v); 260 } catch (Exception e) { 261 throw new IllegalArgumentException(String.format("Exception while resolving %s (%s)", k, v.toString()), e); 262 } 263 } 264 265 return v; 266 }); 267 268 // Convert properties to Map<String, String> 269 parameters.put("properties", getPropertiesAsMap(camelContext)); 270 271 postProcessFactoryParameters(camelContext, parameters); 272 273 PropertyBindingSupport.build().bind(camelContext, factory, parameters); 274 275 answer = factory.newInstance(camelContext); 276 } catch (Exception e) { 277 throw new IllegalArgumentException(e); 278 } 279 } 280 } 281 282 return answer; 283 } 284 285 // ************************************************************************* 286 // Utilities 287 // ************************************************************************* 288 289 protected void postProcessFactoryParameters(CamelContext camelContext, Map<String, Object> parameters) throws Exception { 290 } 291}