I'm testing Spring Data REST.
I was following this tutorial to understand how it works.
The code so far is simple as:
#Entity
#SequenceGenerator(name="my_seq", sequenceName="user_id_seq")
#Table(name = "employees")
data class Employee (#Id #GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "my_seq") val id: Long? = null,
val name: String = "defConstructorHell")
and when I ask with GET I obtain the following json:
{
"_embedded" : {
"employees" : [ {
"name" : "ciao",
"_links" : {
"self" : {
"href" : "http://localhost:5000/api/employees/1"
},
"employee" : {
"href" : "http://localhost:5000/api/employees/1"
}
}
} ]
},
"_links" : {
"self" : {
"href" : "http://localhost:5000/api/employees{?page,size,sort}",
"templated" : true
},
"profile" : {
"href" : "http://localhost:5000/api/profile/employees"
}
},
"page" : {
"size" : 20,
"totalElements" : 1,
"totalPages" : 1,
"number" : 0
}
}
Which is totally fine but I'm using a js framework that requires the header X-Total-Count.
Do you know if there is a way of doing it wi Spring Data REST?
Eventually I added a controller that add the requested header:
#RepositoryRestController
class EmployeeController #Autowired constructor(val repo: EmployeeRepository) {
#RequestMapping(method = arrayOf(GET),
path = arrayOf("employees"))
#ResponseBody
fun getEmployees(#RequestParam("_sort", required = false, defaultValue = "id") _sort: String,
#RequestParam("_order", required = false, defaultValue = "DESC") _order: String,
#RequestParam("_start", required = false, defaultValue = "0") _start: Int,
#RequestParam("_end", required = false, defaultValue = "20") _end: Int): ResponseEntity<MutableIterable<Employee>> {
val pr = PageRequest(_start, 20, Sort.Direction.valueOf(_order), _sort)
val result = repo.findAll(pr)
val headers = HttpHeaders()
headers.add("X-Total-Count", result.count().toString())
return ResponseEntity(result.content, headers, OK)
}
}
Related
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")), // <----------
I have 3 collections: attributes, products and product_attributes. I am trying to retrieve product attributes with aggregation with nested attribute and product in it. But product and attribute always null
service method:
fun findById(id: String): Mono<ProductAttribute> {
val idField = "_id"
val productId = "productId"
val attributeId = "attributeId"
val attributesDb = "attributes"
val productsDb = "products"
val attributeName = "attribute"
val productName = "product"
fun lookup(from: String, localField: String, name: String) = LookupOperation.newLookup()
.from(from)
.localField(localField)
.foreignField(idField)
.`as`(name)
fun match(id: String) = Aggregation.match(Criteria.where(idField).`is`(ObjectId(id)))
fun unwind(field: String) = Aggregation.unwind(field)
val aggregation = Aggregation.newAggregation(
match(id),
lookup(productsDb, productId, productName),
unwind(productName),
lookup(attributesDb, attributeId, attributeName),
unwind(attributeName)
).withOptions(AggregationOptions.builder().allowDiskUse(true).build())
return operations.aggregate(aggregation, "product_attributes", ProductAttribute::class.java)
.last()
.doOnError { throwable -> logger.error("Failed to get productAttribute", throwable) }
}
ProductAttribute.kt
#Document(collection = "product_attributes")
data class ProductAttribute(
#Id
#JsonProperty("_id")
val id: String? = ObjectId().toHexString(),
#get:Transient #Value("null") val product: Product?,
#get:Transient #Value("null") val attribute: Attribute?,
val attributeId: String,
val productId: String,
val name: String,
val quantity: Int,
val photos: List<String>
) : Serializable
Response:
{
"data": {
"productAttributeId": {
"id": "62a3bff787418b6f837e9150",
"product": null,
"attribute": null,
"name": "string",
"quantity": 1,
"photos": [
"string"
]
}
}
}
As a workaround I have ended up with this result:
I made aggregate method with generic type. It will aggregate to Any type and then map to my specific type.
inline fun <reified T> aggregate(
aggregation: Aggregation,
collectionName: String,
operations: ReactiveMongoOperations,
objectMapper: ObjectMapper
): Flux<T> =
operations.aggregate(aggregation, collectionName, Any::class.java)
.map { data -> objectMapper.convertValue(data, T::class.java) }
I think there is a way to implement custom converter. I will think about this later. I will update answer on success
i have this mongo query:
db.getCollection('My_collection_name').aggregate([
{ $project: { warehouses: { $objectToArray: "$outputVariables" } } },
{ $unwind: "$warehouses" },
{ $group: { _id: "$warehouses.k" }}
])
someone could help me to translate in spring mongoTemplate?
Thanks
The query should be write as below:
import static org.springframework.data.mongodb.core.aggregation.Aggregation.*;
...
AggregationOperation project = project()
.and(ObjectToArray.valueOfToArray("outputVariables")).as("warehouses");
AggregationOperation unwind = unwind("warehouses");
AggregationOperation group = Aggregation.group("warehouses.k");
Aggregation aggregation = Aggregation.newAggregation(
project,
unwind,
group);
String collectionName = "My_collection_name";
System.out.println("aggregation=" + aggregation);
this.mongoTemplate.aggregate(aggregation, collectionName, Output.class);
It generate output:
aggregation={ "aggregate" : "__collection__", "pipeline" : [{ "$project" : { "warehouses" : { "$objectToArray" : "$outputVariables" } } }, { "$unwind" : "$warehouses" }, { "$group" : { "_id" : "$warehouses.k" } }] }
I'm making some tests with Spring Data Elasticsearch, Spring Boot, Spring Data Rest and an h2 Database (embedded).
I don't understand why the values are stored in the indexes despite I have this configuration:
#Entity
#Document(indexName = "computerindex", type="computers")
public class Computer {
#Id
#GeneratedValue(generator="system-uuid")
#GenericGenerator(name="system-uuid", strategy = "uuid")
private String id;
#Field(type=FieldType.String, store=false) private String name;
#Field(type=FieldType.String, store=false) private String brand;
}
#Configuration
#EnableWebMvc
#EnableJpaRepositories
#EnableElasticsearchRepositories
public class ElasticSearchConfig {
#Bean
public ElasticsearchTemplate elasticsearchTemplate() throws IOException {
return new ElasticsearchTemplate(getNodeClient());
}
private static NodeClient getNodeClient() throws IOException {
String pathHome = new File(".").getCanonicalPath();
NodeBuilder nodeBuilder = new NodeBuilder();
nodeBuilder
.settings()
.put("path.home", pathHome)
.put("path.logs", pathHome+"/logs");
return (NodeClient) nodeBuilder.clusterName("elasticsearch").local(true).node().client();
}
}
Going to the url http://localhost:8080/computers, initially there is an empty "_embedded" result (as expected):
{
"_embedded" : {
"computers" : [ ]
},
"_links" : {
"self" : {
"href" : "http://localhost:8080/computers"
},
"profile" : {
"href" : "http://localhost:8080/profile/computers"
},
"search" : {
"href" : "http://localhost:8080/computers/search"
}
},
"page" : {
"size" : 20,
"totalElements" : 0,
"totalPages" : 0,
"number" : 0
}
}
When I save a new "Computer", I have this result:
{
"_embedded" : {
"computers" : [ {
"name" : "pc",
"brand" : "brand",
"_links" : {
"self" : {
"href" : "http://localhost:8080/computers/AVZZwpMlqIneBcTGH3av"
},
"computer" : {
"href" : "http://localhost:8080/computers/AVZZwpMlqIneBcTGH3av"
}
}
} ]
},
"_links" : {
"self" : {
"href" : "http://localhost:8080/computers"
},
"profile" : {
"href" : "http://localhost:8080/profile/computers"
},
"search" : {
"href" : "http://localhost:8080/computers/search"
}
},
"page" : {
"size" : 20,
"totalElements" : 1,
"totalPages" : 1,
"number" : 0
}
}
Now there is the interesting part.
If I stop the application and I restart it (without erase the indexes), since I have an embedded H2 database that runs and lives together with my application, I will expect that my "pc" is lost after that
the previous session has been terminated.
The indexes are also configured to not store any data.
When I restart the application, I still have this result:
"computers" : [ {
"name" : "pc",
"brand" : "brand",
"_links" : {
"self" : {
"href" : "http://localhost:8080/computers/AVZZwpMlqIneBcTGH3av"
},
"computer" : {
"href" : "http://localhost:8080/computers/AVZZwpMlqIneBcTGH3av"
}
}
The only explanation could be that the values are restored from the indexes: in fact, if I delete the indexes before restarting the application, I have an empty database (as I expect).
But I don't want to save the data in the indexes, and I don't want that the datas are pulled from the indexes.
Furthermore, I have explicitely configured store=false, and I didn't set the data(true) property (like this):
return (NodeClient) nodeBuilder.clusterName("elasticsearch").local(true).data(true).node().client();
Any ideas?
Just a little hack for validating against a kendo.data.Model
So instead of adding the HTML5 required attribute to each input field manually
this little hack gets all the validation rules that you defined in your Model and
add them as attributes to the appropriate field. Not fully tested just a quick hack. According to Kendo the validation rules don't get processed if you are using MVVM, but they do if for example you bind the Dataource directly to a grid.
Code:
I just put this code in a file called definevalidation.js
function getValidationAttributesFromModel(myFields) {
var myValidatedFields = [];
var obj = null;
$.each(myFields, function(fieldName) {
if (this.validation) {
var obj = {
fieldName : fieldName,
validation : this.validation
};
myValidatedFields.push(obj);
}
});
addValidationAttributes(myValidatedFields);
}
function addValidationAttributes(myValidatedFields) {
$.each(myValidatedFields, function(index) {
$('#' + this.fieldName).attr(this.validation);
});
}
Usage:
If ParentDS is your datasource then in your form code just use
getValidationAttributesFromModel(ParentDS.options.schema.model.fields)
Sample Model:
mydatamodel = kendo.data.Model.define({
id : "__KEY",
fields : {
__KEY : {
type : "string"
},
__STAMP : {
type : "number"
},
ID : {
editable : false,
nullable : true
},
firstName : {
type : "string",
validation : {
required : true
}
},
middleName : {
type : "string"
},
lastName : {
type : "string"
}
}
});