Community
Participate
Working Groups
How about a fairy tale (or a "green thread" :-)) describing the life of a new E4 developer: === Say I am a developer who just picked up E4 to make my new RCP text editor. I've scanned a whitepaper that explained that E4 uses model, and, in particluar the MEditor interface to represent an editor. That sounds cool so I make my MEditor class, and... hmm... I guess I need to create some SWT controls to show up the contents? StyledText? OK, that needs parent composite... Hmm... Where do I get the parent composite? And... hmm... where do I place the code that actually creates the conrol? === At this point the story supposed to somehow jump to adding lines like this: @Inject private Composite parent; to the editor's class for the parent composite and add @PostConstruct void init() {...} to hold the code to create the control. === Question is: how would the developer know about this? (The example is intentionally oversimplified. Those two values probably can be considered "well known" things and we can say that we'll have a wizard that would create a sceleton editor with those lines. But the point here is that we need to consider a general solution to this.)
The inherent looseness of DI hides the *requirements* necessary for the implementation object to work correctly in its expected environment. These requirements are two-way; - The renderer has to guarantee that certain elements such as 'parent' are in the context. - The implementation must at least provide a @PostConstruct method that creates its widget(s). Neither of these directions are explicitly defined. The same problem exists for folks implementing menu/tb items, just with different requirements. Without falling back to a pattern where API is explicit (i.e. requiring the implementation to be based off of one (or more) interfaces) it seems the only answer is good examples, docs and tooling. Note that we've been using this for some time in our demos but Paul's observations are correct in that when he had to do a new one it was hard to figure out. Please compare this to the current 3.x story where the contribution is *required* to implement a particular API (IViewPart) and so the client has no doubts as to where to add their code. This requirement is enforced by the code and deeply supported by the tooling. Is this really simpler?
(In reply to comment #1) > The inherent looseness of DI hides the *requirements* necessary for the > implementation object to work correctly in its expected environment. These > requirements are two-way; > > - The renderer has to guarantee that certain elements such as 'parent' are in > the context. > > - The implementation must at least provide a @PostConstruct method that creates > its widget(s). It is exactly what we did in XWT Workbench integration.
This fairy tale sounds like my life; I've never been a Prince Charming before :-) This was the point I was trying to get across in my somewhat rambling post to the e4-dev list in December. I'll append the relevant part here: ---- If I have a peeve with e4, it would have to do with the use of dependency injection and the move away from standard interfaces. Although the use of dependency injection is admittedly powerful, it's exceedingly difficult to figure out what API-ish items can/need to be implemented. In implementing a command handler, there's no single place (at least that I've found) where I can see the possible methods or the possible injectable argument. With e4's moving away from compulsory interfaces, discovery is now a pain: it's hard to figure out how to find various necessary bits. How does a handler implementation find its corresponding tool bar item to change the item's appearance? With E3.x, I can follow the call chains; with e4, I'm left to combing through the photo and contacts demo and hoping to find a relevant example. [There also seems to be some inconsistencies with the use of annotations for marking injections: some methods and constructors require an @Inject (I believe predominantly on contributed classes for MParts) but not on others (handler implementations, I believe).] I wondered if a possible solution might be to provide an equivalent to Smalltalk-like protocols by providing a @noimplement interface for each model class type that describes all the possible methods, with javadoc explaining the possible injections that can be provided. For example: /** * Command handlers in e4 provide the implementation * to enact a command. As there may be multiple handlers * for a command, the handler is the first handler from the list * responding with 'true' to {@link #canExecute()}. * Injectable fields include: * <ul> * <li> ... * </ul> */ public interface e4CommandHandler { /** * <p>Return true if this command can execute at this point. * <p>Optional injectable arguments include:</p> * <ul> * <li> etc * </ul> */ void canExecute(); /** * <p>Cause the command to be executed. This method may return an * object that will be used as the result of executing the command.</p> * <p>Optional injectable arguments include:</p> * <ul> * <li> {@link IEclipseContext} the e4 context * <li> The active selection type (e.g., <code>Contact</code> in * the <code>org.eclipse.e4.demo.contacts</code> example). * <li> {@link IProgressMonitor} to obtain a progress monitor * <li> {@link IExceptionHandler} to obtain the exception handler * <li> {@link IWorkbench} for the workbench instance * <li> {@link IWorkspace} for the active workspace * <li> {@link Shell} for the active window shell * <li> {@link MApplication} for the current application * <li> {@link IPresentationEngine} for the rendering engine * <li> {@link IStylingEngine} for the rendering engine * <li> {@link MApplication} for the current application * <li> The context for a named element (e.g., * <code>@Named(IServiceConstants.ACTIVE_PART) MContext context</code> * </ul> */ Object execute(); } 覧 There are also differences from things like handlers, which apparently *shouldn't* have their canExecute() and execute() methods marked with @Inject (since they're explicitly invoked?), vs part implementation classes where setInput() must be marked with @Inject. Maybe an alternative would be to provide abstract base classes that implementors *can* subclass if they wish. They'll at least act as documentation, aid in discovery, and also embody best-practices (e.g., not creating the SWT stuff from within the constructor, but a @PostConstruct init() method).
This is an interesting problem, and it's one we certainly need to think about. Stepping back a bit, the purpose of injection was to make it *easier* to obtain services in the general case, as well as to decouple of service user from knowing or caring about who provided the service. I'll offer a couple of counter-examples that I've taken from the existing 3.x code (all from the current ResourceNavigator.java in 3.6): 1) I want to print a message on the status line. In 3.x this is: getViewSite().getActionBars().getStatusLineManager().setMessage(msg); In e4 this should be: @Inject IStatusLineManager statusLine; ... statusLine.setMessage(msg); 2) I want to get the selected resources: ArrayList list = new ArrayList(); if (selection instanceof IStructuredSelection) { IStructuredSelection ssel = (IStructuredSelection) selection; for (Iterator i = ssel.iterator(); i.hasNext();) { Object o = i.next(); IResource resource = null; if (o instanceof IResource) { resource = (IResource) o; } else { if (o instanceof IAdaptable) { resource = (IResource) ((IAdaptable) o) .getAdapter(IResource.class); } } if (resource != null) { list.add(resource); } } } return new StructuredSelection(list); I'm not sure how we handle multi-selection in e4, but this should be something like: @Inject public void setSelection(@Named("selection") List<IResource> selection) { selectedResources = selection; } 3) I want to associate a help context with my control getSite().getWorkbenchWindow().getWorkbench().getHelpSystem().setHelp( viewer.getControl(), getHelpContextId()); In e4 this should be something like: @Inject IWorkbenchHelpSystem helpSystem; ... helpSystem.setHelp(viewer.getControl(), getHelpContextId()); The end result is simpler code, but it also removes a mass of assumptions that the client previously had to make, about what their containment structure looked like and where specific services came from. I think we sometimes take for granted how difficult it can be for clients in 3.x to figure out where to get particular service from. Having said all that, we have probably gone too far down the "non-specification" route in e4. I think it is essential that we provide interfaces containing specification of things clients must implement (e.g., you need a createPartControl() method in which your part is created). We can make the interface optional if we want, but there has to be *somewhere* where we specify the behavior expected/required for clients. To me this specification of expected behavior for clients is orthogonal to service injection so I hope that we can have both. The problem Brian describes of trying to find what services are available in a particular situation is a tough one. As a modular platform the set of services available for injection is always going to be elastic. We could list some "core" services that will always be available, but that is only partially helpful for a client living on top of a higher framework that has a much broader set of available services (think IWorkspace or Java model services). I'm hopeful that tooling support and documentation can make this problem easier. Imagine a tooling view that would examine your bundle's imports and provide links to the documentation on services available to that particular bundle, for example.
Brian, nice synopsis. One of the reasons for going the DI route was to try to avoid some of the rigidity of the strict API. One of the greatest pain points to developing eclipse over extended time is that we ended up painting ourselves into API corners. I'm not saying it's correct, just trying to give some background. AFAIK we've ended up with the following 'best practices' (Boris, jump in if I've misunderstood). Abstract classes should be used for anything that we want clients to 'implement', they're far easier to extend than interfaces. Interfaces are only used when we don't want to expose an internal implementation to the client. You make a really good point about the @inject usage and the subtleties about not using it for 'direct' calls...note that if the client were to use ABC's then its implementation would at least cover most of these. For my money I'd have an ABC for each of our 'standard' contributions (parts, handlers, menu/tb items...) that we recommend that clients use. If someone wants to work without a net they are free to ignore this advice and just use DI...;-).
I experimented today a bit with how one could provide people a Java-Type-API to access our 20-things services. The initial work can be found in bug 302824
This came up again in one of our e4 BOFs last night. Tooling around DI, and there are 2 concerns. 1) is covered by this bug. I'm writing a part or a handler, how can I find out what services or data are available to be injected. Tooling should help us (yes, I know we don't have any at the moment :-) 2) invoke, which follows the same pattern as (and uses) our DI engine, but is about calling client methods (per a contract) without requiring a hard Interface dependency (like implementing IHandler). I've opened bug 307061 for this. Back to issue #1, one option that would somewhat be supported by JDT is to use annotations to mark services and data that we expect to be injected. ex (don't focus on the names of the annotations, those can be fixed if this is a solution worth pursuing :-) : @Service public class ECommandService {...} @Service public class ESelectionService {...} And from IServiceConstants: @Data public final static String SELECTION = "selection"; //$NON-NLS-1$ If it's marked by an annotation and on the classpath, that gives us some options for finding it (either writing extra tooling or simply searching for references to @Service/@Data). If we wrote tooling to combine our known annotations + any OSGi DS that would give us a leg up on what's available. PW
(In reply to comment #7) > If it's marked by an annotation and on the classpath, that gives us some > options for finding it (either writing extra tooling or simply searching for > references to @Service/@Data). If we wrote tooling to combine our known > annotations + any OSGi DS that would give us a leg up on what's available. What about scoping of the context? Or is the general assumption that the context contains *everything* that's on the classpath? FWIW, I also like the idea of having real contracts for parts of e4 where it really makes sense. After all, you are writing an e4 application, an e4 part and/or and an e4 command handler. P[O]JO doesn't mean that you are not allowed to extend other classes or implement interfaces. An abstract class is still a P[O]JO. DI really makes sense for simplifying the access to "services" (which -btw- are just P[O]JO methods as well). It helps in understanding the real dependencies because they are no longer hidden behind getA().getB().getC().do() calls. Although this might have an impact on overall system memory (more references than before) I still think it's worth that. DI helps with dependency management and object con-/destruction. But I don't think it should be [ab]used as an execution engine. Frankly, the @PostConstruct for actually creating the control of a part looks like a not-so-nice workaround to me. I don't say that it should be allowed. But in my point of view it would be better to have a more specific API than @PostContruct for this.
(In reply to comment #8) > What about scoping of the context? Or is the general assumption that the > context contains *everything* that's on the classpath? The possible scopes for a specific service can be part of the @DIService annotation, e.g. @DIService(DIApplicationScope.class) public class ECommandService {...} Where the scope is defined in a hierarchy like @DIScope(DIRootScope.class) public class DIApplicationScope {} (or something... :-)) This allows a developer to add new scopes in the future. An alternative can be to explicitly specify all the scopes where the service will be present as in @DIService(DIApplicationScope.class,DIPartScope.class,DIWizardScope.class) public class ECommandService {...} but his later version cannot easily be extended :-( When you create your POJO for your part, handler, wizard, etc, you can annotate it with the intended uses, e.g. @DIUse(DIPartScope.class) @DIUse(DIDialogScope.class) public class MyDataViewer { ... } In this case the available services are those defined in both scopes. We can also pick up uses information from the various extension points and Application.xmi entries the refers to the class...
(In reply to comment #9) > (In reply to comment #8) > > What about scoping of the context? Or is the general assumption that the > > context contains *everything* that's on the classpath? > > The possible scopes for a specific service can be part of the @DIService > annotation, e.g. I actually wasn't talking about this type of scoping. Although it's valid too. I was more talking about the predictability of *what* will be in a context at runtime at any given point in time. This gets really hard when multi-tenancy might get involved (which might not be that relevant for a single RCP instance).
(In reply to comment #9) > (In reply to comment #8) > > What about scoping of the context? Or is the general assumption that the > > context contains *everything* that's on the classpath? > > The possible scopes for a specific service can be part of the @DIService > annotation, e.g. > I know that scopes were mentioned as part of the DI BOF and I think are part of EE, right (JSR 299)? We don't support them ... for now, although if someone could make us understand how that would work in the context of our framework and it made our life easier, we could :-) Our DI and invoke are based on the appropriate IEclipseContext. Our IEclipseContexts match our part hierarchy. In general, we have an OSGi service context <- Application context <- window context <- part context. Our contract: when you ask for information from your context, you will get the appropriate view of the world. So a part sees any part specific data, plus the data its window provides, plus any application data that is not overridden. The scoping is determined by where the object is in the hierarchy. This is the basis for our framework->model->part communication. Any explicit way to specify scopes would have to take this into account. PW
(In reply to comment #11) > I know that scopes were mentioned as part of the DI BOF and I think are part of > EE, right (JSR 299)? > > We don't support them ... for now, although if someone could make us understand > how that would work in the context of our framework and it made our life > easier, we could :-) As far as I understand, we could "simply" have scope == context. So when you write OSGi service context <- Application context <- window context <- part context That also implies four scopes with hierarchical nesting.
(In reply to comment #11) > We don't support them ... for now, although if someone could make us understand > how that would work in the context of our framework and it made our life > easier, we could :-) So this is interesting. Scoping is a runtime thing. In the meaning of JSR 299 it's related to state. Context nesting is a different kind of scoping and related to development (I'm a part not a window). For example @RequestScoped defines one scope during Servlet's #service() invocation. This means that a type annotated with @RequestScoped is allowed to build state during such a processing, i.e. the same object is used within a request. After processing the request, a new object is created. In contrast, a type annotated with @ApplicationScoped is like a singleton *per* (web) application. The default scope is actually not singleton. From the JavaDoc of @Scope (http://java.sun.com/javaee/6/docs/api/javax/inject/Scope.html) "By default, if no scope annotation is present, the injector creates an instance (by injecting the type's constructor), uses the instance for one injection, and then forgets it." This means that a new object is created for every injection. Does the e4 injector implement this rule currently, i.e. is really a new object created every time? For example, I don't see a @Singleton annotation in EclipseAdapter. This implies that everytime an Adapter is injected somewhere a new instance of EclipseAdapter is created. So, maybe e4 wants to come up with its own set of scope definitions. I can imagine that @Singleton is not always the best choice. For example, the RAP folks might not like @Singleton at all for some service objects. Few might still be @Singleton scoped (required for state reasons or to facilitate resource/memory sharing). I can imagine that "application" is an interesting scope for e4/RAP.
Created attachment 166390 [details] DIService/DIData v01 Here's what I'm proposing for 1.0. We can annotate any data constants we publish and services we support with @DIData or @DIService. Searching for either can now provide references in your workspace and target platform (at least if I change the retention policy to CODE :-) PW
(In reply to comment #14) > Created an attachment (id=166390) > DIService/DIData v01 Small nit: the comment for DIData: + * Use this annotation to tag constant fields that represent data available for dependency injection. + * <p> + * This annotation must not be applied to more than one method per class. If several class + * methods are tagged with this annotation, only one of them will be called. The last bit is unnecessary since the annotation only applies to fields.
Created attachment 192205 [details] Experiment: Run a static analysis Experiment. Exploring a static analysis of what can be in context (from a target platform). Uses the JDT APIs (java search, AST, etc) to parse out the first parameter from a set of calls (a similar pattern to walking back up the Call Hierarchy). But I only have one level of search, and I'm not dealing with context.set(String, ...) just context.set(Class<T>, ...). The String method is needed to find data. Obviously, we'd need a lot more before we could see what's available :-) Available services: org.eclipse.core.commands.CommandManager Available services: org.eclipse.core.databinding.observable.Realm Available services: org.eclipse.core.resources.IWorkspace Available services: org.eclipse.e4.core.commands.ECommandService Available services: org.eclipse.e4.core.internal.contexts.ContextObjectSupplier Available services: org.eclipse.e4.core.services.log.ILoggerProvider Available services: org.eclipse.e4.core.services.translation.TranslationService Available services: org.eclipse.e4.ui.bindings.internal.BindingTableManager Available services: org.eclipse.e4.ui.di.UISynchronize Available services: org.eclipse.e4.ui.internal.workbench.PartServiceImpl Available services: org.eclipse.e4.ui.model.application.MApplication Available services: org.eclipse.equinox.app.IApplicationContext Available services: org.eclipse.swt.widgets.Display
Archived bug, marking as fixed.