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;
}
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 | 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:
@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
:
@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:
<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:
@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:
<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 |