Eclipse Community Forums
Forum Search:

Search      Help    Register    Login    Home
Home » Modeling » TMF (Xtext) » inject extension not injected in JUnit test(inject extension not injected in JUnit test)
inject extension not injected in JUnit test [message #964519] Tue, 30 October 2012 15:38 Go to next message
Michael Veit is currently offline Michael Veit
Messages: 12
Registered: January 2012
Junior Member
Hello,

I want to run a model to model transformation with Xtend 2.3.1, without having a Xtext grammar.

My problem is:
When using the @Inject extension, it does not get injected.
When I run an JUnit test, the injected type is null and therefore creating a NullPointerException during runtime.

I started with a migration from Xtend 1, where the grammar was like:

extension my::package::classname_dt;

create targetmodel::Application mapApplication(Application app, targetmodel::Model model):
[...]

With Xtend2, it looks like:
class MyClass {
  @Inject extension ClassnameDt

 def targetmodel.Application create TargetmodelFactory::eINSTANCE.createApplication mapApplication(
    sourcemodel.Application app,
    targetmodel.Model model
  ) {
    [...]
  }
}

As I use a create method, I cannot inject the ClassnameDt as static extension, which would have solved my problem.
In the test class, I use
@RunWith(XtextRunner.class)
@InjectWith(InjectorProviderCustom.class)
public class TargetmodelFactoryServiceTest extends AbstractTargetmodelFactoryTest {

  @Test
  public void testAllParameterTypes() throws Exception {
    [...]
  }
}

which calls the transform() method of the TargetmodelFactory:
Injector injector = new mypackage.TargetmodelFactoryStandaloneSetup().createInjectorAndDoEMFRegistration();
//Injector injector = new Mwe2StandaloneSetup().createInjectorAndDoEMFRegistration();
//Injector injector = new XtendStandaloneSetup().createInjectorAndDoEMFRegistration();
Mwe2Runner mweRunner = injector.getInstance(Mwe2Runner.class);
//mweRunner.run(URI.createFileURI(mweFile.getAbsolutePath()), empty);
mweRunner.run(URI.createFileURI("myworkflow.mwe2"), prepareWorkflowProperties(), slots);

I tried several methods how to create the StandaloneSetup.
The TargetmodelFactoryStandaloneSetup looks like:
public class TargetmodelFactoryStandaloneSetup extends Mwe2StandaloneSetupGenerated {

  public static void doSetup() {
    new TargetmodelFactoryStandaloneSetup().createInjectorAndDoEMFRegistration();
  }

  public Injector createInjectorAndDoEMFRegistration() {
    org.eclipse.xtext.xbase.XbaseStandaloneSetup.doSetup();

    // register default ePackages
    if (!Resource.Factory.Registry.INSTANCE.getExtensionToFactoryMap().containsKey("ecore"))
      Resource.Factory.Registry.INSTANCE.getExtensionToFactoryMap().put(
          "ecore", new org.eclipse.emf.ecore.xmi.impl.EcoreResourceFactoryImpl());
    if (!Resource.Factory.Registry.INSTANCE.getExtensionToFactoryMap().containsKey("xmi"))
      Resource.Factory.Registry.INSTANCE.getExtensionToFactoryMap().put(
          "xmi", new org.eclipse.emf.ecore.xmi.impl.XMIResourceFactoryImpl());
    if (!EPackage.Registry.INSTANCE.containsKey(org.eclipse.xtext.XtextPackage.eNS_URI))
EPackage.Registry.INSTANCE.put(org.eclipse.xtext.XtextPackage.eNS_URI, org.eclipse.xtext.XtextPackage.eINSTANCE);

    Injector injector = createInjector();
    register(injector);
    return injector;
  }
	
  public Injector createInjector() {
    return Guice.createInjector(Modules2.mixin(
        new mypackage.TargetmodelFactoryRuntimeModule(),
        new org.eclipse.emf.mwe2.language.Mwe2RuntimeModule()
    ));
  }
	
  public void register(Injector injector) {

    if (!EPackage.Registry.INSTANCE.containsKey("TargetmodelFactory")) {
      EPackage.Registry.INSTANCE.put("TargetmodelFactory", mypackage.TargetmodelFactory.eINSTANCE);
    }

    // Copied from Mwe2StandaloneSetupGenerated:
    org.eclipse.xtext.resource.IResourceFactory resourceFactory = injector.getInstance(org.eclipse.xtext.resource.IResourceFactory.class);
    org.eclipse.xtext.resource.IResourceServiceProvider serviceProvider = injector.getInstance(org.eclipse.xtext.resource.IResourceServiceProvider.class);
    Resource.Factory.Registry.INSTANCE.getExtensionToFactoryMap().put("mwe2", resourceFactory);
    org.eclipse.xtext.resource.IResourceServiceProvider.Registry.INSTANCE.getExtensionToFactoryMap().put("mwe2", serviceProvider);
  }
}

The RuntimeModule looks like:
public class TargetmodelFactoryRuntimeModule extends AbstractGenericResourceRuntimeModule {

  // contributed by org.eclipse.xtext.generator.types.TypesGeneratorFragment
  public org.eclipse.xtext.common.types.TypesFactory bindTypesFactoryToInstance() {
    return org.eclipse.xtext.common.types.TypesFactory.eINSTANCE;
  }

  @Override
  protected String getLanguageName() {
    return "targetmodel";
  }

  @Override
  protected String getFileExtensions() {
    return "mod";
  }

}

The InjectorProviderCustom is a copy from the domainmodel example:
public class InjectorProviderCustom extends TargetmodelFactoryInjectorProvider {
	
  public Injector getInjector() {
    if (injector == null) {
      stateBeforeInjectorCreation = GlobalRegistries.makeCopyOfGlobalState();
      this.injector = new TargetmodelFactoryStandaloneSetup() {
        //@Override
        //public Injector createInjector() {
        //	return Guice.createInjector(new SMMFactoryRuntimeModule() {
            //@Override
            //public ClassLoader bindClassLoaderToInstance() {
            //	return InjectorProviderCustom.class.getClassLoader();
            //}
        //	});
        //}
      }.createInjectorAndDoEMFRegistration();
      stateAfterInjectorCreation = GlobalRegistries.makeCopyOfGlobalState();
    }
    return injector;
  }
}

Does anyone have any idea how to get these Xtend classes injected?

[Updated on: Wed, 31 October 2012 07:03]

Report message to a moderator

Re: inject extension not injected in JUnit test [message #964852 is a reply to message #964519] Tue, 30 October 2012 21:03 Go to previous messageGo to next message
Sebastian Zarnekow is currently offline Sebastian Zarnekow
Messages: 2829
Registered: July 2009
Senior Member
Hi Michael,

where does the instanceof of MyClass come from that is used in the test,
e.g. how does testAllParameterTypes look like? Please note that MWE is
usually not required in unit tests and it does not use dependency
injection out of the box.

Regards,
Sebastian
--
Looking for professional support for Xtext, Xtend or Eclipse Modeling?
Go visit: http://xtext.itemis.com

Am 30.10.12 16:38, schrieb Michael Veit:
> Hello,
>
> I want to run a model to model transformation with Xtend 2.3.1, without
> having a Xtext grammar.
>
> My problem is:
> When using the @Inject extension, it does not get injected.
> When I run an JUnit test, the injected type is null and therefore
> creating a NullPointerException during runtime.
>
> I started with a migration from Xtend 1, where the grammar was like:
>
> extension my::package::classname_dt;
>
> create targetmodel::Application mapApplication(Application app,
> targetmodel::Model model):
> [...]
>
>
> With Xtend2, it looks like:
>
> class MyClass {
> @Inject extension ClassnameDt
>
> def targetmodel.Application create
> TargetmodelFactory::eINSTANCE.createApplication mapApplication(
> sourcemodel.Application app,
> targetmodel.Model model
> ) {
> [...]
> }
> }
>
>
> As I use a create method, I cannot inject the ClassnameDt as static
> extension, which would have solved my problem.
>
> In the test class, I use
> @RunWith(XtextRunner.class)
> @InjectWith(InjectorProviderCustom.class)
> public class TargetmodelFactoryServiceTest extends
> AbstractTargetmodelFactoryTest {
>
> @Test
> public void testAllParameterTypes() throws Exception {
> [...]
> }
> }
>
> which calls the transform() method of the TargetmodelFactory:
> Injector injector = new
> mypackage.TargetmodelFactoryStandaloneSetup().createInjectorAndDoEMFRegistration();
>
> //Injector injector = new
> Mwe2StandaloneSetup().createInjectorAndDoEMFRegistration();
> //Injector injector = new
> XtendStandaloneSetup().createInjectorAndDoEMFRegistration();
> Mwe2Runner mweRunner = injector.getInstance(Mwe2Runner.class);
> //mweRunner.run(URI.createFileURI(mweFile.getAbsolutePath()), empty);
> mweRunner.run(URI.createFileURI("myworkflow.mwe2"),
> prepareWorkflowProperties(), slots);
>
>
> I tried several methods how to create the StandaloneSetup.
>
> The TargetmodelFactoryStandaloneSetup looks like:
> public class TargetmodelFactoryStandaloneSetup extends
> Mwe2StandaloneSetupGenerated {
>
> public static void doSetup() {
> new
> TargetmodelFactoryStandaloneSetup().createInjectorAndDoEMFRegistration();
> }
>
> public Injector createInjectorAndDoEMFRegistration() {
> org.eclipse.xtext.xbase.XbaseStandaloneSetup.doSetup();
>
> // register default ePackages
> if
> (!Resource.Factory.Registry.INSTANCE.getExtensionToFactoryMap().containsKey("ecore"))
>
>
> Resource.Factory.Registry.INSTANCE.getExtensionToFactoryMap().put(
> "ecore", new
> org.eclipse.emf.ecore.xmi.impl.EcoreResourceFactoryImpl());
> if
> (!Resource.Factory.Registry.INSTANCE.getExtensionToFactoryMap().containsKey("xmi"))
>
>
> Resource.Factory.Registry.INSTANCE.getExtensionToFactoryMap().put(
> "xmi", new
> org.eclipse.emf.ecore.xmi.impl.XMIResourceFactoryImpl());
> if
> (!EPackage.Registry.INSTANCE.containsKey(org.eclipse.xtext.XtextPackage.eNS_URI))
>
>
> EPackage.Registry.INSTANCE.put(org.eclipse.xtext.XtextPackage.eNS_URI,
> org.eclipse.xtext.XtextPackage.eINSTANCE);
>
> Injector injector = createInjector();
> register(injector);
> return injector;
> }
>
> public Injector createInjector() {
> return Guice.createInjector(Modules2.mixin(
> new mypackage.TargetmodelFactoryRuntimeModule(),
> new org.eclipse.emf.mwe2.language.Mwe2RuntimeModule()
> ));
> }
>
> public void register(Injector injector) {
>
> if
> (!EPackage.Registry.INSTANCE.containsKey("TargetmodelFactory")) {
> EPackage.Registry.INSTANCE.put("TargetmodelFactory",
> mypackage.TargetmodelFactory.eINSTANCE);
> }
>
>
> // Copied from Mwe2StandaloneSetupGenerated:
> org.eclipse.xtext.resource.IResourceFactory resourceFactory =
> injector.getInstance(org.eclipse.xtext.resource.IResourceFactory.class);
> org.eclipse.xtext.resource.IResourceServiceProvider
> serviceProvider =
> injector.getInstance(org.eclipse.xtext.resource.IResourceServiceProvider.class);
>
>
> Resource.Factory.Registry.INSTANCE.getExtensionToFactoryMap().put("mwe2", resourceFactory);
>
>
> org.eclipse.xtext.resource.IResourceServiceProvider.Registry.INSTANCE.getExtensionToFactoryMap().put("mwe2",
> serviceProvider);
> }
> }
>
>
> The RuntimeModule looks like:
> public class TargetmodelFactoryRuntimeModule extends
> AbstractGenericResourceRuntimeModule {
>
>
> // contributed by
> org.eclipse.xtext.generator.types.TypesGeneratorFragment
> public org.eclipse.xtext.common.types.TypesFactory
> bindTypesFactoryToInstance() {
> return org.eclipse.xtext.common.types.TypesFactory.eINSTANCE;
> }
>
> @Override
> protected String getLanguageName() {
> return "targetmodel";
> }
>
> @Override
> protected String getFileExtensions() {
> return "mod";
> }
>
> }
>
> The InjectorProviderCustom is a copy from the domainmodel example:
> public class InjectorProviderCustom extends
> TargetmodelFactoryInjectorProvider {
>
> public Injector getInjector() {
> if (injector == null) {
> stateBeforeInjectorCreation =
> GlobalRegistries.makeCopyOfGlobalState();
> this.injector = new TargetmodelFactoryStandaloneSetup() {
> //@Override
> //public Injector createInjector() {
> // return Guice.createInjector(new
> SMMFactoryRuntimeModule() {
> //@Override
> //public ClassLoader bindClassLoaderToInstance() {
> // return
> InjectorProviderCustom.class.getClassLoader();
> //}
> // });
> //}
> }.createInjectorAndDoEMFRegistration();
> stateAfterInjectorCreation =
> GlobalRegistries.makeCopyOfGlobalState();
> }
> return injector;
> }
> }
>
>
>
> Does anyone have any idea how to get these Xtend classes injected?
Re: inject extension not injected in JUnit test [message #965392 is a reply to message #964852] Wed, 31 October 2012 07:41 Go to previous messageGo to next message
Michael Veit is currently offline Michael Veit
Messages: 12
Registered: January 2012
Junior Member
Hi Sebastian,

in the MWE2 workflow, I call
  component = mypackage.MyRootClass {
    metaModel = mod
    metaModel = java
    outputSlot = "outputmodel"
  }

which implements AbstractExpressionsUsingWorkflowComponent:
import org.eclipse.xtend.expression.AbstractExpressionsUsingWorkflowComponent

class MyRootClassextends AbstractExpressionsUsingWorkflowComponent {
  @Inject extension MyClass
  [...]

  /** Entry point of transformation. */
  def mypackage.TargetModel create mypackage::TargetmodelFactory::eINSTANCE.createModel main(
    Collection<BaseException> sysExes
  ) {
    if (sysExes.size > 0) {
      sysExes.forEach(e | e.mapSysEx(it))
    }
    [...]
  }

  /** Call main(). Replaces invoke of XtendComponent in Xtend 1.x. */
  override invoke(IWorkflowContext ctx) {
    val mySystemExceptions = ctx.get("mySystemExceptions") as List<BaseException>
    val result = main(mySystemExceptions)
    ctx.put(outputSlot, result);
  }

  [...]
}

which gives a NullPointerException when executing e.mapSysEx(it). With debugging I verified that the Collection is not null and does not contain null entries. The _myclass instance is null and causes the exception. All parameters passed by the MWE2 workflow contain the expected values.

The testAllParameterTypes() is just plain Java, calling the transform() method of the TargetmodelFactory, which runs the standalone MWE2 workflow:
import org.junit.Before;
import org.junit.runner.RunWith;
import mypackage.mock.model.elements.ElementBuilder;
import org.eclipse.xtext.junit4.InjectWith;
import org.eclipse.xtext.junit4.XtextRunner;
[...]

@RunWith(XtextRunner.class)
@InjectWith(InjectorProviderCustom.class)
public class TargetmodelFactoryServiceTest extends AbstractTargetmodelFactoryTest {

  ElementBuilder b;

  @Before
  public void setup() {
    b = new ElementBuilder();
  }

  @Test
  public void testAllParameterTypes() throws Exception {
    [...]
    b.addIn(si, "inparam", 0, 1, dt);
    b.addOut(si, "outparam", 0, 1, dt);
    si.addBusinessException("BE123456", "SomeBusinessException",
        "description...", null);

    File modelFile = null;
    modelFile = File.createTempFile("testModel", "smm");

    new TargetmodelFactory(modelFile).transform(b.getSysExes());
  }

With Xtend1, it was possible to run the JUnit test, without this huge amount of helper classes.
Re: inject extension not injected in JUnit test [message #965408 is a reply to message #965392] Wed, 31 October 2012 07:54 Go to previous messageGo to next message
Michael Veit is currently offline Michael Veit
Messages: 12
Registered: January 2012
Junior Member
Hi Sebastion

Regarding the static extension approach:
If I could import a static extension, this would solve my problem. But the create methods prevent me from converting all methods to static methods.

In the generated Java class, I cannot find any restrictiion which would prevent static create methods. Xtend creates a private HashMap, which also could be static. If all generated artefacts would be static, everything would run.

Did I miss any option for static create methods?
Re: inject extension not injected in JUnit test [message #976252 is a reply to message #965408] Thu, 08 November 2012 12:37 Go to previous message
Michael Veit is currently offline Michael Veit
Messages: 12
Registered: January 2012
Junior Member
Finally I got it running. I just want to share the knowledge in case that anyone else gets such a problem, too.
Basically I had to pass the injector to the workflowcomponent and be sure to create the instance of the Xtend class using that injector. Afterwards everything is initialized by the injection mechanism.

Here is the solution:
I implemented a StandaloneSetup, which looks like:
package mypackage;

import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.mwe2.language.Mwe2StandaloneSetup;
import org.eclipse.emf.mwe2.language.Mwe2StandaloneSetupGenerated;
import org.eclipse.xtext.ISetup;
import org.eclipse.xtext.util.Modules2;

import com.google.inject.Guice;
import com.google.inject.Injector;
import mypackage.MyFactory;


@SuppressWarnings("all")
public class SMMFactoryStandaloneSetup extends Mwe2StandaloneSetupGenerated {

	@Override
	public Injector createInjector() {
		return Guice.createInjector(Modules2.mixin(
				new mypackage.MyFactoryRuntimeModule(),
				new org.eclipse.emf.mwe2.language.Mwe2RuntimeModule()
		));
	}

	public void register(Injector injector) {
		if (!EPackage.Registry.INSTANCE.containsKey("MyFactory")) {
			EPackage.Registry.INSTANCE.put("MyFactory", mypackage.MyFactory.eINSTANCE);
		}

		// Copied from Mwe2StandaloneSetupGenerated:
		super.register(injector);
	}
}

The RuntimeModule looks like:
package mypackage;

import org.eclipse.xtext.resource.generic.AbstractGenericResourceRuntimeModule;
import org.eclipse.xtext.service.SingletonBinding;

/**
 * Use this class to register components to be used at runtime / without the Equinox extension registry.
 */
@SuppressWarnings("all")
public class MyFactoryRuntimeModule extends AbstractGenericResourceRuntimeModule {

	@SingletonBinding
	public Class<MyClass1> bindMyClass1() {
		return MyClass1.class;
	}

	@SingletonBinding
	public Class<MyClass2> bindMyClass2() {
		return MyClass2.class;
	}

	@Override
	protected String getLanguageName() {
		return "mypackage.targetmodel";
	}

	@Override
	protected String getFileExtensions() {
		return "extension";
	}

}

When I call the workflow, I add the injector to the context. This is required for in order to have the injector initialized when using my Xtend classes.
IWorkflowContext slots = new WorkflowContextImpl();
Injector injector = new MyFactoryStandaloneSetup().createInjectorAndDoEMFRegistration();
slots.put("injector", injector);
Mwe2Runner mweRunner = injector.getInstance(Mwe2Runner.class);
mweRunner.run(URI.createFileURI("myworkflow.mwe2"), new HashMap<String, String>(), slots);

Finally, my Xtend class looks like:
package mypackage

import com.google.inject.Inject
import com.google.inject.Singleton
import org.eclipse.emf.mwe2.runtime.workflow.IWorkflowContext

import static extension com.mypackage.XtendClassWithStaticMethods.*


@Singleton
class MyClass1 extends AbstractMyWorkflowComponent {
  @Inject extension MyClass2
  // Not sure if this is required for @Singleton
  @Inject new() {}

  [...]
  /** Call main(). Replaces invoke of XtendComponent in Xtend 1.x. */
  override invoke(IWorkflowContext ctx) {
    // Necessary for injection
    val MyClass1 obj = super.injectMembers(ctx, this) as MyClass1

    val result = obj.main(myParameters)
    ctx.put(outputSlot, result)
  }
}

The injection will then be initialized here, using obj.getInstance(). This initializes all extensions and solved my problem:
package mypackage;

import com.google.inject.Injector;
import org.eclipse.emf.mwe2.runtime.workflow.IWorkflowContext;
import org.eclipse.xtend.expression.AbstractExpressionsUsingWorkflowComponent;


public abstract class AbstractMyWorkflowComponent extends AbstractExpressionsUsingWorkflowComponent {
  protected Injector injector;
  protected String outputSlot = null;

  public void setOutputSlot(final String outputSlot) {
    this.outputSlot = outputSlot;
  }

  /** Inject members. */
  protected Object injectMembers(final IWorkflowContext ctx, Object obj) {
    injector = (Injector) ctx.get("injector");
    //injector.injectMembers(obj);
    return injector.getInstance(obj.getClass());
  }
}


So your hint "Where does the instance come from" was good in order to get a deeper dive into the injection mechanism and to solve it. Thanks Sebastian.
Previous Topic:Resolve proxies
Next Topic:How to access annotations in JvmAnnotaionReference?
Goto Forum:
  


Current Time: Tue Sep 16 21:46:43 GMT 2014

Powered by FUDForum. Page generated in 0.02267 seconds
.:: Contact :: Home ::.

Powered by: FUDforum 3.0.2.
Copyright ©2001-2010 FUDforum Bulletin Board Software