Skip to content
Merged
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
62 changes: 22 additions & 40 deletions sdk-api-gen/src/main/java/dev/restate/sdk/gen/ElementConverter.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
import javax.tools.Diagnostic;
import org.jspecify.annotations.Nullable;

public class ElementConverter {
class ElementConverter {

private static final PayloadType EMPTY_PAYLOAD =
new PayloadType(true, "", "Void", "dev.restate.sdk.common.Serde.VOID");
Expand All @@ -47,49 +47,29 @@ public ElementConverter(Messager messager, Elements elements, Types types) {
this.types = types;
}

public Service fromTypeElement(TypeElement element) {
Service fromTypeElement(MetaRestateAnnotation metaAnnotation, TypeElement element) {
validateType(element);

dev.restate.sdk.annotation.Service serviceAnnotation =
element.getAnnotation(dev.restate.sdk.annotation.Service.class);
dev.restate.sdk.annotation.VirtualObject virtualObjectAnnotation =
element.getAnnotation(dev.restate.sdk.annotation.VirtualObject.class);
dev.restate.sdk.annotation.Workflow workflowAnnotation =
element.getAnnotation(dev.restate.sdk.annotation.Workflow.class);
boolean isAnnotatedWithService = serviceAnnotation != null;
boolean isAnnotatedWithVirtualObject = virtualObjectAnnotation != null;
boolean isAnnotatedWithWorkflow = workflowAnnotation != null;

// Should be guaranteed by the caller
assert isAnnotatedWithWorkflow || isAnnotatedWithVirtualObject || isAnnotatedWithService;

// Check there's no more than one annotation
if (!Boolean.logicalXor(
isAnnotatedWithService,
Boolean.logicalXor(isAnnotatedWithWorkflow, isAnnotatedWithVirtualObject))) {
messager.printMessage(
Diagnostic.Kind.ERROR,
"The type can be annotated only with one annotation between @VirtualObject, @Workflow and @Service",
element);
}

ServiceType type =
isAnnotatedWithWorkflow
? ServiceType.WORKFLOW
: isAnnotatedWithService ? ServiceType.SERVICE : ServiceType.VIRTUAL_OBJECT;
// Find annotation mirror
AnnotationMirror metaAnnotationMirror =
element.getAnnotationMirrors().stream()
.filter(
a ->
a.getAnnotationType()
.asElement()
.equals(metaAnnotation.getAnnotationTypeElement()))
.findFirst()
.orElseThrow(
() ->
new IllegalStateException(
"Cannot find the annotation mirror for meta annotation "
+ metaAnnotation.getAnnotationTypeElement().getQualifiedName()));

// Infer names

CharSequence targetPkg = elements.getPackageOf(element).getQualifiedName();
CharSequence targetFqcn = element.getQualifiedName();

String serviceName =
isAnnotatedWithService
? serviceAnnotation.name()
: isAnnotatedWithVirtualObject
? virtualObjectAnnotation.name()
: workflowAnnotation.name();
if (serviceName.isEmpty()) {
String serviceName = metaAnnotation.resolveName(metaAnnotationMirror);
if (serviceName == null || serviceName.isEmpty()) {
// Use simple class name, flattening subclasses names
serviceName =
targetFqcn.toString().substring(targetPkg.length()).replaceAll(Pattern.quote("."), "");
Expand All @@ -105,7 +85,9 @@ public Service fromTypeElement(TypeElement element) {
|| e.getAnnotation(Workflow.class) != null
|| e.getAnnotation(Exclusive.class) != null
|| e.getAnnotation(Shared.class) != null)
.map(e -> fromExecutableElement(type, ((ExecutableElement) e)))
.map(
e ->
fromExecutableElement(metaAnnotation.getServiceType(), ((ExecutableElement) e)))
.collect(Collectors.toList());

if (handlers.isEmpty()) {
Expand All @@ -118,7 +100,7 @@ public Service fromTypeElement(TypeElement element) {
.withTargetPkg(targetPkg)
.withTargetFqcn(targetFqcn)
.withServiceName(serviceName)
.withServiceType(type)
.withServiceType(metaAnnotation.getServiceType())
.withHandlers(handlers)
.validateAndBuild();
} catch (Exception e) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH
//
// This file is part of the Restate Java SDK,
// which is released under the MIT license.
//
// You can find a copy of the license in file LICENSE in the root
// directory of this repository or package, or at
// https://github.com/restatedev/sdk-java/blob/main/LICENSE
package dev.restate.sdk.gen;

import dev.restate.sdk.common.ServiceType;
import java.util.Map;
import javax.lang.model.element.*;
import org.jspecify.annotations.Nullable;

class MetaRestateAnnotation {

private final TypeElement annotation;
private final ServiceType serviceType;

private MetaRestateAnnotation(TypeElement annotation, ServiceType serviceType) {
this.annotation = annotation;
this.serviceType = serviceType;
}

TypeElement getAnnotationTypeElement() {
return annotation;
}

ServiceType getServiceType() {
return serviceType;
}

@Nullable String resolveName(AnnotationMirror mirror) {
for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry :
mirror.getElementValues().entrySet()) {
if (entry.getKey().getSimpleName().contentEquals("name")) {
// Got a match, this is the name of the service
return (String) entry.getValue().getValue();
}
}
// No name parameter found!
return null;
}

static @Nullable MetaRestateAnnotation metaRestateAnnotationOrNull(TypeElement elem) {
if (elem.getQualifiedName().toString().equals("dev.restate.sdk.annotation.Service")
|| elem.getAnnotation(dev.restate.sdk.annotation.Service.class) != null) {
return new MetaRestateAnnotation(elem, ServiceType.SERVICE);
}
if (elem.getQualifiedName().toString().equals("dev.restate.sdk.annotation.VirtualObject")
|| elem.getAnnotation(dev.restate.sdk.annotation.VirtualObject.class) != null) {
return new MetaRestateAnnotation(elem, ServiceType.VIRTUAL_OBJECT);
}
if (elem.getQualifiedName().toString().equals("dev.restate.sdk.annotation.Workflow")
|| elem.getAnnotation(dev.restate.sdk.annotation.Workflow.class) != null) {
return new MetaRestateAnnotation(elem, ServiceType.WORKFLOW);
}
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,7 @@
import javax.tools.FileObject;
import javax.tools.StandardLocation;

@SupportedAnnotationTypes({
"dev.restate.sdk.annotation.Service",
"dev.restate.sdk.annotation.Workflow",
"dev.restate.sdk.annotation.VirtualObject"
})
@SupportedAnnotationTypes("*")
@SupportedSourceVersion(SourceVersion.RELEASE_11)
public class ServiceProcessor extends AbstractProcessor {

Expand Down Expand Up @@ -97,9 +93,19 @@ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment
// Parsing phase
List<Map.Entry<Element, Service>> parsedServices =
annotations.stream()
.flatMap(annotation -> roundEnv.getElementsAnnotatedWith(annotation).stream())
.filter(e -> e.getKind().isClass() || e.getKind().isInterface())
.map(e -> Map.entry((Element) e, converter.fromTypeElement((TypeElement) e)))
.map(MetaRestateAnnotation::metaRestateAnnotationOrNull)
.filter(Objects::nonNull)
.flatMap(
metaAnnotation ->
roundEnv
.getElementsAnnotatedWith(metaAnnotation.getAnnotationTypeElement())
.stream()
.filter(e -> e.getKind().isClass() || e.getKind().isInterface())
.map(
e ->
Map.entry(
(Element) e,
converter.fromTypeElement(metaAnnotation, (TypeElement) e))))
.collect(Collectors.toList());

Filer filer = processingEnv.getFiler();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,16 @@
// https://github.com/restatedev/sdk-java/blob/main/LICENSE
package dev.restate.sdk.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.annotation.*;

/**
* Annotation to define a class/interface as Restate Service. This triggers the code generation of
* the related Client class and the {@link
* dev.restate.sdk.common.syscalls.ServiceDefinitionFactory}.
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Service {
/**
* Name of the Service for Restate. If not provided, it will be the simple class name of the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,16 @@
// https://github.com/restatedev/sdk-java/blob/main/LICENSE
package dev.restate.sdk.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.annotation.*;

/**
* Annotation to define a class/interface as Restate VirtualObject. This triggers the code
* generation of the related Client class and the {@link
* dev.restate.sdk.common.syscalls.ServiceDefinitionFactory}.
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface VirtualObject {
/**
* Name of the VirtualObject for Restate. If not provided, it will be the simple class name of the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,7 @@
// https://github.com/restatedev/sdk-java/blob/main/LICENSE
package dev.restate.sdk.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.annotation.*;

/**
* Annotation to define a class/interface as Restate Workflow. This triggers the code generation of
Expand All @@ -20,7 +17,8 @@
* workflow, you must annotate one of its methods too as {@link Workflow}.
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.SOURCE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Workflow {
/**
* Name of the Workflow for Restate. If not provided, it will be the simple class name of the
Expand Down
1 change: 1 addition & 0 deletions sdk-spring-boot-starter/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ dependencies {

testAnnotationProcessor(project(":sdk-api-gen"))
testImplementation(project(":sdk-serde-jackson"))
testImplementation(project(":sdk-testing"))

testImplementation("org.springframework.boot:spring-boot-starter-test:$springBootVersion")
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH
//
// This file is part of the Restate Java SDK,
// which is released under the MIT license.
//
// You can find a copy of the license in file LICENSE in the root
// directory of this repository or package, or at
// https://github.com/restatedev/sdk-java/blob/main/LICENSE
package dev.restate.sdk.springboot;

import dev.restate.sdk.annotation.Service;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Proxy annotation for {@link Service}, to avoid naming clashes with Spring's built in annotations
*
* @see Service
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Service
@RestateComponent
public @interface RestateService {
/**
* Name of the Service for Restate. If not provided, it will be the simple class name of the
* annotated element.
*/
String name() default "";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH
//
// This file is part of the Restate Java SDK,
// which is released under the MIT license.
//
// You can find a copy of the license in file LICENSE in the root
// directory of this repository or package, or at
// https://github.com/restatedev/sdk-java/blob/main/LICENSE
package dev.restate.sdk.springboot;

import dev.restate.sdk.annotation.Service;
import dev.restate.sdk.annotation.VirtualObject;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Proxy annotation for {@link VirtualObject}.
*
* @see Service
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@VirtualObject
@RestateComponent
public @interface RestateVirtualObject {
/**
* Name of the VirtualObject for Restate. If not provided, it will be the simple class name of the
* annotated element.
*/
String name() default "";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH
//
// This file is part of the Restate Java SDK,
// which is released under the MIT license.
//
// You can find a copy of the license in file LICENSE in the root
// directory of this repository or package, or at
// https://github.com/restatedev/sdk-java/blob/main/LICENSE
package dev.restate.sdk.springboot;

import dev.restate.sdk.annotation.Service;
import dev.restate.sdk.annotation.Workflow;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Proxy annotation for {@link Workflow}.
*
* @see Service
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Workflow
@RestateComponent
public @interface RestateWorkflow {
/**
* Name of the Workflow for Restate. If not provided, it will be the simple class name of the
* annotated element.
*/
String name() default "";
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,9 @@

import dev.restate.sdk.Context;
import dev.restate.sdk.annotation.Handler;
import dev.restate.sdk.annotation.Service;
import org.springframework.beans.factory.annotation.Value;

@RestateComponent
@Service
@RestateService(name = "greeter")
public class Greeter {

@Value("${greetingPrefix}")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,6 @@ public void httpEndpointShouldBeRunning() throws IOException, InterruptedExcepti

assertThat(endpointManifest.getServices())
.map(dev.restate.sdk.core.manifest.Service::getName)
.containsOnly("Greeter");
.containsOnly("greeter");
}
}
Loading