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