Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft: Fix access rules #121

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,31 +15,47 @@
*/
package io.fabric8.crd.generator;

import static io.sundr.model.utils.Types.BOOLEAN_REF;
import static io.sundr.model.utils.Types.DOUBLE_REF;
import static io.sundr.model.utils.Types.INT_REF;
import static io.sundr.model.utils.Types.LONG_REF;
import static io.sundr.model.utils.Types.STRING_REF;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import io.fabric8.crd.generator.annotation.SchemaSwap;

import io.fabric8.crd.generator.utils.Properties;
import io.fabric8.crd.generator.utils.Types;
import io.fabric8.kubernetes.api.model.Duration;
import io.fabric8.kubernetes.api.model.IntOrString;
import io.fabric8.kubernetes.api.model.Quantity;
import io.sundr.builder.internal.functions.TypeAs;
import io.sundr.model.*;
import io.sundr.model.AnnotationRef;
import io.sundr.model.ClassRef;
import io.sundr.model.Method;
import io.sundr.model.PrimitiveRefBuilder;
import io.sundr.model.Property;
import io.sundr.model.TypeDef;
import io.sundr.model.TypeRef;
import io.sundr.utils.Strings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;
import java.util.stream.Collectors;

import static io.sundr.model.utils.Types.BOOLEAN_REF;

import static io.sundr.model.utils.Types.STRING_REF;

import static io.sundr.model.utils.Types.INT_REF;

import static io.sundr.model.utils.Types.LONG_REF;

import static io.sundr.model.utils.Types.DOUBLE_REF;

/**
* Encapsulates the common logic supporting OpenAPI schema generation for CRD generation.
Expand Down Expand Up @@ -237,7 +253,8 @@ private T internalFromImpl(TypeDef definition, Set<String> visited, List<Interna
// index potential accessors by name for faster lookup
final Map<String, Method> accessors = indexPotentialAccessors(definition);

for (Property property : definition.getProperties()) {
List<Property> visibleProperties = Properties.getVisibleProperties(definition);
for (Property property : visibleProperties) {
String name = property.getName();
if (property.isStatic() || ignores.contains(name)) {
LOGGER.debug("Ignoring property {}", name);
Expand Down Expand Up @@ -485,6 +502,9 @@ public Set<InternalSchemaSwap> getMatchedSchemaSwaps() {

private boolean isPotentialAccessor(Method method) {
final String name = method.getName();
if (!method.isPublic() || method.isStatic() || method.isTransient()) {
return false;
}
return name.startsWith("is") || name.startsWith("get") || name.startsWith("set");
}

Expand Down Expand Up @@ -589,7 +609,7 @@ private T internalFromImpl(String name, TypeRef typeRef, Set<String> visited, Li

// check if we're dealing with an enum
if (def.isEnum()) {
final JsonNode[] enumValues = def.getProperties().stream()
final JsonNode[] enumValues = Properties.getVisibleProperties(def).stream()
.map(this::extractUpdatedNameFromJacksonPropertyIfPresent)
.filter(n -> !n.startsWith("$"))
.map(JsonNodeFactory.instance::textNode)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package io.fabric8.crd.generator.utils;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import io.sundr.model.AnnotationRef;
import io.sundr.model.Method;
import io.sundr.model.Property;
import io.sundr.model.PropertyBuilder;
import io.sundr.model.TypeDef;

// TODO: We should use the visibility settings from the object mapper that is being used.
public class Properties {
// Get a list of visible properties.
// Returns public fields as well as private fields that are accessible
// through get<fieldName> or is<fieldName> methods
public static List<Property> getVisibleProperties(TypeDef def) {
List<Property> props = new ArrayList<>();
if (def.getFullyQualifiedName().startsWith("java.")) {
return props;
}

// Get public properties
for (Property p : def.getProperties()) {
if (!p.isPublic() || p.isTransient()) {
continue;
}
props.add(p);
}

// Get getters
for (Method m : def.getMethods()) {
if (
!m.isPublic() || m.isTransient() || m.isStatic() ||
m.getName().equals("getClass") ||
!(m.getName().startsWith("get") || m.getName().startsWith("is"))
) {
continue;
}

String propName = propertyNameForMethod(m);
// Find the property for the getter.
boolean found = false;
for (Property p : def.getProperties()) {
if (p.getName().equals(propName)) {

List<AnnotationRef> mergedAnnotations = Stream.concat(
p.getAnnotations().stream(),
m.getAnnotations().stream()
)
.collect(Collectors.toList());

Property newProp = new PropertyBuilder(p).withAnnotations(mergedAnnotations).build();
props.add(newProp);

found = true;
break;
}
}

if (!found) {
props.add(propertyFromMethod(m));
}
}

return props;
}

private static Property propertyFromMethod(Method m) {
return new PropertyBuilder()
.withName(propertyNameForMethod(m))
.withTypeRef(m.getReturnType())
.withAnnotations(m.getAnnotations())
.withModifiers(m.getModifiers())
.withAttributes(m.getAttributes())
.withComments(m.getComments())
.build();
}

private static String propertyNameForMethod(Method method) {
String name = method.getName();
if (name.equals("get") || name.equals("is")) {
return name;
}

if (name.startsWith("get")) {
return lowerFirstLetter(removePrefix(name, "get"));
} else if (name.startsWith("is")) {
return lowerFirstLetter(removePrefix(name, "is"));
}
return name;
}

private static String removePrefix(String text, String prefix) {
if (text.startsWith(prefix)) {
return text.substring(prefix.length());
}
return text;
}

private static String lowerFirstLetter(String text) {
if (text.isEmpty()) {
return text;
}

return Character.toLowerCase(text.charAt(0)) + text.substring(1);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,23 +15,40 @@
*/
package io.fabric8.crd.generator.utils;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import io.fabric8.kubernetes.api.model.Namespaced;
import io.fabric8.kubernetes.client.CustomResource;
import io.sundr.adapter.api.AdapterContext;
import io.sundr.adapter.api.Adapters;
import io.sundr.adapter.apt.AptAdapter;
import io.sundr.adapter.apt.AptContext;
import io.sundr.builder.TypedVisitor;
import io.sundr.model.*;
import io.sundr.model.ClassRef;
import io.sundr.model.ClassRefBuilder;
import io.sundr.model.Method;
import io.sundr.model.PrimitiveRef;
import io.sundr.model.Property;
import io.sundr.model.PropertyBuilder;
import io.sundr.model.TypeDef;
import io.sundr.model.TypeDefBuilder;
import io.sundr.model.TypeParamDef;
import io.sundr.model.TypeParamRef;
import io.sundr.model.TypeRef;
import io.sundr.model.VoidRef;
import io.sundr.model.WildcardRef;
import io.sundr.model.functions.GetDefinition;
import io.sundr.model.repo.DefinitionRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;


public class Types {
Expand Down Expand Up @@ -60,11 +77,12 @@ public static TypeDef unshallow(TypeDef definition) {
final List<ClassRef> classRefs = new ArrayList<>(Types.projectSuperClasses(definition));
// resolve properties
final List<Property> properties = Types.projectProperties(definition);
final List<Method> methods = Types.projectMethods(definition);
// re-create TypeDef with all the needed information
return new TypeDef(definition.getKind(), definition.getPackageName(),
definition.getName(), definition.getComments(), definition.getAnnotations(), classRefs,
definition.getImplementsList(), definition.getParameters(), properties,
definition.getConstructors(), definition.getMethods(), definition.getOuterTypeName(),
definition.getConstructors(), methods, definition.getOuterTypeName(),
definition.getInnerTypes(), definition.getModifiers(), definition.getAttributes());
}

Expand Down Expand Up @@ -159,23 +177,30 @@ private static Set<ClassRef> projectSuperClasses(TypeDef definition) {
private static List<Property> projectProperties(TypeDef typeDef) {
final String fqn = typeDef.getFullyQualifiedName();
return Stream.concat(
typeDef.getProperties().stream().filter(p -> {
// enums have self-referential static properties for each enum case so we cannot ignore them
if (typeDef.isEnum()) {
final TypeRef typeRef = p.getTypeRef();
if (typeRef instanceof ClassRef && fqn.equals(((ClassRef) typeRef).getFullyQualifiedName())) {
// we're dealing with an enum case so keep it
return true;
typeDef.getProperties()
.stream()
.filter(p -> {
if (p.isTransient()) {
return false;
}
}
// otherwise exclude all static properties
return !p.isStatic();
}),

// enums have self-referential static properties for each enum case so we cannot ignore them
if (typeDef.isEnum()) {
final TypeRef typeRef = p.getTypeRef();
if (typeRef instanceof ClassRef && fqn.equals(((ClassRef) typeRef).getFullyQualifiedName())) {
// we're dealing with an enum case so keep it
return true;
}
}
// otherwise exclude all static and transient properties
return !p.isStatic();
}),
typeDef.getExtendsList().stream()
.filter(e -> !e.getFullyQualifiedName().startsWith("java."))
.flatMap(e -> projectProperties(projectDefinition(e))
.stream()
.filter(p -> filterCustomResourceProperties(e).test(p)))
.filter(p -> filterCustomResourceProperties(e).test(p))
)
)

.collect(Collectors.toList());
Expand All @@ -188,6 +213,49 @@ private static Predicate<Property> filterCustomResourceProperties(ClassRef ref)
(p.getName().equals("spec") || p.getName().equals("status")));
}

/**
* All non-static methods (including inherited).
*
* @param typeDef The type.
* @return A list with all methods.
*/
private static List<Method> projectMethods(TypeDef typeDef) {
final String fqn = typeDef.getFullyQualifiedName();
return Stream.concat(
typeDef.getMethods()
.stream()
.filter(m -> {
if (m.isTransient()) {
return false;
}
// enums have self-referential static properties for each enum case so we cannot ignore them
if (typeDef.isEnum()) {
TypeRef typeRef = m.getReturnType();
if (typeRef instanceof ClassRef && fqn.equals(((ClassRef) typeRef).getFullyQualifiedName())) {
// we're dealing with an enum case so keep it
return true;
}
}
// otherwise exclude all static properties
return !m.isStatic();
}),
typeDef.getExtendsList()
.stream()
.filter(e -> !e.getFullyQualifiedName().startsWith("java."))
.flatMap(e -> projectMethods(projectDefinition(e))
.stream()
.filter(m -> filterCustomResourceMethods(e).test(m))
)
)
.collect(Collectors.toList());
}

private static Predicate<Method> filterCustomResourceMethods(ClassRef ref) {
return m -> !m.isStatic() &&
(!ref.getFullyQualifiedName().equals(CUSTOM_RESOURCE_NAME) ||
(m.getName().equals("getSpec") || m.getName().equals("getStatus")));
}


public static void output(TypeDef def) {
final StringBuilder builder = new StringBuilder(300);
Expand All @@ -201,7 +269,7 @@ public static void describeType(TypeDef def, String indent, Set<String> visited,
}

visited.add(def.getFullyQualifiedName());
for (Property property : def.getProperties()) {
for (Property property : Properties.getVisibleProperties(def)) {
TypeRef typeRef = property.getTypeRef();
if (typeRef instanceof ClassRef) {
final TypeDef typeDef = typeDefFrom((ClassRef) typeRef);
Expand Down Expand Up @@ -308,7 +376,7 @@ public static boolean isNamespaced(TypeDef definition, Set<TypeDef> visited) {
* @return the an optional property.
*/
public static Optional<Property> findStatusProperty(TypeDef typeDef) {
return typeDef.getProperties().stream()
return Properties.getVisibleProperties(typeDef).stream()
.filter(Types::isStatusProperty)
.findFirst();
}
Expand Down
Loading
Loading