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.impl;
018
019import java.io.File;
020import java.io.FileInputStream;
021import java.io.IOException;
022import java.io.InputStream;
023import java.util.ArrayList;
024import java.util.HashSet;
025import java.util.LinkedHashMap;
026import java.util.List;
027import java.util.Map;
028import java.util.Set;
029import java.util.concurrent.atomic.AtomicInteger;
030
031import org.apache.camel.CamelContext;
032import org.apache.camel.CamelContextAware;
033import org.apache.camel.NamedNode;
034import org.apache.camel.model.Model;
035import org.apache.camel.model.RouteConfigurationDefinition;
036import org.apache.camel.model.RouteConfigurationsDefinition;
037import org.apache.camel.model.RouteDefinition;
038import org.apache.camel.model.RouteTemplateDefinition;
039import org.apache.camel.model.RouteTemplatesDefinition;
040import org.apache.camel.model.RoutesDefinition;
041import org.apache.camel.model.app.RegistryBeanDefinition;
042import org.apache.camel.model.rest.RestDefinition;
043import org.apache.camel.model.rest.RestsDefinition;
044import org.apache.camel.spi.DumpRoutesStrategy;
045import org.apache.camel.spi.ModelToXMLDumper;
046import org.apache.camel.spi.ModelToYAMLDumper;
047import org.apache.camel.spi.Resource;
048import org.apache.camel.spi.annotations.ServiceFactory;
049import org.apache.camel.support.PluginHelper;
050import org.apache.camel.support.ResourceSupport;
051import org.apache.camel.support.service.ServiceSupport;
052import org.apache.camel.util.FileUtil;
053import org.apache.camel.util.IOHelper;
054import org.apache.camel.util.StringHelper;
055import org.slf4j.Logger;
056import org.slf4j.LoggerFactory;
057
058import static org.apache.camel.support.LoggerHelper.stripSourceLocationLineNumber;
059
060/**
061 * Default {@link DumpRoutesStrategy} that dumps the routes to standard logger.
062 */
063@ServiceFactory("default-" + DumpRoutesStrategy.FACTORY)
064public class DefaultDumpRoutesStrategy extends ServiceSupport implements DumpRoutesStrategy, CamelContextAware {
065
066    private static final Logger LOG = LoggerFactory.getLogger(DefaultDumpRoutesStrategy.class);
067    private static final String DIVIDER = "--------------------------------------------------------------------------------";
068
069    private final AtomicInteger counter = new AtomicInteger();
070    private CamelContext camelContext;
071
072    private String include = "routes";
073    private boolean resolvePlaceholders = true;
074    private boolean uriAsParameters;
075    private boolean generatedIds = true;
076    private boolean log = true;
077    private String output;
078    private String outputFileName;
079
080    @Override
081    public CamelContext getCamelContext() {
082        return camelContext;
083    }
084
085    @Override
086    public void setCamelContext(CamelContext camelContext) {
087        this.camelContext = camelContext;
088    }
089
090    @Override
091    protected void doStart() throws Exception {
092        // output can be a filename, dir, or both
093        String name = FileUtil.stripPath(output);
094        if (name != null && name.contains(".")) {
095            outputFileName = name;
096            output = FileUtil.onlyPath(output);
097            if (output == null || output.isEmpty()) {
098                output = ".";
099            }
100        }
101    }
102
103    public String getInclude() {
104        return include;
105    }
106
107    public void setInclude(String include) {
108        this.include = include;
109    }
110
111    public boolean isResolvePlaceholders() {
112        return resolvePlaceholders;
113    }
114
115    public void setResolvePlaceholders(boolean resolvePlaceholders) {
116        this.resolvePlaceholders = resolvePlaceholders;
117    }
118
119    public boolean isGeneratedIds() {
120        return generatedIds;
121    }
122
123    public void setGeneratedIds(boolean generatedIds) {
124        this.generatedIds = generatedIds;
125    }
126
127    public boolean isLog() {
128        return log;
129    }
130
131    public void setLog(boolean log) {
132        this.log = log;
133    }
134
135    public String getOutput() {
136        return output;
137    }
138
139    public void setOutput(String output) {
140        this.output = output;
141    }
142
143    public boolean isUriAsParameters() {
144        return uriAsParameters;
145    }
146
147    public void setUriAsParameters(boolean uriAsParameters) {
148        this.uriAsParameters = uriAsParameters;
149    }
150
151    @Override
152    public void dumpRoutes(String format) {
153        if ("yaml".equalsIgnoreCase(format)) {
154            doDumpRoutesAsYaml(camelContext);
155        } else if ("xml".equalsIgnoreCase(format)) {
156            doDumpRoutesAsXml(camelContext);
157        }
158    }
159
160    protected void doDumpRoutesAsYaml(CamelContext camelContext) {
161        final ModelToYAMLDumper dumper = PluginHelper.getModelToYAMLDumper(camelContext);
162        final Model model = camelContext.getCamelContextExtension().getContextPlugin(Model.class);
163        final DummyResource dummy = new DummyResource(null, null);
164        final Set<String> files = new HashSet<>();
165
166        if (include.contains("*") || include.contains("all") || include.contains("beans")) {
167            int size = model.getRegistryBeans().size();
168            if (size > 0) {
169                Map<Resource, List<RegistryBeanDefinition>> groups = new LinkedHashMap<>();
170                for (RegistryBeanDefinition bean : model.getRegistryBeans()) {
171                    Resource res = bean.getResource();
172                    if (res == null) {
173                        res = dummy;
174                    }
175                    List<RegistryBeanDefinition> beans = groups.computeIfAbsent(res, resource -> new ArrayList<>());
176                    beans.add(bean);
177                }
178                StringBuilder sbLog = new StringBuilder();
179                for (Map.Entry<Resource, List<RegistryBeanDefinition>> entry : groups.entrySet()) {
180                    List<RegistryBeanDefinition> beans = entry.getValue();
181                    Resource resource = entry.getKey();
182
183                    StringBuilder sbLocal = new StringBuilder();
184                    doDumpYamlBeans(camelContext, beans, resource == dummy ? null : resource, dumper, "beans", sbLocal, sbLog);
185                    // dump each resource into its own file
186                    doDumpToDirectory(resource, sbLocal, "beans", "yaml", files);
187                }
188                if (!sbLog.isEmpty() && log) {
189                    LOG.info("Dumping {} beans as YAML", size);
190                    LOG.info("{}", sbLog);
191                }
192            }
193        }
194
195        if (include.contains("*") || include.contains("all") || include.contains("routes")) {
196            int size = model.getRouteDefinitions().size();
197            if (size > 0) {
198                Map<Resource, RoutesDefinition> groups = new LinkedHashMap<>();
199                for (RouteDefinition route : model.getRouteDefinitions()) {
200                    if ((route.isRest() != null && route.isRest()) || (route.isTemplate() != null && route.isTemplate())) {
201                        // skip routes that are rest/templates
202                        continue;
203                    }
204                    Resource res = route.getResource();
205                    if (res == null) {
206                        res = dummy;
207                    }
208                    RoutesDefinition routes = groups.computeIfAbsent(res, resource -> new RoutesDefinition());
209                    routes.getRoutes().add(route);
210                }
211                StringBuilder sbLog = new StringBuilder();
212                for (Map.Entry<Resource, RoutesDefinition> entry : groups.entrySet()) {
213                    RoutesDefinition def = entry.getValue();
214                    Resource resource = entry.getKey();
215
216                    StringBuilder sbLocal = new StringBuilder();
217                    doDumpYaml(camelContext, def, resource == dummy ? null : resource, dumper, "routes", sbLocal, sbLog);
218                    // dump each resource into its own file
219                    doDumpToDirectory(resource, sbLocal, "routes", "yaml", files);
220                }
221                if (!sbLog.isEmpty() && log) {
222                    LOG.info("Dumping {} routes as YAML", size);
223                    LOG.info("{}", sbLog);
224                }
225            }
226        }
227
228        if (include.contains("*") || include.contains("all") || include.contains("routeConfigurations")
229                || include.contains("route-configurations")) {
230            int size = model.getRouteConfigurationDefinitions().size();
231            if (size > 0) {
232                Map<Resource, RouteConfigurationsDefinition> groups = new LinkedHashMap<>();
233                for (RouteConfigurationDefinition config : model.getRouteConfigurationDefinitions()) {
234                    Resource res = config.getResource();
235                    if (res == null) {
236                        res = dummy;
237                    }
238                    RouteConfigurationsDefinition routes
239                            = groups.computeIfAbsent(res, resource -> new RouteConfigurationsDefinition());
240                    routes.getRouteConfigurations().add(config);
241                }
242                StringBuilder sbLog = new StringBuilder();
243                for (Map.Entry<Resource, RouteConfigurationsDefinition> entry : groups.entrySet()) {
244                    RouteConfigurationsDefinition def = entry.getValue();
245                    Resource resource = entry.getKey();
246
247                    StringBuilder sbLocal = new StringBuilder();
248                    doDumpYaml(camelContext, def, resource == dummy ? null : resource, dumper, "route-configurations", sbLocal,
249                            sbLog);
250                    // dump each resource into its own file
251                    doDumpToDirectory(resource, sbLocal, "route-configurations", "yaml", files);
252                }
253                if (!sbLog.isEmpty() && log) {
254                    LOG.info("Dumping {} route-configurations as YAML", size);
255                    LOG.info("{}", sbLog);
256                }
257            }
258        }
259
260        if (include.contains("*") || include.contains("all") || include.contains("rests")) {
261            int size = model.getRestDefinitions().size();
262            if (size > 0) {
263                Map<Resource, RestsDefinition> groups = new LinkedHashMap<>();
264                for (RestDefinition rest : model.getRestDefinitions()) {
265                    Resource res = rest.getResource();
266                    if (res == null) {
267                        res = dummy;
268                    }
269                    RestsDefinition rests = groups.computeIfAbsent(res, resource -> new RestsDefinition());
270                    rests.getRests().add(rest);
271                }
272                StringBuilder sbLog = new StringBuilder();
273                for (Map.Entry<Resource, RestsDefinition> entry : groups.entrySet()) {
274                    RestsDefinition def = entry.getValue();
275                    Resource resource = entry.getKey();
276
277                    StringBuilder sbLocal = new StringBuilder();
278                    doDumpYaml(camelContext, def, resource == dummy ? null : resource, dumper, "rests", sbLocal, sbLog);
279                    // dump each resource into its own file
280                    doDumpToDirectory(resource, sbLocal, "rests", "yaml", files);
281                }
282                if (!sbLog.isEmpty() && log) {
283                    LOG.info("Dumping {} rests as YAML", size);
284                    LOG.info("{}", sbLog);
285                }
286            }
287        }
288
289        if (include.contains("*") || include.contains("all") || include.contains("routeTemplates")
290                || include.contains("route-templates")) {
291            int size = model.getRouteTemplateDefinitions().size();
292            if (size > 0) {
293                Map<Resource, RouteTemplatesDefinition> groups = new LinkedHashMap<>();
294                for (RouteTemplateDefinition rt : model.getRouteTemplateDefinitions()) {
295                    Resource res = rt.getResource();
296                    if (res == null) {
297                        res = dummy;
298                    }
299                    RouteTemplatesDefinition rests = groups.computeIfAbsent(res, resource -> new RouteTemplatesDefinition());
300                    rests.getRouteTemplates().add(rt);
301                }
302                StringBuilder sbLog = new StringBuilder();
303                for (Map.Entry<Resource, RouteTemplatesDefinition> entry : groups.entrySet()) {
304                    RouteTemplatesDefinition def = entry.getValue();
305                    Resource resource = entry.getKey();
306
307                    StringBuilder sbLocal = new StringBuilder();
308                    doDumpYaml(camelContext, def, resource == dummy ? null : resource, dumper, "route-templates", sbLocal,
309                            sbLog);
310                    // dump each resource into its own file
311                    doDumpToDirectory(resource, sbLocal, "route-templates", "yaml", files);
312                }
313                if (!sbLog.isEmpty() && log) {
314                    LOG.info("Dumping {} route-templates as YAML", size);
315                    LOG.info("{}", sbLog);
316                }
317            }
318        }
319    }
320
321    protected void doDumpYaml(
322            CamelContext camelContext, NamedNode def, Resource resource,
323            ModelToYAMLDumper dumper, String kind, StringBuilder sbLocal, StringBuilder sbLog) {
324        try {
325            String dump = dumper.dumpModelAsYaml(camelContext, def, resolvePlaceholders, uriAsParameters, generatedIds);
326            sbLocal.append(dump);
327            appendLogDump(resource, dump, sbLog);
328        } catch (Exception e) {
329            LOG.warn("Error dumping {}} to YAML due to {}. This exception is ignored.", kind, e.getMessage(), e);
330        }
331    }
332
333    protected void doDumpYamlBeans(
334            CamelContext camelContext, List beans, Resource resource,
335            ModelToYAMLDumper dumper, String kind, StringBuilder sbLocal, StringBuilder sbLog) {
336        try {
337            String dump = dumper.dumpBeansAsYaml(camelContext, beans);
338            sbLocal.append(dump);
339            appendLogDump(resource, dump, sbLog);
340        } catch (Exception e) {
341            LOG.warn("Error dumping {}} to YAML due to {}. This exception is ignored.", kind, e.getMessage(), e);
342        }
343    }
344
345    protected void doDumpRoutesAsXml(CamelContext camelContext) {
346        final ModelToXMLDumper dumper = PluginHelper.getModelToXMLDumper(camelContext);
347        final Model model = camelContext.getCamelContextExtension().getContextPlugin(Model.class);
348        final DummyResource dummy = new DummyResource(null, null);
349        final Set<String> files = new HashSet<>();
350
351        if (include.contains("*") || include.contains("all") || include.contains("beans")) {
352            int size = model.getRegistryBeans().size();
353            if (size > 0) {
354                Map<Resource, List<RegistryBeanDefinition>> groups = new LinkedHashMap<>();
355                for (RegistryBeanDefinition bean : model.getRegistryBeans()) {
356                    Resource res = bean.getResource();
357                    if (res == null) {
358                        res = dummy;
359                    }
360                    List<RegistryBeanDefinition> beans = groups.computeIfAbsent(res, resource -> new ArrayList<>());
361                    beans.add(bean);
362                }
363                StringBuilder sbLog = new StringBuilder();
364                for (Map.Entry<Resource, List<RegistryBeanDefinition>> entry : groups.entrySet()) {
365                    List<RegistryBeanDefinition> beans = entry.getValue();
366                    Resource resource = entry.getKey();
367
368                    StringBuilder sbLocal = new StringBuilder();
369                    doDumpXmlBeans(camelContext, beans, resource == dummy ? null : resource, dumper, "beans", sbLocal, sbLog);
370                    // dump each resource into its own file
371                    doDumpToDirectory(resource, sbLocal, "beans", "xml", files);
372                }
373                if (!sbLog.isEmpty() && log) {
374                    LOG.info("Dumping {} beans as XML", size);
375                    LOG.info("{}", sbLog);
376                }
377            }
378        }
379
380        if (include.contains("*") || include.contains("all") || include.contains("routes")) {
381            int size = model.getRouteDefinitions().size();
382            if (size > 0) {
383                Map<Resource, RoutesDefinition> groups = new LinkedHashMap<>();
384                for (RouteDefinition route : model.getRouteDefinitions()) {
385                    if ((route.isRest() != null && route.isRest()) || (route.isTemplate() != null && route.isTemplate())) {
386                        // skip routes that are rest/templates
387                        continue;
388                    }
389                    Resource res = route.getResource();
390                    if (res == null) {
391                        res = dummy;
392                    }
393                    RoutesDefinition routes = groups.computeIfAbsent(res, resource -> new RoutesDefinition());
394                    routes.getRoutes().add(route);
395                }
396                StringBuilder sbLog = new StringBuilder();
397                for (Map.Entry<Resource, RoutesDefinition> entry : groups.entrySet()) {
398                    RoutesDefinition def = entry.getValue();
399                    Resource resource = entry.getKey();
400
401                    StringBuilder sbLocal = new StringBuilder();
402                    doDumpXml(camelContext, def, resource == dummy ? null : resource, dumper, "route", "routes", sbLocal,
403                            sbLog);
404                    // dump each resource into its own file
405                    doDumpToDirectory(resource, sbLocal, "routes", "xml", files);
406                }
407                if (!sbLog.isEmpty() && log) {
408                    LOG.info("Dumping {} routes as XML", size);
409                    LOG.info("{}", sbLog);
410                }
411            }
412        }
413
414        if (include.contains("*") || include.contains("all") || include.contains("routeConfigurations")
415                || include.contains("route-configurations")) {
416            int size = model.getRouteConfigurationDefinitions().size();
417            if (size > 0) {
418                Map<Resource, RouteConfigurationsDefinition> groups = new LinkedHashMap<>();
419                for (RouteConfigurationDefinition config : model.getRouteConfigurationDefinitions()) {
420                    Resource res = config.getResource();
421                    if (res == null) {
422                        res = dummy;
423                    }
424                    RouteConfigurationsDefinition routes
425                            = groups.computeIfAbsent(res, resource -> new RouteConfigurationsDefinition());
426                    routes.getRouteConfigurations().add(config);
427                }
428                StringBuilder sbLog = new StringBuilder();
429                for (Map.Entry<Resource, RouteConfigurationsDefinition> entry : groups.entrySet()) {
430                    RouteConfigurationsDefinition def = entry.getValue();
431                    Resource resource = entry.getKey();
432
433                    StringBuilder sbLocal = new StringBuilder();
434                    doDumpXml(camelContext, def, resource == dummy ? null : resource, dumper, "routeConfiguration",
435                            "route-configurations",
436                            sbLocal, sbLog);
437                    // dump each resource into its own file
438                    doDumpToDirectory(resource, sbLocal, "route-configurations", "xml", files);
439                }
440                if (!sbLog.isEmpty() && log) {
441                    LOG.info("Dumping {} route-configurations as XML", size);
442                    LOG.info("{}", sbLog);
443                }
444            }
445        }
446
447        if (include.contains("*") || include.contains("all") || include.contains("rests")) {
448            int size = model.getRestDefinitions().size();
449            if (size > 0) {
450                Map<Resource, RestsDefinition> groups = new LinkedHashMap<>();
451                for (RestDefinition rest : model.getRestDefinitions()) {
452                    Resource res = rest.getResource();
453                    if (res == null) {
454                        res = dummy;
455                    }
456                    RestsDefinition routes = groups.computeIfAbsent(res, resource -> new RestsDefinition());
457                    routes.getRests().add(rest);
458                }
459                StringBuilder sbLog = new StringBuilder();
460                for (Map.Entry<Resource, RestsDefinition> entry : groups.entrySet()) {
461                    RestsDefinition def = entry.getValue();
462                    Resource resource = entry.getKey();
463
464                    StringBuilder sbLocal = new StringBuilder();
465                    doDumpXml(camelContext, def, resource == dummy ? null : resource, dumper, "rest", "rests", sbLocal, sbLog);
466                    // dump each resource into its own file
467                    doDumpToDirectory(resource, sbLocal, "rests", "xml", files);
468                }
469                if (!sbLog.isEmpty() && log) {
470                    LOG.info("Dumping {} rests as XML", size);
471                    LOG.info("{}", sbLog);
472                }
473            }
474        }
475
476        if (include.contains("*") || include.contains("all") || include.contains("routeTemplates")
477                || include.contains("route-templates")) {
478            int size = model.getRouteTemplateDefinitions().size();
479            if (size > 0) {
480                Map<Resource, RouteTemplatesDefinition> groups = new LinkedHashMap<>();
481                for (RouteTemplateDefinition rt : model.getRouteTemplateDefinitions()) {
482                    Resource res = rt.getResource();
483                    if (res == null) {
484                        res = dummy;
485                    }
486                    RouteTemplatesDefinition routes = groups.computeIfAbsent(res, resource -> new RouteTemplatesDefinition());
487                    routes.getRouteTemplates().add(rt);
488                }
489                StringBuilder sbLog = new StringBuilder();
490                for (Map.Entry<Resource, RouteTemplatesDefinition> entry : groups.entrySet()) {
491                    RouteTemplatesDefinition def = entry.getValue();
492                    Resource resource = entry.getKey();
493
494                    StringBuilder sbLocal = new StringBuilder();
495                    doDumpXml(camelContext, def, resource == dummy ? null : resource, dumper, "routeTemplate",
496                            "route-templates", sbLocal, sbLog);
497                    // dump each resource into its own file
498                    doDumpToDirectory(resource, sbLocal, "route-templates", "xml", files);
499                }
500                if (!sbLog.isEmpty() && log) {
501                    LOG.info("Dumping {} route-templates as XML", size);
502                    LOG.info("{}", sbLog);
503                }
504            }
505        }
506
507        if (output != null && !files.isEmpty()) {
508            // all XML files need to have <camel> as root tag
509            doAdjustXmlFiles(files);
510        }
511    }
512
513    protected void doDumpXmlBeans(
514            CamelContext camelContext, List beans, Resource resource,
515            ModelToXMLDumper dumper, String kind, StringBuilder sbLocal, StringBuilder sbLog) {
516        try {
517            String dump = dumper.dumpBeansAsXml(camelContext, beans);
518            sbLocal.append(dump);
519            appendLogDump(resource, dump, sbLog);
520        } catch (Exception e) {
521            LOG.warn("Error dumping {}} to XML due to {}. This exception is ignored.", kind, e.getMessage(), e);
522        }
523    }
524
525    protected void doDumpXml(
526            CamelContext camelContext, NamedNode def, Resource resource,
527            ModelToXMLDumper dumper, String replace, String kind, StringBuilder sbLocal, StringBuilder sbLog) {
528        try {
529            String xml = dumper.dumpModelAsXml(camelContext, def, resolvePlaceholders, generatedIds);
530            // remove spring schema xmlns that camel-jaxb dumper includes
531            xml = StringHelper.replaceFirst(xml, " xmlns=\"http://camel.apache.org/schema/spring\">", ">");
532            xml = xml.replace("</" + replace + ">", "</" + replace + ">\n");
533            // remove outer tag (routes, rests, etc)
534            replace = replace + "s";
535            xml = StringHelper.replaceFirst(xml, "<" + replace + ">", "");
536            xml = StringHelper.replaceFirst(xml, "</" + replace + ">", "");
537
538            sbLocal.append(xml);
539            appendLogDump(resource, xml, sbLog);
540        } catch (Exception e) {
541            LOG.warn("Error dumping {}} to XML due to {}. This exception is ignored.", kind, e.getMessage(), e);
542        }
543    }
544
545    protected void doDumpToDirectory(Resource resource, StringBuilder sbLocal, String kind, String ext, Set<String> files) {
546        if (output != null && !sbLocal.isEmpty()) {
547            // make sure directory exists
548            File dir = new File(output);
549            dir.mkdirs();
550
551            String name = resolveFileName(ext, resource);
552            boolean newFile = files.isEmpty() || !files.contains(name);
553            File target = new File(output, name);
554            try {
555                if (newFile) {
556                    // write as new file (override old file if exists)
557                    IOHelper.writeText(sbLocal.toString(), target);
558                } else {
559                    // append to existing file
560                    IOHelper.appendText(sbLocal.toString(), target);
561                }
562                files.add(name);
563                LOG.info("Dumped {} to file: {}", kind, target);
564            } catch (IOException e) {
565                throw new RuntimeException("Error dumping " + kind + " to file: " + target, e);
566            }
567        }
568    }
569
570    protected void doAdjustXmlFiles(Set<String> files) {
571        for (String name : files) {
572            if (name.endsWith(".xml")) {
573                try {
574                    File file = new File(output, name);
575                    // wrap xml files with <camel> root tag
576                    StringBuilder sb = new StringBuilder();
577                    sb.append("<camel>\n\n");
578                    String xml = IOHelper.loadText(new FileInputStream(file));
579                    sb.append(xml);
580                    sb.append("\n</camel>\n");
581                    IOHelper.writeText(sb.toString(), file);
582                } catch (Exception e) {
583                    LOG.warn("Error adjusting dumped XML file: {} due to {}. This exception is ignored.", name, e.getMessage(),
584                            e);
585                }
586            }
587        }
588    }
589
590    protected void appendLogDump(Resource resource, String dump, StringBuilder sbLog) {
591        String loc = null;
592        if (resource != null) {
593            loc = extractLocationName(resource.getLocation());
594        }
595        if (loc != null) {
596            sbLog.append(String.format("%nSource: %s%n%s%n%s%n", loc, DIVIDER, dump));
597        } else {
598            sbLog.append(String.format("%n%n%s%n", dump));
599        }
600    }
601
602    private static final class DummyResource extends ResourceSupport {
603
604        private DummyResource(String scheme, String location) {
605            super(scheme, location);
606        }
607
608        @Override
609        public boolean exists() {
610            return true;
611        }
612
613        @Override
614        public InputStream getInputStream() throws IOException {
615            return null; // not in use
616        }
617    }
618
619    private static String extractLocationName(String loc) {
620        if (loc == null) {
621            return null;
622        }
623        loc = stripSourceLocationLineNumber(loc);
624        if (loc != null) {
625            if (loc.contains(":")) {
626                // strip prefix
627                loc = StringHelper.after(loc, ":", loc);
628
629                // file based such as xml and yaml
630                loc = FileUtil.stripPath(loc);
631            }
632        }
633        return loc;
634    }
635
636    protected String resolveFileName(String ext, Resource resource) {
637        if (outputFileName != null) {
638            return outputFileName;
639        }
640
641        // compute name from resource or auto-generated
642        String name = resource != null ? resource.getLocation() : null;
643        if (name == null) {
644            name = "dump" + counter.incrementAndGet();
645        }
646        // strip scheme
647        if (name.contains(":")) {
648            name = StringHelper.after(name, ":");
649        }
650        return FileUtil.onlyName(name) + "." + ext;
651    }
652
653}