/* * Copyright (c) 2009, Rickard Öberg. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.qi4j.api.common; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.Collections; import java.util.List; import org.junit.Ignore; import org.junit.Test; import org.qi4j.api.injection.scope.Service; import org.qi4j.api.injection.scope.Structure; import org.qi4j.api.mixin.Mixins; import org.qi4j.api.service.ImportedServiceDescriptor; import org.qi4j.api.service.ServiceActivation; import org.qi4j.api.service.ServiceComposite; import org.qi4j.api.service.ServiceFinder; import org.qi4j.api.service.ServiceImporter; import org.qi4j.api.service.ServiceImporterException; import org.qi4j.api.service.ServiceReference; import org.qi4j.api.structure.Application; import org.qi4j.bootstrap.ApplicationAssembler; import org.qi4j.bootstrap.ApplicationAssembly; import org.qi4j.bootstrap.ApplicationAssemblyFactory; import org.qi4j.bootstrap.Assembler; import org.qi4j.bootstrap.AssemblyException; import org.qi4j.bootstrap.Energy4Java; import org.qi4j.bootstrap.LayerAssembly; import org.qi4j.bootstrap.LayerName; import org.qi4j.bootstrap.ModuleAssembly; import org.qi4j.bootstrap.ModuleName; import org.qi4j.functional.Iterables; import static org.qi4j.functional.Iterables.first; import static org.qi4j.functional.Iterables.toArray; /** * Sample of how a plugin architecture could work. * The plugins can use services in the main application and the main * application can use services from the plugins */ public class PluginTest { @Test @Ignore( "Must fix the TODOs below. This example relied on ability to set Service MetaInfo at runtime, it seems it's not possible anymore." ) public void testPlugins() throws Exception { Energy4Java runtime = new Energy4Java(); Application app = runtime.newApplication( new MainApplicationAssembler() ); app.activate(); } // Main application class MainApplicationAssembler implements ApplicationAssembler { public ApplicationAssembly assemble( ApplicationAssemblyFactory applicationFactory ) throws AssemblyException { return applicationFactory.newApplicationAssembly( new Assembler[][][] { { { new PluginAssembler(), new UIAssembler(), } }, { { new ServiceAssembler() } } } ); } } // The UI uses the plugins class UIAssembler implements Assembler { public void assemble( ModuleAssembly module ) throws AssemblyException { module.services( PluginTesterService.class ).instantiateOnStartup(); module.importedServices( Plugin.class ) .importedBy( ServiceFinderImporter.class ) .visibleIn( Visibility.layer ); } } // Get a reference to a plugin and use it @Mixins( PluginTesterService.PluginTesterMixin.class ) interface PluginTesterService extends ServiceActivation, ServiceComposite { class PluginTesterMixin implements ServiceActivation { @Service Plugin plugin; @Service PluginsService plugins; public void activateService() throws Exception { // Use plugin System.out.println( plugin.say( "Hello", "World" ) ); // Restart plugin plugins.passivateService(); // Plugin is now unavailable try { System.out.println( plugin.say( "Hello", "World" ) ); } catch( ServiceImporterException e ) { // Ignore } plugins.activateService(); // Use plugin System.out.println( plugin.say( "Hello", "World" ) ); } public void passivateService() throws Exception { } } } // Assemble the base service that the plugin can use class ServiceAssembler implements Assembler { public void assemble( ModuleAssembly module ) throws AssemblyException { module.services( HelloWorldService.class ).visibleIn( Visibility.application ); } } // Plugins can access service instances with this interface interface HelloWorld { String say( String phrase, String name ); } // Implementation of service that the plugin can use @Mixins( HelloWorldService.HelloWorldMixin.class ) interface HelloWorldService extends HelloWorld, ServiceComposite { class HelloWorldMixin implements HelloWorld { public String say( String phrase, String name ) { return phrase + " " + name; } } } // Assemble the plugins module class PluginAssembler implements Assembler { public void assemble( ModuleAssembly module ) throws AssemblyException { module.services( PluginsService.class ).instantiateOnStartup(); } } // Plugin service // This assembles and activates a separate application for the plugins // The plugins can look up services in the host application and the // plugins can be looked up by the host application @Mixins( PluginsService.PluginsMixin.class ) interface PluginsService extends ServiceComposite, ServiceActivation { class PluginsMixin implements ServiceActivation { @Structure ServiceFinder finder; @Service ServiceReference<Plugin> plugin; private Application app; public void activateService() throws Exception { Energy4Java runtime = new Energy4Java(); app = runtime.newApplication( new PluginApplicationAssembler( finder ) ); app.activate(); ServiceFinder pluginFinder = app.findModule( "Plugin layer", "Plugin module" ); // TODO: Niclas wrote: No clue how all this Test is supposed to work, and can't figure out to create a workaround for this. // finder.findService(Plugin.class).metaInfo().add(ServiceFinder.class, pluginFinder); } public void passivateService() throws Exception { // TODO: Niclas wrote: No clue how all this Test is supposed to work, and can't figure out to create a workaround for this. // plugin.metaInfo().remove(ServiceFinder.class); app.passivate(); } } } // Assemble the plugin application public static class PluginApplicationAssembler implements ApplicationAssembler { private ServiceFinder finder; public PluginApplicationAssembler( ServiceFinder finder ) { this.finder = finder; } public ApplicationAssembly assemble( ApplicationAssemblyFactory applicationFactory ) throws AssemblyException { return applicationFactory.newApplicationAssembly( new Assembler() { public void assemble( ModuleAssembly module ) throws AssemblyException { new LayerName( "Plugin layer" ).assemble( module ); new ModuleName( "Plugin module" ).assemble( module ); LayerAssembly layer = module.layer(); // In a real case you would "detect" the plugins somehow. Here the plugin assembler is hardcoded List<Assembler> pluginAssemblers = Collections.<Assembler>singletonList( new SimonAssembler() ); for( int i = 0; i < pluginAssemblers.size(); i++ ) { ModuleAssembly pluginModule = layer.module( "Plugin " + ( i + 1 ) ); Assembler assembler = pluginAssemblers.get( i ); assembler.assemble( pluginModule ); } // Import host services module.importedServices( HelloWorld.class ) .importedBy( ServiceFinderImporter.class ) .setMetaInfo( finder ) .visibleIn( Visibility.layer ); } } ); } } // Service importer that uses a ServiceFinder public static class ServiceFinderImporter implements ServiceImporter { public Object importService( final ImportedServiceDescriptor serviceDescriptor ) throws ServiceImporterException { final Class<?> mainType = first( serviceDescriptor.types() ); Class[] interfaces = toArray( Class.class, Iterables.<Class>cast( serviceDescriptor.types() ) ); return Proxy.newProxyInstance( mainType.getClassLoader(), interfaces, new InvocationHandler() { public Object invoke( Object proxy, Method method, Object[] args ) throws Throwable { ServiceFinder finder = serviceDescriptor.metaInfo( ServiceFinder.class ); if( finder == null ) { throw new ServiceImporterException( "No ServiceFinder specified for imported service " + serviceDescriptor .identity() ); } Object service = finder.findService( mainType ).get(); return method.invoke( service, args ); } } ); } public boolean isAvailable( Object instance ) { return true; } } // The plugin interface. Plugins should implement this, and they can then // be looked up by the host application interface Plugin { String say( String phrase, String name ); } // Assemble a sample plugin public static class SimonAssembler implements Assembler { public void assemble( ModuleAssembly module ) throws AssemblyException { module.services( SimonSaysPlugin.class ).visibleIn( Visibility.layer ); } } // Test implementation of the plugin interface @Mixins( SimonSaysPlugin.SimonSaysMixin.class ) interface SimonSaysPlugin extends Plugin, ServiceComposite { class SimonSaysMixin implements Plugin { @Service HelloWorld helloWorld; private long time; public SimonSaysMixin() { time = System.currentTimeMillis(); } public String say( String phrase, String name ) { return "Simon says:" + helloWorld.say( phrase, name ) + " (plugin started at:" + time + ")"; } } } }