Stack is Spring Boot w/ Jetty/Jersey. Here's the resource method in question:
#GET
#Path("campaignTargets")
#Produces(MediaType.APPLICATION_JSON)
#Transactional(readOnly=true)
public List<CampaignTargetOutputDTO> getCampaignTargets(
#PathParam("businessUnitId") Integer id,
#QueryParam("name") String name,
#Pattern(regexp = DATE_VALIDATION_PATTERN) #QueryParam("startDate") String startDate,
#Pattern(regexp = DATE_VALIDATION_PATTERN) #QueryParam("endDate") String endDate,
#Pattern(regexp = INTEGER_CSV_VALIDATION_PATTERN) #QueryParam("targetTypeIds") String targetTypeIds,
#Pattern(regexp = ALPHANUM_CSV_VALIDATION_PATTERN) #QueryParam("statuses") String statuses) {
return ResourceUtil.entityOr404(campaignService.getAdvertiserCampaignTargets(id, name, startDate, endDate, targetTypeIds, statuses));
}
When Jersey intercepts the call to this method to perform the validation, it doesn't (always) get this method. The reason I know this is because I have taken the advice of the Jersey documentation and created the following ValidationConfig:
#Provider
public class ValidationConfigurationContextResolver implements
ContextResolver<ValidationConfig> {
#Context
private ResourceContext resourceContext;
#Override
public ValidationConfig getContext(Class<?> type) {
final ValidationConfig config = new ValidationConfig();
config.constraintValidatorFactory(
resourceContext.getResource(InjectingConstraintValidatorFactory.class));
config.parameterNameProvider(new CustomParameterNameProvider());
return config;
}
private static class CustomParameterNameProvider extends DefaultParameterNameProvider {
private static final Pattern PROXY_CLASS_PATTERN = Pattern.compile("(.*?)\\$\\$EnhancerBySpringCGLIB\\$\\$.*$");
public CustomParameterNameProvider() {
}
#Override
public List<String> getParameterNames(Method method) {
/*
* Since we don't have a full object here, there's no good way to tell if the method we are receiving
* is from a proxy or the resource object itself. Proxy objects have a class containing the string
* $$EnhancerBySpringCGLIB$$ followed by some random digits. These proxies don't have the same annotations
* on their method params as their targets, so they can actually interfere with this parameter naming.
*/
String className = method.getDeclaringClass().getName();
Matcher m = PROXY_CLASS_PATTERN.matcher(className);
if(m.matches()) {
try {
return getParameterNames(method.getDeclaringClass().getSuperclass().
getMethod(method.getName(), method.getParameterTypes()));
} catch (Exception e) {
return super.getParameterNames(method);
}
}
Annotation[][] annotationsByParam = method.getParameterAnnotations();
List<String> paramNames = new ArrayList<>(annotationsByParam.length);
for(Annotation[] annotations : annotationsByParam) {
String name = getParamName(annotations);
if(name == null) {
name = "arg" + (paramNames.size() + 1);
}
paramNames.add(name);
}
return paramNames;
}
private String getParamName(Annotation[] annotations) {
for(Annotation annotation : annotations) {
if(annotation.annotationType() == QueryParam.class) {
return ((QueryParam) annotation).value();
} else if(annotation.annotationType() == PathParam.class) {
return ((PathParam) annotation).value();
}
}
return null;
}
}
}
My main problem with this solution is that it requires a paragraph of comment to (hopefully) prevent future confusion. Otherwise it seems to work. Without this, I get uninformative parameter names like arg1 and so on, which I'd like to avoid. Another big problem with this solution is that it relies too heavily on the implementation of Aop proxying in Spring. The pattern may change and break this code at some point in the future and I may not be here to explain this code when the comment fails to illuminate its purpose. The weirdest thing about this is that it seems to be intermittent. Sometimes the parameter names are good and sometimes they're not. Any advice is appreciated.
It turns out this happens as a result of running the server from eclipse. I haven't quite figured out why, but running the server from the command line fixes the problem. If anyone can figure out why eclipse does this and how to turn off whatever "feature" of eclipse is causing this, I will upvote/accept your answer. For now the answer is, don't run the service in eclipse.
Related
I am starting to learn the Spring boot framework, especially for developing an application under the MVC concept with Spring Boot. I found an article and tried to re-implement:
https://www.toptal.com/spring/beginners-guide-to-mvc-with-spring-framework
I used Java 8, Spring Boot 2.5.1, and Gradle. During the implementation, I got several errors especially in this section:
#RequestMapping(value="/developer/{id}/skills", method=RequestMethod.POST)
public String developersAddSkill(#PathVariable Long id, #RequestParam Long skillId, Model model) {
Skill skill = skillRepository.findOne(skillId);
Developer developer = repository.findOne(id);
if (developer != null) {
if (!developer.hasSkill(skill)) {
developer.getSkills().add(skill);
}
repository.save(developer);
model.addAttribute("developer", repository.findOne(id));
model.addAttribute("skills", skillRepository.findAll());
return "redirect:/developer/" + developer.getId();
}
model.addAttribute("developers", repository.findAll());
return "redirect:/developers";
}
I am a new learner of Spring Boot and Java. The problem like similar to the previous post.
I modified the code by following the Eclipse recommendation and discussion in the previous post.
This is my new code:
#RequestMapping(value="/developer/{id}/skills", method=RequestMethod.POST)
public String developersAddSkill(#PathVariable Long id, #RequestParam Long skillId, Model model) {
Optional<Skill> skill = skillRepository.findById(skillId);
Optional<Developer> optionalDeveloper = repository.findById(id);
Developer developer = optionalDeveloper.get();
if (developer != null) {
if (!developer.hasSkill(skill)) {
developer.getSkills().add(skill.get());
}
repository.save(developer);
model.addAttribute("developer", repository.findById(id));
model.addAttribute("skills", skillRepository.findAll());
return "redirect:/developer/" + developer.getId();
}
model.addAttribute("developers", repository.findAll());
return "redirect:/developers";
}
However, there is still one error:
The method hasSkill(Skill) in the type Developer is not applicable for the arguments (Optional<Skill>
I tried to fix it, but I am still cannot do it.
How can I fix this problem? Could everyone suggest the solution? I would appreciate it.
Thank you.
You have two problems:
You are doing
Developer developer = optionalDeveloper.get();
if (developer != null) {
...
without checking if the Optional<Developer> is present.
If the Optional is not present (represents the absence of a value),
then this will throw a NoSuchElementException - it will not return null.
So, this should be:
if(optionalDeveloper.isPresent()) {
...
You have
if (!developer.hasSkill(skill)) {
developer.getSkills().add(skill);
}
but developer.hasSkill() takes a Skill as an argument - you are passing an Optional.
So, this should be:
if(optionalSkill.isPresent()) {
final Skill skill = optionalSkill.get());
if (!developer.hasSkill(skill)) {
developer.getSkills().add(skill);
}
}
Its simple. You are passing Optional varibale inside the method when it actually expects skill object.
Modified code:
#RequestMapping(value="/developer/{id}/skills", method=RequestMethod.POST)
public String developersAddSkill(#PathVariable Long id, #RequestParam Long skillId, Model model) {
Optional<Skill> optionalSkill = skillRepository.findById(skillId);
Optional<Developer> optionalDeveloper = repository.findById(id);
if (optionalDeveloper.isPresent() && optionalSkill.isPresent()) {
Developer developer = optionalDeveloper.get();
Skill skill = optionalSkill.get();
if (!developer.hasSkill(skill)) {
developer.getSkills().add(skill);
}
repository.save(developer);
model.addAttribute("developer", developer);
model.addAttribute("skills", skillRepository.findAll());
return "redirect:/developer/" + developer.getId();
}
model.addAttribute("developers", repository.findAll());
return "redirect:/developers";
}
So I have an API client type class right now, which I am trying to connect to my repository so that I can store data in the MySQL database.
The problem I'm having is that the API client class instantiates a new object of itself, so the Autowiring doesn't work correctly. I've looked around for a workaround for this problem, and I've seen a couple options, but I'm confused on how to apply them to my problem.
For reference, here are parts of some of the relevant files:
GeniusApiClient.java:
#Component
public final class GeniusApiClient {
private final OkHttpClient client = new OkHttpClient();
#Autowired
private ArtistDao artistDao;
public static void main(String[] args) throws Exception {
GeniusApiClient geniusApiClient = new GeniusApiClient();
String artistId = (geniusApiClient.getArtistId("Ugly Duckling"));
ArrayList<String> artistSongIds = geniusApiClient.getArtistSongIds(artistId);
System.out.println(geniusApiClient.getAllSongAnnotations(artistSongIds, artistId));
}
public String getAllSongAnnotations(ArrayList<String> songIds, String artistId) {
Artist artist = new Artist("test name for now", "string123", "223");
artistDao.save(artist);
return "finished";
}
}
ArtistDao.java:
#Transactional
public interface ArtistDao extends CrudRepository<Artist, Long> {
public Artist findByGeniusId(String geniusId);
}
ArtistController.java:
#Controller
public class ArtistController {
#Autowired
private ArtistDao artistDao;
/**
* GET /create --> Create a new artist and save it in the database.
*/
#RequestMapping("/create")
#ResponseBody
public String create(String name, String annotations, String genius_id) {
String userId = "";
try {
genius_id = genius_id.replaceAll("/$", "");
Artist artist = new Artist(name, annotations, genius_id);
artistDao.save(artist);
userId = String.valueOf(artist.getId());
}
catch (Exception ex) {
return "Error creating the artist: " + ex.toString();
}
return "User succesfully created with id = " + userId;
}
/**
* GET /get-by-email --> Return the id for the user having the passed
* email.
*/
#RequestMapping("/get")
#ResponseBody
public String getByEmail(String genius_id) {
String artistId = "";
try {
Artist artist = artistDao.findByGeniusId(genius_id);
artistId = String.valueOf(artist.getId());
}
catch (Exception ex) {
return "User not found";
}
return "The user id is: " + artistId;
}
}
The problem is that in GeniusApiClient.java in the getAllSongAnnotations method, I have a null pointer exception when I try and access the artistDao. I understand that my instantiation of this class is what is messing up the Autowiring, but I'm curious on what the best way to go about fixing this might be.
I considered making all of my methods in the class static so that I wouldn't have to instantiate a new method, but I don't think this would work very well. Any suggestions?
Thanks
EDIT:
Removed some irrelevant code for clarity.
EDIT2:
Added ArtistController.java
To be able to autowire/inject an object, that object must be a Spring bean.
Here you can't autowire ArtistDao because it's not a bean. There are several annotation options to make it bean but the one suits in this case is #Repository annotation. It's just a specialized version of #Component which you used in GeniusApiClient class.
So,
#Repository
#Transactional
public interface ArtistDao extends CrudRepository<Artist, Long> {
public Artist findByGeniusId(String geniusId);
}
should work.
I'd suggest you to read: http://docs.spring.io/spring/docs/current/spring-framework-reference/html/beans.html
If reading reference documentation sounds scary to you, you can also take a look at Core Spring part of Spring in Action.
Don't make GeniusApiClient.class final. Spring will use CGLIB to dynamically extend your class in order to make a proxy. And the requirement for CGLIB to work is to have your classes non-final.
More on this here: Make Spring Service Classes Final?
What you are trying to do in your catch block is not clear to me,you have to correct that and replace it with desired action to be taken on any exception occurrence.
I am running the service under TomEE.
The model is very simple:
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class Card {
#XmlElement(required = true, nillable = false)
private String cardNumber;
public Card() {
//no-op
}
public Card(final String s) {
cardNumber = s;
}
public String getCardNumber() {
return cardNumber;
}
public void setCardNumber(String cardNumber) {
this.cardNumber = cardNumber;
}
}
I followed this example
https://docs.oracle.com/javaee/7/tutorial/jaxrs-advanced008.htm
The service is also very simple like:
#Consumes(APPLICATION_XML)
#Produces(APPLICATION_XML)
public class MyService {
#POST
#Path("status")
public String queryStatus(Card card) {
// do something
}
}
If my input is wrongly formatted, it will have a proper exception. But it doesn't seem to be able to validate empty card number or null.
For example, when I have
"<card></card>"
or
"<card><cardNumber> </cardNumber></card>"
(with an empty string), the service still goes through, with the "cardNumber" property being null or empty.
Well, I could do something in the setter to throw out an exception. But I was hoping JavaEE automatically handle this kind of this if I put the annotation on the property.
So what am I missing here?
Thank you for any tips!
With Bean Validation (http://beanvalidation.org/) Java EE offers a standard way to validate objects. It is also integrated with JAX RS.
So you can use annotations like #NotNull in your Card class. In your Service just say that you want a #Valid Card.
An example can be found here: https://jaxenter.com/integrating-bean-validation-with-jax-rs-2-106887.html
In Spring 3:
My Bean:
public class UserFormBean {
private String userEmail;
private String userMobNo;
public String getUserEmail() {
return userEmail;
}
public void setUserEmail(String userEmail) {
this.userEmail = userEmail;
}
public String getUserMobNo() {
return userMobNo;
}
public void setUserMobNo(String userMobNo) {
this.userMobNo = userMobNo;
}
}
And my request handler in Controller:
#RequestMapping(value = "/userData", method = RequestMethod.GET)
String userData(UserFormBean bean){
//code to handle incoming data
}
if user dosen't set any value in 'userEmail' and 'userMobNo' attributes at request time, spring by default sets null in these attributes and when i am getting value of these attributes in my request handler by String str = bean.getUserEmail(); it returns me null in double quote like "null".
Is there any way to convert these null values as "" (blank) when they contain null because i made an extra handler to handle these "null" values. Or some better idea to resolve this issue.
Thanks
I think the easiest way would be to initialize the properties to the empty String:
private String userEmail = "";
private String userMobNo = "";
I suspect the setter is called with "\"null\"" for the new value. Try this:
public void setUserEmail(final String newValue)
{
if (StringUtils.isNotBlank(newValue) &&
"\"null\"".equals(newValue))
{
userEmail = newValue;
}
else
{
userEmail = StringUtils.EMPTY;
}
}
You could define a standard for naming your "web methods", for example:
#RequestMapping(value = "/userData", method = RequestMethod.GET)
String webUserData(UserFormBean bean){
//code
}
When all your methods start with web*, then you can write an aspect that will initalize all arguments as you would like.
If you need help about creating and defining an aspect, just ask.
UPDATE: creating and defining an aspect
I will not write the code for you, because it's much better for you to learn by reading the documentation - you will get to know a lot of extra details.
That said, the steps to create an aspect are:
Enable AspectJ support - this step is done only once in your whole app
Declare an aspect - create a class that has an #Aspect annotation
Declare a pointcut inside your aspect - specify the point of interest in your code, around which the aspect will do some stuff, in your case, the web* methods
Declare an advice inside your aspect - #Before, #AfterReturning, #AfterThrowing, #After, #Around, here you will write your specific aspect code
In your aspect code you will have to access the current method arguments and do your logic for setting the empty string values for Strings.
I'm trying to build a RESTful API using Spring MVC. I'm shooting for clean and manageable code where the package structure follows the url structure.
So here is what I've got:
// com.test.api.library
#RequestMapping("/library/{libraryId}")
public Library getLibrary(#PathVariable long libraryId) {
return service.getLibraryById(libraryId);
}
// com.test.api.library.book
#RequestMapping("/library/{libraryId}/book/{bookId}")
public Book getBook(#PathVariable long libraryId, #PathVariable long bookId) {
Library library service.getLibraryById(libraryId);
return library.getBookById(bookId);
}
While this works, I find it messy and error-prone to have to repeat "/library/{libraryId}" in all inherited #RequestMappings, /library is likely to be to root of a big part of the API and it should be written once and reused instead of written everywhere.
I would like to rewrite the book-class to something like this:
// com.test.api.library.book
#RequestMapping("/book/{bookId}")
public Book getBook(#PathVariable long bookId) {
// long libraryId magically given to me from the library-class's getLibrary()
Library library service.getLibraryById(libraryId);
return library.getBookById(bookId);
}
Is there any way Spring can help me here? It is acceptable for me to use normal java inheritance, spring annotation or anything else that helps me to not write "/library/{libraryId}" as a part of every url I ever write.
I believe this question has been asked & answered before: Spring MVC #RequestMapping Inheritance
That said, here is one way to reduce the amount of duplicate information. I don't actually do this in my own code because I think having the URI right next to the code is more maintainable, even if it means a little duplication.
#RequestMapping(URI_LIBRARY)
public interface LibraryNamespace {
public static String URI_LIBRARY = "/library/{libraryId}";
}
#RequestMapping(URI_BOOK)
public interface BookNamespace {
public static String URI_BOOK = LibraryNamespace.URI_LIBRARY + "/book/{bookId}";
}
#Controller
public class LibraryController implements LibraryNamespace {
#RequestMapping("")
public Library get(#PathVariable long libraryId) {
return service.getLibraryById(libraryId);
}
}
#Controller
public class BookController implements BookNamespace {
#RequestMapping("")
public Book get(#PathVariable long libraryId, #PathVariable long bookId) {
Library library service.getLibraryById(libraryId);
return library.getBookById(bookId);
}
}
Since I wouldn't take this approach myself, I haven't actually tried this solution! Based on my understanding of Spring, I think it should work though...
Use a polymorphic parent approach.
#Controller
public class CommentsController {
#RequestMapping(value="/comments", method = RequestMethod.GET)
public #ResponseBody String index() {
/* kludge to allow optional path parameters */
return index(null, null);
}
#RequestMapping(value="/{parent_collection}/{parent_id}/comments", method = RequestMethod.GET)
public #ResponseBody String index(#PathVariable("parent_collection") String parentCollection, #PathVariable("parent_id") String parentId) {
if (parentCollection == null) {
return "all comments";
}
else if ((parentCollection != null) && (parentCollection.equals("posts"))) {
/* get parent, then get comments for parent */
return "comments for single post";
}
else if ((parentCollection != null) && (parentCollection.equals("customers"))) {
/* get parent, then get comments for parent */
return "comments for single customer";
}
else if ((parentCollection != null) && (parentCollection.equals("movies"))) {
/* get parent, then get comments for parent */
return "comments for single movie";
}
}
#RequestMapping(value = "/comments/{id}", method = RequestMethod.GET)
public #ResponseBody String show(#PathVariable Integer id) {
/* kludge to allow optional path parameters */
return show(null, null, id);
}
#RequestMapping(value = "/{parent_collection}/{parent_id}/comments/{id}", method = RequestMethod.GET)
public #ResponseBody String show(#PathVariable("parent_collection") String parentCollection, #PathVariable("parent_id") String parentId, #PathVariable Integer id) {
/* get comment, then get parent from foreign key */
if (parentCollection == null) {
return "single comment";
}
else if ((parentCollection != null) && (parentCollection.equals("posts"))) {
return "single comment for single post";
}
else if ((parentCollection != null) && (parentCollection.equals("customers"))) {
return "single comment for single customer";
}
else if ((parentCollection != null) && (parentCollection.equals("movies"))) {
return "single comment for single movie";
}
}
}
Additionally, you could use a base controller to route the URI prefix to parent resources (/libraries/{library_id}/../..), add the parent models to the request scope, and then let the regular request mappings handle the rest of the URI to child resources (/../../books/1). I don't have an example of this off-hand.
Side note. Singular nested resources are generally regarded as an antipattern for URI design. A controller should handle its own resources. The most common implementations make the key for the singular nested resource unique, i.e., not dependent on its parent resource. For instance, a database record primary key. However, there are situations where the key might not be unique, such as an ordinal or position value (e.g., book 1, chapter 1, chapter 2), or maybe even a natural key (e.g., book ISBN, person SSN, email address, username, filename).
Example of canonical URIs for nested resources:
/articles => ArticlesController#index
/articles/1 => ArticlesController#show
/articles/1/comments => CommentsController#index
/articles/1/comments/2 => CommentsController#show (okay, but not preferred)
/comments/2 => CommentsController#show (preferred)
I don't think it's possible. But you can have the #RequestMapping annotation on the class itself, so it will save you at least some typing.
#Controller
#RequestMapping("/library/{libraryId}")
public class HelloWorldController {
#RequestMapping(value="/book/{bookId}")
public ModelAndView helloWorld() {
....
}
}