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