Cross region import of EventBus is unable to import the target bus - aws-cdk

I'm trying to translate my existing CDK project from Java to Typescript, and at the same time migrate from CDKv1, used by the older project, to CDK v2. I'm mostly done, but there is one last thing that's driving me crazy.
This project uses Chime, and I use its events for some application logic. AWS Chime events are produced in the us-east-1 region, and my application is in eu-west-1. The solution that I came up to was to create a Rule, listening to Chime events, in us-east-1, then redirect the events on an EventBus on eu-west-1: then I'd filter the events on that bus using rules for the events I'm interested in and execute my logic through lambdas.
This is the old project's code:
App.java
Environment env = Environment.builder()
.account(ACCOUNT_ID)
.region(REGION)
.build();
StackProps sp = StackProps.builder()
.env(env)
.build();
Environment envUsEast1 = Environment.builder()
.account(ACCOUNT_ID)
.region("us-east-1")
.build();
StackProps spUsEast1 = StackProps.builder()
.env(envUsEast1)
.build();
MonitoringStack eventsMonitoringStack = new MonitoringStack(app,
"EventMonitoringStack",
sp);
EventsOriginStack eventsOriginStack = new EventsOriginStack(app,
"EventOriginStack", spUsEast1);
chimeEventsOriginStack.addEventBridgeToOriginRule(eventsMonitoringStack.getListenerBus());
MonitoringStack.java
public class MonitoringStack extends Stack {
EventBus listenerBus;
public MonitoringStack(final Construct scope, final String id, final StackProps props) {
super(scope, id, props);
listenerBus = new EventBus(this, "listener-event-bus", EventBusProps
.builder()
.eventBusName("listener-event-bus")
.build());
Rule attendeeJoinedRule = new Rule(this, "attendee-joined-rule", RuleProps.builder()
.ruleName("attendee-joined-rule")
.eventBus(listenerBus)
.eventPattern(EventPattern.builder()
.detailType(Collections.singletonList("Chime Meeting State Change"))
.source(Collections.singletonList("aws.chime"))
.detail(Map.of("eventType", List.of("chime:AttendeeJoined")))
.build())
.build());
...more logic...
EventsOriginStack.java
public class EventsOriginStack extends Stack {
Rule chimeEventsRule;
public EventsOriginStack(#Nullable Construct scope, #Nullable String id, #Nullable StackProps props) {
super(scope, id, props);
chimeEventsRule = new Rule(this, "chime-all-events-rule", RuleProps.builder()
.ruleName("chime-all-events-rule")
.eventPattern(EventPattern.builder()
.detailType(Collections.singletonList("Chime Meeting State Change"))
.source(Collections.singletonList("aws.chime"))
.build())
.build());
}
public void addEventBridgeToOriginRule(EventBus eventBus) {
chimeEventsRule.addTarget(new software.amazon.awscdk.services.events.targets.EventBus(eventBus));
}
}
And now the same code is like this:
app.ts
const env: Environment = {
account: process.env.AWS_ACCOUNT_ID,
region: process.env.REGION
}
const props: StackProps = {
env: env
}
const envUsEast1: Environment = {
account: process.env.AWS_ACCOUNT_ID,
region: "us-east-1"
}
const propsUsEast1: StackProps = {
env: envUsEast1
}
const eventsMonitoringStack: EventsMonitoringStack = new EventsMonitoringStack(app,
"eventMonitoringStack", {
env: env
});
const eventsOriginStack: EventsOriginStack = new EventsOriginStack(app,
"EventOriginStack", propsUsEast1);
chimeEventsOriginStack.addEventBridgeToOriginRule(eventsMonitoringStack.listenerBus);
events-monitoring-stack.ts
export class EventsMonitoringStack extends Stack {
private _listenerBus: EventBus;
public get listenerBus(): EventBus {
return this._listenerBus;
}
public set listenerBus(value: EventBus) {
this._listenerBus = value;
}
constructor(scope: Construct, id: string, props: EventsMonitoringStackProps) {
super(scope, id, props);
this.listenerBus = new EventBus(this, "listener-event-bus", {
eventBusName: "listener-event-bus"
});
const attendeeJoinedRule = new Rule(this, "attendee-joined-rule", {
ruleName: "attendee-joined-rule",
eventBus: this.listenerBus,
eventPattern: {
detailType: [
"Chime Meeting State Change"
],
source: [
"aws.chime"
],
detail: {
"eventType": ["chime:AttendeeJoined"]
}
}
});
...more logic...
events-origin-stack.ts
//the naming choice is quite annoying
import { EventBus, Rule } from "aws-cdk-lib/aws-events";
import { EventBus as EventBusTarget } from "aws-cdk-lib/aws-events-targets";
export class EventsOriginStack extends Stack {
private _chimeEventsRule: Rule;
public get chimeEventsRule(): Rule {
return this._chimeEventsRule;
}
public set chimeEventsRule(value: Rule) {
this._chimeEventsRule = value;
}
constructor(scope: Construct, id: string, props: StackProps) {
super(scope, id, props);
this._chimeEventsRule = new Rule(this, "chime-all-events-rule", {
ruleName: "chime-all-events-rule",
eventPattern: {
detailType: ["Chime Meeting State Change"],
source: ["aws.chime"]
}
});
}
public addEventBridgeToOriginRule(eventBus: EventBus) {
this.chimeEventsRule.addTarget(new EventBusTarget(eventBus));
}
}
So,the problem: at cdk diff time, and on deploy, this comes out:
┌───┬───────────────────────────────────────────────────────────────────────────────────────┬────────┬──────────────────┬─────────────────────────────────────────┬───────────┐
│ │ Resource │ Effect │ Action │ Principal │ Condition │
├───┼───────────────────────────────────────────────────────────────────────────────────────┼────────┼──────────────────┼─────────────────────────────────────────┼───────────┤
│ + │ ${chime-all-events-rule/EventsRole.Arn} │ Allow │ sts:AssumeRole │ Service:events.amazonaws.com │ │
├───┼───────────────────────────────────────────────────────────────────────────────────────┼────────┼──────────────────┼─────────────────────────────────────────┼───────────┤
│ + │ arn:${AWS::Partition}:events:${AWS::Region}:<my-aws-account-id>:event-bus/listener-event-bus │ Allow │ events:PutEvents │ AWS:${chime-all-events-rule/EventsRole} │ │
└───┴───────────────────────────────────────────────────────────────────────────────────────┴────────┴──────────────────┴─────────────────────────────────────────┴───────────┘
Unlike before on Java and CDKv1, where the result was something like this:
IAM Statement Changes
┌───┬────────────────────────────────────────────────────────────────────┬────────┬──────────────────┬─────────────────────────────────────────┬───────────┐
│ │ Resource │ Effect │ Action │ Principal │ Condition │
├───┼────────────────────────────────────────────────────────────────────┼────────┼──────────────────┼─────────────────────────────────────────┼───────────┤
│ + │ ${chime-all-events-rule/EventsRole.Arn} │ Allow │ sts:AssumeRole │ Service:events.amazonaws.com │ │
├───┼────────────────────────────────────────────────────────────────────┼────────┼──────────────────┼─────────────────────────────────────────┼───────────┤
│ + │ arn:aws:events:eu-west-1:<my-aws-account-id>:event-bus/listener-event-bus │ Allow │ events:PutEvents │ AWS:${chime-all-events-rule/EventsRole} │ │
└───┴────────────────────────────────────────────────────────────────────┴────────┴──────────────────┴─────────────────────────────────────────┴───────────┘
And on deploying, the result I get is the two stacks, correctly deployed in their respective regions. The issue is the destination for the origin rule: instead of being the listener event bus on eu-west-1, it's some event bus with the same name in us-east-1:
So, instead of pointing to an event bus with an ARN like this:
arn:aws:events:eu-west-1:<my-account-id>:event-bus/listener-event-bus
it's something like this:
arn:aws:events:us-east-1:<my-account-id>:event-bus/listener-event-bus
I suspect is an issue with the CloudFormation references, and how they are resolved at deploy time, but I can't figure how can I solve this, especially because it worked fine in the older Java version.
Can anyone give me a pointer on how to solve this issue? For what matters, I've launched cdk bootstrap after updating cdk to version 2, and it seems the version is far beyond the required version 6.

Thank you #gshpychka, that was exactly what was going on! From the old Java code I left the variable REGION, which was actually initialized as private static final String REGION= System.getenv("AWS_REGION");. AWS_REGION is the actual environment variable where I put the default region.
Changing region: process.env.REGION to region: process.env.AWS_REGION solved my issues.

Related

Pass properties over from one Construct to another with CDK before synth runs

I've got two Constructs being created in one Stack, and during synthesis I would like to merge a bunch of properties and use the merged result.
The reason for this is to split creation of resources into a specific look in the Stack. In other words, I want to initialize two Constructs in the one Stack, but when CDK deploys the stack, I would like properties from both to be merged, with the resource(s) created in one of the Stacks, (Environment in my example below) being able to use the merged properties from both Constructs.
Here is where I've got to so far, but when running a cdk synth the resulting someProperties object does not reflect the items I've sent through from my ExampleThing Construct that was initialized in the same Stack.
export class IExampleEnvironment extends Construct {
readonly someId?: string;
}
export interface ExampleProps {
readonly environment: Environment;
readonly a: string;
readonly b: string;
}
export class Environment extends IExampleEnvironment {
private someProperties: any;
constructor(scope: Construct, id: string, props: StackProps) {
super(scope, id);
new ExampleResourceThatUsesSomeProperties(this, 'Example' {
foo: this.someProperties // this is where I would like to use some properties that are brought in from another Construct (in the same stack as Environment)
});
}
public addSomeProperties(someProps: ExampleProps) {
this.someProperties = { A: someProps.a, B: someProps.b };
}
}
export class ExampleThing extends Construct {
constructor(scope: Construct, id: string, props: ExampleProps) {
super(scope, id);
props.environment.addSomeProperties(props);
}
}
// Creating the Stack...
const environment = new Environment(this, 'Environment', { someId: "123" });
const exampleThing = new ExampleThing(this, 'ExampleThing', props);
Currently, I'm using a reference to the Environment property sent in, and then calling the internal addSomeProperties() method on it, hoping that would work. I suspect this is my issue, but I'm not sure of how to achieve this.
The important thing here is that the Environment stack must be created first, as the ExampleThing stack needs to supply a environment property as part of its ExampleProps and pass the Environment in for that.
Is there a better way that would work?
So I'll admit this is a very niche requirement, and not really the way CDK is meant to be used, but I wanted to get this very specific use case working.
I dug in a little more and found that it is possible by overriding the prepare() method in the Construct. Here is my working example, which shows how one construct can create an S3 bucket, and another can update this instance's S3 bucket purely by creating an instance and passing in the other instance:
thing.ts:
import { Construct } from "#aws-cdk/core";
import { Foo } from "./foo";
export interface ExampleThingProps {
readonly foo: Foo;
readonly a: string;
readonly b?: string;
}
export class ExampleThing extends Construct {
constructor(scope: Construct, id: string, props: ExampleThingProps) {
super(scope, id);
props.foo.addSomeProperties(props);
}
}
foo.ts:
import { Construct, StackProps } from "#aws-cdk/core";
import * as s3 from "#aws-cdk/aws-s3";
import { ExampleThingProps } from "./thing";
import { IBucket } from "#aws-cdk/aws-s3";
export class IExampleFoo extends Construct {
readonly someId?: string;
}
export interface FooProps {
bucketName: string;
}
export class Foo extends IExampleFoo {
private someProperties: any;
private theBucket: IBucket;
constructor(scope: Construct, id: string, props: FooProps) {
super(scope, id);
this.someProperties = { A: props.bucketName };
this.theBucket = new s3.Bucket(this, 'ExampleBucket', {
bucketName: this.someProperties.A
});
}
prepare() {
const firstBucket = this.node.tryFindChild("ExampleBucket");
if (firstBucket) {
console.log('found first bucket node: ' + firstBucket.node.uniqueId);
const removed = this.node.tryRemoveChild(firstBucket.node.id);
console.log('removed the found node: ' + removed);
}
this.theBucket = new s3.Bucket(this, 'ExampleBucket', {
bucketName: this.someProperties.A
});
}
public addSomeProperties(someProps: ExampleThingProps) {
console.log('updates the existing properties...');
this.someProperties = { A: someProps.a, B: someProps.b };
}
}
Usage:
const foo = new Foo(this, 'Foo', {
bucketName: "this-is-the-name-set-from-foo-construction"
});
const thing = new ExampleThing(this, 'Thing', {
foo: foo,
a: "shiny-new-name"
});
If you don't initialize a new ExampleThing then the bucket will be named this-is-the-name-set-from-foo-construction.
If you initialize a new ExampleThing, then the bucket's name will be changed for synthesize (and will apply in a cdk diff or deploy and be named shiny-new-name.
I think you are looking to create dependencies between the resources, here is an example
const app = new App();
const myFirstStack = new MyFirstStack(
app,
stackNameone
);
const mySecondStack = new MySecondStack(
app,
stackNametwo
);
mySecondStack.addDependency(myFirstStack, "Depends on resources");
You can do this with most resources in CDK so whichever resources you want created first add it as a dependency like the above example.

How do I get a list of subnet IDs from one stack to another using SSM when StringListParameter doesn't work?

According to this bug and this bug, ssm.StringListParameter doesn't work in the CDK due to some issues with CFT.
I need to be able to export an arbitrarily length list of subnetIds from one stack and import the list into another using SSM, as the lifetime of subnetIds is completely different than the lifetime of the consumer of subnetIds in a second stack (especially in production, though not in a sandbox). I cannot hard code the subnet IDs, as when creating a sandbox the IDs would vary from sandbox to sandbox. I want the latest version of whatever is in the SSM key.
However, the bugs appear to not be resolvable, and I cannot find a work around.
I tried serializing using JSON, but the item passed around the code is is a late binding Token, which is treated as a string, and the deserialized items is a string [], so it's not possible to get such code to compile.
Here's an example of what I attempted. It doesn't compile:
export function getOtherStackOutputList(stack: cdk.Stack, mangledOtherStackName: string, key: string): string [] {
const globallyUniqueKey = `/${mangledOtherStackName}/${key}`;
const jsonResult = ssm.StringParameter.fromStringParameterName(stack, key + 'SSM', globallyUniqueKey).stringValue;
if (cdk.Token.isUnresolved(jsonResult)) {
return jsonResult;
} else {
const result = JSON.parse(jsonResult);
return result;
}
};
which would be used by code that looks like this:
const efsVpcIsolatedSubnetsIds = StackValueShare.getOtherStackOutputList(this, mangledStackName + efsDbStackSuffix,
'efsTimeSeriesDatabaseVpcIsolatedSubnets');
This works for me:
import { Construct } from '#aws-cdk/core';
import { AwsCustomResource, AwsSdkCall } from '#aws-cdk/custom-resources';
import iam = require("#aws-cdk/aws-iam");
interface SSMParameterReaderProps {
parameterName: string;
region: string;
}
export class SSMParameterReader extends AwsCustomResource {
constructor(scope: Construct, name: string, props: SSMParameterReaderProps) {
const { parameterName, region } = props;
const ssmAwsSdkCall: AwsSdkCall = {
service: 'SSM',
action: 'getParameter',
parameters: {
Name: parameterName
},
region,
physicalResourceId: {id:Date.now().toString()} // Update physical id to always fetch the latest version
};
super(scope, name, { onUpdate: ssmAwsSdkCall,policy:{
statements:[new iam.PolicyStatement({
resources : ['*'],
actions : ['ssm:GetParameter'],
effect:iam.Effect.ALLOW,
}
)]
}});
}
public getParameterValue(): string {
return this.getResponseField('Parameter.Value').toString();
}
}

AWS CDK- Access resource from other stack

Suppose i have a resource, say stepfunction activity, in one stack. How can i access it's arn in other stack?
I found CfnConstruct suitables for exporting (https://docs.aws.amazon.com/cdk/api/latest/docs/#aws-cdk_core.CfnOutput.html). As of now i have used CfnConstruct to export it:
this.initiateValidationActivityArn = new cdk.CfnOutput(this, 'InitiateValidationActivityArn', {
value: igvsStateMachine.initiateValidationActivity.activityArn
});
Now how can i access it in other file. I have tried this:
ecsService.initiateValidationActivityArn.value
But value is private to construct so can't use it.
If you have the stacks in one deployable cdk-app, you can access the property value from the stack by making it accessible from outside / not-private.
What I recommend doing is to keep it readonly so that it can't be re-initialized from outside the stack.
// file: ./lib/first-stack.ts
// import statements
export class FirstStack extends Stack {
readonly vpc: IVpc;
constructor(...) {
// setup
this.vpc = new Vpc(this, "VPC", {...});
}
}
In the dependent stack, you can pass it via (custom) props.
// file: ./lib/second-stack.ts
export interface SecondStackProps extends StackProps {
importedVpc: IVpc;
}
export class SecondStack extends Stack {
constructor(scope: cdk.Construct, id: string, props: SecondStackProps) {
super(scope, id, props);
const importedVpc = props.importedVpc;
// do sth. with your imported resource
}
}
Why is that working you may ask...?
It works because CDK generates the Resource IDs and during the synth phase it can put the tokens for the resources inside the generated templates.
This doesn't work, when you have separated cdk-apps / deployments of stacks, because CDK can't resolve the (existing) Resource ID Tokens during cdk-synth.
With that, you need to have another approach:
// file: ./lib/first-stack-separated-deployment.ts
// import statements
export class FirstStackSeparatedDeployment extends Stack {
cfnVpcId: CfnOutput;
constructor(...) {
// setup
const vpc = new Vpc(this, "VPC", {...});
this.cfnVpcId= new cdk.CfnOutput(this, "FirstStackCfnVpcId", {
value: vpc.vpcId,
exportName: "UniqueNameForYourVpcId"
});
}
}
In the other stack, which requires the already deployed resource, you do the following:
// file: ./lib/second-stack-separated-deployment.ts
export class SecondStackSeparatedDeployment extends Stack {
constructor(...) {
// setup
const vpcId = Fn.importValue("UniqueNameForYourVpcId")
const importedVpc = ec2.Vpc.fromVpcAttributes(this, "ImportedVpc", {
vpcId: vpcId,
availabilityZones: [this.region],
})
// proceed further...
}
}
Basically, you take the exportName from the CfnOutput Construct as an identifier and import it via the core Fn.importValue.
With that approach, the first stack already needs to be deployed.

Discriminator multi-tenant setup with SpringSecurity plugin in Grails

I'm trying to setup a discriminator-based multi-tenant system based on the documentation found in the GORM guide
Do determine which tenant that is using the system I want to use the Spring Security Plugin and show data depending on user id.
This is my tenantresolver:
class MyTenantResolver implements AllTenantsResolver {
def springSecurityService
#Override
Iterable<Serializable> resolveTenantIds() {
return new DetachedCriteria(User)
.distinct('id')
.list()
}
#Override
Serializable resolveTenantIdentifier() throws TenantNotFoundException {
return springSecurityService.currentUser.id
}
}
And I add the springsecurityService in the resources.groovy file:
beans = {
myTenantResolver(MyTenantResolver) {
springSecurityService = ref("springSecurityService")
}
}
Unfortunately this causes a circular dependency:
The dependencies of some of the beans in the application context form a cycle:
grailsCacheFilter
↓
(inner bean)#6c1f419f
↓
urlMappingsHandlerMapping
↓
openSessionInViewInterceptor
┌─────┐
| hibernateDatastore
↑ ↓
| hibernateConnectionSourceFactory
↑ ↓
| myTenantResolver
↑ ↓
| springSecurityCoreSpringSecurityService
↑ ↓
| transactionManager
└─────┘
What can I do differently to be able to check against the logged in user?
(Using Grails 3.2.8 and Spring Security 3.1.1)
Finally solved it by fetching the session through the RequestContextHolder:
class MyTenantResolver implements AllTenantsResolver {
#Override
Iterable<Serializable> resolveTenantIds() {
return new DetachedCriteria(Customer)
.distinct('id')
.list()
}
#Override
Serializable resolveTenantIdentifier() throws TenantNotFoundException {
User.withTransaction {
def session = RequestContextHolder.currentRequestAttributes().getSession()
return User.read(session.getAttribute('SPRING_SECURITY_CONTEXT').authentication.principal.id).customerId
}
}
}
You may want to take a look at the sample project over on grails-samples. It has a fully functional "user as tenant" tenant resolver in place to use as a base for your own work.

XText cross-reference to an non-DSL resource

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.
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)
}

Resources