How to use #ConfigProperty in a MapStruct mapper? - mapstruct

I need a #ConfigProperty in my Mapper.
I cannot inject it, since the Mapper is an interface.
How can I solve this?
#ConfigProperty(name = "limit") // <- Does not work here
int limit;
#Mapping(target = "myTarget", source = "mySource", qualifiedByName = "myLimitMapper")
MyDto toDTO(Entity entity);
#Named(value = "myLimitMapper")
default int mapLimit(int number) {
if (number >= limit) return limit;
else return number;
}

I'm assuming you're using Quarkus, seeing the #ConfigProperty. But you can define an abstract mapper and use the cdi componentModel to let MapStruct create an #ApplicationScoped CDI bean. This is described in the dependency injection section of the MapStruct docs.
E.g. you have 2 classes to map:
public record UserModel(String name, int number) {}
public record UserDto(String name, int number) {}
In which limit is configured in the application.properties as 100.
Your mapper will look like:
#Mapper(componentModel = "cdi")
public abstract class UserMapper {
#ConfigProperty(name = "limit")
int limit;
#Mapping(target = "number", source = "number", qualifiedByName = "myLimitMapper")
abstract UserDto mapToDto(UserModel userModel);
#Named(value = "myLimitMapper")
int mapLimit(int number) {
if (number >= limit) return limit;
else return number;
}
}
You could run this as a #QuarkusTest to verify:
#QuarkusTest
public class LimitTest {
#Inject
UserMapper userMapper;
#Test
public void testMapping() {
UserModel userModel = new UserModel("John", 150);
UserDto userDto = userMapper.mapToDto(userModel);
assertEquals("John", userDto.name());
assertEquals(100, userDto.number());
}
}

Related

Custom DynamoDb TableNameResolver not being called when using CrudRepository

I am testing DynamoDB tables and want to set up different table names for prod and dev environment using the keyword"dev" for development and prod for production.
I have a POJO
#DynamoDBTable(tableName = "abc_xy_dev_MyProjectName_Employee")
public class Employee implements Cloneable {
}
On Prod I want its name to be abc_xy_prod_MyProjectName_Employee.
So, I wrote a TableNameResolver
public static class MyTableNameResolver implements TableNameResolver {
public static final MyTableNameResolver INSTANCE = new MyTableNameResolver();
#Override
public String getTableName(Class<?> clazz, DynamoDBMapperConfig config) {
final TableNameOverride override = config.getTableNameOverride();
String tableNameToReturn = null;
if (override != null) {
final String tableName = override.getTableName();
if (tableName != null) {
System.out.println("MyTableNameResolver ==================================");
return tableName;
}
}
String env = System.getenv("DEPLOYMENT_ENV");
for(Annotation annotation : clazz.getAnnotations()){
if(annotation instanceof DynamoDBTable){
DynamoDBTable myAnnotation = (DynamoDBTable) annotation;
if ("production".equals(env)){
tableNameToReturn = myAnnotation.tableName().replace("_dev_", "_prod_");
}
else {
tableNameToReturn = myAnnotation.tableName();
}
}
}
return tableNameToReturn;
}
}
This works by creating a table with the name abc_xy_prod_MyProjectName_Employee in production.
However, I have a repository with the following code
#EnableScan
public interface EmployeeRepository extends CrudRepository<Employee, String>
{
#Override
<S extends Employee> S save(S employee);
Optional<Employee> findById(String id);
#Override
List<Employee> findAll();
Optional<Employee> findByEmployeeNumber(String EmployeeNumber);
}
Thus when i try to call the method findAll via a endpoint /test case, i get the exception
There was an unexpected error (type=Internal Server Error,
status=500). User:
arn:aws:iam::87668976786:user/svc_nac_ps_MyProjectName_prod is not
authorized to perform: dynamodb:Scan on resource:
:table/abc_xy_dev_MyProjectName_Employee (Service: AmazonDynamoDBv2;
Status Code: 400; Error Code: AccessDeniedException; Request ID:
aksdnhLDFL)
i.e MyTableNameResolver doesn't get called internally when the respository methods are executed. It still points to table name with the name abc_xy_dev_MyProjectName_Employee given in the annotation #DynamoDBTable(tableName = "abc_xy_dev_MyProjectName_Employee")
You have used spring JPA as persistence dynamoDB Integration.
Below configuration can be used to set table name override as part of spring boot configuration.
Sample example is found in https://github.com/ganesara/SpringExamples/tree/master/spring-dynamo
Map Dynamo db repository with user defined mapper config reference
#EnableDynamoDBRepositories(basePackages = "home.poc.spring", dynamoDBMapperConfigRef="dynamoDBMapperConfig")
Mapper Config for table override is as below
#Bean
public DynamoDBMapperConfig dynamoDBMapperConfig() {
DynamoDBMapperConfig mapperConfig = new DynamoDBMapperConfig
.Builder()
.withTableNameOverride(DynamoDBMapperConfig.TableNameOverride.withTableNamePrefix("PROD_"))
.build();
return mapperConfig;
}
Full configuration for reference
#Configuration
#EnableDynamoDBRepositories(basePackages = "home.poc.spring", dynamoDBMapperConfigRef="dynamoDBMapperConfig")
public class DynamoDBConfig {
#Value("${amazon.dynamodb.endpoint}")
private String amazonDynamoDBEndpoint;
#Value("${amazon.aws.accesskey}")
private String amazonAWSAccessKey;
#Value("${amazon.aws.secretkey}")
private String amazonAWSSecretKey;
#Bean
public AmazonDynamoDB amazonDynamoDB() {
AmazonDynamoDB amazonDynamoDB
= new AmazonDynamoDBClient(amazonAWSCredentials());
if (!StringUtils.isEmpty(amazonDynamoDBEndpoint)) {
amazonDynamoDB.setEndpoint(amazonDynamoDBEndpoint);
}
return amazonDynamoDB;
}
#Bean
public AWSCredentials amazonAWSCredentials() {
return new BasicAWSCredentials(
amazonAWSAccessKey, amazonAWSSecretKey);
}
#Bean
public DynamoDBMapperConfig dynamoDBMapperConfig() {
DynamoDBMapperConfig mapperConfig = new DynamoDBMapperConfig
.Builder()
.withTableNameOverride(DynamoDBMapperConfig.TableNameOverride.withTableNamePrefix("PROD_"))
.build();
return mapperConfig;
}
#Bean
public DynamoDBMapper dynamoDBMapper() {
return new DynamoDBMapper(amazonDynamoDB(), dynamoDBMapperConfig());
}
}
You are using DynamoDBMapper (the Java SDK). Here is how I use it. Lets say I have a table called Users, with an associated User POJO. In DynamoDB I have DEV_Users and LIVE_Users.
I have an environment variable 'ApplicationEnvironmentName' which is either DEV or LIVE.
I create a custom DynamoDBMapper like this:
public class ApplicationDynamoMapper {
private static Map<String, DynamoDBMapper> mappers = new HashMap<>();
private static AmazonDynamoDB client = AmazonDynamoDBClientBuilder.standard()
.withRegion(System.getProperty("DynamoDbRegion")).build();
protected ApplicationDynamoMapper() {
// Exists only to defeat instantiation.
}
public static DynamoDBMapper getInstance(final String tableName) {
final ApplicationLogContext LOG = new ApplicationLogContext();
DynamoDBMapper mapper = mappers.get(tableName);
if (mapper == null) {
final String tableNameOverride = System.getProperty("ApplicationEnvironmentName") + "_" + tableName;
LOG.debug("Creating DynamoDBMapper with overridden tablename {}.", tableNameOverride);
final DynamoDBMapperConfig mapperConfig = new DynamoDBMapperConfig.Builder().withTableNameOverride(TableNameOverride.withTableNameReplacement(tableNameOverride)).build();
mapper = new DynamoDBMapper(client, mapperConfig);
mappers.put(tableName, mapper);
}
return mapper;
}
}
My Users POJO looks like this:
#DynamoDBTable(tableName = "Users")
public class User {
...
}
When I want to use the database I create an application mapper like this:
DynamoDBMapper userMapper = ApplicationDynamoMapper.getInstance(User.DB_TABLE_NAME);
If I wanted to a load a User, I would do it like this:
User user = userMapper.load(User.class, userId);
Hope that helps.

How to reference an entity with inheritance in Spring Data REST when POSTing new entity?

I have entities with joined inheritance:
Supporter
#Entity
#Inheritance(strategy=InheritanceType.JOINED)
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "supporterType")
#JsonSubTypes({
#JsonSubTypes.Type(value = PersonSupporterEntity.class, name = "PERSON"),
#JsonSubTypes.Type(value = CompanySupporterEntity.class, name = "COMPANY")
})
#DiscriminatorColumn(name="supporter_type")
#Table(name = "supporter")
public class SupporterEntity extends UpdatableEntity {
private long id;
private SupporterType supporterType;
private PartnerEntity partner;
...
}
PersonSupporter
#Entity
#DiscriminatorValue("PERSON")
#Table(name = "person_supporter")
public class PersonSupporterEntity extends SupporterEntity {
...
}
CompanySupporter
#Entity
#DiscriminatorValue("COMPANY")
#Table(name = "company_supporter")
public class CompanySupporterEntity extends SupporterEntity {
...
}
I have another entity which references SupporterEntity
#Entity
#Table(name = "contact")
public class ContactEntity extends UpdatableEntity {
private long id;
private SupporterEntity supporter;
...
#ManyToOne // same error with #OneToOne
#JoinColumn(name = "supporter_id", referencedColumnName = "id", nullable = false)
public SupporterEntity getSupporter() {
return supporter;
}
...
}
Repositories
#Transactional
#RepositoryRestResource(collectionResourceRel = "supporters", path = "supporters")
public interface SupporterEntityRepository extends JpaRepository<SupporterEntity, Long> {
#Transactional(readOnly = true)
#RestResource(path = "by-partner", rel = "by-partner")
public Page<SupporterEntity> findByPartnerName(#Param("name") String name, Pageable pageable);
}
#Transactional
#RepositoryRestResource(collectionResourceRel = "person_supporters", path = "person_supporters")
public interface PersonSupporterEntityRepository extends JpaRepository<PersonSupporterEntity, Long> {
}
#Transactional
#RepositoryRestResource(collectionResourceRel = "company_supporters", path = "company_supporters")
public interface CompanySupporterEntityRepository extends JpaRepository<CompanySupporterEntity, Long> {
}
#Transactional
#RepositoryRestResource(collectionResourceRel = "contacts", path = "contacts")
public interface ContactEntityRepository extends JpaRepository<ContactEntity, Long> {
#Transactional(readOnly = true)
#RestResource(path = "by-supporter", rel = "by-supporter")
public ContactEntity findBySupporterId(#Param("id") Long id);
}
I use Spring Boot, Spring Data REST, Spring Data JPA, Hibernate, Jackson. When I try to create a new ContactEntity with a post request like this:
{
"supporter":"/supporters/52",
"postcode":"1111",
"city":"Test City 1",
"address":"Test Address 1",
"email":"test1#email.com",
"newsletter":true
}
I get this exception:
Caused by: com.fasterxml.jackson.databind.JsonMappingException: Unexpected token (VALUE_STRING), expected FIELD_NAME: missing property 'supporterType' that is to contain type id (for class com.facer.domain.supporter.SupporterEntity)
at [Source: HttpInputOverHTTP#4321c221; line: 1, column: 2] (through reference chain: com.facer.domain.supporter.ContactEntity["supporter"])
at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:148) ~[jackson-databind-2.4.4.jar:2.4.4]
After 2 days of debugging I found a way, but I kinda guessed it. So if I post it like this:
{
"supporter":{
"supporterType":"PERSON",
"id":"52"
},
"postcode":"1111",
"city":"Test City 1",
"address":"Test Address 1",
"email":"test1#email.com",
"newsletter":true
}
It works, but I don't know why. What's wrong with the other request? It works like that everywhere else when the referenced entity does not have inheritance.
Just another workaround using a RelProvider:
Do not use #JsonTypeInfo
Create a RelProvider for SupporterEntity sub-classes
#Component
#Order(Ordered.HIGHEST_PRECEDENCE)
public class SupporterEntityRelProvider implements RelProvider {
#Override
public String getCollectionResourceRelFor(final Class<?> type) {
return "supporters";
}
#Override
public String getItemResourceRelFor(final Class<?> type) {
return "supporter";
}
#Override
public boolean supports(final Class<?> delimiter) {
return org.apache.commons.lang3.ClassUtils.isAssignable(delimiter, SupporterEntity.class);
}
}
See also:
https://jira.spring.io/browse/DATAREST-344
http://docs.spring.io/spring-hateoas/docs/current/reference/html/#configuration.at-enable
It looks like a Jackson problem. To be specific, it's the following code in com.fasterxml.jackson.databind.deser.SettableBeanProperty:
if (_valueTypeDeserializer != null) {
return _valueDeserializer.deserializeWithType(jp, ctxt, _valueTypeDeserializer);
}
return _valueDeserializer.deserialize(jp, ctxt);
Without inheritance _valueDeserializer.deserialize would be called which in turn runs some Spring code to convert the URI to a Supporter.
With inheritance _valueDeserializer.deserializeWithType is called and vanilla Jackson, of course, expects an object, not a URI.
If supporter was nullable you could first POST to /contacts and then PUT the supporter's URI to /contacts/xx/supporter. Unfortunately I am not aware of any other solution.
You should be able to workaround this by setting #JsonTypeInfo(use= JsonTypeInfo.Id.NONE) at the property/method level e.g.
Try with this:
#ManyToOne // same error with #OneToOne
#JoinColumn(name = "supporter_id", referencedColumnName = "id", nullable = false)
#JsonTypeInfo(use= JsonTypeInfo.Id.NONE)
public SupporterEntity getSupporter() {
return supporter;
}

JPA, How to find an object that has composite id?

Based on second approach answered here I designed my JPA class.
#Entity(name = "SearchKeywordJPA")
#IdClass(SearchKeywordJPA.SearchKeyId.class)
public class SearchKeywordJPA implements Comparable<SearchKeywordJPA> {
#Id
private String keyword;
#Id
private long date;
private String userUUID;
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
SearchKeywordJPA that = (SearchKeywordJPA) o;
if (date != that.date) return false;
if (!keyword.equals(that.keyword)) return false;
if (!userUUID.equals(that.userUUID)) return false;
return true;
}
#Override
public int hashCode() {
int result = keyword.hashCode();
result = 31 * result + (int) (date ^ (date >>> 32));
result = 31 * result + userUUID.hashCode();
return result;
}
#Override
public String toString() {
return "SearchKeywordJPA{" +
"keyword='" + keyword + '\'' +
", date=" + date +
", userUUID='" + userUUID + '\'' +
'}';
}
public String getKeyword() {
return keyword;
}
public void setKeyword(String keyword) {
this.keyword = keyword;
}
public long getDate() {
return date;
}
public void setDate(long date) {
this.date = date;
}
public String getUserUUID() {
return userUUID;
}
public void setUserUUID(String userUUID) {
this.userUUID = userUUID;
}
#Override
public int compareTo(SearchKeywordJPA searchRecord) {
long comparedDate = searchRecord.date;
if (this.date > comparedDate) {
return 1;
} else if (this.date == comparedDate) {
return 0;
} else {
return -1;
}
}
/**********************
* Key class
**********************/
public class SearchKeyId {
private int id;
private int version;
}
}
In my servlet I want to check datastore and store my object if is not exist.
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
...
for(SearchKeywordJPA item: applicationList) {
if(!isRecorded(item))
storeRecord(item);
}
}
private boolean isRecorded(SearchKeywordJPA record) {
EntityManager em = EMF.get().createEntityManager();
SearchKeywordJPA item = em.find(SearchKeywordJPA.class, record);
return item != null;
}
private void storeRecord(SearchKeywordJPA record) {
EntityManager em = EMF.get().createEntityManager();
em.persist(record);
}
However when I run, application crashes and log says
javax.persistence.PersistenceException: org.datanucleus.store.appengine.FatalNucleusUserException: Received a request to find an object of type com.twitterjaya.model.SearchKeywordJPA identified by SearchKeywordJPA{keyword='airasia', date=1335680686149, userUUID='FFFF0000'}. This is not a valid representation of a primary key for an instance of com.twitterjaya.model.SearchKeywordJPA.
What is the reason? any suggestion would be appreciated. Thanks
You pass an instance of the IdClass into em.find ... i.e SearchKeyId. Obviously if you really have an IdClass that has no equals/hashCode/toString/constructor then you will likely get many problems. Those problems will only be increased by using an ancient plugin for GAE/Datastore.
If your Key is
#Entity(name = "SearchKeywordJPA")
#IdClass(SearchKeywordJPA.SearchKeyId.class)
public class SearchKeywordJPA implements Comparable<SearchKeywordJPA> {
you are doing it wrong.
IdClass does not need any annotation of #IdClass just the #Id
annotation.
Key can not be an entity.
Need to implements Serializable , comparable is not needed
Need to override equals and hascode and have no arg constructor
Class key need to be as follows.
public class SearchKeyId implements Serializable {
private String keyword;
private long date;
And your entity I assume something like this.
#Entity(name = "SearchKeywordJPA")
#IdClass(SearchKeyId.class)
public class SearchKeywordJPA {
#Id
private String keyword;
#Id
private long date;
private String userUUID;
Just consider that find method will use the SearchKey.class to find
the entities.
Fields that are in the IdClass need to have #Id annotation in the entity.
Key can not be an entity on its own.
Comparable is not really needed as all the comparison are placed in the IdClass

Null pointer exception when creating custom Dao with ormlite-android

I'm trying to extend the base ORMLite DAO class so I can add some custom methods. I've tried following the answer here, but I'm getting a null error and not sure how to cast the dao object correctly (Ormlite - Constructor call failing when BaseDaoImpl is extended) Currently, I have the following table:
#DatabaseTable(tableName="beers", daoClass=BeerDao.class)
public class Beer {
public static final String BEER_NAME = "name";
#DatabaseField(generatedId = true)
private UUID id = UUID.randomUUID();
#DatabaseField()
private String name;
#DatabaseField()
private String breweryName;
public Beer() {}
... getters/setters
}
The BeerDao class:
public class BeerDao<Beer,UUID> extends BaseDaoImpl<Beer,UUID> {
public BeerDao(ConnectionSource connectionSource, Class<Beer> dataClass) throws SQLException {
super(connectionSource, dataClass);
}
}
DatabaseHelper code:
public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
private static final String TAG = "Database";
private static final String DATABASE_NAME = "brewgenius.db";
private static final int DATABASE_VERSION = 7;
private BeerDao<Beer, UUID> beerDao = null;
private Dao<Checkin, UUID> checkinDao = null;
public DatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION, R.raw.ormlite_config);
}
/* ... onCreate and onUpgrade code ..*/
/**
* Get Beer Model DAO
*
* #return Beer DAO
*/
public BeerDao<Beer, UUID> getBeerDao() {
if (beerDao == null) {
try {
beerDao = getDao(Beer.class);
} catch (SQLException e) {
e.printStackTrace();
}
}
return beerDao;
}
}
In my activity, when I try to get the Dao, I get a null pointer exception.
BeerDao<Beer,UUID> dao = getHelper().getBeerDao();
FYI, Dao is cast to:
BeerDao<Beer,UUID>
UPDATE
It looks like my DatabaseConfigUtil isn't reading the DaoClass attribute.
public class DatabaseConfigUtil extends OrmLiteConfigUtil {
public static void main(String[] args) throws SQLException, IOException {
writeConfigFile("ormlite_config.txt");
}
}
Removing ormlite_config.txt from DatabaseHelper's constuctor caused on-the-fly reading which correctly reads the DaoClass attribute. Any idea why the writer isn't writing this?
Here's ormlite_config.txt
# --table-start--
dataClass=com.brewgenius.model.Beer
tableName=beers
# --table-fields-start--
# --field-start--
fieldName=id
columnName=_id
generatedId=true
# --field-end--
# --field-start--
fieldName=name
# --field-end--
# --field-start--
fieldName=breweryName
# --field-end--
# --table-fields-end--
# --table-end--
#################################

JAXB works with JPA, throw IllegalAnnotationsException

I have been stacked in this place for few days. any help will be appreciated.
Here is my story:
I have a JPA entity class (ExtOffer), and Now I annotate it with JAXB annotation in order to do marshall/unmarshall by JAXB. And I also create a wrapper class(ExtOffers), which basically is a collection of ExtOffer.
And when I call JAXBContext.newInstance(ExtOffers.class), I got an IllegalAnnotationsException:JAXB annotation is placed on a method that is not a JAXB property.
I search google and some post says that it's due to annotate #XmlElement on wrong place.
But my class has #XmlAccessorType(XmlAccessType.NONE) annotation and only the getter method has been annotated with #Xmlelement.
below is my ExtOffer class and ExtOffers class:
//ExtOffer:
#Entity
#Table (name = "extoffer")
#XmlType(name = "ExtOfferType")
#XmlAccessorType(XmlAccessType.NONE)
public class ExtOffer {
public ExtOffer() {
}
#Id
#Column(name = "OfferID", nullable = false, unique = true, length = 32)
protected String offerId;
#Column(name = "HasMoreScreenShot", nullable = false, unique = false, length = 1)
private String hasMoreScreenShot;
public void setOfferId(String offerId) {
this.offerId = offerId;
}
#XmlElement(name="OfferID", required = true)
public String getOfferId() {
return offerId;
}
public void setHasMoreScreenShot(String hasMoreScreenShot) {
this.hasMoreScreenShot= hasMoreScreenShot;
}
#XmlElement(name = "HasMoreScreenShot")
public String GetHasMoreScreenShot() {
return hasMoreScreenShot;
}
}
//ExtOffers wrapper
#XmlRootElement(name="extoffers")
#XmlAccessorType(XmlAccessType.NONE)
public class ExtOfferWrapper {
private List<ExtOffer> extoffers;
public ExtOfferWrapper() {
}
#XmlElement(name="extoffer")
public List<ExtOffer> getExtoffers() {
return extoffers;
}
public void setExtoffers(List<ExtOffer> extoffers) {
this.extoffers = extoffers;
}
}
JAXB annotation is placed on a method that is not a JAXB property
this problem is related to the following location:
at #javax.xml.bind.annotation.XmlElement(nillable=false, name=HasMoreScreenShot, required=false, defaultValue=, type=class javax.xml.bind.annotation.XmlElement$DEFAULT, namespace=##default)
at com.symbio.fuhu.appstore.jpa.entity.ExtOffer
at public java.util.List com.symbio.fuhu.appstore.jaxb.mapping.wrapper.ExtOfferWrapper.getExtoffers()
at com.symbio.fuhu.appstore.jaxb.mapping.wrapper.ExtOfferWrapper
You have an upper case 'G'
#XmlElement(name = "HasMoreScreenShot")
public String GetHasMoreScreenShot() {
return hasMoreScreenShot;
}