How to get annotations name atributes? - java

I have a class named ClBranch.java like below:
#Entity
#Table(name = "PROVINCE")
public class PROVINCE implements Serializable {
#Id
#Column(name="PR_CODE", length = 50)
private String provinceCode
#Column(name="PR_NAME", length = 500)
private String provinceName
......
getter-setter.
}
This is my code:
public static String getClassAnnotationValue(Class classType, Class annotationType, String attributeName) {
String value = null;
Annotation annotation = classType.getAnnotation(annotationType);
if (annotation != null) {
try {
value = (String) annotation.annotationType().getMethod(attributeName).invoke(annotation);
} catch (Exception ex) {
ex.printStackTrace();
}
}
return value;
}
String columnName = getClassAnnotationValue(PROVINCE .class, Column.class, "name");
By this way, I only get ColumnName as PROVINCE. I can not get ColumnName. How can I do it?

The #Column annotation is defined on the fields, not on the class. So you must query annotation values from the private fields:
String columnName = getAnnotationValue(PROVINCE.class.getDeclaredField("provinceCode"), Column.class, "name");
To be able to pass Field objects to your method, change the type of your classType parameter from Class to AnnotatedElement. Then you can pass classes, fields, parameters or methods:
public static String getAnnotationValue(AnnotatedElement element, Class annotationType, String attributeName) {
...
}

Related

Java Reflection set enum in model

I'm trying to update my DTO with Reflection. The problem is that some fields in my DTO are enums and I get an error that I can not set the enum field to String.
DTO:
#Entity
#AllArgsConstructor
#NoArgsConstructor
#Data
#Builder
#Table(name = "xxx")
public class Model {
#Id
#Column(name = "id")
private String runId;
#Column(name = "name")
private String name;
#Column(name = "status")
#Enumerated(EnumType.STRING)
private ExecutionStatus status;
}
Controller:
#PatchMapping(path = "/{id}", consumes = "application/json")
public ResponseEntity<Void> partialUpdateModel(#PathVariable String id, #RequestBody Map<Object, Object> fields)
throws Exception {
Optional<Model> model= service.getById(id);
if (model.isPresent()) {
fields.forEach((key, value) -> {
Field field = ReflectionUtils.findField(Model.class, (String) key);
field.setAccessible(true);
ReflectionUtils.setField(field, model.get(), value);
});
So when it comes to the enum field, the field can not be set. It says
cannot set ExecutionStatus to String
What you are trying to do is this:
model.status = "STATUS_1";
// incompatible types: java.lang.String cannot be converted to so.A.ExecutionStatus
What you apparently want to do is finding the enum constant of the specified enum type with the specified string. That's what the Enum.valueOf or YourEnum.valueOf method is doing.
Example:
enum ExecutionStatus {
STATUS_1,
STATUS_2,
}
static class Model {
public String runId;
public String name;
public ExecutionStatus status;
#Override
public String toString() {
return "Model{" +
"runId='" + runId + '\'' +
", name='" + name + '\'' +
", status=" + status +
'}';
}
}
public static void main(String[] args) {
Map<Object, Object> fields = Map.of(
"runId", "MyRunId",
"name", "MyName",
"status", "STATUS_1"
);
Model model = new Model();
fields.forEach((key, value) -> {
Field field = null;
try {
field = Model.class.getDeclaredField((String) key);
field.setAccessible(true);
if (field.getType().isEnum()) {
// First variant (YourEnum.valueOf(String)
Method valueOf = field.getType().getMethod("valueOf", String.class);
Object enumConstant = valueOf.invoke(null, value);
field.set(model, enumConstant);
// Alternative (Enum.valueOf(Class, String) (cast is safe due to isEnum)
field.set(model, Enum.valueOf((Class<Enum>) field.getType(), (String) value));
} else {
field.set(model, value);
}
} catch (NoSuchFieldException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
e.printStackTrace();
}
});
System.out.println(model);
}
You can do like this, Also understand reflection is costly it involves types that are being dynamically resolved, i'd recommend writing setters or constructor instead
if ("ExecutionStatus".equalsIgnoreCase(field.getType().getSimpleName())) {
ReflectionUtils.setField(field, model.get(), ExecutionStatus.valueOf(value));
} else {
ReflectionUtils.setField(field, model.get(), value);
}
This is because your source map is of type <Object, Object>
What you try to do is to set a field of type ExecutionStatus reading a value of type Object. Types on your fields must match. First convert a value to ExecutionStatus then use the method .setField(..).

How to fix missing descriptor for class POJO after update server? [duplicate]

I'm using EclipseLink to run some Native SQL. I need to return the data into a POJO. I followed the instructions at EclipseLink Docs, but I receive the error Missing descriptor for [Class]
The query columns have been named to match the member variables of the POJO. Do I need to do some additional mapping?
POJO:
public class AnnouncementRecipientsFlattenedDTO {
private BigDecimal announcementId;
private String recipientAddress;
private String type;
public AnnouncementRecipientsFlattenedDTO() {
super();
}
public AnnouncementRecipientsFlattenedDTO(BigDecimal announcementId, String recipientAddress, String type) {
super();
this.announcementId = announcementId;
this.recipientAddress = recipientAddress;
this.type = type;
}
... Getters/Setters
Entity Manager call:
public List<AnnouncementRecipientsFlattenedDTO> getNormalizedRecipientsForAnnouncement(int announcementId) {
Query query = em.createNamedQuery(AnnouncementDeliveryLog.FIND_NORMALIZED_RECIPIENTS_FOR_ANNOUNCEMENT, AnnouncementRecipientsFlattenedDTO.class);
query.setParameter(1, announcementId);
return query.getResultList();
}
I found out you can put the results of a Native Query execution into a List of Arrays that hold Objects. Then one can iterate over the list and Array elements and build the desired Entity objects.
List<Object[]> rawResultList;
Query query =
em.createNamedQuery(AnnouncementDeliveryLog.FIND_NORMALIZED_RECIPIENTS_FOR_ANNOUNCEMENT);
rawResultList = query.getResultList();
for (Object[] resultElement : rawResultList) {
AnnouncementDeliveryLog adl = new AnnouncementDeliveryLog(getAnnouncementById(announcementId), (String)resultElement[1], (String)resultElement[2], "TO_SEND");
persistAnnouncementDeliveryLog(adl);
}
You can only use native SQL queries with a class if the class is mapped. You need to define the AnnouncementRecipientsFlattenedDTO class as an #Entity.
Otherwise just create the native query with only the SQL and get an array of the data back and construct your DTO yourself using the data.
Old question but may be following solution will help someone else.
Suppose you want to return a list of columns, data type and data length for a given table in Oracle. I have written below a native sample query for this:
private static final String TABLE_COLUMNS = "select utc.COLUMN_NAME, utc.DATA_TYPE, utc.DATA_LENGTH "
+ "from user_tab_columns utc "
+ "where utc.table_name = ? "
+ "order by utc.column_name asc";
Now the requirement is to construct a list of POJO from the result of above query.
Define TableColumn entity class as below:
#Entity
public class TableColumn implements Serializable {
#Id
#Column(name = "COLUMN_NAME")
private String columnName;
#Column(name = "DATA_TYPE")
private String dataType;
#Column(name = "DATA_LENGTH")
private int dataLength;
public String getColumnName() {
return columnName;
}
public void setColumnName(String columnName) {
this.columnName = columnName;
}
public String getDataType() {
return dataType;
}
public void setDataType(String dataType) {
this.dataType = dataType;
}
public int getDataLength() {
return dataLength;
}
public void setDataLength(int dataLength) {
this.dataLength = dataLength;
}
public TableColumn(String columnName, String dataType, int dataLength) {
this.columnName = columnName;
this.dataType = dataType;
this.dataLength = dataLength;
}
public TableColumn(String columnName) {
this.columnName = columnName;
}
public TableColumn() {
}
#Override
public int hashCode() {
int hash = 0;
hash += (columnName != null ? columnName.hashCode() : 0);
return hash;
}
#Override
public boolean equals(Object object) {
if (!(object instanceof TableColumn)) {
return false;
}
TableColumn other = (TableColumn) object;
if ((this.columnName == null && other.columnName != null) || (this.columnName != null && !this.columnName.equals(other.columnName))) {
return false;
}
return true;
}
#Override
public String toString() {
return getColumnName();
}
}
Now we are ready to construct a list of POJO. Use the sample code below to construct get your result as List of POJOs.
public List<TableColumn> findTableColumns(String table) {
List<TableColumn> listTables = new ArrayList<>();
EntityManager em = emf.createEntityManager();
Query q = em.createNativeQuery(TABLE_COLUMNS, TableColumn.class).setParameter(1, table);
listTables = q.getResultList();
em.close();
return listTables;
}
Also, don't forget to add in your POJO class in persistence.xml! It can be easy to overlook if you are used to your IDE managing that file for you.
Had the same kind of problem where I wanted to return a List of POJOs, and really just POJOs (call it DTO if you want) and not #Entity annotated Objects.
class PojoExample {
String name;
#Enumerated(EnumType.STRING)
SomeEnum type;
public PojoExample(String name, SomeEnum type) {
this.name = name;
this.type = type;
}
}
With the following Query:
String query = "SELECT b.name, a.newtype as type FROM tablea a, tableb b where a.tableb_id = b_id";
Query query = getEntityManager().createNativeQuery(query, "PojoExample");
#SuppressWarnings("unchecked")
List<PojoExample> data = query.getResultList();
Creates the PojoExample from the database without the need for an Entity annotation on PojoExample. You can find the method call in the Oracle Docs here.
edit:
As it turns out you have to use #SqlResultSetMapping for this to work, otherwise your query.getResultList() returns a List of Object.
#SqlResultSetMapping(name = "PojoExample",
classes = #ConstructorResult(columns = {
#ColumnResult(name = "name", type = String.class),
#ColumnResult(name = "type", type = String.class)
},
targetClass = PojoExample.class)
)
Just put this anywhere under your #Entity annotation (so in this example either in tablea or tableb because PojoExample has no #Entity annotation)

How to invoke getter method for instance that locate inside of another instance

I have some classes like below:
#Getter
#Setter
class Person{
#JsonProperty("cInfo")
private ContactInformation contactInfo;
private String name;
private String position;
}
#Getter
#Setter
class ContactInformation{
#JsonProperty("pAddress")
private Address address;
}
#Getter
#Setter
class Address{
private String street;
private String district;
}
And what I am going to do is writing an Utils method for the Person object that take one parameter which is the attributeName as String and return the getter value for this attribute.
Ex:
attributeName = name -> return person.getName()
attributeName = position -> return person.getPosition()
attributeName = cInfo.pAddress.street -> return person.getContactInfo().getAddress().getStreet()
attributeName = cInfo.pAddress.district -> return person.getContactInfo().getAddress().getDistrict()
Below is what I've done: I loop through all the fields in the Person object and check if the attributeName equal to either the JsonProperty's Name or the Field's Name then I will return this getter.
Object result;
Field[] fields = Person.class.getDeclaredFields();
for (Field field : fields) {
JsonProperty jsonProperty = field.getDeclaredAnnotation(JsonProperty.class);
if (jsonProperty != null && jsonProperty.value().equals(attributeName)) {
result = Person.class.getMethod("get" + capitalize(field.getName())).invoke(person);
} else {
if (field.getName().equals(attributeName)) {
result = person.class.getMethod("get" + capitalize(field.getName()))
.invoke(person);
}
}
}
This worked but only with the fields that locate direct in the Person class, ex: name, position. With the fields inside of contactInfo or address I am still getting stuck there. Can anyone give me some hint here how can I do it?
Thank you!
Because path like a.b.c related to different objects. So you need to. split by point and for each token call get and use obtained result for next token
UPDATE: something like:
private static Object invkGen(Object passedObj, String attributeName) throws Exception {
final String[] split = attributeName.split("\\.");
Object result = passedObj;
for (String s : split) {
if (result == null) {
break;
}
result = invk(result, s);
}
return result;
}
private static Object invk(Object passedObj, String attributeName) throws Exception {
Object result = null;
final Field[] fields = passedObj.getClass().getDeclaredFields();
for (Field field : fields) {
JsonProperty jsonProperty = field.getDeclaredAnnotation(JsonProperty.class);
if (jsonProperty != null && jsonProperty.value().equals(attributeName)) {
result = Person.class.getMethod("get" + capitalize(field.getName())).invoke(passedObj);
} else {
if (field.getName().equals(attributeName)) {
result = passedObj.getClass().getMethod("get" + capitalize(field.getName()))
.invoke(passedObj);
}
}
}
return result;
}

How to pass parameter in genericgenerator of model to another class (IdGenerator)

I want to use custom id generator in hibernate. This is my model:
#Entity(name="Poli")
#Table(name="POLI")
public class Poli extends DefaultEntityImpl implements Serializable{
#Id
#GenericGenerator(
name = "string-sequence",
strategy = "id.rekam.medis.service.generator.IdGenerator",
parameters = {
#org.hibernate.annotations.Parameter(
name = "sequence_name",
value = "pol_seq"),
#org.hibernate.annotations.Parameter(
name = "sequence_prefix",
value = "POL-")
})
#GeneratedValue(
generator = "string-sequence",
strategy = GenerationType.SEQUENCE)
#Basic(optional = false)
#Column(name = "ID",nullable = false)
private String id;
#Column(name = "NAMA", length = 10)
private String nama;
//getter setter
}
And my IdGenerator Class is :
public class IdGenerator implements IdentifierGenerator, Configurable {
private static final Log logger = LogFactory.getLog(IdGenerator.class);
private String sequenceName;
private String sequencePrefix;
public static final String SEQUENCE_PREFIX = "sequence_prefix";
#Override
public Serializable generate(SessionImplementor session, Object obj) throws HibernateException {
Connection con = session.connection();
Long nextValue = null;
try {
PreparedStatement p = con.prepareStatement(" SELECT POL_SEQ.NEXTVAL FROM DUAL ");
ResultSet rs = p.executeQuery();
while(rs.next()) {
nextValue = rs.getLong("nextVal");
}
} catch (SQLException e) {
e.printStackTrace();
}
if(logger.isDebugEnabled()) logger.debug("new id is generated:" + nextValue);
return "POL-" + nextValue;
}
#Override
public void configure(Type type, Properties params, Dialect dlct) throws MappingException {
sequencePrefix = ConfigurationHelper.getString(SEQUENCE_PREFIX, params,"SEQ_");
}
}
My Goal is, I want that my IdGenerator Class can be used for all Entities/Models. Just need to change the paramters in entity.
My Question: How to catch the parameters in the IdGenerator Class?
I want to get "pol_seq" and "POL-" in IdGenerator Class.
Hot Regard,
Tarmizi
That's what you've implemented the Configurable Interface for.
The configure() Method has these parameters in the Properties parameter. Look at its JavaDoc, it's basically a HashMap, so just do
params.getProperty("sequence_prefix");
And maybe you want to turn these names into constants, either public static final Strings, or better yet Enums.

How to Infer Information in Custom Annotation with Java

I created a custom annotation called CrudSearchable and have defined some attributes there. However, the attributes I am assigning are already visible from the bean. Is there a way I can grab these values without having to redefine them manually?
// Bean
public class MockUser {
#CrudSearchable(attribute = "name",
parentClass = MockUser.class)
private String name;
#CrudSearchable(attribute = "group",
parentClass = MockUser.class,
mappedClass = MockGroup.class)
private MockGroup group;
// Get/Set, Equals/Hashcode, etc...
}
// Annotation Class
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.FIELD)
public #interface CrudSearchable {
String attribute();
boolean searchable() default true;
Class<?> mappedClass() default CrudSearchable.class;
Class<?> parentClass() default Object.class;
}
Where attribute is the attribute name, parentClass is the class literal using the annotation, and mapped class is the nested class object if applicatable.
MockUser obj = new MockUser();
Class<?> c = obj.getClass();
Field[] fields = c.getDeclaredFields();
CrudSearchable annotation = fields[0].getAnnotation(CrudSearchable.class);
System.out.println("attribute: " + annotation.attribute() +
"searchable: " + annotation.searchable());
Hope this helps
I found the answer on another Question. This is what I was looking for.
// Put in Reflector.java
/**
* Changes the annotation value for the given key of the given annotation to newValue and returns
* the previous value.
*/
#SuppressWarnings("unchecked")
public static Object changeAnnotationValue(Annotation annotation, String key, Object newValue){
Object handler = Proxy.getInvocationHandler(annotation);
Field f;
try {
f = handler.getClass().getDeclaredField("memberValues");
} catch (NoSuchFieldException | SecurityException e) {
throw new IllegalStateException(e);
}
f.setAccessible(true);
Map<String, Object> memberValues;
try {
memberValues = (Map<String, Object>) f.get(handler);
} catch (IllegalArgumentException | IllegalAccessException e) {
throw new IllegalStateException(e);
}
Object oldValue = memberValues.get(key);
if (oldValue == null || oldValue.getClass() != newValue.getClass()) {
throw new IllegalArgumentException();
}
memberValues.put(key,newValue);
return oldValue;
}
Once I have this I can call the method below in the Constructor to alter the annotations I use.
// Put in Service Class
public void modifySearchable(Class<?> clazz) {
for(Field f : clazz.getDeclaredFields()){
CrudSearchable[] searchableArray = f.getDeclaredAnnotationsByType(CrudSearchable.class);
for(CrudSearchable searchable : searchableArray){
if(searchable == null){
continue;
}
Reflector.alterAnnotation(searchable, "attribute", f.getName());
Reflector.alterAnnotation(searchable, "parentClass", clazz);
if(!(searchable.mappedAttribute().equals(""))){
String mappedGetter = "get" +
searchable.mappedAttribute().substring(0, 1).toUpperCase() +
searchable.mappedAttribute().substring(1);
Reflector.alterAnnotation(searchable, "mappedClass", f.getType());
Reflector.alterAnnotation(searchable, "mappedGetter", mappedGetter);
}
}
}
}
// Changed Bean
#Id
#GeneratedValue
#Column(name = "MOCK_USER_ID")
private Long id;
#Column(name = "MOCK_USER_NAME")
#CrudSearchable
private String name;
#ManyToOne
#JoinColumn(name = "MOCK_GROUP", nullable = false)
#CrudSearchable(mappedAttribute = "name")
private MockGroup group;
public MockUser(){
super();
new Searchable<>().modifySearchable(this.getClass());
}
Seems like a lot to change the values instead of having the user define them, but I believe that it will make the code more user friendly.
Hope this helps someone. I found the answer on this post: Modify a class definition's annotation string parameter at runtime. Check it out!

Categories