Convert List to another List with multiple parameters - mapstruct

I have an existing mapping of 2 objects ExpertJpa to ExpertDto that need another param to filter ExpertJpa.
This map working properly and now I try to convert List of ExpertJpa to List of ExpertDto, I add this second param.
#Mappings({
#Mapping(target = "status", ignore = true),
#Mapping(target = "profile", source = "input.expertProfile"),
#Mapping(target = "engagementId", expression = "java(new MapperHelper().ReturnExpertEngagementIdByApiKey(input,identity))"),
#Mapping(target = "campaignId", expression = "java(new MapperHelper().ReturnExpertCampaignIdByApiKey(input,identity))"),
})
Expert ExpertJpaToExpert(com.consumer.expert.dbaccessor.entities.Expert input, Identity identity);
List<Expert> ListExpertsJpaToListExperts(List<com.consumer.expert.dbaccessor.entities.Expert> input, Identity identity);
On build, I get Error message that List is an interface and cannot be instance....
Error:(53, 18) java: The return type java.util.List is an abstract class or interface. Provide a non abstract / non interface result type or a factory method.

MapStruct can do this automatically for you. However it cannot handle multiple argument methods (in principle it maps source to target).
Having said that, if you rewrite your code a little bit it you could get rid of the expression and have a full type safe solution.
So:
class IdentityContext {
private final Identity id;
private final MapperHelper mapperHelper;
public IdentityContext(Identity id){
this.id = id;
this.mapperHelper = new MapperHelper();
}
#AfterMapping
public void setIds(com.consumer.expert.dbaccessor.entities.Expert input, #MappingTarget Expert expertOut) {
expertOut.setEngagementId( mapperHelper.ReturnExpertEngagementIdByApiKey(input,identity) );
expertOut.setCampaignId( mapperHelper. ReturnExpertCampaignIdByApiKey(input,identity) );
}
}
now define your mapper as such:
#Mappings({
#Mapping(target = "status", ignore = true),
#Mapping(target = "profile", source = "input.expertProfile")
})
Expert ExpertJpaToExpert(com.consumer.expert.dbaccessor.entities.Expert input, #Context IdentityContext ctx);
List<Expert> ListExpertsJpaToListExperts(List<com.consumer.expert.dbaccessor.entities.Expert> input, #Context IdentityContext ctx)
Note: MapStruct will now recognise the list mapping because the IdentityContext is marked as #Context (so: it will be only set in the calling method but in essence not be part of the mapping source-target itself).

Related

Mapstruct: ignored MapperConfig

I've two MapperConfig:
#MapperConfig(
uses = {
StringTypeMapper.class,
ExtensionMapper.class
}
)
public interface ElementMapperConfig extends GenericMapperConfig {
#Mapping(target = "id", source = "idElement")
#Mapping(target = "extension", source = "extension")
Element mapElement(org.hl7.fhir.r4.model.Element fhir);
}
And GenericMapperConfig:
#MapperConfig(
componentModel = "spring",
injectionStrategy = InjectionStrategy.CONSTRUCTOR,
nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS,
nullValueMappingStrategy = NullValueMappingStrategy.RETURN_NULL
)
public interface GenericMapperConfig {
}
As you can see, I'm using spring component model.
Nevertheless, Mapper implementation is getting required Mapper using Mappers.getMapper(...).
As you can see, ElementMapperConfig extends GenericMapperConfig, but it seems that configuration from GenericMapperConfig is ignored.
Generated Mapper example:
#Generated(
value = "org.mapstruct.ap.MappingProcessor"
)
public class StringTypeMapperImpl implements StringTypeMapper {
private final ExtensionMapper extensionMapper = Mappers.getMapper( ExtensionMapper.class );
}
StringTypeMapper is:
#Mapper(
config = ElementMapperConfig.class
)
public interface StringTypeMapper {
#InheritConfiguration(name = "mapElement")
StringType fhirToMpi(org.hl7.fhir.r4.model.StringType stringType);
}
I don't quite figure out why GenericMapperConfig configuration is not populated, I mean, I don't get why componentModel = "spring" is ignored on Mapper implementation.
The documentation does not mention this way of composing multiple MapperConfigurations.
It also does not mention another way of doing it, but this one works. The idea is to extend mappers instead of configurations.
Introduce a base mapper with generic configuration:
#Mapper(
config = GenericMapperConfig.class
)
public interface BaseMapper {
}
Base your concrete mapper on the base one and configure it using the specific configuration:
#Mapper(
config = ElementMapperConfig.class
)
public interface StringTypeMapper extends BaseMapper {
#InheritConfiguration(name = "mapElement")
StringType fhirToMpi(org.hl7.fhir.r4.model.StringType stringType);
}
Finally make ElementMapperConfig not inherit GenericMapperConfig:
#MapperConfig(
uses = {
StringTypeMapper.class,
ExtensionMapper.class
}
)
public interface ElementMapperConfig {
#Mapping(target = "id", source = "idElement")
#Mapping(target = "extension", source = "extension")
Element mapElement(org.hl7.fhir.r4.model.Element fhir);
}

Why ContentFragment.adaptTo() return null?

I want to create a custom sling model which can be adapted from com.adobe.cq.dam.cfm.ContentFragment
like below,
import com.adobe.cq.dam.cfm.ContentFragment;
#Model(adaptables = ContentFragment.class, adapters = EventInfo.class)
public class EventInfoImpl implements EventInfo{
#Self
ContentFragment cf;
#Override
public String getTitle(){
return cf.getElement("title").getContent();
}
}
but in a caller class,
EventInfo e = contentFragment.adaptTo(EventInfo.class);
adaptTo() returns null.(variable "e" is null)
Why adaptTo() returns null? and How do I adapt correctly in this case?
Sling models can only be adapted from Resource or SlingHttpServletRequest. For anything else you need a classic AdapterFactory.
https://sling.apache.org/apidocs/sling11/org/apache/sling/api/adapter/AdapterFactory.html
How to implement a custom AdapterFactory for Sling Resource?
You can see it in the Sling source code of the ModelAdapterFactory. There is the method createModel:
<ModelType> ModelType createModel(#NotNull Object adaptable, #NotNull Class<ModelType> type)
https://github.com/apache/sling-org-apache-sling-models-impl/blob/master/src/main/java/org/apache/sling/models/impl/ModelAdapterFactory.java
If you dig down, the real filtering happens in the helper-class AdapterImplementations (line 258-268)
https://github.com/apache/sling-org-apache-sling-models-impl/blob/master/src/main/java/org/apache/sling/models/impl/AdapterImplementations.java
if (adaptableType == Resource.class) {
map = resourceTypeMappingsForResources;
resourceTypeRemovalLists = resourceTypeRemovalListsForResources;
} else if (adaptableType == SlingHttpServletRequest.class) {
map = resourceTypeMappingsForRequests;
resourceTypeRemovalLists = resourceTypeRemovalListsForRequests;
} else {
log.warn("Found model class {} with resource type {} for adaptable {}. Unsupported type for resourceType binding.",
new Object[] { clazz, resourceType, adaptableType });
return;
}

Document QueryDSL endpoint with Swagger

I'm using Spring Boot Data, QueryDSL and Swagger.
I've define endpoint like this:
#GetMapping
public ResponseEntity<?> listOfThings(
#PageableDefault(size = 20, sort = "uID", direction = Sort.Direction.DESC) final Pageable pageable,
#QuerydslPredicate(root = Thing.class) final Predicate predicate)
However Swagger define only variables: page, size, sort - it doesn't seem to parse Entity to show all fields as filterable.
I have repository like this:
#Repository
public interface ThingRepository
extends JpaSpecificationExecutor<Thing>, CrudRepository<Thing, String>, PagingAndSortingRepository<Thing, String>,
QuerydslPredicateExecutor<Thing>, QuerydslBinderCustomizer<QThing>
{
#Override
default void customize(final QuerydslBindings bindings, final QThing thing)
{
bindings.bind(thing.status).first((status, value) -> status.eq(value));
bindings.bind(thing.recipient).first(StringExpression::containsIgnoreCase);
bindings.bind(String.class).first((StringPath path, String value) -> path.containsIgnoreCase(value));
}
}
I expect Swagger to display all String fields as filters, especially status & recipient which are strictly defined.
Use maven dependency:
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-data-rest</artifactId>
<version>1.6.8</version>
</dependency>
And specify your endpoint like this:
#GetMapping(produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<List<Case>> getCasesByQuery(#ParameterObject
#QuerydslPredicate(root = Case.class, bindings = CaseRepository.class) Predicate predicate,
#ParameterObject Pageable pageable) {
Now you should see all query params in SwaggerUI.
Define some dummy parameters and add all query parameters as RequestParams but do not use them ... just use the predicate. We use this as a workaround to support swagger file for codegeneration. Not perfect but works!
public Iterable<SCGameInfo> findSCGameInfo(
#QuerydslPredicate(root = GameInfo.class) Predicate predicate,
#RequestParam(name= "gameName",required = false) String gameName,
#RequestParam(name= "vendor",required = false) String vendor
){
return scService.findAllGameInfosForPredicate(predicate);
}

How do you create a dynamic QueryDSL operator based on a quoted String value

I have a pojo that contains a property name, logic operator as String and the value of property. What I want to accomplish is create a Predicate or Expression etc dynamically from the pojo data. Below are my code:
public class QueryParam {
private String property = "acctType"; //can be any property of classname
private String operator = "eqic" //can be any logic operator !=, >, <, >=, <= etc
private Object value; //will store the value of
// getters/setters here
}
public interface CustomerRepository extends JpaRepository<Customer, Long>, QueryDslPredicateExecutor<Customer>{
}
#Service("CustomerService")
class MyCustomerServiceImpl {
#Resource
private CustomerRepository custRpstry;
//if classname is Customer, property is "acctType", operator is "eqic", and value is "Corporate"
//I want my findAll below to retrieve all Customers having acctType = "Corporate"
List<Customer> findAll(List<QueryParam> qryParam) {
QCustomer qcust = QCustomer.customer;
BooleanBuilder where = new BooleanBuilder();
for(QueryParam param : qryParam) {
//within this block, i want a BooleanBuilder to resolve to:
where.and(qcust.acctType.equalsIgnoreCase("Corporate"));
something like:
where.and(param.getClassname().param.getProperty().param.getOperator().param.getValue())
}
return custRpstry.findAll(where.getValue()).getContent();
}
}
I can't figure out to formulate my BooleanBuilder especially the portion that will convert
getOperator() into .equalIgnoreCase().
Any help would be greatly appreciated.
Thanks in advance,
Mario
After combining several answers to some related questions here in so, I was able to formulate a solution that works for me.
BooleanBuilder where = new BooleanBuilder();
for(QueryParam param: qryParam) {
//create: Expressions.predicate(Operator<Boolean> opr, StringPath sp, filter value)
//create an Operator<Boolean>
Operator<Boolean> opr = OperationUtils.getOperator(param.getOperator().getValue());
//create a StringPath to a class' property
Path<User> entityPath = Expressions.path(Customer.class, "customer");
Path<String> propPath = Expressions.path(String.class, entityPath, param.getProperty());
//create Predicate expression
Predicate predicate = Expressions.predicate(opr, propPath, Expressions.constant(param.getValue()));
where.and(predicate);
}
list = repository.findAll(where.getValue(), pageReq).getContent();
My OperationUtils.java
public class OperationUtils {
public static com.mysema.query.types.Operator<Boolean> getOperator(String key) {
Map<String, com.mysema.query.types.Operator<Boolean>> operators = ImmutableMap.<String, com.mysema.query.types.Operator<Boolean>>builder()
.put(Operator.EQ.getValue() ,Ops.EQ)
.put(Operator.NE.getValue() ,Ops.NE)
.put(Operator.GT.getValue() ,Ops.GT)
.put(Operator.GTE.getValue() ,Ops.GOE)
.put(Operator.LT.getValue() ,Ops.LT)
.put(Operator.LTE.getValue() ,Ops.LOE)
.build();
return operators.get(key);
}
}

Where does IModel Apache Wicket retrieve an object?

First of all, please take a look at how IModel is used in this example:
#SuppressWarnings("serial")
public static List<IColumn> getTableColumns(
final ReportParams reportParams, final boolean columnsSortable
) {
List<IColumn> columns = new ArrayList<IColumn>();
final Map<String,ToolInfo> eventIdToolMap = Locator.getFacade().getEventRegistryService().getEventIdToolMap();
// site
if(Locator.getFacade().getReportManager().isReportColumnAvailable(reportParams, StatsManager.T_SITE)) {
columns.add(new PropertyColumn(new ResourceModel("th_site"), columnsSortable ? ReportsDataProvider.COL_SITE : null, ReportsDataProvider.COL_SITE) {
#Override
public void populateItem(Item item, String componentId, IModel model) {
final String site = ((Stat) model.getObject()).getSiteId();
String lbl = "", href = "";
Site s = null;
try{
s = Locator.getFacade().getSiteService().getSite(site);
lbl = s.getTitle();
href = s.getUrl();
}catch(IdUnusedException e){
lbl = (String) new ResourceModel("site_unknown").getObject();
href = null;
}
item.add(new ImageWithLink(componentId, null, href, lbl, "_parent"));
}
});
}
And my questions are:
How does populateItem get an input for IModel parameter?
I cannot find any code in this application, which explicitly constructs IModel object. Is it correct for me to assume that the object is retrieved directly from a table in the database? I'm thinking of this because Mapping Hibernate is used for this application.
The models are created using the IDataProvider you provide to the DataTable (DataTable constructor will also take your IColumn List) .
The IDataProvider could use Hibernate - hard to say without having more information on that implementation.