|
1 | 1 | packageorg.lowcoder.api.framework.plugin;
|
2 | 2 |
|
| 3 | +importstaticorg.springframework.web.reactive.function.server.RequestPredicates.DELETE; |
| 4 | +importstaticorg.springframework.web.reactive.function.server.RequestPredicates.GET; |
| 5 | +importstaticorg.springframework.web.reactive.function.server.RequestPredicates.OPTIONS; |
| 6 | +importstaticorg.springframework.web.reactive.function.server.RequestPredicates.PATCH; |
| 7 | +importstaticorg.springframework.web.reactive.function.server.RequestPredicates.POST; |
| 8 | +importstaticorg.springframework.web.reactive.function.server.RequestPredicates.PUT; |
| 9 | +importstaticorg.springframework.web.reactive.function.server.RouterFunctions.route; |
| 10 | + |
| 11 | +importjava.lang.reflect.InvocationTargetException; |
| 12 | +importjava.lang.reflect.Method; |
| 13 | +importjava.util.ArrayList; |
| 14 | +importjava.util.Comparator; |
| 15 | +importjava.util.LinkedHashMap; |
| 16 | +importjava.util.List; |
3 | 17 | importjava.util.Map;
|
4 | 18 |
|
| 19 | +importorg.apache.commons.collections4.CollectionUtils; |
| 20 | +importorg.apache.commons.lang3.StringUtils; |
| 21 | +importorg.lowcoder.plugin.EndpointExtension; |
5 | 22 | importorg.lowcoder.plugin.LowcoderPlugin;
|
6 |
| -importorg.lowcoder.sdk.config.CommonConfig; |
7 |
| -importorg.springframework.boot.system.ApplicationHome; |
8 |
| -importorg.springframework.context.ConfigurableApplicationContext; |
| 23 | +importorg.lowcoder.plugin.PluginEndpoint; |
| 24 | +importorg.lowcoder.sdk.exception.BaseException; |
| 25 | +importorg.springframework.context.ApplicationContext; |
| 26 | +importorg.springframework.core.ResolvableType; |
9 | 27 | importorg.springframework.stereotype.Component;
|
| 28 | +importorg.springframework.web.reactive.function.server.RequestPredicate; |
| 29 | +importorg.springframework.web.reactive.function.server.RouterFunction; |
| 30 | +importorg.springframework.web.reactive.function.server.ServerRequest; |
| 31 | +importorg.springframework.web.reactive.function.server.ServerResponse; |
10 | 32 |
|
11 | 33 | importjakarta.annotation.PostConstruct;
|
| 34 | +importjakarta.annotation.PreDestroy; |
12 | 35 | importlombok.RequiredArgsConstructor;
|
13 | 36 | importlombok.extern.slf4j.Slf4j;
|
| 37 | +importreactor.core.publisher.Mono; |
14 | 38 |
|
15 | 39 | @RequiredArgsConstructor
|
16 | 40 | @Component
|
17 | 41 | @Slf4j
|
18 | 42 | publicclassLowcoderPluginManager
|
19 | 43 | {
|
20 |
| -privatefinalConfigurableApplicationContextapplicationContext; |
21 |
| -privatefinalCommonConfigcommon; |
22 |
| -privatefinalApplicationHomeapplicationHome; |
23 |
| - |
24 |
| -privateMap<String,LowcoderPlugin>plugins; |
25 |
| - |
| 44 | +privatefinalApplicationContextapplicationContext; |
| 45 | +privatefinalPluginLoaderpluginLoader; |
26 | 46 |
|
| 47 | +privateMap<String,LowcoderPlugin>plugins =newLinkedHashMap<>(); |
| 48 | +privateList<RouterFunction<ServerResponse>>routes =newArrayList<>(); |
| 49 | + |
27 | 50 | @PostConstruct
|
28 | 51 | privatevoidloadPlugins()
|
29 | 52 | {
|
| 53 | +registerPlugins(); |
| 54 | +List<LowcoderPlugin>sorted =newArrayList<>(plugins.values()); |
| 55 | +sorted.sort(Comparator.comparing(LowcoderPlugin::loadOrder)); |
| 56 | + |
| 57 | +for (LowcoderPluginplugin :sorted) |
| 58 | +{ |
| 59 | +if (plugin.load(applicationContext)) |
| 60 | +{ |
| 61 | +log.info("Plugin [{}] loaded successfully.",plugin.pluginId()); |
| 62 | +registerEndpoints(plugin); |
| 63 | +} |
| 64 | +} |
| 65 | +} |
| 66 | + |
| 67 | +@PreDestroy |
| 68 | +publicvoidunloadPlugins() |
| 69 | +{ |
| 70 | +for (LowcoderPluginplugin :plugins.values()) |
| 71 | +{ |
| 72 | +try |
| 73 | +{ |
| 74 | +plugin.unload(); |
| 75 | +} |
| 76 | +catch(Throwablecause) |
| 77 | +{ |
| 78 | +log.warn("Error unloading plugin: {}!",plugin.pluginId(),cause); |
| 79 | +} |
| 80 | +} |
| 81 | +} |
| 82 | + |
| 83 | +publicList<RouterFunction<ServerResponse>>getEndpoints() |
| 84 | +{ |
| 85 | +returnthis.routes; |
| 86 | +} |
| 87 | + |
| 88 | +publicList<PluginInfo>getLoadedPluginsInfo() |
| 89 | +{ |
| 90 | +List<PluginInfo>infos =newArrayList<>(); |
| 91 | +for (LowcoderPluginplugin :plugins.values()) |
| 92 | +{ |
| 93 | +infos.add(newPluginInfo(plugin.pluginId(),plugin.description(),plugin.pluginInfo())); |
| 94 | +} |
| 95 | +returninfos; |
| 96 | +} |
| 97 | + |
| 98 | +privatevoidregisterPlugins() |
| 99 | +{ |
| 100 | +List<LowcoderPlugin>loaded =pluginLoader.loadPlugins(); |
| 101 | +if (CollectionUtils.isNotEmpty(loaded)) |
| 102 | +{ |
| 103 | +for (LowcoderPluginplugin :loaded) |
| 104 | +{ |
| 105 | +if (!plugins.containsKey(plugin.pluginId())) |
| 106 | +{ |
| 107 | +log.info("Registered plugin: {} ({})",plugin.pluginId(),plugin.getClass().getName()); |
| 108 | +plugins.put(plugin.pluginId(),plugin); |
| 109 | +} |
| 110 | +else |
| 111 | +{ |
| 112 | +log.warn("Plugin {} already registered (from: {}), skipping {}.",plugin.pluginId(), |
| 113 | +plugins.get(plugin.pluginId()).getClass().getName(), |
| 114 | +plugin.getClass().getName()); |
| 115 | +} |
| 116 | +} |
| 117 | +} |
| 118 | +} |
| 119 | + |
| 120 | + |
| 121 | +privatevoidregisterEndpoints(LowcoderPluginplugin) |
| 122 | +{ |
| 123 | +if (CollectionUtils.isNotEmpty(plugin.endpoints())) |
| 124 | +{ |
| 125 | +for (PluginEndpointendpoint :plugin.endpoints()) |
| 126 | +{ |
| 127 | +Method[]handlers =endpoint.getClass().getDeclaredMethods(); |
| 128 | +if (handlers !=null &&handlers.length >0) |
| 129 | +{ |
| 130 | +for (Methodhandler :handlers) |
| 131 | +{ |
| 132 | +registerEndpointHandler(plugin,endpoint,handler); |
| 133 | +} |
| 134 | +} |
| 135 | +} |
| 136 | +} |
| 137 | +} |
| 138 | + |
| 139 | +@SuppressWarnings("unchecked") |
| 140 | +privatevoidregisterEndpointHandler(LowcoderPluginplugin,PluginEndpointendpoint,Methodhandler) |
| 141 | +{ |
| 142 | +if (handler.isAnnotationPresent(EndpointExtension.class)) |
| 143 | +{ |
| 144 | +if (checkHandlerMethod(handler)) |
| 145 | +{ |
| 146 | + |
| 147 | +EndpointExtensionendpointMeta =handler.getAnnotation(EndpointExtension.class); |
| 148 | +routes.add(route(createRequestPredicate(plugin,endpointMeta),req -> { |
| 149 | +Mono<ServerResponse>result =null; |
| 150 | +try |
| 151 | +{ |
| 152 | +result = (Mono<ServerResponse>)handler.invoke(endpoint,req); |
| 153 | +} |
| 154 | +catch (IllegalAccessException |InvocationTargetExceptioncause) |
| 155 | +{ |
| 156 | +thrownewBaseException("Error running handler for [ " +endpointMeta.method() +": " +endpointMeta.uri() +"] !"); |
| 157 | +} |
| 158 | +returnresult; |
| 159 | +}) |
| 160 | +); |
| 161 | +log.info("Registered plugin endpoint: {} -> {} -> {}: {}",plugin.pluginId(),endpoint.getClass().getSimpleName(),endpointMeta.method(),endpointMeta.uri()); |
| 162 | +} |
| 163 | +else |
| 164 | +{ |
| 165 | +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()); |
| 166 | +} |
| 167 | +} |
| 168 | +} |
| 169 | + |
| 170 | + |
| 171 | +privatebooleancheckHandlerMethod(Methodmethod) |
| 172 | +{ |
| 173 | +ResolvableTypereturnType =ResolvableType.forMethodReturnType(method); |
30 | 174 |
|
| 175 | +return (returnType.isAssignableFrom(Mono.class) |
| 176 | +&&returnType.getGenerics().length ==1 |
| 177 | +&&returnType.getGeneric(0).isAssignableFrom(ServerResponse.class) |
| 178 | +&&method.getParameterCount() ==1 |
| 179 | +&&method.getParameterTypes()[0].isAssignableFrom(ServerRequest.class) |
| 180 | +); |
31 | 181 | }
|
32 | 182 |
|
| 183 | +privateRequestPredicatecreateRequestPredicate(LowcoderPluginplugin,EndpointExtensionendpoint) |
| 184 | +{ |
| 185 | +StringbasePath ="/plugins/" +plugin.pluginId(); |
| 186 | + |
| 187 | +switch(endpoint.method()) |
| 188 | +{ |
| 189 | +caseGET: |
| 190 | +returnGET(pluginEndpointUri(basePath,endpoint.uri())); |
| 191 | +casePOST: |
| 192 | +returnPOST(pluginEndpointUri(basePath,endpoint.uri())); |
| 193 | +casePUT: |
| 194 | +returnPUT(pluginEndpointUri(basePath,endpoint.uri())); |
| 195 | +casePATCH: |
| 196 | +returnPATCH(pluginEndpointUri(basePath,endpoint.uri())); |
| 197 | +caseDELETE: |
| 198 | +returnDELETE(pluginEndpointUri(basePath,endpoint.uri())); |
| 199 | +caseOPTIONS: |
| 200 | +returnOPTIONS(pluginEndpointUri(basePath,endpoint.uri())); |
| 201 | +} |
| 202 | +returnnull; |
| 203 | +} |
| 204 | + |
| 205 | +privateStringpluginEndpointUri(StringbasePath,Stringuri) |
| 206 | +{ |
| 207 | +returnStringUtils.join(basePath,StringUtils.prependIfMissing(uri,"/")); |
| 208 | +} |
| 209 | + |
| 210 | + |
| 211 | +privaterecordPluginInfo( |
| 212 | +Stringid, |
| 213 | +Stringdescription, |
| 214 | +Objectinfo |
| 215 | +) {} |
| 216 | + |
33 | 217 | }
|