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.management.mbean;
018
019import java.io.ByteArrayOutputStream;
020import java.io.ObjectOutputStream;
021import java.util.List;
022import java.util.Set;
023
024import org.apache.camel.CamelContext;
025import org.apache.camel.Exchange;
026import org.apache.camel.ExchangePropertyKey;
027import org.apache.camel.Expression;
028import org.apache.camel.MessageHistory;
029import org.apache.camel.NoTypeConversionAvailableException;
030import org.apache.camel.Predicate;
031import org.apache.camel.Route;
032import org.apache.camel.RuntimeCamelException;
033import org.apache.camel.api.management.ManagedResource;
034import org.apache.camel.api.management.mbean.ManagedBacklogDebuggerMBean;
035import org.apache.camel.impl.debugger.DefaultBacklogDebugger;
036import org.apache.camel.spi.Language;
037import org.apache.camel.spi.ManagementStrategy;
038import org.apache.camel.support.LoggerHelper;
039import org.apache.camel.util.StringHelper;
040import org.apache.camel.util.URISupport;
041
042@ManagedResource(description = "Managed BacklogDebugger")
043public class ManagedBacklogDebugger implements ManagedBacklogDebuggerMBean {
044
045    private final CamelContext camelContext;
046    private final DefaultBacklogDebugger backlogDebugger;
047
048    public ManagedBacklogDebugger(CamelContext camelContext, DefaultBacklogDebugger backlogDebugger) {
049        this.camelContext = camelContext;
050        this.backlogDebugger = backlogDebugger;
051    }
052
053    public void init(ManagementStrategy strategy) {
054        // do nothing
055    }
056
057    public CamelContext getContext() {
058        return camelContext;
059    }
060
061    public DefaultBacklogDebugger getBacklogDebugger() {
062        return backlogDebugger;
063    }
064
065    @Override
066    public String getCamelId() {
067        return camelContext.getName();
068    }
069
070    @Override
071    public String getCamelManagementName() {
072        return camelContext.getManagementName();
073    }
074
075    @Override
076    public String getLoggingLevel() {
077        return backlogDebugger.getLoggingLevel();
078    }
079
080    @Override
081    public void setLoggingLevel(String level) {
082        backlogDebugger.setLoggingLevel(level);
083    }
084
085    @Override
086    public boolean isEnabled() {
087        return backlogDebugger.isEnabled();
088    }
089
090    @Override
091    public boolean isStandby() {
092        return backlogDebugger.isStandby();
093    }
094
095    @Override
096    public void enableDebugger() {
097        backlogDebugger.enableDebugger();
098    }
099
100    @Override
101    public void disableDebugger() {
102        backlogDebugger.disableDebugger();
103    }
104
105    @Override
106    public void addBreakpoint(String nodeId) {
107        backlogDebugger.addBreakpoint(nodeId);
108    }
109
110    @Override
111    public void addConditionalBreakpoint(String nodeId, String language, String predicate) {
112        backlogDebugger.addConditionalBreakpoint(nodeId, language, predicate);
113    }
114
115    @Override
116    public void removeBreakpoint(String nodeId) {
117        backlogDebugger.removeBreakpoint(nodeId);
118    }
119
120    @Override
121    public void removeAllBreakpoints() {
122        backlogDebugger.removeAllBreakpoints();
123    }
124
125    @Override
126    public Set<String> breakpoints() {
127        return backlogDebugger.getBreakpoints();
128    }
129
130    @Override
131    public void resumeBreakpoint(String nodeId) {
132        backlogDebugger.resumeBreakpoint(nodeId);
133    }
134
135    @Override
136    public void setMessageBodyOnBreakpoint(String nodeId, Object body) {
137        backlogDebugger.setMessageBodyOnBreakpoint(nodeId, body);
138    }
139
140    @Override
141    public void setMessageBodyOnBreakpoint(String nodeId, Object body, String type) {
142        try {
143            Class<?> classType = camelContext.getClassResolver().resolveMandatoryClass(type);
144            backlogDebugger.setMessageBodyOnBreakpoint(nodeId, body, classType);
145        } catch (ClassNotFoundException e) {
146            throw RuntimeCamelException.wrapRuntimeCamelException(e);
147        }
148    }
149
150    @Override
151    public void removeMessageBodyOnBreakpoint(String nodeId) {
152        backlogDebugger.removeMessageBodyOnBreakpoint(nodeId);
153    }
154
155    @Override
156    public void setMessageHeaderOnBreakpoint(String nodeId, String headerName, Object value) {
157        try {
158            backlogDebugger.setMessageHeaderOnBreakpoint(nodeId, headerName, value);
159        } catch (NoTypeConversionAvailableException e) {
160            throw RuntimeCamelException.wrapRuntimeCamelException(e);
161        }
162    }
163
164    @Override
165    public void setMessageHeaderOnBreakpoint(String nodeId, String headerName, Object value, String type) {
166        try {
167            Class<?> classType = camelContext.getClassResolver().resolveMandatoryClass(type);
168            backlogDebugger.setMessageHeaderOnBreakpoint(nodeId, headerName, value, classType);
169        } catch (Exception e) {
170            throw RuntimeCamelException.wrapRuntimeCamelException(e);
171        }
172    }
173
174    @Override
175    public void removeMessageHeaderOnBreakpoint(String nodeId, String headerName) {
176        backlogDebugger.removeMessageHeaderOnBreakpoint(nodeId, headerName);
177    }
178
179    @Override
180    public void resumeAll() {
181        backlogDebugger.resumeAll();
182    }
183
184    @Override
185    public void stepBreakpoint(String nodeId) {
186        backlogDebugger.stepBreakpoint(nodeId);
187    }
188
189    @Override
190    public boolean isSuspendedMode() {
191        return backlogDebugger.isSuspendMode();
192    }
193
194    @Override
195    public boolean isSingleStepMode() {
196        return backlogDebugger.isSingleStepMode();
197    }
198
199    @Override
200    public void step() {
201        backlogDebugger.step();
202    }
203
204    @Override
205    public void stepOver() {
206        backlogDebugger.stepOver();
207    }
208
209    @Override
210    public Set<String> suspendedBreakpointNodeIds() {
211        return backlogDebugger.getSuspendedBreakpointNodeIds();
212    }
213
214    @Override
215    public void disableBreakpoint(String nodeId) {
216        backlogDebugger.disableBreakpoint(nodeId);
217    }
218
219    @Override
220    public void enableBreakpoint(String nodeId) {
221        backlogDebugger.enableBreakpoint(nodeId);
222    }
223
224    @Override
225    public int getBodyMaxChars() {
226        return backlogDebugger.getBodyMaxChars();
227    }
228
229    @Override
230    public void setBodyMaxChars(int bodyMaxChars) {
231        backlogDebugger.setBodyMaxChars(bodyMaxChars);
232    }
233
234    @Override
235    public boolean isIncludeExchangeProperties() {
236        return backlogDebugger.isIncludeExchangeProperties();
237    }
238
239    @Override
240    public void setIncludeExchangeProperties(boolean includeExchangeProperties) {
241        backlogDebugger.setIncludeExchangeProperties(includeExchangeProperties);
242    }
243
244    @Override
245    public boolean isIncludeExchangeVariables() {
246        return backlogDebugger.isIncludeExchangeVariables();
247    }
248
249    @Override
250    public void setIncludeExchangeVariables(boolean includeExchangeVariables) {
251        backlogDebugger.setIncludeExchangeVariables(includeExchangeVariables);
252    }
253
254    @Override
255    public boolean isBodyIncludeStreams() {
256        return backlogDebugger.isBodyIncludeStreams();
257    }
258
259    @Override
260    public void setBodyIncludeStreams(boolean bodyIncludeStreams) {
261        backlogDebugger.setBodyIncludeStreams(bodyIncludeStreams);
262    }
263
264    @Override
265    public boolean isBodyIncludeFiles() {
266        return backlogDebugger.isBodyIncludeFiles();
267    }
268
269    @Override
270    public void setBodyIncludeFiles(boolean bodyIncludeFiles) {
271        backlogDebugger.setBodyIncludeFiles(bodyIncludeFiles);
272    }
273
274    @Override
275    public String dumpTracedMessagesAsXml(String nodeId) {
276        return backlogDebugger.dumpTracedMessagesAsXml(nodeId);
277    }
278
279    @Override
280    @Deprecated(since = "4.2.0")
281    public String dumpTracedMessagesAsXml(String nodeId, boolean includeExchangeProperties) {
282        return dumpTracedMessagesAsXml(nodeId);
283    }
284
285    @Override
286    public String dumpTracedMessagesAsJSon(String nodeId) {
287        return backlogDebugger.dumpTracedMessagesAsJSon(nodeId);
288    }
289
290    @Override
291    public long getDebugCounter() {
292        return backlogDebugger.getDebugCounter();
293    }
294
295    @Override
296    public void resetDebugCounter() {
297        backlogDebugger.resetDebugCounter();
298    }
299
300    @Override
301    public String validateConditionalBreakpoint(String language, String predicate) {
302        Language lan = null;
303        try {
304            lan = camelContext.resolveLanguage(language);
305            lan.createPredicate(predicate);
306            return null;
307        } catch (Exception e) {
308            if (lan == null) {
309                return e.getMessage();
310            } else {
311                return "Invalid syntax " + predicate + " due: " + e.getMessage();
312            }
313        }
314    }
315
316    @Override
317    public long getFallbackTimeout() {
318        return backlogDebugger.getFallbackTimeout();
319    }
320
321    @Override
322    public void setFallbackTimeout(long fallbackTimeout) {
323        backlogDebugger.setFallbackTimeout(fallbackTimeout);
324    }
325
326    @Override
327    public String evaluateExpressionAtBreakpoint(String nodeId, String language, String expression) {
328        return evaluateExpressionAtBreakpoint(nodeId, language, expression, "java.lang.String").toString();
329    }
330
331    @Override
332    public void setExchangePropertyOnBreakpoint(String nodeId, String exchangePropertyName, Object value) {
333        try {
334            backlogDebugger.setExchangePropertyOnBreakpoint(nodeId, exchangePropertyName, value);
335        } catch (NoTypeConversionAvailableException e) {
336            throw RuntimeCamelException.wrapRuntimeCamelException(e);
337        }
338    }
339
340    @Override
341    public void setExchangePropertyOnBreakpoint(String nodeId, String exchangePropertyName, Object value, String type) {
342        try {
343            Class<?> classType = camelContext.getClassResolver().resolveMandatoryClass(type);
344            backlogDebugger.setExchangePropertyOnBreakpoint(nodeId, exchangePropertyName, value, classType);
345        } catch (Exception e) {
346            throw RuntimeCamelException.wrapRuntimeCamelException(e);
347        }
348    }
349
350    @Override
351    public void removeExchangePropertyOnBreakpoint(String nodeId, String exchangePropertyName) {
352        backlogDebugger.removeExchangePropertyOnBreakpoint(nodeId, exchangePropertyName);
353    }
354
355    @Override
356    public void setExchangeVariableOnBreakpoint(String nodeId, String variableName, Object value) {
357        try {
358            backlogDebugger.setExchangeVariableOnBreakpoint(nodeId, variableName, value);
359        } catch (NoTypeConversionAvailableException e) {
360            throw RuntimeCamelException.wrapRuntimeCamelException(e);
361        }
362    }
363
364    @Override
365    public void setExchangeVariableOnBreakpoint(String nodeId, String variableName, Object value, String type) {
366        try {
367            Class<?> classType = camelContext.getClassResolver().resolveMandatoryClass(type);
368            backlogDebugger.setExchangeVariableOnBreakpoint(nodeId, variableName, value, classType);
369        } catch (Exception e) {
370            throw RuntimeCamelException.wrapRuntimeCamelException(e);
371        }
372    }
373
374    @Override
375    public void removeExchangeVariableOnBreakpoint(String nodeId, String variableName) {
376        backlogDebugger.removeExchangeVariableOnBreakpoint(nodeId, variableName);
377    }
378
379    @Override
380    public Object evaluateExpressionAtBreakpoint(String nodeId, String language, String expression, String resultType) {
381        Exchange suspendedExchange;
382        try {
383            Language lan = camelContext.resolveLanguage(language);
384            suspendedExchange = backlogDebugger.getSuspendedExchange(nodeId);
385            if (suspendedExchange != null) {
386                Object result;
387                Class<?> resultClass = camelContext.getClassResolver().resolveMandatoryClass(resultType);
388                if (!Boolean.class.isAssignableFrom(resultClass)) {
389                    Expression expr = lan.createExpression(expression);
390                    expr.init(camelContext);
391                    result = expr.evaluate(suspendedExchange, resultClass);
392                } else {
393                    Predicate pred = lan.createPredicate(expression);
394                    pred.init(camelContext);
395                    result = pred.matches(suspendedExchange);
396                }
397                //Test if result is serializable
398                if (!isSerializable(result)) {
399                    String resultStr = suspendedExchange.getContext().getTypeConverter().tryConvertTo(String.class, result);
400                    if (resultStr != null) {
401                        result = resultStr;
402                    }
403                }
404                return result;
405            }
406        } catch (Exception e) {
407            return e.getMessage();
408        }
409        return null;
410    }
411
412    @Override
413    public String messageHistoryOnBreakpointAsXml(String nodeId) {
414        StringBuilder messageHistoryBuilder = new StringBuilder();
415        messageHistoryBuilder.append("<messageHistory>\n");
416
417        Exchange suspendedExchange = backlogDebugger.getSuspendedExchange(nodeId);
418        if (suspendedExchange != null) {
419            List<MessageHistory> list = suspendedExchange.getProperty(ExchangePropertyKey.MESSAGE_HISTORY, List.class);
420            if (list != null) {
421                // add incoming origin of message on the top
422                String routeId = suspendedExchange.getFromRouteId();
423                Route route = suspendedExchange.getContext().getRoute(routeId);
424                String loc = route != null ? route.getSourceLocationShort() : "";
425                String id = routeId;
426                String label = "";
427                if (suspendedExchange.getFromEndpoint() != null) {
428                    label = "from["
429                            + URISupport
430                                    .sanitizeUri(
431                                            StringHelper.limitLength(suspendedExchange.getFromEndpoint().getEndpointUri(), 100))
432                            + "]";
433                }
434
435                long elapsed = suspendedExchange.getClock().elapsed();
436
437                messageHistoryBuilder
438                        .append("    <messageHistoryEntry")
439                        .append(" location=\"").append(StringHelper.xmlEncode(loc)).append("\"")
440                        .append(" routeId=\"").append(StringHelper.xmlEncode(routeId)).append("\"")
441                        .append(" processorId=\"").append(StringHelper.xmlEncode(id)).append("\"")
442                        .append(" processor=\"").append(StringHelper.xmlEncode(label)).append("\"")
443                        .append(" elapsed=\"").append(elapsed).append("\"")
444                        .append("/>\n");
445
446                for (MessageHistory history : list) {
447                    // and then each history
448                    loc = LoggerHelper.getLineNumberLoggerName(history.getNode());
449                    if (loc == null) {
450                        loc = "";
451                    }
452                    routeId = history.getRouteId() != null ? history.getRouteId() : "";
453                    id = history.getNode().getId();
454                    // we need to avoid leak the sensible information here
455                    // the sanitizeUri takes a very long time for very long string
456                    // and the format cuts this to
457                    // 78 characters, anyway. Cut this to 100 characters. This will
458                    // give enough space for removing
459                    // characters in the sanitizeUri method and will be reasonably
460                    // fast
461                    label = URISupport.sanitizeUri(StringHelper.limitLength(history.getNode().getLabel(), 100));
462                    elapsed = history.getElapsed();
463
464                    messageHistoryBuilder
465                            .append("    <messageHistoryEntry")
466                            .append(" location=\"").append(StringHelper.xmlEncode(loc)).append("\"")
467                            .append(" routeId=\"").append(StringHelper.xmlEncode(routeId)).append("\"")
468                            .append(" processorId=\"").append(StringHelper.xmlEncode(id)).append("\"")
469                            .append(" processor=\"").append(StringHelper.xmlEncode(label)).append("\"")
470                            .append(" elapsed=\"").append(elapsed).append("\"")
471                            .append("/>\n");
472                }
473            }
474        }
475        messageHistoryBuilder.append("</messageHistory>\n");
476        return messageHistoryBuilder.toString();
477    }
478
479    @Override
480    public void attach() {
481        backlogDebugger.attach();
482    }
483
484    @Override
485    public void detach() {
486        backlogDebugger.detach();
487    }
488
489    private static boolean isSerializable(Object obj) {
490        final ByteArrayOutputStream baos = new ByteArrayOutputStream(512);
491        try (ObjectOutputStream out = new ObjectOutputStream(baos)) {
492            out.writeObject(obj);
493            return true;
494        } catch (Exception e) {
495            return false;
496        }
497    }
498
499}