Skip to content

Commit

Permalink
Merge pull request #235 from Artur-/master
Browse files Browse the repository at this point in the history
Plugin for Vaadin
  • Loading branch information
skybber authored Apr 27, 2018
2 parents 491ea5d + 7e2bb70 commit a3e4e45
Show file tree
Hide file tree
Showing 6 changed files with 226 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ uses agent services to:
* [RestEasy](plugin/hotswap-agent-resteasy-registry-plugin/README.md) (2.x, 3.x) - Cleanups and registers class redefinitions.
* [Seam](plugin/hotswap-agent-seam-plugin/README.md) (2.2, 2.3) - flush JBoss reference cache. Support for properties file change (messages[])
* [Spring](plugin/hotswap-agent-spring-plugin/README.md) (3x, 4.x) - Reload Spring configuration after class definition/change.
* [Vaadin](plugin/hotswap-agent-spring-plugin/README.md) (10.x) - Update routes, template models and in practice anything on the fly.
* [WebObjects](plugin/hotswap-agent-webobjects-plugin/README.md) - Clear key value coding, component, action and validation caches after class change.
* [Weld](plugin/hotswap-agent-weld-plugin/README.md) (CDI) (2.2-2.4) - reload bean class definition after class definition/change. Beans can be reloaded according strategy defined in property file.
* [WildFlyELResolver](plugin/hotswap-agent-wildfly-el-plugin/README.md) - Clear BeanELResolver after any class redefinition.
Expand Down
6 changes: 6 additions & 0 deletions hotswap-agent/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,12 @@
<version>${project.version}</version>
</dependency>

<dependency>
<groupId>org.hotswap.agent</groupId>
<artifactId>hotswap-agent-vaadin-plugin</artifactId>
<version>${project.version}</version>
</dependency>

<dependency>
<groupId>org.pegdown</groupId>
<artifactId>pegdown</artifactId>
Expand Down
15 changes: 15 additions & 0 deletions plugin/hotswap-agent-vaadin-plugin/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[Vaadin Platform](https://vaadin.com/)
====================================

Vaadin is a development platform for web applications that prioritizes ease of development and uncompromised end user experience.

To create a vaadin project, go to https://vaadin.com/start

= Features
* Register changes in `@Route` to the router when a class is modified (add new views on the fly)
* Changes to a template model are live after a browser refresh
* All internal metadata caches are cleared whenever a class is changed

Known issues:
* Removing a class with `@Route` does not remove the mapping

21 changes: 21 additions & 0 deletions plugin/hotswap-agent-vaadin-plugin/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>org.hotswap.agent</groupId>
<artifactId>hotswap-agent-parent</artifactId>
<version>1.2.1-SNAPSHOT</version>
<relativePath>../../hotswap-agent-parent/pom.xml</relativePath>
</parent>

<artifactId>hotswap-agent-vaadin-plugin</artifactId>

<dependencies>
<dependency>
<groupId>org.hotswap.agent</groupId>
<artifactId>hotswap-agent-core</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
package org.hotswap.agent.plugin.vaadin;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;

import org.hotswap.agent.annotation.FileEvent;
import org.hotswap.agent.annotation.Init;
import org.hotswap.agent.annotation.LoadEvent;
import org.hotswap.agent.annotation.OnClassFileEvent;
import org.hotswap.agent.annotation.OnClassLoadEvent;
import org.hotswap.agent.annotation.Plugin;
import org.hotswap.agent.command.ReflectionCommand;
import org.hotswap.agent.command.Scheduler;
import org.hotswap.agent.javassist.CannotCompileException;
import org.hotswap.agent.javassist.CtClass;
import org.hotswap.agent.javassist.NotFoundException;
import org.hotswap.agent.logging.AgentLogger;
import org.hotswap.agent.util.PluginManagerInvoker;

/**
* Vaadin Platform hotswap support
*
* https://vaadin.com
*
* @author Artur Signell
*/
@Plugin(name = "Vaadin", description = "Vaadin Platform support", testedVersions = {
"10.0.0.beta9" }, expectedVersions = { "10.0+" })
public class VaadinPlugin {

@Init
Scheduler scheduler;

@Init
ClassLoader appClassLoader;

ReflectionCommand clearReflectionCache = new ReflectionCommand(this,
"com.vaadin.flow.internal.ReflectionCache", "clearAll");

private Object vaadinServlet;

private Method vaadinServletGetServletContext;

private Method routeRegistryGet;

private static AgentLogger LOGGER = AgentLogger
.getLogger(VaadinPlugin.class);

public VaadinPlugin() {
}

@OnClassLoadEvent(classNameRegexp = "com.vaadin.flow.server.VaadinServlet")
public static void init(CtClass ctClass)
throws NotFoundException, CannotCompileException {
String src = PluginManagerInvoker
.buildInitializePlugin(VaadinPlugin.class);
src += PluginManagerInvoker.buildCallPluginMethod(VaadinPlugin.class,
"registerServlet", "this", "java.lang.Object");
ctClass.getDeclaredConstructor(new CtClass[0]).insertAfter(src);

LOGGER.info("Initialized Vaadin plugin");
}

public void registerServlet(Object vaadinServlet) {
this.vaadinServlet = vaadinServlet;

try {
Class<?> servletContextClass = resolveClass(
"javax.servlet.ServletContext");
vaadinServletGetServletContext = resolveClass(
"javax.servlet.GenericServlet")
.getDeclaredMethod("getServletContext");
routeRegistryGet = getRouteRegistryClass()
.getDeclaredMethod("getInstance", servletContextClass);
} catch (NoSuchMethodException | SecurityException
| ClassNotFoundException e) {
e.printStackTrace();
}

LOGGER.info("Plugin {} initialized for servlet {}", getClass(),
vaadinServlet);
}

private Class<?> getRouteRegistryClass() throws ClassNotFoundException {
return resolveClass("com.vaadin.flow.server.startup.RouteRegistry");
}

public Object getRouteRegistry() {
try {
Object servletContext = vaadinServletGetServletContext
.invoke(vaadinServlet);
Object routeRegistry = routeRegistryGet.invoke(null,
servletContext);
return routeRegistry;
} catch (IllegalAccessException | IllegalArgumentException
| InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}

@OnClassLoadEvent(classNameRegexp = ".*", events = LoadEvent.REDEFINE)
public void invalidateReflectionCache() throws Exception {
LOGGER.debug("Clearing Vaadin reflection cache");
scheduler.scheduleCommand(clearReflectionCache);
}

@OnClassFileEvent(classNameRegexp = ".*", events = { FileEvent.CREATE,
FileEvent.MODIFY })
public void addNewRoute(CtClass ctClass) throws Exception {
LOGGER.debug("Class file event for " + ctClass.getName());
if (ctClass.hasAnnotation("com.vaadin.flow.router.Route")) {
LOGGER.debug("New route class: " + ctClass.getName());
ensureInRouter(ctClass);
}
}

private void ensureInRouter(CtClass ctClass)
throws ReflectiveOperationException {
Object routeRegistry = getRouteRegistry();
Set<Class<?>> routeClasses = getCurrentRouteClasses(routeRegistry);

Class<?> hashSet = resolveClass("java.util.HashSet");
Object classSet = hashSet.newInstance();
Method addAll = hashSet.getMethod("addAll",
resolveClass("java.util.Collection"));
Method add = hashSet.getMethod("add", resolveClass("java.lang.Object"));
addAll.invoke(classSet, routeClasses);
add.invoke(classSet, resolveClass(ctClass.getName()));

forceRouteUpdate(routeRegistry, classSet);

}

private void forceRouteUpdate(Object routeRegistry, Object routeClassSet)
throws ReflectiveOperationException {

Field targetRoutesField = getRouteRegistryClass()
.getDeclaredField("targetRoutes");
Field routesField = getRouteRegistryClass().getDeclaredField("routes");
Field routeDataField = getRouteRegistryClass()
.getDeclaredField("routeData");

targetRoutesField.setAccessible(true);
routesField.setAccessible(true);
routeDataField.setAccessible(true);

targetRoutesField.set(routeRegistry, createAtomicRef());
routesField.set(routeRegistry, createAtomicRef());
routeDataField.set(routeRegistry, createAtomicRef());

Method setNavigationTargets = getRouteRegistryClass().getDeclaredMethod(
"setNavigationTargets", resolveClass("java.util.Set"));
setNavigationTargets.invoke(routeRegistry, routeClassSet);
}

private Object createAtomicRef() throws InstantiationException,
IllegalAccessException, ClassNotFoundException {
return resolveClass("java.util.concurrent.atomic.AtomicReference")
.newInstance();
}

private Set<Class<?>> getCurrentRouteClasses(Object routeRegistry)
throws ReflectiveOperationException {
Field targetRoutesField = getRouteRegistryClass()
.getDeclaredField("targetRoutes");
targetRoutesField.setAccessible(true);
AtomicReference<Map> ref = (AtomicReference<Map>) targetRoutesField
.get(routeRegistry);
return ref.get().keySet();
}

private Class<?> resolveClass(String name) throws ClassNotFoundException {
return Class.forName(name, true, appClassLoader);
}

}
1 change: 1 addition & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
<module>plugin/hotswap-agent-deltaspike-plugin</module>
<module>plugin/hotswap-agent-wildfly-el-plugin</module>
<module>plugin/hotswap-agent-glassfish-plugin</module>
<module>plugin/hotswap-agent-vaadin-plugin</module>
<module>hotswap-agent</module>
</modules>

Expand Down

0 comments on commit a3e4e45

Please sign in to comment.