Feign Client and Spring-data-rest (HAL): Howto navigate to linked (`_links`) resorces? - spring-data-rest

finally after extensive stack-overflowing ;-) and debugging I made it work:
My Feign-client can make requests on Spring-Data-Rest's API and I get a Resource<Something> with filled links back.
My code so far...
The FeignClient:
#FeignClient(name = "serviceclient-hateoas",
url = "${service.url}",
decode404 = true,
path = "${service.basepath:/api/v1}",
configuration = MyFeignHateoasClientConfig.class)
public interface MyFeignHateoasClient {
#RequestMapping(method = RequestMethod.GET, path = "/bookings/search/findByBookingUuid?bookingUuid={uuid}")
Resource<Booking> getBookingByUuid(#PathVariable("uuid") String uuid);
}
The client-config:
#Configuration
public class MyFeignHateoasClientConfig{
#Value("${service.user.name:bla}")
private String serviceUser;
#Value("${service.user.password:blub}")
private String servicePassword;
#Bean
public BasicAuthRequestInterceptor basicAuth() {
return new BasicAuthRequestInterceptor(serviceUser, servicePassword);
}
#Bean
public Decoder decoder() {
return new JacksonDecoder(getObjectMapper());
}
#Bean
public Encoder encoder() {
return new JacksonEncoder(getObjectMapper());
}
public ObjectMapper getObjectMapper() {
return new ObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.registerModule(new Jackson2HalModule());
}
#Bean
public Logger logger() {
return new Slf4jLogger(MyFeignHateoasClient.class);
}
#Bean
public Logger.Level logLevel() {
return Logger.Level.FULL;
}
}
And in the application using the client via an jar-dependency:
#SpringBootApplication
#EnableAutoConfiguration
#EnableFeignClients(basePackageClasses=MyFeignHateoasClient.class)
#EnableHypermediaSupport(type = EnableHypermediaSupport.HypermediaType.HAL)
#ComponentScan(excludeFilters = #Filter(type = ... ), basePackageClasses= {....class}, basePackages="...")
public class Application {
...
Now this is working:
#Autowired
private MyFeignHateoasClient serviceClient;
...
void test() {
Resource<Booking> booking = serviceClient.getBookingByUuid(id);
Link link = booking.getLink("relation-name");
}
Now my question:
How do I go on from here, i.e. navigate to the resource in the Link?
The Link is containing an URL on the resource I want to request.
Do I really have to parse the ID out of the URL and add a method to the FeignClient like getRelationById(id)
Is there at least a way to pass the complete resource-url to a method of a FeignClient?
I have found no examples which demonstrate how to proceed from here (despite the POST/modify). Any hints appreciated!
Thx

My current solution:
I added an additional request in the Feign client, taking the whole resource path:
...
public interface MyFeignHateoasClient {
...
#RequestMapping(method = RequestMethod.GET, path = "{resource}")
Resource<MyLinkedEntity> getMyEntityByResource(#PathVariable("resource") String resource);
}
Then I implemented some kind of "HAL-Tool":
...
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import org.springframework.hateoas.Link;
import feign.Target;
import lombok.SneakyThrows;
public class HalTool {
private Object feignClient;
public static HalTool forClient( Object feignClient ) {
return new HalTool(feignClient);
}
private HalTool( Object feignClient ) {
this.feignClient = feignClient;
}
#SneakyThrows
private String getUrl() {
InvocationHandler invocationHandler = Proxy.getInvocationHandler(feignClient);
Field target = invocationHandler.getClass().getDeclaredField("target");
target.setAccessible(true);
Target<?> value = (Target<?>) target.get(invocationHandler);
return value.url();
}
public String toPath( Link link ) {
String href = link.getHref();
String url = getUrl();
int idx = href.indexOf(url);
if (idx >= 0 ) {
idx += url.length();
}
return href.substring(idx);
}
}
And then I could do request a linked resource like this:
Link link = booking.getLink("relation-name");
Resource<MyLinkedEntity> entity = serviceClient.getMyEntityByResource(
HalTool.forClient(serviceClient).toPath(link));

Related

how to mock and test inside/outside get and set methods?

I dont know why but Im always getting NullPointer and no idea why and how exactly this test should looks like. Its about method: webServiceTemplate():
#Configuration
public class ErdConfiguration {
#Autowired
private EJwtProperties eJwtProperties;
public Jaxb2Marshaller marshaller() {
Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
// this package must match the package in the <generatePackage> specified in pom.xml
marshaller.setContextPath("erdUserRoles.wsdl");
return marshaller;
}
public WebServiceTemplate webServiceTemplate() {
WebServiceTemplate webServiceTemplate = new WebServiceTemplate();
webServiceTemplate.setMarshaller(marshaller());
webServiceTemplate.setUnmarshaller(marshaller());
webServiceTemplate.setDefaultUri(eJwtProperties.getRoles().getErdServiceUri());
return webServiceTemplate;
}
}
and EJwtProperties class which it uses:
public class EJwtProperties {
private Map<String, String> claims = new HashMap<>();
private String signingKey;
private SourceTokenConfig sourceToken = new SourceTokenConfig();
private RolesConfig roles = new RolesConfig();
private List<String> generateEjwtRoles = Collections.emptyList();
private boolean cacheDisabled = false;
#Data
public static class SourceTokenConfig {
private boolean embedSourceToken = false;
private String embeddedTokenClaimName = "source-token";
}
#Data
public static class RolesConfig {
private boolean rolesEnabled = false;
private String rolesClaimName = "roles";
private String erdAppId;
private String erdServiceUri;
}
}
My code so far looks like this and got null pointer while Im trying to check getRoles() in when-thenReturn :
#Mock
private EJwtProperties eJwtProperties;
#InjectMocks
private ErdConfiguration underTest;
Jaxb2Marshaller marshaller;
#BeforeEach
void setUp() {
MockitoAnnotations.openMocks(this);
}
#Test
void webServiceTemplateTest() {
EJwtProperties.RolesConfig roles = new EJwtProperties.RolesConfig();
roles.setErdServiceUri("testErdServiceUri");
eJwtProperties.setRoles(roles);
underTest = new ErdConfiguration();
when(eJwtProperties.getRoles()).thenReturn(roles); //this one passed
when(eJwtProperties.getRoles().getErdServiceUri()).thenReturn(roles.getErdServiceUri()); //here nullPointer
// underTest.webServiceTemplate(); //this is what I was planning to do next
//assertEquals(underTest.webServiceTemplate(), eJwtProperties.getRoles().getErdServiceUri()); //or this
// assertEquals(marshaller, underTest.webServiceTemplate().getMarshaller());
// assertEquals(marshaller, underTest.webServiceTemplate().getUnmarshaller());
}
}
Please keep in mind that I'm still learning tests. Id be thankful for any help. How the hack it should looks like? What am I missing that it return null ? Should I initialize whole properties??

I am using TestRestTemplate to Test with #RequestParam value how to execute

//how to send #RequestParam value to url
enter code here#ApiRestController
public class CityController extends BaseController{
#GetMapping("/cities")
public ResponseEntity<CitiesResponse> getAll(
#RequestParam(value = "pageNumber", defaultValue = "1") int pageNumber,
#RequestParam(value = "pageSize", defaultValue = "100") int pageSize,
#RequestParam(value = "sortBy", defaultValue = "id", required = false) String sortBy,
#RequestParam(value = "sortDirection", defaultValue = "asc", required = false) String sortDirection,
#RequestParam(value = "search", required = false) String search) {
return new ResponseEntity(cityService.getAll(pageNumber, pageSize, sortBy, sortDirection, search), HttpStatus.OK);
}
}
To easily manipulate URLs / path / params / etc., you can use Spring's UriComponentsBuilder class. It's cleaner that manually concatenating strings and it takes care of the URL encoding for you:
UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(url)
.queryParam("pageNumber", 1)
.queryParam("pageSize", 10)
.queryParam("sortBy", "id")
.queryParam("sortDirection", "desc")
.queryParam("search", "hello search");
HttpEntity<?> entity = new HttpEntity<>(headers); //Update this as per your code
HttpEntity<String> response = restTemplate.exchange(
builder.build().encode().toUri(),
HttpMethod.GET,
entity,
String.class);
There are different ways to test in spring boot. Check the samples below:
First option:
It's more like an integration test. In this case the port will be the default 8080
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
public class DemoApplicationTests {
private TestRestTemplate restTemplate = new TestRestTemplate();
#Test
public void contextLoads() {
String url = "http://localhost:8080";
URI uri = UriComponentsBuilder.fromHttpUrl(url).path("/books")
.queryParam("order", "asc").build().toUri();
this.restTemplate.getForEntity(uri, Void.class);
}
}
Second option:
Very similar to the first option but this time it will run in an random port which can be capture by #LocalServerPort
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class DemoApplicationTests {
#LocalServerPort
private int port;
private TestRestTemplate restTemplate = new TestRestTemplate();
#Test
public void contextLoads() {
String url = "http://localhost:" + this.port;
URI uri = UriComponentsBuilder.fromHttpUrl(url).path("/books")
.queryParam("order", "asc").build().toUri();
this.restTemplate.getForEntity(uri, Void.class);
}
}
UriComponentsBuilder has been used to build the uri in a very friendly way.
Third option:
This option doesn't involve TestRestTemplate but just involve the RestController by itself. Any dependency inside the controller should be mark with #MockBean in the test.
#RunWith(SpringRunner.class)
#WebMvcTest(BookRestController.class)
public class DemoApplicationTests {
#Autowired
private MockMvc mvc;
#Test
public void contextLoads() throws Exception {
this.mvc.perform(MockMvcRequestBuilders.get("/books")
.param("order", "asc"))
.andExpect(MockMvcResultMatchers.status().isOk());
}
}

Entity not posting to restful service

I'm having trouble posting a request to a restfull service.
It looks like my Entity is not getting converted into json correctly.
I get a 400 Bad request response.
I suspect it's the List of DateTimeRange objects causing the issue - as I have a very similar request that works but all the pojos properties are Strings.
Do I need to annotate my Entity to enable marshalling to/from json for Lists and my custom DateTimeRange?
String actionUrl = buildUrl( myResftullUrlTest );
Client client = ClientBuilder.newClient().register( JacksonJsonProvider.class );
Builder target = client.target( actionUrl ).request();
CreateWebinarRequest createWebinarRequest = new CreateWebinarRequest();
createWebinarRequest.setDescription("Test1 desc");
createWebinarRequest.setSubject("Test1 subject")
createWebinarRequest.setTimeZone("Europe/Dublin")
List<DateTimeRange> dateTimeRangeParam = new ArrayList<DateTimeRange>();
DateTimeRange dateTimeRange = new DateTimeRange();
dateTimeRange.setStartTime( "2016-11-03T08:34:12" );
dateTimeRange.setEndTime( "2016-11-03T09:34:12" );
dateTimeRangeParam.add( dateTimeRange );
createWebinarRequest.setTimes( dateTimeRangeParam );
Response response = null;
switch ( goToTrainingRequestData.getRequestType() ) {
case HTTP_POST :
response = target.buildPost( Entity.json(createWebinarRequest) ).invoke();
...
}
}
CreateWebinarRequest:
public class CreateWebinarRequest implements Serializable {
private String subject = null;
private String description = null;
private List<DateTimeRange> times = new ArrayList<DateTimeRange>();
private String timeZone = null;
public String getSubject() {
return subject;
}
public void setSubject( String subject ) {
this.subject = subject;
}
public String getDescription() {
return description;
}
public void setDescription( String description ) {
this.description = description;
}
public List<DateTimeRange> getTimes() {
return times;
}
public void setTimes( List<DateTimeRange> times ) {
this.times = times;
}
public String getTimeZone() {
return timeZone;
}
public void setTimeZone( String timeZone ) {
this.timeZone = timeZone;
}
}
DateTimeRange:
public class DateTimeRange {
private String startTime = null;
private String endTime = null;
public String getStartTime() {
return startTime;
}
public void setStartTime( String startTime ) {
this.startTime = startTime;
}
public String getEndTime() {
return endTime;
}
public void setEndTime( String endTime ) {
this.endTime = endTime;
}
}
Problem solved guys and gals!
Nothing wrong with any data marshalling, I got the detailed response from server with:
String error = response.readEntity( String.class );
And it was a boo boo on my behalf!
{"errorCode":"RequestValidationError","description":"Request contains invalid parameters. See validationErrorCodes for specific errors.","Details":"times cannot be in past","incident":"6100682426566883853","validationErrorCodes":[{"code":"requestStartTimeInPast","description":"times cannot be in past"}]}

Trying to use PlaceRequest the right way

i have two Presenters: A DevicePresenter and a ContainerPresenter. I place a PlaceRequest in the DevicePresenter to call the ContainerPresenter with some parameters like this:
PlaceRequest request = new PlaceRequest.Builder()
.nameToken("containersPage")
.with("action","editContainer")
.with("containerEditId", selectedContainerDto.getUuid().toString())
.build();
placeManager.revealPlace(request);
In my ContainersPresenter i have this overridden method:
#Override
public void prepareFromRequest(PlaceRequest placeRequest) {
Log.debug("prepareFromRequest in ContainersPresenter");
super.prepareFromRequest(placeRequest);
String actionString = placeRequest.getParameter("action", "");
String id;
//TODO: Should we change that to really retrieve the object from the server? Or should we introduce a model that keeps all values and inject that into all presenters?
if (actionString.equals("editContainer")) {
try {
id = placeRequest.getParameter("id", null);
for(ContainerDto cont : containerList) {
Log.debug("Compare " + id + " with " + cont.getUuid());
if(id.equals(cont.getUuid())) {
containerDialog.setCurrentContainerDTO(new ContainerDto());
addToPopupSlot(containerDialog);
break;
}
}
} catch (NumberFormatException e) {
Log.debug("id cannot be retrieved from URL");
}
}
}
But when revealPlace is called, the URL in the browser stays the same and the default presenter (Home) is shown instead.
When i print the request, it seems to be fine:
PlaceRequest(nameToken=containersPage, params={action=editContainer, containerEditId=8fa5f730-fe0f-11e3-a3ac-0800200c9a66})
And my NameTokens are like this:
public class NameTokens {
public static final String homePage = "!homePage";
public static final String containersPage = "!containersPage";
public static final String devicesPage = "!devicesPage";
public static String getHomePage() {
return homePage;
}
public static String getDevicesPage() {
return devicesPage;
}
public static String getContainersPage() {
return containersPage;
}
}
What did i miss? Thanks!
In your original code, when constructing your PlaceRequest, you forgot the '!' at the beginning of your nametoken.
.nameToken("containersPage")
while your NameTokens entry is
public static final String containersPage = "!containersPage";
As you noted, referencing the constant in NameTokens is less prone to such easy mistakes to make!
Sometimes the problem exists "between the ears". If i avoid strings but use the proper symbol from NameTokens like
PlaceRequest request = new PlaceRequest.Builder()
.nameToken(NameTokens.containersPage)
.with("action","editContainer")
.with("containerEditId", selectedContainerDto.getUuid().toString())
.build();
it works just fine. Sorry!

Can I use RE-Captcha with Wicket?

Can I use recaptcha with apache wicket 1.5.3? Is there some good example?
In terms of Google reCAPTCHA v2, you can just follow its instruction, which is straightforward.
First of all, go to Google reCAPTCHA, and register your application there. Then you can work on the client and server sides respectively as below:
On the client side (see ref)
First, paste the snippet below <script...></script> before the closing tag on your HTML template, for example:
<script src='https://www.google.com/recaptcha/api.js'></script>
</head>
Then paste the snippet below <div...></div> at the end of the where you want the reCAPTCHA widget to appear, for example:
<div class="g-recaptcha" data-sitekey="{your public site key given by Google reCAPTCHA}"></div>
</form>
That's all on the client side.
On the server side (see ref)
When a user submits the form, you need to get the user response token from the g-recaptcha-response POST parameter. Then use the token, together with the secret key given by Google reCAPTCHA, and optional with the user's IP address, and then POST a request to the Google reCAPTCHA API. You'll then get the response from Google reCAPTHA, indicating whether the form verification succeeds or fails.
Below is the sample code on the server side.
User summits a Wicket form (Wicket 6 in this example):
protected void onSubmit() {
HttpServletRequest httpServletRequest = (HttpServletRequest)getRequest().getContainerRequest();
boolean isValidRecaptcha = ReCaptchaV2.getInstance().verify(httpServletRequest);
if(!isValidRecaptcha){
verificationFailedFeedbackPanel.setVisible(true);
return;
}
// reCAPTCHA verification succeeded, carry on handling form submission
...
}
ReCaptchaV2.java (Just Java, web framework independent)
import javax.servlet.http.HttpServletRequest;
import org.apache.log4j.Logger;
import org.codehaus.jackson.map.ObjectMapper;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
public class ReCaptchaV2 {
private final static Logger logger = Logger.getLogger(ReCaptchaV2.class);
private final static String VERIFICATION_URL = "https://www.google.com/recaptcha/api/siteverify";
private final static String SECRET = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
private static ReCaptchaV2 instance = new ReCaptchaV2();
private ReCaptchaV2() {}
public static ReCaptchaV2 getInstance() {
return instance;
}
private boolean verify(String recaptchaUserResponse, String remoteip) {
boolean ret = false;
if (recaptchaUserResponse == null) {
return ret;
}
RestTemplate rt = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
MultiValueMap<String, String> map= new LinkedMultiValueMap<String, String>();
map.add("secret", SECRET);
map.add("response", recaptchaUserResponse);
if (remoteip != null) {
map.add("remoteip", remoteip);
}
HttpEntity<MultiValueMap<String, String>> httpEntity = new HttpEntity<MultiValueMap<String, String>>(map, headers);
ResponseEntity<String> res = null;
try {
res = rt.exchange(VERIFICATION_URL, HttpMethod.POST, httpEntity, String.class);
} catch (Exception e) {
logger.error("Exception: " + e.getMessage());
}
if (res == null || res.getBody() == null) {
return ret;
}
Response response = null;
try {
response = new ObjectMapper().readValue(res.getBody(), Response.class);
} catch (Exception e) {
logger.error("Exception: " + e.getMessage());
}
if (response != null && response.isSuccess()) {
ret = true;
}
logger.info("Verification result: " + ret);
return ret;
}
public boolean verify(HttpServletRequest httpServletRequest) {
boolean ret = false;
if (httpServletRequest == null) {
return ret;
}
String recaptchaUserResponse = httpServletRequest.getParameter("g-recaptcha-response");
String remoteAddr = httpServletRequest.getRemoteAddr();
return verify(recaptchaUserResponse, remoteAddr);
}
}
Response.java (Java POJO)
public class Response {
private String challenge_ts;
private String hostname;
private boolean success;
public Response() {}
public String getChallenge_ts() {
return challenge_ts;
}
public void setChallenge_ts(String challenge_ts) {
this.challenge_ts = challenge_ts;
}
public String getHostname() {
return hostname;
}
public void setHostname(String hostname) {
this.hostname = hostname;
}
public boolean isSuccess() {
return success;
}
public void setSuccess(boolean success) {
this.success = success;
}
#Override
public String toString() {
return "ClassPojo [challenge_ts = " + challenge_ts + ", hostname = " + hostname + ", success = " + success + "]";
}
}
Have you read this?
I have added the guide here in case page disappears.
Usage
We will create a panel called RecaptchaPanel. In order to use this component to your application all you'll have to do is this:
add(new RecaptchaPanel("recaptcha"));
and of course, add the component in your markup:
<div wicket:id="recaptcha"></div>
Implementation
Implementation is simple. All you have to do, is to follow several steps:
Add recaptcha dependency to your project
<dependency>
<groupid>net.tanesha.recaptcha4j</groupid>
<artifactid>recaptcha4j</artifactid>
<version>0.0.7</version>
</dependency>
This library hides the implementation details and expose an API for dealing with recaptcha service.
Create associated markup (RecaptchaPanel.html)
<wicket:panel><div wicket:id="captcha"></div></wicket:panel>
Create RecaptchaPanel.java
import net.tanesha.recaptcha.ReCaptcha;
import net.tanesha.recaptcha.ReCaptchaFactory;
import net.tanesha.recaptcha.ReCaptchaImpl;
import net.tanesha.recaptcha.ReCaptchaResponse;
/**
* Displays recaptcha widget. It is configured using a pair of public/private keys which can be registered at the
* following location:
*
* https://www.google.com/recaptcha/admin/create
* <br>
* More details about recaptcha API: http://code.google.com/apis/recaptcha/intro.html
*
* #author Alex Objelean
*/
#SuppressWarnings("serial")
public class RecaptchaPanel extends Panel {
private static final Logger LOG = LoggerFactory.getLogger(RecaptchaPanel.class);
#SpringBean
private ServiceProvider serviceProvider;
public RecaptchaPanel(final String id) {
super(id);
final ReCaptcha recaptcha = ReCaptchaFactory.newReCaptcha(serviceProvider.getSettings().getRecaptchaPublicKey(),
serviceProvider.getSettings().getRecaptchaPrivateKey(), false);
add(new FormComponent<void>("captcha") {
#Override
protected void onComponentTagBody(final MarkupStream markupStream, final ComponentTag openTag) {
replaceComponentTagBody(markupStream, openTag, recaptcha.createRecaptchaHtml(null, null));
}
#Override
public void validate() {
final WebRequest request = (WebRequest)RequestCycle.get().getRequest();
final String remoteAddr = request.getHttpServletRequest().getRemoteAddr();
final ReCaptchaImpl reCaptcha = new ReCaptchaImpl();
reCaptcha.setPrivateKey(serviceProvider.getSettings().getRecaptchaPrivateKey());
final String challenge = request.getParameter("recaptcha_challenge_field");
final String uresponse = request.getParameter("recaptcha_response_field");
final ReCaptchaResponse reCaptchaResponse = reCaptcha.checkAnswer(remoteAddr, challenge, uresponse);
if (!reCaptchaResponse.isValid()) {
LOG.debug("wrong captcha");
error("Invalid captcha!");
}
}
});
}
}
</void>
Things to notice:
ServiceProvider - is a spring bean containing reCaptcha configurations (public key and private key). These keys are different depending on the domain where your application is deployed (by default works for any key when using localhost domain). You can generate keys here: https://www.google.com/recaptcha/admin/create
The RecaptchaPanel contains a FormComponent, which allows implementing validate method, containing the validation logic.
Because reCaptcha use hardcoded values for hidden fields, this component cannot have multiple independent instances on the same page.
Maybe the xaloon wicket components can be a solution for you. They have a Recaptcha plugin.