in tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/EndpointSchemaGeneratorMojo.java [917:1123]
protected void findComponentClassProperties(
ComponentModel componentModel, Class<?> classElement,
String prefix, String nestedTypeName, String nestedFieldName) {
final Class<?> orgClassElement = classElement;
Set<String> excludes = new HashSet<>();
while (true) {
processMetadataClassAnnotation(componentModel, classElement, excludes);
List<Method> methods = findCandidateClassMethods(classElement);
// if the component has options with annotations then we only want to generate options that are annotated
// as ideally components should favour doing this, so we can control what is an option and what is not
List<Field> fields = Stream.of(classElement.getDeclaredFields()).toList();
boolean annotationBasedOptions = fields.stream().anyMatch(f -> f.getAnnotation(Metadata.class) != null)
|| methods.stream().anyMatch(m -> m.getAnnotation(Metadata.class) != null);
if (!methods.isEmpty() && !annotationBasedOptions) {
getLog().warn("Component class " + classElement.getName() + " has not been marked up with @Metadata for "
+ methods.size() + " options.");
}
for (Method method : methods) {
String methodName = method.getName();
Metadata metadata = method.getAnnotation(Metadata.class);
boolean deprecated = method.getAnnotation(Deprecated.class) != null;
String deprecationNote = null;
if (metadata != null) {
deprecationNote = metadata.deprecationNote();
}
// we usually favor putting the @Metadata annotation on the
// field instead of the setter, so try to use it if its there
String fieldName = methodName.substring(3);
fieldName = fieldName.substring(0, 1).toLowerCase() + fieldName.substring(1);
Field fieldElement = getFieldElement(classElement, fieldName);
if (fieldElement != null && metadata == null) {
metadata = fieldElement.getAnnotation(Metadata.class);
}
if (metadata != null && metadata.skip()) {
continue;
}
// skip methods/fields which has no annotation if we only look for annotation based
if (annotationBasedOptions && metadata == null) {
continue;
}
// if the field type is a nested parameter then iterate
// through its fields
if (fieldElement != null) {
Class<?> fieldTypeElement = fieldElement.getType();
String fieldTypeName = getTypeName(GenericsUtil.resolveType(orgClassElement, fieldElement));
UriParams fieldParams = fieldTypeElement.getAnnotation(UriParams.class);
if (fieldParams != null) {
String nestedPrefix = prefix;
String extraPrefix = fieldParams.prefix();
if (!Strings.isNullOrEmpty(extraPrefix)) {
nestedPrefix += extraPrefix;
}
nestedTypeName = fieldTypeName;
nestedFieldName = fieldElement.getName();
findClassProperties(componentModel, fieldTypeElement, Collections.emptySet(), nestedPrefix,
nestedTypeName, nestedFieldName, true);
nestedTypeName = null;
nestedFieldName = null;
// we also want to include the configuration itself so continue and add ourselves
}
}
boolean required = metadata != null && metadata.required();
String label = metadata != null ? metadata.label() : null;
boolean secret = metadata != null && metadata.secret();
boolean autowired = metadata != null && metadata.autowired();
boolean supportFileReference = metadata != null && metadata.supportFileReference();
boolean largeInput = metadata != null && metadata.largeInput();
String inputLanguage = metadata != null ? metadata.inputLanguage() : null;
// we do not yet have default values / notes / as no annotation
// support yet
// String defaultValueNote = param.defaultValueNote();
Object defaultValue = metadata != null ? metadata.defaultValue() : "";
String defaultValueNote = null;
String name = prefix + fieldName;
String displayName = metadata != null ? metadata.displayName() : null;
// compute a display name if we don't have anything
if (Strings.isNullOrEmpty(displayName)) {
displayName = Strings.asTitle(name);
}
Class<?> fieldType = method.getParameters()[0].getType();
String fieldTypeName = getTypeName(GenericsUtil.resolveParameterTypes(orgClassElement, method)[0]);
String docComment = findJavaDoc(method, fieldName, name, classElement, false);
if (Strings.isNullOrEmpty(docComment)) {
docComment = metadata != null ? metadata.description() : null;
}
if (Strings.isNullOrEmpty(docComment)) {
// apt cannot grab javadoc from camel-core, only from
// annotations
if ("setHeaderFilterStrategy".equals(methodName)) {
docComment = HEADER_FILTER_STRATEGY_JAVADOC;
} else {
docComment = "";
}
}
// gather enums
List<String> enums = getEnums(metadata, fieldType);
// the field type may be overloaded by another type
boolean isDuration = false;
if (metadata != null && !Strings.isNullOrEmpty(metadata.javaType())) {
String mjt = metadata.javaType();
if ("java.time.Duration".equals(mjt)) {
isDuration = true;
} else {
fieldTypeName = mjt;
}
}
// generics for collection types
String nestedType = null;
String desc = fieldTypeName;
if (desc.contains("<") && desc.contains(">")) {
desc = Strings.between(desc, "<", ">");
// if it has additional nested types, then we only want the outer type
int pos = desc.indexOf('<');
if (pos != -1) {
desc = desc.substring(0, pos);
}
// if its a map then it has a key/value, so we only want the last part
pos = desc.indexOf(',');
if (pos != -1) {
desc = desc.substring(pos + 1);
}
desc = desc.replace('$', '.');
desc = desc.trim();
// skip if the type is generic or a wildcard
if (!desc.isEmpty() && desc.indexOf('?') == -1 && !desc.contains(" extends ")) {
nestedType = desc;
}
}
// prepare default value so its value is correct according to its type
defaultValue = getDefaultValue(defaultValue, fieldTypeName, isDuration);
String group = EndpointHelper.labelAsGroupName(label, componentModel.isConsumerOnly(),
componentModel.isProducerOnly());
// filter out consumer/producer only
boolean accept = !excludes.contains(name);
if (componentModel.isConsumerOnly() && "producer".equals(group)) {
accept = false;
} else if (componentModel.isProducerOnly() && "consumer".equals(group)) {
accept = false;
}
if (accept) {
Optional<ComponentOptionModel> prev = componentModel.getComponentOptions().stream()
.filter(opt -> name.equals(opt.getName())).findAny();
if (prev.isPresent()) {
String prv = prev.get().getJavaType();
String cur = fieldTypeName;
if (prv.equals("java.lang.String")
|| prv.equals("java.lang.String[]") && cur.equals("java.util.Collection<java.lang.String>")) {
componentModel.getComponentOptions().remove(prev.get());
} else {
accept = false;
}
}
}
if (accept) {
ComponentOptionModel option = new ComponentOptionModel();
option.setKind("property");
option.setName(name);
option.setDisplayName(displayName);
option.setType(MojoHelper.getType(fieldTypeName, false, isDuration));
option.setJavaType(fieldTypeName);
option.setRequired(required);
option.setDefaultValue(defaultValue);
option.setDefaultValueNote(defaultValueNote);
option.setDescription(docComment.trim());
option.setDeprecated(deprecated);
option.setDeprecationNote(deprecationNote);
option.setSecret(secret);
option.setAutowired(autowired);
option.setGroup(group);
option.setLabel(label);
option.setEnums(enums);
option.setNestedType(nestedType);
option.setConfigurationClass(nestedTypeName);
option.setConfigurationField(nestedFieldName);
option.setSupportFileReference(supportFileReference);
option.setLargeInput(largeInput);
option.setInputLanguage(inputLanguage);
componentModel.addComponentOption(option);
}
}
// check super classes which may also have fields
Class<?> superclass = classElement.getSuperclass();
if (superclass != null && superclass != Object.class) {
classElement = superclass;
} else {
break;
}
}
}