Free MuleSoft CONNECT Keynote & Expo Pass Available!

Register now+
Nav

Multi-Level Metadata

Imagine the case where your end user will use an operation that invokes an action provided by a service. To know the metadata that the action requires as arguments or the metadata of what it returns, you need to differentiate the service from the action to be executed. You do this with a metadata key id. For these cases, the metadata key id does not need to be a single String. It can instead contain multiple parts bundled within a complex parameter that holds the information needed to resolve the metadata.

Implementing a Multi-Level Metadata Key

It is necessary to set the order in which parameters of the metadata key are specified, starting with 1. Returning to the example above, the end user will first specify the service field, then the action to invoke. So, the service parameter needs to set order = 1 and the action parameter order = 2.

All the parameters of the POJO representing the MetadataKeyId must be annotated with a @MetadataKeyPart annotation that sets the order value for it. These annotated fields must be of type String.

Here is an example of a metadata key id class:


         
      
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class ActionIdentifier{

  @Parameter
  @MetadataKeyPart(order = 1) (1)
  private String service;

  @Parameter
  @MetadataKeyPart(order = 2) (1)
  private String action;

  @Override
  public String getService() {
    return service;
  }

  @Override
  public String getAction() {
    return action;
  }

  @Override
  public void setService(String service) {
    this.service = service;
  }

  @Override
  public void setAction(String action) {
    this.action = action;
  }
}

The next example shows an operation that uses a MetadataKeyId that is a complex type:


         
      
1
2
3
4
5
@OutputResolver(output = OutputOperationTypeResolver.class)
public Object invoke( @ParameterGroup(name = "Operation") @MetadataKeyId(OperationTypeKeysResolver.class) ActionIdentifier identifier,
                      @TypeResolver(InputOperationTypeResolver.class) Map<String, Object> args){
  return invokeAction(identifier.getService(), identifier.getAction(), args);
}

Next is an example of a TypeKeysResolver that returns each available MetadataKey:


         
      
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class OperationTypeKeysResolver implements TypeKeysResolver {

  @Override
  public Set<MetadataKey> getKeys(MetadataContext context) throws MetadataResolvingException, ConnectionException {
    Set<MetadataKey> keys = new HashSet<>();
    for(String service : getServices()){
      MetadataKeyBuilder key = MetadataKeyBuilder.newKey(service);
      for(String action : getActions(service)){
        key.withChild(MetadataKeyBuilder.newKey(action).build());
      }
      keys.add(key.build());
    }
    return keys;
  }
}

         
      
1
2
3
4
5
6
7
8
public class OutputOperationTypeResolver implements OutputTypeResolver<ActionIdentifier>{

  @Override
  public MetadataType getOutputType(MetadataContext context, ActionIdentifier key)
     throws MetadataResolvingException, ConnectionException {
     // Resolve the output metadata using the ActionIdentifier
  }
}

         
      
1
2
3
4
5
6
7
8
9
public class InputOperationTypeResolver implements {

  @Override
  public MetadataType getInputMetadata(MetadataContext context, ActionIdentifier key)
    throws MetadataResolvingException, ConnectionException {
    // Resolve the input metadata using the ActionIdentifier
  }

}

Partial Fetching

Available since version 1.1

The process of getting each MetadataKey can be very time consuming. In the example that gets all the possible services and the actions of each service, the information you are fetching might be on another server.

Partial fetching enables you to resolve one level of the MetadataKey tree at the time.

The getKeys method from TypeKeysResolver will only return the first level of each tree, not the complete tree of each MetadataKey.

So, the amount of information needed is greatly reduced because you only need to get the possible services. Once a service is selected, you need to provide the next level of the MetadataKey tree.

To make this possible, your key resolver must implement PartialTypeKeysResolver, for example:


         
      
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class OperationTypeKeysResolver implements PartialTypeKeysResolver<ActionIdentifier> {

  @Override
  public Set<MetadataKey> getKeys(MetadataContext context) throws MetadataResolvingException, ConnectionException {
    Set<MetadataKey> keys = new HashSet<>();
    for(String service : getServices()){ (1)
      MetadataKeyBuilder key = MetadataKeyBuilder.newKey(service);
      keys.add(key.build());
    }
    return keys;
  }

  @Override
  public MetadataKey resolveChilds(MetadataContext metadataContext, ServiceOperation key)
      throws MetadataResolvingException, ConnectionException {

    if(key.getService() == null){
      throw new MetadataResolvingException("Missing Service name. Cannot resolve Actions without a service",
                                         FailureCode.INVALID_METADATA_KEY);
    }

    MetadataKeyBuilder key = MetadataKeyBuilder.newKey(key.getService()); (2)
    for(String action : getActions(key.getService())){
      key.withChild(MetadataKeyBuilder.newKey(action).build()); (3)
    }
    return key;
  }

}
1 Only the services are retrieved. The actions of a service will be retrieved on demand.
2 Build a single MetadataKey tree with a new, complete level of metadata, in this case, the actions level.
3 Add the actions of that service as children.

Using User Input As Partial Level

Available since version 1.1

In some cases, you might not be able to provide the end user with a hint about part of your MetadataKey. For example, the universe of options might be too big (a dropdown wiht all the classes in a classpath makes not sense) or when the starting point of the ID is a free input (for example, a query).

Imagine a MetadataKeyId that has a part that is a String representing a Java class. It can be very time consuming to retrieve all the classes, and it is complicated for the user to have so many options on a dropdown.

So, you can signal that a MetadataKeyPart will not be provided by the resolver and must be inserted by the user. You do this by setting the providedByKeyResolver to false value on the MetadataKeyPart annotation.

Here is an example where the POJO representing the MetadataKeyId represents a Java method:


         
      
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class MethodIdentifier{

  @Parameter
  @Alias("class")
  @MetadataKeyPart(order = 1, providedByKeyResolver = false) (1)
  private String clazz;

  @Parameter
  @Alias("method")
  @MetadataKeyPart(order = 2)
  private String methodId;

  @Override
  public String getClazz() {
    return clazz;
  }

  @Override
  public String getMethodId() {
    return methodId;
  }

  @Override
  public void setClazz(String clazz) {
    this.clazz = clazz;
  }

  @Override
  public void setMethodId(String methodId) {
    this.methodId = methodId;
  }
}
1 The clazz field must be inserted by the end user without hints.

In this case, it also means that the getKeys method cannot return all the possible classes:


         
      
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class MethodTypeKeysResolver implements PartialTypeKeysResolver<MethodIdentifier> {

  @Override
  public Set<MetadataKey> getKeys(MetadataContext context) throws MetadataResolvingException, ConnectionException {
    return emptySet(); (1)
  }

  @Override
  public MetadataKey resolveChilds(MetadataContext metadataContext, MethodIdentifier key)
      throws MetadataResolvingException, ConnectionException {

    if(key.getClazz() == null){
      throw new MetadataResolvingException("Missing Class name. Cannot resolve Methods without a target Class",
                                         FailureCode.INVALID_METADATA_KEY);
    }

    MetadataKeyBuilder key = MetadataKeyBuilder.newKey(key.getClazz()); (2)
    for(String methodId : getMethodIds(key.getClazz())){
      key.withChild(MetadataKeyBuilder.newKey(methodId).build()); (3)
    }
    return key;
  }

}
1 Return an empty set of `MetadataKey`s because the end user will provide this information.
2 Build a single MetadataKey tree with a new, complete level of metadata, in this case, the methodIds level.
3 Add the methodIds of that class as children.