Skip to main content
Jump to: navigation, search

Difference between revisions of "Efxclipse/Runtime/Recipes"

(Adapter System)
(e4)
Line 443: Line 443:
  
 
= e4 =
 
= e4 =
 +
 +
== Keybinding vs Native implementation ==
 +
 +
== Theme specific images in e4xmi ==

Revision as of 18:06, 26 May 2014


This page holds best practice recipes when writing JavaFX application using e(fx)clipse

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;
}

@Adapt

Sometimes a generic container objects like an IStructuredSelection are pushed into the context but in your application you want to get a concrete type. So generic type conversion can be implemented using the AdapterService introduced below. To make direct use of the adapters as part of the the DI-Process you can use the @org.eclipse.fx.core.adapter.Adapt annotation who will try to convert the value in the context using the the AdapterService.

For the example. Let's suppose there's an adapter who is able to convert from a String to an Integer you could use DI in the following way

import org.eclipse.fx.core.adapter.Adapt;
 
@Inject
@Adapt
@Named("myValue")
Integer myValueAsInteger;

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>

e4

Keybinding vs Native implementation

Theme specific images in e4xmi

Back to the top