-
Notifications
You must be signed in to change notification settings - Fork 220
Native Image Friendly #231
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
Changes from all commits
1e2efe0
0ffe8ef
6f6260e
decfd9c
ede8325
8aa10d1
9380215
62fba5c
41a10a2
14167cf
4103622
795c656
2697810
7075ddf
ef3dd1a
d969187
bf2cb03
7eaad18
e02804f
be8468d
b207ba9
49272c7
3121fce
bb89c00
0f110e1
9482857
e07d463
5446214
84a2b9c
58334a6
93f150a
99a541d
e5909fa
5a0af91
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
package io.javaoperatorsdk.operator; | ||
|
||
import io.fabric8.kubernetes.client.CustomResource; | ||
import io.javaoperatorsdk.operator.api.ResourceController; | ||
import org.apache.commons.lang3.ClassUtils; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
import java.io.BufferedReader; | ||
import java.io.IOException; | ||
import java.io.InputStreamReader; | ||
import java.net.URL; | ||
import java.util.*; | ||
import java.util.stream.Collectors; | ||
|
||
|
||
class ControllerToCustomResourceMappingsProvider { | ||
private static final Logger log = LoggerFactory.getLogger(ControllerUtils.class); | ||
|
||
static Map<Class<? extends ResourceController>, Class<? extends CustomResource>> provide(final String resourcePath) { | ||
Map<Class<? extends ResourceController>, Class<? extends CustomResource>> controllerToCustomResourceMappings = new HashMap(); | ||
try { | ||
final Enumeration<URL> customResourcesMetadataList = ControllerUtils.class.getClassLoader().getResources(resourcePath); | ||
for (Iterator<URL> it = customResourcesMetadataList.asIterator(); it.hasNext(); ) { | ||
URL url = it.next(); | ||
|
||
List<String> classNamePairs = retrieveClassNamePairs(url); | ||
classNamePairs.forEach(clazzPair -> { | ||
try { | ||
final String[] classNames = clazzPair.split(","); | ||
if (classNames.length != 2) { | ||
throw new IllegalStateException(String.format("%s is not valid CustomResource metadata defined in %s", clazzPair, url.toString())); | ||
} | ||
|
||
controllerToCustomResourceMappings.put( | ||
(Class<? extends ResourceController>) ClassUtils.getClass(classNames[0]), | ||
(Class<? extends CustomResource>) ClassUtils.getClass(classNames[1]) | ||
); | ||
} catch (ClassNotFoundException e) { | ||
throw new RuntimeException(e); | ||
} | ||
}); | ||
} | ||
log.debug("Loaded Controller to CustomResource mappings {}", controllerToCustomResourceMappings); | ||
return controllerToCustomResourceMappings; | ||
} catch (IOException e) { | ||
throw new RuntimeException(e); | ||
} | ||
} | ||
|
||
private static List<String> retrieveClassNamePairs(URL url) throws IOException { | ||
return new BufferedReader( | ||
new InputStreamReader(url.openStream()) | ||
).lines().collect(Collectors.toList()); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,43 +1,49 @@ | ||
package io.javaoperatorsdk.operator; | ||
|
||
import io.javaoperatorsdk.operator.api.Controller; | ||
import io.javaoperatorsdk.operator.api.ResourceController; | ||
import io.fabric8.kubernetes.api.builder.Function; | ||
import io.fabric8.kubernetes.client.CustomResource; | ||
import io.fabric8.kubernetes.client.CustomResourceDoneable; | ||
import javassist.*; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
import io.javaoperatorsdk.operator.api.Controller; | ||
import io.javaoperatorsdk.operator.api.ResourceController; | ||
|
||
import java.util.HashMap; | ||
import java.util.Map; | ||
|
||
|
||
public class ControllerUtils { | ||
|
||
private final static double JAVA_VERSION = Double.parseDouble(System.getProperty("java.specification.version")); | ||
private static final String FINALIZER_NAME_SUFFIX = "/finalizer"; | ||
public static final String CONTROLLERS_RESOURCE_PATH = "javaoperatorsdk/controllers"; | ||
private static Map<Class<? extends ResourceController>, Class<? extends CustomResource>> controllerToCustomResourceMappings; | ||
|
||
// this is just to support testing, this way we don't try to create class multiple times in memory with same name. | ||
// note that other solution is to add a random string to doneable class name | ||
private static Map<Class<? extends CustomResource>, Class<? extends CustomResourceDoneable<? extends CustomResource>>> | ||
doneableClassCache = new HashMap<>(); | ||
static { | ||
controllerToCustomResourceMappings = | ||
ControllerToCustomResourceMappingsProvider | ||
.provide(CONTROLLERS_RESOURCE_PATH); | ||
} | ||
|
||
static String getFinalizer(ResourceController controller) { | ||
final String annotationFinalizerName = getAnnotation(controller).finalizerName(); | ||
if (!Controller.NULL.equals(annotationFinalizerName)) { | ||
return annotationFinalizerName; | ||
} | ||
final String crdName = getAnnotation(controller).crdName() + FINALIZER_NAME_SUFFIX; | ||
return crdName; | ||
return getAnnotation(controller).crdName() + FINALIZER_NAME_SUFFIX; | ||
} | ||
|
||
static boolean getGenerationEventProcessing(ResourceController controller) { | ||
static boolean getGenerationEventProcessing(ResourceController<?> controller) { | ||
return getAnnotation(controller).generationAwareEventProcessing(); | ||
} | ||
|
||
static <R extends CustomResource> Class<R> getCustomResourceClass(ResourceController<R> controller) { | ||
return (Class<R>) getAnnotation(controller).customResourceClass(); | ||
final Class<? extends CustomResource> customResourceClass = controllerToCustomResourceMappings | ||
.get(controller.getClass()); | ||
if (customResourceClass == null) { | ||
throw new IllegalArgumentException( | ||
String.format( | ||
"No custom resource has been found for controller %s", | ||
controller.getClass().getCanonicalName() | ||
) | ||
); | ||
} | ||
return (Class<R>) customResourceClass; | ||
} | ||
|
||
static String getCrdName(ResourceController controller) { | ||
|
@@ -48,38 +54,15 @@ static String getCrdName(ResourceController controller) { | |
public static <T extends CustomResource> Class<? extends CustomResourceDoneable<T>> | ||
getCustomResourceDoneableClass(ResourceController<T> controller) { | ||
try { | ||
Class<? extends CustomResource> customResourceClass = getAnnotation(controller).customResourceClass(); | ||
String className = customResourceClass.getPackage().getName() + "." + customResourceClass.getSimpleName() + "CustomResourceDoneable"; | ||
|
||
if (doneableClassCache.containsKey(customResourceClass)) { | ||
return (Class<? extends CustomResourceDoneable<T>>) doneableClassCache.get(customResourceClass); | ||
} | ||
|
||
ClassPool pool = ClassPool.getDefault(); | ||
pool.appendClassPath(new LoaderClassPath(Thread.currentThread().getContextClassLoader())); | ||
|
||
CtClass superClass = pool.get(CustomResourceDoneable.class.getName()); | ||
CtClass function = pool.get(Function.class.getName()); | ||
CtClass customResource = pool.get(customResourceClass.getName()); | ||
CtClass[] argTypes = {customResource, function}; | ||
CtClass customDoneable = pool.makeClass(className, superClass); | ||
CtConstructor ctConstructor = CtNewConstructor.make(argTypes, null, "super($1, $2);", customDoneable); | ||
customDoneable.addConstructor(ctConstructor); | ||
|
||
Class<? extends CustomResourceDoneable<T>> doneableClass; | ||
if (JAVA_VERSION >= 9) { | ||
doneableClass = (Class<? extends CustomResourceDoneable<T>>) customDoneable.toClass(customResourceClass); | ||
} else { | ||
doneableClass = (Class<? extends CustomResourceDoneable<T>>) customDoneable.toClass(); | ||
} | ||
doneableClassCache.put(customResourceClass, doneableClass); | ||
return doneableClass; | ||
} catch (CannotCompileException | NotFoundException e) { | ||
throw new IllegalStateException(e); | ||
final Class<T> customResourceClass = getCustomResourceClass(controller); | ||
return (Class<? extends CustomResourceDoneable<T>>) Class.forName(customResourceClass.getCanonicalName() + "Doneable"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Will this work if the CR class is an inner class? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes it does work, haven't tested that with native-images yet though. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure, I'll add this test There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. After while, I believe that we should test it here, not in example. We would like to introduce native build as a main feature of the SDK. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe we could add native build as a test step in next PR. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
True, would be great to have it as one of the examples in this repository, will be easier to find for the users and also can be used as regression tests for future changes in the framework. |
||
} catch (ClassNotFoundException e) { | ||
e.printStackTrace(); | ||
return null; | ||
} | ||
} | ||
|
||
private static Controller getAnnotation(ResourceController controller) { | ||
private static Controller getAnnotation(ResourceController<?> controller) { | ||
return controller.getClass().getAnnotation(Controller.class); | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
package io.javaoperatorsdk.operator.processing; | ||
|
||
import com.google.auto.service.AutoService; | ||
import com.squareup.javapoet.*; | ||
import io.fabric8.kubernetes.api.builder.Function; | ||
import io.fabric8.kubernetes.client.CustomResourceDoneable; | ||
import io.javaoperatorsdk.operator.api.ResourceController; | ||
import javax.annotation.processing.*; | ||
import javax.lang.model.SourceVersion; | ||
import javax.lang.model.element.*; | ||
import javax.lang.model.type.DeclaredType; | ||
import javax.lang.model.type.TypeKind; | ||
import javax.lang.model.type.TypeMirror; | ||
import javax.tools.Diagnostic; | ||
import javax.tools.FileObject; | ||
import javax.tools.JavaFileObject; | ||
import javax.tools.StandardLocation; | ||
import java.io.IOException; | ||
import java.io.PrintWriter; | ||
import java.util.ArrayList; | ||
import java.util.HashSet; | ||
import java.util.List; | ||
import java.util.Set; | ||
import java.util.stream.Collectors; | ||
|
||
import static io.javaoperatorsdk.operator.ControllerUtils.CONTROLLERS_RESOURCE_PATH; | ||
|
||
@SupportedAnnotationTypes( | ||
"io.javaoperatorsdk.operator.api.Controller") | ||
@SupportedSourceVersion(SourceVersion.RELEASE_8) | ||
@AutoService(Processor.class) | ||
public class ControllerAnnotationProcessor extends AbstractProcessor { | ||
private FileObject resource; | ||
PrintWriter printWriter = null; | ||
private Set<String> generatedDoneableClassFiles = new HashSet<>(); | ||
|
||
@Override | ||
public synchronized void init(ProcessingEnvironment processingEnv) { | ||
super.init(processingEnv); | ||
try { | ||
resource = processingEnv.getFiler().createResource(StandardLocation.CLASS_OUTPUT, "", CONTROLLERS_RESOURCE_PATH); | ||
} catch (IOException e) { | ||
throw new RuntimeException(e); | ||
} | ||
try { | ||
printWriter = new PrintWriter(resource.openOutputStream()); | ||
} catch (IOException e) { | ||
throw new RuntimeException(e); | ||
} | ||
} | ||
|
||
@Override | ||
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { | ||
try { | ||
for (TypeElement annotation : annotations) { | ||
Set<? extends Element> annotatedElements | ||
= roundEnv.getElementsAnnotatedWith(annotation); | ||
annotatedElements.stream().filter(element -> element.getKind().equals(ElementKind.CLASS)) | ||
.map(e -> (TypeElement) e) | ||
.forEach(e -> this.generateDoneableClass(e, printWriter)); | ||
} | ||
} finally { | ||
printWriter.close(); | ||
} | ||
return true; | ||
} | ||
|
||
private void generateDoneableClass(TypeElement controllerClassSymbol, PrintWriter printWriter) { | ||
try { | ||
final TypeMirror resourceType = findResourceType(controllerClassSymbol); | ||
|
||
TypeElement customerResourceTypeElement = processingEnv | ||
.getElementUtils() | ||
.getTypeElement(resourceType.toString()); | ||
|
||
final String doneableClassName = customerResourceTypeElement.getSimpleName() + "Doneable"; | ||
final String destinationClassFileName = customerResourceTypeElement.getQualifiedName() + "Doneable"; | ||
final TypeName customResourceType = TypeName.get(resourceType); | ||
|
||
if (!generatedDoneableClassFiles.add(destinationClassFileName)) { | ||
processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, | ||
String.format( | ||
"%s already exist! adding the mapping to the %s", | ||
destinationClassFileName, | ||
CONTROLLERS_RESOURCE_PATH) | ||
); | ||
printWriter.println(controllerClassSymbol.getQualifiedName() + "," + customResourceType.toString()); | ||
return; | ||
} | ||
JavaFileObject builderFile = processingEnv.getFiler() | ||
.createSourceFile(destinationClassFileName); | ||
|
||
try (PrintWriter out = new PrintWriter(builderFile.openWriter())) { | ||
printWriter.println(controllerClassSymbol.getQualifiedName() + "," + customResourceType.toString()); | ||
final MethodSpec constructor = MethodSpec.constructorBuilder() | ||
.addModifiers(Modifier.PUBLIC) | ||
.addParameter(customResourceType, "resource") | ||
.addParameter(Function.class, "function") | ||
.addStatement("super(resource,function)") | ||
.build(); | ||
|
||
|
||
final TypeSpec typeSpec = TypeSpec.classBuilder(doneableClassName) | ||
.superclass(ParameterizedTypeName.get(ClassName.get(CustomResourceDoneable.class), customResourceType)) | ||
.addModifiers(Modifier.PUBLIC) | ||
.addMethod(constructor) | ||
.build(); | ||
|
||
final PackageElement packageElement = processingEnv.getElementUtils().getPackageOf(customerResourceTypeElement); | ||
JavaFile file = JavaFile.builder(packageElement.getQualifiedName().toString(), typeSpec) | ||
.build(); | ||
file.writeTo(out); | ||
} | ||
} catch (Exception ioException) { | ||
ioException.printStackTrace(); | ||
} | ||
} | ||
|
||
private TypeMirror findResourceType(TypeElement controllerClassSymbol) throws Exception { | ||
try { | ||
final DeclaredType controllerType = collectAllInterfaces(controllerClassSymbol) | ||
.stream() | ||
.filter(i -> i.toString() | ||
.startsWith(ResourceController.class.getCanonicalName()) | ||
) | ||
.findFirst() | ||
.orElseThrow(() -> new Exception("ResourceController is not implemented by " + controllerClassSymbol.toString())); | ||
|
||
return controllerType.getTypeArguments().get(0); | ||
} catch (Exception e) { | ||
e.printStackTrace(); | ||
return null; | ||
} | ||
} | ||
|
||
private List<DeclaredType> collectAllInterfaces(TypeElement element) { | ||
try { | ||
List<DeclaredType> interfaces = new ArrayList<>(element.getInterfaces()).stream().map(t -> (DeclaredType) t).collect(Collectors.toList()); | ||
TypeElement superclass = ((TypeElement) ((DeclaredType) element.getSuperclass()).asElement()); | ||
while (superclass.getSuperclass().getKind() != TypeKind.NONE) { | ||
interfaces.addAll(superclass.getInterfaces().stream().map(t -> (DeclaredType) t).collect(Collectors.toList())); | ||
superclass = ((TypeElement) ((DeclaredType) superclass.getSuperclass()).asElement()); | ||
} | ||
return interfaces; | ||
} catch (Exception e) { | ||
e.printStackTrace(); | ||
return null; | ||
} | ||
} | ||
} |
Uh oh!
There was an error while loading. Please reload this page.