2121import com .arcadedb .ContextConfiguration ;
2222import com .arcadedb .GlobalConfiguration ;
2323import com .arcadedb .server .ArcadeDBServer ;
24+ import com .arcadedb .server .ServerException ;
2425import com .arcadedb .server .ServerPlugin ;
26+ import com .arcadedb .server .http .HttpServer ;
27+ import io .undertow .server .handlers .PathHandler ;
2528import org .junit .jupiter .api .AfterEach ;
2629import org .junit .jupiter .api .BeforeEach ;
2730import org .junit .jupiter .api .Test ;
31+ import org .junit .jupiter .api .io .TempDir ;
2832
29- import java .io .File ;
33+ import java .io .*;
34+ import java .nio .file .Files ;
35+ import java .nio .file .Path ;
3036import java .util .Collection ;
37+ import java .util .Set ;
38+ import java .util .concurrent .atomic .AtomicBoolean ;
39+ import java .util .concurrent .atomic .AtomicInteger ;
40+ import java .util .jar .JarEntry ;
41+ import java .util .jar .JarOutputStream ;
3142
3243import static org .junit .jupiter .api .Assertions .*;
3344
@@ -40,18 +51,24 @@ public class PluginManagerTest {
4051 private ArcadeDBServer server ;
4152 private PluginManager pluginManager ;
4253
54+ @ TempDir
55+ Path tempDir ;
56+
4357 @ BeforeEach
4458 public void setup () {
4559 final ContextConfiguration configuration = new ContextConfiguration ();
46- configuration .setValue (GlobalConfiguration .SERVER_ROOT_PATH , "./target/test-server" );
47- configuration .setValue (GlobalConfiguration .SERVER_DATABASE_DIRECTORY , "./target/test-server/ databases" );
60+ configuration .setValue (GlobalConfiguration .SERVER_ROOT_PATH , tempDir . toString () );
61+ configuration .setValue (GlobalConfiguration .SERVER_DATABASE_DIRECTORY , tempDir . resolve ( " databases"). toString () );
4862
4963 server = new ArcadeDBServer (configuration );
5064 pluginManager = new PluginManager (server , configuration );
5165 }
5266
5367 @ AfterEach
5468 public void teardown () {
69+ if (pluginManager != null ) {
70+ pluginManager .stopPlugins ();
71+ }
5572 if (server != null && server .isStarted ()) {
5673 server .stop ();
5774 }
@@ -89,4 +106,341 @@ public void testStopPluginsWhenEmpty() {
89106 // Should handle stopping with no plugins loaded
90107 assertDoesNotThrow (() -> pluginManager .stopPlugins ());
91108 }
109+
110+ @ Test
111+ public void testDiscoverPluginsWithEmptyDirectory () throws IOException {
112+ // Create empty plugins directory
113+ final Path pluginsDir = tempDir .resolve ("lib/plugins" );
114+ Files .createDirectories (pluginsDir );
115+
116+ pluginManager .discoverPlugins ();
117+ assertEquals (0 , pluginManager .getPluginCount ());
118+ }
119+
120+ @ Test
121+ public void testLoadPluginWithMetaInfServices () throws Exception {
122+ // Create a test plugin JAR with proper META-INF/services
123+ final Path pluginsDir = tempDir .resolve ("lib/plugins" );
124+ Files .createDirectories (pluginsDir );
125+
126+ final File pluginJar = createTestPluginJar (pluginsDir , "test-plugin" , TestPlugin1 .class );
127+
128+ pluginManager .discoverPlugins ();
129+
130+ assertEquals (1 , pluginManager .getPluginCount ());
131+ assertTrue (pluginManager .getPluginNames ().contains ("test-plugin" ));
132+
133+ final Collection <ServerPlugin > plugins = pluginManager .getPlugins ();
134+ assertEquals (1 , plugins .size ());
135+ }
136+
137+ @ Test
138+ public void testLoadMultiplePlugins () throws Exception {
139+ final Path pluginsDir = tempDir .resolve ("lib/plugins" );
140+ Files .createDirectories (pluginsDir );
141+
142+ createTestPluginJar (pluginsDir , "plugin1" , TestPlugin1 .class );
143+ createTestPluginJar (pluginsDir , "plugin2" , TestPlugin2 .class );
144+
145+ pluginManager .discoverPlugins ();
146+
147+ assertEquals (2 , pluginManager .getPluginCount ());
148+ final Set <String > names = pluginManager .getPluginNames ();
149+ assertTrue (names .contains ("plugin1" ));
150+ assertTrue (names .contains ("plugin2" ));
151+ }
152+
153+ @ Test
154+ public void testPluginLifecycle () throws Exception {
155+ final Path pluginsDir = tempDir .resolve ("lib/plugins" );
156+ Files .createDirectories (pluginsDir );
157+
158+ createTestPluginJar (pluginsDir , "lifecycle-plugin" , LifecycleTestPlugin .class );
159+
160+ pluginManager .discoverPlugins ();
161+ assertEquals (1 , pluginManager .getPluginCount ());
162+
163+ // Start the plugin
164+ pluginManager .startPlugins (ServerPlugin .INSTALLATION_PRIORITY .BEFORE_HTTP_ON );
165+
166+ // Verify plugin was configured and started
167+ final PluginDescriptor descriptor = pluginManager .getPluginDescriptor ("lifecycle-plugin" );
168+ assertNotNull (descriptor );
169+ assertTrue (descriptor .isStarted ());
170+ assertTrue (descriptor .getPluginInstance () instanceof LifecycleTestPlugin );
171+
172+ final LifecycleTestPlugin plugin = (LifecycleTestPlugin ) descriptor .getPluginInstance ();
173+ assertTrue (plugin .configured .get ());
174+ assertTrue (plugin .started .get ());
175+ assertFalse (plugin .stopped .get ());
176+
177+ // Stop the plugin
178+ pluginManager .stopPlugins ();
179+ assertTrue (plugin .stopped .get ());
180+ assertFalse (descriptor .isStarted ());
181+ }
182+
183+ @ Test
184+ public void testPluginStartOrderByPriority () throws Exception {
185+ final Path pluginsDir = tempDir .resolve ("lib/plugins" );
186+ Files .createDirectories (pluginsDir );
187+
188+ createTestPluginJar (pluginsDir , "before-plugin" , BeforeHttpPlugin .class );
189+ createTestPluginJar (pluginsDir , "after-plugin" , AfterHttpPlugin .class );
190+
191+ pluginManager .discoverPlugins ();
192+ assertEquals (2 , pluginManager .getPluginCount ());
193+
194+ // Start BEFORE_HTTP_ON plugins
195+ pluginManager .startPlugins (ServerPlugin .INSTALLATION_PRIORITY .BEFORE_HTTP_ON );
196+
197+ PluginDescriptor beforeDesc = pluginManager .getPluginDescriptor ("before-plugin" );
198+ PluginDescriptor afterDesc = pluginManager .getPluginDescriptor ("after-plugin" );
199+
200+ assertTrue (beforeDesc .isStarted ());
201+ assertFalse (afterDesc .isStarted ());
202+
203+ // Start AFTER_HTTP_ON plugins
204+ pluginManager .startPlugins (ServerPlugin .INSTALLATION_PRIORITY .AFTER_HTTP_ON );
205+
206+ assertTrue (beforeDesc .isStarted ());
207+ assertTrue (afterDesc .isStarted ());
208+ }
209+
210+ @ Test
211+ public void testPluginWithoutMetaInfServices () throws Exception {
212+ final Path pluginsDir = tempDir .resolve ("lib/plugins" );
213+ Files .createDirectories (pluginsDir );
214+
215+ // Create JAR without META-INF/services
216+ final File pluginJar = pluginsDir .resolve ("invalid-plugin.jar" ).toFile ();
217+ try (JarOutputStream jos = new JarOutputStream (new FileOutputStream (pluginJar ))) {
218+ // Just create an empty JAR
219+ jos .putNextEntry (new JarEntry ("dummy.txt" ));
220+ jos .write ("test" .getBytes ());
221+ jos .closeEntry ();
222+ }
223+
224+ pluginManager .discoverPlugins ();
225+
226+ // Plugin should not be loaded due to missing META-INF/services
227+ assertEquals (0 , pluginManager .getPluginCount ());
228+ }
229+
230+ @ Test
231+ public void testPluginStartException () throws Exception {
232+ final Path pluginsDir = tempDir .resolve ("lib/plugins" );
233+ Files .createDirectories (pluginsDir );
234+
235+ createTestPluginJar (pluginsDir , "failing-plugin" , FailingPlugin .class );
236+
237+ pluginManager .discoverPlugins ();
238+ assertEquals (1 , pluginManager .getPluginCount ());
239+
240+ // Starting the plugin should throw exception
241+ assertThrows (ServerException .class , () ->
242+ pluginManager .startPlugins (ServerPlugin .INSTALLATION_PRIORITY .BEFORE_HTTP_ON ));
243+ }
244+
245+ @ Test
246+ public void testGetPluginDescriptor () throws Exception {
247+ final Path pluginsDir = tempDir .resolve ("lib/plugins" );
248+ Files .createDirectories (pluginsDir );
249+
250+ createTestPluginJar (pluginsDir , "test-plugin" , TestPlugin1 .class );
251+
252+ pluginManager .discoverPlugins ();
253+
254+ final PluginDescriptor descriptor = pluginManager .getPluginDescriptor ("test-plugin" );
255+ assertNotNull (descriptor );
256+ assertEquals ("test-plugin" , descriptor .getPluginName ());
257+ assertNotNull (descriptor .getPluginJarFile ());
258+ assertNotNull (descriptor .getClassLoader ());
259+ assertNotNull (descriptor .getPluginInstance ());
260+ assertFalse (descriptor .isStarted ());
261+ }
262+
263+ @ Test
264+ public void testClassLoaderIsolation () throws Exception {
265+ final Path pluginsDir = tempDir .resolve ("lib/plugins" );
266+ Files .createDirectories (pluginsDir );
267+
268+ createTestPluginJar (pluginsDir , "plugin1" , TestPlugin1 .class );
269+ createTestPluginJar (pluginsDir , "plugin2" , TestPlugin2 .class );
270+
271+ pluginManager .discoverPlugins ();
272+
273+ final PluginDescriptor desc1 = pluginManager .getPluginDescriptor ("plugin1" );
274+ final PluginDescriptor desc2 = pluginManager .getPluginDescriptor ("plugin2" );
275+
276+ // Each plugin should have its own class loader
277+ assertNotNull (desc1 .getClassLoader ());
278+ assertNotNull (desc2 .getClassLoader ());
279+ assertNotSame (desc1 .getClassLoader (), desc2 .getClassLoader ());
280+
281+ // Both should be PluginClassLoader instances
282+ assertTrue (desc1 .getClassLoader () instanceof PluginClassLoader );
283+ assertTrue (desc2 .getClassLoader () instanceof PluginClassLoader );
284+ }
285+
286+ @ Test
287+ public void testStopPluginsReverseOrder () throws Exception {
288+ final Path pluginsDir = tempDir .resolve ("lib/plugins" );
289+ Files .createDirectories (pluginsDir );
290+
291+ createTestPluginJar (pluginsDir , "plugin1" , OrderTestPlugin1 .class );
292+ createTestPluginJar (pluginsDir , "plugin2" , OrderTestPlugin2 .class );
293+ createTestPluginJar (pluginsDir , "plugin3" , OrderTestPlugin3 .class );
294+
295+ OrderTestPlugin1 .stopOrder .set (0 );
296+ OrderTestPlugin2 .stopOrder .set (0 );
297+ OrderTestPlugin3 .stopOrder .set (0 );
298+ OrderTestPlugin1 .stopCounter .set (0 );
299+ OrderTestPlugin2 .stopCounter .set (0 );
300+ OrderTestPlugin3 .stopCounter .set (0 );
301+
302+ pluginManager .discoverPlugins ();
303+ pluginManager .startPlugins (ServerPlugin .INSTALLATION_PRIORITY .BEFORE_HTTP_ON );
304+
305+ // Stop plugins - should be in reverse order of discovery
306+ pluginManager .stopPlugins ();
307+
308+ // Verify plugins were stopped in reverse order
309+ assertTrue (OrderTestPlugin3 .stopOrder .get () < OrderTestPlugin2 .stopOrder .get ());
310+ assertTrue (OrderTestPlugin2 .stopOrder .get () < OrderTestPlugin1 .stopOrder .get ());
311+ }
312+
313+ /**
314+ * Helper method to create a test plugin JAR with proper META-INF/services
315+ */
316+ private File createTestPluginJar (final Path pluginsDir , final String pluginName , final Class <? extends ServerPlugin > pluginClass )
317+ throws Exception {
318+ final File jarFile = pluginsDir .resolve (pluginName + ".jar" ).toFile ();
319+
320+ try (JarOutputStream jos = new JarOutputStream (new FileOutputStream (jarFile ))) {
321+ // Add the plugin class
322+ final String classFileName = pluginClass .getName ().replace ('.' , '/' ) + ".class" ;
323+ jos .putNextEntry (new JarEntry (classFileName ));
324+
325+ // Load class bytes from current classloader
326+ try (InputStream is = getClass ().getClassLoader ().getResourceAsStream (classFileName )) {
327+ if (is != null ) {
328+ is .transferTo (jos );
329+ }
330+ }
331+ jos .closeEntry ();
332+
333+ // Add META-INF/services/com.arcadedb.server.ServerPlugin
334+ jos .putNextEntry (new JarEntry ("META-INF/services/com.arcadedb.server.ServerPlugin" ));
335+ jos .write (pluginClass .getName ().getBytes ());
336+ jos .closeEntry ();
337+ }
338+
339+ return jarFile ;
340+ }
341+
342+ // Test plugin implementations
343+ public static class TestPlugin1 implements ServerPlugin {
344+ @ Override
345+ public void startService () {
346+ }
347+ }
348+
349+ public static class TestPlugin2 implements ServerPlugin {
350+ @ Override
351+ public void startService () {
352+ }
353+ }
354+
355+ public static class LifecycleTestPlugin implements ServerPlugin {
356+ public final AtomicBoolean configured = new AtomicBoolean (false );
357+ public final AtomicBoolean started = new AtomicBoolean (false );
358+ public final AtomicBoolean stopped = new AtomicBoolean (false );
359+
360+ @ Override
361+ public void configure (ArcadeDBServer arcadeDBServer , ContextConfiguration configuration ) {
362+ configured .set (true );
363+ }
364+
365+ @ Override
366+ public void startService () {
367+ started .set (true );
368+ }
369+
370+ @ Override
371+ public void stopService () {
372+ stopped .set (true );
373+ }
374+ }
375+
376+ public static class BeforeHttpPlugin implements ServerPlugin {
377+ @ Override
378+ public void startService () {
379+ }
380+
381+ @ Override
382+ public INSTALLATION_PRIORITY getInstallationPriority () {
383+ return INSTALLATION_PRIORITY .BEFORE_HTTP_ON ;
384+ }
385+ }
386+
387+ public static class AfterHttpPlugin implements ServerPlugin {
388+ @ Override
389+ public void startService () {
390+ }
391+
392+ @ Override
393+ public INSTALLATION_PRIORITY getInstallationPriority () {
394+ return INSTALLATION_PRIORITY .AFTER_HTTP_ON ;
395+ }
396+ }
397+
398+ public static class FailingPlugin implements ServerPlugin {
399+ @ Override
400+ public void startService () {
401+ throw new RuntimeException ("Plugin failed to start" );
402+ }
403+ }
404+
405+ public static class OrderTestPlugin1 implements ServerPlugin {
406+ public static final AtomicInteger stopCounter = new AtomicInteger (0 );
407+ public static final AtomicInteger stopOrder = new AtomicInteger (0 );
408+
409+ @ Override
410+ public void startService () {
411+ }
412+
413+ @ Override
414+ public void stopService () {
415+ stopOrder .set (stopCounter .incrementAndGet ());
416+ }
417+ }
418+
419+ public static class OrderTestPlugin2 implements ServerPlugin {
420+ public static final AtomicInteger stopCounter = new AtomicInteger (0 );
421+ public static final AtomicInteger stopOrder = new AtomicInteger (0 );
422+
423+ @ Override
424+ public void startService () {
425+ }
426+
427+ @ Override
428+ public void stopService () {
429+ stopOrder .set (stopCounter .incrementAndGet ());
430+ }
431+ }
432+
433+ public static class OrderTestPlugin3 implements ServerPlugin {
434+ public static final AtomicInteger stopCounter = new AtomicInteger (0 );
435+ public static final AtomicInteger stopOrder = new AtomicInteger (0 );
436+
437+ @ Override
438+ public void startService () {
439+ }
440+
441+ @ Override
442+ public void stopService () {
443+ stopOrder .set (stopCounter .incrementAndGet ());
444+ }
445+ }
92446}
0 commit comments