//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());
}
}
Related
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'm trying to serialize an object (Root), with some duplicated entries of MyObject. Just want store the whole objects one, I'm using #JsonIdentityReference, which works pretty well.
However, I realize that it will generate un-deserializable object, if there're equal objects with different reference. I wonder if there's a configuration in Jackson to change this behavior, thanks!
#Value
#AllArgsConstructor
#NoArgsConstructor(force = true)
class Root {
private List<MyObject> allObjects;
private Map<String, MyObject> objectMap;
}
#Value
#AllArgsConstructor
#NoArgsConstructor(force = true)
#JsonIdentityReference
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
class MyObject {
private String id;
private int value;
}
public class Main {
public static void main() throws JsonProcessingException {
// Constructing equal objects
val obj1 = new MyObject("a", 1);
val obj2 = new MyObject("a", 1);
assert obj1.equals(obj2);
val root = new Root(
Lists.newArrayList(obj1),
ImmutableMap.of(
"lorem", obj2
)
);
val objectMapper = new ObjectMapper();
val json = objectMapper.writeValueAsString(root);
// {"allObjects":[{"id":"a","value":1}],"objectMap":{"lorem":{"id":"a","value":1}}}
// Note here both obj1 and obj2 are expanded.
// Exception: Already had POJO for id
val deserialized = objectMapper.readValue(json, Root.class);
assert root.equals(deserialized);
}
}
I'm using Jackson 2.10.
Full stacktrace:
Exception in thread "main" com.fasterxml.jackson.databind.JsonMappingException: Already had POJO for id (java.lang.String) [[ObjectId: key=a, type=com.fasterxml.jackson.databind.deser.impl.PropertyBasedObjectIdGenerator, scope=java.lang.Object]] (through reference chain: Root["objectMap"]->java.util.LinkedHashMap["lorem"]->MyObject["id"])
at com.fasterxml.jackson.databind.JsonMappingException.wrapWithPath(JsonMappingException.java:394)
at com.fasterxml.jackson.databind.JsonMappingException.wrapWithPath(JsonMappingException.java:353)
at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.wrapAndThrow(BeanDeserializerBase.java:1714)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:371)
at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeWithObjectId(BeanDeserializerBase.java:1257)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:157)
at com.fasterxml.jackson.databind.deser.std.MapDeserializer._readAndBindStringKeyMap(MapDeserializer.java:527)
at com.fasterxml.jackson.databind.deser.std.MapDeserializer.deserialize(MapDeserializer.java:364)
at com.fasterxml.jackson.databind.deser.std.MapDeserializer.deserialize(MapDeserializer.java:29)
at com.fasterxml.jackson.databind.deser.impl.FieldProperty.deserializeAndSet(FieldProperty.java:138)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:288)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:151)
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4202)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3205)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3173)
at Main.main(Main.java:53)
Caused by: java.lang.IllegalStateException: Already had POJO for id (java.lang.String) [[ObjectId: key=a, type=com.fasterxml.jackson.databind.deser.impl.PropertyBasedObjectIdGenerator, scope=java.lang.Object]]
at com.fasterxml.jackson.annotation.SimpleObjectIdResolver.bindItem(SimpleObjectIdResolver.java:24)
at com.fasterxml.jackson.databind.deser.impl.ReadableObjectId.bindItem(ReadableObjectId.java:57)
at com.fasterxml.jackson.databind.deser.impl.ObjectIdValueProperty.deserializeSetAndReturn(ObjectIdValueProperty.java:101)
at com.fasterxml.jackson.databind.deser.impl.ObjectIdValueProperty.deserializeAndSet(ObjectIdValueProperty.java:83)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:369)
... 14 more
As I mentioned earlier, this setup only works if obj1 == obj2, as the two objects with same ID should be identity-equal. In that case, the second object would also net get expanded during serialization (alwaysAsId = false only expands the first object).
However, if you want to have this setup and are fine with the serialization, you could use a custom Resolver for deserialization that stores a single instance per key:
#JsonIdentityReference(alwaysAsId = false)
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id", resolver = CustomScopeResolver.class)
static class MyObject {
private String id;
// ...
}
class CustomScopeResolver implements ObjectIdResolver {
Map<String, MyObject> data = new HashMap<>();
#Override
public void bindItem(final IdKey id, final Object pojo) {
data.put(id.key.toString(), (MyObject) pojo);
}
#Override
public Object resolveId(final IdKey id) {
return data.get(id.key);
}
#Override
public ObjectIdResolver newForDeserialization(final Object context) {
return new CustomScopeResolver();
}
#Override
public boolean canUseFor(final ObjectIdResolver resolverType) {
return false;
}
}
NEW EDIT: Apparently, its very easy: Just turn on objectMapper.configure(SerializationFeature.USE_EQUALITY_FOR_OBJECT_ID, true); so that the DefaultSerializerProvider uses a regular Hashmap instead of an IdentityHashMap to manage the serialized beans.
DEPRECATED: Update for Serialization: It is possible to achieve this by adding a custom SerializationProvider:
class CustomEqualObjectsSerializerProvider extends DefaultSerializerProvider {
private final Collection<MyObject> data = new HashSet<>();
private final SerializerProvider src;
private final SerializationConfig config;
private final SerializerFactory f;
public CustomEqualObjectsSerializerProvider(
final SerializerProvider src,
final SerializationConfig config,
final SerializerFactory f) {
super(src, config, f);
this.src = src;
this.config = config;
this.f = f;
}
#Override
public DefaultSerializerProvider createInstance(final SerializationConfig config, final SerializerFactory jsf) {
return new CustomEqualObjectsSerializerProvider(src, this.config, f);
}
#Override
public WritableObjectId findObjectId(final Object forPojo, final ObjectIdGenerator<?> generatorType) {
// check if there is an equivalent pojo, use it if exists
final Optional<MyObject> equivalentObject = data.stream()
.filter(forPojo::equals)
.findFirst();
if (equivalentObject.isPresent()) {
return super.findObjectId(equivalentObject.get(), generatorType);
} else {
if (forPojo instanceof MyObject) {
data.add((MyObject) forPojo);
}
return super.findObjectId(forPojo, generatorType);
}
}
}
#Test
public void main() throws IOException {
// Constructing equal objects
final MyObject obj1 = new MyObject();
obj1.setId("a");
final MyObject obj2 = new MyObject();
obj2.setId("a");
assert obj1.equals(obj2);
final Root root = new Root();
root.setAllObjects(Collections.singletonList(obj1));
root.setObjectMap(Collections.singletonMap(
"lorem", obj2));
final ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setSerializerProvider(
new CustomEqualObjectsSerializerProvider(
objectMapper.getSerializerProvider(),
objectMapper.getSerializationConfig(),
objectMapper.getSerializerFactory()));
final String json = objectMapper.writeValueAsString(root);
System.out.println(json); // second object is not expanded!
}
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));
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"}]}
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!