When dows 'this' keyword points ? In rumtime or compile time? - spring

How does this work in this condition? If I have to initialize brandrepository in every method at compilation how can I make it
#Autowired
private BrandRepository brandRepository;
#Override
public Page<BrandDTO> getBrandList(int page, String sortBy, String orderBy) {
BrandRepository brandRepository = this.brandRepository;**Does this code execute once or multiple times**
Pageable pageable = PageRequest.of(page, 10, Sort.by(Sort.Direction.valueOf(orderBy), sortBy));
Page<BrandEntity> result = brandRepository.findAll(pageable);
return result.map(brandEntity -> toDTO(brandEntity));
}

Related

No converter found capable of converting from type

org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [com.skywarelabs.runwayreporting.api.modules.asset.Asset] to type [com.skywarelabs.runwayreporting.api.modules.asset.AssetPageView]
I have two repository methods which do essentialy the same thing.
I'm expecting them to return an object of <Page>.
If I call findPageByAerodrome, this works correctly.
However, If I call findPageByFilter, I get the error below.
..the reason I want to use findPageByFilter is that I wish to extend this with additional filtering parameters.
'''
public interface AssetRepository extends PageCrudRepository<Asset, Long> {
#Query("SELECT a FROM Asset a WHERE " +
"(a.aerodrome = :aerodrome)")
<T> Page<T> findPageByFilter(#Param("aerodrome") Aerodrome aerodrome,
Pageable pageable, Class<T> type);
<T> Page<T> findPageByAerodrome(#Param("aerodrome") Aerodrome aerodrome,
Pageable pageable, Class<T> type);
,,,
Use this instead
<T> Page<T> findAll(Specification s, Pageable pageable, Class<T> type);
and pass a custom Specification where you create your equality filter like this:
<T> Page<T> findPageByFilter(#Param("aerodrome") Aerodrome aerodrome,
Pageable pageable, Class<T> type) {
return findAll(
new Specification() {
#Override
public Predicate toPredicate(Root<T> root, CriteriaQuery query, CriteriaBuilder cb) {
return cb.equal(root.get("aerodrome"), aerodrome);
}
},
pageable,
type
);
}
Figured it out. I need to add an entity specific constructor.
'''
public AssetPageView(Asset asset) {
this ( asset.getId(), asset.getAssetType(), asset.getAerodrome(), asset.getName(),
asset.getAreaOfOperationsLoc(), asset.getDisplayOrder() );
}
public AssetPageView(Long id, AssetType assetType, Aerodrome aerodrome, String name,
String areaOfOperationsLoc, Long displayOrder) {
this.id = id;
this.assetType = assetType.getName();
this.aerodrome = aerodrome.getCallSign();
this.name = name;
this.areaOfOperationsLoc = areaOfOperationsLoc;
this.displayOrder = displayOrder;
}
'''

How to generate cache CustomKey for Redis in Spring Boot

I have spring boot application which is integrated with Redis cache. Have to implement caching for one of the method call. That method argument is an object with multiple params which is external Request object. This object params will vary for each request also based on that param and its values output of the method is varies. I need to create a cache key using that Request object field/param values. How to achieve it.
We can use SimpleKeyGenerator only when method params are static?
UserService.java
#Cacheable(value = "usercache", keyGenerator="customKeyGenerator")
public UserResponse getUserResp(User user){
//Some backend calls
return user
}
User.java
public class User {
private String firstname;
private String lastname;
private Integer age;
private Date dob;
private Address address;
// Another 10 params
}
In this method implementation User object is dynamic. I have to create a cache key based on User object fields which is having valid non null values. How to achieve it.
I have implemented as like below.
User.java
public class User implements Serializable {
private String firstname;
private String lastname;
private Integer age;
private Date dob;
private Address address;
// Another 10 params
#Override
public int hashCode() {
final int prime = 31;
//Add necessary fields
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
//Add necessary fields
}
}
public class UserKeyGenerator implements KeyGenerator{
private static final String UNDERSCORE_DELIMITER = "_";
#Override
public Object generate(Object target, Method method, Object... params) {
String cacheKey = null;
if(params.length > 0) {
StringJoiner paramStrJoiner = new StringJoiner(UNDERSCORE_DELIMITER);
User userReq = (User) params[0];
paramStrJoiner.add(target.getClass().getSimpleName());
paramStrJoiner.add(method.getName());
paramStrJoiner.add(String.valueOf(userReq.hashCode()));
cacheKey = paramStrJoiner.toString();
}
return cacheKey;
}

Spring WebFlux - Convert Flux to List<Object>

I am learning Spring WebFlux.
My Entity goes like this:
#Table("users")
public class User {
#Id
private Integer id;
private String name;
private int age;
private double salary;
}
I have a Repository (R2 using H2 Database) like below:
public interface UserRepository extends ReactiveCrudRepository<User,Integer> {
}
And my controller is:
#Autowired
private UserRepository userRepository;
private static List<User> userList = new ArrayList<>();
#PostConstruct
public void initializeStockObjects() {
User stock1 = new User(11, "aaaa", 123, 123);
User stock2 = new User(12, "bbb", 123, 123);
User stock3 = new User(13, "ccc", 123, 123);
userList.add(stock1);
userList.add(stock2);
userList.add(stock3);
}
#RequestMapping(value = "/livelistofusers", method = RequestMethod.GET, produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<List<User>> getUsers() {
return getUserData(userList);
}
public Flux<List<User>> getUserData(List<User> userList) {
Flux<Long> interval = Flux.interval(Duration.ofSeconds(3));
interval.subscribe((i) -> userList.forEach(user -> addNewUser(user)));
Flux<List<User>> transactionFlux = Flux.fromStream(Stream.generate(() -> userList));
return Flux.zip(interval, transactionFlux).map(Tuple2::getT2);
}
All good till this point. I am able to return the the entire list of users every 3 seconds to the view. No issues at all here.
Now, I want to send the Flue i.e. Flux flux2 = userRepository.findAll() to the view. That means, instead of return getUserData(userList); how can I do return getUserData(flux2(...what should I do here ???... I tried couple of things but I end up making the Blocking list instead of Non-Blocking ...)); ?
Question: How can I achieve this? i.e. How can I send the entire Flux every 3 seconds to my view. I am feeling lost here and clueless. Any relevant help links or solution will be greatly appreciated.
Edit:
As per Nipuna's comments I tried this:
#RequestMapping(value = "/livelistofusersall", method = RequestMethod.GET, produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<List<User>> getUsersall() {
Flux<Long> interval = Flux.interval(Duration.ofSeconds(3));
interval.subscribe((i) -> userRepository.findAll());
Flux<List<User>> transactionFlux = userRepository.findAll().collectList().flatMapMany(Flux::just);
return Flux.zip(interval, transactionFlux).map(Tuple2::getT2);
}
But now at my context path, the list loads but "only once" after a wait of 3 seconds. What I am missing here?
You can use collectList() operator in Flux for this which gives a Mono of List.
userRepository.findAll().collectList().flatMapMany(Flux::just);

Cannot Write Data to ElasticSearch with AbstractReactiveElasticsearchConfiguration

I am trying out to write data to my local Elasticsearch Docker Container (7.4.2), for simplicity I used the AbstractReactiveElasticsearchConfiguration given from Spring also Overriding the entityMapper function. The I constructed my repository extending the ReactiveElasticsearchRepository
Then in the end I used my autowired repository to saveAll() my collection of elements containing the data. However Elasticsearch doesn't write any data. Also i have a REST controller which is starting my whole process returning nothing basicly, DeferredResult>
The REST method coming from my ApiDelegateImpl
#Override
public DeferredResult<ResponseEntity<Void>> openUsageExporterStartPost() {
final DeferredResult<ResponseEntity<Void>> deferredResult = new DeferredResult<>();
ForkJoinPool.commonPool().execute(() -> {
try {
openUsageExporterAdapter.startExport();
deferredResult.setResult(ResponseEntity.accepted().build());
} catch (Exception e) {
deferredResult.setErrorResult(e);
}
}
);
return deferredResult;
}
My Elasticsearch Configuration
#Configuration
public class ElasticSearchConfig extends AbstractReactiveElasticsearchConfiguration {
#Value("${spring.data.elasticsearch.client.reactive.endpoints}")
private String elasticSearchEndpoint;
#Bean
#Override
public EntityMapper entityMapper() {
final ElasticsearchEntityMapper entityMapper = new ElasticsearchEntityMapper(elasticsearchMappingContext(), new DefaultConversionService());
entityMapper.setConversions(elasticsearchCustomConversions());
return entityMapper;
}
#Override
public ReactiveElasticsearchClient reactiveElasticsearchClient() {
ClientConfiguration clientConfiguration = ClientConfiguration.builder()
.connectedTo(elasticSearchEndpoint)
.build();
return ReactiveRestClients.create(clientConfiguration);
}
}
My Repository
public interface OpenUsageRepository extends ReactiveElasticsearchRepository<OpenUsage, Long> {
}
My DTO
#Data
#Document(indexName = "open_usages", type = "open_usages")
#TypeAlias("OpenUsage")
public class OpenUsage {
#Field(name = "id")
#Id
private Long id;
......
}
My Adapter Implementation
#Autowired
private final OpenUsageRepository openUsageRepository;
...transform entity into OpenUsage...
public void doSomething(final List<OpenUsage> openUsages){
openUsageRepository.saveAll(openUsages)
}
And finally my IT test
#SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
#Testcontainers
#TestPropertySource(locations = {"classpath:application-it.properties"})
#ContextConfiguration(initializers = OpenUsageExporterApplicationIT.Initializer.class)
class OpenUsageExporterApplicationIT {
#LocalServerPort
private int port;
private final static String STARTCALL = "http://localhost:%s/open-usage-exporter/start/";
#Container
private static ElasticsearchContainer container = new ElasticsearchContainer("docker.elastic.co/elasticsearch/elasticsearch:6.8.4").withExposedPorts(9200);
static class Initializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
#Override
public void initialize(final ConfigurableApplicationContext configurableApplicationContext) {
final List<String> pairs = new ArrayList<>();
pairs.add("spring.data.elasticsearch.client.reactive.endpoints=" + container.getContainerIpAddress() + ":" + container.getFirstMappedPort());
pairs.add("spring.elasticsearch.rest.uris=http://" + container.getContainerIpAddress() + ":" + container.getFirstMappedPort());
TestPropertyValues.of(pairs).applyTo(configurableApplicationContext);
}
}
#Test
void testExportToES() throws IOException, InterruptedException {
final List<OpenUsageEntity> openUsageEntities = dbPreparator.insertTestData();
assertTrue(openUsageEntities.size() > 0);
final String result = executeRestCall(STARTCALL);
// Awaitility here tells me nothing is in ElasticSearch :(
}
private String executeRestCall(final String urlTemplate) throws IOException {
final String url = String.format(urlTemplate, port);
final HttpUriRequest request = new HttpPost(url);
final HttpResponse response = HttpClientBuilder.create().build().execute(request);
// Get the result.
return EntityUtils.toString(response.getEntity());
}
}
public void doSomething(final List<OpenUsage> openUsages){
openUsageRepository.saveAll(openUsages)
}
This lacks a semicolon at the end, so it should not compile.
But I assume this is just a typo, and there is a semicolon in reality.
Anyway, saveAll() returns a Flux. This Flux is just a recipe for saving your data, and it is not 'executed' until subscribe() is called by someone (or something like blockLast()). You just throw that Flux away, so the saving never gets executed.
How to fix this? One option is to add .blockLast() call:
openUsageRepository.saveAll(openUsages).blockLast();
But this will save the data in a blocking way effectively defeating the reactivity.
Another option is, if the code you are calling saveAll() from supports reactivity is just to return the Flux returned by saveAll(), but, as your doSomething() has void return type, this is doubtful.
It is not seen how your startExport() connects to doSomething() anyway. But it looks like your 'calling code' does not use any notion of reactivity, so a real solution would be to either rewrite the calling code to use reactivity (obtain a Publisher and subscribe() on it, then wait till the data arrives), or revert to using blocking API (ElasticsearchRepository instead of ReactiveElasticsearchRepository).

Custom Object as a #RequestParam

I have a paginated endpoint that looks like this /api/clients?range=0-25.
I'd like the getClients() method in my ClientController to directly receive an instance of a custom Range object rather than having to validate a "0-25" String but I'm having trouble figuring this out.
#Getter
final class Range {
#Min(0)
private Integer offset = 0;
#Min(1)
private Integer limit = 25;
}
#ResponseBody
#GetMapping(params = { "range" })
public ResponseEntity<?> getAllClients(#RequestParam(value = "range", required = false) QueryRange queryRange, final HttpServletResponse response) {
...
}
I'm not sure how to instruct the Controller to correctly deserialize the "0-25" string into the Range...
You can use a Converter<S, T>, as shown below:
#Component
public class RangeConverter implements Converter<String, Range> {
#Override
public Range convert(String source) {
String[] values = source.split("-");
return new Range(Integer.valueOf(values[0]), Integer.valueOf(values[1]));
}
}
You also could handle invalid values according to your needs. If you use the above converter as is, the attempt to convert an invalid value such as 1-x will result in a ConversionFailedException.
You can also do the following it seems :
public class QueryRangeEditor extends PropertyEditorSupport {
private static final Pattern PATTERN = Pattern.compile("^([1-9]\\d*|0)-([1-9]\\d*|0)$");
#Override
public void setAsText(String text) throws IllegalArgumentException {
final QueryRange range = new QueryRange();
Matcher matcher = PATTERN.matcher(text);
if (matcher.find()) {
range.setOffset(Integer.valueOf(matcher.group(1)));
range.setLimit(Integer.valueOf(matcher.group(2)));
} else {
throw new IllegalArgumentException("OI"); // todo - replace
}
setValue(range);
}
}
#InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(QueryRange.class, new QueryRangeEditor());
}
But #cassiomolin's looks cleaner...

Resources