Displaying APIs of Spring Data Rest - jhipster

After adding Spring Data Rest to a JHipster generated project, how to make those Rest APIs viewable in addition of APIs on the controller level?
#RepositoryRestResource(collectionResourceRel = "api/myEntities", path = "api/myEntites")
I also try "/api/myEntities".
In Spring Data Rest, I would be able to see those Rest APIs something like
/api/myEntities/search/<method name>
The following is the SwaggerConfiguration class in my project. I don't see how I can customize it to show APIs from Spring Data Rest.
#Configuration
#EnableSwagger2
#Profile("!"+Constants.SPRING_PROFILE_PRODUCTION)
public class SwaggerConfiguration implements EnvironmentAware {
private final Logger log = LoggerFactory.getLogger(SwaggerConfiguration.class);
public static final String DEFAULT_INCLUDE_PATTERN = "/api/.*";
private RelaxedPropertyResolver propertyResolver;
#Override
public void setEnvironment(Environment environment) {
this.propertyResolver = new RelaxedPropertyResolver(environment, "swagger.");
}
/**
* Swagger Springfox configuration.
*/
#Bean
public Docket swaggerSpringfoxDocket() {
log.debug("Starting Swagger");
StopWatch watch = new StopWatch();
watch.start();
Docket docket = new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.genericModelSubstitutes(ResponseEntity.class)
.forCodeGeneration(true)
.genericModelSubstitutes(ResponseEntity.class)
.directModelSubstitute(org.joda.time.LocalDate.class, String.class)
.directModelSubstitute(org.joda.time.LocalDateTime.class, Date.class)
.directModelSubstitute(org.joda.time.DateTime.class, Date.class)
.directModelSubstitute(java.time.LocalDate.class, String.class)
.directModelSubstitute(java.time.ZonedDateTime.class, Date.class)
.directModelSubstitute(java.time.LocalDateTime.class, Date.class)
.select()
.paths(regex(DEFAULT_INCLUDE_PATTERN))
.build();
watch.stop();
log.debug("Started Swagger in {} ms", watch.getTotalTimeMillis());
return docket;
}
/**
* API Info as it appears on the swagger-ui page.
*/
private ApiInfo apiInfo() {
return new ApiInfo(
propertyResolver.getProperty("title"),
propertyResolver.getProperty("description"),
propertyResolver.getProperty("version"),
propertyResolver.getProperty("termsOfServiceUrl"),
propertyResolver.getProperty("contact"),
propertyResolver.getProperty("license"),
propertyResolver.getProperty("licenseUrl"));
}
}

In xx.xx.config.apidoc.SwaggerConfiguration.java
Following is the default pattern to include in SwaggerConfiguration which will display API in your administration tab.
public static final String DEFAULT_INCLUDE_PATTERN = "/api/.*";
And following line from the same class
.paths(regex(DEFAULT_INCLUDE_PATTERN))
added the above pattern.
So you can add another one of your own, examining the Spring Data Rest api pattern. And add that api pattern in the paths() function like as above one.
Here I am talking with jhipster 2.18.0

Related

Spring Integration - Customize ObjectMapper used by WebFlux OutboundGateway

How do we customize the Jackson ObjectMapper used by WebFlux OutboundGateway? The normal customization done via Jackson2ObjectMapperBuilder or Jackson2ObjectMapperBuilderCustomizer is NOT respected.
Without this customization, LocalDate is serialized as SerializationFeature.WRITE_DATES_AS_TIMESTAMPS. Sample output - [2022-10-20] and there is NO way to customize the format
I assume you really talk about Spring Boot auto-configuration which is applied to the WebFlux instance. Consider to use an overloaded WebFlux.outboundGateway(String uri, WebClient webClient) to be able to auto-wire a WebClient.Builder which might be already configured with the mentioned customized ObjectMapper.
Registering a bean of type com.fasterxml.jackson.databind.module.SimpleModule will automatically be used by the pre-configured ObjectMapper bean. In SimpleModule, it is possible to register custom serialization and deserialization specifications.
To put that into code, a very simple solution would be the following:
#Bean
public SimpleModule odtModule() {
SimpleModule module = new SimpleModule();
JsonSerializer<LocalDate> serializer = new JsonSerializer<>() {
#Override
public void serialize(LocalDate odt, JsonGenerator jgen, SerializerProvider provider) throws IOException {
String formatted = odt.format(DateTimeFormatter.ISO_LOCAL_DATE);
jgen.writeString(formatted);
}
};
JsonDeserializer<LocalDate> deserializer = new JsonDeserializer<>() {
#Override
public LocalDate deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
return LocalDate.parse(jsonParser.getValueAsString());
}
};
module.addSerializer(LocalDate.class, serializer);
module.addDeserializer(LocalDate.class, deserializer);
return module;
}
Note that using lambdas for the implementations has sometimes resulted in weird behaviors for me, so I tend not to do that.

How to write unit test for Hybris OCC controllers

We are writing unit test for commerce webservices (OCC) controllers, for example CartsControllers, UsersController etc. Almost all the methods within these controllers return web service DTO i.e the ones that ends with *WsDTO. This object conversion is done by dataMapper which is part of spring web application context. The challenge we are facing is unit test or integration tests cannot access web application context and fetch the bean from there. 90% of commerce webservices (OCC) controller methods are not testable without this since they all return DTOs. Mocking dataMapper itself will not achieve anything since that will defeat the purpose of writing test.
Please help!!
I can give some points on how you can start writing testcases for OCC controllers.
Lets say you want to test CartsControllers -> getCart method which you have written in your custom customlswebservices extension.
#RequestMapping(value = "/{cartId}", method = RequestMethod.GET)
#ResponseBody
public CartWsDTO getCart(#RequestParam(required = false, defaultValue = DEFAULT_FIELD_SET) final String fields)
{
// CartMatchingFilter sets current cart based on cartId, so we can return cart from the session
return getDataMapper().map(getSessionCart(), CartWsDTO.class, fields);
}
Integration Test :
import static org.fest.assertions.Assertions.assertThat;
#NeedsEmbeddedServer(webExtensions = { "customlswebservices", "oauth2" })
#IntegrationTest
#FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class CartWebServiceIntegrationTest extends AbstractCoreIntegrationTest
{
private WsSecuredRequestBuilder wsSecuredRequestBuilder;
#Before
public void beforeTest() throws Exception
{
wsSecuredRequestBuilder = new WsSecuredRequestBuilder() //
.extensionName("customlswebservices") //
.path("v2") //
.client("trusted_client", "secret") //
.grantClientCredentials();
}
#Test
public void testGetCart()
{
final Response wsResponse = wsSecuredRequestBuilder //
.path("electronics") // Put your custom wcms site here
.path("users") //
.path("test#test.com") // Add current user id here
.path("carts") //
.path("100038383") // Cart ID
.queryParam("fields", "DEFAULT") //
.build() //
.get(); //
assertThat(wsResponse).isNotNull();
assertThat(wsResponse.getStatus()).isEqualTo(HttpServletResponse.SC_OK);
final CartWsDTO cartWsDTO = wsResponse.readEntity(CartWsDTO.class);
assertThat(cartWsDTO).isNotNull();
}
}
Hope this may help you.

How can microservice can talk to other microservice in JHipster

I am planning to create a microservice aplication with a dedicated service for dealing with data (mostly a Mongodb based service). I am wondering if there is a way using which my other microservices will be able to communicate with this service to make use of the shared data. Is it possible with JHipster API Gateway ?
If not how can I achieve this. I dont want to keep multiple copies of the same data within each microservice.
You can also use Feign clients with JHipster.
Annotate your SpringBootApplication with #EnableFeignClients
...
import org.springframework.cloud.openfeign.EnableFeignClients;
...
#SpringBootApplication
#EnableConfigurationProperties({LiquibaseProperties.class, ApplicationProperties.class})
#EnableDiscoveryClient
#EnableFeignClients
public class MyApp {
...
}
Create a Feign client in your microservice
...
import org.springframework.cloud.openfeign.FeignClient;
...
#FeignClient("another-service")
public interface AnotherClient {
#RequestMapping(method = RequestMethod.GET, value = "/api/another")
List<AnotherDTO> getAll();
}
Inject the Feign client with #Autowired and call it. It should be ready to use.
#RestController
#RequestMapping("/api")
public class MyResource {
...
#Autowired
private AnotherClient anotherClient;
...
#GetMapping("/another")
#Timed
public List<AnotherDTO> getAll() {
log.debug("REST request to get all");
return anotherClient.getAll();
}
}
For us, it worked without implementing a ClientHttpRequestInterceptor and setting a JWT token.
You can register your microservices to the same registry and then they can call each other.
UPDATE : Here is how I made it work.
In the microservice consuming the data one, use RestTemplate with the current user's jwt-token in the Authorization-header for the API calls :
#Component
public class AuthenticateClientHttpRequestInterceptor implements ClientHttpRequestInterceptor {
#Override
public ClientHttpResponse intercept(HttpRequest httpRequest, byte[] bytes, ClientHttpRequestExecution clientHttpRequestExecution) throws IOException {
String token = SecurityUtils.getCurrentUserJWT();
httpRequest.getHeaders().add("Authorization","Bearer "+token);
return clientHttpRequestExecution.execute( httpRequest, bytes );
}
}
My custom restTemplate using ClientHttpRequestInterceptor for adding token in header.
#Configuration
public class CustomBean {
#Autowired
AuthenticateClientHttpRequestInterceptor interceptor;
#Bean
#LoadBalanced
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
restTemplate.setInterceptors(Collections.singletonList(interceptor));
return restTemplate;
}
}
And in the resource controller where your are making the call for data:
#RestController
#RequestMapping("/api")
public class DataResource {
#Autowired
RestTemplate restTemplate;
#PostMapping("/hello")
#Timed
public ResponseEntity<Hello> createHello(#RequestBody Hello Hello) throws URISyntaxException {
//The name your data micro service registrated in the Jhipster Registry
String dataServiceName = "data_micro_service";
URI uri = UriComponentsBuilder.fromUriString("//" + dataServiceName + "/api/datas")
.build()
.toUri();
//call the data microservice apis
List<Data> result = restTemplate.getForObject(uri, Data[].class);
return ResponseEntity.created(new URI("/api/hellos/" + result.getId()))
.headers(HeaderUtil.createEntityCreationAlert(ENTITY_NAME, result.getId().toString()))
.body(result);
}
}
Typically microservices talk to each other. Thats the whole point. With Eureka discovery in place you simply call the microservice by name instead of the FQDN which we normally would use without microservice.
For e.g. your book-service will call the author-service like this
http://author-service/authors
full example here https://spring.io/blog/2015/01/20/microservice-registration-and-discovery-with-spring-cloud-and-netflix-s-eureka
Please don't forget that JHipster is an opinionated framework based off of Spring Cloud so you can find most of this stuff by searching Spring docs.
you can use below solution :
Microservice A (i.e UAA-SERVICE), and Microservice B
Microservice B want to connect microservice A and call services with Feign client.
1)This code for Microservice B
Client proxy :- #AuthorizedFeignClient(name = "UAA-SERVICE")
#AuthorizedFeignClient(name = "UAA-SERVICE")
public interface UaaServiceClient {
#RequestMapping(method = RequestMethod.GET, path = "api/users")
public List<UserDTO> getUserList();
#RequestMapping(method = RequestMethod.PUT, path = "api/user-info")
public String updateUserInfo(#RequestBody UserDTO userDTO);
}
UAA-SERVICE : find this name with running Application Instances with registry.
2) In Microservice B (application.yml)
Increase feign client connection Time Out:
feign:
client:
config:
default:
connectTimeout: 10000
readTimeout: 50000
Increase hystrix Thread time out:-
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 60000
shareSecurityContext: true
3) add #EnableFeignClients in main #SpringBootApplication class.
This solution is working fine for me.

Spring Integration Cassandra persistence workflow

I try to realize the following workflow with Spring Integration:
1) Poll REST API
2) store the POJO in Cassandra cluster
It's my first try with Spring Integration, so I'm still a bit overwhelmed about the mass of information from the reference. After some research, I could make the following work.
1) Poll REST API
2) Transform mapped POJO JSON result into a string
3) save string into file
Here's the code:
#Configuration
public class ConsulIntegrationConfig {
#InboundChannelAdapter(value = "consulHttp", poller = #Poller(maxMessagesPerPoll = "1", fixedDelay = "1000"))
public String consulAgentPoller() {
return "";
}
#Bean
public MessageChannel consulHttp() {
return MessageChannels.direct("consulHttp").get();
}
#Bean
#ServiceActivator(inputChannel = "consulHttp")
MessageHandler consulAgentHandler() {
final HttpRequestExecutingMessageHandler handler =
new HttpRequestExecutingMessageHandler("http://localhost:8500/v1/agent/self");
handler.setExpectedResponseType(AgentSelfResult.class);
handler.setOutputChannelName("consulAgentSelfChannel");
LOG.info("Created bean'consulAgentHandler'");
return handler;
}
#Bean
public MessageChannel consulAgentSelfChannel() {
return MessageChannels.direct("consulAgentSelfChannel").get();
}
#Bean
public MessageChannel consulAgentSelfFileChannel() {
return MessageChannels.direct("consulAgentSelfFileChannel").get();
}
#Bean
#ServiceActivator(inputChannel = "consulAgentSelfFileChannel")
MessageHandler consulAgentFileHandler() {
final Expression directoryExpression = new SpelExpressionParser().parseExpression("'./'");
final FileWritingMessageHandler handler = new FileWritingMessageHandler(directoryExpression);
handler.setFileNameGenerator(message -> "../../agent_self.txt");
handler.setFileExistsMode(FileExistsMode.APPEND);
handler.setCharset("UTF-8");
handler.setExpectReply(false);
return handler;
}
}
#Component
public final class ConsulAgentTransformer {
#Transformer(inputChannel = "consulAgentSelfChannel", outputChannel = "consulAgentSelfFileChannel")
public String transform(final AgentSelfResult json) throws IOException {
final String result = new StringBuilder(json.toString()).append("\n").toString();
return result;
}
This works fine!
But now, instead of writing the object to a file, I want to store it in a Cassandra cluster with spring-data-cassandra. For that, I commented out the file handler in the config file, return the POJO in transformer and created the following, :
#MessagingGateway(name = "consulCassandraGateway", defaultRequestChannel = "consulAgentSelfFileChannel")
public interface CassandraStorageService {
#Gateway(requestChannel="consulAgentSelfFileChannel")
void store(AgentSelfResult agentSelfResult);
}
#Component
public final class CassandraStorageServiceImpl implements CassandraStorageService {
#Override
public void store(AgentSelfResult agentSelfResult) {
//use spring-data-cassandra repository to store
LOG.info("Received 'AgentSelfResult': {} in Cassandra cluster...");
LOG.info("Trying to store 'AgentSelfResult' in Cassandra cluster...");
}
}
But this seems to be a wrong approach, the service method is never triggered.
So my question is, what would be a correct approach for my usecase? Do I have to implement the MessageHandler interface in my service component, and use a #ServiceActivator in my config. Or is there something missing in my current "gateway-approach"?? Or maybe there is another solution, that I'm not able to see..
Like mentioned before, I'm new to SI, so this may be a stupid question...
Nevertheless, thanks a lot in advance!
It's not clear how you are wiring in your CassandraStorageService bean.
The Spring Integration Cassandra Extension Project has a message-handler implementation.
The Cassandra Sink in spring-cloud-stream-modules uses it with Java configuration so you can use that as an example.
So I finally made it work. All I needed to do was
#Component
public final class CassandraStorageServiceImpl implements CassandraStorageService {
#ServiceActivator(inputChannel="consulAgentSelfFileChannel")
#Override
public void store(AgentSelfResult agentSelfResult) {
//use spring-data-cassandra repository to store
LOG.info("Received 'AgentSelfResult': {}...");
LOG.info("Trying to store 'AgentSelfResult' in Cassandra cluster...");
}
}
The CassandraMessageHandler and the spring-cloud-streaming seemed to be a to big overhead to my use case, and I didn't really understand yet... And with this solution, I keep control over what happens in my spring component.

How can I instantiate OWIN IDataProtectionProvider in Azure Web Jobs?

I need an instance of IDataProtectionProvider to generate email confirmation tokens using the Identity Framework UserManager in an Azure Web Jobs worker:
var confirmToken = await UserManager.GenerateEmailConfirmationTokenAsync(user.Id);
This crashes because a null IUserTokenProvider<User, int> was passed to the UserManager<User, int> upon constuction.
In the MVC application an instance is created like this:
public class OWINStartup
{
public void Configuration(IAppBuilder app)
{
var dataProtectionProvider = app.GetDataProtectionProvider();
But of course, Azure Web Jobs doesn't have an OWINStartup hook. Any advice?
Taking a look at the Katana source code for the OWIN startup context you can see the default implementation of the DataProtectionProvider is a MachineKeyDataProtectionProvider. Unfortunately this class is not exposed to us, only the DpapiDataProtectionProvider which will not work when hosted in azure.
You can find the implementation of the MachineKeyDataProtectionProvider here. You will need to also implement your own MachineKeyDataProtector as seen here. These are not difficult implmentations and are essentially wrappers around MachineKey.Protect() and MachineKey.Unprotect().
The implementation for MachineKeyDataProtectionProvider and MachineKeyDataProtector from the Katana project source (apache 2.0 license):
internal class MachineKeyProtectionProvider : IDataProtectionProvider
{
public IDataProtector Create(params string[] purposes)
{
return new MachineKeyDataProtector(purposes);
}
}
internal class MachineKeyDataProtector : IDataProtector
{
private readonly string[] _purposes;
public MachineKeyDataProtector(string[] purposes)
{
_purposes = purposes;
}
public byte[] Protect(byte[] userData)
{
return MachineKey.Protect(userData, _purposes);
}
public byte[] Unprotect(byte[] protectedData)
{
return MachineKey.Unprotect(protectedData, _purposes);
}
}
Once you have that implemented it is easy to plug into the UserManager:
var usermanager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>());
var machineKeyProtectionProvider = new MachineKeyProtectionProvider();
usermanager.UserTokenProvider = new DataProtectorTokenProvider<ApplicationUser>(machineKeyProtectionProvider.Create("ASP.NET Identity"));
Hope that helps get you in the right direction.

Resources