I have a base class, Record, that represents records in a database. I have Customer and Job classes that extend record. I've never used annotations before but what i think i would like to do is create a custom annotation and mark a method in my Customer class that return its Jobs objects so i know to save the Jobs objects to the database when i save the Customer.
Something like this
class Record{
private int id;
public void save(){
//look up all methods in the current object that are marked as #alsoSaveList,
//call those methods, and save them as well.
//look up all methods in the current object that are marked as #alsoSaveRecord,
//call those methods, and save the returned Record.
}
}
class Customer extends Record{
#alsoSaveList
public List<Job> jobs(){
return list of all of customers jobs objects;
}
}
class Job extends Record{
#alsoSaveRecord
public Customer customer(){
return its customer object;
}
}
Is this possible? can someone point me in the right direction?
I agree, typically if your using an ORM then you could let JPA or Hibernate deal with this. However if you want a programatic response like your mentioning here's a simple example based :
Define your Annotation: AlsoSaveRecord.class
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface AlsoSaveRecord {
// The record class type
Class<?> value();
}
Code to find methods to invoke: Code you could add to your class example above
public void save() {
List<Method> toSaveRecords = findMethodsAnnotatedWith(AlsoSaveRecord.class, obj);
for (Method rec : toSaveRecords) {
AlsoSaveRecord anno = rec.getAnnotation(AlsoSaveRecord.class);
Class<?> recordType = anno.value();
Object objToSave = rec.invoke(obj);
}
}
List<Method> findMethodsAnnotatedWith(Class<? extends Annotation> annotation, Object instance)
{
Method[] methods = instance.getClass().getDeclaredMethods();
List<Method> result = new ArrayList<Method>();
for (Method m : methods) {
if (m.isAnnotationPresent(annotation)) {
result.add(m);
}
}
return result;
}
The above will scan for AlsoSaveRecord annotations in the Object in hand and return any applicable methods. You can then invoke those methods returned which were of a result of being annotated. The invoke will return the Object which you can cast or do something with.
Edited as requested to have the "Record Type" defined within the annotation (ie. #AlsoSaveRecord(MyRecord.class);
The method above can now grab the recordType which is the defined class when annotated
Related
I have a Util class and there is a static method called csvToEmployees. In order to use this method with different type of request classes, I am trying to convert the class as shown below that takes generic parameter:
public class CsvHelper<T> {
public List<T> csvToEmployees(InputStream is) {
//code omitted
for (CSVRecord rec : records) {
T employee = new T(
// ...
);
employees.add(employee);
}
return employees;
}
}
I call this method from my service by injecting this util class as shown below:
#Service
#RequiredArgsConstructor
public class EmployeeService {
private final EmployeeRepository employeeRepository;
private final CsvHelper<EmployeeRequest> helper;
public void create(MultipartFile file) {
List<Employee> employees = helper.csvToEmployees(file.getInputStream()).stream()
.map(EmployeeRequestMapper::mapToEntity)
.toList();
// ...
}
}
My problems are:
1. Is the implementation approach above is ok or not? I mean assuming that there are different kind of requests with the same fields, is using generic with that approach ok?
2. I get "Type parameter 'T' cannot be instantiated directly" error in the T employee = new T( line of util class. How can I fix it?
The best solution, in my opinion, is just creating multiple csvToObject methods inside the classes that you need to process.
I mean if you already know that you’re transforming a stream into a list of employees (it’s “hard coded” in the service method) why would you need to use generics? Just use the method for employees instead.
I want to mark a field of a class with my custom annotation. And whenever any method is invoke I want to do some modification on that field.
public class Message{
public Integer id;
#FreeText // this is my custom annotation
public String htmlMsg;
public String textMsg ;
}
This annotation (#FreeText) can be used in any class.
In seasar framework, I can do this by create an interceptor and override invoke method. The I can get the object of this class and the find the field that marked with my annotation and modify it. However, i cannot find a way to do it in Spring.
In spring, I found some method like MethodInvocationInterceptor, but I don't know how to implement it. Can you suggest any way to do this in Spring?
Seasar2 and Spring are very close. I have not tested but you can do something like this.
First create FreeText custom annotation
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.FIELD)
#Documented
public #interface FreeText {}
Then create the following interceptor
public class EncryptSensitiveDataInterceptor extends MethodInterceptor {
#Override
public Object invoke(MethodInvocation in) throws Throwable {
Object[] params = in.getArguments();
Object param = params[0];
for (Field field : param.getClass().getDeclaredFields()) {
for (Annotation anno : field.getDeclaredAnnotations()) {
if (anno instanceof FreeText) {
field.set(param, [YOUR CUSTOM LOGIC METHOD]);
}
}
}
return in.proceed();
}
Hope this help.
Assume this class diagram:
I have class named Organization that many objects has association to that. also there are many object in addition to the StoreHouse and Personnel, but to have simple class diagram, i didn't put those classes into diagram (assume more than 1000 classes are depended to Organization class).
Now, i want to add enabled field to the Organization class. it's very simple and there isn't any problem. but after that, i want to prevent all business points and services to use disabled organizations.
for example assume this below service:
#Service
public class PersonnelService {
#Autowired
private PersonnelRepository repository;
public long save(Personnel entity) {
return repository.save(entity);
}
}
If i had above code in the application, after add enabled field to Organization, i should change above method to this:
#Service
public class PersonnelService {
#Autowired
private PersonnelRepository repository;
public long save(Personnel entity) {
if(!entity.getOrganization().getEnabled()) {
throw Exception();
}
return repository.save(entity);
}
}
And because of this action is very time consuming, to change more than 1,000 classes.
I want to know is there a way to this action without changing business point, for example with using Aspect or something like it, and detect when the modification are making on the object and it have a field with type of Organization check the enabled value?
I am supposing that you are using spring data jpa or spring data rest to achieve update. If so then this can be achieved as follows:
Create an annotation UpdateIfTrue
#Documented
#Target(ElementType.TYPE)
#Retention(RUNTIME)
public #interface UpdateIfTrue {
String value();
}
Create a service
#Service("updateIfTrueService")
public class UpdateIfTrueService {
public boolean canUpdate(Object object){
Class klass = object.getClass();
UpdateIfTrue updateIfTrue = (UpdateIfTrue) klass.getAnnotation(UpdateIfTrue.class);
if (updateIfTrue != null){
String propertyTree[] = updateIfTrue.value().split(".");
/*Traverse heirarchy now to get the value of the last one*/
int i=0;
try{
while(i<propertyTree.length){
for (Field field : klass.getDeclaredFields()){
if (field.getName().equalsIgnoreCase(propertyTree[i])){
if (i < (propertyTree.length - 1)){
i++;
klass = field.getClass();
object = field.get(object);
break;
}
else if (i == (propertyTree.length - 1)){
i++;
klass= field.getClass();
object = field.get(object);
return (Boolean)object;
}
}
}
}
}catch(Exception e){
e.printStackTrace();
}
}else{
return true;
}
return true;
}
}
Annotate the entity that you want to be checked. e.g., User with following annotation
#UpdateIfTrue("personnel.organization.enabled")
Now In the Repository do the following
#Override
#PreAuthorize("#updateIfTrueService.canUpdate(#user)")
User save(#Param("user")User user);
I am using byte-buddy to build an ORM on top of Ignite, we need to add a field to a class and then access it in a method interceptor..
So here's an example where I add a field to a class
final ByteBuddy buddy = new ByteBuddy();
final Class<? extends TestDataEnh> clz = buddy.subclass(TestDataEnh.class)
.defineField("stringVal",String.class)
.method(named("setFieldVal")).intercept(
MethodDelegation.to(new SetterInterceptor())
)
.make()
.load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
.getLoaded();
final TestDataEnh enh = clz.newInstance();
enh.getFieldVal();
enh.setFieldVal();
System.out.println(enh.getClass().getName());
And the Interceptor is like this
public class SetterInterceptor {
#RuntimeType
public Object intercept() {
System.out.println("Invoked method with: ");
return null;
}
}
So how do I get the value of the new field into the interceptor so I can change it's value? (stringVal)
Thanks in advance
You can use a FieldProxy to access a field by its name. You need to install a FieldProxy.Binder and register it on the MethodDdelegation before you can use it as it requires a custom type for type-safe instrumentation. The javadoc explains how this can be done. Alternatively, you can use reflection on an instance by using #This. The JVM is quite efficient in optimizing the use of reflection.
An example would be:
interface FieldGetter {
Object getValue();
}
interface FieldSetter {
void setValue(Object value);
}
public class SetterInterceptor {
#RuntimeType
public Object intercept(#FieldProxy("stringVal") FieldGetter accessor) {
Object value = accessor.getValue();
System.out.println("Invoked method with: " + value);
return value;
}
}
For bean properties, the FieldProxy annotation does not require an explicit name but discovers the name from the name of the intercepted getter or setter.
The installation can be done as follows:
MethodDelegation.to(SetterInterceptor.class)
.appendParameterBinder(FieldProxy.Binder.install(FieldGetter.class,
FieldSetter.class));
In Java is it possible to use a class annotation as a typed method parameter.
For example - this is your annotation
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
public #interface Entity {
}
then
#Entity
public class Car {
...
}
and then do
interface Persister {
void persist(Entity entity);
}
You can do
public #interface Entity {
String name();
}
public class Car implements Entity{
public String name(){ return "car"; }
}
but that's just odd. Entity should be an ordinary interface instead.
---
It is possible though that through annotation processing, we can require that an argument to a method must have a static type that contains certain annotation. Not sure if someone has done that.
You can do this but it won't do what you expect. This persist(Entity) method can only take your Entity annotation, not an instance of a class you want to use.
Instead what you can do is
interface Entity { }
interface Car extends Entity {
interface Persister {
void persist(Entity entity);
}
This will work as expected and you can pass an instance of a Car to the persist method.