|
1 | 1 | package org.lowcoder.api.framework.plugin;
|
2 | 2 |
|
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; |
13 |
| -import java.util.ArrayList; |
14 |
| -import java.util.Comparator; |
15 |
| -import java.util.LinkedHashMap; |
16 |
| -import java.util.List; |
17 | 3 | import java.util.Map;
|
18 | 4 |
|
19 |
| -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; |
23 |
| -import org.lowcoder.plugin.api.LowcoderPlugin; |
24 |
| -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; |
| 5 | +import org.lowcoder.plugin.LowcoderPlugin; |
| 6 | +import org.lowcoder.sdk.config.CommonConfig; |
| 7 | +import org.springframework.boot.system.ApplicationHome; |
| 8 | +import org.springframework.context.ConfigurableApplicationContext; |
31 | 9 | 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; |
36 | 10 |
|
37 | 11 | import jakarta.annotation.PostConstruct;
|
38 |
| -import jakarta.annotation.PreDestroy; |
39 | 12 | import lombok.RequiredArgsConstructor;
|
40 | 13 | import lombok.extern.slf4j.Slf4j;
|
41 |
| -import reactor.core.publisher.Mono; |
42 | 14 |
|
43 | 15 | @RequiredArgsConstructor
|
44 | 16 | @Component
|
45 | 17 | @Slf4j
|
46 | 18 | public class LowcoderPluginManager
|
47 | 19 | {
|
48 |
| - private final LowcoderServices lowcoderServices; |
49 |
| - private final PluginLoader pluginLoader; |
50 |
| - |
51 |
| - private Map<String, LowcoderPlugin> plugins = new LinkedHashMap<>(); |
52 |
| - private List<RouterFunction<ServerResponse>> routes = new ArrayList<>(); |
53 |
| - |
54 |
| - @PostConstruct |
55 |
| - private void loadPlugins() |
56 |
| - { |
57 |
| - registerPlugins(); |
58 |
| - List<LowcoderPlugin> sorted = new ArrayList<>(plugins.values()); |
59 |
| - sorted.sort(Comparator.comparing(LowcoderPlugin::loadOrder)); |
60 |
| - |
61 |
| - for (LowcoderPlugin plugin : sorted) |
62 |
| - { |
63 |
| - if (plugin.load(lowcoderServices)) |
64 |
| - { |
65 |
| - log.info("Plugin [{}] loaded successfully.", plugin.pluginId()); |
66 |
| - registerEndpoints(plugin); |
67 |
| - } |
68 |
| - } |
69 |
| - } |
70 |
| - |
71 |
| - @PreDestroy |
72 |
| - public void unloadPlugins() |
73 |
| - { |
74 |
| - for (LowcoderPlugin plugin : plugins.values()) |
75 |
| - { |
76 |
| - try |
77 |
| - { |
78 |
| - plugin.unload(); |
79 |
| - } |
80 |
| - catch(Throwable cause) |
81 |
| - { |
82 |
| - log.warn("Error unloading plugin: {}!", plugin.pluginId(), cause); |
83 |
| - } |
84 |
| - } |
85 |
| - } |
86 |
| - |
87 |
| - public List<RouterFunction<ServerResponse>> getEndpoints() |
88 |
| - { |
89 |
| - return this.routes; |
90 |
| - } |
91 |
| - |
92 |
| - public List<PluginInfo> getLoadedPluginsInfo() |
93 |
| - { |
94 |
| - List<PluginInfo> infos = new ArrayList<>(); |
95 |
| - for (LowcoderPlugin plugin : plugins.values()) |
96 |
| - { |
97 |
| - infos.add(new PluginInfo(plugin.pluginId(), plugin.description(), plugin.pluginInfo())); |
98 |
| - } |
99 |
| - return infos; |
100 |
| - } |
101 |
| - |
102 |
| - private void registerPlugins() |
103 |
| - { |
104 |
| - List<LowcoderPlugin> loaded = pluginLoader.loadPlugins(); |
105 |
| - if (CollectionUtils.isNotEmpty(loaded)) |
106 |
| - { |
107 |
| - for (LowcoderPlugin plugin : loaded) |
108 |
| - { |
109 |
| - if (!plugins.containsKey(plugin.pluginId())) |
110 |
| - { |
111 |
| - log.info("Registered plugin: {} ({})", plugin.pluginId(), plugin.getClass().getName()); |
112 |
| - plugins.put(plugin.pluginId(), plugin); |
113 |
| - } |
114 |
| - else |
115 |
| - { |
116 |
| - log.warn("Plugin {} already registered (from: {}), skipping {}.", plugin.pluginId(), |
117 |
| - plugins.get(plugin.pluginId()).getClass().getName(), |
118 |
| - plugin.getClass().getName()); |
119 |
| - } |
120 |
| - } |
121 |
| - } |
122 |
| - } |
123 |
| - |
| 20 | + private final ConfigurableApplicationContext applicationContext; |
| 21 | + private final CommonConfig common; |
| 22 | + private final ApplicationHome applicationHome; |
124 | 23 |
|
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 |
| - } |
| 24 | + private Map<String, LowcoderPlugin> plugins; |
173 | 25 |
|
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 | 26 |
|
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) |
| 27 | + @PostConstruct |
| 28 | + private void loadPlugins() |
221 | 29 | {
|
222 |
| - String basePath = "/plugins/" + plugin.pluginId(); |
223 | 30 |
|
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 | 31 | }
|
241 |
| - |
242 |
| - private String pluginEndpointUri(String basePath, String uri) |
243 |
| - { |
244 |
| - return StringUtils.join(basePath, StringUtils.prependIfMissing(uri, "/")); |
245 |
| - } |
246 |
| - |
247 |
| - |
248 |
| - private record PluginInfo( |
249 |
| - String id, |
250 |
| - String description, |
251 |
| - Object info |
252 |
| - ) {} |
253 | 32 |
|
254 | 33 | }
|
0 commit comments