How can I map from a collection to a collection property? - mapstruct

Let's say I have following mapping target.
class Some {
private List<Other> others;
}
How can I map from an Iterable of Others to the others property?
Can I do this?
#Mapper
interface SomeMapper {
// map source to target.others
Some mapOthers(Iterable<? extends Other> source, #MappingTarget Some target);
}

When you want to map a source parameter to a target property then you should defined #Mapping if the source parameter does not match the name of the target property. In this case something you could do something like:
#Mapper
interface SomeMapper {
#Mapping(target = "others", source = "source")
Some mapOthers(Iterable<? extends Other> source, #MappingTarget Some target);
}

Related

Custom annotation processing while coding

The purpose of the project I'm working on is to handle annotation at compile time, it is not focused on what exactly I'm developing.
I took a simple subject for this and I'm writing a custom collection that will store elements and provide methods to manage them.
What I wanna do is to create an annotation #Contains, for example, to generate itemsContains method that could be processed while coding (instead of writing code manually).
public class Array {
private List<String> items;
public Array() {
items = Arrays.asList("abc", "def", "xyz");
}
public boolean itemsContains(String expected) {
return items.contains(expected);
}
}
Generally, I want my class to look something like:
public class Array {
#Contains
private List<String> items;
public Array() {
items = Arrays.asList("abc", "def", "111");
}
}
The important thing I want to reach is to have itemsContains method show up once the annotation is applied to a field. This is how it should look like:
expected result
Alternate existing examples are Lombok's #Getter/#Setter.
So what functionality or configurations should I implement to get the expected result?
Would be grateful for some real implementations or guides how to perform it step by step.
Annotation processing does not change the source file yet it generates a new file,
Lombok on the other hand does a trick to modify the source file itself, meaning that you need to call the generated class somewhere in your code.
One way to do this is to generate a class that extends the main class
#Generated
public class ArrayProxy extends Array {
public boolean itemsContains(String expected) {
return items.contains(expected);
}
}
and in your main class you need to do two things:
first you need to make items protected
you can add factory method to actually create the generated class
public class Array {
#Contains
protected List<String> items;
public static ArrayProxy create(){
return new ArrayProxy();
}
private Array() {
items = Arrays.asList("abc", "def", "111");
}
}
And of course you need to use it like this
ArrayProxy array = Array.create();
array.itemsContains("expected");

Why is Spring Data MongoDB unable to instantiate this nested type structure?

My document structure is like:
{
_id: "A",
groups:[{
groupId: "someId",
groupName: "someName",
params: {
type1: ["a", "b"],
type2: ["c", d]
}
}],
config: {
person: {}
dataDetails: {
dataTypeDetails: {},
dataList: ["dt1", "dt2"]
}
}
}
My Spring Data MongoDB model types look like this:
// Imports etc.
#Document
public class Entity {
#Id
private String _id;
private List<Group> groups;
private Config config;
// setters and getters
public class Group {
private String groupId;
private String groupName;
private ParamData params;
// setter and getters
}
public class ParamData {
private List<String> type1;
private List<String> type2;
}
public class Config {
private Map person;
private DataConfig dataDetails;
}
public class DataConfig {
private Map dataTypeDetails;
private List<String> dataList;
}
}
Stacktrace:
org.springframework.data.mapping.model.MappingInstantiationException: Failed to instantiate com.****.common.models.Entity$ParamData using constructor public com.****.common.models.Entity$ParamData(com.****.common.models.Entity) with arguments com.****.common.models.Entity$Group#2eb61a7b
at org.springframework.data.convert.ReflectionEntityInstantiator.createInstance(ReflectionEntityInstantiator.java:78)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.read(MappingMongoConverter.java:257)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.read(MappingMongoConverter.java:237)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.readValue(MappingMongoConverter.java:1136)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.access$100(MappingMongoConverter.java:78)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter$MongoDbPropertyValueProvider.getPropertyValue(MappingMongoConverter.java:1085)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.getValueInternal(MappingMongoConverter.java:816)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter$1.doWithPersistentProperty(MappingMongoConverter.java:270)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter$1.doWithPersistentProperty(MappingMongoConverter.java:263)
at org.springframework.data.mapping.model.BasicPersistentEntity.doWithProperties(BasicPersistentEntity.java:261)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.read(MappingMongoConverter.java:263)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.read(MappingMongoConverter.java:237)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.readCollectionOrArray(MappingMongoConverter.java:861)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.readValue(MappingMongoConverter.java:1134)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.access$100(MappingMongoConverter.java:78)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter$MongoDbPropertyValueProvider.getPropertyValue(MappingMongoConverter.java:1085)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.getValueInternal(MappingMongoConverter.java:816)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter$1.doWithPersistentProperty(MappingMongoConverter.java:270)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter$1.doWithPersistentProperty(MappingMongoConverter.java:263)
at org.springframework.data.mapping.model.BasicPersistentEntity.doWithProperties(BasicPersistentEntity.java:261)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.read(MappingMongoConverter.java:263)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.read(MappingMongoConverter.java:237)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.read(MappingMongoConverter.java:201)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.read(MappingMongoConverter.java:197)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.read(MappingMongoConverter.java:78)
at org.springframework.data.mongodb.core.MongoTemplate$ReadDbObjectCallback.doWith(MongoTemplate.java:2016)
at org.springframework.data.mongodb.core.MongoTemplate.executeFindMultiInternal(MongoTemplate.java:1700)
at org.springframework.data.mongodb.core.MongoTemplate.doFind(MongoTemplate.java:1523)
at org.springframework.data.mongodb.core.MongoTemplate.doFind(MongoTemplate.java:1507)
at org.springframework.data.mongodb.core.MongoTemplate.find(MongoTemplate.java:532)
at org.springframework.data.mongodb.core.MongoTemplate.findOne(MongoTemplate.java:497)
at org.springframework.data.mongodb.core.MongoTemplate.findOne(MongoTemplate.java:489)
My in DAO I am trying to fetch the document by identifier, but it is failing only for the values at dataDetails and params. If I comment out the ParamData param, I get error for DataConfig. The data was added using command line/node scripts and was not added using this code. But it is getting retrieved properly by node/mongoose as well as from command line.
This seems to be an issue with doubly nested inner classes and the synthetically generated constructors created by the compiler. I could reproduce that issue locally and see if we can provide a fix. In the meantime you have two options:
Turn the inner class into static ones as this will remove the synthetic constructors and instantiation will work correctly.
Nest the type declarations in the same way you nest the properties. I.e. move the ParamData class into the Group class, DataConfig into Config as that will cause the synthetic constructors created in a way they match instantiation order Spring Data currently relies on.
I'd suggest the former approach as it doesn't artificially bind the classes to instances of the outer class.
Failed to instantiate ... using constructor public ... ReflectionEntityInstantiator
says it cannot create the objects using reflection.
Do you have getters and setters for all the fields in all your classes? Your code above does not have them for ParamData, Config and DataConfig.
Also, if you happen to have non-default constructors in any of your classes make sure you have an empty argument constructor, else reflection will fail.

How to prevent Json.NET from using the Entity Framework proxy type name?

In my design I have a class that has a property whose type can be inherited from:
public class Feed
{
...
[JsonProperty(TypeNameHandling = TypeNameHandling.Auto)]
public FeedSource Source { get; set; }
...
}
public abstract class FeedSource { ... }
public class CsvSource : FeedSource { ... }
public class DbSource : FeedSource { ... }
I'm using the Entity Framework to load and store this object to a database and I'm using Json.NET to serialize this object into JSON for further processing.
The problem I stumbled on is that the $type property is containing the typename of the EF proxy instead of the "real" typename. So instead of:
$type: "System.Data.Entity.DynamicProxies.CsvSource_0B3579D9BE67D7EE83EEBDDBFA269439AFC6E1122A59B4BB81EB1F0147C7EE12"
which is meaningless to other clients, I would like to get:
$type: "MyNamespace.CsvSource"
in my JSON.
What's the best way to achieve this?
Another way which doesn't require you to make changes to your EF configuration is to use a custom SerializationBinder, e.g.:
class EntityFrameworkSerializationBinder : SerializationBinder
{
public override void BindToName(Type serializedType, out string assemblyName, out string typeName)
{
assemblyName = null;
if (serializedType.Namespace == "System.Data.Entity.DynamicProxies")
typeName = serializedType.BaseType.FullName;
else
typeName = serializedType.FullName;
}
public override Type BindToType(string assemblyName, string typeName)
{
throw new NotImplementedException();
}
}
Usage:
string json = JsonConvert.SerializeObject(entityFrameworkObject, new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All, Binder = new EntityFrameworkSerializationBinder() });
You can do two things:
disabling tracking proxies, by setting ProxyCreationEnabled to false. You can find this property in your context's Configuration property. If you use a context for a single GetXxx method, you can do it without interfering other context instanced.
using the AsNoTracking() extension method when you recover your entity, like this:
MyContext.MyTable.AsNoTracking(). // rest of the query here
This indicates that you don't want a tracking proxy for your entity, so you'll get the entity class. This has no interference with the afore mentioned configuration.

springdata mongo repository method to return specific document property list

using spring data for mongodb, how do I specify the return type of the repository method to include a particular property from the document?
Ex:
#Document (collection = "foo")
class Foo {
String id
String name
... many more attributes
}
repository:
interface FooRepository extends MongoRepository<Foo, String> {
#Query { value = "{}", fields = "{'name' : 1}" }
List<String> findAllNames()
}
Above findAllNames works as expected and fetches only name property from the document. However spring data returned object is a string representation of Foo object which has id and name properties with values and remaining attributes as null.
Instead of Foo objects, I need to fetch List<String> which represents names.
As of now, I used a custom interface to achieve this. Moved the findAllNames() method from Spring data repository interface to my custom interface
interface FooRepositoryCustom {
List<String> findAllNames()
}
interface FooRepository extends MongoRepository<Foo, String>, FooRepositoryCustom {
}
#Component
class FooRepositoryImpl implements FooRepositoryCustom {
#Autowired
MongoOperations mongoOperations;
List<String> findAllNames() {
//using mongoOperations create the query and execute. Return the property values from document
}
}

Restrict property type

Good day,
I have a class which represents a collection. The collection has a property of type Type which allows you to specify the data type of each element's meta data object. Each time an element is added to the collection a new instance of the assigned meta data object is created with Activator.CreateInstance(Type type) from within the collection class.
What I need is to restrict the meta data object's type to a type that implements a specific intreface. Example:
publlic class Collection
{
public Type MetaDataType;
// other code
}
public class CollectionImplementation
{
// some properties
public CollectionImplementation()
{
Collection c = new Collection();
// valid assignment
c.MetaDataType = typeof(ValidMetaClass);
// invalid assignement
c.MetaDataType = typeof(InvalidMetaClass);
}
// some functions
}
public class ValidMetaClass : IMetaInterface
{
// valid meta class code
}
public class InvalidMetaClass
{
// invalid meta class code
}
public interface IMetaInterface
{
// interface code
}
Is something like this possible?
Thank you in advance to any and all contributors; I appreciate any and all input.
Kind regards,
me
Try using generics and type constraints on your constructor instead of setting the public field MetaDataType in your Collection class.
public CollectionImplementation<T>(T MetaDataType) where T : NameOfInterface
{
}
You can use restrictions on generic types.
public class Collection<T> where T : IMetaInterface
{
T MetaDataType;
}
Or you could just throw an exception when adding elements like:
if( !( addedElement is IMetaInterface )) throw new Exception("Please don't do this");