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