MapStruct - How to set different null strategy for different mapping methods? - mapstruct

I want to have a single Mapper class with both create and update methods. The generated code for create method is fine, but in case of update, I want to set the properties in the target, only if they are not null in the source.
How do I do it with mapStruct?
The confusion arises because the nullValueMappingStrategy is being defined at Mapper or Mapping level.
If I set that value at Mapper level, it will be applied to all methods, including create and update.
#Mapper // If I define null strategy here, it will be applied to all methods
public interface AmcPkgMapper {
AmcPkgMapper MAPPER = Mappers.getMapper(AmcPkgMapper.class);
AmcPackage create(AmcPackageRequest amcPackageRequest);
// How to define the null strategy here??
void update(AmcPackageRequest amcPackageRequest, #MappingTarget AmcPackage amcPackage);
}
And if I set it on the method with Mapping, then it expects me to define a target object, for which I probably need a wrapper object and somehow map all the internal properties inside that.
#Mapping(target = "amcPackage", nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
void update(AmcPackageRequest amcPackageRequest, #MappingTarget AmcPackageWrapper amcPackageWrapper);
With the above method, the generated code looks as below, which isn't going inside amcPackage to set all properties.
#Override
public void update(AmcPackageRequest amcPackageRequest, AmcPackageWrapper amcPackageWrapper) {
if ( amcPackageRequest == null ) {
return;
}
// nothing is mapped actually!!
}
Is there a simple way to do it without creating separate mapper classes for create and update?

Got it done with #BeanMapping
#BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE,
nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS)
void update(AmcPackageRequest amcPackageRequest, #MappingTarget AmcPackage amcPackage);

Related

Mapping Hierarchy of Classes with Mapstruct

I have a hierarchy of classes: VehicleDTO is a base abstract class.
CarDTO, TruckDTO, VanDTO extend from it.
I have the same hierarchy on the other side of a mapper:
VehicleBO <- CarBO, TruckBO, VanBO.
I want to have all the mapping logic consolidated in one mapper. Period.
I have defined mappings for common attributes, but here is when it becomes interesting, I get this exception during compilation:
The return type ... is an abstract class or interface.
Provide a non abstract / non interface result type or a factory method.
So, how do I specify a factory method, that based on a value of a particular attribute or a class of the pojo, would create a target object for me? I would appreciate a good code snippet that actually does the trick.
Thanks!
You can use a method annotated with #ObjectFactory receiving a source parameter for what you need.
Let's assume that you have a mapper that looks like:
#Mapper
public interface VehicleMapper {
VehicleDTO map(VehicleBO vehicle);
// more
}
If you add a method looking like:
#ObjectFactory
default VehicleDTO createVehicleDto(VehicleBO vehicle) {
// your creation logic
}
Then MapStruct will use the createVehicleDto to create the VehicleDTO object.
NOTE when mapping hierarchies and when the mapping looks like the one in the answer then MapStruct will only map the properties which are in the VehicleDTO class and not in possible implementations of the class. The reason for that is that MapStruct generates the mapping code during compilation and not during runtime.
For mapping hierarchies like what you explained you can do something like the following:
public interface VehicleMapper {
default VehicleDTO map(VehicleBO vehicle) {
if (vehicle instanceOf CarBO) {
return map((CarBO) vehicle);
} else if (vehicle instanceOf TruckBO) {
return map((TruckBO) vehicle);
} else if (vehicle instanceOf VanBO) {
return map((VanBO) vehicle);
} else {
//TODO decide what you want to do
}
}
#Named("car")
CarDTO map(CarBO car);
#Named("truck")
TruckDTO map(TruckBO truck);
#Named("car")
VanDTO map(VanBO van);
// more
}
There is mapstruct/mapstruct#131 requesting for generating code like my example out of the box
Nowadays, maybe using Visitor pattern could be better choice instead of the instanceOf way, check below:
https://techlab.bol.com/en/blog/mapstruct-object-hierarchies
You need to set the subclassExhaustiveStrategy property in your #Mapper annotation to RUNTIME_EXCEPTION.
See Mapstruct documentation:
...
To allow mappings for abstract classes or interfaces you need to set the subclassExhaustiveStrategy to RUNTIME_EXCEPTION, you can do this at the #MapperConfig, #Mapper or #BeanMapping annotations. If you then pass a GrapeDto an IllegalArgumentException will be thrown because it is unknown how to map a GrapeDto. Adding the missing (#SubclassMapping) for it will fix that.
...

Ef core abstract HasQueryFilter from DbContext

I'm trying to build some generic authorize stuff ontop of DbContext so that my devs do not need to care about authorization in the repos/domain.
Simple example like
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>().Property<string>("TenantId").HasField("_tenantId");
// Configure entity filters
modelBuilder.Entity<Blog>().HasQueryFilter(b => EF.Property<string>(b, "TenantId") == _tenantId);
modelBuilder.Entity<Post>().HasQueryFilter(p => !p.IsDeleted);
}
Which is MS example works. _tenantId will be used to create an expression and for each instance of DbContext it will use correct value of _tenantId.
But I do not want all our authorizen configured from our DB context, I want to inject it. Something like
public class AgreementAuthorization : IEntityAuthorization
{
private readonly string _legalEntityNumber;
public AgreementAuthorization(IAuthScope scope)
{
_legalEntityNumber = scope.LegalEntityNumber;
}
public void Build(ModelBuilder builder)
{
builder
.Entity<Agreement>()
.HasQueryFilter(a => _legalEntityNumber == null || a.LegalEntity.Number == _legalEntityNumber);
}
}
public class MyDbContext : DbContext
{
private IEnumerable<IEntityAuthorization> _entityAuthorization;
MyDbContext(IEnumerable<IEntityAuthorization> entityAuthorization)
{
_entityAuthorization = entityAuthorization;
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
builder.ApplyConfigurationsFromAssembly(typeof(MyDbContext).Assembly);
_entityAuthorization.ForEach(a => a.Build(builder));
}
}
This does not work, query willl test against null every time and pass. If I move the code directly to DbContext it will work. Meaning _entityAuthorization lies directly on DbContext.
From https://learn.microsoft.com/en-us/ef/core/querying/filters
Filters cannot contain references to navigation properties.
You are referencing navigation property LegalEntity.
And this could potentially affect you as well as you're passing in an IEnumerable<IEntityAuthorization>:
It is currently not possible to define multiple query filters on the
same entity - only the last one will be applied. However, you can
define a single filter with multiple conditions using the logical AND
operator (&& in C#).
Update:
My guess would then be that you need to reference a field/property of the MyDbContext directly and not within another object. Inject a class/interface that gives access to the values by which to filter then configure the filters at the end of the OnModelCreating method. You can interate over the entity configurations and their properties to apply the desired filter(s) based on the presence of the applicable property.

wicket :how to combine CompoundPropertyModel and LoadableDetachableModel

I want to achieve two goals:
I want my model to be loaded every time from the DB when it's in a life-cycle (for every request there will be just one request to the DB)
I want my model to be attached dynamically to the page and that wicket will do all this oreable binding for me
In order to achieve these two goals I came to a conclusion that I need to use both CompoundPropertyModel and LoadableDetachableModel.
Does anyone know if this is a good approach?
Should I do new CompoundPropertyModel(myLoadableDetachableModel)?
Yes, you are right, it is possible to use
new CompoundPropertyModel<T>(new LoadableDetachableModel<T> { ... })
or use static creation (it does the same):
CompoundPropertyModel.of(new LoadableDetachableModel<T> { ... })
that has both features of compound model and lazy detachable model. Also detaching works correctly, when it CompoudPropertyModel is detached it also proxies detaching to inner model that is used as the model object in this case.
I use it in many cases and it works fine.
EXPLANATION:
See how looks CompoundPropertyModel class (I'm speaking about Wicket 1.6 right now):
public class CompoundPropertyModel<T> extends ChainingModel<T>
This mean, CompoundPropertyModel adds the property expression behavior to the ChainingModel.
ChainingModel has the following field 'target' and the constructor to set it.
private Object target;
public ChainingModel(final Object modelObject)
{
...
target = modelObject;
}
This take the 'target' reference to tho object or model.
When you call getObject() it checks the target and proxies the functionality if the target is a subclass of IModel:
public T getObject()
{
if (target instanceof IModel)
{
return ((IModel<T>)target).getObject();
}
return (T)target;
}
The similar functionality is implemented for setObject(T), that also sets the target or proxies it if the target is a subclass of IModel
public void setObject(T object)
{
if (target instanceof IModel)
{
((IModel<T>)target).setObject(object);
}
else
{
target = object;
}
}
The same way is used to detach object, however it check if the target (model object) is detachable, in other words if the target is a subclass if IDetachable, that any of IModel really is.
public void detach()
{
// Detach nested object if it's a detachable
if (target instanceof IDetachable)
{
((IDetachable)target).detach();
}
}

GWT null has no properties

I have an issue when I compile the user interface, when i add a method messages.usuario(), Firebug show the error : TypeError: null has no properties
lblUsuario = new Label_2(null.nullMethod()); this is the code of my class :
public class AdministradorMVP implements EntryPoint {
private MessageConstants messages;
#Inject
public void setMensajes(MessageConstants mensajes) {
this.messages = mensajes;
}
private final MyWidgetGinjector injector = GWT.create(MyWidgetGinjector.class);
private Place defaultPlace = new SignInPlace("Admin");
private SimplePanel appWidget = new SimplePanel();
/**
* This is the entry point method.
*/
Label lblUsuario = new Label(messages.usuario());
Label lblNombre = new Label(messages.nombre());
so I can't find the source of the problem, thank you
The GWT compiler generates null.nullMethod() whenever it can statically determine that a particular method is always called on a null reference. In this case, messages has been determined to always be null (either setMensajes is called with a null value or it's not called at all), so messages.usuario() would always throw a NullPointerException, and this is translated into a null.nullMethod() in the generated JavaScript code.
From your code I'm missing the 'boostrap the injection' (see JavaDoc of Ginjector). In other words, you need to trigger the initial inject to take place. Creating MyWidgetGinjector is not enough.
One solution is to add a method void inject(AdministradorMVP entryPoint); to the interface MyWidgetGinjector and in the class AdministradorMVP in onModuleLoad call as (one of) the first statements: injector.inject(this);.

Entity Framework - Auditing activity

My database has a 'LastModifiedUser' column on every table in which I intend to collect the logged in user from an application who makes a change. I am not talking about the database user so essentially this is just a string on each entity. I would like to find a way to default this for each entity so that other developers don't have to remember to assign it any time they instantiate the entity.
So something like this would occur:
using (EntityContext ctx = new EntityContext())
{
MyEntity foo = new MyEntity();
// Trying to avoid having the following line every time
// a new entity is created/added.
foo.LastModifiedUser = Lookupuser();
ctx.Foos.Addobject(foo);
ctx.SaveChanges();
}
There is a perfect way to accomplish this in EF 4.0 by leveraging ObjectStateManager
First, you need to create a partial class for your ObjectContext and subscribe to
ObjectContext.SavingChanges Event. The best place to subscribe to this event is inside the OnContextCreated Method. This method is called by the context object’s constructor and the constructor overloads which is a partial method with no implementation:
partial void OnContextCreated() {
this.SavingChanges += Context_SavingChanges;
}
Now the actual code that will do the job:
void Context_SavingChanges(object sender, EventArgs e) {
IEnumerable<ObjectStateEntry> objectStateEntries =
from ose
in this.ObjectStateManager.GetObjectStateEntries(EntityState.Added
| EntityState.Modified)
where ose.Entity != null
select ose;
foreach (ObjectStateEntry entry in objectStateEntries) {
ReadOnlyCollection<FieldMetadata> fieldsMetaData = entry.CurrentValues
.DataRecordInfo.FieldMetadata;
FieldMetadata modifiedField = fieldsMetaData
.Where(f => f.FieldType.Name == "LastModifiedUser").FirstOrDefault();
if (modifiedField.FieldType != null) {
string fieldTypeName = modifiedField.FieldType.TypeUsage.EdmType.Name;
if (fieldTypeName == PrimitiveTypeKind.String.ToString()) {
entry.CurrentValues.SetString(modifiedField.Ordinal, Lookupuser());
}
}
}
}
Code Explanation:
This code locates any Added or Modified entries that have a LastModifiedUser property and then updates that property with the value coming from your custom Lookupuser() method.
In the foreach block, the query basically drills into the CurrentValues of each entry. Then, using the Where method, it looks at the names of each FieldMetaData item for that entry, picking up only those whose Name is LastModifiedUser. Next, the if statement verifies that the LastModifiedUser property is a String field; then it updates the field's value.
Another way to hook up this method (instead of subscribing to SavingChanges event) is by overriding the ObjectContext.SaveChanges Method.
By the way, the above code belongs to Julie Lerman from her Programming Entity Framework book.
EDIT for Self Tracking POCO Implementation:
If you have self tracking POCOs then what I would do is that I first change the T4 template to call the OnContextCreated() method. If you look at your ObjectContext.tt file, there is an Initialize() method that is called by all constructors, therefore a good candidate to call our OnContextCreated() method, so all we need to do is to change ObjectContext.tt file like this:
private void Initialize()
{
// Creating proxies requires the use of the ProxyDataContractResolver and
// may allow lazy loading which can expand the loaded graph during serialization.
ContextOptions.ProxyCreationEnabled = false;
ObjectMaterialized += new ObjectMaterializedEventHandler(HandleObjectMaterialized);
// We call our custom method here:
OnContextCreated();
}
And this will cause our OnContextCreated() to be called upon creation of the Context.
Now if you put your POCOs behind the service boundary, then it means that the ModifiedUserName must come with the rest of data from your WCF service consumer. You can either expose this
LastModifiedUser property to them to update or if it stores in another property and you wish to update LastModifiedUser from that property, then you can modify the 2nd code as follows:
foreach (ObjectStateEntry entry in objectStateEntries) {
ReadOnlyCollection fieldsMetaData = entry.CurrentValues
.DataRecordInfo.FieldMetadata;
FieldMetadata sourceField = fieldsMetaData
.Where(f => f.FieldType.Name == "YourPropertyName").FirstOrDefault();
FieldMetadata modifiedField = fieldsMetaData
.Where(f => f.FieldType.Name == "LastModifiedUser").FirstOrDefault();
if (modifiedField.FieldType != null) {
string fieldTypeName = modifiedField.FieldType.TypeUsage.EdmType.Name;
if (fieldTypeName == PrimitiveTypeKind.String.ToString()) {
entry.CurrentValues.SetString(modifiedField.Ordinal,
entry.CurrentValues[sourceField.Ordinal].ToString());
}
}
}
Hope this helps.
There is a nuget package for this now : https://www.nuget.org/packages/TrackerEnabledDbContext
Github: https://github.com/bilal-fazlani/tracker-enabled-dbcontext