Skip to content

Commit

Permalink
Tags: new tag feature with service aggregation and custom module support
Browse files Browse the repository at this point in the history
  • Loading branch information
eledhwen committed May 10, 2021
1 parent 32f33a9 commit b830d3d
Show file tree
Hide file tree
Showing 13 changed files with 203 additions and 55 deletions.
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Add the following in your `pom.xml`:
<dependency>
<groupId>com.noleme</groupId>
<artifactId>noleme-vault</artifactId>
<version>0.13</version>
<version>0.14</version>
</dependency>
```

Expand Down Expand Up @@ -105,7 +105,8 @@ Other features that will need to be documented include:
* service aliasing
* service closing
* service container composition
* custom modules
* service tagging & aggregation
* custom and generic modules
* custom preprocessing routines

_TODO_
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<groupId>com.noleme</groupId>
<artifactId>noleme-vault</artifactId>
<version>0.13</version>
<version>0.14</version>
<packaging>jar</packaging>

<name>Noleme Vault</name>
Expand Down
11 changes: 10 additions & 1 deletion src/main/java/com/noleme/vault/container/definition/Tag.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.noleme.vault.container.definition;

import com.fasterxml.jackson.databind.node.ObjectNode;

/**
* @author Pierre LECERF ([email protected])
* Created on 08/05/2021
Expand All @@ -8,11 +10,13 @@ public class Tag
{
private final String identifier;
private final String service;
private final ObjectNode node;

public Tag(String identifier, String service)
public Tag(String identifier, String service, ObjectNode node)
{
this.identifier = identifier;
this.service = service;
this.node = node;
}

public String getIdentifier()
Expand All @@ -24,4 +28,9 @@ public String getService()
{
return this.service;
}

public ObjectNode getNode()
{
return this.node;
}
}
17 changes: 11 additions & 6 deletions src/main/java/com/noleme/vault/factory/VaultFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -138,13 +138,18 @@ private Map<String, ServiceDefinition> computeDefinitionMap(Definitions definiti

for (String identifier : definitions.tags().identifiers())
{
if (definitions.services().has(identifier) && !(definitions.services().get(identifier) instanceof ServiceTag))
throw new VaultCompilationException("Tag identifier "+identifier+" is in conflict with service definition "+definitions.services().get(identifier).toString());
if (!definitions.services().has(identifier))
{
logger.debug("Tag identifier {} has not been declared and thus automatic tag aggregation cannot be performed", identifier);
continue;
}
if (!(definitions.services().get(identifier) instanceof ServiceTag))
{
logger.debug("Tag aggregate {} has apparently been overridden by definition {}", identifier, definitions.services().get(identifier).toString());
continue;
}

var serviceTag = definitions.services().has(identifier)
? (ServiceTag) definitions.services().get(identifier)
: new ServiceTag(identifier)
;
ServiceTag serviceTag = (ServiceTag) definitions.services().get(identifier);

for (Tag tag : definitions.tags().forIdentifier(identifier))
serviceTag.addEntry(tag.getService());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ public class VaultCompositeParser implements VaultParser
private final List<VaultModule> modules = Lists.of(
new VariableResolvingModule(),
new VariableReplacementModule(),
new TagModule(),
new ServiceModule()
);

Expand Down
30 changes: 14 additions & 16 deletions src/main/java/com/noleme/vault/parser/module/ServiceModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public void process(ObjectNode json, Definitions definitions) throws VaultParser
String identifier = entry.getKey();
JsonNode node = entry.getValue();
ObjectNode serviceNode = node.isTextual()
? Json.newObject().put("value", node.asText())
? Json.newObject().put("marker", node.asText())
: (ObjectNode) entry.getValue()
;

Expand All @@ -58,7 +58,7 @@ private void extractService(ObjectNode definition, Definitions definitions) thro
def = this.generateAlias(definition, definitions.tags());
else if (definition.has("method"))
def = this.generateProvider(definition, definitions.tags());
else if (definition.has("value"))
else if (definition.has("marker"))
def = this.generateMarkerDefinition(definition, definitions.tags());
else
def = this.generateInstantiation(definition, definitions.tags());
Expand Down Expand Up @@ -115,6 +115,8 @@ private ServiceDefinition generateProvider(ObjectNode definition, Tags tags) thr
}

/**
* Marker definitions rely on short-hand string notations.
* As it stands, no feature relies on markers ; tag declarations did for a short lapse, but were moved to a separate module.
*
* @param definition
* @param tags
Expand All @@ -123,11 +125,9 @@ private ServiceDefinition generateProvider(ObjectNode definition, Tags tags) thr
*/
private ServiceDefinition generateMarkerDefinition(ObjectNode definition, Tags tags) throws VaultParserException
{
String value = definition.get("value").asText();
if (value.equals("tag"))
return new ServiceTag(definition.get("identifier").asText());
String marker = definition.get("marker").asText();

throw new VaultParserException("An unknown marker of type "+value+" was found.");
throw new VaultParserException("An unknown marker of type "+marker+" was found.");
}

/**
Expand Down Expand Up @@ -229,19 +229,17 @@ private void extractTags(ObjectNode definition, Tags tags) throws VaultParserExc

for (JsonNode tagNode : definition.get("tags"))
{
Tag tag;
if (tagNode.isTextual())
tag = new Tag(tagNode.asText(), serviceIdentifier);
else {
if (!tagNode.has("id"))
throw new VaultParserException("Service "+serviceIdentifier+" has an object tag declaration without and 'id'.");
ObjectNode node = tagNode.isTextual()
? Json.newObject().put("id", tagNode.asText())
: (ObjectNode) tagNode
;

String id = tagNode.get("id").asText();
if (!node.has("id"))
throw new VaultParserException("Service "+serviceIdentifier+" has an object tag declaration without and 'id'.");

tag = new Tag(id, serviceIdentifier);
}
String id = node.get("id").asText();

tags.register(tag);
tags.register(new Tag(id, serviceIdentifier, node));
}
}
}
49 changes: 49 additions & 0 deletions src/main/java/com/noleme/vault/parser/module/TagModule.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package com.noleme.vault.parser.module;

import com.fasterxml.jackson.databind.node.ObjectNode;
import com.noleme.vault.container.definition.Definitions;
import com.noleme.vault.container.definition.ServiceTag;
import com.noleme.vault.exception.VaultParserException;

import static com.noleme.commons.function.RethrowConsumer.rethrower;

/**
* @author Pierre LECERF ([email protected])
* Created on 10/05/2021
*/
public class TagModule implements VaultModule
{
@Override
public String identifier()
{
return "tags";
}

@Override
public void process(ObjectNode json, Definitions definitions) throws VaultParserException
{
json.fields().forEachRemaining(rethrower(entry -> {
String identifier = entry.getKey();
ObjectNode tagNode = (ObjectNode) entry.getValue();

if (tagNode.has("identifier") && !identifier.equals(tagNode.get("identifier").asText()))
throw new VaultParserException("A tag was declared with conflicting identifiers, the shorthand notation '"+identifier+"' is different from the 'identifier' field of value '"+tagNode.get("identifier").asText()+"' found in the declaration ");

tagNode.put("identifier", identifier);

this.extractTag(tagNode, definitions);
}));
}

/**
*
* @param definition
* @param definitions
*/
private void extractTag(ObjectNode definition, Definitions definitions)
{
String identifier = definition.get("identifier").asText();

definitions.services().set(identifier, new ServiceTag(identifier));
}
}
2 changes: 2 additions & 0 deletions src/main/java/com/noleme/vault/reflect/LenientClassUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@ public static Pair<Method, Object[]> getLenientMethod(Class<?> type, String meth
/* Then, we perform a method lookup in order to find signatures where we can convert non-matching types using one or several of the indexed convertible arguments */
METHOD_LOOP: for (Method method : type.getMethods())
{
if (!method.getName().equals(methodName))
continue;
if (method.getParameterTypes().length != parameterTypes.length)
continue;

Expand Down
118 changes: 91 additions & 27 deletions src/test/java/com/noleme/vault/tag/CompositeTest.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,23 @@
package com.noleme.vault.tag;

import com.noleme.json.Json;
import com.noleme.vault.container.Cellar;
import com.noleme.vault.container.Invocation;
import com.noleme.vault.container.definition.Definitions;
import com.noleme.vault.container.definition.ServiceInstantiation;
import com.noleme.vault.container.definition.ServiceTag;
import com.noleme.vault.container.definition.Tag;
import com.noleme.vault.exception.VaultInjectionException;
import com.noleme.vault.factory.VaultFactory;
import com.noleme.vault.parser.VaultCompositeParser;
import com.noleme.vault.parser.module.GenericModule;
import com.noleme.vault.service.StringProvider;
import com.noleme.vault.service.tag.ComponentService;
import com.noleme.vault.service.tag.CompositeService;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

import java.util.ArrayList;
import java.util.List;

/**
Expand All @@ -19,7 +29,7 @@ public class CompositeTest
private static final VaultFactory factory = new VaultFactory();

@Test
void compositeTest()
void compositeTest__builds()
{
Assertions.assertDoesNotThrow(() -> {
var definitions = factory.parser().extractOrigin(List.of(
Expand All @@ -44,59 +54,113 @@ void compositeTest()
}

@Test
void compositeWithFailsafeTest()
void compositeTest__does_not_build()
{
Assertions.assertDoesNotThrow(() -> {
Assertions.assertThrows(VaultInjectionException.class, () -> {
var definitions = factory.parser().extractOrigin(List.of(
"com/noleme/vault/parser/tag/composite_with_failsafe.yml",
"com/noleme/vault/parser/tag/composite_without_declaration.yml",
"com/noleme/vault/parser/tag/component.yml"
), new Definitions());

var cellar = factory.populate(new Cellar(), definitions);

var composite = cellar.getService("composite_service", CompositeService.class);
Assertions.assertTrue(composite.contains("string_a"));
Assertions.assertTrue(composite.contains("string_b"));
Assertions.assertTrue(composite.contains("string_c"));
Assertions.assertEquals(4, composite.size());
factory.populate(new Cellar(), definitions);
});
}

@Test
void emptyCompositeTest()
void emptyCompositeTest__builds()
{
Assertions.assertThrows(VaultInjectionException.class, () -> {
var definitions = factory.parser().extractOrigin(List.of(
"com/noleme/vault/parser/tag/composite.yml"
), new Definitions());

var cellar = factory.populate(new Cellar(), definitions);
Assertions.assertDoesNotThrow(() -> {
var cellar = factory.populate(new Cellar(), "com/noleme/vault/parser/tag/composite.yml");

var composite = cellar.getService("composite_service", CompositeService.class);
Assertions.assertEquals(0, composite.size());
});
}

@Test
void emptyCompositeWithFailsafeTest()
void compositeTest__conflictsWithService()
{
Assertions.assertDoesNotThrow(() -> {
var cellar = factory.populate(new Cellar(), "com/noleme/vault/parser/tag/conflict_with_service.yml");

var notComposite = cellar.getService("composite_service_components");
Assertions.assertTrue(notComposite instanceof StringProvider);
});
}

@Test
void customConfig__builds()
{
Assertions.assertDoesNotThrow(() -> {
var parser = new VaultCompositeParser().register(new CustomModule());
var factory = new VaultFactory(parser);

var definitions = factory.parser().extractOrigin(List.of(
"com/noleme/vault/parser/tag/composite_with_failsafe.yml"
"com/noleme/vault/parser/tag/custom_module.yml",
"com/noleme/vault/parser/tag/component.yml"
), new Definitions());

var cellar = factory.populate(new Cellar(), definitions);

var composite = cellar.getService("composite_service", CompositeService.class);
Assertions.assertEquals(0, composite.size());
var composite = cellar.getService("my_custom_composite", CustomComposite.class);
Assertions.assertEquals(3, composite.size());
Assertions.assertEquals(7, composite.weight());
});
}

@Test
void tagConflictWithServiceTest()
public static class CustomModule extends GenericModule<CustomConfig>
{
Assertions.assertThrows(VaultInjectionException.class, () -> {
factory.populate(new Cellar(), "com/noleme/vault/parser/tag/conflict_with_service.yml");
});
public CustomModule()
{
super("custom", CustomConfig.class, CustomModule::process);
}

private static void process(CustomConfig cfg, Definitions defs)
{
if (!cfg.enabled)
return;

var definition = new ServiceInstantiation(cfg.name, CustomComposite.class.getName());

for (String tagName : cfg.tagNames)
{
if (!defs.services().has(tagName))
defs.services().set(tagName, new ServiceTag(tagName));

for (Tag tag : defs.tags().forIdentifier(tagName))
{
CustomTag ctag = Json.fromJson(tag.getNode(), CustomTag.class);

definition.addInvocation(new Invocation("addComponent", "@"+tag.getService()));
definition.addInvocation(new Invocation("addWeight", ctag.weight));
}
}

defs.services().set(cfg.name, definition);
}
}

public static class CustomComposite
{
private final List<ComponentService> components = new ArrayList<>();
private int weight = 0;

public void addComponent(ComponentService component) { this.components.add(component); }
public void addWeight(int weight) { this.weight += weight; }

public int size() { return this.components.size(); }
public int weight() { return this.weight; }
}

public static class CustomConfig
{
public boolean enabled;
public String name;
public List<String> tagNames;
}

public static class CustomTag
{
public int weight = 0;
}
}
Loading

0 comments on commit b830d3d

Please sign in to comment.