How to add OpenApi/Swagger securitySchemes in Apache Camels RouteBuilder.restConfiguration()? - spring-boot

I try to add springdoc-openapi-ui and camel-springdoc-starter. Works not so bad.
For now i've trouble with the context path '/camel' and with the missing securitySchemes.
Does anyone have an idea how to do this
How do i get such a configuration?
{
"openapi": "3.0.1",
"info": {
"title": "some title",
"version": "0.8.15-SNAPSHOT"
},
"servers": [
{
"url": "http://localhost"
}
],
"security": [
{
"Keycloak": []
}
],
"components": {
"schemas": {
...
},
"securitySchemes": {
"Keycloak": {
"type": "oauth2",
"name": "Keycloak",
"flows": {
"password": {
"tokenUrl": "http://localhost:8080/auth/realms/sample-app/protocol/openid-connect/token",
"scopes": {
"email": "",
"profile": ""
}
}
}
}
}
}
}
Using something like this:
#Override
public void configure() {
restConfiguration()
.component("servlet")
.apiProperty("api.title", "RDF-Pub Server")
.apiProperty("api.version", appVersion)
.apiProperty("api.components.securitySchemes.Keycloak.type", "oauth2")
.apiProperty("api.components.securitySchemes.Keycloak.name", "Keycloak")
.apiProperty("api.components.securitySchemes.Keycloak.flows.password.tokenUrl", "http://localhost:8080/auth/realms/example-app/protocol/openid-connect/token")
.apiProperty("api.components.securitySchemes.Keycloak.flows.password.scopes.email", "")
.apiProperty("api.components.securitySchemes.Keycloak.flows.password.scopes.profile", "");
}

found that way:
private void routeCollection() {
rest()
.securityDefinitions()
.oauth2("local_keycloak", "Using a local keycloak instance")
.password("http://localhost:8080/auth/realms/sample-app/protocol/openid-connect/token")
.withScope("email", "accessing the email address")
.withScope("profile", "accessing the profile")
.end()
.end()
.get("/{owner}/{collection}")
.route()
.routeId("readCollection")
.process("processorA")
.process("processorB")
.endRest();
}
I extracted the stuff in a method:
private RestDefinition oauth2Rest() {
return rest()
.securityDefinitions()
.oauth2("local_keycloak", "Using a local keycloak instance")
.password("http://localhost:8080/auth/realms/sample-app/protocol/openid-connect/token")
.withScope("email", "accessing the email address")
.withScope("profile", "accessing the profile")
.end()
.end();
}
and called it once in the configuration:
#Override
public void configure() {
oauth2Rest();
// it tells Camel how to configure the REST service
restConfiguration()
...
Result:

Related

Schema validation failed by Swagger/OpenAPI online validator

I have a config my Swagger schema, /api-docs -> openapi 3.0.1:
#Configuration
public class SwaggerConfig {
#Bean
public OpenAPI customOpenAPI() {
return new OpenAPI()
.components(new Components().addSecuritySchemes("Bearer",
new SecurityScheme().type(SecurityScheme.Type.HTTP).scheme("Bearer")
.in(SecurityScheme.In.HEADER).name("Authorization")))
.info(new Info().title("SSM API").version("0.1"));
}
}
Example of controller:
#Tag(name = "MetalBalance", description = "Контроллер баланса металла")
#RestController
#RequestMapping(path = "/metal-balance", produces = MediaType.APPLICATION_JSON_VALUE)
#SecurityRequirement(name = "Bearer")
#ResponseBody
#RequiredArgsConstructor
public class MetalBalanceController {
...
#DeleteMapping("/operations/{id}")
#Operation(summary = "Удаление операции над агрегатом")
#Parameters(value = {
#Parameter(in = ParameterIn.PATH, name = "id", example = "720050516121420278143",
description = "Идентификатор операции",
content = #Content(schema = #Schema(type = "integer"))),
})
#RolesAllowed({"MASTER_SHIHTA", "NACH_SMEN_VZKKC1", "MASTER_KONV", "FERRO_R6", "NACH_SMEN_VZKKC1", "FERRO_R", "DESIGNER",
"KONV_R", "LAB_ST_PR", "NACH_SMEN_VZKKC1", "NACH_UPPVS", "ING_CEN_CEH", "RAB_UPK", "UPK_R",
"MASTER_VAK", "VAK_R", "R_VAKUUMAT", "MASTER_UDM", "UDM_R"})
public void deleteOperation(#PathVariable("id") BigInteger operationId) {
balanceService.deleteUnitOperation(operationId);
}
}
openapi response, what i have from '/api-docs' endpoint:
"/metal-balance/operations/{id}": {
"delete": {
"tags": [
"MetalBalance"
],
"summary": "Удаление операции над агрегатом",
"operationId": "deleteOperation",
"parameters": [
{
"name": "id",
"in": "path",
"description": "Идентификатор операции",
"required": true,
"example": 720050516121420300000,
"content": {
"*/*": {
"schema": {
"type": "integer"
}}}}],
"responses": {
"200": {
"description": "OK"
},
"400": {
"description": "Bad Request",
"content": {
"*/*": {
"schema": {
"type": "object"
}}}}},
"security": [
{
"Bearer": []}
]}}
When I try to validate it by Swagger/OpenAPI online validator I recieved the exceptions, attached: Validation Error
How to tune my config correct?
Thanks in advance!
There's no need to wrap the schema into content in case of path parameters and primitive parameters in general (i.e. numbers/strings/booleans). The #Content annotation is typically only used with request bodies and responses.
Replace
#Parameter(in = ParameterIn.PATH, name = "id", example = "720050516121420278143",
description = "Идентификатор операции",
content = #Content(schema = #Schema(type = "integer"))),
with
#Parameter(in = ParameterIn.PATH, name = "id", example = "720050516121420278143",
description = "Идентификатор операции",
schema = #Schema(type = "integer")), // <----------

How to log Serilog debug messages conditionally in .NET 6?

I am working on a .net 6 application. I have used Serilog for logging. I have received a request to log the debug logs based on the database settings.
Below is my setting table:
Create table ApplicationSettings
(
Id UNIQUEIDENTIFIER PRIMARY KEY NOT NULL,
Name VARCHAR(500) NOT NULL,
[Value] VARCHAR(200) NOT NULL,
CreatedOn DATETIMEOFFSET,
Active BIT
)
INSERT INTO ApplicationSettings VALUES(NEWID(),'Log Debug Message','true', GETDATE(),1)
SELECT * FROM ApplicationSettings
If "Log Debug Message is true" in above table then only I have to log the debug messages of Serilog or else I don't have to log the debug message.
Here is my appsettings.json
{
"ConnectionStrings": {
"DefaultConnection": "Server=Something;Database=SampleDb;Trusted_Connection=True;MultipleActiveResultSets=true;"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"Serilog": {
"Using": [ "Serilog.Enrichers.ClientInfo", "Serilog.Sinks.MSSqlServer" ],
"MinimumLevel": {
"Default": "Debug",
"Override": {
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Warning",
"System": "Warning",
"System.Net.Http.HttpClient": "Warning"
}
},
"Enrich": [ "FromLogContext", "WithMachineName", "WithClientIp", "WithClientAgent" ],
"WriteTo": [
{
"Name": "MSSqlServer",
"Args": {
"connectionString": "Server=Something;Database=SampleDb;Trusted_Connection=True;MultipleActiveResultSets=true;",
"sinkOptionsSection": {
"tableName": "tblLogs",
"autoCreateSqlTable": true
},
"restrictedToMinimumLevel": "Debug",
"columnOptionsSection": {
"primaryKeyColumnName": "Id",
"addStandardColumns": [ "LogEvent", "SourceContext" ],
"removeStandardColumns": [ "Properties" ],
"additionalColumns": [
{
"ColumnName": "ClientIP",
"PropertyName": "ClientIp",
"DataType": "nvarchar"
}
]
}
}
}
]
}
}
Program.cs
using Serilog;
using Serilog.Events;
var builder = WebApplication.CreateBuilder(args);
#region Configure serilog
builder.Logging.ClearProviders();
IConfigurationRoot configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json", false, true)
.AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Production"}.json", optional: true)
.Build();
var logger = new LoggerConfiguration()
.ReadFrom.Configuration(configuration)
.MinimumLevel.Override("Microsoft", LogEventLevel.Error)
.MinimumLevel.Override("Microsoft.AspNetCore", LogEventLevel.Error)
.MinimumLevel.Override("Serilog", LogEventLevel.Error)
.Enrich.FromLogContext()
.Enrich.WithClientIp()
.Enrich.WithClientAgent()
.CreateLogger();
Log.Logger = logger;
builder.Logging.AddSerilog(logger);
builder.Host.UseSerilog();
#endregion
// Add services to the container.
builder.Services.AddControllersWithViews();
var app = builder.Build();
// Configure the HTTP request pipeline.
if(app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else if(app.Environment.IsStaging() || app.Environment.IsProduction())
{
app.UseExceptionHandler("/Error");
}
app.UseSerilogRequestLogging();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.Run();
HomeController.cs
public class HomeController : Controller
{
public IActionResult Index()
{
return View();
}
public IActionResult Privacy()
{
//Here If "Log Debug Message is set to true" in "ApplicationSettings" table
//then only log debug message of Serilog or else don't log.
Log.Debug("Started executing Privacy");
try
{
int a = 1;
int b = 0;
int c = a / b;
}
catch (Exception ex)
{
Log.Error(ex, ex.Message);
}
return View();
}
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}
}
Can anybody help me on this? thanks.

Spring Boot Mongo update nested array of documents

I'm trying to set an attribute of a document inside an array to uppercase.
This is a document example
{
"_id": ObjectId("5e786a078bc3b3333627341e"),
"test": [
{
"itemName": "alpha305102992",
"itemNumber": ""
},
{
"itemName": "beta305102630",
"itemNumber": "P5000"
},
{
"itemName": "gamma305102633 ",
"itemNumber": ""
}]
}
I already tried a lot of thing.
private void NameElementsToUpper() {
AggregationUpdate update = AggregationUpdate.update();
//This one does not work
update.set("test.itemName").toValue(StringOperators.valueOf(test.itemName).toUpper());
//This one also
update.set(SetOperation.set("test.$[].itemName").withValueOfExpression("test.#this.itemName"));
//And every variant in between these two.
// ...
Query query = new Query();
UpdateResult result = mongoTemplate.updateMulti(query, update, aClass.class);
log.info("updated {} records", result.getModifiedCount());
}
I see that Fields class in spring data is hooking into the "$" char and behaving special if you mention it. Do not seem to find the correct documentation.
EDIT: Following update seems to work but I do not seem to get it translated into spring-batch-mongo code
db.collection.update({},
[
{
$set: {
"test": {
$map: {
input: "$test",
in: {
$mergeObjects: [
"$$this",
{
itemName: {
$toUpper: "$$this.itemName"
}
}
]
}
}
}
}
}
])
Any solutions?
Thanks!
For now I'm using which does what i need. But a spring data way would be cleaner.
mongoTemplate.getDb().getCollection(mongoTemplate.getCollectionName(Application.class)).updateMany(
new BasicDBObject(),
Collections.singletonList(BasicDBObject.parse("""
{
$set: {
"test": {
$map: {
input: "$test",
in: {
$mergeObjects: [
"$$this",
{
itemName: { $toUpper: "$$this.itemName" }
}
]
}
}
}
}
}
"""))
);

HotChocolate GraphQL filtering on GUID

I have very limited knowledge on GraphQL as I am still in the learning process. Now I stumbled upon an issue that I cannot resolve by myself without some help.
I'm using HotChocolate in my service.
I have a class ConsumerProductCategory with a Guid as Id which has a parent that is also a ConsumerProductCategory (think category > sub-category > ...)
Now I want to get the sub categories for a specific category, in linq you would write:
.Where(cat => cat.Parent.Id == id)
First of all lets start with our classes:
public class BaseViewModel : INode
{
public virtual Guid Id { get; set; }
}
public class ConsumerProductCategory : BaseViewModel
{
public ConsumerProductCategory()
{
}
public string Name { get; set; }
[UsePaging]
[UseFiltering]
[UseSorting]
public List<ConsumerProduct> Products { get; set; } = new List<ConsumerProduct>();
public ConsumerProductCategoryImage Image { get; set; }
public ConsumerProductCategory Parent { get; set; } = null;
public bool HasParent => this.Parent != null;
}
The object type definition is like this:
public class ConsumerProductCategoryType : ObjectType<ConsumerProductCategory>
{
protected override void Configure(IObjectTypeDescriptor<ConsumerProductCategory> descriptor)
{
descriptor
.Name(nameof(ConsumerProductCategory));
descriptor
.Description("Categories.");
descriptor
.Field(x => x.Id)
//.Type<UuidType>()
.Type<IdType>()
.Description($"{nameof(ConsumerProductCategory)} Id.");
descriptor
.Field(x => x.Name)
.Type<StringType>()
.Description($"{nameof(ConsumerProductCategory)} name.");
descriptor
.Field(x => x.Parent)
.Description($"{nameof(ConsumerProductCategory)} parent category.");
descriptor
.Field(x => x.Products)
.Description($"{nameof(ConsumerProductCategory)} products.");
descriptor
.ImplementsNode()
.IdField(t => t.Id)
.ResolveNode((context, id) => context.Service<IConsumerProductCategoryService>().GetByIdAsync(id));
}
}
The query to get the "main" categories would be like this:
query GetAllCategories {
consumerProductCategories(
#request: { searchTerm: "2"}
first: 10
after: null
where: { hasParent: { eq: false } }
order: {
name: ASC
}
) {
nodes {
id
name
image {
url
alt
}
}
pageInfo {
endCursor
hasNextPage
}
}
}
This returns this result:
{
"data": {
"consumerProductCategories": {
"nodes": [
{
"id": "Q29uc3VtZXJQcm9kdWN0Q2F0ZWdvcnkKZ2EyOTYxNmRlMWMzMjQ4ZTU4YTU2YzRjYjdhMGQ5NmY5",
"name": "Category 1",
"image": {
"url": "https://picsum.photos/200",
"alt": "Category 1 Image"
}
},
{
"id": "Q29uc3VtZXJQcm9kdWN0Q2F0ZWdvcnkKZ2NmZWI0YzNiMGQyNjQyOWI4MGU0MmQ1NGNjYWE1N2Q4",
"name": "Category 2",
"image": {
"url": "https://picsum.photos/200",
"alt": "Category 2 Image"
}
},
{
"id": "Q29uc3VtZXJQcm9kdWN0Q2F0ZWdvcnkKZ2I0MjhjYWE2NGMxNTQ4MTdiMjM1ZWFhZWU3OGRhYWYz",
"name": "Category 3",
"image": {
"url": "https://picsum.photos/200",
"alt": "Category 3 Image"
}
}
],
"pageInfo": {
"endCursor": "Mg==",
"hasNextPage": false
}
}
}
}
The first thing I noticed was that the Id's (Guid's) are changed to some base64 encoded strings.
Weird, but if I would do this:
query {
node(
id: "Q29uc3VtZXJQcm9kdWN0Q2F0ZWdvcnkKZ2EyOTYxNmRlMWMzMjQ4ZTU4YTU2YzRjYjdhMGQ5NmY5"
) {
... on ConsumerProductCategory {
id
name
}
}
}
this perfectly works, result:
{
"data": {
"node": {
"id": "Q29uc3VtZXJQcm9kdWN0Q2F0ZWdvcnkKZ2EyOTYxNmRlMWMzMjQ4ZTU4YTU2YzRjYjdhMGQ5NmY5",
"name": "Category 1"
}
}
}
However, now I want to filter on the Parent.Id,
query GetSubcategories {
consumerProductCategories(
first: 10
after: null
where: { parent: { id: { eq: "Q29uc3VtZXJQcm9kdWN0Q2F0ZWdvcnkKZ2NmZWI0YzNiMGQyNjQyOWI4MGU0MmQ1NGNjYWE1N2Q4"}} }
order: {
name: ASC
}
) {
nodes {
id
name
image {
url
alt
}
parent {
id
}
}
pageInfo {
endCursor
hasNextPage
}
}
}
This gives an error that the fieldtype where I do the "eq" is not correct, makes sense because in the data it's actually a Guid.
The result:
{
"errors": [
{
"message": "The specified value type of field `eq` does not match the field type.",
"locations": [
{
"line": 5,
"column": 31
}
],
"path": [
"consumerProductCategories"
],
"extensions": {
"fieldName": "eq",
"fieldType": "UUID",
"locationType": "UUID",
"specifiedBy": "http://spec.graphql.org/June2018/#sec-Values-of-Correct-Type"
}
}
]
}
I understand why it gives me this error, but I have no clue how to resolve this.
I looked everywhere on Google but have not found a similar question and in the official docs of HotChocolate I cannot really find a solution for this issue.
Can anyone point me in the right direction?
By the way, is it a good practice to use these "autogenerated" base64 strings as Id's, or is there some way to specify that this generation should not happen and actually return the Guid's instead?
Thanks in advance!
Ok, I can answer my own question, basically it isn't supported yet: github
What I've done for now is just add a second Guid in the base class:
This results in:

How to wrap a retrofit response in JAVA?

I have to use a backend that returns me this:
{
"ok": true,
"data": [
{
"id": 0,
"name": "Intempestivo"
},
{
"id": 1,
"name": "Vacaciones"
}
}
But other times it returns me something like this:
{
"ok": true,
"data": [
{
"id": 10,
"startDate": "2020-05-29T12:00:00.000Z",
"endDate": "2020-05-30T12:00:00.000Z",
"status": "En revision",
"type": "Vacaciones"
},
{
"id": 11,
"startDate": "2020-05-29T12:00:00.000Z",
"endDate": "2020-05-30T12:00:00.000Z",
"status": "En revision",
"type": "Vacaciones"
}
]
}
How I could wrap this responses in a General Response class where the only thing that change might be the data POJO object.
public class GeneralResponse<T> {
#SerializedName("data")
private WrapperData<T> wrapperData;
#SerializedName("ok")
private boolean ok;
#SerializedName("error")
private Error error;
}
And the wrapper data is:
public class WrapperData<T> {
private T dataResponse;
public WrapperData(T POJOResponse) {
this.dataResponse = POJOResponse;
}
public T getDataResponse() {
return dataResponse;
}
}
The retrofit interface have a method like this:
#GET("permission_types/")
Call<GeneralResponse<ArrayList<PermissionType>>> getPermissionTypesWrapped();
But when I make the request I got the following exception:
com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was BEGIN_ARRAY at line 'n' column 'n' path $.data
I just solve this.
I replace private WrapperData <T> wrapperData; to private <T> wrapperDate; so when I wnat to get a list I call Call<GeneralResponse<Collection<PermissionTypes>>>

Resources