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}