Skip to content

Commit

Permalink
Add Dev-UI
Browse files Browse the repository at this point in the history
  • Loading branch information
Dudeplayz committed Nov 3, 2024
1 parent 7399f2a commit f4af8c8
Show file tree
Hide file tree
Showing 9 changed files with 557 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright 2024 Marco Collovati, Dario Götze
*
* Licensed 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 com.github.mcollovati.quarkus.hilla.deployment.devui;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import io.quarkus.arc.deployment.devui.Name;
import org.jboss.jandex.AnnotationInstance;
import org.jboss.jandex.DotName;

public record AccessAnnotationInfo(Name name, List<String> roles) {

public static DotName ROLES_ALLOWED_ANNOTATION = DotName.createSimple("jakarta.annotation.security.RolesAllowed");

public static AccessAnnotationInfo from(AnnotationInstance annotation) {
if (annotation.name().equals(ROLES_ALLOWED_ANNOTATION)) {
return new AccessAnnotationInfo(
Name.from(annotation.name()),
Arrays.asList(annotation.value("value").asStringArray()));
}
return new AccessAnnotationInfo(Name.from(annotation.name()), Collections.emptyList());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright 2024 Marco Collovati, Dario Götze
*
* Licensed 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 com.github.mcollovati.quarkus.hilla.deployment.devui;

import java.util.List;

import io.quarkus.builder.item.SimpleBuildItem;

public final class EndpointBuildItem extends SimpleBuildItem {

private final List<EndpointInfo> endpoints;

public EndpointBuildItem(List<EndpointInfo> endpoints) {
this.endpoints = endpoints;
}

public List<EndpointInfo> getEndpoints() {
return endpoints;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/*
* Copyright 2024 Marco Collovati, Dario Götze
*
* Licensed 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 com.github.mcollovati.quarkus.hilla.deployment.devui;

import jakarta.annotation.Nullable;
import java.lang.reflect.Modifier;
import java.util.Collections;
import java.util.List;
import java.util.Optional;

import io.quarkus.arc.deployment.devui.Name;
import org.jboss.jandex.AnnotationInstance;
import org.jboss.jandex.AnnotationTarget;
import org.jboss.jandex.ClassInfo;
import org.jboss.jandex.DotName;
import org.jboss.jandex.MethodInfo;

public record EndpointInfo(
AnnotationTarget.Kind kind,
Name declaringClass,
@Nullable MethodDescriptor methodDescriptor,
@Nullable String endpointAnnotation,
AccessAnnotationInfo accessAnnotation,
List<EndpointInfo> children) {

public static DotName ENDPOINT_ANNOTATION = DotName.createSimple("com.vaadin.hilla.Endpoint");
public static DotName BROWSER_CALLABLE_ANNOTATION = DotName.createSimple("com.vaadin.hilla.BrowserCallable");
public static DotName DENY_ALL_ANNOTATION = DotName.createSimple("jakarta.annotation.security.DenyAll");
public static DotName PERMIT_ALL_ANNOTATION = DotName.createSimple("jakarta.annotation.security.PermitAll");
public static DotName ROLES_ALLOWED_ANNOTATION = DotName.createSimple("jakarta.annotation.security.RolesAllowed");
public static DotName ANONYMOUS_ALLOWED_ANNOTATION =
DotName.createSimple("com.vaadin.flow.server.auth.AnonymousAllowed");
private static final AccessAnnotationInfo DENY_ALL_ACCESS_ANNOTATION_INFO =
new AccessAnnotationInfo(Name.from(DENY_ALL_ANNOTATION), Collections.emptyList());

public static EndpointInfo from(ClassInfo classInfo) {
final var accessAnnotation =
getEffectiveAccessAnnotation(classInfo.declaredAnnotations()).orElse(DENY_ALL_ACCESS_ANNOTATION_INFO);
return new EndpointInfo(
classInfo.kind(),
Name.from(classInfo.name()),
null,
classInfo.declaredAnnotations().stream()
.map(AnnotationInstance::name)
.filter(a -> a.equals(ENDPOINT_ANNOTATION) || a.equals(BROWSER_CALLABLE_ANNOTATION))
.findFirst()
.map(DotName::withoutPackagePrefix)
.orElse(null),
accessAnnotation,
classInfo.methods().stream()
.filter(m ->
Modifier.isPublic(m.flags()) && !m.isConstructor() && !Modifier.isStatic(m.flags()))
.map(m -> from(m, accessAnnotation))
.toList());
}

public static EndpointInfo from(MethodInfo methodInfo, AccessAnnotationInfo classAccessAnnotation) {
return new EndpointInfo(
methodInfo.kind(),
Name.from(methodInfo.declaringClass().name()),
MethodDescriptor.from(methodInfo),
null,
getEffectiveAccessAnnotation(methodInfo.declaredAnnotations()).orElse(classAccessAnnotation),
null);
}

/**
* Returns the effective access annotation. Based on the following rules:
* <a href="https://hilla.dev/docs/react/guides/security/configuring">...</a>
* @param annotations the annotations
* @return the effective access annotation, if any
*/
private static Optional<AccessAnnotationInfo> getEffectiveAccessAnnotation(List<AnnotationInstance> annotations) {
return annotations.stream()
.filter(EndpointInfo::isAccessAnnotation)
.reduce((a1, a2) -> {
if (a1.name().equals(DENY_ALL_ANNOTATION)) {
return a1;
} else if (a2.name().equals(DENY_ALL_ANNOTATION)) {
return a2;
} else if (a1.name().equals(ANONYMOUS_ALLOWED_ANNOTATION)) {
return a1;
} else if (a2.name().equals(ANONYMOUS_ALLOWED_ANNOTATION)) {
return a2;
} else if (a1.name().equals(ROLES_ALLOWED_ANNOTATION)) {
return a1;
} else if (a2.name().equals(ROLES_ALLOWED_ANNOTATION)) {
return a2;
} else {
return a1;
}
})
.map(AccessAnnotationInfo::from);
}

private static boolean isAccessAnnotation(AnnotationInstance annotation) {
return annotation.name().packagePrefix().equals("jakarta.annotation.security")
|| annotation.name().packagePrefix().equals("com.vaadin.flow.server.auth");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright 2024 Marco Collovati, Dario Götze
*
* Licensed 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 com.github.mcollovati.quarkus.hilla.deployment.devui;

import java.util.List;

import org.jboss.jandex.MethodInfo;

public record MethodDescriptor(TypeInfo returnType, String methodName, List<ParameterInfo> parameters) {

public static MethodDescriptor from(MethodInfo methodInfo) {
return new MethodDescriptor(
TypeInfo.from(methodInfo.returnType()),
methodInfo.name(),
methodInfo.parameters().stream().map(ParameterInfo::from).toList());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Copyright 2024 Marco Collovati, Dario Götze
*
* Licensed 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 com.github.mcollovati.quarkus.hilla.deployment.devui;

import org.jboss.jandex.MethodParameterInfo;

public record ParameterInfo(TypeInfo type, String name) {

public static ParameterInfo from(MethodParameterInfo parameterInfo) {
return new ParameterInfo(TypeInfo.from(parameterInfo.type()), parameterInfo.name());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* Copyright 2024 Marco Collovati, Dario Götze
*
* Licensed 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 com.github.mcollovati.quarkus.hilla.deployment.devui;

import java.util.stream.Stream;

import io.quarkus.deployment.IsDevelopment;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.builditem.CombinedIndexBuildItem;
import io.quarkus.devui.spi.page.CardPageBuildItem;
import io.quarkus.devui.spi.page.Page;
import org.jboss.jandex.AnnotationInstance;
import org.jboss.jandex.AnnotationTarget;
import org.jboss.jandex.DotName;

public class QuarkusHillaDevUIProcessor {

private static final DotName SIGNALS_HANDLER =
DotName.createSimple("com.vaadin.hilla.signals.handler.SignalsHandler");

@BuildStep(onlyIf = IsDevelopment.class)
public void collectEndpoints(
BuildProducer<EndpointBuildItem> producer, CombinedIndexBuildItem combinedIndexBuildItem) {
final var endpointAnnotated =
combinedIndexBuildItem.getComputingIndex().getAnnotations(EndpointInfo.ENDPOINT_ANNOTATION);
final var browserCallableAnnotated =
combinedIndexBuildItem.getComputingIndex().getAnnotations(EndpointInfo.BROWSER_CALLABLE_ANNOTATION);
final var endpoints = Stream.concat(endpointAnnotated.stream(), browserCallableAnnotated.stream())
.map(AnnotationInstance::target)
.filter(target -> target.kind().equals(AnnotationTarget.Kind.CLASS))
.map(AnnotationTarget::asClass)
.filter(c -> !SIGNALS_HANDLER.equals(c.name()))
.map(EndpointInfo::from)
.toList();
producer.produce(new EndpointBuildItem(endpoints));
}

@BuildStep(onlyIf = IsDevelopment.class)
public CardPageBuildItem pages(EndpointBuildItem endpointBuildItems) {
CardPageBuildItem cardPageBuildItem = new CardPageBuildItem();
cardPageBuildItem.addBuildTimeData("hillaEndpoints", endpointBuildItems.getEndpoints());
cardPageBuildItem.addPage(Page.webComponentPageBuilder()
.componentLink("qwc-quarkus-hilla-endpoints.js")
.title("Endpoints")
.icon("font-awesome-solid:table-list")
.staticLabel(String.valueOf(endpointBuildItems.getEndpoints().stream()
.mapToInt(a -> a.children().size())
.sum())));
return cardPageBuildItem;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Copyright 2024 Marco Collovati, Dario Götze
*
* Licensed 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 com.github.mcollovati.quarkus.hilla.deployment.devui;

import jakarta.annotation.Nullable;
import java.util.List;

import org.jboss.jandex.AnnotationInstance;
import org.jboss.jandex.Type;

public record TypeInfo(List<String> annotations, String type, @Nullable List<TypeInfo> generics) {

public static TypeInfo from(Type type) {
if (type.kind() == Type.Kind.PARAMETERIZED_TYPE) {
return new TypeInfo(
annotationsSimple(type.annotations()),
type.name().withoutPackagePrefix(),
type.asParameterizedType().arguments().stream()
.map(TypeInfo::from)
.toList());
}
return new TypeInfo(annotationsSimple(type.annotations()), type.name().withoutPackagePrefix(), null);
}

private static List<String> annotationsSimple(List<AnnotationInstance> annotations) {
return annotations.stream().map(a -> a.toString(true)).toList();
}
}
Loading

0 comments on commit f4af8c8

Please sign in to comment.