Documentation

Documentation versions (currently viewingVaadin 23)

You are viewing documentation for Vaadin 23. View latest documentation

Using Services with Vaadin

A Vaadin bundle being deployed to an OSGi container may use any functionality provided by the OSGi container via the API. You can’t use Declarative Services directly, because Vaadin components, including navigation targets, aren’t declared as services. However, it’s possible to use services through an OSGi API that allows you to use Declarative Services indirectly.

Using Services with Vaadin Components

OSGi provides a generic API that you can use anywhere to access a service. You can call the API from any class, be it a component, a navigation target, or a helper class.

The following example accesses a SomeService service in a main view:

@Route("")
public class MainView extends Div {
    @Override
    protected void onAttach(AttachEvent attachEvent) {
        BundleContext bundleContext = FrameworkUtil.getBundle(getClass())
                .getBundleContext();
        ServiceReference<SomeService> reference = bundleContext
                .getServiceReference(SomeService.class);
        if (reference == null) {
            // Fallback to alternative action
        } else {
            SomeService service = bundleContext.getService(reference);
            // Do something with the service
        }
    }
}

In this example, the main view is available unconditionally in the application, so the route is registered regardless of the presence of SomeService. Hence, you need to check for the availability of the service.

If the application logic allows you to be sure that the service is always registered at the point when it’s requested, you can omit the fallback. This is the case in the next topic, where you provide a route conditionally, based on the availability of a service that it requires. The view component which has the route can trust that the service is available.

Another case is when a component or view in which you need to access a service doesn’t even exist if the service isn’t available. You can handle this case by using dynamic route registration.

The following example registers a route when the service becomes available, by using some boilerplate code to track the service:

@org.osgi.service.component.annotations.Component(service = Servlet.class)
@HttpWhiteboardServletAsyncSupported
@HttpWhiteboardServletPattern("/*")
public class FixedVaadinServlet extends OSGiVaadinServlet {

    private ServiceTracker<SomeService, SomeService> tracker;

    @Override
    protected void servletInitialized() throws ServletException {
        getService().setClassLoader(getClass().getClassLoader());

        VaadinServletContext servletContext =
            (VaadinServletContext) getService().getContext();

        Bundle bundle = FrameworkUtil.getBundle(FixedVaadinServlet.class);
        tracker = new ServiceTracker<SomeService, SomeService>(
                bundle.getBundleContext(), SomeService.class, null) {
            @Override
            public SomeService addingService(
                    ServiceReference<SomeService> reference) {
                Bundle[] usingBundles = reference.getUsingBundles();
                if (usingBundles == null || usingBundles.length == 0) {
                    ApplicationRouteRegistry.getInstance(servletContext)
                            .setRoute("",SomeView.class,Collections.emptyList());
                }
                return super.addingService(reference);
            }

            @Override
            public void removedService(ServiceReference<SomeService> reference,
                    SomeService service) {
                Bundle[] usingBundles = reference.getUsingBundles();
                if (usingBundles != null && usingBundles.length == 1) {
                    ApplicationRouteRegistry.getInstance(servletContext)
                            .removeRoute("");
                }
                super.removedService(reference, service);
            }
        };
        tracker.open();

    }

    @Override
    public void destroy() {
        super.destroy();
        if (tracker != null) {
            tracker.close();
        }
    }

}

public class SomeView extends Div {
    // .....
}

The service tracker in this example registers the SomeView navigation target when the service becomes available, and the route is unregistered when the service is unregistered.

Note
Conditional routes shouldn’t be annotated
The SomeView class in the example isn’t annotated with @Route at all. Otherwise, it would be considered as a static route that’s always registered, regardless of the presence of the service.

Declarative Services and Vaadin Components

You can use OSGi Declarative Services only indirectly in composite components.

Consider the following example:

@Route("")
public class MainView extends Div {

    @Override
    protected void onAttach(AttachEvent attachEvent) {
       BundleContext bundleContext = FrameworkUtil.getBundle(getClass())
            .getBundleContext();
        ServiceReference<PairedOSGiService> reference = bundleContext
            .getServiceReference(PairedOSGiService.class);
        if (reference != null){
             bundleContext.getService(reference).setView(this);
        }
    }

}

@org.osgi.service.component.annotations.Component(scope=ServiceScope.PROTOTYPE, service=PairedOSGiService.class)
public class PairedOSGiService {

    @Reference
    private SomeService service;

    private AtomicReference<MainView> viewReference = new AtomicReference<>();

    void setView(MainView view){
       // store view to call its methods
       viewReference.set(view);
    }

    @Activate
    void activate(){
       MainView view = viewReference.get();
       if (view!= null && view.isAttached()){
           view.getUI().access( () -> {
                // mutate UI state
           });
       }
    }
}

In this example, the MainView class handles UI actions, while PairedOSGiService handles OSGi-related functionality. The border is quite clear: every time OSGi wants to make changes in the UI, it should call a command through UI::access().

You should again be aware of the possible absence of PairedOSGiService. If SomeService isn’t yet activated, PairedOSGiService isn’t available either. In this case, a fallback with a static route or dynamic route registration can be used, as described earlier.

Technically, the same approach can be used to mix OSGi services with Vaadin components if distinguishing between OSGi logic and UI logic isn’t suitable for some reason.

In such cases, the navigation target component can be a wrapper for a service component, as in the following example:

@Route("")
public class MainViewWrapper extends Div {

    private ServiceTracker<MainView, MainView> tracker;

    @Override
    protected void onAttach(AttachEvent attachEvent) {
        UI ui = attachEvent.getUI();
        Bundle bundle = FrameworkUtil.getBundle(MainView.class);
        tracker = new ServiceTracker<MainView, MainView>(
                bundle.getBundleContext(), MainView.class, null) {
            @Override
            public MainView addingService(
                    ServiceReference<MainView> reference) {
                Bundle[] usingBundles = reference.getUsingBundles();
                if (usingBundles == null || usingBundles.length == 0) {
                    ServiceObjects<MainView> serviceObjects = bundle
                            .getBundleContext().getServiceObjects(reference);
                    MainView view = serviceObjects.getService();
                    ui.access(() -> add(view));
                }
                return super.addingService(reference);
            }

            @Override
            public void removedService(ServiceReference<MainView> reference,
                    MainView service) {
                Bundle[] usingBundles = reference.getUsingBundles();
                if (usingBundles != null && usingBundles.length == 1) {
                    ui.access(MainViewWrapper.this::removeAll);
                }
                super.removedService(reference, service);
            }
        };
        tracker.open();
    }

    @Override
    protected void onDetach(DetachEvent detachEvent) {
        if (tracker!= null){
            tracker.close();
        }
    }

}

@org.osgi.service.component.annotations.Component(scope=ServiceScope.PROTOTYPE, service=MainView.class)
public class MainView extends Div {

    @Reference
    private SomeService service;

    @Override
    protected void onAttach(AttachEvent attachEvent) {
        // ...use the service...
    }

    @Activate
    void activate() {
    }

}

Using a component as a Declarative Service is possible, but you should use some boilerplate code that helps to avoid mistakes.

The code in the example may work "on the fly". If the page is already opened in the browser and service becomes available, the view is updated automatically if Push is used in the project. Otherwise, the browser would need to be refreshed to show the content of the main view.

Moreover, as has already been discussed, if there is no point in showing an empty navigation target page, as in the example, or some fallback component while the service is unavailable, the route may be registered dynamically in the same way. Then there is no need to have a tracker inside the MainViewWrapper. The code can be adapted to support this.

Challenges with Declarative Services

As noted earlier, it’s impossible to use Declarative Services directly with Vaadin.

For example, the following example would not work, because the service isn’t always available:

@Route("")
public class MainView extends Div {

    @Reference
    private SomeService service;

    @Override
    protected void onAttach(AttachEvent attachEvent) {
        // Do something with the service
    }
}

Doing this would avoid having to get a service programmatically and creating a fallback handler, as described earlier.

This isn’t supported because of several reasons:

  • It can’t work with OSGi Declarative Services. The MainView would need to be a service by itself, which requires quite error-prone boilerplate code, such as defining it with: @Component(scope=ServiceScope.PROTOTYPE, service=SomeDedicatedService.class)

  • It’s easy to make the following two mistakes in the @Component declaration:

    • The scope has to be prototype. Any other scope is invalid for the component.

    • The service has to be a dedicated type. It could be, for example, HasElement, but this is another way to make a mistake by forgetting to specify the service at all.

  • Such a feature becomes self-contradictory and confusing; @Route causes a component to be registered statically/unconditionally in Flow. But in OSGi, having MainView as a service would make it impossible to register the route statically. It should be registered only when MainView becomes available as a service, which again depends on the SomeService service. Therefore, the semantics of @Route would need to be changed for OSGi. But this is impossible (see the next item).

  • A navigation target annotated with @Route isn’t an OSGi service and should still work. It would need to be statically registered as a navigation target. It’s necessary to support the regular use case; a web application which works without OSGi should work inside OSGi without any changes.

  • If you want to use some OSGi lifecycle methods, such as activate() / deactivate(), or other methods published in Declarative Services annotations, you should be aware that they aren’t called from the HTTP request dispatcher thread. Hence, it’s necessary to use UI::access() or VaadinSession::access() to invoke methods on UI objects. This is rather error prone, so you should avoid doing it.

1D8749AF-2497-454A-853F-BA38868A427A