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