Spring Boot Get method, Request Mapping by extension/mime - java

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!

Related

Integration test not able to mock for methods annotated with #CircuitBreaker

Integration test not able to mock RestTemplate field on methods annotated with #CircuitBreaker.
controller:
#RestController
public class CollegeController {
#Autowired
private CollegeService collegeService;
#RequestMapping(value = "/college/student/{collegeId}")
public ResponseEntity<Map> getCollegeStudent(#PathVariable String collegeId){
ResponseEntity<Map> studentByCollege = collegeService.getStudentByCollege(collegeId);
return studentByCollege;
}
}
Service:
#Service
public class CollegeServiceImpl implements CollegeService {
public static final String COLLEGE_SERVICE = "collegeService";
#Autowired
RestTemplate restTemplate;
int count = 0;
#Override
#Retry(name = COLLEGE_SERVICE/*, fallbackMethod = "fallbackForRetry"*/)
#CircuitBreaker(name = COLLEGE_SERVICE, fallbackMethod = "getAllStudentFallback")
public ResponseEntity<Map> getStudentByCollege(String collegeId) {
ResponseEntity<Map> forEntity = null;
String url = "http://localhost:8080/student/{collegeId}";
System.out.println(" count = " + ++count);
HttpHeaders headers = new HttpHeaders();
HttpEntity request = new HttpEntity(headers);
forEntity = restTemplate.exchange(url, HttpMethod.GET,request, Map.class,collegeId);
//return testCircuitBreakerInFunctionalProgramming().apply(collegeId);
//throw new RuntimeException("abc");
return forEntity;
}
public ResponseEntity<Map> getAllStudentFallback(String collegeId, Throwable t) {
System.out.println("fallback method");
return null;
}
public ResponseEntity<Map> getAllStudentFallback(String collegeId, CallNotPermittedException t) {
System.out.println("fallback call not permitted method");
return null;
}
Integration Test :
#SpringBootTest
#RunWith(SpringRunner.class)
public class CollegeIntegrationTest {
//#InjectMocks
#Autowired
private CollegeController controller;
#InjectMocks
#Autowired
private CollegeServiceImpl service;
#Mock
private RestTemplate restTemplate;
#Before
public void setup() {
MockitoAnnotations.openMocks(this);
}
#Test
public void testCollege() {
ResponseEntity<Map> response = new ResponseEntity<>(HttpStatus.OK);
Mockito.when(restTemplate.exchange(Mockito.anyString(), Mockito.same(HttpMethod.GET),Mockito.<HttpEntity> any(), Mockito.any(Class.class),Mockito.anyString())).thenReturn(response);
controller.getCollegeStudent("102");
}
}
What I am doing:
1 of the micorservice is down and I am implementing CircuitBreaker for that scenario and Its working as expected.
Issue:
When I am not using CircuitBreaker annotation, mock of RestTemplate is available with same id in service class. i.e Mocking working fine as expected.
In case when I use #CircuitBreaker, some mock id is generated in IntegrationTest class but the same is not available on service, so not able to mock restcall.
I am initializing rest template using :
#Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
return restTemplate;
}
In whole mocking is not working as expected while using #CircuitBreaker.
Any suggestion would be appreciated.

Listen message queue SQS with Spring Boot not works with standard config

I'm unable to make works queue listener with Spring Boot and SQS
(the message is sent and appear in SQS ui)
The #MessageMapping or #SqsListener not works
Java: 11
Spring Boot: 2.1.7
Dependencie: spring-cloud-aws-messaging
This is my config
#Configuration
#EnableSqs
public class SqsConfig {
#Value("#{'${env.name:DEV}'}")
private String envName;
#Value("${cloud.aws.region.static}")
private String region;
#Value("${cloud.aws.credentials.access-key}")
private String awsAccessKey;
#Value("${cloud.aws.credentials.secret-key}")
private String awsSecretKey;
#Bean
public Headers headers() {
return new Headers();
}
#Bean
public MessageQueue queueMessagingSqs(Headers headers,
QueueMessagingTemplate queueMessagingTemplate) {
Sqs queue = new Sqs();
queue.setQueueMessagingTemplate(queueMessagingTemplate);
queue.setHeaders(headers);
return queue;
}
private ResourceIdResolver getResourceIdResolver() {
return queueName -> envName + "-" + queueName;
}
#Bean
public DestinationResolver destinationResolver(AmazonSQSAsync amazonSQSAsync) {
DynamicQueueUrlDestinationResolver destinationResolver = new DynamicQueueUrlDestinationResolver(
amazonSQSAsync,
getResourceIdResolver());
destinationResolver.setAutoCreate(true);
return destinationResolver;
}
#Bean
public QueueMessagingTemplate queueMessagingTemplate(AmazonSQSAsync amazonSQSAsync,
DestinationResolver destinationResolver) {
return new QueueMessagingTemplate(amazonSQSAsync, destinationResolver, null);
}
#Bean
public QueueMessageHandlerFactory queueMessageHandlerFactory() {
QueueMessageHandlerFactory factory = new QueueMessageHandlerFactory();
MappingJackson2MessageConverter messageConverter = new MappingJackson2MessageConverter();
messageConverter.setStrictContentTypeMatch(false);
factory.setArgumentResolvers(Collections.singletonList(new PayloadArgumentResolver(messageConverter)));
return factory;
}
#Bean
public SimpleMessageListenerContainerFactory simpleMessageListenerContainerFactory(AmazonSQSAsync amazonSqs) {
SimpleMessageListenerContainerFactory factory = new SimpleMessageListenerContainerFactory();
factory.setAmazonSqs(amazonSqs);
factory.setMaxNumberOfMessages(10);
factory.setWaitTimeOut(2);
return factory;
}
}
I notice also that org.springframework.cloud.aws.messaging.config.SimpleMessageListenerContainerFactory and org.springframework.cloud.aws.messaging.config.annotation.SqsConfiguration run on startup
And my test
#RunWith(SpringJUnit4ClassRunner.class)
public class ListenTest {
#Autowired
private MessageQueue queue;
private final String queueName = "test-queue-receive";
private String result = null;
#Test
public void test_listen() {
// given
String data = "abc";
// when
queue.send(queueName, data).join();
// then
Awaitility.await()
.atMost(10, TimeUnit.SECONDS)
.until(() -> Objects.nonNull(result));
Assertions.assertThat(result).equals(data);
}
#MessageMapping(value = queueName)
public void receive(String data) {
this.result = data;
}
}
Do you think something is wrong ?
I create a repo for exemple : (https://github.com/mmaryo/java-sqs-test)
In test folder, change aws credentials in 'application.yml'
Then run tests
I had the same issue when using the spring-cloud-aws-messaging package, but then I used the queue URL in the #SqsListener annotation instead of the queue name and it worked.
#SqsListener(value = { "https://full-queue-URL" }, deletionPolicy = SqsMessageDeletionPolicy.ON_SUCCESS)
public void receive(String message) {
// do something
}
It seems you can use the queue name when using the spring-cloud-starter-aws-messaging package. I believe there is some configuration that allows usage of the queue name instead of URL if you don't want to use the starter package.
EDIT: I noticed the region was being defaulted to us-west-2 despite me listing us-east-1 in my properties file. Then I created a RegionProvider bean and set the region to us-east-1 in there and now when I use the queue name in the #SqsMessaging it is found and correctly resolved to the URL in the framework code.
you'll need to leverage the #Primary annotation, this is what worked for me:
#Autowired(required = false)
private AWSCredentialsProvider awsCredentialsProvider;
#Autowired
private AppConfig appConfig;
#Bean
public QueueMessagingTemplate getQueueMessagingTemplate() {
return new QueueMessagingTemplate(sqsClient());
}
#Primary
#Bean
public AmazonSQSAsync sqsClient() {
AmazonSQSAsyncClientBuilder builder = AmazonSQSAsyncClientBuilder.standard();
if (this.awsCredentialsProvider != null) {
builder.withCredentials(this.awsCredentialsProvider);
}
if (appConfig.getSqsRegion() != null) {
builder.withRegion(appConfig.getSqsRegion());
} else {
builder.withRegion(Regions.DEFAULT_REGION);
}
return builder.build();
}
build.gradle needs these deps:
implementation("org.springframework.cloud:spring-cloud-starter-aws:2.2.0.RELEASE")
implementation("org.springframework.cloud:spring-cloud-aws-messaging:2.2.0.RELEASE")

Java Spring 5 Get and findById mongo MonoOnErrorResume

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

Why doesn't #Getmapping work in some instances?

In my controller, the following use of #GetMapping works:
#GetMapping(value = "/new")
public String newEssay(){
return "articles/essay_new";
}
But it doesn't work like this:
#GetMapping(value = "/essays/{essayId: [0-9]+}")
//#RequestMapping(value = "/essays/{essayId:[0-9]+}", method = RequestMethod.GET)
public String getEssay(Model model,
#PathVariable("essayId") long essayId) throws NoFindException, ForBiddenException, ParseException {
JsEssay jsEssay = jsBiz.get(JsEssay.class, essayId);
model.addAttribute("jsEssay", jsEssay);
return "articles/essay";
}
I tried it with Spring 4.3.3 and 5.0.0.M5.
Config:
#Configuration
#ComponentScan( basePackages = {"me.freezehome.blog"},
excludeFilters = {
#ComponentScan.Filter(type = FilterType.ANNOTATION, value = EnableWebMvc.class)
}
)
public class RootConfig {
}
#Configuration
#EnableWebMvc
#Import({WebSecurityConfig.class})
public class WebConfig extends WebMvcConfigurerAdapter{
#Bean
public RequestMappingHandlerMapping requestMappingHandlerMapping(){
return new RequestMappingHandlerMapping();
}
#Bean
public RequestMappingHandlerAdapter requestMappingHandlerAdapter(){
return new RequestMappingHandlerAdapter();
}
}
Google results:
Add support for #GetMapping, #PostMapping etc. introduced in Spring 4.3 in ControllerLinkBuilder #471
GetMapping and PostMapping annotations Ask
github source: lbfreeze-blog-develop
Please remove the space after essayId:
Also, you don't need to write value = for #GetMapping.

#Cacheable in DAO layer not being triggered (Spring/Reddis)

I am having trouble caching internal methods within my DAO layer while in Proxy mode.
I am aware that while in Proxy mode, only external method calls coming in through the proxy are intercepted. However,I want to avoid having to switch to AspectJ mode and was wondering if any other work arounds existed.
I am displaying my code below and am wondering what changes, if any, I can add to make this process work.
--Note I am using swagger to document my code
--Also note my code has been watered down....for obvious reasons
//Controller
#RestController
#Api(produces = "application/json", protocols = "https", tags = "Securities", description = "Securities information")
public class SecuritiesInfoController extends Controller {
private SecuritiesInfoManager _securitiesInfoManager = new SecuritiesInfoManager();
#RequestMapping(value = "/security", method = RequestMethod.GET)
public List<SecuritiesInfo> getAll(){
return _securitiesInfoManager.getAll();
}
}
//Service
public class SecuritiesInfoManager extends Manager {
private SecuritiesInfoDAO _securitiesDAO = new SecuritiesInfoDAO();
public List<SecuritiesInfo> getAll() {
return _securitiesDAO.getAll();
}
}
//DAO
public class SecuritiesInfoDAO extends DAO {
private static String securitiesTable = "Securities";
#SecuritiesInfoDAOInterface
public List<SecuritiesInfo> getAll() {
//Magic
}
}
//Interface
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.METHOD})
#Cacheable(cacheNames = "SecuritiesInfo",cacheManager="cacheManager",
keyGenerator="keyGenerator" )
public #interface SecuritiesInfoDAOInterface {
}
//CacheConfig
#Configuration
//#EnableCaching(mode = AdviceMode.PROXY)
#EnableCaching(proxyTargetClass = true)
//#EnableCaching
public class CacheConfig extends CachingConfigurerSupport {
#Bean
public SecuritiesInfoDAO myService() {
// configure and return a class having #Cacheable methods
return new SecuritiesInfoDAO();
}
#Bean
public JedisConnectionFactory redisConnectionFactory() {
JedisConnectionFactory redisConnectionFactory = new JedisConnectionFactory();
// Defaults
redisConnectionFactory.setHostName("Nope");
redisConnectionFactory.setPort(LoL);
System.out.println("IN CONNTECTION");
redisConnectionFactory.setPassword("Please help me :)");
return redisConnectionFactory;
}
#Bean
public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory cf) {
System.out.println("cf: "+cf.toString());
RedisTemplate<String, String> redisTemplate = new RedisTemplate<String, String>();
redisTemplate.setConnectionFactory(cf);
return redisTemplate;
}
/*
#Primary
#Bean
public RedisTemplate<String,ExpiringSession> redisTemplate2(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, ExpiringSession> template = new RedisTemplate<String, ExpiringSession>();
template.setHashValueSerializer(new LdapFailAwareRedisObjectSerializer());
template.setConnectionFactory(connectionFactory);
return template;
}
*/
#Bean
public CacheManager cacheManager(RedisTemplate<String, String> redisTemplate) {
System.out.println("IN CACHE MANAGER");
RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate);
// Number of seconds before expiration. Defaults to unlimited (0)
// cacheManager.setDefaultExpiration(300);
return cacheManager;
}
#Bean
public KeyGenerator keyGenerator() {
return new KeyGenerator() {
#Override
public Object generate(Object o, Method method, Object... objects) {
// This will generate a unique key of the class name, the method name,
// and all method parameters appended.
StringBuilder sb = new StringBuilder();
sb.append(o.getClass().getName());
sb.append(method.getName());
for (Object obj : objects) {
sb.append(obj.toString());
}
System.out.println(sb.toString());
return sb.toString();
}
};
}
So I figured out the answer. It turns out I wasn't implementing/instantiating the interface correctly.
First I have to #Autowire my manager class in my controller. Then #autowire my interface class in my manager.
For a more detailed solution, I am placing my revised code below.
//Controller
#RestController
#Api(produces = "application/json", protocols = "https", tags = "Securities", description = "Securities information")
public class SecuritiesInfoController extends Controller {
#Autowired
private SecuritiesInfoManager _securitiesInfoManager = new SecuritiesInfoManager();
#RequestMapping(value = "/security", method = RequestMethod.GET)
public List<SecuritiesInfo> getAll(){
return _securitiesInfoManager.getAll();
}
}
//Service
public class SecuritiesInfoManager extends Manager {
#Autowired
public void setSecuritiesInfoDAOInterface(SecuritiesInfoDAOInterface _securitiesInfoDAOInterface) {
this._securitiesInfoDAOInterface = _securitiesInfoDAOInterface;
}
public List<SecuritiesInfo> getAll() {
return _securitiesInfoDAOInterface.getAll();
}
}
//DAO
public class SecuritiesInfoDAO extends DAO implements SecuritiesInfoDAOInterface {
private static String securitiesTable = "Securities";
#Override
public List<SecuritiesInfo> getAll() {
//Magic
}
}
//Interface
public interface SecuritiesInfoDAOInterface {
#Cacheable(cacheNames = "SecuritiesInfo",cacheManager="cacheManager", keyGenerator="keyGenerator" )
List<SecuritiesInfo> getAll();
}
}
//CacheConfig
#Configuration
#EnableCaching
public class CacheConfig extends CachingConfigurerSupport {
#Bean
public SecuritiesInfoManager myService() {
// configure and return a class having #Cacheable methods
return new SecuritiesInfoManager();
}
//rest same as before
}
//WebConfig
#Configuration
#ComponentScan(basePackages = {"package name"})
public class WebConfig extends WebMvcConfigurerAdapter {
#Override
public void configurePathMatch(PathMatchConfigurer configurer) {
AntPathMatcher matcher = new AntPathMatcher();
matcher.setCaseSensitive(false);
configurer.setPathMatcher(matcher);
}
}

Categories

Resources