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.cluster; 018 019import java.time.Duration; 020import java.util.ArrayList; 021import java.util.Collection; 022import java.util.Collections; 023import java.util.List; 024import java.util.Map; 025import java.util.Objects; 026import java.util.Set; 027import java.util.concurrent.ConcurrentHashMap; 028import java.util.concurrent.ConcurrentMap; 029import java.util.concurrent.CopyOnWriteArraySet; 030import java.util.concurrent.TimeUnit; 031import java.util.stream.Collectors; 032 033import org.apache.camel.CamelContext; 034import org.apache.camel.NamedNode; 035import org.apache.camel.Route; 036import org.apache.camel.RuntimeCamelException; 037import org.apache.camel.cluster.CamelClusterService; 038import org.apache.camel.impl.engine.DefaultRouteController; 039import org.apache.camel.meta.Experimental; 040import org.apache.camel.model.RouteDefinition; 041import org.apache.camel.spi.RoutePolicy; 042import org.apache.camel.spi.RoutePolicyFactory; 043import org.apache.camel.support.cluster.ClusterServiceHelper; 044import org.apache.camel.support.cluster.ClusterServiceSelectors; 045import org.apache.camel.support.service.ServiceHelper; 046import org.apache.camel.util.ObjectHelper; 047import org.slf4j.Logger; 048import org.slf4j.LoggerFactory; 049 050@Experimental 051public class ClusteredRouteController extends DefaultRouteController { 052 private static final Logger LOGGER = LoggerFactory.getLogger(ClusteredRouteController.class); 053 054 private final Set<String> routes; 055 private final ConcurrentMap<String, ClusteredRouteConfiguration> configurations; 056 private final List<ClusteredRouteFilter> filters; 057 private final PolicyFactory policyFactory; 058 private final ClusteredRouteConfiguration defaultConfiguration; 059 private CamelClusterService clusterService; 060 private CamelClusterService.Selector clusterServiceSelector; 061 062 public ClusteredRouteController() { 063 this.routes = new CopyOnWriteArraySet<>(); 064 this.configurations = new ConcurrentHashMap<>(); 065 this.filters = new ArrayList<>(); 066 this.clusterServiceSelector = ClusterServiceSelectors.DEFAULT_SELECTOR; 067 this.policyFactory = new PolicyFactory(); 068 069 this.defaultConfiguration = new ClusteredRouteConfiguration(); 070 this.defaultConfiguration.setInitialDelay(Duration.ofMillis(0)); 071 } 072 073 // ******************************* 074 // Properties. 075 // ******************************* 076 077 /** 078 * Add a filter used to to filter cluster aware routes. 079 */ 080 public void addFilter(ClusteredRouteFilter filter) { 081 this.filters.add(filter); 082 } 083 084 /** 085 * Sets the filters used to filter cluster aware routes. 086 */ 087 public void setFilters(Collection<ClusteredRouteFilter> filters) { 088 this.filters.clear(); 089 this.filters.addAll(filters); 090 } 091 092 public Collection<ClusteredRouteFilter> getFilters() { 093 return Collections.unmodifiableList(filters); 094 } 095 096 /** 097 * Add a configuration for the given route. 098 */ 099 public void addRouteConfiguration(String routeId, ClusteredRouteConfiguration configuration) { 100 configurations.put(routeId, configuration); 101 } 102 103 /** 104 * Sets the configurations for the routes. 105 */ 106 public void setRoutesConfiguration(Map<String, ClusteredRouteConfiguration> configurations) { 107 this.configurations.clear(); 108 this.configurations.putAll(configurations); 109 } 110 111 public Map<String, ClusteredRouteConfiguration> getRoutesConfiguration() { 112 return Collections.unmodifiableMap(this.configurations); 113 } 114 115 public Duration getInitialDelay() { 116 return this.defaultConfiguration.getInitialDelay(); 117 } 118 119 /** 120 * Set the amount of time the route controller should wait before to start 121 * the routes after the camel context is started. 122 * 123 * @param initialDelay the initial delay. 124 */ 125 public void setInitialDelay(Duration initialDelay) { 126 this.defaultConfiguration.setInitialDelay(initialDelay); 127 } 128 129 public String getNamespace() { 130 return this.defaultConfiguration.getNamespace(); 131 } 132 133 /** 134 * Set the default namespace. 135 */ 136 public void setNamespace(String namespace) { 137 this.defaultConfiguration.setNamespace(namespace); 138 } 139 140 public CamelClusterService getClusterService() { 141 return clusterService; 142 } 143 144 /** 145 * Set the cluster service to use. 146 */ 147 public void setClusterService(CamelClusterService clusterService) { 148 ObjectHelper.notNull(clusterService, "CamelClusterService"); 149 150 this.clusterService = clusterService; 151 } 152 153 public CamelClusterService.Selector getClusterServiceSelector() { 154 return clusterServiceSelector; 155 } 156 157 /** 158 * Set the selector strategy to look-up a {@link CamelClusterService} 159 */ 160 public void setClusterServiceSelector(CamelClusterService.Selector clusterServiceSelector) { 161 ObjectHelper.notNull(clusterService, "CamelClusterService.Selector"); 162 163 this.clusterServiceSelector = clusterServiceSelector; 164 } 165 166 // ******************************* 167 // 168 // ******************************* 169 170 @Override 171 public Collection<Route> getControlledRoutes() { 172 return this.routes.stream().map(getCamelContext()::getRoute).filter(Objects::nonNull).collect(Collectors.toList()); 173 } 174 175 @Override 176 public void doStart() throws Exception { 177 final CamelContext context = getCamelContext(); 178 179 // Parameters validation 180 ObjectHelper.notNull(defaultConfiguration.getNamespace(), "Namespace"); 181 ObjectHelper.notNull(defaultConfiguration.getInitialDelay(), "initialDelay"); 182 ObjectHelper.notNull(context, "camelContext"); 183 184 if (clusterService == null) { 185 // Finally try to grab it from the camel context. 186 clusterService = ClusterServiceHelper.mandatoryLookupService(context, clusterServiceSelector); 187 } 188 189 LOGGER.debug("Using ClusterService instance {} (id={}, type={})", clusterService, clusterService.getId(), clusterService.getClass().getName()); 190 191 if (!ServiceHelper.isStarted(clusterService)) { 192 // Start the cluster service if not yet started. 193 clusterService.start(); 194 } 195 196 super.doStart(); 197 } 198 199 @Override 200 public void doStop() throws Exception { 201 if (ServiceHelper.isStarted(clusterService)) { 202 // Stop the cluster service. 203 clusterService.stop(); 204 } 205 } 206 207 @Override 208 public void setCamelContext(CamelContext camelContext) { 209 if (!camelContext.getRoutePolicyFactories().contains(this.policyFactory)) { 210 camelContext.addRoutePolicyFactory(this.policyFactory); 211 } 212 213 super.setCamelContext(camelContext); 214 } 215 216 // ******************************* 217 // Route operations are disabled 218 // ******************************* 219 220 @Override 221 public void startRoute(String routeId) throws Exception { 222 failIfClustered(routeId); 223 224 // Delegate to default impl. 225 super.startRoute(routeId); 226 } 227 228 @Override 229 public void stopRoute(String routeId) throws Exception { 230 failIfClustered(routeId); 231 232 // Delegate to default impl. 233 super.stopRoute(routeId); 234 } 235 236 @Override 237 public void stopRoute(String routeId, long timeout, TimeUnit timeUnit) throws Exception { 238 failIfClustered(routeId); 239 240 // Delegate to default impl. 241 super.stopRoute(routeId, timeout, timeUnit); 242 } 243 244 @Override 245 public boolean stopRoute(String routeId, long timeout, TimeUnit timeUnit, boolean abortAfterTimeout) throws Exception { 246 failIfClustered(routeId); 247 248 // Delegate to default impl. 249 return super.stopRoute(routeId, timeout, timeUnit, abortAfterTimeout); 250 } 251 252 @Override 253 public void suspendRoute(String routeId) throws Exception { 254 failIfClustered(routeId); 255 256 // Delegate to default impl. 257 super.suspendRoute(routeId); 258 } 259 260 @Override 261 public void suspendRoute(String routeId, long timeout, TimeUnit timeUnit) throws Exception { 262 failIfClustered(routeId); 263 264 // Delegate to default impl. 265 super.suspendRoute(routeId, timeout, timeUnit); 266 } 267 268 @Override 269 public void resumeRoute(String routeId) throws Exception { 270 failIfClustered(routeId); 271 272 // Delegate to default impl. 273 super.resumeRoute(routeId); 274 } 275 276 // ******************************* 277 // Helpers 278 // ******************************* 279 280 private void failIfClustered(String routeId) { 281 // Can't perform action on routes managed by this controller as they 282 // are clustered and they may be part of the same view. 283 if (routes.contains(routeId)) { 284 throw new UnsupportedOperationException("Operation not supported as route " + routeId + " is clustered"); 285 } 286 } 287 288 // ******************************* 289 // Factories 290 // ******************************* 291 292 private final class PolicyFactory implements RoutePolicyFactory { 293 @Override 294 public RoutePolicy createRoutePolicy(CamelContext camelContext, String routeId, NamedNode node) { 295 RouteDefinition route = (RouteDefinition)node; 296 // All the filter have to be match to include the route in the 297 // clustering set-up 298 if (filters.stream().allMatch(filter -> filter.test(camelContext, routeId, route))) { 299 300 if (ObjectHelper.isNotEmpty(route.getRoutePolicies())) { 301 // Check if the route is already configured with a clustered 302 // route policy, in that case exclude it. 303 if (route.getRoutePolicies().stream().anyMatch(ClusteredRoutePolicy.class::isInstance)) { 304 LOGGER.debug("Route '{}' has a ClusteredRoutePolicy already set-up", routeId); 305 return null; 306 } 307 } 308 309 try { 310 final ClusteredRouteConfiguration configuration = configurations.getOrDefault(routeId, defaultConfiguration); 311 final String namespace = ObjectHelper.supplyIfEmpty(configuration.getNamespace(), defaultConfiguration::getNamespace); 312 final Duration initialDelay = ObjectHelper.supplyIfEmpty(configuration.getInitialDelay(), defaultConfiguration::getInitialDelay); 313 314 ClusteredRoutePolicy policy = ClusteredRoutePolicy.forNamespace(clusterService, namespace); 315 policy.setCamelContext(getCamelContext()); 316 policy.setInitialDelay(initialDelay); 317 318 LOGGER.debug("Attaching route '{}' to namespace '{}'", routeId, namespace); 319 320 routes.add(routeId); 321 322 return policy; 323 } catch (Exception e) { 324 throw RuntimeCamelException.wrapRuntimeCamelException(e); 325 } 326 } 327 328 return null; 329 } 330 } 331}