Skip to content

Commit

Permalink
fix: restore endpoint registration after hotswap (#3087)
Browse files Browse the repository at this point in the history
* Fix endpoint registration after hotswap

* Remove obsolete endpoints

* Change when internal endpoint name is made lowercase

---------

Co-authored-by: Soroosh Taefi <[email protected]>
  • Loading branch information
cromoteca and taefi authored Jan 9, 2025
1 parent e181cc6 commit d6e5b26
Show file tree
Hide file tree
Showing 7 changed files with 23 additions and 82 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -96,14 +96,7 @@ public void update() {
GeneratorProcessor generator = new GeneratorProcessor(
engineConfiguration);
generator.process();

try {
var openApiPath = engineConfiguration.getOpenAPIFile();
this.endpointController
.registerEndpoints(openApiPath.toUri().toURL());
} catch (IOException e) {
throw new RuntimeException(e);
}
this.endpointController.registerEndpoints();
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.net.URL;
import java.util.TreeMap;
import java.util.stream.Collectors;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.node.ObjectNode;
Expand Down Expand Up @@ -114,7 +114,7 @@ public EndpointController(ApplicationContext context,
* Initializes the controller by registering all endpoints found in the
* OpenApi definition or, as a fallback, in the Spring context.
*/
public void registerEndpoints(URL openApiResource) {
public void registerEndpoints() {
// Spring returns bean names in lower camel case, while Hilla names
// endpoints in upper camel case, so a case-insensitive map is used to
// ease searching
Expand All @@ -124,27 +124,16 @@ public void registerEndpoints(URL openApiResource) {
endpointBeans
.putAll(context.getBeansWithAnnotation(BrowserCallable.class));

if (endpointRegistry.isEmpty() && !endpointBeans.isEmpty()) {
LOGGER.debug("No endpoints found in openapi.json:"
+ " registering all endpoints found using the Spring context");
var currentEndpointNames = endpointBeans.values().stream()
.map(endpointRegistry::registerEndpoint)
.collect(Collectors.toSet());
// remove obsolete endpoints
endpointRegistry.getEndpoints().keySet()
.retainAll(currentEndpointNames);

endpointBeans.forEach((name, endpointBean) -> endpointRegistry
.registerEndpoint(endpointBean));
}

if (!endpointRegistry.isEmpty()) {
HillaStats.reportHasEndpoint();
}

// make sure that signalsHandler endpoint is always registered, but not
// counted as a regular endpoint in stats:
if (endpointRegistry.get(SIGNALS_HANDLER_BEAN_NAME) == null) {
var signalHandlerBean = endpointBeans
.get(SIGNALS_HANDLER_BEAN_NAME);
if (signalHandlerBean != null) {
endpointRegistry.registerEndpoint(signalHandlerBean);
}
}
endpointBeans.keySet().stream()
.filter(name -> !name.equals(SIGNALS_HANDLER_BEAN_NAME))
.findAny().ifPresent(name -> HillaStats.reportHasEndpoint());

// Temporary Hack
VaadinService vaadinService = VaadinService.getCurrent();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,10 +99,10 @@ private static String getEndpointNameForClass(Class<?> beanType) {
// BrowserCallable has no value so this works
return Optional.ofNullable(beanType.getAnnotation(Endpoint.class))
.map(Endpoint::value).filter(value -> !value.isEmpty())
.orElse(beanType.getSimpleName());
.orElse(beanType.getSimpleName()).toLowerCase(Locale.ENGLISH);
}

void registerEndpoint(Object endpointBean) {
String registerEndpoint(Object endpointBean) {
// Check the bean type instead of the implementation type in
// case of e.g. proxies
Class<?> beanType = ClassUtils.getUserClass(endpointBean.getClass());
Expand All @@ -129,10 +129,11 @@ void registerEndpoint(Object endpointBean) {
Method[] endpointPublicMethods = beanType.getMethods();
AccessibleObject.setAccessible(endpointPublicMethods, true);

vaadinEndpoints.put(endpointName.toLowerCase(Locale.ENGLISH),
vaadinEndpoints.put(endpointName,
new VaadinEndpointData(endpointBean, endpointPublicMethods));
LOGGER.debug("Registered endpoint '{}' with class '{}'", endpointName,
beanType);
return endpointName;
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,26 +1,13 @@
package com.vaadin.hilla.startup;

import com.vaadin.flow.function.DeploymentConfiguration;
import com.vaadin.flow.server.ServiceInitEvent;
import com.vaadin.flow.server.VaadinServiceInitListener;
import com.vaadin.hilla.EndpointController;
import com.vaadin.hilla.engine.EngineConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.net.MalformedURLException;
import java.net.URL;

@Component
public class EndpointRegistryInitializer implements VaadinServiceInitListener {

private static final Logger LOGGER = LoggerFactory
.getLogger(EndpointRegistryInitializer.class);

private static final String OPEN_API_PROD_RESOURCE_PATH = '/'
+ EngineConfiguration.OPEN_API_PATH;

private final EndpointController endpointController;

public EndpointRegistryInitializer(EndpointController endpointController) {
Expand All @@ -29,28 +16,6 @@ public EndpointRegistryInitializer(EndpointController endpointController) {

@Override
public void serviceInit(ServiceInitEvent event) {
var deploymentConfig = event.getSource().getDeploymentConfiguration();
var openApiResource = getOpenApiAsResource(deploymentConfig);
endpointController.registerEndpoints(openApiResource);
}

private URL getOpenApiAsResource(DeploymentConfiguration deploymentConfig) {
if (deploymentConfig.isProductionMode()) {
return getClass().getResource(OPEN_API_PROD_RESOURCE_PATH);
}
var openApiPathInDevMode = deploymentConfig.getProjectFolder().toPath()
.resolve(deploymentConfig.getBuildFolder())
.resolve(EngineConfiguration.OPEN_API_PATH);
try {
return openApiPathInDevMode.toFile().exists()
? openApiPathInDevMode.toUri().toURL()
: null;
} catch (MalformedURLException e) {
LOGGER.debug(String.format(
"%s Mode: Path %s to resource %s seems to be malformed/could not be parsed. ",
deploymentConfig.getMode(), openApiPathInDevMode.toUri(),
EngineConfiguration.OPEN_API_PATH), e);
return null;
}
endpointController.registerEndpoints();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -827,7 +827,7 @@ endpointObjectMapper, mock(ExplicitNullableTypeChecker.class),
mock(ServletContext.class), registry);

new EndpointController(contextMock, registry, invoker, null)
.registerEndpoints(getDefaultOpenApiResourcePathInDevMode());
.registerEndpoints();

verify(contextMock, never()).getBean(ObjectMapper.class);
verify(contextMock, times(1))
Expand Down Expand Up @@ -1172,8 +1172,7 @@ private EndpointRegistry registerEndpoints(String openApiFilename) {
"libraryEndpoint", new LibraryEndpoint())).when(context)
.getBeansWithAnnotation(Endpoint.class);
var controller = createVaadinControllerWithApplicationContext(context);
controller.registerEndpoints(getClass()
.getResource("/com/vaadin/hilla/packages/" + openApiFilename));
controller.registerEndpoints();
return controller.endpointRegistry;
}

Expand Down Expand Up @@ -1314,8 +1313,7 @@ private <T> EndpointController createVaadinController(T endpoint,
EndpointController connectController = Mockito
.spy(new EndpointController(mockApplicationContext, registry,
invoker, csrfChecker));
connectController
.registerEndpoints(getDefaultOpenApiResourcePathInDevMode());
connectController.registerEndpoints();
return connectController;
}

Expand Down Expand Up @@ -1343,8 +1341,7 @@ private EndpointController createVaadinControllerWithApplicationContext(
EndpointController hillaController = controllerMockBuilder
.withObjectMapperFactory(new JacksonObjectMapperFactory.Json())
.withApplicationContext(applicationContext).build();
hillaController
.registerEndpoints(getDefaultOpenApiResourcePathInDevMode());
hillaController.registerEndpoints();
return hillaController;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ public void setUp() throws IOException {
EndpointControllerMockBuilder controllerMockBuilder = new EndpointControllerMockBuilder();
EndpointController controller = controllerMockBuilder
.withApplicationContext(applicationContext).build();
controller.registerEndpoints(getDefaultOpenApiResourcePathInDevMode());
controller.registerEndpoints();
mockMvcForEndpoint = MockMvcBuilders.standaloneSetup(controller)
.build();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@

import com.vaadin.hilla.EndpointController;
import com.vaadin.hilla.EndpointControllerMockBuilder;
import com.vaadin.hilla.engine.EngineConfiguration;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
Expand Down Expand Up @@ -69,10 +68,7 @@ public void setUp() throws IOException {
EndpointControllerMockBuilder controllerMockBuilder = new EndpointControllerMockBuilder();
EndpointController controller = controllerMockBuilder
.withApplicationContext(applicationContext).build();
var openApiResource = projectFolder.getRoot().toPath()
.resolve(appConfig.getBuildFolder())
.resolve(EngineConfiguration.OPEN_API_PATH);
controller.registerEndpoints(openApiResource.toUri().toURL());
controller.registerEndpoints();
mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
}

Expand Down

0 comments on commit d6e5b26

Please sign in to comment.