Skip to content

Commit

Permalink
fix: loading entities from plugins
Browse files Browse the repository at this point in the history
  • Loading branch information
bendavies99 committed Jun 30, 2023
1 parent 2069981 commit 45b1bac
Show file tree
Hide file tree
Showing 2 changed files with 154 additions and 52 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@

import jakarta.persistence.Entity;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
import net.bdavies.babblebot.BabblebotApplication;
import net.bdavies.babblebot.api.IApplication;
import net.bdavies.babblebot.api.connect.ConnectQueue;
Expand All @@ -35,7 +36,15 @@
import net.bdavies.babblebot.api.plugins.Plugin;
import net.bdavies.babblebot.api.plugins.PluginConfig;
import net.bdavies.babblebot.plugins.PluginConfigParser;
import org.hibernate.jpa.HibernatePersistenceProvider;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.support.JpaRepositoryFactory;
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
import org.springframework.orm.jpa.JpaVendorAdapter;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.persistenceunit.PersistenceManagedTypes;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Repository;
Expand All @@ -44,12 +53,14 @@
import org.xeustechnologies.jcl.JarClassLoader;
import reactor.core.publisher.Flux;

import javax.sql.DataSource;
import java.io.ByteArrayInputStream;
import java.lang.annotation.Annotation;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.net.URL;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Supplier;
import java.util.stream.Collectors;

/**
* @author ben.davies99@outlook.com (Ben Davies)
Expand Down Expand Up @@ -83,8 +94,7 @@ public Flux<Object> importPlugin(IPluginModel config)
{
//Load All
return Flux.create(sink -> {
List<String> check = Arrays.asList("Proxy", "java/lang", "com/sun", "Guice", "google",
"GeneratedMethodAccessor", "GeneratedConstructorAccessor", "javax", "discord4j");

if (jcl.getLoadedResources().keySet().isEmpty())
{
log.error("Unable to load plugin: {} no classes found", config.getName());
Expand All @@ -93,60 +103,40 @@ public Flux<Object> importPlugin(IPluginModel config)
log.info("Loaded plugin jar: {} -- time took: {}ms", config.getName(),
System.currentTimeMillis() - now);
}
jcl.getLoadedResources().keySet().stream()
.map(c -> {
if (c.endsWith(".class") && !c.contains("module-info"))

List<Class<?>> pluginClasses = getPluginClasses(config);
List<Class<?>> entityClasses = getEntityClasses(pluginClasses);
List<Class<?>> repositoryClasses = getRepositoryClasses(pluginClasses);

setupJpa(entityClasses, repositoryClasses, config.getName());
pluginClasses
.stream()
.peek(c -> {
try
{
try
log.info("Handling class: {}", c);
BabblebotApplication bbApp = application.get(BabblebotApplication.class);
GenericApplicationContext applicationContext =
(GenericApplicationContext) bbApp.getApplicationContext();
if (applicationContext.getBeanNamesForType(c).length > 0)
{
return jcl.loadClass(c.replaceAll("/", ".").replace(".class", ""));
Arrays.stream(applicationContext
.getBeanNamesForType(c)).forEach(
applicationContext::removeBeanDefinition);
}
catch (ClassNotFoundException | NoClassDefFoundError e)
if (c.isAnnotationPresent(PluginConfig.class))
{
application.get(PluginConfigParser.class)
.setupPluginConfig(c, config);
} else if (!c.isAnnotationPresent(Repository.class)
&& !c.isAnnotationPresent(Entity.class))
{
if (e.getMessage().contains("net/bdavies"))
{
log.error("Failed to load plugin: {} because it is out " +
"of date with the current server version cannot" +
" load " +
"class {} because of no ClassDefinition for {}." +
" " +
"either update the plugin to the latest version" +
" or " +
"contact the plugin maintainer",
config.getName(), c, e.getMessage());
} else if (contains(c, e.getMessage(), check))
{
return null;
} else
{
log.error("Failed to load class: " + c +
", please contact the plugin maintainer.", e);
}
return null;
applicationContext.registerBean(c);
}
}
return null;
})
.filter(Objects::nonNull)
.map(c -> (Class<?>) c)
.filter(this::isRegistrableBean)
.peek(c -> {
BabblebotApplication bbApp = application.get(BabblebotApplication.class);
GenericApplicationContext applicationContext =
(GenericApplicationContext) bbApp.getApplicationContext();
if (applicationContext.getBeanNamesForType(c).length > 0)
{
Arrays.stream(applicationContext
.getBeanNamesForType(c)).forEach(
applicationContext::removeBeanDefinition);
}
if (c.isAnnotationPresent(PluginConfig.class))
{
application.get(PluginConfigParser.class)
.setupPluginConfig(c, config);
} else
catch (Exception e)
{
applicationContext.registerBean(c);
log.error("Something went wrong {}", e.getMessage());
}
})
.filter(c -> c.isAnnotationPresent(Plugin.class) ||
Expand All @@ -170,6 +160,115 @@ public Flux<Object> importPlugin(IPluginModel config)
return Flux.empty();
}

private void setupJpa(List<Class<?>> entityClasses, List<Class<?>> repositoryClasses, String pluginName)
{
BabblebotApplication bbApp = (BabblebotApplication) application;
GenericApplicationContext gac = (GenericApplicationContext) bbApp.getApplicationContext();
LocalContainerEntityManagerFactoryBean entityManagerFactoryBean =
new LocalContainerEntityManagerFactoryBean();
entityManagerFactoryBean.setJpaVendorAdapter(application.get(JpaVendorAdapter.class));
entityManagerFactoryBean.setDataSource(application.get(DataSource.class));
log.info("{}", jcl.getLoadedClasses());
entityManagerFactoryBean.setResourceLoader(new DefaultResourceLoader(jcl));
entityManagerFactoryBean.setPersistenceProviderClass(
HibernatePersistenceProvider.class);
entityManagerFactoryBean.setManagedTypes(new PersistenceManagedTypes()
{
@Override
public List<String> getManagedClassNames()
{
return entityClasses.stream().map(Class::getName).collect(Collectors.toList());
}

@Override
public List<String> getManagedPackages()
{
return new ArrayList<>();
}

@Override
public URL getPersistenceUnitRootUrl()
{
return null;
}
});
entityManagerFactoryBean.setJpaProperties(new Properties());
entityManagerFactoryBean.setPersistenceUnitName(pluginName + "$$jpa");
entityManagerFactoryBean.afterPropertiesSet();

try (val manager = entityManagerFactoryBean.getNativeEntityManagerFactory()
.createEntityManager())
{
RepositoryFactorySupport factory = new JpaRepositoryFactory(manager);
factory.setBeanClassLoader(jcl);

repositoryClasses.forEach(rc -> {
//noinspection unchecked
Class<Object> rcO = (Class<Object>) rc;
Object repository = factory.getRepository(rcO);
Supplier<Object> o = () -> repository;
gac.registerBean(rcO, o);
});
}
}

private List<Class<?>> getEntityClasses(List<Class<?>> pluginClasses)
{
return pluginClasses.stream().filter(c -> c.isAnnotationPresent(Entity.class))
.collect(Collectors.toList());
}

private List<Class<?>> getRepositoryClasses(List<Class<?>> pluginClasses)
{
return pluginClasses.stream().filter(c -> c.isAnnotationPresent(Repository.class)
|| JpaRepository.class.isAssignableFrom(c))
.collect(Collectors.toList());
}

private List<Class<?>> getPluginClasses(IPluginModel config)
{
List<String> check = Arrays.asList("Proxy", "java/lang", "com/sun", "Guice", "google",
"GeneratedMethodAccessor", "GeneratedConstructorAccessor", "javax", "discord4j");
return jcl.getLoadedResources().keySet().stream()
.map(c -> {
if (c.endsWith(".class") && !c.contains("module-info"))
{
try
{
return jcl.loadClass(c.replaceAll("/", ".").replace(".class", ""));
}
catch (ClassNotFoundException | NoClassDefFoundError e)
{
if (e.getMessage().contains("net/bdavies"))
{
log.error("Failed to load plugin: {} because it is out " +
"of date with the current server version cannot" +
" load " +
"class {} because of no ClassDefinition for {}." +
" " +
"either update the plugin to the latest version" +
" or " +
"contact the plugin maintainer",
config.getName(), c, e.getMessage());
} else if (contains(c, e.getMessage(), check))
{
return null;
} else
{
log.error("Failed to load class: " + c +
", please contact the plugin maintainer.", e);
}
return null;
}
}
return null;
})
.filter(Objects::nonNull)
.map(c -> (Class<?>) c)
.filter(this::isRegistrableBean)
.collect(Collectors.toList());
}

private boolean isRegistrableBean(Class<?> c)
{
List<Class<? extends Annotation>> registrableAnnotations = List.of(
Expand All @@ -178,6 +277,7 @@ private boolean isRegistrableBean(Class<?> c)
Plugin.class,
PluginConfig.class,
Repository.class,
Entity.class,
Controller.class,
RestController.class,
Entity.class,
Expand Down
2 changes: 2 additions & 0 deletions server/src/main/resources/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ connect.use-connect=${CONNECT_ENABLED:false}
connect.worker=${IS_WORKER:false}
connect.rabbitmq.enabled=${CONNECT_RABBIT_MQ_ENABLED:true}
server.port=${SERVER_PORT:8080}
spring.servlet.multipart.max-file-size=1GB
spring.servlet.multipart.max-request-size=1GB

0 comments on commit 45b1bac

Please sign in to comment.