How can i extend spring-kafka's '#KafkaListener' annotation to create my own annotation with limited attributes? - spring-kafka

I've been using '#KafkaListener' at method level to create consumers. Now, I'm trying to create my own custom annotation by extending '#KafkaListener' and limit the no of attributes (for example, because of some reasons, I don't want to expose attributes like 'errorHandler' 'containerGroup' etc ). Now my question is, to implement this, is there any option to extend the existing '#KafkaListener' ? Please suggest.

Yes, it's quite easy.
#SpringBootApplication
public class So61684460Application {
public static void main(String[] args) {
SpringApplication.run(So61684460Application.class, args);
}
#MyListener(topics = "so61684460", id = "so61684460")
public void listen(String in) {
System.out.println(in);
}
#Bean
public NewTopic topic() {
return TopicBuilder.name("so61684460").partitions(3).replicas(1).build();
}
}
#Retention(RetentionPolicy.RUNTIME)
#Target({ ElementType.TYPE, ElementType.METHOD, ElementType.ANNOTATION_TYPE })
#KafkaListener(concurrency = "${my.concurrency}")
#interface MyListener {
#AliasFor(annotation = KafkaListener.class, attribute = "id")
String id();
#AliasFor(annotation = KafkaListener.class, attribute = "topics")
String[] topics() default "";
}
As you can see, as well as restricting the visibility of some attributes, you can make them required (id above), change the default value, or set hard-coded or parameterized values in the invisible attributes (concurrency above).
This is described in the documentation.

Related

ReactiveUI Observable as Property Helper equivalent in Prism?

I use ReactiveUI and Prism in different apps and there are things that i would take from both.
Especially, the Observable as Property Helper feature from ReactiveUI.
https://reactiveui.net/docs/handbook/observable-as-property-helper/
Is there a way to make a Property observe another property in Prism? I know this can be done with commands, where they can observe properties, but it's not what I need.
One common use case is show/hide elements based on other properties, but i could think of many.
Thanks
If you want to take some good from ReactiveUI, you can start with Rx! Perhaps there is already an implementation of OAPH somewhere, but you can write it yourself. I would start with something like this:
public sealed class ObservableAsPropertyHelper<T> : IDisposable
{
private readonly IDisposable _cleanup;
private readonly BehaviorSubject<T> _subject;
public ObservableAsPropertyHelper(IObservable<T> source,
Action onPropertyChanged,
T initialValue = default,
IScheduler scheduler = null)
{
var subscription = source.StartWith(initialValue)
.SubscribeOn(scheduler ?? CurrentThreadScheduler.Instance)
.Subscribe(_subject);
var onChanged = _subject.Subscribe(_ => onPropertyChanged());
_cleanup = new CompositeDisposable(subscription, onChanged, _subject);
}
public T Value => _subject.Value;
public void Dispose()
{
_cleanup.Dispose();
}
}
public static class ObservableExt
{
public static ObservableAsPropertyHelper<TValue> ToProperty<TObj, TValue>(this
IObservable<TValue> target,
TObj source,
Expression<Func<TObj, TValue>> expression,
TValue initialValue = default,
IScheduler scheduler = null)
where TObj : BindableBase
{
if (!(expression?.Body is MemberExpression memberExpression))
throw new ArgumentNullException(nameof(expression));
string propertyName = memberExpression.Member.Name;
return new ObservableAsPropertyHelper<TValue>(target,
target.RaisingPropertyChanged(propertyName),
initialValue,
scheduler);
}
}
To make it work you need Reactive Extensions
Be cearful it's just a concept, i dont't test it yet.

Spring OAuth2 Making `state` param at least 32 characters long

I am attempting to authorize against an external identity provider. Everything seems setup fine, but I keep getting a validation error with my identity provider because the state parameter automatically tacked onto my authorization request is not long enough:
For example:
&state=uYG5DC
The requirements of my IDP say that this state param must be at least 32-characters long. How can I programmatically increase the size of this auto-generated number?
Even if I could generate this number myself, it is not possible to override with other methods I have seen suggested. The following attempt fails because my manual setting of ?state=abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz is superceded by the autogenerated param placed after it during the actual request:
#Bean
public OAuth2ProtectedResourceDetails loginGovOpenId() {
AuthorizationCodeResourceDetails details = new AuthorizationCodeResourceDetails() {
#Override
public String getUserAuthorizationUri() {
return super.getUserAuthorizationUri() + "?state=abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz";
}
};
details.setClientId(clientId);
details.setAccessTokenUri(accessTokenUri);
details.setUserAuthorizationUri(userAuthorizationUri);
details.setScope(Arrays.asList("openid", "email"));
details.setPreEstablishedRedirectUri(redirectUri);
details.setUseCurrentUri(true);
return details;
}
The 6-character setting seems to be set here, is there a way to override this?
https://github.com/spring-projects/spring-security-oauth/blob/master/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/util/RandomValueStringGenerator.java
With the help of this post:
spring security StateKeyGenerator custom instance
I was able to come up with a working solution.
In my configuration class marked with these annotations:
#Configuration
#EnableOAuth2Client
I configured the following beans:
#Bean
public OAuth2ProtectedResourceDetails loginGovOpenId() {
AuthorizationCodeResourceDetails details = new AuthorizationCodeResourceDetails();
AuthorizationCodeResourceDetails details = new
details.setClientId(clientId);
details.setClientSecret(clientSecret);
details.setAccessTokenUri(accessTokenUri);
details.setUserAuthorizationUri(userAuthorizationUri);
details.setScope(Arrays.asList("openid", "email"));
details.setPreEstablishedRedirectUri(redirectUri);
details.setUseCurrentUri(true);
return details;
}
#Bean
public StateKeyGenerator stateKeyGenerator() {
return new CustomStateKeyGenerator();
}
#Bean
public AccessTokenProvider accessTokenProvider() {
AuthorizationCodeAccessTokenProvider accessTokenProvider = new AuthorizationCodeAccessTokenProvider();
accessTokenProvider.setStateKeyGenerator(stateKeyGenerator());
return accessTokenProvider;
}
#Bean
public OAuth2RestTemplate loginGovOpenIdTemplate(final OAuth2ClientContext clientContext) {
final OAuth2RestTemplate template = new OAuth2RestTemplate(loginGovOpenId(), clientContext);
template.setAccessTokenProvider(accessTokenProvider());
return template;
}
Where my CustomStateKeyGenerator implementation class looks as follows:
public class CustomStateKeyGenerator implements StateKeyGenerator {
// login.gov requires state to be at least 32-characters long
private static int length = 32;
private RandomValueStringGenerator generator = new RandomValueStringGenerator(length);
#Override
public String generateKey(OAuth2ProtectedResourceDetails resource) {
return generator.generate();
}
}

How to get basePackages of #ComponentScan programatically at runtime?

The scanBasePackages of #SpringBootApplication configured as follow:
package com.xxx.boot.sample;
#SpringBootApplication(scanBasePackages = { "com.xxx.boot.sample", "com.xxx.boot.service" })
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
The requirement is we want to integration Apache Dubbo component annotation scan with Spring Boot by programming at runtime for zero properties configuration, not by annotation.
#Configuration
#ConditionalOnClass({ EnableDubboConfig.class, AbstractConfig.class })
#ConditionalOnProperty(prefix = "dubbo", name = "enabled", havingValue = "true", matchIfMissing = true)
public class DubboAutoConfiguration {
/// Dubbo配置
#Configuration
#EnableDubboConfig
#ConditionalOnProperty(prefix = "dubbo.config", name = "enabled", havingValue = "true", matchIfMissing = true)
#EnableConfigurationProperties(DubboProperties.class)
public static class DubboConfigConfiguration {
}
/// Dubbo注解扫描
#Configuration
#ConditionalOnClass({ Service.class, Reference.class })
#ConditionalOnProperty(prefix = "dubbo.annotation", name = "enabled", havingValue = "true", matchIfMissing = true)
public static class DubboAnnotationConfiguration {
#Bean(name = "serviceAnnotationBeanPostProcessor")
#ConditionalOnMissingBean
public ServiceAnnotationBeanPostProcessor serviceAnnotationBeanPostProcessor(BeanFactory beanFactory) {
// 获取 Spring Boot 主入口类所在的包路径
List<String> packagesToScan = AutoConfigurationPackages.get(beanFactory);
if (packagesToScan == null) {
packagesToScan = Collections.emptyList();
}
return new ServiceAnnotationBeanPostProcessor(packagesToScan);
}
#Bean(name = ReferenceAnnotationBeanPostProcessor.BEAN_NAME)
#ConditionalOnMissingBean
public ReferenceAnnotationBeanPostProcessor referenceAnnotationBeanPostProcessor() {
return new ReferenceAnnotationBeanPostProcessor();
}
}
}
But AutoConfigurationPackages#get(BeanFactory) only return "com.xxx.boot.sample", not include "com.xxx.boot.service". I hope return all scanBasePackages value.
By debug, I found the #SpringBootApplication instance is a proxy class instance. I try to get Annotation use Class#getAnnotations, then get scanBasePackages field by reflection. But not success.
Question:
How to get scanBasePackages of #SpringBootApplication or basePackages of #ComponentScan programatically?
It isn't really advisable to try to reuse the scanBasePackages attributes for your own purposes. If you look at the source of #SpringBootApplication you'll see the following:
#AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
String[] scanBasePackages() default {};
#AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
Class<?>[] scanBasePackageClasses() default {};
This is saying that these attributes are an alias for #ComponentScan. Since this annotation can be used on any #Configuration class it's actually legal to have many of them.
The #ComponentScan annotation triggers scanning by the ConfigurationClassParser. Look at the doProcessConfigurationClass method for all the gory details.
If you really want to find the annotation attributes yourself you can do the following:
applicationContext.getBeansWithAnnotation(ComponentScan.class).forEach((name, instance) -> {
Set<ComponentScan> scans = AnnotatedElementUtils.getMergedRepeatableAnnotations(instance.getClass(), ComponentScan.class);
for (ComponentScan scan : scans) {
System.out.println(Arrays.toString(scan.basePackageClasses()));
System.out.println(Arrays.toString(scan.basePackages()));
}
});
This will just get you those two values. You're still not considering any #Condition annotations or any include/exclude filters. You also won't deal with #ComponentScan() which means scan from the current package down.
What Spring Boot tends to do in these circumstances is define a new annotation for a specific purpose. For example, you can use #EntityScan to define where JPA entities are found. We then use AutoConfigurationPackages as the default value if you don't specify any override.

Resolve named registration dependency in Unity with runtime parameter

I have a following problem. I register my components and initialize them in Unity like this (example is for a Console application):
public class SharePointBootstrapper : UnityBootstrapper
{
...
public object Initialize(Type type, object parameter) =>
Container.Resolve(type,
new DependencyOverride<IClientContext>(Container.Resolve<IClientContext>(parameter.ToString())),
new DependencyOverride<ITenantRepository>(Container.Resolve<ITenantRepository>(parameter.ToString())));
public void RegisterComponents()
{
Container
.RegisterType<IClientContext, SharePointOnlineClientContext>(SharePointClientContext.Online.ToString())
.RegisterType<IClientContext, SharePointOnPremiseClientContext>(SharePointClientContext.OnPremise.ToString())
.RegisterType<ITenantRepository, DocumentDbTenantRepository>(SharePointClientContext.Online.ToString())
.RegisterType<ITenantRepository, JsonTenantRepository>(SharePointClientContext.OnPremise.ToString());
}
}
public enum SharePointClientContext
{
Online,
OnPremise
}
class Program
{
static void Main(string[] args)
{
...
bootstrap.RegisterComponents();
var bla = bootstrap.Initialize(typeof(ISharePointManager), SharePointClientContext.Online);
}
}
So, I register my components in MVC, WCF, Console etc. once with RegisterComponents() and initialize them with Initialize().
My question is, if I want to initialize specific named registration at runtime, from e.g. user input, can it be done otherwise as the code presented (with InjectionFactory or similar)?
This code works fine, but I'm not happy with its implementation. I have a feeling that it could be written in RegisterComponents() instead of Initialize() so that it accepts a parameter of some type, but I don't know how to do it.
Or, is maybe my whole concept wrong? If so, what would you suggest? I need to resolve named registration from a parameter that is only known at runtime, regardless of the technology (MVC, WCF, Console, ...).
Thanks!
Instead of doing different registrations, I would do different resolves.
Let's say that you need to inject IClientContext, but you want different implementations depending on a runtime parameter.
I wrote a similiar answer here. Instead of injecting IClientContext, you could inject IClientContextFactory, which would be responsible for returning the correct IClientContext. It's called Strategy Pattern.
public interface IClientContextFactory
{
string Context { get; } // Add context to the interface.
}
public class SharePointOnlineClientContext : IClientContextFactory
{
public string Context
{
get
{
return SharePointClientContext.Online.ToString();
}
}
}
// Factory for resolving IClientContext.
public class ClientContextFactory : IClientContextFactory
{
public IEnumerable<IClientContext> _clientContexts;
public Factory(IClientContext[] clientContexts)
{
_clientContexts = clientContexts;
}
public IClientContext GetClientContext(string parameter)
{
IClientContext clientContext = _clientContexts.FirstOrDefault(x => x.Context == parameter);
return clientContext;
}
}
Register them all, just as you did. But instead of injecting IClientContext you inject IClientContextFactor.
There also another solution where you use a Func-factory. Look at option 3, in this answer. One may argue that this is a wrapper for the service locator-pattern, but I'll leave that discussion for another time.
public class ClientContextFactory : IClientContextFactory
{
private readonly Func<string, IClientContext> _createFunc;
public Factory(Func<string, IClientContext> createFunc)
{
_createFunc = createFunc;
}
public IClientContext CreateClientContext(string writesTo)
{
return _createFunc(writesTo);
}
}
And use named registrations:
container.RegisterType<IClientContext, SharePointOnlineClientContext>(SharePointClientContext.Online.ToString());
container.RegisterType<IClientContext, SharePointOnPremiseClientContext>(SharePointClientContext.OnPremise.ToString());
container.RegisterType<IFactory, Factory>(
new ContainerControlledLifetimeManager(), // Or any other lifetimemanager.
new InjectionConstructor(
new Func<string, IClientContext>(
context => container.Resolve<IClientContext>(context));
Usage:
public class MyService
{
public MyService(IClientContextFactory clientContextFactory)
{
_clientContextFactory = clientContextFactory;
}
public void DoStuff();
{
var myContext = SharePointClientContext.Online.ToString();
IClientContextclientContext = _clientContextFactory.CreateClientContext(myContext);
}
}

ASP.NET Web API Controller Specific Serializer

I've a self host Web API with 2 controllers:
For controller 1, I need default DataContractSerializer (I'm exposing EF 5 POCO)
For controller 2, I need XmlFormatter with parameter UseXmlSerializer set to true (I'm exposing an XmlDocument)
I've tried to set formatters during controller initialization, but the configuration seems to be global, affecting all controllers:
public class CustomConfigAttribute : Attribute, IControllerConfiguration
{
public void Initialize(HttpControllerSettings settings,
HttpControllerDescriptor descriptor)
{
settings.Formatters.XmlFormatter.UseXmlSerializer = true;
}
}
How can I solve this?
You were very much on the right track. But you need to initallise a new instance of the XmlMediaTypeFormatter in your config attributes otherwise you will affect the global reference.
As you know, you need to create 2 attributes based on the IControllerConfiguration interface.
public class Controller1ConfigAttribute : Attribute, IControllerConfiguration
{
public void Initialize(HttpControllerSettings controllerSettings,
HttpControllerDescriptor controllerDescriptor)
{
var xmlFormater = new XmlMediaTypeFormatter {UseXmlSerializer = true};
controllerSettings.Formatters.Clear();
controllerSettings.Formatters.Add(xmlFormater);
}
}
public class Controller2ConfigAttribute : Attribute, IControllerConfiguration
{
public void Initialize(HttpControllerSettings controllerSettings,
HttpControllerDescriptor controllerDescriptor)
{
var xmlFormater = new XmlMediaTypeFormatter();
controllerSettings.Formatters.Clear();
controllerSettings.Formatters.Add(xmlFormater);
}
}
Then decorate your controllers with the relevant attribute
[Controller1ConfigAttribute]
public class Controller1Controller : ApiController
{
[Controller2ConfigAttribute]
public class Controller2Controller : ApiController
{
Configuration:
config.Formatters.Remove(config.Formatters.JsonFormatter);
config.Formatters.Insert(0, new CustomXmlMediaTypeFormatter());
The Custom formatter:
public class CustomXmlMediaTypeFormatter : XmlMediaTypeFormatter
{
public CustomXmlMediaTypeFormatter()
{
UseXmlSerializer = true;
}
}
This seems to work, ok not so elegant.
Removing default Xml Formatter does not work,
so I concluded that the framework is somehow still using it.
Mark Jones' answer has a big downside: By clearing all formatters it is not possible to request different ContentTypes and make use of the relevant formatter.
A better way to enable the XMLSerializer per Controller is to replace the default formatter.
public class UseXMLSerializerAttribute : Attribute, IControllerConfiguration
{
public void Initialize(HttpControllerSettings controllerSettings, HttpControllerDescriptor controllerDescriptor)
{
// Find default XMLFormatter
var xmlFormatter = controllerSettings.Formatters.FirstOrDefault(c => c.SupportedMediaTypes.Any(x => x.MediaType == "application/xml"));
if (xmlFormatter != null)
{
// Remove default formatter
controllerSettings.Formatters.Remove(xmlFormatter);
}
// Add new XMLFormatter which uses XmlSerializer
controllerSettings.Formatters.Add(new XmlMediaTypeFormatter { UseXmlSerializer = true });
}
}
And use it like this:
[UseXMLSerializer]
public TestController : ApiController
{
//Actions
}
I think you could write a custom ActionFilterAttribute.
In OnActionExecuting, store away the original values in the HttpContext and then in OnActionExecuted, restore the original values.
the controllers actions themselves should not be concerned with how the data is serialized. yo should be able to request the data and any format necessary the operation to retrieve the data would be the same.
by default web api serialized to json objects. however if you set the content type of the request to xml is should return the same result, but formatted as xml instead of json.

Resources