spring cloud programmatic metadata generation - spring-cloud

Is there anyway that I can generate some metadata to add to the service when it registers.
We are moving from Eureka to Consul and I need to add a UUID value to the registered metadata when a service starts. So that later I can get this metadata value when I retrieve the service instances by name.
Some background: We were using this excellent front end UI from https://github.com/VanRoy/spring-cloud-dashboard. It is set to use the Eureka model for services in which you have an Application with a name. Each application will have multiple instances each with an instance id.
So with the eureka model there is a 2 level service description whereas the spring cloud model is a flat one where n instances each of which have a service id.
The flat model won't work with the UI that I referenced above since there is no distinction between application name and instance id which is the spring model these are the same.
So if I generate my own instance id and handle it through metadata then I can preserve some of the behaviour without rewriting the ui.

See the documentation on metadata and tags in spring cloud consul. Consul doesn't support metadata on service discovery yet, but spring cloud has a metadata abstraction (just a map of strings). In consul tags created with key=value style are parsed into that metadata map.
For example in, application.yml:
spring:
cloud:
consul:
discovery:
tags: foo=bar, baz
The above configuration will result in a map with foo→bar and baz→baz.

Based on Spencer's answer I added an EnvironmentPostProcessor to my code.
It works and I am able to add the metadata tag I want programmatically but it is a complement to the "tags: foo=bar, baz" element so it overrides that one. I will probably figure a way around it in the next day or so but I thougth I would add what I did for other who look at this answer and say, so what did you do?
first add a class as follows:
#Slf4j
public class MetaDataEnvProcessor implements EnvironmentPostProcessor, Ordered {
// Before ConfigFileApplicationListener
private int order = ConfigFileApplicationListener.DEFAULT_ORDER - 1;
private UUID instanceId = UUID.randomUUID();
#Override
public int getOrder() {
return this.order;
}
#Override
public void postProcessEnvironment(ConfigurableEnvironment environment,
SpringApplication application) {
LinkedHashMap<String, Object> map = new LinkedHashMap<>();
map.put("spring.cloud.consul.discovery.tags", "instanceId="+instanceId.toString());
MapPropertySource propertySource = new MapPropertySource("springCloudConsulTags", map);
environment.getPropertySources().addLast(propertySource);
}
}
then add a spring.factories in resources/META-INF with eht following line to add this processor
org.springframework.boot.env.EnvironmentPostProcessor=com.example.consul.MetaDataEnvProcessor
This works fine except for the override of what is in your application.yml file for tags

Related

OpenAPI spec for reactive REST service using Quarkus

The jouney so far
I'm trying to get a reactive REST service up and running, following the official guide, using RESTEasy and JSON-B.
I also added support for OpenAPI for testing the service following this guide.
Both parts work on their own, the service properly returns the hard coded demo data. The Swagger UI shows the available routes and allows to invoke them.
However, it's not as smooth as I liked it to be...
From the simple, non-reactive routes, schemas have been correctly extracted:
Fruit:
type: object
properties:
description:
type: string
name:
type: string
But from the reactive routes, empty schemas have been extracted. For example, introducing
#GET
#Path("/{name}")
public Uni<Fruit> getOne(#PathParam(value = "name") String name) {
}
resulted in the schema:
UniFruit:
type: object
Is there a way to re-use the existing Fruit schema?
I tried annotating the route, but that didn't have any effect:
#GET
#Path("/{name}")
// #Schema(ref = "#/components/schemas/Fruit") // Nope...
// #Schema(ref = "Fruit") // Nope...
public Uni<Fruit> getOne(#PathParam(value = "name") String name) {
}
Ideally, I wouldn't want to annotate every reactive method separately anyways.
The question
Is there a way to configure project-wide to use the schema of T whenever a route returns Uni<T> or Multi<T>?
I have browsed the MicroProfile OpenAPI spec https://github.com/eclipse/microprofile-open-api/blob/master/spec/src/main/asciidoc/microprofile-openapi-spec.adoc but could not find a way to do the project wide schema change you request. You can do it for a given class, but here the class is Uni and you want different schemas depending on the parameterized type.
So the fix in Quarkus seems like the viable approach unless SmallRye OpenAPI (the implementation) has some specific ways to do this.
In the meantime, you can use the implementation attribute of the #Schema annotation:
#GET
#Path("/{name}")
#APIResponse(
content = #Content(mediaType = MediaType.APPLICATION_JSON,
schema = #Schema(implementation = Fruit.class)))
public Uni<Fruit> getOne(#PathParam(value = "name") String name) {
}

How to use DiscoveredResource to traverse to a single entity resource exposed by a RepositoryRestResource

I'm trying to set up a system with multiple applications connecting by use of a discovery server. I can traverse the hal responses to a specific resource, but I'm looking for a solution to get from a collection resource to a single resource and find the data for a specific entity.
In 1 application I have a RepositoryRestResource exposing some object:
#RestRepositoryResource(collectionResourceRel="things", itemResourceRel="thing") public interface ThingRepo extends CrudRepository<Thing,Long> {}
In some other application, I would like to get to a single thing. I have the id (let's say it's 1) and have the relation name of the collection and the single resource.
I would like to use a DiscoveredResource to get a link to this single item resource, or to the collection resource which I can then somehow expand using the ID (which would require a templated resource).
If at all possible I would not like to just add "/1" at the end of the URL.
this is how I currently create a DiscoveredResource to point to the collection resource:
new DiscoveredResource(new DynamicServiceInstanceProvider(discoveryClient, traverson -> traverson.follow("things"));
Should I and is it possible to add a templated link on a collection resource created by a #RepositoryRestResource. Or is there some other trick I am missing?
The solution here is to add a custom method as a #RestResource which exposes a relation with a templates URL you can then follow to.
Repo:
#RestRepositoryResource(collectionResourceRel="things", itemResourceRel="thing") public interface ThingRepo extends CrudRepository<Thing,Long> {
#RestResource(rel = "thing")
Thing findOneById(#Param("id") Long id);
}
Discovery + traverson:
DiscoveredResource resource = new DiscoveredResource(new DynamicServiceInstanceProvider(discoveryClient, traverson -> traverson.follow("things","search","thing"));
Link link = resource.getLink().expand(id);

enverse-how to customize user id in customized revision listner

i am using jpa with hibernate envers of micro service.
i tried
public class MyRevisionEntityListener implements RevisionListener {
#Override
public void newRevision(Object revisionEntity) {
// If you use spring security, you could use SpringSecurityContextHolder.
final UserContext userContext = UserContextHolder.getUserContext();
MyRevisionEntity mre = MyRevisionEntity.class.cast( revisionEntity );
mre.setUserName( userContext.getUserName() );
}
}
it saves username better.but i want to save user name as"by system" when updates the record by another micro service and when user updates should save the user name as above.how to customize above code as my requirement
It would seem the most logical based on your supplied code might be to simply add a boolean flag to your UserContext thread local variable and simply check that inside the listener.
By default this flag would be false but for your special microservice or business use case, you could alter that state temporarily, run your process, and clear that state after you've finished, very much like a web filter chain works in web applications.

App properties in Spring Cloud Data Flow application

Based on the documentation for Spring Cloud Data Flow (SCDF) only properties that are prefixed by either "deployed." or "app." are considered when deploying an application (be it a source, processor or sink) as part of a stream.
However, I've noticed that besides the prefix, all the properties must be provided as "strings", no matter what their original type is; otherwise, they are simply discarded by SCDF as per this line of code:
propertiesToUse = DeploymentPropertiesUtils.convert(props);
which does this:
public static Map<String, String> convert(Properties properties) {
Map<String, String> result = new HashMap<>(properties.size());
for (String key : properties.stringPropertyNames()) {
result.put(key, properties.getProperty(key));
}
return result;
}
As you can see from the snippet above, it only considers "stringPropertyNames" which filters out anything that is not provided as a "String".
I presume this behaviour is intentional, but why? Why not just pick up all the properties defined by the user with the proper prefix?
Thanks for your support.
All the deployment properties are expected to be of Map<String, String> based on the contract set by the deployer SPI.
I believe one of the reasons is to have String key, values being passed to target deployment platform without serialization/de-serialization hurdle. and, using String values is similar to how one could set those key, value properties as environment variables (for example) in the target deployment platform.

Alternate way of configuring data sources in quartz scheduler properties file

We are configuring the Quartz Scheduler data sources as specified in the documentation that is by providing all the details without encrypting the data base details. By this the data base details are exposed to the other users and any one who have access to the file system can easily get hands on.
So are there any other ways to provide the data sources details using API or provide the database details by encrypting and providing the details as part of quartz.properties file
On class "StdSchedulerFactory" you can call the method "initialize(Properties props)" to set needed propertries by API. Then you don't need a property-file. (See: StdSchedulerFactory API)
Example:
public Scheduler createSchedulerWithProperties(Properties props)
throws SchedulerException {
StdSchedulerFactory factory = new StdSchedulerFactory(props);
return factory.getScheduler();
}
But then you have to set all properties of SchedulerFactory. Also the properties, that have a default value with default constructor. (Search for 'quartz.properties' inside of 'quartz-2.2.X.jar' to get default property values of quartz.)