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.runtimecatalog;
018
019import java.io.Serializable;
020import java.util.Arrays;
021import java.util.Collections;
022import java.util.LinkedHashMap;
023import java.util.LinkedHashSet;
024import java.util.List;
025import java.util.Map;
026import java.util.Set;
027
028/**
029 * Details result of validating endpoint uri.
030 */
031public class EndpointValidationResult implements Serializable {
032
033    private final String uri;
034    private int errors;
035    private int warnings;
036
037    // general error
038    private String syntaxError;
039    // general warnings
040    private String unknownComponent;
041    private String incapable;
042
043    // options
044    private Set<String> unknown;
045    private Map<String, String[]> unknownSuggestions;
046    private Set<String> lenient;
047    private Set<String> notConsumerOnly;
048    private Set<String> notProducerOnly;
049    private Set<String> required;
050    private Set<String> deprecated;
051    private Map<String, String> invalidEnum;
052    private Map<String, String[]> invalidEnumChoices;
053    private Map<String, String[]> invalidEnumSuggestions;
054    private Map<String, String> invalidReference;
055    private Map<String, String> invalidBoolean;
056    private Map<String, String> invalidInteger;
057    private Map<String, String> invalidNumber;
058    private Map<String, String> defaultValues;
059
060    public EndpointValidationResult() {
061        this(null);
062    }
063
064    public EndpointValidationResult(String uri) {
065        this.uri = uri;
066    }
067
068    public String getUri() {
069        return uri;
070    }
071
072    public boolean hasErrors() {
073        return errors > 0;
074    }
075
076    public int getNumberOfErrors() {
077        return errors;
078    }
079
080    public boolean hasWarnings() {
081        return warnings > 0;
082    }
083
084    public int getNumberOfWarnings() {
085        return warnings;
086    }
087
088    public boolean isSuccess() {
089        boolean ok = syntaxError == null && unknown == null && required == null;
090        if (ok) {
091            ok = notConsumerOnly == null && notProducerOnly == null;
092        }
093        if (ok) {
094            ok = invalidEnum == null && invalidEnumChoices == null && invalidReference == null
095                && invalidBoolean == null && invalidInteger == null && invalidNumber == null;
096        }
097        return ok;
098    }
099
100    public void addSyntaxError(String syntaxError) {
101        this.syntaxError = syntaxError;
102        errors++;
103    }
104
105    public void addIncapable(String uri) {
106        this.incapable = uri;
107        warnings++;
108    }
109
110    public void addUnknownComponent(String name) {
111        this.unknownComponent = name;
112        warnings++;
113    }
114
115    public void addUnknown(String name) {
116        if (unknown == null) {
117            unknown = new LinkedHashSet<>();
118        }
119        if (!unknown.contains(name)) {
120            unknown.add(name);
121            errors++;
122        }
123    }
124
125    public void addUnknownSuggestions(String name, String[] suggestions) {
126        if (unknownSuggestions == null) {
127            unknownSuggestions = new LinkedHashMap<>();
128        }
129        unknownSuggestions.put(name, suggestions);
130    }
131
132    public void addLenient(String name) {
133        if (lenient == null) {
134            lenient = new LinkedHashSet<>();
135        }
136        if (!lenient.contains(name)) {
137            lenient.add(name);
138        }
139    }
140
141    public void addRequired(String name) {
142        if (required == null) {
143            required = new LinkedHashSet<>();
144        }
145        if (!required.contains(name)) {
146            required.add(name);
147            errors++;
148        }
149    }
150
151    public void addDeprecated(String name) {
152        if (deprecated == null) {
153            deprecated = new LinkedHashSet<>();
154        }
155        if (!deprecated.contains(name)) {
156            deprecated.add(name);
157        }
158    }
159
160    public void addInvalidEnum(String name, String value) {
161        if (invalidEnum == null) {
162            invalidEnum = new LinkedHashMap<>();
163        }
164        if (!invalidEnum.containsKey(name)) {
165            invalidEnum.put(name, value);
166            errors++;
167        }
168    }
169
170    public void addInvalidEnumChoices(String name, String[] choices) {
171        if (invalidEnumChoices == null) {
172            invalidEnumChoices = new LinkedHashMap<>();
173        }
174        invalidEnumChoices.put(name, choices);
175    }
176
177    public void addInvalidEnumSuggestions(String name, String[] suggestions) {
178        if (invalidEnumSuggestions == null) {
179            invalidEnumSuggestions = new LinkedHashMap<>();
180        }
181        invalidEnumSuggestions.put(name, suggestions);
182    }
183
184    public void addInvalidReference(String name, String value) {
185        if (invalidReference == null) {
186            invalidReference = new LinkedHashMap<>();
187        }
188        if (!invalidReference.containsKey(name)) {
189            invalidReference.put(name, value);
190            errors++;
191        }
192    }
193
194    public void addInvalidBoolean(String name, String value) {
195        if (invalidBoolean == null) {
196            invalidBoolean = new LinkedHashMap<>();
197        }
198        if (!invalidBoolean.containsKey(name)) {
199            invalidBoolean.put(name, value);
200            errors++;
201        }
202    }
203
204    public void addInvalidInteger(String name, String value) {
205        if (invalidInteger == null) {
206            invalidInteger = new LinkedHashMap<>();
207        }
208        if (!invalidInteger.containsKey(name)) {
209            invalidInteger.put(name, value);
210            errors++;
211        }
212    }
213
214    public void addInvalidNumber(String name, String value) {
215        if (invalidNumber == null) {
216            invalidNumber = new LinkedHashMap<>();
217        }
218        if (!invalidNumber.containsKey(name)) {
219            invalidNumber.put(name, value);
220            errors++;
221        }
222    }
223
224    public void addDefaultValue(String name, String value)  {
225        if (defaultValues == null) {
226            defaultValues = new LinkedHashMap<>();
227        }
228        defaultValues.put(name, value);
229    }
230
231    public void addNotConsumerOnly(String name) {
232        if (notConsumerOnly == null) {
233            notConsumerOnly = new LinkedHashSet<>();
234        }
235        if (!notConsumerOnly.contains(name)) {
236            notConsumerOnly.add(name);
237            errors++;
238        }
239    }
240
241    public void addNotProducerOnly(String name) {
242        if (notProducerOnly == null) {
243            notProducerOnly = new LinkedHashSet<>();
244        }
245        if (!notProducerOnly.contains(name)) {
246            notProducerOnly.add(name);
247            errors++;
248        }
249    }
250
251    public String getSyntaxError() {
252        return syntaxError;
253    }
254
255    public String getIncapable() {
256        return incapable;
257    }
258
259    public Set<String> getUnknown() {
260        return unknown;
261    }
262
263    public Set<String> getLenient() {
264        return lenient;
265    }
266
267    public Map<String, String[]> getUnknownSuggestions() {
268        return unknownSuggestions;
269    }
270
271    public String getUnknownComponent() {
272        return unknownComponent;
273    }
274
275    public Set<String> getRequired() {
276        return required;
277    }
278
279    public Set<String> getDeprecated() {
280        return deprecated;
281    }
282
283    public Map<String, String> getInvalidEnum() {
284        return invalidEnum;
285    }
286
287    public Map<String, String[]> getInvalidEnumChoices() {
288        return invalidEnumChoices;
289    }
290
291    public List<String> getEnumChoices(String optionName) {
292        if (invalidEnumChoices != null) {
293            String[] enums = invalidEnumChoices.get(optionName);
294            if (enums != null) {
295                return Arrays.asList(enums);
296            }
297        }
298
299        return Collections.emptyList();
300    }
301
302    public Map<String, String> getInvalidReference() {
303        return invalidReference;
304    }
305
306    public Map<String, String> getInvalidBoolean() {
307        return invalidBoolean;
308    }
309
310    public Map<String, String> getInvalidInteger() {
311        return invalidInteger;
312    }
313
314    public Map<String, String> getInvalidNumber() {
315        return invalidNumber;
316    }
317
318    public Map<String, String> getDefaultValues() {
319        return defaultValues;
320    }
321
322    public Set<String> getNotConsumerOnly() {
323        return notConsumerOnly;
324    }
325
326    public Set<String> getNotProducerOnly() {
327        return notProducerOnly;
328    }
329
330    /**
331     * A human readable summary of the validation errors.
332     *
333     * @param includeHeader    whether to include a header
334     * @return the summary, or <tt>null</tt> if no validation errors
335     */
336    public String summaryErrorMessage(boolean includeHeader) {
337        return summaryErrorMessage(includeHeader, true, false);
338    }
339
340    /**
341     * A human readable summary of the validation errors.
342     *
343     * @param includeHeader    whether to include a header
344     * @param ignoreDeprecated whether to ignore deprecated options in use as an error or not
345     * @param includeWarnings  whether to include warnings as an error or not
346     * @return the summary, or <tt>null</tt> if no validation errors
347     */
348    public String summaryErrorMessage(boolean includeHeader, boolean ignoreDeprecated, boolean includeWarnings) {
349        boolean ok = isSuccess();
350
351        // special check if we should ignore deprecated options being used
352        if (ok && !ignoreDeprecated) {
353            ok = deprecated == null;
354        }
355
356        if (includeWarnings) {
357            if (incapable != null) {
358                return "\tIncapable of parsing uri: " + incapable;
359            } else if (syntaxError != null) {
360                return "\tSyntax error: " + syntaxError;
361            } else if (unknownComponent != null) {
362                return "\tUnknown component: " + unknownComponent;
363            }
364        }
365
366        if (ok) {
367            return null;
368        }
369
370        // for each invalid option build a reason message
371        Map<String, String> options = new LinkedHashMap<>();
372        if (unknown != null) {
373            for (String name : unknown) {
374                if (unknownSuggestions != null && unknownSuggestions.containsKey(name)) {
375                    String[] suggestions = unknownSuggestions.get(name);
376                    if (suggestions != null && suggestions.length > 0) {
377                        String str = Arrays.asList(suggestions).toString();
378                        options.put(name, "Unknown option. Did you mean: " + str);
379                    } else {
380                        options.put(name, "Unknown option");
381                    }
382                } else {
383                    options.put(name, "Unknown option");
384                }
385            }
386        }
387        if (notConsumerOnly != null) {
388            for (String name : notConsumerOnly) {
389                options.put(name, "Option not applicable in consumer only mode");
390            }
391        }
392        if (notProducerOnly != null) {
393            for (String name : notProducerOnly) {
394                options.put(name, "Option not applicable in producer only mode");
395            }
396        }
397        if (required != null) {
398            for (String name : required) {
399                options.put(name, "Missing required option");
400            }
401        }
402        if (deprecated != null) {
403            for (String name : deprecated) {
404                options.put(name, "Deprecated option");
405            }
406        }
407        if (invalidEnum != null) {
408            for (Map.Entry<String, String> entry : invalidEnum.entrySet()) {
409                String name = entry.getKey();
410                String[] choices = invalidEnumChoices.get(name);
411                String defaultValue = defaultValues != null ? defaultValues.get(entry.getKey()) : null;
412                String str = Arrays.asList(choices).toString();
413                String msg = "Invalid enum value: " + entry.getValue() + ". Possible values: " + str;
414                if (invalidEnumSuggestions != null) {
415                    String[] suggestions = invalidEnumSuggestions.get(name);
416                    if (suggestions != null && suggestions.length > 0) {
417                        str = Arrays.asList(suggestions).toString();
418                        msg += ". Did you mean: " + str;
419                    }
420                }
421                if (defaultValue != null) {
422                    msg += ". Default value: " + defaultValue;
423                }
424
425                options.put(entry.getKey(), msg);
426            }
427        }
428        if (invalidReference != null) {
429            for (Map.Entry<String, String> entry : invalidReference.entrySet()) {
430                boolean empty = isEmpty(entry.getValue());
431                if (empty) {
432                    options.put(entry.getKey(), "Empty reference value");
433                } else if (!entry.getValue().startsWith("#")) {
434                    options.put(entry.getKey(), "Invalid reference value: " + entry.getValue() + " must start with #");
435                } else {
436                    options.put(entry.getKey(), "Invalid reference value: " + entry.getValue());
437                }
438            }
439        }
440        if (invalidBoolean != null) {
441            for (Map.Entry<String, String> entry : invalidBoolean.entrySet()) {
442                boolean empty = isEmpty(entry.getValue());
443                if (empty) {
444                    options.put(entry.getKey(), "Empty boolean value");
445                } else {
446                    options.put(entry.getKey(), "Invalid boolean value: " + entry.getValue());
447                }
448            }
449        }
450        if (invalidInteger != null) {
451            for (Map.Entry<String, String> entry : invalidInteger.entrySet()) {
452                boolean empty = isEmpty(entry.getValue());
453                if (empty) {
454                    options.put(entry.getKey(), "Empty integer value");
455                } else {
456                    options.put(entry.getKey(), "Invalid integer value: " + entry.getValue());
457                }
458            }
459        }
460        if (invalidNumber != null) {
461            for (Map.Entry<String, String> entry : invalidNumber.entrySet()) {
462                boolean empty = isEmpty(entry.getValue());
463                if (empty) {
464                    options.put(entry.getKey(), "Empty number value");
465                } else {
466                    options.put(entry.getKey(), "Invalid number value: " + entry.getValue());
467                }
468            }
469        }
470
471        // build a table with the error summary nicely formatted
472        // lets use 24 as min length
473        int maxLen = 24;
474        for (String key : options.keySet()) {
475            maxLen = Math.max(maxLen, key.length());
476        }
477        String format = "%" + maxLen + "s    %s";
478
479        // build the human error summary
480        StringBuilder sb = new StringBuilder();
481        if (includeHeader) {
482            sb.append("Endpoint validator error\n");
483            sb.append("---------------------------------------------------------------------------------------------------------------------------------------\n");
484            sb.append("\n");
485        }
486        if (uri != null) {
487            sb.append("\t").append(uri).append("\n");
488        } else {
489            sb.append("\n");
490        }
491        for (Map.Entry<String, String> option : options.entrySet()) {
492            String out = String.format(format, option.getKey(), option.getValue());
493            sb.append("\n\t").append(out);
494        }
495
496        return sb.toString();
497    }
498
499    private static boolean isEmpty(String value) {
500        return value == null || value.isEmpty() || value.trim().isEmpty();
501    }
502}