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