Efxclipse/Runtime/Recipes
This page holds best practice recipes when writing JavaFX application using e(fx)clipse
Contents
Logging
e(fx)clipse has its own logging facade org.eclipse.fx.core.log.Logger
which allows to plug-in different log frameworks.
Currently available are:
-
java.util.Logging
(default) - log4j by adding
org.eclipse.fx.core.log4j
bundle to your OSGi-Runtime
Usage
There are different ways to use get a logger.
LoggerCreator
If you are running on OSGi you can add the org.eclipse.fx.osgi.util
bundle which provides access to the LoggerCreator
factory class
import org.eclipse.fx.core.log.Logger; import org.eclipse.fx.osgi.util.LoggerCreator; public class MyClass { private static Logger LOGGER = LoggerCreator.createLogger(MyClass.class); // .... }
LoggerFactory Service
The different logger bundles contribute their LoggerFactory
implementation into the OSGi-Registry. In case you are e.g. contributing a service via DS you can get simple add a service reference and you'll get the LoggerFactory
with the highest rank injected.
import org.eclipse.fx.core.log.LoggerFactory; import org.eclipse.fx.core.log.Logger; public class MyServiceImpl implements MyService { private Logger logger; public void setLoggerFactory(LoggerFactory factory) { this.logger = factory.createLogger(MyService.class.getName()); } public void unsetLoggerFactory(LoggerFactory factory) { } }
<?xml version="1.0" encoding="UTF-8"?> <scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="myservice"> <implementation class="impl.MyServiceImpl"/> <service> <provide interface="service.MyService"/> </service> <reference bind="setLoggerFactory" cardinality="1..1" interface="org.eclipse.fx.core.log.LoggerFactory" name="LoggerFactory" policy="static" unbind="unsetLoggerFactory"/> </scr:component>
Eclipse DI & @Log annotation
If you make use of Eclipse DI in your code you can get a Logger
instance injected with:
import org.eclipse.fx.core.log.Log; import org.eclipse.fx.core.log.Logger; import javax.inject.Inject; public class MyDIComponent { @Inject @Log Logger logger; }
Google Guice & @Log annotation
If you use Guice as the DI container you use the org.eclipse.fx.core.guice
bundle to get a Logger injected in your component with:
import org.eclipse.fx.core.log.Log; import org.eclipse.fx.core.log.Logger; import javax.inject.Inject; public class MyDIComponent { @Log Logger logger; }
if you have configured your Guice-Module with:
import com.google.inject.Module; import com.google.inject.Binder; import org.eclipse.fx.core.log.LoggerFactory; import org.eclipse.fx.core.log4j.Log4JLoggerFactory; import org.eclipse.fx.core.guice.FXLoggerListener; public class MyModule implements Module { public void configure(Binder binder) { binder.bind(LoggerFactory.class).toProvider(Log4JLoggerFactory.class); // or JUtilLoggerFactory binder.bindListener(Matchers.any(), new FXLoggerListener()); } }
Instead of directly binding to a logger factory you can delegate to the OSGi-Service registry by using OSGiLoggerFactoryProvider
:
import com.google.inject.Module; import com.google.inject.Binder; import org.eclipse.fx.core.log.LoggerFactory; import org.eclipse.fx.core.guice.FXLoggerListener; import org.eclipse.fx.core.guice.OSGiLoggerFactoryProvider; public class MyModule implements Module { public void configure(Binder binder) { binder.bind(LoggerFactory.class).toProvider(OSGiLoggerFactoryProvider.class); binder.bindListener(Matchers.any(), new FXLoggerListener()); } }
Extending
Like outlined above there are 2 logger implementations available from the e(fx)clipse p2 repository. If you want to use a different logging framework you are able to plug-in your own by implementing LoggerFactory
and contributing it to the OSGi-Service-Registry.
import javax.inject.Provider; import org.eclipse.fx.core.log.LoggerFactory; import org.eclipse.fx.core.log.Logger; public class MyLoggerFactory implements LoggerFactory, Provider<LoggerFactory> { @Override public LoggerFactory get() { return this; } @Override public Logger createLogger(String name) { return new LoggerImpl(name); } static class LoggerImpl implements Logger { private String name; public LoggerImpl(String name) { this.name = name; } // .... } }
You contribute it to the OSGi-Service registry e.g. by using DS. You should give the service a higher ranking than 1 (which is the ranking of the log4j service) to ensure it is picked when a logger is requested.
<?xml version="1.0" encoding="UTF-8"?> <scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="my.logger.framework.factory"> <implementation class="my.logger.framework.MyLoggerFactory"/> <property name="service.ranking" type="Integer" value="2"/> <service> <provide interface="org.eclipse.fx.core.log.LoggerFactory"/> </service> </scr:component>
Eclipse DI
Publishing to the IEclipseContext
The IEclipseContext
is the central component of the Eclipse DI container. Retrieving values from it is as easy as writing @Inject
in your java class and the DI container will fill it with a value and keep it up-to-date if you used field or method injection.
The opposite - publishing a value into the context - is not as easy because your java component will get a dependency on the DI-Container because it needs to access the IEclipseContext
directly. e(fx)clipse provides you the possibility to get around this architectual problem by defining an annotation named @ContextValue
which marks a slot in IEclipseContext
instance which can be used to observe the value and modified.
import org.eclipse.fx.core.di.ContextBoundValue; import org.eclipse.fx.core.di.ContextValue; import javax.inject.Inject; public static class SimpleInject { @Inject @ContextValue(contextKey="user") public ContextBoundValue<String> value; private void updateValue() { value.publish("tomschindl"); } }
To make use of this your bundle needs to have a dependency on org.eclipse.fx.core.di
and your runtime has to include org.eclipse.fx.core.di.context
.
Injected value as an observable
A reoccuring pattern when developing with Eclipse Databinding and Dependency Injection is that the injected value is used as the master in master-detail binding scenario.
import org.eclipse.core.databinding.observable.value.IObservableValue; import org.eclipse.core.databinding.observable.value.WritableValue; import org.eclipse.fx.core.di.ContextBoundValue; import org.eclipse.fx.core.di.ContextValue; public class DetailView { private IObservableValue master = new WritableValue(); @Inject public void update(Person p) { master.getRealm().exec(new Runnable() { public void run() { master.setValue(p); } }); } }
The @ContextValue
framework introduce in the recipe above is able to reduce the code to
import org.eclipse.core.databinding.observable.value.IObservableValue; import org.eclipse.fx.core.di.ContextBoundValue; import org.eclipse.fx.core.di.ContextValue; public class DetailView { @Inject @ContextValue("my.domain.Person") IObservableValue master; }
If you prefer the JavaFX observable system you get let the system inject you this type as well
import javafx.beans.property.Property; import org.eclipse.fx.core.di.ContextBoundValue; import org.eclipse.fx.core.di.ContextValue; public class DetailView { @Inject @ContextValue("my.domain.Person") Property<Person> property; }
Generally speaking you can use any type for the value injected by @ContextValue
as long as there's an adapter registered which is able to convert from ContextBoundValue
to it.
An observable value is not a one way street so it also allows publish through it:
public class ListView { @PostConstruct public void initUI(@ContextValue("my.domain.Person") Property<Person> property) { ListView<Person> v = new ListView<>(); // ... property.bind(v.getSelectionModel().selectedItemProperty()); } }
@FXMLLoader
If you want to use DI in the controller attached to an FXML-File you'd normally use an org.eclipse.fx.ui.di.InjectingFXMLLoader
to free you from configuring the loader with information the DI container already knows you can make use of the @org.eclipse.fx.ui.di.FXMLLoader
in your components which provides you an org.eclipse.fx.ui.di.FXMLLoaderFactory
instance.
public class MyLoginView { @PostConstruct void init(BorderPane p, @FXMLLoader FXMLLoaderFactory factory) { GridPane p = factory.loadRequestorRelative("myLoginView.fxml").build(); // ... } }
and now you can use @Inject
in your controller:
public class MyLoginController { @Inject MyLoginService loginService; @FXML TextField username; @FXML TextField password; }
FXML
FXML in OSGi
Loading FXML-Files in OSGi is a bit harder than doing it in a standard java application and one can not use the static FXMLLoader.load()
so we provide an extra class named org.eclipse.fx.osgi.utilOSGiFXMLLoader
public class MyView { @PostConstruct public void init(BorderPane p) { Node n = OSGiFXMLLoader.load(getClass(), "myView.fxml", null, null); } }
DI in FXML controller
Eclipse DI
If you are running in OSGi and with Eclipse DI (e.g. when you are writing an Eclipse 4 Application) you can make use of org.eclipse.fx.ui.di.InjectingFXMLLoader
.
public class MyView { @PostConstruct public void initUI(BorderPane p, IEclipseContext context) { InjectingFXMLLoader<Node> iFXMLLoader = InjectingFXMLLoader.create( context, getClass(), "myView.fxml" ); Node n = iFXMLLoader.load(); // ... } }
The above code is perfectly ok although you can get rid of the direct IEclipseContext
dependency by using the @FXMLLoader annotation.
Google Guice
If you are not running on OSGi and Eclipse DI but develop a standard java application which uses Google Guice as the DI container you can make use of org.eclipse.fx.core.guice.InjectingFXMLLoader
public class MyGuiceComponent { public void init(Injector injector) { Node n = InjectingFXMLLoader.loadFXML(injector,getClass().getResource("myui.fxml")); // ... } }
Adapter System
The @ContextValue
support introduce above makes use of the adapter system provided by e(fx)clipse core runtime bundle (org.eclipse.fx.core
) which constits of the 3 main interfaces:
-
Adaptable
-
AdapterService
-
AdapterProvider
Usage
The simplest usage is if the source-object implements the Adaptable
interface
public class Sample { private void sellProduct(Person person) { Customer customer = person.adapt(Customer.class); // ... } }
If the source-object itself does not implement the Adaptable
interface or you are the one who has to implement a class which implements Adaptable
you have to use the AdapterService
whicn is provided through the OSGi-Service-Registry if the object is managed by Eclipse DI it will look like this:
public class Sample { @Inject AdapterService adapterService; private void sellProduct(Person person) { Customer customer = adapterService.adapt(person,Customer.class); // ... } }
Otherwise you need to query the OSGi-Service-Registry to get access to the AdapterService
.
Extending
To enhance the adapter system it is possible to register AdapterProvider
as OSGi-Services. All you need to do is to implement the AdapterProvider
likes this:
public class CustomerAdapterProvider implements AdapterProvider<Person, Customer> { @Override public Class<Person> getSourceType() { return Person.class; } @Override public Class<Customer> getTargetType() { return Customer.class; } @Override public boolean canAdapt(Person sourceObject, Class<Customer> targetType) { return true; } @Override public Customer adapt(final Person sourceObject, Class<Customer> targetType, ValueAccess... valueAccess) { // ... } }
and register it e.g. through DS with
<?xml version="1.0" encoding="UTF-8"?> <scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="my.adapter.sample.customer"> <implementation class="my.adapter.sample.CustomerAdapterProvider"/> <service> <provide interface="org.eclipse.fx.core.adapter.AdapterProvider"/> </service> </scr:component>