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