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}