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