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