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