Everytime i try to show the results from the page, it downloads the results in json format instead of showing them on page.
It starts to download when i enter the url where the objects/information is stored, instead of showing the page http://localhost:8082/spring-rest-demo/api/students
If i run the server and paste this info in postman og google chrome, it does show the correct information without downloading it as a json file.
this is how it should be
Thank you
Edit:
package com.luv2code.springdemo.rest;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.PostConstruct;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.luv2code.springdemo.entity.Student;
#RestController
#RequestMapping("/api")
public class StudentRestController {
private List<Student> theStudents;
// define #PostConstruct to load the student data only once!
#PostConstruct
public void loadData() {
theStudents = new ArrayList<>();
theStudents.add(new Student("Poornima", "Patel"));
theStudents.add(new Student("Mario", "Rossi"));
theStudents.add(new Student("Mary", "Smith"));
}
// define endpoint for "/Student"-- return list of students
#GetMapping("/students")
public List<Student> getStudents() {
return theStudents;
}
// define endpoint for "/Student({studentid}"-- return list of students at index
#GetMapping("/students/{studentId}")
public Student getStudent(#PathVariable int studentId) {
// just index into the list .... keep it simple for now
return theStudents.get(studentId);
}
}
Try to add produces application/json in #GetMapping annotation.
This is a bug in recent versions of Eclipse. Hopefully the Eclipse team will fix it soon
Related
I'm working on my first REST API and I had it working for a minute. Basically, I have a Entity class User which has a reference to another database table Bookmark. I used #JsonIgnore at that field to exclude it from User's JSON representation:
import org.codehaus.jackson.annotate.JsonIgnore;
import javax.persistence.*;
#Entity
#Table(name = "user")
public class User implements Serializable {
private static final long serialVersionUID = 1L;
#Id
private String id;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "user", fetch = FetchType.LAZY)
#JsonIgnore
private List<Bookmark> bookmarks = new ArrayList<>();
#Column(name = "name")
private String name;
}
To get it running, I added an Application class (empty for now):
import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;
#ApplicationPath("/api")
public class RestApplication extends Application {
}
And finally, I took my good old UserService and added a REST endpoint:
#Dependent
#Named
#Path("/userService")
public class UserService {
#Inject
private UserDAO userDAO;
#GET
#Path("/getUserById/{id}")
#Produces(MediaType.APPLICATION_JSON)
public User getUserById(#PathParam("id") String id) {
return userDAO.getUserById(id);
}
}
Now I was able to access .../api/userService/getUserById/XYZ and get back a JSON representation of User including only id and name.
However, when trying to access the API from a second project running on the same machine, I stumbled upon the necessity to enable Cross-Origin Resource Sharing. I did so by following the many online guides and implemented a CorsFilter:
import java.io.IOException;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerResponseContext;
import javax.ws.rs.container.ContainerResponseFilter;
import javax.ws.rs.ext.Provider;
#Provider
public class CorsFilter implements ContainerResponseFilter {
#Override
public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext)
throws IOException {
responseContext.getHeaders().add("Access-Control-Allow-Origin", "*");
responseContext.getHeaders().add("Access-Control-Allow-Credentials", "true");
responseContext.getHeaders().add("Access-Control-Allow-Headers", "origin, content-type, accept, authorization");
responseContext.getHeaders().add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS, HEAD");
}
}
It had no effect at first, but it worked when I included CorsFilter.class in the RestApplication's getClasses() method. Curling the API now showed the correct headers with CORS enabled, but the endpoints returned nothing, so I also included the UserService.class.
That's my RestApplication class right now:
import java.util.HashSet;
import java.util.Set;
import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;
#ApplicationPath("/api")
public class RestApplication extends Application {
#Override
public Set<Class<?>> getClasses() {
final Set<Class<?>> classes = new HashSet<>();
classes.add(UserService.class);
classes.add(CorsFilter.class);
return classes;
}
}
However, now I get the infamous RESTEASY008205: JSON Binding serialization error org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.xyz.User.bookmarks, could not initialize proxy - no Session. Trying to access a different endpoint results in RESTEASY008205: JSON Binding serialization error javax.json.bind.JsonbException: Error getting value on: org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor#2ba18598, by the way, even though I can't figure out the difference between both endpoints.
At this point I'm kind of lost, it all went downhill when I tried to add the filter. Any advice on where I went wrong?
By the way, it's running on jboss-eap-7.2 and I added the following dependency to my POM to access org.codehaus.jackson.annotate.JsonIgnore:
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-jackson-provider</artifactId>
<version>3.6.1.SP2</version>
</dependency>
According to the specification:
When an Application subclass is present in the archive, if both Application.getClasses and Application.getSingletons return an empty collection then all root resource classes and providers packaged in the web application MUST be included and the JAX-RS implementation is REQUIRED to discover them automatically by scanning a .war file as described above. If either getClasses or getSingletons returns a non-empty collection then only those classes or singletons returned MUST be included in the published JAX-RS application.
So once you provide the getClasses override, the automatic discovery is turned off, and you must register explicitely the resources and the providers, including the Jackson features.
I want to collect metrics for particular REST API
Suppose I have a URL like /company/{companyName}/person/{id}
Is it possible to collect metrics across
/company/test/person/{id}
/compaby/test2/person/{id}
There's no out-of-the-box support for it but you can provide your own WebMvcTagsProvider to implement it via a Spring bean.
Note that it could lead to tag explosion and end up with OOM if there's any possibility to companyName path variable explosion by a mistake or attack.
In case you are using Spring and RestTemplate for http call, you can register MetricsClientHttpRequestInterceptor with your RestTemplate .
import javax.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration;
import org.springframework.boot.actuate.metrics.web.client.MetricsRestTemplateCustomizer;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
#Component
#AutoConfigureAfter({MetricsAutoConfiguration.class})
public class RestClientMetricConfiguration {
private final ApplicationContext applicationContext;
#Autowired
public RestClientMetricConfiguration(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
#PostConstruct
public void init() {
MetricsRestTemplateCustomizer restTemplateCustomizer =
applicationContext.getBean(MetricsRestTemplateCustomizer.class);
applicationContext.getBeansOfType(RestTemplate.class).values().forEach(restTemplateCustomizer::customize);
}
}
And use Below method provided by spring RestTemplate to make http call.
public <T> ResponseEntity<T> exchange(String url, HttpMethod method, #Nullable HttpEntity<?> requestEntity, ParameterizedTypeReference<T> responseType, Map<String, ?> uriVariables) throws RestClientException {
Type type = responseType.getType();
RequestCallback requestCallback = this.httpEntityCallback(requestEntity, type);
ResponseExtractor<ResponseEntity<T>> responseExtractor = this.responseEntityExtractor(type);
return (ResponseEntity)nonNull(this.execute(url, method, requestCallback, responseExtractor, uriVariables));
}
I am trying to start with a simple web service following the example : https://spring.io/guides/gs/rest-service/
I am unable to get any response when I hit the service URL. The way I am trying to run this is by running the project on Apache web server from Eclipse.
This is my controller:
package com.nscm.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.nscm.model.User;
#RestController
public class NSCMController {
#RequestMapping("/getuser")
public User getUser()
{
System.out.println("NSCMController.getUser()");
return new User();
}
}
This is my service class:
package com.nscm.service;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.SpringApplication;
import org.springframework.context.annotation.ComponentScan;
#ComponentScan("com")
#EnableAutoConfiguration
public class Application {
public static void main(String[] args) {
System.out.println("Application.main()");
SpringApplication.run(Application.class, args);
}
}
Even though as the example mentions the web.xml and the spring.xml is not required, even after placing these, my code does not work. None of the SOPs are called when I hit the URL. Please help me if I am missing anything here.
In my entities I have some hibernate annotations for validation, like #NotEmpty, #Pattern.. and others
In my controller, on save action, it has an #Valid parameter.
But if any entity has any required field, and there is no annotation I will have problems.
So I would like to test each entity, to ensure they have the necessary notes.
Something like:
#Test(expect=IllegalArgumentException.class)
public void testAllNull() {
Person p = new Persson(); // Person name has an #NotEmpty
validator.validate(p);
}
But how to validate it? Who is called to check #Valid?
Thanks.
I found out how to check:
#Autowired
private LocalValidatorFactoryBean validator;
...
validator.validateProperty(object, propertyName)
Here is a Spring v4.1.x based example of a test validating presence and processing of the #Valid annotation and building of custom JSON response in case of an error.
jUnit
import com.fasterxml.jackson.core.type.TypeReference;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.context.annotation.Bean;
import org.springframework.http.MediaType;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import javax.inject.Inject;
import java.util.List;
import static org.abtechbit.miscboard.util.JsonUtils.toJson;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.junit.Assert.assertThat;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = {
RegistrationValidationTest.MockDependencies.class,
})
public class RegistrationValidationTest {
#Inject
MockMvc mvc;
#Test
public void validatesRegistration() throws Exception {
Registration registration = ... //build an invalid Registration object
MvcResult result = mvc.perform(post(RegistrationController.CONTEXT_REGISTER).
contentType(MediaType.APPLICATION_JSON).
content(toJson(registration))).
andExpect(status().isBadRequest()).
andExpect(content().contentType(MediaType.APPLICATION_JSON)).
andReturn();
assertThat(result.getResolvedException(), is(notNullValue()));
String content = result.getResponse().getContentAsString();
assertThat(content, is(notNullValue()));
List<Message> messages = JsonUtils.fromJson(content, new TypeReference<List<Message>>() {
});
assertThat(messages.size(), is(1));
}
public static class MockDependencies {
#Bean
public MockMvc mvc() {
return MockMvcBuilders.standaloneSetup(new RegistrationController()).build();
}
}
}
Controller
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.List;
import java.util.stream.Collectors;
#Controller
public class RegistrationController
{
public static final String CONTEXT_REGISTER = "/register";
#RequestMapping(value = CONTEXT_REGISTER, method = RequestMethod.POST)
#ResponseBody
public String register(#RequestBody #Valid Registration registration) {
//perform registration
}
#ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<List> handleValidationException(MethodArgumentNotValidException ex) {
//Build a list of custom Message{String message;} objects
List<Message> messages = ex.getBindingResult().getAllErrors().
stream().map(e->new Message(e.getDefaultMessage())).collect(Collectors.toList());
return ResponseEntity.status(HttpStatus.BAD_REQUEST).contentType(MediaType.APPLICATION_JSON).body(messages);
}
}
Spring MVC Test Framework might be a good choice. By using this, you can be assured that validations in your tests runs codes as Spring #MVC actually works.
Actually, the #Valid annotation is detected by HandlerMethodInvoker, which processes annotations on the handler methods of Spring controllers. Internally, the actual validation logic is delegated to the Validator bean depending on your application context settings. (Hibernate Validator is widely used.)
By default configuration (e.g. <mvc:annotation-driven />), LocalValidatorFactoryBean is used internally to process #Valid annotation as #Falci noted, but it may differ time to time. Instead, Spring MVC Test Framework provides the same environment as the main application uses, hence a good choice.
If I get the following json from a RESTful client, how do I elegantly unmarshal the java.util.Date? (Is it possible without providing (aka. hard-coding) the format, that's what I mean by elegantly...)
{
"class": "url",
"link": "http://www.empa.ch",
"rating": 5,
"lastcrawl" : "2009-06-04 16:53:26.706 CEST",
"checksum" : "837261836712xxxkfjhds",
}
The cleanest way is probably to register a custom DataBinder for possible date formats.
import java.beans.PropertyEditorSupport;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class CustomDateBinder extends PropertyEditorSupport {
private final List<String> formats;
public CustomDateBinder(List formats) {
List<String> formatList = new ArrayList<String>(formats.size());
for (Object format : formats) {
formatList.add(format.toString()); // Force String values (eg. for GStrings)
}
this.formats = Collections.unmodifiableList(formatList);
}
#Override
public void setAsText(String s) throws IllegalArgumentException {
if (s != null)
for (String format : formats) {
// Need to create the SimpleDateFormat every time, since it's not thead-safe
SimpleDateFormat df = new SimpleDateFormat(format);
try {
setValue(df.parse(s));
return;
} catch (ParseException e) {
// Ignore
}
}
}
}
You'd also need to implement a PropertyEditorRegistrar
import org.springframework.beans.PropertyEditorRegistrar;
import org.springframework.beans.PropertyEditorRegistry;
import grails.util.GrailsConfig;
import java.util.Date;
import java.util.List;
public class CustomEditorRegistrar implements PropertyEditorRegistrar {
public void registerCustomEditors(PropertyEditorRegistry reg) {
reg.registerCustomEditor(Date.class, new CustomDateBinder(GrailsConfig.get("grails.date.formats", List.class)));
}
}
and create a Spring-bean definition in your grails-app/conf/spring/resources.groovy:
beans = {
"customEditorRegistrar"(CustomEditorRegistrar)
}
and finally define the date formats in your grails-app/conf/Config.groovy:
grails.date.formats = ["yyyy-MM-dd HH:mm:ss.SSS ZZZZ", "dd.MM.yyyy HH:mm:ss"]
Be aware that the new version of Grails 2.3+ supports this type of feature out of the box.
See Date Formats for Data Binding
With that said, if you are forced to use a version of Grails prior to 2.3, the CustomEditorRegistrar
can be updated using the following code to eliminate the deprecation warning, and also uses the #Component annotation, which allows you to remove / skip the step of adding the bean directly in resources.groovy.
Also not that I changed the grails configuration property name to grails.databinding.dateFormats, which matches the property now supported in Grails 2.3+. Finally, my version is a .groovy, not .java file.
import javax.annotation.Resource
import org.codehaus.groovy.grails.commons.GrailsApplication
import org.springframework.beans.PropertyEditorRegistrar
import org.springframework.beans.PropertyEditorRegistry
import org.springframework.stereotype.Component
#Component
public class CustomEditorRegistrar implements PropertyEditorRegistrar {
#Resource
GrailsApplication grailsApplication
public void registerCustomEditors(PropertyEditorRegistry reg){
def dateFormats = grailsApplication.config.grails.databinding.dateFormats as List
reg.registerCustomEditor(Date.class, new CustomDateBinder(dateFormats))
}
}