I really can't figure out what's going on.
Here's my openapi generated class which will be doing the updating:
/**
* Consultation
*/
@Schema(name = "Consultation", description = "Consultation")
@Generated(value = "org.openapitools.codegen.languages.SpringCodegen", date = "2023-03-31T19:23:20.818876600+02:00[Europe/Brussels]")
public class Consultation {
@JsonProperty("publicId")
private UUID publicId;
@JsonProperty("decision")
private Decision decision;
@JsonProperty("consultationDate")
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
private LocalDateTime consultationDate;
@JsonProperty("languageCode")
private String languageCode;
@JsonProperty("remoteIpAddress")
private String remoteIpAddress;
@JsonProperty("remoteIpAddressCountryCode")
private String remoteIpAddressCountryCode;
@JsonProperty("createdBy")
private String createdBy;
@JsonProperty("createdOn")
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
private LocalDateTime createdOn;
@JsonProperty("modifiedBy")
private String modifiedBy;
@JsonProperty("modifiedOn")
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
private LocalDateTime modifiedOn;
public Consultation publicId(UUID publicId) {
this.publicId = publicId;
return this;
}
/**
* ID of the consultation
* @return publicId
*/
@Valid
@Schema(name = "publicId", description = "ID of the consultation", required = false)
public UUID getPublicId() {
return publicId;
}
public void setPublicId(UUID publicId) {
this.publicId = publicId;
}
public Consultation decision(Decision decision) {
this.decision = decision;
return this;
}
/**
* Get decision
* @return decision
*/
@Valid
@Schema(name = "decision", required = false)
public Decision getDecision() {
return decision;
}
public void setDecision(Decision decision) {
this.decision = decision;
}
public Consultation consultationDate(LocalDateTime consultationDate) {
this.consultationDate = consultationDate;
return this;
}
/**
* date of the consultation
* @return consultationDate
*/
@Valid
@Schema(name = "consultationDate", description = "date of the consultation", required = false)
public LocalDateTime getConsultationDate() {
return consultationDate;
}
public void setConsultationDate(LocalDateTime consultationDate) {
this.consultationDate = consultationDate;
}
public Consultation languageCode(String languageCode) {
this.languageCode = languageCode;
return this;
}
/**
* language code of the consultation
* @return languageCode
*/
@Schema(name = "languageCode", description = "language code of the consultation", required = false)
public String getLanguageCode() {
return languageCode;
}
public void setLanguageCode(String languageCode) {
this.languageCode = languageCode;
}
public Consultation remoteIpAddress(String remoteIpAddress) {
this.remoteIpAddress = remoteIpAddress;
return this;
}
/**
* IP address of the user who consulted the decision
* @return remoteIpAddress
*/
@Schema(name = "remoteIpAddress", description = "IP address of the user who consulted the decision", required = false)
public String getRemoteIpAddress() {
return remoteIpAddress;
}
public void setRemoteIpAddress(String remoteIpAddress) {
this.remoteIpAddress = remoteIpAddress;
}
public Consultation remoteIpAddressCountryCode(String remoteIpAddressCountryCode) {
this.remoteIpAddressCountryCode = remoteIpAddressCountryCode;
return this;
}
/**
* Country code of the IP address of the user who consulted the decision
* @return remoteIpAddressCountryCode
*/
@Schema(name = "remoteIpAddressCountryCode", description = "Country code of the IP address of the user who consulted the decision", required = false)
public String getRemoteIpAddressCountryCode() {
return remoteIpAddressCountryCode;
}
public void setRemoteIpAddressCountryCode(String remoteIpAddressCountryCode) {
this.remoteIpAddressCountryCode = remoteIpAddressCountryCode;
}
public Consultation createdBy(String createdBy) {
this.createdBy = createdBy;
return this;
}
/**
* User who created the consultation
* @return createdBy
*/
@Schema(name = "createdBy", description = "User who created the consultation", required = false)
public String getCreatedBy() {
return createdBy;
}
public void setCreatedBy(String createdBy) {
this.createdBy = createdBy;
}
public Consultation createdOn(LocalDateTime createdOn) {
this.createdOn = createdOn;
return this;
}
/**
* Date when the consultation was created
* @return createdOn
*/
@Valid
@Schema(name = "createdOn", description = "Date when the consultation was created", required = false)
public LocalDateTime getCreatedOn() {
return createdOn;
}
public void setCreatedOn(LocalDateTime createdOn) {
this.createdOn = createdOn;
}
public Consultation modifiedBy(String modifiedBy) {
this.modifiedBy = modifiedBy;
return this;
}
/**
* User who last modified the consultation
* @return modifiedBy
*/
@Schema(name = "modifiedBy", description = "User who last modified the consultation", required = false)
public String getModifiedBy() {
return modifiedBy;
}
public void setModifiedBy(String modifiedBy) {
this.modifiedBy = modifiedBy;
}
public Consultation modifiedOn(LocalDateTime modifiedOn) {
this.modifiedOn = modifiedOn;
return this;
}
/**
* Date when the consultation was last modified
* @return modifiedOn
*/
@Valid
@Schema(name = "modifiedOn", description = "Date when the consultation was last modified", required = false)
public LocalDateTime getModifiedOn() {
return modifiedOn;
}
public void setModifiedOn(LocalDateTime modifiedOn) {
this.modifiedOn = modifiedOn;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Consultation consultation = (Consultation) o;
return Objects.equals(this.publicId, consultation.publicId) &&
Objects.equals(this.decision, consultation.decision) &&
Objects.equals(this.consultationDate, consultation.consultationDate) &&
Objects.equals(this.languageCode, consultation.languageCode) &&
Objects.equals(this.remoteIpAddress, consultation.remoteIpAddress) &&
Objects.equals(this.remoteIpAddressCountryCode, consultation.remoteIpAddressCountryCode) &&
Objects.equals(this.createdBy, consultation.createdBy) &&
Objects.equals(this.createdOn, consultation.createdOn) &&
Objects.equals(this.modifiedBy, consultation.modifiedBy) &&
Objects.equals(this.modifiedOn, consultation.modifiedOn);
}
@Override
public int hashCode() {
return Objects.hash(publicId, decision, consultationDate, languageCode, remoteIpAddress, remoteIpAddressCountryCode, createdBy, createdOn, modifiedBy, modifiedOn);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("class Consultation {\n");
sb.append(" publicId: ").append(toIndentedString(publicId)).append("\n");
sb.append(" decision: ").append(toIndentedString(decision)).append("\n");
sb.append(" consultationDate: ").append(toIndentedString(consultationDate)).append("\n");
sb.append(" languageCode: ").append(toIndentedString(languageCode)).append("\n");
sb.append(" remoteIpAddress: ").append(toIndentedString(remoteIpAddress)).append("\n");
sb.append(" remoteIpAddressCountryCode: ").append(toIndentedString(remoteIpAddressCountryCode)).append("\n");
sb.append(" createdBy: ").append(toIndentedString(createdBy)).append("\n");
sb.append(" createdOn: ").append(toIndentedString(createdOn)).append("\n");
sb.append(" modifiedBy: ").append(toIndentedString(modifiedBy)).append("\n");
sb.append(" modifiedOn: ").append(toIndentedString(modifiedOn)).append("\n");
sb.append("}");
return sb.toString();
}
/**
* Convert the given object to string with each line indented by 4 spaces
* (except the first line).
*/
private String toIndentedString(Object o) {
if (o == null) {
return "null";
}
return o.toString().replace("\n", "\n ");
}
}
The business object which will be updated by the mapper:
public class ConsultationBO extends BasicData {
private final UUID publicId;
private final Decision decision;
private final LocalDateTime consultationDate;
private final String languageCode;
private final String remoteIpAddress;
private final String remoteIpAddressCountryCode;
public ConsultationBO(String createdBy,
LocalDateTime createdOn,
String modifiedBy,
LocalDateTime modifiedOn,
UUID publicId,
Decision decision,
LocalDateTime consultationDate,
String languageCode,
String remoteIpAddress,
String remoteIpAddressCountryCode) {
super(createdBy, createdOn, modifiedBy, modifiedOn);
this.publicId = publicId;
this.decision = decision;
this.consultationDate = consultationDate;
this.languageCode = languageCode;
this.remoteIpAddress = remoteIpAddress;
this.remoteIpAddressCountryCode = remoteIpAddressCountryCode;
}
public UUID getPublicId() {
return publicId;
}
public Decision getDecision() {
return decision;
}
public LocalDateTime getConsultationDate() {
return consultationDate;
}
public String getLanguageCode() {
return languageCode;
}
public String getRemoteIpAddress() {
return remoteIpAddress;
}
public String getRemoteIpAddressCountryCode() {
return remoteIpAddressCountryCode;
}
}
Here's the mapper:
@Mapper
@Component
public interface ConsultationMapper {
ConsultationBO consultationLogEntityToConsultationBO(DecisionConsultationLogEntity entity);
void updateConsultationLogEntityWithConsultationBO(ConsultationBO businessObject, @MappingTarget DecisionConsultationLogEntity entity);
DecisionConsultationLogEntity consultationBOToConsultationLogEntity(ConsultationBO businessObject);
Consultation consultationBOToConsultationAPIModel(ConsultationBO businessObject);
// This one isn't being implemented
void updateConsultationBOWithConsultationAPIModel(Consultation apiModel, @MappingTarget ConsultationBO businessObject);
ConsultationBO consultationAPIModelToConsultationBO(Consultation apiModel);
}
Here's the implementation that mapstruct is generating for the one before last mapper method:
@Override
public void updateConsultationBOWithConsultationAPIModel(Consultation apiModel, ConsultationBO businessObject) {
if ( apiModel == null ) {
return;
}
}
There should be a whole implementation of the mapping like all the other methods who have succesfully been generated. Here's an example:
@Override
public ConsultationBO consultationAPIModelToConsultationBO(Consultation apiModel) {
if ( apiModel == null ) {
return null;
}
String createdBy = null;
LocalDateTime createdOn = null;
String modifiedBy = null;
LocalDateTime modifiedOn = null;
UUID publicId = null;
be.fgov.just.judgment.dvaauthenticsourceapi.domain.Decision decision = null;
LocalDateTime consultationDate = null;
String languageCode = null;
String remoteIpAddress = null;
String remoteIpAddressCountryCode = null;
createdBy = apiModel.getCreatedBy();
createdOn = apiModel.getCreatedOn();
modifiedBy = apiModel.getModifiedBy();
modifiedOn = apiModel.getModifiedOn();
publicId = apiModel.getPublicId();
decision = decisionToDecision1( apiModel.getDecision() );
consultationDate = apiModel.getConsultationDate();
languageCode = apiModel.getLanguageCode();
remoteIpAddress = apiModel.getRemoteIpAddress();
remoteIpAddressCountryCode = apiModel.getRemoteIpAddressCountryCode();
ConsultationBO consultationBO = new ConsultationBO( createdBy, createdOn, modifiedBy, modifiedOn, publicId, decision, consultationDate, languageCode, remoteIpAddress, remoteIpAddressCountryCode );
return consultationBO;
}
I thought it was because the source class is generated by openapi maven generator, but it isn't a problem for any other methods. Then I thought maybe it's because there's no constructor in the generated class. But that makes no sense because that's just the source, it doesn't need to set anything on that class. My BO class which is the target, does have a constructor which should be used automatically by mapstruct, but no setters indeed. I asked my tech lead and he said to leave the class immutable.
Maybe I need to specify that it should use the constructor, but that doesn't make sense either... Per the mapstruct docs:
MapStruct supports using constructors for mapping target types. When doing a mapping MapStruct checks if there is a builder for the type being mapped. If there is no builder, then MapStruct looks for a single accessible constructor.
Also, it can find the constructor and use it in standard mappings into the BO. Here's another method it implemented correctly using the constructor of the immutable BO class:
@Override
public ConsultationBO consultationLogEntityToConsultationBO(DecisionConsultationLogEntity entity) {
if ( entity == null ) {
return null;
}
String createdBy = null;
LocalDateTime createdOn = null;
String modifiedBy = null;
LocalDateTime modifiedOn = null;
UUID publicId = null;
be.fgov.just.judgment.dvaauthenticsourceapi.domain.Decision decision = null;
LocalDateTime consultationDate = null;
String languageCode = null;
String remoteIpAddress = null;
String remoteIpAddressCountryCode = null;
createdBy = entity.getCreatedBy();
createdOn = entity.getCreatedOn();
modifiedBy = entity.getModifiedBy();
modifiedOn = entity.getModifiedOn();
publicId = entity.getPublicId();
decision = decisionEntityToDecision( entity.getDecision() );
consultationDate = entity.getConsultationDate();
languageCode = entity.getLanguageCode();
remoteIpAddress = entity.getRemoteIpAddress();
remoteIpAddressCountryCode = entity.getRemoteIpAddressCountryCode();
ConsultationBO consultationBO = new ConsultationBO( createdBy, createdOn, modifiedBy, modifiedOn, publicId, decision, consultationDate, languageCode, remoteIpAddress, remoteIpAddressCountryCode );
return consultationBO;
}
The problem here is that ConsultationBO
cannot be modified once created (all of its fields are final, and have no setters), but you're using @MappingTarget
to update it. The constructor isn't being used since @MappingTarget
provides an object to be updated, rather than creating a new object. And since none of the properties on it are modifiable, the object is returned without changes.
One solution is to instead have MapStruct create the object for you:
ConsultationBO map(Consultation apiModel);
The other solution would be to make ConsultationBO
mutable (change its fields to be non-final and add setters).