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}