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