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