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 void skipOver() {
211        backlogDebugger.skipOver();
212    }
213
214    @Override
215    public Set<String> suspendedBreakpointNodeIds() {
216        return backlogDebugger.getSuspendedBreakpointNodeIds();
217    }
218
219    @Override
220    public Set<String> suspendedBreakpointExchangeIds() {
221        return backlogDebugger.getSuspendedExchangeIds();
222    }
223
224    @Override
225    public void disableBreakpoint(String nodeId) {
226        backlogDebugger.disableBreakpoint(nodeId);
227    }
228
229    @Override
230    public void enableBreakpoint(String nodeId) {
231        backlogDebugger.enableBreakpoint(nodeId);
232    }
233
234    @Override
235    public int getBodyMaxChars() {
236        return backlogDebugger.getBodyMaxChars();
237    }
238
239    @Override
240    public void setBodyMaxChars(int bodyMaxChars) {
241        backlogDebugger.setBodyMaxChars(bodyMaxChars);
242    }
243
244    @Override
245    public boolean isIncludeExchangeProperties() {
246        return backlogDebugger.isIncludeExchangeProperties();
247    }
248
249    @Override
250    public void setIncludeExchangeProperties(boolean includeExchangeProperties) {
251        backlogDebugger.setIncludeExchangeProperties(includeExchangeProperties);
252    }
253
254    @Override
255    public boolean isIncludeExchangeVariables() {
256        return backlogDebugger.isIncludeExchangeVariables();
257    }
258
259    @Override
260    public void setIncludeExchangeVariables(boolean includeExchangeVariables) {
261        backlogDebugger.setIncludeExchangeVariables(includeExchangeVariables);
262    }
263
264    @Override
265    public boolean isBodyIncludeStreams() {
266        return backlogDebugger.isBodyIncludeStreams();
267    }
268
269    @Override
270    public void setBodyIncludeStreams(boolean bodyIncludeStreams) {
271        backlogDebugger.setBodyIncludeStreams(bodyIncludeStreams);
272    }
273
274    @Override
275    public boolean isBodyIncludeFiles() {
276        return backlogDebugger.isBodyIncludeFiles();
277    }
278
279    @Override
280    public void setBodyIncludeFiles(boolean bodyIncludeFiles) {
281        backlogDebugger.setBodyIncludeFiles(bodyIncludeFiles);
282    }
283
284    @Override
285    public String dumpTracedMessagesAsXml(String nodeId) {
286        return backlogDebugger.dumpTracedMessagesAsXml(nodeId);
287    }
288
289    @Override
290    @Deprecated(since = "4.2.0")
291    public String dumpTracedMessagesAsXml(String nodeId, boolean includeExchangeProperties) {
292        return dumpTracedMessagesAsXml(nodeId);
293    }
294
295    @Override
296    public String dumpTracedMessagesAsJSon(String nodeId) {
297        return backlogDebugger.dumpTracedMessagesAsJSon(nodeId);
298    }
299
300    @Override
301    public long getDebugCounter() {
302        return backlogDebugger.getDebugCounter();
303    }
304
305    @Override
306    public void resetDebugCounter() {
307        backlogDebugger.resetDebugCounter();
308    }
309
310    @Override
311    public String validateConditionalBreakpoint(String language, String predicate) {
312        Language lan = null;
313        try {
314            lan = camelContext.resolveLanguage(language);
315            lan.createPredicate(predicate);
316            return null;
317        } catch (Exception e) {
318            if (lan == null) {
319                return e.getMessage();
320            } else {
321                return "Invalid syntax " + predicate + " due: " + e.getMessage();
322            }
323        }
324    }
325
326    @Override
327    public long getFallbackTimeout() {
328        return backlogDebugger.getFallbackTimeout();
329    }
330
331    @Override
332    public void setFallbackTimeout(long fallbackTimeout) {
333        backlogDebugger.setFallbackTimeout(fallbackTimeout);
334    }
335
336    @Override
337    public String evaluateExpressionAtBreakpoint(String nodeId, String language, String expression) {
338        return evaluateExpressionAtBreakpoint(nodeId, language, expression, "java.lang.String").toString();
339    }
340
341    @Override
342    public void setExchangePropertyOnBreakpoint(String nodeId, String exchangePropertyName, Object value) {
343        try {
344            backlogDebugger.setExchangePropertyOnBreakpoint(nodeId, exchangePropertyName, value);
345        } catch (NoTypeConversionAvailableException e) {
346            throw RuntimeCamelException.wrapRuntimeCamelException(e);
347        }
348    }
349
350    @Override
351    public void setExchangePropertyOnBreakpoint(String nodeId, String exchangePropertyName, Object value, String type) {
352        try {
353            Class<?> classType = camelContext.getClassResolver().resolveMandatoryClass(type);
354            backlogDebugger.setExchangePropertyOnBreakpoint(nodeId, exchangePropertyName, value, classType);
355        } catch (Exception e) {
356            throw RuntimeCamelException.wrapRuntimeCamelException(e);
357        }
358    }
359
360    @Override
361    public void removeExchangePropertyOnBreakpoint(String nodeId, String exchangePropertyName) {
362        backlogDebugger.removeExchangePropertyOnBreakpoint(nodeId, exchangePropertyName);
363    }
364
365    @Override
366    public void setExchangeVariableOnBreakpoint(String nodeId, String variableName, Object value) {
367        try {
368            backlogDebugger.setExchangeVariableOnBreakpoint(nodeId, variableName, value);
369        } catch (NoTypeConversionAvailableException e) {
370            throw RuntimeCamelException.wrapRuntimeCamelException(e);
371        }
372    }
373
374    @Override
375    public void setExchangeVariableOnBreakpoint(String nodeId, String variableName, Object value, String type) {
376        try {
377            Class<?> classType = camelContext.getClassResolver().resolveMandatoryClass(type);
378            backlogDebugger.setExchangeVariableOnBreakpoint(nodeId, variableName, value, classType);
379        } catch (Exception e) {
380            throw RuntimeCamelException.wrapRuntimeCamelException(e);
381        }
382    }
383
384    @Override
385    public void removeExchangeVariableOnBreakpoint(String nodeId, String variableName) {
386        backlogDebugger.removeExchangeVariableOnBreakpoint(nodeId, variableName);
387    }
388
389    @Override
390    public Object evaluateExpressionAtBreakpoint(String nodeId, String language, String expression, String resultType) {
391        Exchange suspendedExchange;
392        try {
393            Language lan = camelContext.resolveLanguage(language);
394            suspendedExchange = backlogDebugger.getSuspendedExchange(nodeId);
395            if (suspendedExchange != null) {
396                Object result;
397                Class<?> resultClass = camelContext.getClassResolver().resolveMandatoryClass(resultType);
398                if (!Boolean.class.isAssignableFrom(resultClass)) {
399                    Expression expr = lan.createExpression(expression);
400                    expr.init(camelContext);
401                    result = expr.evaluate(suspendedExchange, resultClass);
402                } else {
403                    Predicate pred = lan.createPredicate(expression);
404                    pred.init(camelContext);
405                    result = pred.matches(suspendedExchange);
406                }
407                //Test if result is serializable
408                if (!isSerializable(result)) {
409                    String resultStr = suspendedExchange.getContext().getTypeConverter().tryConvertTo(String.class, result);
410                    if (resultStr != null) {
411                        result = resultStr;
412                    }
413                }
414                return result;
415            }
416        } catch (Exception e) {
417            return e.getMessage();
418        }
419        return null;
420    }
421
422    @Override
423    public String messageHistoryOnBreakpointAsXml(String nodeId) {
424        StringBuilder messageHistoryBuilder = new StringBuilder();
425        messageHistoryBuilder.append("<messageHistory>\n");
426
427        Exchange suspendedExchange = backlogDebugger.getSuspendedExchange(nodeId);
428        if (suspendedExchange != null) {
429            List<MessageHistory> list = suspendedExchange.getProperty(ExchangePropertyKey.MESSAGE_HISTORY, List.class);
430            if (list != null) {
431                // add incoming origin of message on the top
432                String routeId = suspendedExchange.getFromRouteId();
433                Route route = suspendedExchange.getContext().getRoute(routeId);
434                String loc = route != null ? route.getSourceLocationShort() : "";
435                String id = routeId;
436                String label = "";
437                if (suspendedExchange.getFromEndpoint() != null) {
438                    label = "from["
439                            + URISupport
440                                    .sanitizeUri(
441                                            StringHelper.limitLength(suspendedExchange.getFromEndpoint().getEndpointUri(), 100))
442                            + "]";
443                }
444
445                long elapsed = suspendedExchange.getClock().elapsed();
446
447                messageHistoryBuilder
448                        .append("    <messageHistoryEntry")
449                        .append(" location=\"").append(StringHelper.xmlEncode(loc)).append("\"")
450                        .append(" routeId=\"").append(StringHelper.xmlEncode(routeId)).append("\"")
451                        .append(" processorId=\"").append(StringHelper.xmlEncode(id)).append("\"")
452                        .append(" processor=\"").append(StringHelper.xmlEncode(label)).append("\"")
453                        .append(" elapsed=\"").append(elapsed).append("\"")
454                        .append("/>\n");
455
456                for (MessageHistory history : list) {
457                    // and then each history
458                    loc = LoggerHelper.getLineNumberLoggerName(history.getNode());
459                    if (loc == null) {
460                        loc = "";
461                    }
462                    routeId = history.getRouteId() != null ? history.getRouteId() : "";
463                    id = history.getNode().getId();
464                    // we need to avoid leak the sensible information here
465                    // the sanitizeUri takes a very long time for very long string
466                    // and the format cuts this to
467                    // 78 characters, anyway. Cut this to 100 characters. This will
468                    // give enough space for removing
469                    // characters in the sanitizeUri method and will be reasonably
470                    // fast
471                    label = URISupport.sanitizeUri(StringHelper.limitLength(history.getNode().getLabel(), 100));
472                    elapsed = history.getElapsed();
473
474                    messageHistoryBuilder
475                            .append("    <messageHistoryEntry")
476                            .append(" location=\"").append(StringHelper.xmlEncode(loc)).append("\"")
477                            .append(" routeId=\"").append(StringHelper.xmlEncode(routeId)).append("\"")
478                            .append(" processorId=\"").append(StringHelper.xmlEncode(id)).append("\"")
479                            .append(" processor=\"").append(StringHelper.xmlEncode(label)).append("\"")
480                            .append(" elapsed=\"").append(elapsed).append("\"")
481                            .append("/>\n");
482                }
483            }
484        }
485        messageHistoryBuilder.append("</messageHistory>\n");
486        return messageHistoryBuilder.toString();
487    }
488
489    @Override
490    public void attach() {
491        backlogDebugger.attach();
492    }
493
494    @Override
495    public void detach() {
496        backlogDebugger.detach();
497    }
498
499    private static boolean isSerializable(Object obj) {
500        final ByteArrayOutputStream baos = new ByteArrayOutputStream(512);
501        try (ObjectOutputStream out = new ObjectOutputStream(baos)) {
502            out.writeObject(obj);
503            return true;
504        } catch (Exception e) {
505            return false;
506        }
507    }
508
509}