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