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.support; 018 019import java.io.ByteArrayInputStream; 020import java.io.InputStream; 021import java.util.ArrayList; 022import java.util.List; 023import java.util.Map; 024 025import org.w3c.dom.Document; 026import org.w3c.dom.Node; 027import org.w3c.dom.NodeList; 028 029import org.apache.camel.CamelContext; 030import org.apache.camel.api.management.ManagedAttribute; 031import org.apache.camel.api.management.ManagedOperation; 032import org.apache.camel.model.ModelHelper; 033import org.apache.camel.model.RouteDefinition; 034import org.apache.camel.model.RoutesDefinition; 035import org.apache.camel.spi.ReloadStrategy; 036import org.apache.camel.util.CollectionStringBuffer; 037import org.apache.camel.util.LRUCacheFactory; 038import org.apache.camel.util.ObjectHelper; 039import org.apache.camel.util.StringHelper; 040import org.apache.camel.util.XmlLineNumberParser; 041import org.slf4j.Logger; 042import org.slf4j.LoggerFactory; 043 044/** 045 * Base class for implementing custom {@link ReloadStrategy} SPI plugins. 046 */ 047public abstract class ReloadStrategySupport extends ServiceSupport implements ReloadStrategy { 048 protected final Logger log = LoggerFactory.getLogger(getClass()); 049 050 protected Map<String, Object> cache; 051 052 private CamelContext camelContext; 053 054 private int succeeded; 055 private int failed; 056 057 @Override 058 public CamelContext getCamelContext() { 059 return camelContext; 060 } 061 062 @Override 063 public void setCamelContext(CamelContext camelContext) { 064 this.camelContext = camelContext; 065 } 066 067 @Override 068 public void onReloadXml(CamelContext camelContext, String name, InputStream resource) { 069 log.debug("Reloading routes from XML resource: {}", name); 070 071 Document dom; 072 String xml; 073 try { 074 xml = camelContext.getTypeConverter().mandatoryConvertTo(String.class, resource); 075 // the JAXB model expects the spring namespace (even for blueprint) 076 dom = XmlLineNumberParser.parseXml(new ByteArrayInputStream(xml.getBytes()), null, "camelContext,routeContext,routes", "http://camel.apache.org/schema/spring"); 077 } catch (Exception e) { 078 failed++; 079 log.warn("Cannot load the resource {} as XML", name); 080 return; 081 } 082 083 ResourceState state = ObjectHelper.cast(ResourceState.class, cache.get(name)); 084 if (state == null) { 085 state = new ResourceState(name, dom, xml); 086 cache.put(name, state); 087 } 088 089 String oldXml = state.getXml(); 090 List<Integer> changed = StringHelper.changedLines(oldXml, xml); 091 092 // find all <route> which are the routes 093 NodeList list = dom.getElementsByTagName("route"); 094 095 // collect which routes are updated/skipped 096 List<RouteDefinition> routes = new ArrayList<>(); 097 098 if (list != null && list.getLength() > 0) { 099 for (int i = 0; i < list.getLength(); i++) { 100 Node node = list.item(i); 101 102 // what line number are this within 103 String lineNumber = (String) node.getUserData(XmlLineNumberParser.LINE_NUMBER); 104 String lineNumberEnd = (String) node.getUserData(XmlLineNumberParser.LINE_NUMBER_END); 105 if (lineNumber != null && lineNumberEnd != null && !changed.isEmpty()) { 106 int start = Integer.valueOf(lineNumber); 107 int end = Integer.valueOf(lineNumberEnd); 108 109 boolean within = withinChanged(start, end, changed); 110 if (within) { 111 log.debug("Updating route in lines: {}-{}", start, end); 112 } else { 113 log.debug("No changes to route in lines: {}-{}", start, end); 114 continue; 115 } 116 } 117 118 try { 119 // load from XML -> JAXB model and store as routes to be updated 120 RoutesDefinition loaded = ModelHelper.loadRoutesDefinition(camelContext, node); 121 if (!loaded.getRoutes().isEmpty()) { 122 routes.addAll(loaded.getRoutes()); 123 } 124 } catch (Exception e) { 125 failed++; 126 throw ObjectHelper.wrapRuntimeCamelException(e); 127 } 128 } 129 } 130 131 if (!routes.isEmpty()) { 132 try { 133 boolean unassignedRouteIds = false; 134 135 CollectionStringBuffer csb = new CollectionStringBuffer(","); 136 // collect route ids and force assign ids if not in use 137 for (RouteDefinition route : routes) { 138 unassignedRouteIds |= !route.hasCustomIdAssigned(); 139 String id = route.idOrCreate(camelContext.getNodeIdFactory()); 140 csb.append(id); 141 } 142 log.debug("Reloading routes: [{}] from XML resource: {}", csb, name); 143 144 if (unassignedRouteIds) { 145 log.warn("Routes with no id's detected. Its recommended to assign id's to your routes so Camel can reload the routes correctly."); 146 } 147 // update the routes (add will remove and shutdown first) 148 camelContext.addRouteDefinitions(routes); 149 150 log.info("Reloaded routes: [{}] from XML resource: {}", csb, name); 151 } catch (Exception e) { 152 failed++; 153 throw ObjectHelper.wrapRuntimeCamelException(e); 154 } 155 156 } 157 158 // update cache 159 state = new ResourceState(name, dom, xml); 160 cache.put(name, state); 161 162 succeeded++; 163 } 164 165 private boolean withinChanged(int start, int end, List<Integer> changed) { 166 for (int change : changed) { 167 log.trace("Changed line: {} within {}-{}", change, start, end); 168 if (change >= start && change <= end) { 169 return true; 170 } 171 } 172 return false; 173 } 174 175 @ManagedAttribute(description = "Number of reloads succeeded") 176 public int getReloadCounter() { 177 return succeeded; 178 } 179 180 @ManagedAttribute(description = "Number of reloads failed") 181 public int getFailedCounter() { 182 return failed; 183 } 184 185 public void setSucceeded(int succeeded) { 186 this.succeeded = succeeded; 187 } 188 189 public void setFailed(int failed) { 190 this.failed = failed; 191 } 192 193 @ManagedOperation(description = "Reset counters") 194 public void resetCounters() { 195 succeeded = 0; 196 failed = 0; 197 } 198 199 @Override 200 @SuppressWarnings("unchecked") 201 protected void doStart() throws Exception { 202 // noop 203 cache = LRUCacheFactory.newLRUCache(100); 204 } 205 206 @Override 207 protected void doStop() throws Exception { 208 cache.clear(); 209 } 210 211 /** 212 * To keep state of last reloaded resource 213 */ 214 private static final class ResourceState { 215 private final String name; 216 private final Document dom; 217 private final String xml; 218 219 ResourceState(String name, Document dom, String xml) { 220 this.name = name; 221 this.dom = dom; 222 this.xml = xml; 223 } 224 225 public String getName() { 226 return name; 227 } 228 229 public Document getDom() { 230 return dom; 231 } 232 233 public String getXml() { 234 return xml; 235 } 236 } 237 238}