blob: 9bbe4ec6bd577fea909c5b30e5b797ec8c57f975 [file] [log] [blame]
/*
* 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.apache.zest.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.apache.zest.api.injection.scope.Service;
import org.apache.zest.api.injection.scope.Structure;
import org.apache.zest.api.mixin.Mixins;
import org.apache.zest.api.service.ImportedServiceDescriptor;
import org.apache.zest.api.service.ServiceActivation;
import org.apache.zest.api.service.ServiceComposite;
import org.apache.zest.api.service.ServiceFinder;
import org.apache.zest.api.service.ServiceImporter;
import org.apache.zest.api.service.ServiceImporterException;
import org.apache.zest.api.service.ServiceReference;
import org.apache.zest.api.structure.Application;
import org.apache.zest.bootstrap.ApplicationAssembler;
import org.apache.zest.bootstrap.ApplicationAssembly;
import org.apache.zest.bootstrap.ApplicationAssemblyFactory;
import org.apache.zest.bootstrap.Assembler;
import org.apache.zest.bootstrap.AssemblyException;
import org.apache.zest.bootstrap.Energy4Java;
import org.apache.zest.bootstrap.LayerAssembly;
import org.apache.zest.bootstrap.LayerName;
import org.apache.zest.bootstrap.ModuleAssembly;
import org.apache.zest.bootstrap.ModuleName;
import org.apache.zest.functional.Iterables;
import static org.apache.zest.functional.Iterables.first;
import static org.apache.zest.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 + ")";
}
}
}
}