Nav

Using Generic Parameters For Extended Behavior

Type definitions in the Module API affect the experience provided to the user. The right construction of types can greatly simplify the API that you expose to the user.

Simplifying the UX with SubTypeMapping

If a parameter represents a generic component such as authentication or destination, multiple valid sets of configurations are allowed. Your authentication can be of different types (basic, digest, ntlm, oauth) but it is a terrible experience for the user to have all the parameters of each authentication available all at once.

For example, if we want an authentication that supports basic, digest and ntlm in a single parameter, our Authentication class would be short, but also confusing:


         
      
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Authentication {

  @Parameter
  private boolean useDigest; (1)

  @Parameter
  private String username;

  @Parameter
  @Password
  private String password;

  @Parameter
  @Optional
  private String domain; (2)

  @Parameter
  @Optional
  private String workstation;

}
1 The parameter useDigest only exists to separate basic and digest
2 domain and workstation are only relevant for an ntlm configuration, making useDigest irrelevant.

It’s easy to get lost in the correct configuration even when you need only four parameters.

Instead, if you use @SubTypeMapping, it’s easy to understand how to use the types from the code itself, and declaring values is more concise:


         
      
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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
@Extension(name = "HTTP")
@SubTypeMapping(baseType = Authentication.class, (1)
    subTypes = {BasicAuthentication.class, DigestAuthentication.class, NtlmAuthentication.class})
public class HttpConnector {

}

public interface Authentication { (2)

  void authenticate(HttpRequestBuilder builder);

}

public class BasicAuthentication implements Authentication { (3)

  @Parameter
  private String username;

  @Parameter
  @Password
  private String password;

}

public class DigestAuthentication implements Authentication { (4)

  @Parameter
  private String username;

  @Parameter
  @Password
  private String password;

}

public class NtlmAuthentication implements Authentication { (5)

  @Parameter
  private String username;

  @Parameter
  @Password
  private String password;

  @Parameter
  @Optional
  private String domain;

  @Parameter
  @Optional
  private String workstation;

}
1 Declaration of the SubTypeMapping
2 Generic Authentication interface
3 Implementation of Authentication as basic
4 Implementation of Authentication as digest
5 Implementation of Authentication as ntlm

Next, declare a parameter of the generic type Authentication:


         
      
1
2
3
4
5
6
@Alias("request")
public class HttpRequesterProvider implements CachedConnectionProvider<HttpExtensionClient> {

  @Parameter
  private Authentication authentication;
}

Now the user is able to declare each of the three possible values in a more concise and coherent way:


         
      
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<http:request-connection>
  <http:authentication>
      <http:basic-authentication username="withBasic" password="123">
  </http:authentication>
</http:request-connection>

<http:request-connection>
  <http:authentication>
      <http:digest-authentication username="withDigest" password="456">
  </http:authentication>
</http:request-connection>

<http:request-connection>
  <http:authentication>
      <http:ntlm-authentication username="withNtlm" password="Beeblebrox" domain="Ursa-Minor"/>>
  </http:authentication>
</http:request-connection>

Extending behavior With Modules Contribution

If you want a module to provide its own method of authentication, use a combination of @Import and @SubTypeMapping.

In this example, we’re adding an oauth authentication to the http module:


         
      
1
2
3
4
5
6
7
@Extension(name = "OAuth")
@Import(type = HttpRequestAuthentication.class) (1)
@SubTypeMapping(baseType = Authentication.class, (2)
    subTypes = {DefaultAuthorizationCodeGrantType.class, ClientCredentialsGrantType.class})
public class OAuthExtension {

}
1 Declare the import from the HTTP Authentication type.
2 Add more subtype mappings to the Authentication type from the OAuth extension.

Now, once the two new authentication methods are implemented, we can parameterize them to the HTTP connector in any application, without modifying any code of the original extension HTTP. That is, for the same application we had above, we can add a new authentication method:


         
      
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<http:request-connection host="localhost" port="${oauth.server.port}">
    <http:authentication> (1)
        <oauth:authorization-code-grant-type (2)
                clientId="${client.id}"
                clientSecret="${client.secret}"
                externalCallbackUrl="${local.callback.url}"
                tokenManager="multitenantOauthConfig"
                localAuthorizationUrl="${local.authorization.url}"
                authorizationUrl="${authorization.url}"
                refreshTokenWhen="#[attributes.statusCode == 500]"
                tokenUrl="${token.url}">
        </oauth:authorization-code-grant-type>
    </http:authentication>
</http:request-connection>
1 The authentication element is the same, but it contains a new element, authorization-code-grant-type
2 The element authorization-code-grant-type is from the oauth namespace