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;
024import java.util.Optional;
025
026import javax.xml.bind.annotation.XmlAccessType;
027import javax.xml.bind.annotation.XmlAccessorType;
028import javax.xml.bind.annotation.XmlElement;
029import javax.xml.bind.annotation.XmlRootElement;
030import javax.xml.bind.annotation.XmlTransient;
031
032import org.apache.camel.CamelContext;
033import org.apache.camel.ExtendedCamelContext;
034import org.apache.camel.NoFactoryAvailableException;
035import org.apache.camel.cloud.ServiceDiscovery;
036import org.apache.camel.cloud.ServiceDiscoveryFactory;
037import org.apache.camel.model.IdentifiedType;
038import org.apache.camel.model.ProcessorDefinition;
039import org.apache.camel.model.PropertyDefinition;
040import org.apache.camel.spi.Metadata;
041import org.apache.camel.support.CamelContextHelper;
042import org.apache.camel.support.PropertyBindingSupport;
043import org.apache.camel.util.ObjectHelper;
044
045@Metadata(label = "routing,cloud,service-discovery")
046@XmlRootElement(name = "serviceDiscoveryConfiguration")
047@XmlAccessorType(XmlAccessType.FIELD)
048public class ServiceCallServiceDiscoveryConfiguration extends IdentifiedType implements ServiceDiscoveryFactory {
049    @XmlTransient
050    private final Optional<ServiceCallDefinition> parent;
051    @XmlTransient
052    private final String factoryKey;
053    @XmlElement(name = "properties")
054    @Metadata(label = "advanced")
055    private List<PropertyDefinition> properties;
056
057    public ServiceCallServiceDiscoveryConfiguration() {
058        this(null, null);
059    }
060
061    public ServiceCallServiceDiscoveryConfiguration(ServiceCallDefinition parent, String factoryKey) {
062        this.parent = Optional.ofNullable(parent);
063        this.factoryKey = factoryKey;
064    }
065
066    public ServiceCallDefinition end() {
067        return this.parent.orElseThrow(() -> new IllegalStateException("Parent definition is not set"));
068    }
069
070    public ProcessorDefinition<?> endParent() {
071        return this.parent.map(ServiceCallDefinition::end).orElseThrow(() -> new IllegalStateException("Parent definition is not set"));
072    }
073
074    // *************************************************************************
075    //
076    // *************************************************************************
077
078    public List<PropertyDefinition> getProperties() {
079        return properties;
080    }
081
082    /**
083     * Set client properties to use.
084     * <p/>
085     * These properties are specific to what service call implementation are in
086     * use. For example if using ribbon, then the client properties are define
087     * in com.netflix.client.config.CommonClientConfigKey.
088     */
089    public void setProperties(List<PropertyDefinition> properties) {
090        this.properties = properties;
091    }
092
093    /**
094     * Adds a custom property 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 ServiceCallServiceDiscoveryConfiguration property(String key, String value) {
101        if (properties == null) {
102            properties = new ArrayList<>();
103        }
104        PropertyDefinition prop = new PropertyDefinition();
105        prop.setKey(key);
106        prop.setValue(value);
107        properties.add(prop);
108        return this;
109    }
110
111    protected Map<String, String> getPropertiesAsMap(CamelContext camelContext) throws Exception {
112        Map<String, String> answer;
113
114        if (properties == null || properties.isEmpty()) {
115            answer = Collections.emptyMap();
116        } else {
117            answer = new HashMap<>();
118            for (PropertyDefinition prop : properties) {
119                // support property placeholders
120                String key = CamelContextHelper.parseText(camelContext, prop.getKey());
121                String value = CamelContextHelper.parseText(camelContext, prop.getValue());
122                answer.put(key, value);
123            }
124        }
125
126        return answer;
127    }
128
129    // *************************************************************************
130    // Factory
131    // *************************************************************************
132
133    @Override
134    public ServiceDiscovery newInstance(CamelContext camelContext) throws Exception {
135        ObjectHelper.notNull(factoryKey, "ServiceDiscovery factoryKey");
136
137        ServiceDiscovery answer;
138
139        // First try to find the factory from the registry.
140        ServiceDiscoveryFactory factory = CamelContextHelper.lookup(camelContext, factoryKey, ServiceDiscoveryFactory.class);
141        if (factory != null) {
142            // If a factory is found in the registry do not re-configure it as
143            // it should be pre-configured.
144            answer = factory.newInstance(camelContext);
145        } else {
146
147            Class<?> type;
148            try {
149                // Then use Service factory.
150                type = camelContext.adapt(ExtendedCamelContext.class).getFactoryFinder(ServiceCallDefinitionConstants.RESOURCE_PATH).findClass(factoryKey).orElse(null);
151            } catch (Exception e) {
152                throw new NoFactoryAvailableException(ServiceCallDefinitionConstants.RESOURCE_PATH + factoryKey, e);
153            }
154
155            if (type != null) {
156                if (ServiceDiscoveryFactory.class.isAssignableFrom(type)) {
157                    factory = (ServiceDiscoveryFactory)camelContext.getInjector().newInstance(type, false);
158                } else {
159                    throw new IllegalArgumentException("Resolving ServiceDiscovery: " + factoryKey
160                                                       + " detected type conflict: Not a ServiceDiscoveryFactory implementation. Found: " + type.getName());
161                }
162            }
163
164            try {
165                Map<String, Object> parameters = new HashMap<>();
166                camelContext.adapt(ExtendedCamelContext.class).getBeanIntrospection().getProperties(this, parameters, null, false);
167
168                parameters.replaceAll((k, v) -> {
169                    if (v instanceof String) {
170                        try {
171                            v = camelContext.resolvePropertyPlaceholders((String)v);
172                        } catch (Exception e) {
173                            throw new IllegalArgumentException(String.format("Exception while resolving %s (%s)", k, v.toString()), e);
174                        }
175                    }
176
177                    return v;
178                });
179
180                // Convert properties to Map<String, String>
181                parameters.put("properties", getPropertiesAsMap(camelContext));
182
183                postProcessFactoryParameters(camelContext, parameters);
184
185                PropertyBindingSupport.build().bind(camelContext, factory, parameters);
186
187                answer = factory.newInstance(camelContext);
188            } catch (Exception e) {
189                throw new IllegalArgumentException(e);
190            }
191        }
192
193        return answer;
194    }
195
196    // *************************************************************************
197    // Utilities
198    // *************************************************************************
199
200    protected void postProcessFactoryParameters(CamelContext camelContext, Map<String, Object> parameters) throws Exception {
201    }
202}