Exposing link on collection entity in spring data REST - spring-data

Using spring data REST I have exposed a ProjectRepository that supports listing projects and performing CRUD operations on them. When I go to http://localhost:8080/projects/ I get the list of projects as I expect.
What I am trying to do is add a custom action to the _links section of the JSON response for the Project Collection.
For example, I'd like the call to http://localhost:8080/projects/ to return something like this:
{
"_links" : {
"self" : {
"href" : "http://localhost:8080/projects/{?page,size,sort}",
"templated" : true
},
"search" : {
"href" : "http://localhost:8080/projects/search"
},
"customAction" : {
"href" : "http://localhost:8080/projects/customAction"
}
},
"page" : {
"size" : 20,
"totalElements" : 0,
"totalPages" : 0,
"number" : 0
}
}
Where customAction is defined in some controller.
I've tried creating the following class:
public class ProjectCollectionResourceProcessor implements ResourceProcessor<Resource<Collection<Project>>> {
#Override
public Resource<Collection<Project>> process(Resource<Collection<Project>> listResource) {
// code to add the links to customAction here
return listResource;
}
}
and adding adding the following Bean to my applications configuration:
#Bean
public ProjectCollectionResourceProcessor projectCollectionResourceProcessor() {
return new ProjectCollectionResourceProcessor();
}
But process(...) doesn't ever seem to get called. What is the correct way to add links to Collections of resources?

The collection resources render an instance of Resources<Resource<Project>>, not Resource<Collection<Project>>. So if you change the generic typing in your ResourceProcessor implementation accordingly that should work as you expect it.

I had the same issue. What worked for me was:
public class ProjectsResourceProcessor implements ResourceProcessor<PagedResources<Resource<Project>>> {
private final #NonNull EntityLinks entityLinks;
#Override
public PagedResources<Resource<Project>> process(PagedResources<Resource<Project>> pagedResources) {
...
return pagedResources;
}
}

Related

Default implementation mapping for object without "_class" attribute in spring-data-mongodb

I have the next problem: at the start of the project I had only one implementation of some class:
class Wrapper {
private Child child;
}
class Child { ... }
and when mongodb serializes this class it never set _class attribute for the child object.
Now I need to make the next classes structure:
class Wrapper {
private Child child;
}
abstract class Child { ... }
class ChildA extends Child { ... }
class ChildB extends Child { ... }
Obviously, then I try to deserialize old documents, it throws an exception because it can't instantiate an abstract class.
The question is: is it any way to specify the default implementation for object deserialization? For example, I want to deserialize the child field into the Child1 class if the _class attribute is not present like this parameter in Jackson library?
This may not be the optimal solution, because I do suspect there are ways to control the mongo deserialization. However, if you want to avoid digging into that well, you could use one of the Mongo lifecycle events. In particular, the onAfterLoad is dispatched after a document is read from the database, but before it has been converted into a java object.
So, using the event, you can examine the incoming json, and even change it before it gets converted... Here is an example for your situation:
#Component
public class WrapperRepositoryListener extends AbstractMongoEventListener<Wrapper> {
#Override
public void onAfterLoad(AfterLoadEvent<Wrapper> event) {
Document doc = event.getDocument();
Document child = (Document)doc.get("child");
if (!child.containsKey("_class")) {
child.put("_class", "ChildA");
}
}
}
Alternatively, (although I know this isn't the question you asked), depending on how much data you have out there, you could write a mongodb script that simply updates all of the existing records to have the _class field:
db.wrappers.update(
{
"child" : { $exists : true},
"child._class" : { $exists : false }
},
{
$set:{
"child._class" : "ChildA"
}
},
{
multi: true,
upsert: false
}
)

Retrieve MongoDB collections as a sequence of `IMongoCollection<T>` in .NET driver

IMongoDatabase.ListCollections return a cursor over BsonDocument.
Why doesn't it return a cursor over IMongoCollection<T> instead?
I was trying to write a generic GetCollection method to retrieve the collection given just the document type, something like this:
private IMongoCollection<T> GetCollection<T>()
{
var client = new MongoClient("connectionString");
var db = client.GetDatabase("dbName");
var coll = db.ListCollectionsAsync().Result.ToListAsync().Result
// Find collection of document of type T
// Collection is a BsonDocument instead
.Find(collection => typeof(collection) == typeof(T));
return coll;
}
The driver doesn't know what kind of document is in a collection, which is why it takes a type parameter T. MongoDB itself isn't aware of how the documents in the database map to the types in your application.
It is not possible to take a connection to a "generic" MongoDB deployment and simply discover the collections and types in them. This is code you would need to write, and probably won't work out well as it'll be something akin to trial and error.
If you are simply trying to create a factory type, you will need to build the backing list of collections before calling GetCollection<T>.
You could try using the type name as the collection name. This would make the collection name repeatable (unless the type name is changed). But I've never tested it and it might have some idiosyncrasies in the real world.
public class MyDatabase
{
private readonly IMongoClient _client;
public MyDatabase(string connectionString)
{
_client = new MongoClient(connectionString);
}
public IMongoCollection<T> GetCollection<T>()
{
var db = _client.GetDatabase("dbName");
return db.GetCollection<T>(typeof(T).Name);
}
}
If you prefer collection names to be pluralized, something like Humanizer can help there.
I generally prefer to create a type that has the collections as fields on the class. For example:
public class MyDatabase
{
private readonly IMongoClient _client;
public IMongoCollection<Foo> Foos { get; }
public IMongoCollection<Bar> Bars { get; }
public MyDatabase(string connectionString)
{
_client = new MongoClient(connectionString);
var db = _client.GetDatabase("myDb");
Foos = db.GetCollection<Foo>("foos");
Bars = db.GetCollection<Bar>("bars");
}
}
MongoDB collections have a flexible scheme that allows you to insert any document with any structure into the collection. For example, We can insert the following 3 objects into the test collection and it's valid:
> db.test.insertMany([{one: 1}, {two:2}, {three: 3}])
{
"acknowledged" : true,
"insertedIds" : [
ObjectId("5c87c954ed372bf469367e57"),
ObjectId("5c87c954ed372bf469367e58"),
ObjectId("5c87c954ed372bf469367e59")
]
}
> db.test.find().pretty()
{ "_id" : ObjectId("5c87c954ed372bf469367e57"), "one" : 1 }
{ "_id" : ObjectId("5c87c954ed372bf469367e58"), "two" : 2 }
{ "_id" : ObjectId("5c87c954ed372bf469367e59"), "three" : 3 }
Thus you can't map a MongoDB collection to a .NET Type as the collection has no knowledge of the type.

How to Make a app acording to language change

I developed an app that can support two languages. Going beyond that if I need this app for support with many languages. let assume ten languages how to accomplish this requirement
Now what I do is Hard cording it with both languages I used and then retrieve it. If I going to do that for ten languages it does not sound good is there any smart way to do this.
as an example;
I doing with MVVM pattern In my View Model. I use the property and do this its simple way of doing
public string GetPageTitle => AppResources.VitalSignsViewPage_Title;
and my resource file I have two data set if the language has changed it will use write dataset this is what I am doing right now is there any proper smart way to this to many languages.
There is some solutions to achive this. I recommend to you basic
solution ,
Here is an interface for Language Service (It is optionally , if you
using Dependency Injection):
public interface ILanguageService
{
string GetString(string text);
void ChangeLanguage(bool isALang);
bool IsALanguage();
}
You can create a service for localization :
namespace Service.Language
{
public sealed class LanguageService : ILanguageService
{
List<LanguageRow> LanguageList;
private bool IsFirstLang;
public LanguageService()
{
LanguageList = JsonHelper.ReadJSON<List<LanguageRow>>("Service.Language.MultiLanguage.json", typeof(LanguageService));
IsFirstLang = true;
}
public void ChangeLanguage(bool IsFirstLang)
{
IsFirstLang = !IsFirstLang;
}
public bool IsALangueage()
{
return IsFirstLang;
}
public string GetString(string text)
{
string result;
try
{
var row = LanguageList.FirstOrDefault(i => i.Code.Equals(text));
result = IsFirstLang? row.Values[0] : row.Values[1];
}
catch
{
result = text;
}
return result;
}
}
}
Here is a model to serialization for json :
public class LanguageRow
{
public LanguageRow()
{
Values = new List<string>();
}
public string Code { get; set; }
public List<string> Values { get; set; }
}
At last, here is json file : (EN-FR)
[
{
"Code": "VitalSignsViewPage_Title",
"Values": [ "Page Title", "Titre de la page" ]
},
{
"Code": "VitalSignsViewPage_SubTitle",
"Values": [ "Sub Title", "Sous-titre" ]
},
{
"Code": "VitalSignsViewPage_SubSubTitle",
"Values": [ "Sub Sub Title", "Sous sous-titre" ]
}
]
You can access translations like :
ILanguageService _langService = new LangService()
_langService.GetString(AppResources.VitalSignsViewPage_Title);

MongoDB $addFields using org.springframework.data.mongodb.core.MongoTemplate

How do I go about writing an $addFields query in Spring Data MongoDB Reactive for a simpler and a slightly more complex field addition as shown below:
db.getCollection("mycollection").aggregate(
[
{
"$addFields" : {
"existingObjectField.newFieldArray" : [
"$existingObjectField.existingFieldObject"
]
}
},
{
"$addFields" : {
"existingFieldArray" : {
"$map" : {
"input" : "$existingFieldArray",
"as" : "item",
"in" : {
"existingFieldObject" : {
"_id" : "$$item. existingFieldObject._id",
"newFieldArray" : [
"$$item. existingFieldObject.existingFieldObject"
]
}
}
}
}
}
},
{
"$out" : "mycollection"
}
]
);
In the first add fields, I am simply creating a new array field with one of the existing object field.
In the 2nd add fields, doing the same but within an object in an array in the document.
Like for match/unwind AddFieldOperation is not present in spring data mongo But you can write your own and also a custom Aggregation class to add caller method to addFieldOpration as below.
public class AddFieldOperation implements AggregationOperation {
private final Document document;
/**
* Creates a new {#link MatchOperation} for the given {#link CriteriaDefinition}.
*
* #param criteriaDefinition must not be {#literal null}.
*/
public AddFieldOperation(final Document document) {
Assert.notNull(document, "Criteria must not be null!");
this.document = document;
}
/*
* (non-Javadoc)
*
* #see org.springframework.data.mongodb.core.aggregation.AggregationOperation#toDocument(org.
* springframework.data.mongodb.core.aggregation.AggregationOperationContext)
*/
#Override
public Document toDocument(final AggregationOperationContext context) {
return new Document("$addFields", this.document);
}
}
Now make CustomAggregation class.
public class CustomAggregation extends Aggregation {
public static AddFieldOperation addField(final Document document) {
return new AddFieldOperation(document);
}
}
Everything is ready you need to call Addfield method and pass all query in Document object example:-
AddFieldOperation addField =
CustomAggregation.addField(new Document().append("fieldName", fieldValue));
Note
Document class is from import package org.bson.Document;
Which is a representation of a document as a {#code Map}.
All aggregation operation implemented in spring data mongo is finally converted to the Document object and this will execute in the shell. So if some of the aggregation pipelines are not yet implemented in spirng data, in that case, we can write our own and pass the query which is written in mongo shell we can just pass it in the Document object.

are model-defined functions still supported in EF6?

model-defined functions are discussed here:
https://msdn.microsoft.com/en-us/library/vstudio/dd456857(v=vs.110).aspx
https://msdn.microsoft.com/en-us/library/dd456812.aspx
Entity Framework 6 Code First function mapping
http://www.c-sharpcorner.com/UploadFile/ff2f08/model-defined-function/
are these supported by EF6.1.2?
I'm stepping through the Edm/DbModel stuff and I can't for the life of me work out where the <Function> element in the csdl is supposed to be parsed, because it's not making it into the EdmModel (EdmModel.AddItem(EdmFunction) isn't getting called)
ExpressionConverter.FindFunction looks in EdmModel._functions, and _functions is only added to by EdmModel.AddItem(EdmFunction) and that's only called by the extension method EdmModelExtensions.AddFunction(), and I cannot find anywhere in the EntityFramework source code that calls that function. I must be missing something simple...
more: I gave up on defining the Function in the edmx and now I'm creating my EdmFunction programatically and adding it in a custom IConceptualModelConvention.Apply() method:
class CustomFunctionConvention : IConceptualModelConvention<EdmModel>
{
public void Apply(EdmModel item, DbModel model)
{
var functionPayload = new EdmFunctionPayload () {
CommandText = "CAST (strValue AS int)",
Parameters = new [] {
FunctionParameter.Create("strValue", PrimitiveType.GetEdmPrimitiveType(PrimitiveTypeKind.String).GetEdmPrimitiveType(), ParameterMode.In),
},
ReturnParameters = new [] {
FunctionParameter.Create("ReturnValue", PrimitiveType.GetEdmPrimitiveType(PrimitiveTypeKind.Int32).GetEdmPrimitiveType(), ParameterMode.ReturnValue),
},
IsComposable = true,
};
var function = EdmFunction.Create("ParseInt", "MyNamespace", DataSpace.CSpace, functionPayload, null);
model.ConceptualModel.AddItem(function);
}
}
but now I'm getting a bunch of schema errors in EdmItemCollection.LoadItems() :
Schema specified is not valid. Errors:
(0,0) : error 0005: The 'Aggregate' attribute is not allowed.
(0,0) : error 0005: The 'BuiltIn' attribute is not allowed.
(0,0) : error 0005: The 'NiladicFunction' attribute is not allowed.
(0,0) : error 0005: The 'IsComposable' attribute is not allowed.
(0,0) : error 0005: The 'ParameterTypeSemantics' attribute is not allowed.
(0,0) : error 0005: The 'Schema' attribute is not allowed.
(0,0) : error 0005: The 'Mode' attribute is not allowed.
It seems, model defined functions are usable with code first. Here is a version for your ParseInt example :
namespace EfTestModelFunctions
{
public class CustomFunctionConvention : IConceptualModelConvention<EdmModel>
{
public void Apply(EdmModel item, DbModel model)
{
var functionParseInt = new EdmFunctionPayload()
{
CommandText = String.Format("CAST(strValue AS {0})", PrimitiveType.GetEdmPrimitiveType(PrimitiveTypeKind.Int32)),
Parameters = new[] {
FunctionParameter.Create("strValue", PrimitiveType.GetEdmPrimitiveType(PrimitiveTypeKind.String), ParameterMode.In),
},
ReturnParameters = new[] {
FunctionParameter.Create("ReturnValue", PrimitiveType.GetEdmPrimitiveType(PrimitiveTypeKind.Int32), ParameterMode.ReturnValue),
},
IsComposable = true
};
var function = EdmFunction.Create("ParseInt", model.ConceptualModel.EntityTypes.First().NamespaceName, DataSpace.CSpace, functionParseInt, null);
model.ConceptualModel.AddItem(function);
}
}
public class RootDataContext : DbContext
{
public RootDataContext()
: base("Data Source=******")
{
Database.SetInitializer(new NullDatabaseInitializer<RootDataContext>());
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Conventions.Add<CustomFunctionConvention>();
}
public DbSet<RootEntity> Roots { get; set; }
// declare the function with the Context's NameSpace
[DbFunction("EfTestModelFunctions", "ParseInt")]
public static int ParseInt(string value)
{
throw new NotImplementedException();
}
}
}
usage will then be :
var query = ctx.Roots.Where(r => RootDataContext.ParseInt(r.StringProperty)==123);
Also, it seems there is an issue when database migration/initialization is involved. See
UpForGrabs: Unblock creation of model defined functions in model conventions