Spring newbie here, trying to make a GET http query in a mongo db via findById(id, Object).
But it doesn't seem to be working. I can POST and PUT but when calling a query via ID i get this err MonoOnErrorResume
I'm using EmbeddedMongoDB
Controller
public class ContentController {
public static final String CONTENT_V_1_CONT = "/contents/v1/cont/";
private final ContentService contentService;
#Autowired
public ContentController(ContentService contentService) {
this.contentService = contentService;
}
#GetMapping(path = "{id}", produces =
MediaType.APPLICATION_JSON_UTF8_VALUE)
public Mono<Content> getContent(#PathVariable String id) {
System.out.println(contentService.getContent(id)); //
MonoOnErrorResume
return contentService.getContent(id);
}
#PostMapping(path = "", produces =
MediaType.APPLICATION_JSON_UTF8_VALUE, consumes =
MediaType.APPLICATION_JSON_UTF8_VALUE)
public Mono<Content> createContent(#RequestBody Mono<Content> content){
return contentService.createContent(content);
}
Service Implmentation
public final ReactiveMongoOperations reactiveMongoOperations;
#Autowired
public ContentServiceImplementation(ReactiveMongoOperations reactiveMongoOperations) {
this.reactiveMongoOperations = reactiveMongoOperations;
}
#Override
public Mono<Content> getContent(String id) {
return reactiveMongoOperations.findById(id, Content.class);
}
#Override
public Mono<Content> createContent(Mono<Content> contentMono) {
return reactiveMongoOperations.save(contentMono);
}
Data Config Dont know is this is useful
#Bean
public ReactiveMongoDatabaseFactory mongoDatabaseFactory(MongoClient mongoClient){
return new SimpleReactiveMongoDatabaseFactory(mongoClient, DATABASE_NAME);
}
#Bean
public ReactiveMongoOperations reactiveMongoTemplate(ReactiveMongoDatabaseFactory mongoDatabaseFactory){
return new ReactiveMongoTemplate(mongoDatabaseFactory);
}
Lmk if i'm missing some critical info
Your problem may come from your controller, you declare your path like so:
#GetMapping(path = "{id}", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
So unless you have a / at the end of your controller class mapping you will have issues because your final URL will look like this :
http://localhost:8080/my/route/get1
instead of :
http://localhost:8080/my/route/get/1
Your #PathVariable looks strange as well, try doing this instead :
#PathVariable("id") String id
To ensure Spring is going to map {id} to your #PathVariable
Related
I have a Spring-Boot 1.5.21 application that serves as a REST gateway between an Angular UI and an external API that provides the data (long story - acts as auth between UI and datasource). A request comes to the Spring-Boot application, it calls the data source API with the request payload.
I am new to Unit Testing for Spring-Boot and am trying to write a test for the POST REST method in the Gateway application that creates a new record (create). I've read a couple of tutorials and other websites detailing how to unit test Spring-Boot APIs but nothing that helps me in my situation.
I want to:
Unit test the REST Controller method and check that the #RequestBody is valid
I do not want a record created in the datasource
Controller Method:
#PostMapping(value = "/" + Constants.API_CHANGE_REQUEST, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public String submitChangeRequest(#RequestBody ChangeRequestWrapper changeRequestWrapper) {
logger.info("API Request: Posting Change Request: " + changeRequestWrapper.toString());
return restService.makeApiPost(sharedDataService.buildApiUrlPath(Constants.API_CHANGE_REQUEST), changeRequestWrapper);
}
AppConfig:
#PropertySource({"classpath:application.properties"})
#Configuration
public class AppConfig {
#Resource
private Environment env;
#Bean
public RestTemplate restTemplate() {
RestTemplateBuilder builder = new RestTemplateBuilder();
return builder
.setConnectTimeout(Constants.API_TIMEOUT_CONNECT)
.setReadTimeout(Constants.API_TIMEOUT_READ)
.basicAuthorization(env.getProperty("bpm.user"), env.getProperty("bpm.password"))
.build();
}
}
RestServiceImpl:
#Service
public class RestServiceImpl implements RestService {
private static final Logger logger = LoggerFactory.getLogger(RestServiceImpl.class);
#Autowired
private RestTemplate myRestTemplate;
#Value("${bpm.url}")
private String restUrl;
public String getApiUri() {
return restUrl;
}
public String makeApiCall(String payload) /*throws GradeAdminException */{
logger.info("Implementing API call.");
logger.debug("userApi: " + payload);
return myRestTemplate.getForObject(payload, String.class);
}
public String makeApiPost(String endpoint, Object object) {
logger.info("Implementing API post submission");
logger.debug("userApi endpoint: " + endpoint);
return myRestTemplate.postForObject(endpoint, object, String.class);
}
}
SharedDataServiceImpl:
#Service
public class SharedDataServiceImpl implements SharedDataService {
#Autowired
private RestService restService;
#Override
public String buildApiUrlPath(String request) {
return buildApiUrlPath(request, null);
}
#Override
public String buildApiUrlPath(String request, Object parameter) {
String path;
UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(restService.getApiUri());
if (parameter != null) {
builder = builder.path(getApiPath(request) + "/{object}");
UriComponents buildPath = builder.buildAndExpand(parameter);
path = buildPath.toUriString();
} else {
builder = builder.path(getApiPath(request));
path = builder.build().toUriString();
}
return path;
}
}
What I've done for the GET methods:
#RunWith(SpringRunner.class)
#WebMvcTest(ClientDataRequestController.class)
#ContextConfiguration(classes = { TestConfig.class }, loader = AnnotationConfigWebContextLoader.class)
public class ClientDataRequestControllerTest {
#Autowired
private MockMvc mvc;
#Before
public void setUp() {
}
#Test
public void test_no_endpoint() throws Exception {
this.mvc.perform(get("/")).andExpect(status().isNotFound()).andReturn();
}
#Test
public void test_controller_no_endpoint() throws Exception {
this.mvc.perform(get("/api/")).andExpect(status().isOk()).andReturn();
}
#Test
public void test_getStudent_valid_parameters() throws Exception {
this.mvc.perform(get("/api/students/?pidm=272746")).andExpect(status().isOk()).andReturn();
}
}
I would greatly appreciate some assistance with this.
Solution:
I've since found this SO answer that has solved my problem.
You could mock the RestServiceImpl. Add a dependency in your test and annotate it with MockBean:
#MockBean
private RemoteService remoteService;
Now you can go ahead and mock the methods:
import org.mockito.BDDMockito;
BDDMockito.given(this.remoteService.makeApiPost()).willReturn("whatever is needed for your test");
I'm trying to create several routes for #GetMapping. For example, localhost:8080/tasks and localhost:8080/tasks/?status=...
So I created several methods as below.
Controller
#RestController
#RequestMapping(value = "/tasks", produces = MediaType.APPLICATION_JSON_VALUE)
#ExposesResourceFor(Task.class)
public class TaskRepresentation {
private final TaskResource taskResource;
public TaskRepresentation(TaskResource taskResource) {
this.taskResource = taskResource;
}
#GetMapping
public ResponseEntity<?> getAllTasks() {
return new ResponseEntity<>(this.taskResource.findAll(), HttpStatus.OK);
}
#GetMapping
public ResponseEntity<?> getTasksStatus(#RequestParam("status") int status) {
return new ResponseEntity<>(this.taskResource.getTasksByStatus(status), HttpStatus.OK);
}
}
Resource
#RepositoryRestResource(collectionResourceRel = "task")
public interface TaskResource extends JpaRepository<Task, String> {
#GetMapping
List<Tache> getTasksByStatus(#RequestParam int status);
}
Error
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'requestMappingHandlerMapping' defined in class path resource [org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration$EnableWebMvcConfiguration.class]: Invocation of init method failed; nested exception is java.lang.IllegalStateException: Ambiguous mapping. Cannot map 'taskRepresentation' method
public org.springframework.http.ResponseEntity<?> org.miage.tache.boundary.TacheRepresentation.getTasksStatus(int)
to {GET /tasks, produces [application/json]}: There is already 'taskRepresentation' bean method
(The only solution is to create only one route for #GetMapping with optionnal params?)
Can you help me ?
Thanks for help.
Coming from the other answer, as this one more specific.
You can narrow down your endpoint mapping by specifying the needed query parameters.
#GetMapping
public ResponseEntity<?> getAllTasks() {
return ResponseEntity.ok().body(this.taskResource.findAll());
}
#GetMapping(params = "status")
public ResponseEntity<?> getAllTasksWithStatus(#RequestParam("status") final int status) {
return ResponseEntity.ok().body(this.tacheResource.getTachesByEtat(status));
}
Docs link.
Note : As params is an array, you can specify multiple values with
#GetMapping(params = { "status", "date" })
You can do something like this :
#RestController
#RequestMapping(value = "/tasks", produces = MediaType.APPLICATION_JSON_VALUE)
#ExposesResourceFor(Task.class)
public class TaskRepresentation {
private final TaskResource taskResource;
public TaskRepresentation(TaskResource taskResource) {
this.taskResource = taskResource;
}
#GetMapping
public ResponseEntity<?> getTasksStatus(#RequestParam(value="status", required=false) Integer status) {
if(status==null){
return new ResponseEntity<>(this.taskResource.findAll(), HttpStatus.OK);
}
return new ResponseEntity<>(this.taskResource.getTasksByStatus(status.intValue()), HttpStatus.OK);
}
}
I have a Spring RestController that any attempt to post to it returns 400 Bad Request despite seeing the correct data being sent in Chrome Developer Tools. The #Valid annotation is kicking it out because the ParameterDTO object is not being populated at all.
My Controller
#RestController
#RequestMapping(path = "/api/parameters", consumes = {MediaType.APPLICATION_JSON_VALUE}, produces = {MediaType.APPLICATION_JSON_VALUE})
public class ParameterResource {
private final ParameterService parameterService;
#Autowired
public ParameterResource(ParameterService parameterService) {
this.parameterService = parameterService;
}
#GetMapping
public ResponseEntity<?> getParameters(#RequestParam(value = "subGroupId", required = false) Integer subGroupId) {
if (subGroupId != null) {
return ResponseEntity.ok(parameterService.getParameters(subGroupId));
}
return ResponseEntity.ok(parameterService.getParameters());
}
#PostMapping
public ResponseEntity<?> createParameter(#Valid ParameterDTO parameterData) {
int id = parameterService.saveParameter(parameterData);
URI uri = ServletUriComponentsBuilder.fromCurrentRequest().path("/{id}")
.buildAndExpand(id).toUri();
return ResponseEntity.created(uri).build();
}
#GetMapping(path = "/levels")
public ResponseEntity<?> getParameterLevels() {
return ResponseEntity.ok(ParameterLevels.getParameterLevelMap());
}
#GetMapping(path = "/levels/{id}/values")
public ResponseEntity<?> getLevelValues(#PathVariable("id") int levelId) {
return ResponseEntity.ok(parameterService.getParameterLevelValues(levelId));
}
#GetMapping(path = "/types")
public ResponseEntity<?> getParameterTypes() {
return ResponseEntity.ok(parameterService.getParameterTypes());
}
}
I was using axios from JavaScript and though my problem might be there but I have the same issue using Postman. I am setting the Content-Type and Accept header. It seems like Spring is not deserializing the data at all.
You need to add #RequestBody annotation before ParameterDTO parameterData declaration, like below:
#PostMapping
public ResponseEntity<?> createParameter(#RequestBody #Valid ParameterDTO parameterData) {
int id = parameterService.saveParameter(parameterData);
URI uri = ServletUriComponentsBuilder.fromCurrentRequest().path("/{id}")
.buildAndExpand(id).toUri();
return ResponseEntity.created(uri).build();
}
I'm using Spring Boot to create an HTTP endpoint. I would like to have 2 Get method handlers. One for http://$HOST/something/{key} and a separate one for http://$HOST/something/{key}.xyz Where xyz is an extension I made up, and it's not xml/json.
Example: http://localhost:8080/something/123 should go to method1, and http://localhost:8080/something/123.xyz should go to method2.
This is what I tried:
#Configuration
#Import({
DispatcherServletAutoConfiguration.class,
HttpMessageConvertersAutoConfiguration.class,
ServerPropertiesAutoConfiguration.class
})
public class SpringConfig extends WebMvcAutoConfiguration.WebMvcAutoConfigurationAdapter{
#Bean
#ConditionalOnProperty(prefix = "spring.mvc", name = "invalid")
public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter()
{
return null;
}
#Bean
#ConditionalOnProperty(prefix = "spring.mvc", name = "invalid")
public OrderedHttpPutFormContentFilter httpPutFormContentFilter()
{
return null;
}
#Bean
#Override
#ConditionalOnProperty(prefix = "spring.mvc", name = "invalid")
public RequestContextFilter requestContextFilter()
{
return null;
}
#Primary
#Bean(name = "jacksonObjectMapper")
public ObjectMapper jacksonObjectMapper()
{
return new Jackson2ObjectMapperBuilder()
.propertyNamingStrategy(PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES)
.serializationInclusion(JsonInclude.Include.NON_NULL)
.build();
}
#Override
public void configureMessageConverters(final List<HttpMessageConverter<?>> converters)
{
converters.add(new MappingJackson2HttpMessageConverter(jacksonObjectMapper()));
ArrayList<MediaType> list = new ArrayList<>();
MediaType mediaType = new MediaType("application","xyz");
list.add(mediaType);
StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter();
stringHttpMessageConverter.setSupportedMediaTypes(list);
List<MediaType> supportedList = stringHttpMessageConverter.getSupportedMediaTypes();
converters.add(stringHttpMessageConverter);
}
And here is my endpoint
#CrossOrigin
#RestController
#RequestMapping(value = "/something")
public class MyEndpoint {
#ResponseBody
#RequestMapping(value = "/{key}",method = RequestMethod.GET,consumes = "application/xyz")
public String getXyzHandler(#PathVariable("key") final String key, final HttpServletRequest httpRequest)
{
return null;
}
#ResponseBody
#RequestMapping(value = "/{key}",method = RequestMethod.GET,consumes = "!application/xyz")
public String getAllExtensionsHandler(#PathVariable("key") final String key)
{
return null;
}
}
All my requests are going to getAllExtensionsHandler and even when http::/localhost:8080/something/123.xyz
What am I missing?
I'm want it to be the right solution and not something hacky that would break everything else.
Thank you!
I'm using Spring Data REST JPA to build a RESTful web service. So far, Spring is auto-generating all the responses for all the possible methods and for listing all the resources available and even for searches over them:
#RepositoryRestResource(collectionResourceRel = "scans", path = "scans")
public interface ScanRepository extends PagingAndSortingRepository<Scan, Long> {
List<Scan> findByProjectId(#Param("pid") String pid);
}
Now I would like to modify what is returned "only" to POST requests while leaving intact the support to all the others.
I thought I'd create a controller for this purpose like the following:
#Controller
public class ScanController {
#RequestMapping(value = "/scans", method = POST, produces = {MediaType.APPLICATION_JSON_VALUE})
public #ResponseBody Result parseScan(#RequestParam String projectId, #RequestParam String tool) {
return null;
}
However when I do this, the JPA-data auto-generated responses for all the other methods and searches etc. ceases to exist. For instance, I get "Method not allowed" if I forward a GET request.
Besides, how could I access a JSON payload from the controller?
UPDATE
Now only one of the exposed resource does back to the default methods for requests not manually handled in my own controller. However I have no idea why it does and why this doesn't happen for any of the other resources.*
Despite they all only differ in their entity's attributes.
The following particular resource is the one that does back to the default request handlers for anything that is not POST scan/ or GET /scan/// which I declared in the controller:
#Controller
public class ScanController {
#Autowired
private ScanService scanService;
#RequestMapping(
value = "/scan",
method = POST,
consumes = MediaType.APPLICATION_JSON_VALUE,
produces = {MediaType.APPLICATION_JSON_VALUE})
public #ResponseBody
Scan parseScan(#RequestBody Scan rbody) {
<...do something...>
}
#RequestMapping(value = "/scans/{id}/{totvuln}/{nth}", method = RequestMethod.GET,
produces = {MediaType.APPLICATION_JSON_VALUE})
public #ResponseBody
Scan getScan(#PathVariable String id, #PathVariable int totvuln, #PathVariable int nth) throws ScanNotFound {
<...do something...>
}
It has the following repository interface:
public interface ScanRepository extends PagingAndSortingRepository<Scan, Long> {}
and the following service:
#Service
public class ScanServiceImpl implements ScanService {
#Resource
private ScanRepository scanRepository;
#Resource
private ResultRepository resultRepository;
#Override
#Transactional
public Scan create(Scan shop) {
<some code>
}
#Override
#Transactional
public Scan findById(long id) {
<some code>
}
#Override
#Transactional(rollbackFor = ScanNotFound.class)
public Scan delete(long id) throws ScanNotFound {
<some code>
}
#Override
#Transactional
public List<Scan> findAll() {
<some code>
}
#Override
#Transactional(rollbackFor = ScanNotFound.class)
public Scan update(Scan scan) throws ScanNotFound {
<some code>
}
}
and the resource itself has the following attributes:
#Entity
public class Scan {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private Long projectId;
#OneToMany
private Collection<Result> result;
private int totV;
<getters and setters>
}
While the following semi-identical resource "Rules" does not back to any of the default request handlers. It returns "Method not Allowed" for anything different from POST /rule:
#Controller
public class RulesController {
#Autowired
private RulesService rService;
#Resource
private ScanRepository scanRepository;
#RequestMapping(
value = "/rule",
method = POST,
consumes = MediaType.APPLICATION_JSON_VALUE,
produces = {MediaType.APPLICATION_JSON_VALUE})
public #ResponseBody
Rules generateRules(#RequestBody Scan rbody) throws Exception {
<do something>
}
}
It has the same repository interface:
public interface RulesRepository extends PagingAndSortingRepository<Rules, Long> {}
and also the same service implementation:
#Service
public class RulesServiceImpl implements RulesService {
#Resource
private RulesRepository rRepository;
#Resource
private ResultRepository resultRepository;
#Override
#Transactional
public Rules create(Rules shop) {
<do something>
}
#Override
#Transactional
public Rules findById(long id) {
<do something>
}
#Override
#Transactional(rollbackFor = RulesNotFound.class)
public Rules delete(long id) throws RulesNotFound {
<do something>
}
#Override
#Transactional
public List<Rules> findAll() {
<do something>
}
#Override
#Transactional
public Rules findByScanId(long id) throws RulesNotFound {
<do something>
}
#Override
#Transactional(rollbackFor = RulesNotFound.class)
public Rules update(Rules scan) throws RulesNotFound {
<do something>
}
}
and the resource Rules itself has the following attributes:
#Entity
public class Rules {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#OneToOne
private Scan scan;
#OneToMany
private Collection<Result> result;
private String rules;
<getters and setters>
}
Why isn't Spring exposing the default request handlers also for "Rules" for any request that hasn't been specified manually in my controller class?
I would truly appreciate if you could point out why. Thank you so much!
I've figured out how to access a JSON payload from the controller:
#RequestMapping(
value = "/scan",
method = POST,
consumes = MediaType.APPLICATION_JSON_VALUE,
produces = {MediaType.APPLICATION_JSON_VALUE})
public #ResponseBody
Scan parseScan(#RequestBody Scan rbody) {
Scan scan = new Scan();
scan.setProjectId(rbody.getProjectId());
scan.setTool(rbody.getTool());
return scan;
}
Also I've realised the automatic CRUD operations were actually being already supported for every request not handled by my own controller: I was just requesting the wrong URL.
I got the list of correct URLs to use by requesting "curl http://localhost:8080"
However, the preferred URL for any of the auto-generated operations can be set with
#RepositoryRestResource(collectionResourceRel = pref_URL_suffix, path = pref_URL_suffix)
^^somehow during all the changes I tried, that line above went missing.