Skip to content

Commit

Permalink
Refactor finder registry
Browse files Browse the repository at this point in the history
Signed-off-by: John Niang <[email protected]>
  • Loading branch information
JohnNiang committed Jan 25, 2024
1 parent a382057 commit 93c8c8e
Show file tree
Hide file tree
Showing 4 changed files with 126 additions and 156 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import java.util.stream.Stream;
import lombok.extern.slf4j.Slf4j;
import org.pf4j.PluginRuntimeException;
import org.springframework.beans.factory.support.DefaultBeanNameGenerator;
import org.springframework.boot.env.PropertySourceLoader;
import org.springframework.boot.env.YamlPropertySourceLoader;
import org.springframework.context.ApplicationContext;
Expand Down Expand Up @@ -65,6 +66,7 @@ public ApplicationContext create(String pluginId) {

sw.start("Create");
var context = new PluginApplicationContext(pluginId);
context.setBeanNameGenerator(DefaultBeanNameGenerator.INSTANCE);
context.registerShutdownHook();
context.setParent(pluginManager.getSharedContext());

Expand All @@ -85,6 +87,10 @@ public ApplicationContext create(String pluginId) {
context.registerBean(AggregatedRouterFunction.class);
beanFactory.registerSingleton("pluginWrapper", pluginWrapper);

if (pluginWrapper.getPlugin() instanceof SpringPlugin springPlugin) {
beanFactory.registerSingleton("pluginContext", springPlugin.getPluginContext());
}

var rootContext = pluginManager.getRootContext();
rootContext.getBeanProvider(ViewNameResolver.class)
.ifAvailable(viewNameResolver -> {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package run.halo.app.theme.finders;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;

/**
* Finder registry for class annotated with {@link Finder}.
*
* @author guqing
* @since 2.0.0
*/
@Component
public class DefaultFinderRegistry implements FinderRegistry, InitializingBean {
private final Map<String, List<String>> pluginFindersLookup = new ConcurrentHashMap<>();
private final Map<String, Object> finders = new ConcurrentHashMap<>(64);

private final ApplicationContext applicationContext;

public DefaultFinderRegistry(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}

Object get(String name) {
return finders.get(name);
}

/**
* Given a name, register a Finder for it.
*
* @param name the canonical name
* @param finder the finder to be registered
* @throws IllegalStateException if the name is already existing
*/
void putFinder(String name, Object finder) {
if (finders.containsKey(name)) {
throw new IllegalStateException(
"Finder with name '" + name + "' is already registered");
}
finders.put(name, finder);
}

/**
* Register a finder.
*
* @param finder register a finder that annotated with {@link Finder}
* @return the name of the finder
*/
String putFinder(Object finder) {
var name = getFinderName(finder);
this.putFinder(name, finder);
return name;
}

private String getFinderName(Object finder) {
var annotation = finder.getClass().getAnnotation(Finder.class);
if (annotation == null) {
// should never happen
throw new IllegalStateException("Finder must be annotated with @Finder");
}
String name = annotation.value();
if (name == null) {
name = finder.getClass().getSimpleName();
}
return name;
}

public void removeFinder(String name) {
finders.remove(name);
}

public Map<String, Object> getFinders() {
return Map.copyOf(finders);
}

@Override
public void afterPropertiesSet() {
// initialize finders from application context
applicationContext.getBeansWithAnnotation(Finder.class)
.forEach((beanName, finder) -> {
var finderName = getFinderName(finder);
this.putFinder(finderName, finder);
});
}

@Override
public void register(String pluginId, ApplicationContext pluginContext) {
pluginContext.getBeansWithAnnotation(Finder.class)
.forEach((beanName, finder) -> {
var finderName = getFinderName(finder);
this.putFinder(finderName, finder);
pluginFindersLookup
.computeIfAbsent(pluginId, ignored -> new ArrayList<>())
.add(finderName);
});
}

@Override
public void unregister(String pluginId) {
var finderNames = pluginFindersLookup.remove(pluginId);
if (finderNames != null) {
finderNames.forEach(finders::remove);
}
}

}
Original file line number Diff line number Diff line change
@@ -1,148 +1,20 @@
package run.halo.app.theme.finders;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import run.halo.app.plugin.SpringPlugin;
import run.halo.app.plugin.event.HaloPluginStartedEvent;
import run.halo.app.plugin.event.HaloPluginStoppedEvent;

/**
* Finder registry for class annotated with {@link Finder}.
*
* @author guqing
* @since 2.0.0
*/
@Component
public class FinderRegistry implements InitializingBean {
private final Map<String, List<String>> pluginFindersLookup = new ConcurrentHashMap<>();
private final Map<String, Object> finders = new ConcurrentHashMap<>(64);
public interface FinderRegistry {

private final ApplicationContext applicationContext;
Map<String, Object> getFinders();

public FinderRegistry(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
void register(String pluginId, ApplicationContext pluginContext);

Object get(String name) {
return finders.get(name);
}
void unregister(String pluginId);

/**
* Given a name, register a Finder for it.
*
* @param name the canonical name
* @param finder the finder to be registered
* @throws IllegalStateException if the name is already existing
*/
public void registerFinder(String name, Object finder) {
if (finders.containsKey(name)) {
throw new IllegalStateException(
"Finder with name '" + name + "' is already registered");
}
finders.put(name, finder);
}

/**
* Register a finder.
*
* @param finder register a finder that annotated with {@link Finder}
* @return the name of the finder
*/
public String registerFinder(Object finder) {
Finder annotation = finder.getClass().getAnnotation(Finder.class);
if (annotation == null) {
throw new IllegalStateException("Finder must be annotated with @Finder");
}
String name = annotation.value();
if (name == null) {
name = finder.getClass().getSimpleName();
}
this.registerFinder(name, finder);
return name;
}

public void removeFinder(String name) {
finders.remove(name);
}

public Map<String, Object> getFinders() {
return Map.copyOf(finders);
}

@Override
public void afterPropertiesSet() throws Exception {
// initialize finders from application context
applicationContext.getBeansWithAnnotation(Finder.class)
.forEach((k, v) -> {
registerFinder(v);
});
}

/**
* Register finders for a plugin.
*
* @param event plugin started event
*/
@EventListener(HaloPluginStartedEvent.class)
public void onPluginStarted(HaloPluginStartedEvent event) {
var plugin = event.getPlugin().getPlugin();
var pluginId = event.getPlugin().getPluginId();
if (plugin instanceof SpringPlugin springPlugin) {
var context = springPlugin.getApplicationContext();
context.getBeansWithAnnotation(Finder.class)
.forEach((beanName, finderObject) -> {
// register finder
String finderName = registerFinder(finderObject);
// add to plugin finder lookup
pluginFindersLookup.computeIfAbsent(pluginId, k -> new ArrayList<>())
.add(finderName);
});
}
}

/**
* Remove finders registered by the plugin.
*
* @param event plugin stopped event
*/
@EventListener(HaloPluginStoppedEvent.class)
public void onPluginStopped(HaloPluginStoppedEvent event) {
String pluginId = event.getPlugin().getPluginId();
boolean containsKey = pluginFindersLookup.containsKey(pluginId);
if (!containsKey) {
return;
}
pluginFindersLookup.get(pluginId).forEach(this::removeFinder);
}

public void register(String pluginId, ApplicationContext pluginContext) {
pluginContext.getBeansWithAnnotation(Finder.class)
.forEach((beanName, finder) -> {
var finderName = registerFinder(finder);
pluginFindersLookup.computeIfAbsent(pluginId, igored -> new ArrayList<>())
.add(finderName);
});
}

public void unregister(String pluginId) {
pluginFindersLookup.getOrDefault(pluginId, List.of())
.forEach(this::removeFinder);
}

/**
* Only for test.
*
* @param pluginId plugin id
* @param finderName finder name
*/
void addPluginFinder(String pluginId, String finderName) {
pluginFindersLookup.computeIfAbsent(pluginId, k -> new ArrayList<>())
.add(finderName);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,14 @@

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.Mockito.when;

import java.util.Map;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import org.pf4j.PluginWrapper;
import org.springframework.context.ApplicationContext;
import run.halo.app.plugin.event.HaloPluginStoppedEvent;

/**
* Tests for {@link FinderRegistry}.
Expand All @@ -24,29 +20,29 @@
@ExtendWith(MockitoExtension.class)
class FinderRegistryTest {

private FinderRegistry finderRegistry;
private DefaultFinderRegistry finderRegistry;
@Mock
private ApplicationContext applicationContext;

@BeforeEach
void setUp() {
finderRegistry = new FinderRegistry(applicationContext);
finderRegistry = new DefaultFinderRegistry(applicationContext);
}

@Test
void registerFinder() {
assertThatThrownBy(() -> {
finderRegistry.registerFinder(new Object());
finderRegistry.putFinder(new Object());
}).isInstanceOf(IllegalStateException.class)
.hasMessage("Finder must be annotated with @Finder");

String s = finderRegistry.registerFinder(new FakeFinder());
String s = finderRegistry.putFinder(new FakeFinder());
assertThat(s).isEqualTo("test");
}

@Test
void removeFinder() {
String s = finderRegistry.registerFinder(new FakeFinder());
String s = finderRegistry.putFinder(new FakeFinder());
assertThat(s).isEqualTo("test");
Object test = finderRegistry.get("test");
assertThat(test).isNotNull();
Expand All @@ -60,25 +56,11 @@ void removeFinder() {
void getFinders() {
assertThat(finderRegistry.getFinders()).hasSize(0);

finderRegistry.registerFinder(new FakeFinder());
finderRegistry.putFinder(new FakeFinder());
Map<String, Object> finders = finderRegistry.getFinders();
assertThat(finders).hasSize(1);
}

@Test
void onPluginStopped() {
finderRegistry.registerFinder("a", new Object());
finderRegistry.addPluginFinder("fake", "a");

HaloPluginStoppedEvent event = Mockito.mock(HaloPluginStoppedEvent.class);
PluginWrapper pluginWrapper = Mockito.mock(PluginWrapper.class);
when(event.getPlugin()).thenReturn(pluginWrapper);
when(pluginWrapper.getPluginId()).thenReturn("fake");

finderRegistry.onPluginStopped(event);
assertThat(finderRegistry.get("a")).isNull();
}

@Finder("test")
static class FakeFinder {

Expand Down

0 comments on commit 93c8c8e

Please sign in to comment.