This is an automated email from the ASF dual-hosted git repository. anatole pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/incubator-tamaya-sandbox.git
commit 9f6fda17dfdf7e776c3291e712b0eb89bf1a909b Author: Anatole Tresch <[email protected]> AuthorDate: Sat Dec 1 01:34:47 2018 +0100 Added experimental documentation generator tool. --- documentation/bnd.bnd | 32 +++ documentation/pom.xml | 72 ++++++ .../org/apache/tamaya/doc/ConfigDocumenter.java | 174 ++++++++++++++ .../main/java/org/apache/tamaya/doc/DocFormat.java | 25 ++ .../java/org/apache/tamaya/doc/DocumentedArea.java | 264 +++++++++++++++++++++ .../apache/tamaya/doc/DocumentedConfiguration.java | 186 +++++++++++++++ .../org/apache/tamaya/doc/DocumentedProperty.java | 188 +++++++++++++++ .../apache/tamaya/doc/annot/ConfigAreaSpec.java | 94 ++++++++ .../apache/tamaya/doc/annot/ConfigAreaSpecs.java | 38 +++ .../tamaya/doc/annot/ConfigPropertySpec.java | 63 +++++ .../tamaya/doc/annot/ConfigPropertySpecs.java | 38 +++ .../org/apache/tamaya/doc/annot/ConfigSpec.java | 48 ++++ .../apache/tamaya/doc/formats/HtmlDocFormat.java | 127 ++++++++++ .../apache/tamaya/doc/formats/TextDocFormat.java | 124 ++++++++++ .../apache/tamaya/validation/ConfigValidator.java | 26 ++ .../java/org/apache/tamaya/validation/Finding.java | 54 +++++ .../apache/tamaya/validation/ValidationCheck.java | 194 +++++++++++++++ .../apache/tamaya/validation/ValidationResult.java | 29 +++ documentation/src/main/resources/img/tamaya.png | Bin 0 -> 2015 bytes .../AnnotBasedStandaloneConfigDocumentation.java | 62 +++++ .../apache/tamaya/doc/AnnotatedDocConfigBean.java | 95 ++++++++ .../apache/tamaya/doc/ConfigDocumenterTest.java | 46 ++++ pom.xml | 1 + 23 files changed, 1980 insertions(+) diff --git a/documentation/bnd.bnd b/documentation/bnd.bnd new file mode 100644 index 0000000..ef18c20 --- /dev/null +++ b/documentation/bnd.bnd @@ -0,0 +1,32 @@ +-buildpath: \ + osgi.annotation; version=6.0.0,\ + osgi.core; version=6.0,\ + osgi.cmpn; version=6.0 + +-testpath: \ + ${junit} + +javac.source: 1.8 +javac.target: 1.8 + +Automatic-Module-Name: org.apache.tamaya.doc +Bundle-Version: ${version}.${tstamp} +Bundle-Name: Apache Tamaya - Documentation Generator +Bundle-SymbolicName: org.apache.tamaya.doc +Bundle-Description: Apacha Tamaya Configuration - Documentation Generator +Bundle-Category: Implementation +Bundle-Copyright: (C) Apache Foundation +Bundle-License: Apache Licence version 2 +Bundle-Vendor: Apache Software Foundation +Bundle-ContactAddress: [email protected] +Bundle-DocURL: http://tamaya.apache.org +Export-Package: \ + org.apache.tamaya.doc,\ + org.apache.tamaya.doc.spi +Import-Package: \ + org.apache.tamaya,\ + org.apache.tamaya.spi +Export-Service: \ + org.apache.tamaya.doc.spi.ConfigDocumentationMBean,\ + org.apache.tamaya.doc.spi.ModelProviderSpi + diff --git a/documentation/pom.xml b/documentation/pom.xml new file mode 100644 index 0000000..8ac628f --- /dev/null +++ b/documentation/pom.xml @@ -0,0 +1,72 @@ +<!-- + ~ Licensed to the Apache Software Foundation (ASF) under one + ~ or more contributor license agreements. See the NOTICE file + ~ distributed with this work for additional information + ~ regarding copyright ownership. The ASF licenses this file + ~ to you under the Apache License, Version 2.0 (the + ~ "License"); you may not use this file except in compliance + ~ with the License. You may obtain a copy createObject the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, + ~ software distributed under the License is distributed on an + ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + ~ KIND, either express or implied. See the License for the + ~ specific language governing permissions and limitations + ~ under the License. + --> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.apache.tamaya.ext</groupId> + <artifactId>tamaya-sandbox</artifactId> + <version>0.4-incubating-SNAPSHOT</version> + <relativePath>..</relativePath> + </parent> + + <artifactId>tamaya-doc_alpha</artifactId> + <name>Apache Tamaya Modules - Documentation</name> + <description>This extension module provides functionality to document your configuration options easily. + </description> + <packaging>jar</packaging> + + <dependencies> + <dependency> + <groupId>org.reflections</groupId> + <artifactId>reflections</artifactId> + <version>0.9.9-RC1</version> + </dependency> + <dependency> + <groupId>com.j2html</groupId> + <artifactId>j2html</artifactId> + <version>1.3.0</version> + </dependency> + <dependency> + <groupId>org.apache.tamaya</groupId> + <artifactId>tamaya-api</artifactId> + <version>${project.parent.version}</version> + </dependency> + <dependency> + <groupId>org.apache.tamaya</groupId> + <artifactId>tamaya-core</artifactId> + <version>${project.parent.version}</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.apache.tamaya.ext</groupId> + <artifactId>tamaya-injection-api</artifactId> + <version>${project.parent.version}</version> + </dependency> + <dependency> + <groupId>org.hamcrest</groupId> + <artifactId>java-hamcrest</artifactId> + </dependency> + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + </dependency> + </dependencies> + +</project> diff --git a/documentation/src/main/java/org/apache/tamaya/doc/ConfigDocumenter.java b/documentation/src/main/java/org/apache/tamaya/doc/ConfigDocumenter.java new file mode 100644 index 0000000..6c00d16 --- /dev/null +++ b/documentation/src/main/java/org/apache/tamaya/doc/ConfigDocumenter.java @@ -0,0 +1,174 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy createObject the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.tamaya.doc; + +import org.apache.tamaya.doc.annot.*; +import org.reflections.Reflections; +import org.reflections.scanners.FieldAnnotationsScanner; +import org.reflections.scanners.MethodAnnotationsScanner; +import org.reflections.scanners.TypeAnnotationsScanner; +import org.reflections.util.ClasspathHelper; +import org.reflections.util.ConfigurationBuilder; +import org.reflections.util.FilterBuilder; + +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +/** + * Class to read and store the current configuration documentation. + */ +public final class ConfigDocumenter { + + private DocumentedConfiguration docs = new DocumentedConfiguration(); + + /** + * Read documentation from the given classes. + * @param clazzes the classes to read, not null. + */ + public void readClasses(Class... clazzes){ + FilterBuilder filterBuilder = new FilterBuilder(); + List<URL> urls = new ArrayList<>(clazzes.length); + for(Class clazz:clazzes){ +// filterBuilder.exclude(".*"); + filterBuilder.include(clazz.getName()+".*"); + urls.add(ClasspathHelper.forClass(clazz)); + } + ConfigurationBuilder configBuilder = new ConfigurationBuilder() + .setScanners(new TypeAnnotationsScanner(), new MethodAnnotationsScanner(), new FieldAnnotationsScanner()) + .setUrls(urls) + .filterInputsBy(filterBuilder); + Reflections reflections = new Reflections(configBuilder); + readSpecs(reflections); + } + + /** + * Read documentation from the classes managed by the given classloader. + * @param classLoader the classloader, not null. + */ + public void readClasses(ClassLoader classLoader){ + ConfigurationBuilder configBuilder = new ConfigurationBuilder() + .setScanners(new TypeAnnotationsScanner(), new MethodAnnotationsScanner(), new FieldAnnotationsScanner()) + .setUrls(ClasspathHelper.forClassLoader(classLoader)); + Reflections reflections = new Reflections(configBuilder); + readSpecs(reflections); + } + + /** + * Read documentation from the given packages. + * @param packages the package names, not null. + */ + public void readPackages(String... packages) { + ConfigurationBuilder configBuilder = new ConfigurationBuilder(); + FilterBuilder filterBuilder = new FilterBuilder(); + for (String p : packages) { + filterBuilder.includePackage(p); + } + configBuilder.filterInputsBy(filterBuilder); + configBuilder.setUrls(ClasspathHelper.forJavaClassPath()); + configBuilder.setScanners(new TypeAnnotationsScanner(), new MethodAnnotationsScanner(), new FieldAnnotationsScanner()); + Reflections reflections = new Reflections(configBuilder); + readSpecs(reflections); + } + + /** + * Access the collected configuration documentation. + * @return the documentation, not null. + */ + public DocumentedConfiguration getDocumentation(){ + return docs; + } + + private void readSpecs(Reflections reflections){ + // types + for(Class type:reflections.getTypesAnnotatedWith(ConfigSpec.class)){ + readConfigSpec(type); + } + for(Class type:reflections.getTypesAnnotatedWith(ConfigAreaSpec.class)){ + readAreaSpec(type); + } + for(Class type:reflections.getTypesAnnotatedWith(ConfigAreaSpecs.class)){ + readAreaSpecs(type); + } + for(Class type:reflections.getTypesAnnotatedWith(ConfigPropertySpec.class)){ + readPropertySpec(type); + } + for(Class type:reflections.getTypesAnnotatedWith(ConfigPropertySpecs.class)){ + readPropertySpecs(type); + } + // Fields + for(Field f:reflections.getFieldsAnnotatedWith(ConfigAreaSpec.class)){ + readAreaSpec(f); + } + for(Field f:reflections.getFieldsAnnotatedWith(ConfigAreaSpecs.class)){ + readAreaSpecs(f); + } + for(Field f:reflections.getFieldsAnnotatedWith(ConfigPropertySpec.class)){ + readPropertySpec(f); + } + for(Field f:reflections.getFieldsAnnotatedWith(ConfigPropertySpecs.class)){ + readPropertySpecs(f); + } + // Methods + for(Method m:reflections.getMethodsAnnotatedWith(ConfigAreaSpec.class)){ + readAreaSpec(m); + } + for(Method m:reflections.getMethodsAnnotatedWith(ConfigAreaSpecs.class)){ + readAreaSpecs(m); + } + for(Method m:reflections.getMethodsAnnotatedWith(ConfigPropertySpec.class)){ + readPropertySpec(m); + } + for(Method m:reflections.getMethodsAnnotatedWith(ConfigPropertySpecs.class)){ + readPropertySpecs(m); + } + } + + private void readConfigSpec(Class type){ + docs.init((ConfigSpec)type.getAnnotation(ConfigSpec.class), type); + } + + private void readAreaSpecs(AnnotatedElement elem){ + ConfigAreaSpecs areaSpecs = elem.getAnnotation(ConfigAreaSpecs.class); + for (ConfigAreaSpec areaSpec:areaSpecs.value()){ + docs.addGroup(new DocumentedArea(areaSpec, elem)); + } + } + + private void readPropertySpecs(AnnotatedElement elem){ + ConfigPropertySpecs propertySpecs = elem.getAnnotation(ConfigPropertySpecs.class); + for (ConfigPropertySpec propertySpec:propertySpecs.value()){ + docs.addProperty(new DocumentedProperty(propertySpec, elem)); + } + } + + private void readAreaSpec(AnnotatedElement elem){ + docs.addGroup(new DocumentedArea(elem.getAnnotation(ConfigAreaSpec.class), elem)); + } + + private void readPropertySpec(AnnotatedElement elem){ + docs.addProperty(new DocumentedProperty(elem.getAnnotation(ConfigPropertySpec.class), elem)); + } + +} diff --git a/documentation/src/main/java/org/apache/tamaya/doc/DocFormat.java b/documentation/src/main/java/org/apache/tamaya/doc/DocFormat.java new file mode 100644 index 0000000..0f5e14a --- /dev/null +++ b/documentation/src/main/java/org/apache/tamaya/doc/DocFormat.java @@ -0,0 +1,25 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy createObject the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.tamaya.doc; + +import java.util.function.Function; + +public interface DocFormat<T> extends Function<DocumentedConfiguration, T> { + +} diff --git a/documentation/src/main/java/org/apache/tamaya/doc/DocumentedArea.java b/documentation/src/main/java/org/apache/tamaya/doc/DocumentedArea.java new file mode 100644 index 0000000..e2215b4 --- /dev/null +++ b/documentation/src/main/java/org/apache/tamaya/doc/DocumentedArea.java @@ -0,0 +1,264 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy createObject the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.tamaya.doc; + +import org.apache.tamaya.doc.annot.ConfigAreaSpec; +import org.apache.tamaya.doc.annot.ConfigPropertySpec; +import org.apache.tamaya.inject.api.ConfigDefaultSections; +import org.apache.tamaya.inject.spi.InjectionUtils; +import org.apache.tamaya.spi.PropertyValue; + +import java.lang.reflect.*; +import java.util.*; + +public final class DocumentedArea { + + private final AnnotatedElement owner; + private ConfigAreaSpec configArea; + private String path; + private String description; + private PropertyValue.ValueType groupType; + private Map<String, DocumentedProperty> properties = new HashMap<>(); + private Map<String, DocumentedArea> areas = new HashMap<>(); + private Class<?> valueType; + + private int minCardinality; + private int maxCardinality; + private Set<DocumentedArea> dependsOnGroups = new TreeSet<>(); + private Set<DocumentedProperty> dependsOnProperties = new TreeSet<>(); + + public DocumentedArea(ConfigAreaSpec areaSpec, AnnotatedElement owner){ + this.owner = owner; + this.configArea = areaSpec; + if(!areaSpec.path().isEmpty()) { + this.path = areaSpec.path(); + }else{ + this.path = evaluatePath(owner); + } + if(!areaSpec.description().isEmpty()) { + this.description = areaSpec.description(); + } + this.groupType = areaSpec.areaType(); + if(areaSpec.max()>0) { + this.maxCardinality = areaSpec.max(); + } + if(areaSpec.min()>0) { + this.minCardinality = areaSpec.min(); + } + if(Object.class!=areaSpec.valueType()) { + this.valueType = areaSpec.valueType(); + }else{ + if(owner instanceof Class){ + this.valueType = ((Class)owner); + } + } + for(ConfigPropertySpec ps:areaSpec.properties()) { + this.properties.put(ps.name(), new DocumentedProperty(ps, owner)); + } + } + + private String evaluatePath(AnnotatedElement owner) { + if(owner instanceof Field) { + return String.join(", ", InjectionUtils.getKeys((Field) owner)); + }else if(owner instanceof Method) { + return String.join(", ", InjectionUtils.getKeys((Method) owner)); + }else if(owner instanceof Class) { + ConfigDefaultSections sectionsAnnot = owner.getAnnotation(ConfigDefaultSections.class); + if(sectionsAnnot!=null){ + return String.join(", ", sectionsAnnot.value()); + } + return ((Class)owner).getName()+", "+((Class)owner).getSimpleName(); + } + return "<root>"; + } + + void resolve(DocumentedConfiguration documentation){ + if(configArea !=null){ + for(String key: configArea.dependsOnAreas()){ + this.dependsOnGroups.add(documentation.getGroup(key)); + } + for(String key: configArea.dependsOnProperties()){ + this.dependsOnProperties.add(documentation.getProperty(key)); + } + } + } + + public DocumentedArea addGroup(DocumentedArea group){ + this.areas.put(group.path, group); + return this; + } + + public DocumentedArea addProperty(DocumentedProperty property){ + this.properties.put(property.getName(), property); + return this; + } + + public AnnotatedElement getOwner() { + return owner; + } + + public String getPath() { + return path; + } + + public String getDescription() { + return description; + } + + public PropertyValue.ValueType getGroupType() { + return groupType; + } + + public Map<String, DocumentedProperty> getProperties() { + return properties; + } + + public List<DocumentedProperty> getPropertiesSorted() { + List<DocumentedProperty> result = new ArrayList<>(properties.values()); + result.sort(Comparator.comparing(DocumentedProperty::getName)); + return result; + } + + public Map<String, DocumentedArea> getAreas() { + return areas; + } + + public List<DocumentedArea> getAreasSorted() { + List<DocumentedArea> result = new ArrayList<>(areas.values()); + result.sort(Comparator.comparing(DocumentedArea::getPath)); + return result; + } + + public Class<?> getValueType() { + return valueType; + } + + public int getMinCardinality() { + return minCardinality; + } + + public int getMaxCardinality() { + return maxCardinality; + } + + public List<DocumentedArea> getDependsOnAreas() { + List<DocumentedArea> result = new ArrayList<>(dependsOnGroups); + result.sort(Comparator.comparing(DocumentedArea::getPath)); + return result; + } + + public List<DocumentedProperty> getDependsOnProperties() { + List<DocumentedProperty> result = new ArrayList<>(dependsOnProperties); + result.sort(Comparator.comparing(DocumentedProperty::getName)); + return result; + } + + public DocumentedArea path(String path) { + this.path = path; + return this; + } + + public DocumentedArea description(String description) { + this.description = description; + return this; + } + + public DocumentedArea groupType(PropertyValue.ValueType groupType) { + this.groupType = groupType; + return this; + } + + public DocumentedArea properties(Map<String, DocumentedProperty> properties) { + this.properties = properties; + return this; + } + + public DocumentedArea groups(Map<String, DocumentedArea> groups) { + this.areas = groups; + return this; + } + + public DocumentedArea valueType(Class<?> valueType) { + this.valueType = valueType; + return this; + } + + public DocumentedArea minCardinality(int minCardinality) { + this.minCardinality = minCardinality; + return this; + } + + public DocumentedArea maxCardinality(int maxCardinality) { + this.maxCardinality = maxCardinality; + return this; + } + + public DocumentedArea dependsOnGroups(Set<DocumentedArea> dependsOnGroups) { + this.dependsOnGroups = dependsOnGroups; + return this; + } + + public DocumentedArea dependsOnProperties(Set<DocumentedProperty> dependsOnProperties) { + this.dependsOnProperties = dependsOnProperties; + return this; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + DocumentedArea that = (DocumentedArea) o; + + return Objects.equals(this.dependsOnGroups, that.dependsOnGroups) && + Objects.equals(this.dependsOnProperties, that.dependsOnProperties) && + Objects.equals(this.description, that.description) && + Objects.equals(this.areas, that.areas) && + Objects.equals(this.groupType, that.groupType) && + Objects.equals(this.maxCardinality, that.maxCardinality) && + Objects.equals(this.minCardinality, that.minCardinality) && + Objects.equals(this.path, that.path) && + Objects.equals(this.properties, that.properties) && + Objects.equals(this.valueType, that.valueType); + } + + @Override + public int hashCode() { + return Objects.hash(dependsOnGroups, dependsOnProperties, description, areas, groupType, maxCardinality, + minCardinality, path, properties, valueType); + } + + @Override + public String toString() { + return "ConfigGroup{" + + "path='" + path + '\'' + + ", description='" + description + '\'' + + ", areaType=" + groupType + + ", properties=" + properties + + ", areas=" + areas + + ", valueType=" + valueType + + ", minCardinality=" + minCardinality + + ", maxCardinality=" + maxCardinality + + ", dependsOnAreas=" + dependsOnGroups + + ", dependsOnProperties=" + dependsOnProperties + + '}'; + } + + +} diff --git a/documentation/src/main/java/org/apache/tamaya/doc/DocumentedConfiguration.java b/documentation/src/main/java/org/apache/tamaya/doc/DocumentedConfiguration.java new file mode 100644 index 0000000..2596d84 --- /dev/null +++ b/documentation/src/main/java/org/apache/tamaya/doc/DocumentedConfiguration.java @@ -0,0 +1,186 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy createObject the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.tamaya.doc; + +import org.apache.tamaya.doc.annot.ConfigSpec; + +import java.util.*; + +/** + * Documentation of an application configuration. + */ +public final class DocumentedConfiguration { + + private String name; + private String version; + private Class ownerClass; + + private Map<String, DocumentedProperty> properties = new TreeMap<>(); + private Map<String, DocumentedArea> groups = new TreeMap<>(); + + /** + * Creates a new empty configuration documentation. + */ + public DocumentedConfiguration(){} + + /** + * Creates a new configuration documentation and initializes it with the values from the annotation given. + * @param annotation the spec annotation, not null. + */ + public DocumentedConfiguration(ConfigSpec annotation){ + init(annotation, null); + } + + /** + * Initializes the instance with the given annotation. + * @param annotation the annotation , not null. + * @param ownerClass the annotaed class. + */ + public void init(ConfigSpec annotation, Class ownerClass) { + this.name = annotation.name(); + this.version = annotation.version(); + this.ownerClass = ownerClass; + } + + /** + * Get the current configuration name. + * @return the name, or '<undefined>'. + */ + public String getName() { + if(name==null){ + return "<undefined>"; + } + return name; + } + + /** + * Get the current configuration version. + * @return the version, or '<undefined>'. + */ + public String getVersion() { + if(version==null){ + return "<undefined>"; + } + return version; + } + + /** + * Get the annotated class. + * @return the class, or null. + */ + public Class getOwnerClass() { + return ownerClass; + } + + /** + * Get the documented properties. + * @return the properties, never null. + */ + public Map<String, DocumentedProperty> getProperties() { + return properties; + } + + /** + * Get the documented properties. + * @return the properties, never null. + */ + public List<DocumentedArea> getAllAreasSorted() { + List<DocumentedArea> areas = new ArrayList<>(); + areas.addAll(getAreas().values()); + areas.sort(this::compareAreas); + return areas; + } + + private int compareAreas(DocumentedArea area1, DocumentedArea area2) { + return area1.getPath().compareTo(area2.getPath()); + } + + /** + * Get the documented properties. + * @return the properties, never null. + */ + public List<DocumentedProperty> getAllPropertiesSorted() { + List<DocumentedProperty> props = new ArrayList<>(); + props.addAll(getProperties().values()); + for(DocumentedArea area:getAreas().values()) { + props.addAll(area.getProperties().values()); + } + props.sort(this::compareProperties); + return props; + } + + private int compareProperties(DocumentedProperty property, DocumentedProperty property1) { + return property.getName().compareTo(property1.getName()); + } + + /** + * Get the documented areas. + * @return the areas, never null. + */ + public Map<String, DocumentedArea> getAreas() { + return groups; + } + + public DocumentedArea getGroup(String path) { + return null; + } + + public DocumentedProperty getProperty(String path) { + return null; + } + + public DocumentedConfiguration addProperty(DocumentedProperty property){ + this.properties.put(property.getName(), property); + return this; + } + + public DocumentedConfiguration addGroup(DocumentedArea group){ + this.groups.put(group.getPath(), group); + return this; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof DocumentedConfiguration)) return false; + + DocumentedConfiguration that = (DocumentedConfiguration) o; + + if (!name.equals(that.name)) return false; + return version.equals(that.version); + } + + @Override + public int hashCode() { + int result = name.hashCode(); + result = 31 * result + version.hashCode(); + return result; + } + + @Override + public String toString() { + return "DocumentedConfiguration{" + + "name='" + name + '\'' + + ", version='" + version + '\'' + + ", properties=" + properties + + ", groups=" + groups + + '}'; + } + +} diff --git a/documentation/src/main/java/org/apache/tamaya/doc/DocumentedProperty.java b/documentation/src/main/java/org/apache/tamaya/doc/DocumentedProperty.java new file mode 100644 index 0000000..b270aea --- /dev/null +++ b/documentation/src/main/java/org/apache/tamaya/doc/DocumentedProperty.java @@ -0,0 +1,188 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy createObject the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.tamaya.doc; + +import org.apache.tamaya.doc.annot.ConfigPropertySpec; +import org.apache.tamaya.inject.api.Config; +import org.apache.tamaya.inject.spi.InjectionUtils; + +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.*; + +public final class DocumentedProperty { + + private final AnnotatedElement owner; + private ConfigPropertySpec propertySpec; + private String name; + private String defaultValue; + private boolean required; + private String description; + private Class<?> valueType; + + private Set<DocumentedArea> dependsOnGroups = new TreeSet<>(); + private Set<DocumentedProperty> dependsOnProperties = new TreeSet<>(); + + + public DocumentedProperty(ConfigPropertySpec annot, AnnotatedElement owner){ + this.owner = owner; + this.propertySpec = annot; + if(!annot.name().isEmpty()) { + this.name = annot.name(); + }else{ + if(owner instanceof Field) { + this.name = String.join(", ", InjectionUtils.getKeys((Field) owner)); + }else if(owner instanceof Method) { + this.name = String.join(", ", InjectionUtils.getKeys((Method) owner)); + } + } + this.description = annot.description(); + this.valueType = annot.valueType(); + if(String.class.equals(this.valueType)){ + if(owner instanceof Field){ + Field f = (Field)owner; + this.valueType = f.getType(); + }else if(owner instanceof Method){ + Method m = (Method)owner; + this.valueType = m.getParameterTypes()[0]; + } + } + Config configAnnot = owner.getAnnotation(Config.class); + if(configAnnot!=null){ + this.required = configAnnot.required(); + this.defaultValue = configAnnot.defaultValue(); + } + } + + void resolve(DocumentedConfiguration documentation){ + if(propertySpec !=null){ + for(String key: propertySpec.dependsOnAreas()){ + this.dependsOnGroups.add(documentation.getGroup(key)); + } + for(String key: propertySpec.dependsOnProperties()){ + this.dependsOnProperties.add(documentation.getProperty(key)); + } + } + } + + public AnnotatedElement getOwner() { + return owner; + } + + public String getDefaultValue() { + if(defaultValue==null){ + return ""; + } + return defaultValue; + } + + public boolean isRequired() { + return required; + } + + public String getName() { + return name; + } + + public String getDescription() { + if(description==null){ + return ""; + } + return description; + } + + public Class<?> getValueType() { + return valueType; + } + + public Set<DocumentedArea> getDependsOnGroups() { + return dependsOnGroups; + } + + public Set<DocumentedProperty> getDependsOnProperties() { + return dependsOnProperties; + } + + public DocumentedProperty path(String path) { + this.name = path; + return this; + } + + public DocumentedProperty description(String description) { + this.description = description; + return this; + } + + public DocumentedProperty valueType(Class<?> valueType) { + this.valueType = valueType; + return this; + } + + public DocumentedProperty dependsOnGroups(Collection<DocumentedArea> dependsOnGroups) { + this.dependsOnGroups.addAll(dependsOnGroups); + return this; + } + + public DocumentedProperty dependsOnGroups(DocumentedArea... dependsOnGroups) { + this.dependsOnGroups.addAll(Arrays.asList(dependsOnGroups)); + return this; + } + + public DocumentedProperty dependsOnProperties(Collection<DocumentedProperty> dependsOnProperties) { + this.dependsOnProperties.addAll(dependsOnProperties); + return this; + } + + public DocumentedProperty dependsOnProperties(DocumentedProperty... dependsOnProperties) { + this.dependsOnProperties.addAll(Arrays.asList(dependsOnProperties)); + return this; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + DocumentedProperty that = (DocumentedProperty) o; + + return Objects.equals(this.dependsOnGroups, that.dependsOnGroups) && + Objects.equals(this.dependsOnProperties, that.dependsOnProperties) && + Objects.equals(this.description, that.description) && + Objects.equals(this.name, that.name) && + Objects.equals(this.valueType, that.valueType); + } + + @Override + public int hashCode() { + return Objects.hash(dependsOnGroups, dependsOnProperties, description, name, valueType); + } + + @Override + public String toString() { + return "ConfigGroup{" + + "name='" + name + '\'' + + ", description='" + description + '\'' + + ", valueType=" + valueType + + ", dependsOnAreas=" + dependsOnGroups + + ", dependsOnProperties=" + dependsOnProperties + + '}'; + } + +} diff --git a/documentation/src/main/java/org/apache/tamaya/doc/annot/ConfigAreaSpec.java b/documentation/src/main/java/org/apache/tamaya/doc/annot/ConfigAreaSpec.java new file mode 100644 index 0000000..589cc6c --- /dev/null +++ b/documentation/src/main/java/org/apache/tamaya/doc/annot/ConfigAreaSpec.java @@ -0,0 +1,94 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy createObject the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.tamaya.doc.annot; + +import org.apache.tamaya.spi.PropertyValue; + +import java.lang.annotation.*; + +/** + * This annotation allows to specify a configuration area. + */ +@Repeatable(ConfigAreaSpecs.class) +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD}) +public @interface ConfigAreaSpec { + /** + * The areas configuration area path, e.g. {@code foo.bar}. An empty String means the root + * of the configuration tree. + * @return the path, not null. + */ + String path()default""; + + /** + * Define a description of the area. + * @return the description. + */ + String description() default ""; + + /** + * Defines the value type that is represented by a given area path, e.g. all properties on level + * {@code foo.bar.Server} are mapped to some client classes {@code a.b.ServerConfig}. + * @return the type mapping, if any. + */ + Class<?> valueType() default Object.class; + + /** + * Allows to specifiy the node type of an area: + * <ul> + * <li><b>ARRAY: </b> is an array of child items.</li> + * <li><b>MAP: </b> is an map of named child items.</li> + * </ul> + * @return the area group type, default is {@link org.apache.tamaya.spi.PropertyValue.ValueType#MAP}. + */ + PropertyValue.ValueType areaType() default PropertyValue.ValueType.MAP; + + /** + * The minimal cardinality required. A cardinality > 0 means that the corresponding area must be present + * in your configuration. This can be ensured/checked by a configuration validation system. + * @return the minimal cardinality. + */ + int min() default 0; + + /** + * The maximal cardinality allowed. A cardinality > 0 means that the corresponding area must not be present + * in your configuration more than the configured times. This can be ensured/checked by a configuration validation + * system. + * @return the maximal cardinality. + */ + int max() default 0; + + /** + * The properties managed in this area. + * @return the properties managed within this area. + */ + ConfigPropertySpec[] properties() default {}; + + /** + * Allows to define that this area is only required, if any of these configured areas are present in your config. + * @return the area dependencies. This can be ensured/checked by a configuration validation + */ + String[] dependsOnAreas() default {}; + + /** + * Allows to define that this area is only required, if any of these configured properties are present in your config. + * @return the area dependencies. This can be ensured/checked by a configuration validation + */ + String[] dependsOnProperties() default {}; +} diff --git a/documentation/src/main/java/org/apache/tamaya/doc/annot/ConfigAreaSpecs.java b/documentation/src/main/java/org/apache/tamaya/doc/annot/ConfigAreaSpecs.java new file mode 100644 index 0000000..5735c16 --- /dev/null +++ b/documentation/src/main/java/org/apache/tamaya/doc/annot/ConfigAreaSpecs.java @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy createObject the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.tamaya.doc.annot; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * This is the container annotation for the repeatable {@link ConfigAreaSpec} annotation. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD}) +public @interface ConfigAreaSpecs { + + /** + * The contained child annotations. + * @return the contained annotations. + */ + ConfigAreaSpec[] value()default{}; +} diff --git a/documentation/src/main/java/org/apache/tamaya/doc/annot/ConfigPropertySpec.java b/documentation/src/main/java/org/apache/tamaya/doc/annot/ConfigPropertySpec.java new file mode 100644 index 0000000..0e77e05 --- /dev/null +++ b/documentation/src/main/java/org/apache/tamaya/doc/annot/ConfigPropertySpec.java @@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy createObject the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.tamaya.doc.annot; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + + +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD}) +public @interface ConfigPropertySpec { + +// boolean id() default false; + + /** + * The property name. The full property key is a combination of the parent + * @return + */ + String name() default ""; + + /** + * Define a description of the property. + * @return the description. + */ + String description() default ""; + + /** + * The property's value type, by default {@link String}. + * @return the property's value type. + */ + Class<?> valueType() default String.class; + + /** + * Allows to define that this property is only required, if any of these configured areas are present in your config. + * @return the area dependencies. This can be ensured/checked by a configuration validation + */ + String[] dependsOnAreas() default {}; + + /** + * Allows to define that this property is only required, if any of these configured properties are present in your config. + * @return the area dependencies. This can be ensured/checked by a configuration validation + */ + String[] dependsOnProperties() default {}; + +} diff --git a/documentation/src/main/java/org/apache/tamaya/doc/annot/ConfigPropertySpecs.java b/documentation/src/main/java/org/apache/tamaya/doc/annot/ConfigPropertySpecs.java new file mode 100644 index 0000000..a8fd4f0 --- /dev/null +++ b/documentation/src/main/java/org/apache/tamaya/doc/annot/ConfigPropertySpecs.java @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy createObject the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.tamaya.doc.annot; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * This is the container annotation for the repeatable {@link ConfigPropertySpec} annotation. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD}) +public @interface ConfigPropertySpecs { + + /** + * The contained child annotations. + * @return the contained annotations. + */ + ConfigPropertySpec[] value()default{}; +} diff --git a/documentation/src/main/java/org/apache/tamaya/doc/annot/ConfigSpec.java b/documentation/src/main/java/org/apache/tamaya/doc/annot/ConfigSpec.java new file mode 100644 index 0000000..5f0c798 --- /dev/null +++ b/documentation/src/main/java/org/apache/tamaya/doc/annot/ConfigSpec.java @@ -0,0 +1,48 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy createObject the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.tamaya.doc.annot; + + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation to specify the main configuration properties. + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface ConfigSpec { + String name() default ""; + String version() default ""; + + /** + * Define a description of the configuration. + * @return the description. + */ + String description() default ""; + + /** + * Should loading of the configuration fail, if a validation failed, default is {@code false}, where + * a warning is logged only. + * @return true to fail config load on configuration validation failures. + */ + boolean failOnErrors() default false; +} diff --git a/documentation/src/main/java/org/apache/tamaya/doc/formats/HtmlDocFormat.java b/documentation/src/main/java/org/apache/tamaya/doc/formats/HtmlDocFormat.java new file mode 100644 index 0000000..b37bf80 --- /dev/null +++ b/documentation/src/main/java/org/apache/tamaya/doc/formats/HtmlDocFormat.java @@ -0,0 +1,127 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy createObject the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.tamaya.doc.formats; + +import j2html.tags.ContainerTag; +import org.apache.tamaya.doc.DocFormat; +import org.apache.tamaya.doc.DocumentedArea; +import org.apache.tamaya.doc.DocumentedConfiguration; +import org.apache.tamaya.doc.DocumentedProperty; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import static j2html.TagCreator.*; + +public class HtmlDocFormat implements DocFormat<String> { + @Override + public String apply(DocumentedConfiguration documentedConfiguration) { + List<ContainerTag> areaTags = new ArrayList<>(); + for(DocumentedArea area:documentedConfiguration.getAllAreasSorted()) { + areaTags.addAll(createAreaEntries(area, null)); + } + ContainerTag propertiesTable = createPropertiesTable(documentedConfiguration.getAllPropertiesSorted(), null); + + ContainerTag head = createHead(documentedConfiguration); + ContainerTag body = body() + .with( + h1("Configuration Documentation (" + documentedConfiguration.getName() + ")"), + p("Version: " + documentedConfiguration.getVersion()), + h2("Documented Areas")).with(areaTags) + .with( + h2("Documented Properties"), + propertiesTable); + String result = html(head, body).render(); + writeResultToFile(result); + return result; + } + + private void writeResultToFile(String result) { + File file = new File("./doc.html"); + FileWriter w; + try { + w = new FileWriter(file); + w.append(result); + w.flush(); + w.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private ContainerTag createHead(DocumentedConfiguration config) { + return head(title("Tamaya Configuration - " + config.getName() + " " + + config.getVersion()), + meta().withCharset("utf-8"), + meta().withName("viewport").withContent("width=device-width, initial-scale=0.9, shrink-to-fit=yes"), + link().withRel("stylesheet") + .withHref("https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css") + .attr("integrity","sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm") + .attr("crossorigin","anonymous")); + } + + private ContainerTag createPropertiesTable(List<DocumentedProperty> properties, String parentArea) { + List<ContainerTag> propertyRows = new ArrayList<>(); + for (DocumentedProperty prop : properties) { + propertyRows.add(tr( + td(pre(prop.getName().replace(", ", "\n"))).attr("scope","row"), + td(Object.class==prop.getValueType()?"":prop.getValueType().getName()), + td(prop.getDescription()), + td(i(prop.getDefaultValue())) + )); + } + ContainerTag propertiesTable = table( + thead(tr( + th("Key(s)").attr("scope","col"), + th("Valuetype").attr("scope","col"), + th("Description").attr("width", "75%").attr("scope","col"), + th("Default").attr("scope","col").attr("width", "15%") + )).withClass("thead-dark") + ).with(propertyRows.toArray(new ContainerTag[propertyRows.size()])) + .withClass("table table-striped table-hover table-bordered table-sm") + .attr("width", "90%"); + return propertiesTable; + } + + private List<ContainerTag> createAreaEntries(DocumentedArea area, String parentArea) { + List<ContainerTag> result = new ArrayList<>(); + if(parentArea==null){ + result.add(h4(area.getPath())); + }else{ + result.add(h4(parentArea + "."+ area.getPath())); + } + result.add(ul( + li(b("Group Type: "), text(area.getGroupType().toString()), + li(b("Valuetype: "), text(area.getValueType().getName())) + .withCondHidden(Object.class==area.getValueType()), + li(b("Description: "), text(area.getDescription())) + .withCondHidden(area.getDescription()==null), + li(b("Properties: ")).with(createPropertiesTable(area.getPropertiesSorted(), area.getPath())) + .withCondHidden(area.getProperties().isEmpty()) + ))); + for(DocumentedArea subArea:area.getAreasSorted()){ + result.addAll(createAreaEntries(subArea, area.getPath())); + } + return result; + } + +} diff --git a/documentation/src/main/java/org/apache/tamaya/doc/formats/TextDocFormat.java b/documentation/src/main/java/org/apache/tamaya/doc/formats/TextDocFormat.java new file mode 100644 index 0000000..4172cb9 --- /dev/null +++ b/documentation/src/main/java/org/apache/tamaya/doc/formats/TextDocFormat.java @@ -0,0 +1,124 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy createObject the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.tamaya.doc.formats; + +import org.apache.tamaya.doc.DocFormat; +import org.apache.tamaya.doc.DocumentedConfiguration; +import org.apache.tamaya.doc.DocumentedArea; +import org.apache.tamaya.doc.DocumentedProperty; + +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Type; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class TextDocFormat implements DocFormat<String> { + @Override + public String apply(DocumentedConfiguration documentedConfiguration) { + StringBuilder b = new StringBuilder(); + b.append("Configuration:\n"); + b.append(" Spec : ").append(documentedConfiguration.getName()).append('\n'); + b.append(" Version : ").append(documentedConfiguration.getVersion()).append('\n'); + if(documentedConfiguration.getOwnerClass()!=null){ + b.append(" Owner : ").append(documentedConfiguration.getOwnerClass().getName()).append('\n'); + } + if(!documentedConfiguration.getAreas().isEmpty()) { + b.append("Areas: ").append('\n'); + for (DocumentedArea area : documentedConfiguration.getAreas().values()) { + printArea(" ", area, b); + } + } + if(!documentedConfiguration.getProperties().isEmpty()) { + b.append("Properties: ").append('\n'); + for (DocumentedProperty prop : documentedConfiguration.getProperties().values()) { + printProperty(" ", prop, b); + } + } + return b.toString(); + } + + private void printArea(String inset, DocumentedArea area, StringBuilder b) { + if(!area.getPath().isEmpty()) { + b.append(inset).append("- name : ").append(area.getPath()).append("\n"); + }else{ + b.append(inset).append("- name : NONE\n"); + } +// b.append(inset). append(" Type : area\n"); + if(area.getOwner()!=null){ + b.append(inset).append(" Owner : ").append(printOwner(area.getOwner())).append('\n'); + } + if(area.getDescription()!=null) { + b.append(inset).append(" Descr : ").append(area.getDescription()).append('\n'); + } + b.append(inset). append(" Areatype : ").append(area.getGroupType()).append('\n'); + if(area.getMinCardinality()!=0) { + b.append(inset).append(" Min : ").append(area.getMinCardinality()).append('\n'); + } + if(area.getMaxCardinality()!=0) { + b.append(inset).append(" Max : ").append(area.getMaxCardinality()).append('\n'); + } + if(area.getValueType()!=Object.class) { + b.append(inset).append(" Value : ").append(area.getValueType().getName()).append('\n'); + } + if(!area.getProperties().isEmpty()) { + b.append(inset).append(" Properties : ").append('\n'); + for (DocumentedProperty prop : area.getProperties().values()) { + printProperty(inset + " ", prop, b); + } + } + if(!area.getAreas().isEmpty()) { + b.append(inset).append(" Areas : ").append('\n'); + for (DocumentedArea childArea : area.getAreas().values()) { + printArea(inset + " ", childArea, b); + } + } + } + + private void printProperty(String inset, DocumentedProperty prop, StringBuilder b) { + b.append(inset).append("- Name : ").append(prop.getName()).append("\n"); +// b.append(inset).append(" Type : property\n"); + if(prop.getOwner()!=null){ + b.append(inset).append(" Owner : ").append(printOwner(prop.getOwner())).append('\n'); + } + if(prop.getDescription()!=null) { + b.append(inset).append(" Descr : ").append(prop.getDescription()).append('\n'); + } + b.append(inset).append(" Value : ").append(prop.getValueType().getName()).append('\n'); + } + + private String printOwner(AnnotatedElement owner) { + if(owner instanceof Type){ + return ((Type)owner).getTypeName(); + }else if(owner instanceof Field){ + Field f = (Field)owner; + return f.getDeclaringClass().getName()+ '#' + f.getName()+": " + f.getType().getName(); + } + else if(owner instanceof Method){ + Method m = (Method)owner; + return m.getDeclaringClass().getName()+ '#' + m.getName()+ + "("+String.join(", ", Stream.of(m.getParameterTypes()).map(c -> c.getName()) + .collect(Collectors.toList())) + "): " + + m.getReturnType().getName(); + }else{ + return String.valueOf(owner); + } + } +} diff --git a/documentation/src/main/java/org/apache/tamaya/validation/ConfigValidator.java b/documentation/src/main/java/org/apache/tamaya/validation/ConfigValidator.java new file mode 100644 index 0000000..9a6b548 --- /dev/null +++ b/documentation/src/main/java/org/apache/tamaya/validation/ConfigValidator.java @@ -0,0 +1,26 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy createObject the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.tamaya.validation; + +import org.apache.tamaya.Configuration; + +import java.util.function.Function; + +public interface ConfigValidator extends Function<Configuration, ValidationResult> { +} diff --git a/documentation/src/main/java/org/apache/tamaya/validation/Finding.java b/documentation/src/main/java/org/apache/tamaya/validation/Finding.java new file mode 100644 index 0000000..d75c677 --- /dev/null +++ b/documentation/src/main/java/org/apache/tamaya/validation/Finding.java @@ -0,0 +1,54 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy createObject the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.tamaya.validation; + +/** + * Enum type describing the different validation results supported. + */ +public enum Finding { + /** + * The validated item is valid + */ + VALID, + /** + * The validated item is deprecated. + */ + DEPRECATED, + /** + * The validated item is correct, but the createValue is worth a warning. + */ + WARNING, + /** + * A required parameter or section is missing. + */ + MISSING, + /** + * The validated item has an invalid createValue. + */ + ERROR; + + /** + * Method to quickly evaluate if the current state is an error state. + * + * @return true, if the state is not ERROR or MISSING. + */ + boolean isError() { + return this.ordinal() == MISSING.ordinal() || this.ordinal() == ERROR.ordinal(); + } +} diff --git a/documentation/src/main/java/org/apache/tamaya/validation/ValidationCheck.java b/documentation/src/main/java/org/apache/tamaya/validation/ValidationCheck.java new file mode 100644 index 0000000..2fb40a8 --- /dev/null +++ b/documentation/src/main/java/org/apache/tamaya/validation/ValidationCheck.java @@ -0,0 +1,194 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy createObject the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.tamaya.validation; + +import org.apache.tamaya.doc.annot.ConfigAreaSpec; +import org.apache.tamaya.doc.annot.ConfigPropertySpec; + +import java.lang.annotation.Annotation; +import java.util.Objects; + +/** + * Models a partial configuration configModel result. + */ +public final class ValidationCheck { + /** + * The configModel result. + */ + private final Finding result; + /** + * The configModel message. + */ + private final String message; + + private final Annotation spec; + + /** + * Get the checks annotation. + * @return the annotation. + */ + public Class getSpecType(){ + return spec.getClass(); + } + + /** + * Creates a new ValidationResult. + * + * @param spec the specification item, not null. + * @return a new validation result containing valid parts createObject the given model. + */ + public static ValidationCheck createValid(Annotation spec) { + return new ValidationCheck(spec, Finding.VALID, null); + } + + /** + * Creates a new ValidationResult. + * + * @param spec the validated specification, not null. + * @return a new validation result containing missing parts createObject the given model. + */ + public static ValidationCheck createMissing(Annotation spec) { + return new ValidationCheck(spec, Finding.MISSING, null); + } + + /** + * Creates a new ValidationResult. + * + * @param spec the validated specification, not null. + * @param message Additional message to be shown (optional). + * @return a new validation result containing missing parts createObject the given model with a message. + */ + public static ValidationCheck createMissing(Annotation spec, String message) { + return new ValidationCheck(spec, Finding.MISSING, message); + } + + /** + * Creates a new ValidationResult. + * + * @param spec the validated specification, not null. + * @param error error message to addNode. + * @return a new validation result containing erroneous parts createObject the given model with the given error message. + */ + public static ValidationCheck createError(Annotation spec, String error) { + return new ValidationCheck(spec, Finding.ERROR, error); + } + + /** + * Creates a new ValidationResult. + * + * @param spec the validated specification, not null. + * @param warning warning message to addNode. + * @return a new validation result containing warning parts createObject the given model with the given warning message. + */ + public static ValidationCheck createWarning(Annotation spec, String warning) { + return new ValidationCheck(spec, Finding.WARNING, warning); + } + + /** + * Creates a new ValidationResult. + * + * @param spec the validated specification, not null. + * @param alternativeUsage allows setting a message to indicate non-deprecated replacement, maybe null. + * @return a new validation result containing deprecated parts createObject the given model with an optional message. + */ + public static ValidationCheck createDeprecated(Annotation spec, String alternativeUsage) { + return new ValidationCheck(spec, Finding.DEPRECATED, alternativeUsage != null ? "Use instead: " + alternativeUsage : null); + } + + /** + * Creates a new ValidationResult. + * + * @param spec the validated specification, not null. + * @return a new validation result containing deprecated parts createObject the given model. + */ + public static ValidationCheck createDeprecated(Annotation spec) { + return new ValidationCheck(spec, Finding.DEPRECATED, null); + } + + + /** + * Constructor. + * + * @param spec the validated specification, not null. + * @param result the configModel result, not null. + * @param message the detail message. + * @return new validation result. + */ + public static ValidationCheck create(Annotation spec, Finding result, String message) { + return new ValidationCheck(spec, result, message); + } + + + /** + * Constructor. + * + * @param spec the validated specification, not null. + * @param result the configModel result, not null. + * @param message the detail message. + */ + private ValidationCheck(Annotation spec, Finding result, String message) { + this.message = message; + this.spec = Objects.requireNonNull(spec); + this.result = Objects.requireNonNull(result); + } + + /** + * Get the configModel section. + * + * @return the section, never null. + */ + public Annotation getSpec() { + return spec; + } + + /** + * Get the configModel result. + * + * @return the result, never null. + */ + public Finding getResult() { + return result; + } + + /** + * Get the detail message. + * + * @return the detail message, or null. + */ + public String getMessage() { + return message; + } + + @Override + public String toString() { + String finalMessage = ""; + if (message != null) { + finalMessage = " -> " + message; + } + if(spec instanceof ConfigPropertySpec){ + ConfigPropertySpec pspec = (ConfigPropertySpec)spec; + return result + ": " + pspec.name() + " (property)"+finalMessage + '\n'; + } + else if(spec instanceof ConfigAreaSpec){ + ConfigAreaSpec gspec = (ConfigAreaSpec)spec; + return result + ": " + gspec.path() + " (group)"+finalMessage + '\n'; + } + return result + ": " + spec + ")"+finalMessage + '\n'; + } +} diff --git a/documentation/src/main/java/org/apache/tamaya/validation/ValidationResult.java b/documentation/src/main/java/org/apache/tamaya/validation/ValidationResult.java new file mode 100644 index 0000000..6ff8a4c --- /dev/null +++ b/documentation/src/main/java/org/apache/tamaya/validation/ValidationResult.java @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy createObject the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.tamaya.validation; + +import java.util.Collection; +import java.util.Collections; + +public class ValidationResult { + + public Collection<ValidationCheck> getRules(){ + return Collections.emptyList(); + } +} diff --git a/documentation/src/main/resources/img/tamaya.png b/documentation/src/main/resources/img/tamaya.png new file mode 100644 index 0000000..16df5d7 Binary files /dev/null and b/documentation/src/main/resources/img/tamaya.png differ diff --git a/documentation/src/test/java/org/apache/tamaya/doc/AnnotBasedStandaloneConfigDocumentation.java b/documentation/src/test/java/org/apache/tamaya/doc/AnnotBasedStandaloneConfigDocumentation.java new file mode 100644 index 0000000..223b3d4 --- /dev/null +++ b/documentation/src/test/java/org/apache/tamaya/doc/AnnotBasedStandaloneConfigDocumentation.java @@ -0,0 +1,62 @@ +package org.apache.tamaya.doc; + +import org.apache.tamaya.doc.annot.ConfigAreaSpec; +import org.apache.tamaya.doc.annot.ConfigPropertySpec; +import org.apache.tamaya.doc.annot.ConfigSpec; +import org.apache.tamaya.inject.api.Config; +import org.apache.tamaya.inject.api.ConfigDefaultSections; +import org.apache.tamaya.spi.PropertyValue; + +import java.net.URL; + +@ConfigSpec( + name="Test", + version="0.1.0", + description = "Tomcat Configuration based on Tamaya" +) +@ConfigAreaSpec( + path = "kubernetes", + description = "Kubernetes Settings", + areaType = PropertyValue.ValueType.MAP, + max = 1 +) +@ConfigAreaSpec( + path = "kubernetes.security", + description = "Kubernetes Security Settings", + areaType = PropertyValue.ValueType.MAP, + max = 1 +) +@ConfigAreaSpec( + path = "kubernetes.cluster", + description = "Kubernetes Cluster Options", + areaType = PropertyValue.ValueType.MAP, + max = 1 +) +@ConfigAreaSpec( + path = "<root>", + description = "Main Options", + areaType = PropertyValue.ValueType.MAP, + properties = { + @ConfigPropertySpec(name="log", description ="Log the server startup in detail, default: false.", + valueType = Boolean.class), + @ConfigPropertySpec(name="refresh", description = "Refresh interval in millis, default: 1000ms", + valueType = Long.class), + } +) +public interface AnnotBasedStandaloneConfigDocumentation { + + @ConfigAreaSpec( + description = "Tomcat Server Endpoints", + min = 1, + areaType = PropertyValue.ValueType.ARRAY) + @ConfigDefaultSections("servers") + class Server{ + @ConfigPropertySpec(description = "The server name.") + @Config(required = true) + private String name; + @ConfigPropertySpec(description = "The server url.") + @Config(required = true) + private URL url; + } + +} diff --git a/documentation/src/test/java/org/apache/tamaya/doc/AnnotatedDocConfigBean.java b/documentation/src/test/java/org/apache/tamaya/doc/AnnotatedDocConfigBean.java new file mode 100644 index 0000000..184c4ce --- /dev/null +++ b/documentation/src/test/java/org/apache/tamaya/doc/AnnotatedDocConfigBean.java @@ -0,0 +1,95 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.tamaya.doc; + +import org.apache.tamaya.doc.annot.ConfigAreaSpec; +import org.apache.tamaya.doc.annot.ConfigPropertySpec; +import org.apache.tamaya.doc.annot.ConfigSpec; +import org.apache.tamaya.inject.api.Config; +import org.apache.tamaya.inject.api.DynamicValue; +import org.apache.tamaya.inject.api.NoConfig; +import org.apache.tamaya.spi.PropertyValue; + +import java.util.ArrayList; +import java.util.List; + +/** + * An example showing some basic annotations, using an interface to be proxied by the + * configuration system, nevertheless extending the overall Configuration interface. + * Created by Anatole on 15.02.14. + */ +@ConfigSpec( + name="Test", + version="0.1.0" +) +@ConfigAreaSpec( + path="", + description = "Default root configuration", + areaType = PropertyValue.ValueType.MAP +) +public class AnnotatedDocConfigBean { + + @ConfigPropertySpec(description = "Tests a multi key resolution") + @Config(value = {"foo.bar.myprop", "mp", "common.testdata.myProperty"}, defaultValue = "ET") + public String myParameter; + + @ConfigPropertySpec(description = "Tests a simple String description") + @Config("simple_value") + public String simpleValue; + + @ConfigPropertySpec(description = "Another test value without any explicit definition") + @Config + String anotherValue; + + @ConfigPropertySpec(description = "An explicit config parameter value.") + @Config("[host.name]") + private String hostName; + + @ConfigPropertySpec(description = "An non String typed instance.") + @Config("host.name") + private DynamicValue<String> dynamicHostname; + + @NoConfig + public String javaVersion; + + public String getAnotherValue(){ + return anotherValue; + } + + public String getHostName(){ + return hostName; + } + + public DynamicValue<String> getDynamicValue(){ + return dynamicHostname; + } + + @NoConfig + private List<String> events = new ArrayList<>(); + + // verify we don't try to inject final fields + public static final String CONSTANT = "a constant"; + + + @Config("java.version") + void setJavaVersion(String version){ + this.javaVersion = version; + } + +} diff --git a/documentation/src/test/java/org/apache/tamaya/doc/ConfigDocumenterTest.java b/documentation/src/test/java/org/apache/tamaya/doc/ConfigDocumenterTest.java new file mode 100644 index 0000000..3b6bdcd --- /dev/null +++ b/documentation/src/test/java/org/apache/tamaya/doc/ConfigDocumenterTest.java @@ -0,0 +1,46 @@ +package org.apache.tamaya.doc; + +import org.apache.tamaya.doc.formats.HtmlDocFormat; +import org.apache.tamaya.doc.formats.TextDocFormat; +import org.junit.Test; + +import static org.junit.Assert.*; + +public class ConfigDocumenterTest { + + @Test + public void getDocumentationAndPrint_ConfigBean() { + ConfigDocumenter reader = new ConfigDocumenter(); + reader.readClasses(AnnotatedDocConfigBean.class); + DocumentedConfiguration documentation = reader.getDocumentation(); + assertNotNull(documentation); + System.out.println(new TextDocFormat().apply(documentation)); + } + + @Test + public void getDocumentationAndPrint_AnnotationType() { + ConfigDocumenter reader = new ConfigDocumenter(); + reader.readClasses(AnnotBasedStandaloneConfigDocumentation.class); + DocumentedConfiguration documentation = reader.getDocumentation(); + assertNotNull(documentation); + System.out.println(new TextDocFormat().apply(documentation)); + } + + @Test + public void getDocumentationAndPrint_Package() { + ConfigDocumenter reader = new ConfigDocumenter(); + reader.readPackages("org.apache.tamaya.doc"); + DocumentedConfiguration documentation = reader.getDocumentation(); + assertNotNull(documentation); + System.out.println(new TextDocFormat().apply(documentation)); + } + + @Test + public void getDocumentationAndPrint_Package_html() { + ConfigDocumenter reader = new ConfigDocumenter(); + reader.readPackages("org.apache.tamaya.doc"); + DocumentedConfiguration documentation = reader.getDocumentation(); + assertNotNull(documentation); + System.out.println(new HtmlDocFormat().apply(documentation)); + } +} \ No newline at end of file diff --git a/pom.xml b/pom.xml index 715ca1d..167366d 100644 --- a/pom.xml +++ b/pom.xml @@ -809,6 +809,7 @@ under the License. <module>uom</module> <module>vertx</module> <module>configjsr</module> + <module>documentation</module> <!-- Once the API is officially available ... --> <!-- module>configjsr</module--> </modules>
