3

Please consider this minimal Xtext grammar.

Model:
  "As a" stackeholder=Stakeholder "I want" want=Want;

Stakeholder:
  'client' | 'developer' | 'manager';

Want:
  'everything' | 'cookies' | 'fame';

Now what I need to do, is to move the definition of stakeholders (let's forget about want) to SOME external data source. This "external data source" might be a CSV file, might be a DB or maybe a web service. But I it is highly unlikely to be some Xtext file or to come with an EMF model. But still I want to cross-reference it just like you can cross-reference java types in your DSL.

Issues like manual parsing and caching (for performance sake) aside: is this even doable?

I've dug a little into the topic of scopes and resource providers but everything I found required the external source to be part of at least another DSL.

I'd be very happy about a rough outline what would be needed to be done.

agschaid
  • 173
  • 8
  • you would basically adapt the global scope provider and create eobjects for your external stuff there. the manual parsing and caching issue cannot be answered for a general usacase, it is very specific for each usecase (db vs file vs whatever) and how and when these resources change and how their changes can be detected – Christian Dietrich Mar 31 '15 at 10:32
  • Thx Christian. Yeah I deliberately left out the "specific" part. I'll try your suggestions tomorrow. Still not sure how to wire the EObjects into my xtext file but at least now I know where to dig deeper... – agschaid Apr 01 '15 at 22:52

1 Answers1

4

Sorry it took me so long to respond. I tried Christians suggestion, was not very satisfied and than priorities shifted. Now I'll have another go at the problem and in order to document for others (and to clear my head) I'll write down what I did so far since it was not all that straight forward and required a fair amount of experimentation.

I will not post full classes but only the relevant parts. Feel free to ask for more detail if you need it.

My Syntax-Definition now looks like this:

Model:
  stakeholders+=StakeholderDecl*
  requirements+=Requirement*;

Requirement:
  'As a' stakeholder=[Stakeholder] 'I want' want=('everything' | 'cookies' | 'results')
;

StakeholderDecl returns Stakeholder :
  'Stakeholder' Stakeholder
;

Stakeholder:
  name=ID
;

Let it be noted that everything below needed to to be done in the .ui package.

First I created StakeholdersProvider.xtend:

class StakeholdersProvider extends AbstractResourceDescription {

  // this is the dummy for an "external source". Just raw data.
  val nameList = newArrayList( "buddy", "boss" )

  val cache = nameList.map[it.toDescription]

  private val uri = org.eclipse.emf.common.util.URI.createPlatformResourceURI("neverland", true)

  def public List<IEObjectDescription> loadAdditionalStakeholders() {
        cache
  }

  def private IEObjectDescription toDescription(String name) {

    ExternalFactoryImpl.init()
    val ExternalFactory factory = new ExternalFactoryImpl()

    val Stakeholder obj = factory.createStakeholder as StakeholderImpl
    obj.setName(name)


    new StakeholderDescription(name, obj, uri)
  }

. . .

  override getURI() {
    uri
  }

  def public boolean isProvided( EObject object ) {
    if( object.eClass.classifierID != ExternalPackageImpl.STAKEHOLDER ) {
        false
    }
    else {
        val stakeholder = object as Stakeholder
        nameList.exists[it == stakeholder.name]
    }
  }

}

note that the provider is also a resourceDescription and its uri of course is nonsense.

With this provider I wrote a ScopeWrapper.xtend :

class ScopeWrapper implements IScope {

  private var IScope scope;
  private var StakeholdersProvider provider

  new( IScope scopeParam, StakeholdersProvider providerParam ) {
    scope=scopeParam
    provider = providerParam
  }

  override getAllElements() {
    val elements = scope.allElements.toList

    val ret = provider.loadAdditionalStakeholders()
    ret.addAll(elements)

    ret
  }

  override getSingleElement(QualifiedName name) {
      allElements.filter[it.name == name].head
  }

. . . 

}

and ResourceDescriptionWrapper.xtend

class ResourceDescriptionsWrapper implements IResourceDescriptions {

  private StakeholdersProvider provider;
  private IResourceDescriptions descriptions;

  new(IResourceDescriptions descriptionsParam, StakeholdersProvider providerParam) {
    descriptions = descriptionsParam
    provider = providerParam
  }

  override getAllResourceDescriptions() {
    val resources = descriptions.allResourceDescriptions.toList
    resources.add(provider)
    resources
  }

  override getResourceDescription(URI uri) {
    if( uri == provider.URI ) provider
    else descriptions.getResourceDescription(uri)
  }
  override getExportedObjects() {
    val descriptions = descriptions.exportedObjects.toList

    descriptions.addAll(provider.exportedObjects)

    descriptions

  }

  . . . some overrides for getExportedObjects-functions

}

all of this is wired together MyGlobalScopeProvider.xtend

class MyGlobalScopeProvider extends TypesAwareDefaultGlobalScopeProvider {

  val provider = new StakeholdersProvider()

  override getScope(Resource context, EReference reference, Predicate<IEObjectDescription> filter) {
    val scope = super.getScope(context, reference, filter)
    return new ScopeWrapper(scope, provider)
  }

  override public IResourceDescriptions getResourceDescriptions(Resource resource) {
    val superDescr = super.getResourceDescriptions(resource)
    return new ResourceDescriptionsWrapper(superDescr, provider)
  }

}

which is registered in MyDslUiModule.java

public Class<? extends IGlobalScopeProvider> bindIGlobalScopeProvider() {
    return MyGlobalScopeProvider.class;
}

So far so good. I now get boss and buddy suggested as stakeholders. However when I use one of those 2 I get an error in the editor complaining about a dangling reference and an error logging in the console that a stakeholder cannot be exported as the target is not contained in a resource. Figuring those 2 might are related I tried to fix the error logging, created MyresourceDescriptionStrategy.xtend

class MyResourcesDescriptionStrategy extends DefaultResourceDescriptionStrategy {

  val provider = new StakeholdersProvider()

  override isResolvedAndExternal(EObject from, EObject to) {
    if (provider.isProvided(to)) {
        // The object is a stakeholder that was originally provided by
        // our StakeholdersProvider. So we mark it as resolved.
        true
    } else {
        super.isResolvedAndExternal(from, to)
    }
  }
}

and also wire it in the UiModule:

public Class<? extends IDefaultResourceDescriptionStrategy> bindDefaultResourceDescriptionStrategy() {
    return MyResourcesDescriptionStrategy.class;
}

This fixes the logging error but the "dangling reference" problem remains. I searched for solutions for this and the most prominent result suggests that defining a IResourceServiceProvider would have been the best way to solve my problem in the first place. I'll spend a bit more time on the current approach and than try it with a ResourceProvider.

EDIT: I got the "dangling reference" problem fixed. The loadAdditionalStakeholders() function in StakeholdersProvider.xtend now looks like this:

override loadAdditionalStakeholders() {

    val injector = Guice.createInjector(new ExternalRuntimeModule());
    val rs = injector.getInstance(ResourceSet)
    val resource = rs.createResource(uri)
    nameList.map[it.toDescription(resource)]
}

def private IEObjectDescription toDescription(String name, Resource resource) {

    ExternalFactoryImpl.init()
    val ExternalFactory factory = new ExternalFactoryImpl()

    val Stakeholder obj = factory.createStakeholder as StakeholderImpl

    obj.setName(name)
    // not sure why or how but when adding the obj to the resource, the
    // the resource will be set in obj . . . thus no more dangling ref
    resource.contents += obj


    new StakeholderDescription(name, obj, uri)
} 
agschaid
  • 173
  • 8
  • if you said that the external stuff if emf based i would have recommended you to write a resourceserviceprovider in the first place. but you said it is csv or a db and thus unlikely to be emf based. - loading and adding them to a face resource (once) in the global scope provider would have done the job in your solution as well – Christian Dietrich Apr 28 '15 at 04:19
  • I'm only in the process of reading "the book" and only skimmed the documentation. So I am still not totally sure what EMF based actually means. Sorry if a provided a misleading information. – agschaid Apr 28 '15 at 05:30
  • I'm in the process of reading "the book" and only skimmed the documentation. So I am still not totally sure what EMF based actually means. When I wrote the initial post I figured this would mean having the entities in a file that adheres to the defined syntax. Sorry if a provided a misleading information. But in fact I am still talking about some raw information derived from a db or file. I have a list of names in `StakeholdersProvider.xtend`. Yes it is hardcoded. But in my experimentation phase it poses as a mock for an external data source. Does this change a something? – agschaid Apr 28 '15 at 05:40
  • no, did you try to add the stuff to a fake/dummy resource – Christian Dietrich Apr 28 '15 at 17:56
  • Ok. I got it and edited the answer. After trying to somehow register a fake resource and store the objects in it (without success) I resorted to creating just one on the fly and throw it away after storing the objects in it. – agschaid Apr 30 '15 at 09:34