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.impl.remote;
018
019import java.util.HashMap;
020import java.util.Map;
021import java.util.Optional;
022import java.util.Set;
023
024import org.apache.camel.CamelContextAware;
025import org.apache.camel.ExchangePattern;
026import org.apache.camel.Processor;
027import org.apache.camel.model.ProcessorDefinition;
028import org.apache.camel.model.PropertyDefinition;
029import org.apache.camel.model.remote.ServiceCallConfigurationDefinition;
030import org.apache.camel.model.remote.ServiceCallDefinition;
031import org.apache.camel.spi.ProcessorFactory;
032import org.apache.camel.spi.RouteContext;
033import org.apache.camel.spi.ServiceCallLoadBalancer;
034import org.apache.camel.spi.ServiceCallServer;
035import org.apache.camel.spi.ServiceCallServerListStrategy;
036import org.apache.camel.util.CamelContextHelper;
037import org.apache.camel.util.IntrospectionSupport;
038import org.apache.camel.util.ObjectHelper;
039
040public abstract class DefaultServiceCallProcessorFactory<C, S extends ServiceCallServer> implements ProcessorFactory {
041    @Override
042    public Processor createChildProcessor(RouteContext routeContext, ProcessorDefinition<?> definition, boolean mandatory) throws Exception {
043        // not in use
044        return null;
045    }
046
047    @Override
048    @SuppressWarnings("unchecked")
049    public Processor createProcessor(RouteContext routeContext, ProcessorDefinition<?> definition) throws Exception {
050        return definition instanceof ServiceCallDefinition
051            ? createProcessor(routeContext, (ServiceCallDefinition) definition, createConfiguration(routeContext))
052            : null;
053    }
054
055    protected Processor createProcessor(RouteContext routeContext, ServiceCallDefinition definition, C cfg) throws Exception {
056        String name = definition.getName();
057        String uri = definition.getUri();
058        ExchangePattern mep = definition.getPattern();
059
060        ServiceCallConfigurationDefinition config = definition.getServiceCallConfiguration();
061        ServiceCallConfigurationDefinition configRef = null;
062        if (definition.getServiceCallConfigurationRef() != null) {
063            // lookup in registry first
064            configRef = CamelContextHelper.lookup(routeContext.getCamelContext(), definition.getServiceCallConfigurationRef(), ServiceCallConfigurationDefinition.class);
065            if (configRef == null) {
066                // and fallback as service configuration
067                routeContext.getCamelContext().getServiceCallConfiguration(definition.getServiceCallConfigurationRef(), ServiceCallConfigurationDefinition.class);
068            }
069        }
070
071        // if no configuration explicit configured then use default
072        if (config == null && configRef == null) {
073            config = routeContext.getCamelContext().getServiceCallConfiguration(null, ServiceCallConfigurationDefinition.class);
074        }
075        if (config == null) {
076            // if no default then try to find if there configuration in the registry of the given type
077            Set<ServiceCallConfigurationDefinition> set = routeContext.getCamelContext().getRegistry().findByType(ServiceCallConfigurationDefinition.class);
078            if (set.size() == 1) {
079                config = set.iterator().next();
080            }
081        }
082
083        if (config == null && configRef == null) {
084            throw new IllegalStateException("The ServiceCall: " + definition + " must be configured before it can be used.");
085        }
086
087        if (cfg != null) {
088            // extract the properties from the configuration from the model
089            Map<String, Object> parameters = new HashMap<>();
090            if (configRef != null) {
091                IntrospectionSupport.getProperties(configRef, parameters, null);
092            }
093            if (config != null) {
094                IntrospectionSupport.getProperties(config, parameters, null);
095            }
096
097            IntrospectionSupport.setProperties(cfg, parameters);
098        }
099
100        // lookup the load balancer to use (configured on EIP takes precedence vs configured on configuration)
101        ServiceCallLoadBalancer lb = configureLoadBalancer(cfg, routeContext, definition);
102        if (lb == null && config != null) {
103            lb = configureLoadBalancer(cfg, routeContext, config);
104        }
105        if (lb == null && configRef != null) {
106            lb = configureLoadBalancer(cfg, routeContext, configRef);
107        }
108
109        // lookup the server list strategy to use (configured on EIP takes precedence vs configured on configuration)
110        ServiceCallServerListStrategy sl = configureServerListStrategy(cfg, routeContext, definition);
111        if (sl == null && config != null) {
112            sl = configureServerListStrategy(cfg, routeContext, config);
113        }
114        if (sl == null && configRef != null) {
115            sl = configureServerListStrategy(cfg, routeContext, configRef);
116        }
117
118        // the component is used to configure what the default scheme to use (eg camel component name)
119        String component = config != null ? config.getComponent() : null;
120        if (component == null && configRef != null) {
121            component = configRef.getComponent();
122        }
123
124        if (ObjectHelper.isNotEmpty(lb) && lb instanceof CamelContextAware) {
125            ((CamelContextAware)lb).setCamelContext(routeContext.getCamelContext());
126        }
127
128        if (ObjectHelper.isNotEmpty(sl) && sl instanceof CamelContextAware) {
129            ((CamelContextAware)sl).setCamelContext(routeContext.getCamelContext());
130        }
131
132        if (sl == null) {
133            sl = createDefaultServerListStrategy(cfg);
134        }
135        if (lb == null) {
136            lb = createDefaultLoadBalancer(cfg);
137        }
138
139        Map<String, String> properties = configureProperties(routeContext, config, configRef);
140
141        DefaultServiceCallProcessor processor = createProcessor(name, component, uri, mep, cfg, properties);
142        if (sl != null && processor.getServerListStrategy() == null) {
143            processor.setServerListStrategy(sl);
144        }
145        if (lb != null && processor.getLoadBalancer() == null) {
146            processor.setLoadBalancer(lb);
147        }
148
149        return processor;
150    }
151
152    protected Map<String, String> configureProperties(RouteContext routeContext, ServiceCallConfigurationDefinition config, ServiceCallConfigurationDefinition configRef) throws Exception {
153        Map<String, String> answer = new HashMap<>();
154        if (config != null && config.getProperties() != null) {
155            for (PropertyDefinition prop : config.getProperties()) {
156                // support property placeholders
157                String key = CamelContextHelper.parseText(routeContext.getCamelContext(), prop.getKey());
158                String value = CamelContextHelper.parseText(routeContext.getCamelContext(), prop.getValue());
159                answer.put(key, value);
160            }
161        }
162        if (configRef != null && configRef.getProperties() != null) {
163            for (PropertyDefinition prop : configRef.getProperties()) {
164                // support property placeholders
165                String key = CamelContextHelper.parseText(routeContext.getCamelContext(), prop.getKey());
166                String value = CamelContextHelper.parseText(routeContext.getCamelContext(), prop.getValue());
167                answer.put(key, value);
168            }
169        }
170        return answer;
171    }
172
173    protected ServiceCallLoadBalancer configureLoadBalancer(C conf, RouteContext routeContext, ServiceCallDefinition sd)  throws Exception {
174        ServiceCallLoadBalancer lb = null;
175        String ref;
176
177        if (sd != null) {
178            lb = sd.getLoadBalancer();
179            ref = sd.getLoadBalancerRef();
180            if (lb == null && ref != null) {
181                lb = builtInLoadBalancer(
182                        conf,
183                        ref)
184                    .orElseGet(() -> CamelContextHelper.mandatoryLookup(
185                        routeContext.getCamelContext(),
186                        ref,
187                        ServiceCallLoadBalancer.class)
188                );
189            }
190        }
191
192        return lb;
193    }
194
195    protected ServiceCallLoadBalancer configureLoadBalancer(C conf, RouteContext routeContext, ServiceCallConfigurationDefinition config)  throws Exception {
196        ServiceCallLoadBalancer lb = config.getLoadBalancer();
197        String ref = config.getLoadBalancerRef();
198        if (lb == null && ref != null) {
199            lb = builtInLoadBalancer(
200                    conf,
201                    ref)
202                .orElseGet(() ->CamelContextHelper.mandatoryLookup(
203                    routeContext.getCamelContext(),
204                    ref,
205                    ServiceCallLoadBalancer.class)
206            );
207        }
208        return lb;
209    }
210
211    protected ServiceCallServerListStrategy configureServerListStrategy(C conf, RouteContext routeContext, ServiceCallDefinition sd)  throws Exception {
212        ServiceCallServerListStrategy sl = null;
213        String ref;
214        if (sd != null) {
215            sl = sd.getServerListStrategy();
216            ref = sd.getServerListStrategyRef();
217            if (sl == null && ref != null) {
218                sl = builtInServerListStrategy(
219                        conf,
220                        ref)
221                    .orElseGet(() -> CamelContextHelper.mandatoryLookup(
222                        routeContext.getCamelContext(),
223                        ref,
224                        ServiceCallServerListStrategy.class)
225                );
226            }
227        }
228
229        return sl;
230    }
231
232    protected ServiceCallServerListStrategy configureServerListStrategy(C conf, RouteContext routeContext, ServiceCallConfigurationDefinition config)  throws Exception {
233        ServiceCallServerListStrategy sl = config.getServerListStrategy();
234        String ref = config.getServerListStrategyRef();
235        if (sl == null && ref != null) {
236            sl = builtInServerListStrategy(
237                    conf,
238                    ref)
239                .orElseGet(() -> CamelContextHelper.mandatoryLookup(
240                    routeContext.getCamelContext(),
241                    ref,
242                    ServiceCallServerListStrategy.class)
243            );
244        }
245
246        return sl;
247    }
248
249    // special for ref is referring to built-in load balancers
250    protected Optional<ServiceCallLoadBalancer> builtInLoadBalancer(C conf, String name)  throws Exception {
251        ServiceCallLoadBalancer lb = null;
252        if (ObjectHelper.equal(name, "random", true)) {
253            lb = new RandomServiceCallLoadBalancer();
254        } else if (ObjectHelper.equal(name, "roundrobin", true)) {
255            lb = new RoundRobinServiceCallLoadBalancer();
256        }
257
258        return Optional.ofNullable(lb);
259    }
260
261    // special for ref is referring to built-in server list strategies
262    protected Optional<ServiceCallServerListStrategy> builtInServerListStrategy(C conf, String name)  throws Exception {
263        return Optional.empty();
264    }
265
266    protected DefaultServiceCallProcessor createProcessor(
267            String name,
268            String component,
269            String uri,
270            ExchangePattern mep,
271            C conf,
272            Map<String, String> properties) throws Exception {
273
274        return new DefaultServiceCallProcessor(name, component, uri, mep);
275    }
276
277    // TODO: rename
278    protected abstract C createConfiguration(RouteContext routeContext) throws Exception;
279
280
281    protected ServiceCallLoadBalancer<S> createDefaultLoadBalancer(C conf) throws Exception {
282        return new RoundRobinServiceCallLoadBalancer<>();
283    }
284
285    protected ServiceCallServerListStrategy<S> createDefaultServerListStrategy(C conf) throws Exception {
286        return null;
287    }
288}