Skip to content

Commit 39e3e1f

Browse files
committed
new: extend new plugin system
1 parent a8cc6d9 commit 39e3e1f

File tree

10 files changed

+342
-480
lines changed

10 files changed

+342
-480
lines changed

server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/ServerApplication.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ public void init() {
4545

4646
public static void main(String[] args) {
4747

48+
/** Disable Java Flight Recorder for Redis Lettuce driver **/
49+
System.setProperty("io.lettuce.core.jfr", "false");
50+
4851
Schedulers.enableMetrics();
4952

5053
new SpringApplicationBuilder(ServerApplication.class)

server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/framework/configuration/PluginConfiguration.java

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@
33
import java.util.ArrayList;
44

55
import org.lowcoder.api.framework.plugin.LowcoderPluginManager;
6-
import org.lowcoder.api.framework.plugin.PluginLoader;
7-
import org.springframework.context.ApplicationContext;
6+
import org.lowcoder.api.framework.plugin.endpoint.PluginEndpointHandler;
87
import org.springframework.context.annotation.Bean;
98
import org.springframework.context.annotation.Configuration;
109
import org.springframework.context.annotation.DependsOn;
@@ -20,24 +19,17 @@
2019
@Configuration
2120
public class PluginConfiguration
2221
{
23-
// private final ApplicationContext applicationContext;
24-
// private final PluginLoader pluginLoader;
25-
//
26-
// public LowcoderPluginManager lowcoderPluginManager()
27-
// {
28-
// return new LowcoderPluginManager(applicationContext, pluginLoader);
29-
// }
3022

3123
@SuppressWarnings("unchecked")
3224
@Bean
3325
@DependsOn("lowcoderPluginManager")
34-
RouterFunction<?> pluginEndpoints(LowcoderPluginManager pluginManager)
26+
RouterFunction<?> pluginEndpoints(LowcoderPluginManager pluginManager, PluginEndpointHandler pluginEndpointHandler)
3527
{
3628
RouterFunction<?> pluginsList = RouterFunctions.route()
3729
.GET(RequestPredicates.path("/plugins"), req -> ServerResponse.ok().body(Mono.just(pluginManager.getLoadedPluginsInfo()), ArrayList.class))
3830
.build();
3931

40-
RouterFunction<?> endpoints = pluginManager.getEndpoints().stream()
32+
RouterFunction<?> endpoints = pluginEndpointHandler.registeredEndpoints().stream()
4133
.map(r-> (RouterFunction<ServerResponse>)r)
4234
.reduce((o, r )-> (RouterFunction<ServerResponse>) o.andOther(r))
4335
.orElse(null);

server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/framework/plugin/LowcoderPluginManager.java

Lines changed: 2 additions & 159 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,20 @@
11
package org.lowcoder.api.framework.plugin;
22

3-
import static org.springframework.web.reactive.function.server.RequestPredicates.DELETE;
4-
import static org.springframework.web.reactive.function.server.RequestPredicates.GET;
5-
import static org.springframework.web.reactive.function.server.RequestPredicates.OPTIONS;
6-
import static org.springframework.web.reactive.function.server.RequestPredicates.PATCH;
7-
import static org.springframework.web.reactive.function.server.RequestPredicates.POST;
8-
import static org.springframework.web.reactive.function.server.RequestPredicates.PUT;
9-
import static org.springframework.web.reactive.function.server.RouterFunctions.route;
10-
11-
import java.lang.reflect.InvocationTargetException;
12-
import java.lang.reflect.Method;
133
import java.util.ArrayList;
144
import java.util.Comparator;
155
import java.util.LinkedHashMap;
166
import java.util.List;
177
import java.util.Map;
188

199
import org.apache.commons.collections4.CollectionUtils;
20-
import org.apache.commons.lang3.StringUtils;
21-
import org.lowcoder.api.framework.plugin.data.PluginServerRequest;
22-
import org.lowcoder.plugin.api.EndpointExtension;
2310
import org.lowcoder.plugin.api.LowcoderPlugin;
2411
import org.lowcoder.plugin.api.LowcoderServices;
25-
import org.lowcoder.plugin.api.PluginEndpoint;
26-
import org.lowcoder.plugin.api.data.EndpointRequest;
27-
import org.lowcoder.plugin.api.data.EndpointResponse;
28-
import org.lowcoder.sdk.exception.BaseException;
29-
import org.springframework.core.ResolvableType;
30-
import org.springframework.http.ResponseCookie;
3112
import org.springframework.stereotype.Component;
32-
import org.springframework.web.reactive.function.server.RequestPredicate;
33-
import org.springframework.web.reactive.function.server.RouterFunction;
34-
import org.springframework.web.reactive.function.server.ServerResponse;
35-
import org.springframework.web.reactive.function.server.ServerResponse.BodyBuilder;
3613

3714
import jakarta.annotation.PostConstruct;
3815
import jakarta.annotation.PreDestroy;
3916
import lombok.RequiredArgsConstructor;
4017
import lombok.extern.slf4j.Slf4j;
41-
import reactor.core.publisher.Mono;
4218

4319
@RequiredArgsConstructor
4420
@Component
@@ -49,7 +25,6 @@ public class LowcoderPluginManager
4925
private final PluginLoader pluginLoader;
5026

5127
private Map<String, LowcoderPlugin> plugins = new LinkedHashMap<>();
52-
private List<RouterFunction<ServerResponse>> routes = new ArrayList<>();
5328

5429
@PostConstruct
5530
private void loadPlugins()
@@ -60,11 +35,8 @@ private void loadPlugins()
6035

6136
for (LowcoderPlugin plugin : sorted)
6237
{
63-
if (plugin.load(lowcoderServices))
64-
{
65-
log.info("Plugin [{}] loaded successfully.", plugin.pluginId());
66-
registerEndpoints(plugin);
67-
}
38+
PluginExecutor executor = new PluginExecutor(plugin, lowcoderServices);
39+
executor.start();
6840
}
6941
}
7042

@@ -84,11 +56,6 @@ public void unloadPlugins()
8456
}
8557
}
8658

87-
public List<RouterFunction<ServerResponse>> getEndpoints()
88-
{
89-
return this.routes;
90-
}
91-
9259
public List<PluginInfo> getLoadedPluginsInfo()
9360
{
9461
List<PluginInfo> infos = new ArrayList<>();
@@ -121,130 +88,6 @@ private void registerPlugins()
12188
}
12289
}
12390

124-
125-
private void registerEndpoints(LowcoderPlugin plugin)
126-
{
127-
if (CollectionUtils.isNotEmpty(plugin.endpoints()))
128-
{
129-
for (PluginEndpoint endpoint : plugin.endpoints())
130-
{
131-
Method[] handlers = endpoint.getClass().getDeclaredMethods();
132-
if (handlers != null && handlers.length > 0)
133-
{
134-
for (Method handler : handlers)
135-
{
136-
registerEndpointHandler(plugin, endpoint, handler);
137-
}
138-
}
139-
}
140-
}
141-
}
142-
143-
private void registerEndpointHandler(LowcoderPlugin plugin, PluginEndpoint endpoint, Method handler)
144-
{
145-
if (handler.isAnnotationPresent(EndpointExtension.class))
146-
{
147-
if (checkHandlerMethod(handler))
148-
{
149-
150-
EndpointExtension endpointMeta = handler.getAnnotation(EndpointExtension.class);
151-
routes.add(route(createRequestPredicate(plugin, endpointMeta), req -> {
152-
Mono<ServerResponse> result = null;
153-
try
154-
{
155-
EndpointResponse response = (EndpointResponse)handler.invoke(endpoint, PluginServerRequest.fromServerRequest(req));
156-
result = createServerResponse(response);
157-
}
158-
catch (IllegalAccessException | InvocationTargetException cause)
159-
{
160-
throw new BaseException("Error running handler for [ " + endpointMeta.method() + ": " + endpointMeta.uri() + "] !");
161-
}
162-
return result;
163-
})
164-
);
165-
log.info("Registered plugin endpoint: {} -> {} -> {}: {}", plugin.pluginId(), endpoint.getClass().getSimpleName(), endpointMeta.method(), endpointMeta.uri());
166-
}
167-
else
168-
{
169-
log.error("Cannot register plugin endpoint: {} -> {} -> {}! Handler method must be defined as: public Mono<ServerResponse> {}(ServerRequest request)", plugin.pluginId(), endpoint.getClass().getSimpleName(), handler.getName(), handler.getName());
170-
}
171-
}
172-
}
173-
174-
private Mono<ServerResponse> createServerResponse(EndpointResponse pluginResponse)
175-
{
176-
/** Create response with given status **/
177-
BodyBuilder builder = ServerResponse.status(pluginResponse.statusCode());
178-
179-
/** Set response headers **/
180-
if (pluginResponse.headers() != null && !pluginResponse.headers().isEmpty())
181-
{
182-
pluginResponse.headers().entrySet()
183-
.forEach(entry -> {
184-
builder.header(entry.getKey(), entry.getValue().toArray(new String[] {}));
185-
});
186-
187-
}
188-
189-
/** Set cookies if available **/
190-
if (pluginResponse.cookies() != null && !pluginResponse.cookies().isEmpty())
191-
{
192-
pluginResponse.cookies().values()
193-
.forEach(cookies -> {
194-
cookies.forEach(cookie -> {
195-
builder.cookie(ResponseCookie.from(cookie.getKey(), cookie.getValue()).build());
196-
});
197-
198-
});
199-
}
200-
201-
/** Set response body if available **/
202-
if (pluginResponse.body() != null)
203-
{
204-
return builder.bodyValue(pluginResponse.body());
205-
}
206-
207-
return builder.build();
208-
}
209-
210-
private boolean checkHandlerMethod(Method method)
211-
{
212-
ResolvableType returnType = ResolvableType.forMethodReturnType(method);
213-
214-
return (returnType.getRawClass().isAssignableFrom(EndpointResponse.class)
215-
&& method.getParameterCount() == 1
216-
&& method.getParameterTypes()[0].isAssignableFrom(EndpointRequest.class)
217-
);
218-
}
219-
220-
private RequestPredicate createRequestPredicate(LowcoderPlugin plugin, EndpointExtension endpoint)
221-
{
222-
String basePath = "/plugins/" + plugin.pluginId();
223-
224-
switch(endpoint.method())
225-
{
226-
case GET:
227-
return GET(pluginEndpointUri(basePath, endpoint.uri()));
228-
case POST:
229-
return POST(pluginEndpointUri(basePath, endpoint.uri()));
230-
case PUT:
231-
return PUT(pluginEndpointUri(basePath, endpoint.uri()));
232-
case PATCH:
233-
return PATCH(pluginEndpointUri(basePath, endpoint.uri()));
234-
case DELETE:
235-
return DELETE(pluginEndpointUri(basePath, endpoint.uri()));
236-
case OPTIONS:
237-
return OPTIONS(pluginEndpointUri(basePath, endpoint.uri()));
238-
}
239-
return null;
240-
}
241-
242-
private String pluginEndpointUri(String basePath, String uri)
243-
{
244-
return StringUtils.join(basePath, StringUtils.prependIfMissing(uri, "/"));
245-
}
246-
247-
24891
private record PluginInfo(
24992
String id,
25093
String description,

server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/framework/plugin/PathBasedPluginLoader.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -97,13 +97,12 @@ protected List<String> findPluginCandidates(Path pluginsDir)
9797
protected List<LowcoderPlugin> loadPluginCandidates(String pluginJar)
9898
{
9999
List<LowcoderPlugin> pluginCandidates = new ArrayList<>();
100-
101-
PluginJarClassLoader pluginClassLoader = null;
100+
102101
try
103102
{
104-
pluginClassLoader = new PluginJarClassLoader(getClass().getClassLoader(), Path.of(pluginJar));
103+
Path pluginPath = Path.of(pluginJar);
104+
PluginClassLoader pluginClassLoader = new PluginClassLoader(pluginPath.getFileName().toString(), pluginPath);
105105

106-
107106
ServiceLoader<LowcoderPlugin> pluginServices = ServiceLoader.load(LowcoderPlugin.class, pluginClassLoader);
108107
if (pluginServices != null )
109108
{
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
package org.lowcoder.api.framework.plugin;
2+
3+
import java.io.IOException;
4+
import java.net.MalformedURLException;
5+
import java.net.URL;
6+
import java.net.URLClassLoader;
7+
import java.nio.file.Path;
8+
import java.util.Enumeration;
9+
import java.util.Objects;
10+
11+
import org.apache.commons.lang3.StringUtils;
12+
13+
import lombok.extern.slf4j.Slf4j;
14+
15+
16+
@Slf4j
17+
public class PluginClassLoader extends URLClassLoader
18+
{
19+
private static final ClassLoader baseClassLoader = ClassLoader.getPlatformClassLoader();
20+
private final ClassLoader appClassLoader = Thread.currentThread().getContextClassLoader();
21+
22+
public PluginClassLoader(String name, Path pluginPath)
23+
{
24+
super(name, pathToURLs(pluginPath), baseClassLoader);
25+
}
26+
27+
@Override
28+
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException
29+
{
30+
Class<?> clazz = findLoadedClass(name);
31+
if (clazz != null)
32+
{
33+
return clazz;
34+
}
35+
36+
if (name.startsWith("org.lowcoder.plugin.api."))
37+
{
38+
try
39+
{
40+
clazz = appClassLoader.loadClass(name);
41+
return clazz;
42+
}
43+
catch(Throwable cause)
44+
{
45+
log.error("[{}] :: Error loading class with appClassLoader - {}", name, cause.getMessage(), cause );
46+
}
47+
}
48+
49+
50+
try
51+
{
52+
clazz = super.loadClass(name, resolve);
53+
if (clazz != null)
54+
{
55+
return clazz;
56+
}
57+
}
58+
catch(NoClassDefFoundError cause)
59+
{
60+
log.error("[{}] :: Error loading class - {}", name, cause.getMessage(), cause );
61+
}
62+
63+
return null;
64+
}
65+
66+
@Override
67+
public URL getResource(String name) {
68+
Objects.requireNonNull(name);
69+
if (StringUtils.startsWithAny(name, "org/lowcoder/plugin/api/", "org.lowcoder.plugin.api."))
70+
{
71+
return appClassLoader.getResource(name);
72+
}
73+
return super.getResource(name);
74+
}
75+
76+
77+
@Override
78+
public Enumeration<URL> getResources(String name) throws IOException
79+
{
80+
Objects.requireNonNull(name);
81+
if (StringUtils.startsWithAny(name, "org/lowcoder/plugin/api/", "org.lowcoder.plugin.api."))
82+
{
83+
return appClassLoader.getResources(name);
84+
}
85+
return super.getResources(name);
86+
}
87+
88+
89+
private static URL[] pathToURLs(Path path)
90+
{
91+
URL[] urls = null;
92+
try
93+
{
94+
urls = new URL[] { path.toUri().toURL() };
95+
}
96+
catch(MalformedURLException cause)
97+
{
98+
/** should not happen **/
99+
}
100+
101+
return urls;
102+
}
103+
104+
}

0 commit comments

Comments
 (0)