Override class level #Path annotation on the method - java

I have two java files that contain endpoints that deal with file management. One is called FileResource.java and the other is DirectoryResource.java. DirectoryResource.java has only one method, which is createDirectory. I need to move that method over to FileResource.java and remove DirectoryResource.java completely.
The problem is that the endpoint for the createDirectory method is currently /api/dir/create. When I move it over to FileResource.java it won't work anymore because the class-level #Path annotation is at "/file/" instead of "/dir/".
Here's my question: Is it possible to override the #Path annotation on the method so that I can to maintain the endpoint /api/dir/create after moving it to the FileResource class?
I want to make sure that those who are using the api don't have to refactor their code to point to a new endpoint.
//FileResource.java
...
#Path("/file/")
public class FileResource() {
#POST
#Path("create")
public Response createFile(String fileContent) {
...
return Response.ok().build();
}
...
}
//DirectoryResource.java
...
#Path("/dir/")
public class DirectoryResource() {
#POST
#Path("create")
public Response createDirectory(String path) {
...
return Response.ok().build();
}
...
}

There's no 'overriding' of #Path annotation. They add.
Method annotated with #Path("create") in the class annotated with #Path("dir") will resolve to /dir/create.
You define the path by defining correct methods in correct channels. You move methods and delete channels only if you need to change pathes.
I see no reason you need to change the channel without changing the API, but if you still need to, you should play, for example, with mod_rewrite on Apache. But I'd advise against it. Just keep your channels structure clean.

Related

Method with #Transactional called on target not on proxy instance

I'm currently migrating one of my projects form "self configured spring" to spring boot. while most of the stuff is already working I have a problem with a #Transactional method where when it is called the context is not present as set before due to a call to the "target" instance instead of the "proxy" instance (I'll try to elaborate below).
First a stripped down view of my class hierarchy:
#Entity
public class Config {
// fields and stuff
}
public interface Exporter {
int startExport() throws ExporterException;
void setConfig(Config config);
}
public abstract class ExporterImpl implements Exporter {
protected Config config;
#Override
public final void setConfig(Config config) {
this.config = config;
// this.config is a valid config instance here
}
#Override
#Transactional(readOnly = true)
public int startExport() throws ExporterException {
// this.config is NULL here
}
// other methods including abstract one for subclass
}
#Scope("prototype")
#Service("cars2Exporter")
public class Cars2ExporterImpl extends ExporterImpl {
// override abstract methods and some other
// not touching startExport()
}
// there are other implementations of ExporterImpl too
// in all implementations the problem occurs
the calling code is like this:
#Inject
private Provider<Exporter> cars2Exporter;
public void scheduleExport(Config config) {
Exporter exporter = cars2Exporter.get();
exporter.setConfig(config);
exporter.startExport();
// actually I'm wrapping it here in a class implementing runnable
// and put it in the queue of a `TaskExecutor` but the issue happens
// on direct call too. :(
}
What exactly is the issue?
In the call to startExport() the field config of ExporterImpl is null although it has been set right before.
What I found so far:
With a breakpoint at exporter.startExport(); I checked the id of the exporter instance shown by eclipse debugger. In the debbug round while writing this post it is 16585. Continuing execution into the call/first line of startExport() where i checked the id again (of this this time) expecting it to be the same but realizing that it is not. It is 16606 here... so the call to startExport() is done on another instance of the class... in a previous debug round i checked to wich instance/id the call to setConfig() is going... to the first on (16585 in this case). This explains why the config field is null in the 16606 instance.
To understand what happens between the line where i call exporter.startExport(); and the actuall first line of startExport() i clicked into the steps between those both lines in eclipse debugger.
There i came to line 655 in CglibAopProxy that looks like this:
retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
checking the arguments here i found that proxy is the instance with id 16585 and target the one with 16606.
unfortunately I'm not that deep into springs aop stuff to know if that is how it should be...
I just wonder why there are two instances that get called on diffrent methods. the call to setConfig() goes to the proxy instance and the call do startExport() reaches the target instance and thus does not have access to the config previously set...
As mentioned the project has been migrated to spring boot but we where before already using the Athens-RELEASE version of spring platform bom. From what i can tell there where no special AOP configurations before the migration and no explicitly set values after the migration.
To get this problem fixed (or at least somehow working) i already tried multiple things:
remove #Scope from the sub class
move #Transactional from method level to class
override startExport() in subclass and put #Transactional here
add #EnableAspectJAutoProxy to application class (i wasn't even able to log in - no error message)
set spring.aop.proxy-target-class to true
the above in diffrent combinations...
Currently I'm out of clues on how to get this back working...
Thanks in advance
*hopes someone can help*
Spring Boot tries to create a cglib proxy, which is a class based proxy, before you probably had an interface based (JDK Dynamic Proxy).
Due to this a subclass of your Cars2ExporterImpl is created and all methods are overridden and the advices will be applied. However as your setConfig method is final that cannot be overridden and as a result that method will be actually called on the proxy instead on the proxied instance.
So either remove the final keyword so that CgLib proxy can be created or explicitly disable class based proxies for transactions. Add #EnableTransationManagement(proxy-target-class=false) should also do the trick. Unless there is something else triggering class based proxies that is.

How can I make Spring Framework 's #Cachable work with lastModified property of a File as key?

This is the code I have:
#Cacheable(value = "configurationCache", key = "#myFile.lastModified()")
private Object foo(File myFile) throws IOException {
System.out.println(myFile.lastModified());
try {
Thread.sleep(6000);
} catch (InterruptedException ignored) {
}
final Object foo = new SomeObjectFromFile(myFile);
return foo;
}
I call this method twice passing file objects that have the same lastmodified value but caching does not work, the method will wait for 6 seconds.
Here is the output I am getting:
1456298573000
1456298573000
What am I doing wrong?
key = "#myFile.lastModified"
did not work either..
I am sure my configuration with ehcache is fine.
Juliens answer is probably the right one assuming you do not use aspectj. its not alone invoking a public method, but invoking a public method of an object where spring had the chance to wrap it's proxies around. So make sure you are injecting the service that you want to have enhanced with cacheable support.
For example
#Service
public SomeService {
#Autowired
private CacheEnhancedService css;
public void doSomething() {
css.getConfig(new File("./file"));
}
}
#Service
public CacheEnhancedService {
#Cacheable(value = "configurationCache", key = "#myFile.lastModified()")
public Object getConfig(File myFile) {
...
}
}
}
The issue lies with the fact that your method is private.
As mentioned in the documentation of the Spring Framework:
Method visibility and cache annotations
When using proxies, you should apply the cache annotations only to
methods with public visibility. If you do annotate protected, private
or package-visible methods with these annotations, no error is raised,
but the annotated method does not exhibit the configured caching
settings. Consider the use of AspectJ (see below) if you need to
annotate non-public methods as it changes the bytecode itself.
[...]
In proxy mode (which is the default), only external method calls
coming in through the proxy are intercepted. This means that
self-invocation, in effect, a method within the target object calling
another method of the target object, will not lead to an actual
caching at runtime even if the invoked method is marked with
#Cacheable - considering using the aspectj mode in this case. Also,
the proxy must be fully initialized to provide the expected behaviour
so you should not rely on this feature in your initialization code,
i.e. #PostConstruct.
You should either switch to a public method and make and external call or user AspectJ.

JSON deserialize only with at least 2 parameters

I'm implementing a RESTful service application for TomEE Plus 1.7.1 with Jettison as default json provider. I have several facade classes for my entitiy classes to provide CRUD functionalities for each of them. Service facades have been generated by netbeans.
This is the POST method:
#POST
public void create(Course entity) {
super.create(entity);
}
While using this method (to create a new instance in the database) I got following error:
No message body reader has been found for request class Object, ContentType : application/json.
After several hours of trying, I got it to work: I only had to add another parameter to the method, like that:
#POST
public void create(#Context Context uriInfo, Course entity) {
super.create(entity);
}
I don't understand why I had to add this Context parameter. I don't need the context variable, so actually I would like to remove it...
Does anybody know the reason?
Okay, I think I found the solution:
All my rest services have been implemented as facade classes. The abstract facade (super class of all services) has several methods like:
public void create(T entity) { getEntityManager().persist(entity); }
public void edit(T entity) {getEntityManager().merge(entity);}
These methods are used by the facade classes:
public void create(Course entity) {
super.create(entity);
}
public void edit(#PathParam("id") Integer id, Course entity) {
super.edit(entity);
}
(for better viewing I've removed the annotations here)
The difference between these two methods is, that the edit method has a second parameter "id" and so does not override the edit() method of the super class. But the create() method does only have a single parameter which causes override of the super class method "create()". I don't know why, but cxf is now creating two endpoints:
POST http://localhost:8080/webprog/api/course/ -> void create(Course)
POST http://localhost:8080/webprog/api/course/ -> void create(Object)
This is also the reason why I got it working with a secon parameter: The create() method is not getting overriden anymore.
So what i did now, is simply renaming the method in de super class, to not override them in the facade classes.
by the way: all services classes have been created by netbeans generator... maybe there is a bug in it
Here are some of the pointers
Make sure you have jettison jar in your classpath, CXF automatically registers jettison as json provider.
#Context Context is not mandatory, so if you want to access some context parameters you can add.
For Method create add Media Type #Consumes(MediaType.APPLICATION_JSON)
Finally Check why you are getting No message body reader has been found for request class Object Ideally you should have got No message body reader has been found for request class Course(There might be some issues with your class definations)

How to use Custom type for #PathParam?

I want to use non spring bean class object as parameter for jersey web service class method. But it is giving missing dependency error at build time.
My code is:
#Component
#Path("/abcd")
public class ActorServiceEndpoint {
#POST
#Path("/test/{nonspringBean}")
#Produces(MediaType.APPLICATION_XML)
public void addActor(#PathParam("nonspringBean") MyNonSpringBeanClass nonspringBean){
}
}
The thing is path parameters come in String form. As per the specification, if we want the have a custom type be injected as a #PathParam, the custom class, should have one of three things:
A public static valueOf(String param) that returns the type
A public static fromString(String param) that returns the type
Or a public constructor that accepts a String
Another option implement a ParamConverter. You can see an example here.
If you don't own the class (it's a third-party class that you can't change) then your only option is to use the ParamConverter/ParamConverterProvider pair.
In either of these cases you'll want to construct the instance accordingly by parsing the String either in the constructor or in one of the above mentioned methods. After doing this, the custom type can be made a method parameter with the annotation.
The same holds true for other params, such as #FormParam, #HeaderParam, #QueryParam, etc.
It would help if you gave a bit more details of the error you're getting, but I see two problems with your code snippet:
The correct Spring annotation is #PathVariable, #PathParam is probably from another package. This doesn't apply as I guess you're using JAX-RS, not Spring annotations.
I'm not sure what converters are applied to path variables, but in any case it would need to have one for MyNonSpringBeanClass. I would take a String parameter and then instantiate MyNonSpringBeanClass myself in the function body.

Change #Rest rootUrl at runtime with AndroidAnnotations

I'm using a rest service with AndroidAnnotations, configured like so:
#Rest(rootUrl = "http://192.168.1.48:8080/stuff/services/rest/StuffService/",
converters = {MappingJacksonHttpMessageConverter.class})
public interface IStuff
{
#Post("fetchAllStuff")
public Response fetchAllStuff(Request req);
}
So what happens when I need to change the URL at runtime? If the URL is hard-coded in the annotation, what would I do to change it? Is there some way I could have it in a properties or XML file as well?
As explained on the wiki, you can simply define a void setRootUrl(String rootUrl) method and it will be generated as a setter on the final class

Categories

Resources