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