Output Metadata | MuleSoft Documentation
# Output Metadata
Output metadata is the type resolution for the result of a component. Each component can provide either static or dynamic metadata for the payload and attributes, isolated from each other.
## Declaring an OutputTypeResolver and AttributesTypeResolver
Both `OutputTypeResolver` and `AttributesTypeResolver` implementations handle the requests for resolving a `MetadataType` based on the information provided by the `MetadataContext` and most importantly, using the `MetadataKey` to identify the `MetadataType` required by the end user (who is creating a Mule app). Refer to What is the Metadata of a Component for more information about the `MetadataType`.
The `OutputTypeResolver<T>` and `AttributesTypeResolver<T>` interfaces are parameterized with the generic `T`, which must match the type of the `MetadataKeyId` parameter. The examples here use type `String` for the generic because it is the most common `MetadataKeyId` type. (Other documentation will revisit this generic and go in detail about when the type should change to something other than `String`.)
```
public class OutputEntityResolver
 implements OutputTypeResolver<String>, AttributesTypeResolver<String> {
 @Override
 public String getCategoryName() {
  return "Records";
 }
 @Override
 public String getResolverName() {
  return "OutputEntityResolver";
 }
 @Override
 public MetadataType getOutputType(MetadataContext context, String key)
   throws MetadataResolvingException, ConnectionException {
  switch (key) {
   case "Author_id":
    return context.getTypeLoader().load(AuthorResult.class);
   case "BookList_id":
    return context.getTypeLoader().load(BookListResult.class);
   case "Book_id":
    return context.getTypeLoader().load(BookResult.class);
   default:
    throw new MetadataResolvingException("Unknown key:" + key, INVALID_METADATA_KEY);
  }
 }
 @Override
 public MetadataType getAttributesType(MetadataContext context, String key)
   throws MetadataResolvingException, ConnectionException {
  if ("Book_id".equals(key)){
   return context.getTypeLoader().load(BookAttributes.class);
  }
  // Only Books have Attributes information
  return context.getTypeBuilder().nullType().build();
 }
}
```

The example above uses the `MetadataContext` only to obtain the `typeLoader` for describing a MetadataType based on a Java class, but it could also use the provided Configuration and Connection elements.
## Using the OutputTypeResolver
After you have a `TypeKeysResolver` and an `OutputTypeResolver`, you can add output DataSense support to our Operations and Sources. The main restriction to using an `OutputTypeResolver` with a given `TypeKeysResolver` is that both must belong to the same `category`, which guarantees that the `MetadataKey` provided by the `TypeKeysResolver` can be resolved to a `MetadataType` by the `OutputTypeResolver`:
### Adding DataSense for the Result Payload
```
public class FetchOperations {
 @OutputResolver(output = OutputEntityResolver.class)
 public Map<String,Object> get(@Connection MyConnection connection,
                @MetadataKeyId(EntityKeysResolver.class) String entityKind){
  return connection.getClient().fetch(entityKind);
 }
}
```

Above, the MetadataType for the output is resolved dynamically based on the `entityKind` that is being fetched, so if the `entityKind` is configured with the `Author_id` key provided by the `EntityKeysResolver`, then the `OutputEntityResolver` will be invoked with that `MetadataKey`, and the output for the `get` operation will be resolved to an `AuthorResult` ObjectType.
A source can use the same output type resolution for describing the MetadataType of the object being dispatched to the flow, and its declaration is similar:
```
@MetadataScope(keysResolver = EntityKeysResolver.class,
        outputResolver = OutputEntityResolver.class)
public class ListenerSource extends Source<Map<String, Object>, Void> {
 @MetadataKeyId
 @Parameter
 public String type;
 @Connection
 private ConnectionProvider<MetadataConnection> connection;
 @Override
 public void onStart(SourceCallback<Map<String, Object>, Void> sourceCallback) throws MuleException {
  //...
 }
 @Override
 public void onStop() {
  //...
 }
}
```

Both sources and operations have the same lifecycle for resolving the output type. The first step is to configure the `MetadataKeyId`, and then the `OutputTypeResolver` is invoked with that key in order to resolve the resulting entity MetadataType.
### Adding DataSense for the Result Attributes
Dynamic metadata resolution takes into account that a component’s full output is a `Result<Payload, Attributes>`, not just the payload.
When an operation or source output has a dynamic attributes structure, you can resolve its MetadataType by declaring an `AttributesTypeResolver` (already implemented in the `OutputTypeResolver.java` example) and then adding a reference to it in the operation or source declaration.
In the next example, the `Book` entity can be divided into the content and attributes of the Book, each with its own structures. This division is done so the end user can better understand and use the result of the operation. They can think about the data (which is the content of the Book that goes in the payload) and the metadata that characterizes the payload (the attributes of the Book):
```
public class FetchOperationsWithAttributes {
 @OutputResolver(output = OutputEntityResolver.class,
         attributes = OutputEntityResolver.class)
 public Result<Object, Object> get(@Connection MyConnection connection,
                        @MetadataKeyId(EntityKeysResolver.class) String entityKind){
  if ("Book_id".equals(entityKind)){
   Book book = (Book)connection.getClient().fetch(entityKind);
   return Result.<Object, Object>builder()
          .output(book.content())
          .attributes(book.attributes())
          .build();
  }
  return Result.<Object, Object>builder()
         .output(connection.getClient().fetch(entityKind))
         .build();
 }
}
```

Sources have a declaration similar to the one used for the payload, but it adds an `attributesResolver` reference:
```
@MetadataScope(keysResolver = EntityKeysResolver.class,
        outputResolver = OutputEntityResolver.class,
        attributesResolver = OutputEntityResolver.class)
public class ListenerSource extends Source<Map<String, Object>, Object> {
 @MetadataKeyId
 @Parameter
 public String type;
 //...
}
```

## Output Metadata with a User-Defined MetadataKey
The case for user-defined MetadataKeys also applies for the output of a component. In the case of a query, you do not have a predefined set of possible MetadataKeys. Instead, you have a parameter with a value that characterizes the output type or structure.
For example, the Database connector has the `select` operation. Its output depends on what entities are queried:
```
 @OutputResolver(output = SelectMetadataResolver.class)
 public List<Map<String, Object>> select(@MetadataKeyId String sql, @Config DbConnector connector){
  // ...
 }
```

With the `SelectMetadataResolver` declared as:
```
public class SelectMetadataResolver extends BaseDbMetadataResolver implements OutputTypeResolver<String> {
 @Override
 public String getCategoryName() {
  return "DbCategory";
 }
 @Override
 public String getResolverName() {
  return "SelectResolver";
 }
 @Override
 public MetadataType getOutputType(MetadataContext context, String query)
   throws MetadataResolvingException, ConnectionException {
  if (isEmpty(query)) {
   throw new MetadataResolvingException("No Metadata available for an empty query", FailureCode.INVALID_METADATA_KEY);
  }
  ResultSetMetaData statementMetaData = getStatementMetadata(context, parseQuery(query));
  if (statementMetaData == null) {
   throw new MetadataResolvingException(format("Driver did not return metadata for the provided SQL: [%s]", query),
                      FailureCode.INVALID_METADATA_KEY);
  }
  ObjectTypeBuilder record = context.getTypeBuilder().objectType();
  Map<String, MetadataType> recordModels = resolveRecordModels(statementMetaData);
  recordModels.entrySet()
        .forEach(e -> record.addField().key(e.getKey()).value(e.getValue()));
  return record.build();
 }
}
```
With the `SelectMetadataResolver` declared as:
```
public class SelectMetadataResolver extends BaseDbMetadataResolver implements OutputTypeResolver<String> {
 @Override
 public String getCategoryName() {
  return "DbCategory";
 }
 @Override
 public String getResolverName() {
  return "SelectResolver";
 }
 @Override
 public MetadataType getOutputType(MetadataContext context, String query)
   throws MetadataResolvingException, ConnectionException {
  if (isEmpty(query)) {
   throw new MetadataResolvingException("No Metadata available for an empty query", FailureCode.INVALID_METADATA_KEY);
  }
  ResultSetMetaData statementMetaData = getStatementMetadata(context, parseQuery(query));
  if (statementMetaData == null) {
   throw new MetadataResolvingException(format("Driver did not return metadata for the provided SQL: [%s]", query),
                      FailureCode.INVALID_METADATA_KEY);
  }
  ObjectTypeBuilder record = context.getTypeBuilder().objectType();
  Map<String, MetadataType> recordModels = resolveRecordModels(statementMetaData);
  recordModels.entrySet()
        .forEach(e -> record.addField().key(e.getKey()).value(e.getValue()));
  return record.build();
 }
}
```

## List Metadata Automatic Wrapping
In the `select` example, you can see that the operation returns a `List<Map<String, Object>`. This makes sense because the result of a select query is multiple record entries, but the `SelectMetadataResolver` does not describe an ArrayType in the `getOutputType` method. Instead the returned MetadataType represents a single `record` structure.
Why is that?
As you might know already, the operation is returning an ArrayType (List, PagingProvider, and so on), so you only need to describe the `generic` type of the array. The output and attributes TypeResolvers always resolve the MetadataType of _the elements of the collection_ and not the _collection_ type itself. This allows for greater reuse of the MetadataType resolvers and reduces the amount of code needed.
Take into account that the resolved attributes will _also_ be the attributes of the _elements_ of the collection, _not_ attributes of the operation’s `List` output.
## Resolving Dynamic Output Metadata without MetadataKey
As with input, the output of an operation can be resolved without a specific `MetadataKey`, being the dynamic type affected by the Configuration or Connection of the Component. Again, to declare a keyless resolver, you simply skip the `MetadataKeyId` parameter and ignore the MetadataKey in the TypeResolvers:
```
public class UserTypeResolver implements OutputTypeResolver, AttributesTypeResolver {
 @Override
 public String getCategoryName() {
  return "User";
 }
 @Override
 public MetadataType getOutputType(MetadataContext context, Object key)
   throws MetadataResolvingException, ConnectionException {
  // The `key` parameter will be `null` if the fetch is performed
  // as a `KeyLess` Metadata resolution. We'll just ignore it.
  String schema = getUserSchema(context);
  return new JsonTypeLoader(schema).load("http://demo.user")
      .orElseThrow(() -> new MetadataResolvingException("No Metadata is available for the User",
                               FailureCode.NO_DYNAMIC_TYPE_AVAILABLE));
 }
 @Override
 public MetadataType getAttributesType(MetadataContext context, Object key)
   throws MetadataResolvingException, ConnectionException {
  // The `key` parameter will be `null` if the fetch is performed
  // as a `KeyLess` Metadata resolution. We'll just ignore it.
  String schema = getUserSchema(context);
  return new JsonTypeLoader(schema).load("http://demo.attributes")
      .orElseThrow(() -> new MetadataResolvingException("No Metadata is available for the User Attributes",
                               FailureCode.NO_DYNAMIC_TYPE_AVAILABLE));
 }
 private String getUserSchema(MetadataContext context) throws MetadataResolvingException, ConnectionException {
  return context.<DemoConnection>getConnection()
   .orElseThrow(() -> new MetadataResolvingException("A connection is required to resolve Metadata but none was provided",
                            FailureCode.INVALID_CONFIGURATION))
   .describeUser();
 }
}
```

```
public class UserOperations {
 @OutputResolver(output = UserTypeResolver.class, attributes=UserTypeResolver.class)
 public Result<Map<String,Object>, Object> getUser(@Connection DemoConnection connection){
  User user = connection.getUser();
  return Result.<Map<String,Object>, Object>.builder()
         .output(user.personalInfo())
         .attributes(user.accountInfo())
         .build().
 }
}
```