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