STOMP header on #MessageMapping return - java

In my Spring Boot 1.5 application with Spring Websocket, I'd like to set a custom STOMP header on the return value of a #MessageMapping method, but I don't know how to do this. For example:
#Controller
public class ChannelController {
#MessageMapping("/books/{id}")
public Book receive(#DestinationVariable("id") Long bookId) {
return findBook(bookId);
}
private Book findBook(Long bookId) {
return //...
}
}
When receive is triggered from a client's STOMP SEND, I'd like the STOMP MESSAGE reply frame with the book body to have a custom header: message-type:BOOK like this:
MESSAGE
message-type:BOOK
destination:/topic/books/1
content-type:application/json;charset=UTF-8
subscription:sub-0
message-id:0-7
content-length:1868
{
"createdDate" : "2017-08-10T10:40:39.256",
"lastModifiedDate" : "2017-08-10T10:42:57.976",
"id" : 1,
"name" : "The big book",
"description" : null
}
^#
How do I set a STOMP header for the reply return value in a #MessageMapping?

If the return value signature is not important, you can use SimpMessagingTemplate as #Shchipunov noted in the comments to his answer:
#Controller
#AllArgsConstructor
public class ChannelController {
private final SimpMessagingTemplate messagingTemplate;
#MessageMapping("/books/{id}")
public void receive(#DestinationVariable("id") Long bookId, SimpMessageHeaderAccessor accessor ) {
accessor.setHeader("message-type", "BOOK");
messagingTemplate.convertAndSend(
"/topic/books/" + bookId, findBook(bookId), accessor.toMap()
);
}
private Book findBook(Long bookId) {
return //...
}
}
which does correctly serialize to the MESSAGE frame in the question.

You can try this solution:
#MessageMapping("/books/{id}")
public GenericMessage<Book> receive(#DestinationVariable("id") Long bookId) {
Map<String, List<String>> nativeHeaders = new HashMap<>();
nativeHeaders.put("message-type", Collections.singletonList("BOOK"));
Map<String, Object> headers = new HashMap<>();
headers.put(NativeMessageHeaderAccessor.NATIVE_HEADERS, nativeHeaders);
return new GenericMessage<Book>(findBook(bookId), headers);
}

Related

Is there a way to get a list of all jersey urls a user, based on their role and #RolesAllowed, has access to?

I am hoping to offer a rest api call to clients(via plain jersey, not spring) to return a list of all endpoints allowed for the specific user based on the JWT they send in the header. I have found on stackoverflow(thanks contributors!) example code to get all endpoints, regardless of role, but not the subset based on role. I have found how to get the annotations per method as well, but would like to avoid re-inventing the wheel of "if #PermitAll and not #DenyAll, or role in RolesAllowed, etc...".
Any chance Jersey 2.0 has a a method I can call that will resolve to true/false given SecurityContext and url endpoint or method?
boolean allowed = isMethodAllowed(SecurityContext ctx, String url);
Or
boolean allowed = isMethodAllowed(SecurityContext ctx, Class method);
Thanks to this post: Listing all deployed rest endpoints (spring-boot, jersey)
Specifically Johanne Jander's post(thank you!), I've come up with the below code which seems to work in my case, a simple jersey use case. Providing it here in case it's useful to others.
#Path("/v1/userinterface")
#Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
public class MyUI extends MyRestApi {
private final static Logger log = LoggerFactory.getLogger(MyUI.class);
#Context
private Configuration configuration;
#Context
private SecurityContext security;
#Path("/allowedendpoints")
#GET
#Operation(summary = "List API access points allowed for the currently authenticated user", tags = {
"ui" }, description = "Returns a list of urls", responses = {})
public Response showAll(#Context UriInfo ui) {
log.debug("Get list of all allowed endpoints for user: " + security.getUserPrincipal().getName());
HashMap<String, ArrayList<String>> map = new HashMap<String, ArrayList<String>>();
for (Class<?> c : configuration.getClasses()) {
// Since all of my endpoint classes extend MyRestApi,
// only scan them, not all classes
if (MyRestApi.class.isAssignableFrom(c)) {
scanClass(c, map);
}
}
return Response.ok().entity(map).build();
}
public void scanClass(Class<?> baseClass, HashMap<String, ArrayList<String>> map) {
Builder builder = Resource.builder(baseClass);
if (null != builder) {
Resource resource = builder.build();
String uriPrefix = "";
process(uriPrefix, resource, map);
}
}
private void process(String uriPrefix, Resource resource, HashMap<String, ArrayList<String>> map) {
// recursive method
String pathPrefix = uriPrefix;
List<Resource> resources = new ArrayList<>();
resources.addAll(resource.getChildResources());
if (resource.getPath() != null) {
pathPrefix = (pathPrefix + "/" + resource.getPath()).replaceAll("//", "/");
}
for (ResourceMethod method : resource.getAllMethods()) {
if (method.getType().equals(ResourceMethod.JaxrsType.SUB_RESOURCE_LOCATOR)) {
resources.add(Resource
.from(resource.getResourceLocator().getInvocable().getDefinitionMethod().getReturnType()));
} else {
if (isPathAllowed(security, method.getInvocable().getDefinitionMethod())) {
if (map.containsKey(pathPrefix))
map.get(pathPrefix).add(method.getHttpMethod());
else
map.put(pathPrefix, new ArrayList<String>(Arrays.asList(method.getHttpMethod())));
}
}
}
for (Resource childResource : resources) {
process(pathPrefix, childResource, map);
}
}
public boolean isPathAllowed(SecurityContext ctx, Method method) {
// #DenyAll on the method takes precedence over #RolesAllowed and #PermitAll
if (method.isAnnotationPresent(DenyAll.class)) {
return (false);
}
// #RolesAllowed on the method takes precedence over #PermitAll
RolesAllowed rolesAllowed = method.getAnnotation(RolesAllowed.class);
if (rolesAllowed != null) {
return (hasRole(ctx, rolesAllowed.value()));
}
// #PermitAll on the method takes precedence over #RolesAllowed on the class
if (method.isAnnotationPresent(PermitAll.class)) {
return (true);
}
// #DenyAll can't be attached to classes
// #RolesAllowed on the class takes precedence over #PermitAll on the class
rolesAllowed = method.getDeclaringClass().getAnnotation(RolesAllowed.class);
if (rolesAllowed != null) {
return (hasRole(ctx, rolesAllowed.value()));
}
// #PermitAll on the class
if (method.getDeclaringClass().isAnnotationPresent(PermitAll.class)) {
return (true);
}
return (false); // default
}
private boolean hasRole(SecurityContext ctx, String[] rolesAllowed) {
for (final String role : rolesAllowed) {
if (ctx.isUserInRole(role)) {
return (true);
}
}
return (false);
}
Which returns endpoints the currently authenticated user has access to based on SecurityContext markup with the #DenyAll, #PermitAll and #RolesAllowed annotations.
Full disclosure, my app is simple with just basic class and method annotations at the endpoints. ymmv.
Sample output:
{
"/v1/resources" : [
"POST",
"GET"
],
"/v1/resources/{id}" : [
"DELETE",
"GET"
]
}

Parameters of POST request to Java server from Angular client

I've got Angular app and Java server.
I need to send POST request with JSON object consisting of string array and string field.
I'm using Angularjs $resource and Java javax.ws.rs.
My latest try as follows:
Client:
var messages = $resource('resources/messages/getmessages', {}, {
update: { method: 'POST', url: 'resources/messages/updatemessages' }
});
//...
var _args = { 'msgIdList': ['1', '2', '3'],
'action': 'makeSmth' };
return messages.update(_args).$promise.then(
function (data) {
//...
},
function (error) {
//...
}
)
Server:
#POST
#Path("updatemessages")
#Consumes(MediaType.APPLICATION_FORM_URLENCODED)
#Produces(MediaType.APPLICATION_JSON +"; charset=UTF-8")
public Response updateMessages( #FormParam("msgIdList") List<String> msgIdList,
#DefaultValue("") #FormParam("action") String action,
#CookieParam("rgsid") String c_sid,
#Context HttpServletRequest httpservletreq) {
//...
}
The problem is that I've got 415 Unsupported Media Type error, and don't know what to do next. I've tried lots of things, but may be I was wrong from the start, and I can't pass parameters this way?
Any help would be appreciated, thanks!
you can try this in your angular, maybe it can help.
var sendPost = $http({
method: "post",
url:"JAVA_SERVER_SERVICE_URL",
data: {
msgIdList: 'your_value',
action: 'your_value'
},
headers: { 'Content-Type': 'application/json' }
});
So, eventually I made a wrapper class, so now it looks this way:
#XmlRootElement
private static class RequestWrapper {
#XmlElement
private ArrayList<String> msgIdList;
#XmlElement
private String action;
public ArrayList<String> getMsgIdList() {
return msgIdList;
}
public void setMsgIdList(ArrayList<String> msgIdList) {
this.msgIdList = msgIdList;
}
public String getAction() {
return action;
}
public void setAction(String action) {
this.action = action;
}
public RequestWrapper() {
}
}
#POST
#Path("updatemessages")
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON +"; charset=UTF-8")
public Response updateMessages( RequestWrapper requestData,
#CookieParam("rgsid") String c_sid,
#Context HttpServletRequest httpservletreq) {
//...}
Angular part stays unchanged.
I'm not really sure, if this the right way to go (class description and so on), but it works.

Documenting a wrapped REST response using swagger UI

I have a WidgetDto that I have annotated with swagger UI annotations. The final response wraps a list of WidgetDtos with a layer of metadata (per page 21 of this RESTful best practices document). For example:
{
"data" : [
{
"id" : 1234,
"prop1" : "val1"
...
},
{
"id" : 5678,
"prop1" : "val2"
...
},
...
]
}
My java code looks like this:
#GET
#Produces(MediaType.APPLICATION_JSON)
#ApiOperation(
value = "Get all widgets.",
response = WidgetDto.class
)
#ApiResponses(value = {
#ApiResponse(code = 200, message = "Returns the list of widgets.")
})
public Response getWidgets() {
List<WidgetDto> widgets;
...
Map<String, Object> responseBody = new HashMap<>();
responseBody.put("data", widgets);
return Response.ok(responseBody).build();
}
I'd like to reuse this pattern on multiple resources, and I don't want to create list DTOs for every response type. Is there an elegant way to use swagger to document these types of response bodies?
Your metadata is not a part of your resource but it's a part of your resource's representation.
In my case, responses types are 'application/hal+json' and 'application/json', each of them use a different wrapper with different metadatas.
To solve this problem, I created an extern document to explain these two wrappers and for each of them, how a single resource and a list of resources are represented with metadata.
I think my choice is correct because I separate the resource of its representations (per page 7 'Manipulation of Resources Through Representations' of this RESTful best practices document)
In your case, you returns a list of WidgetDtos, the layer of metadata is a part of the representation of your resource.
However, you can use a generic class like Resource and Resources used by spring-hateoas :
public class Resources<T> implements Iterable<T> {
private final Collection<T> content;
Resources(Iterable<T> content) {
this.content = new ArrayList<T>();
for (T element : content) {
this.content.add(element);
}
}
}
And use it like this:
#GET
#Produces(MediaType.APPLICATION_JSON)
#ApiOperation(
value = "Get all widgets.",
response = WidgetDto.class
)
#ApiResponses(value = {
#ApiResponse(code = 200, message = "Returns the list of widgets.")
})
public Response getWidgets() {
List<WidgetDto> widgets;
...
return Response.ok(new Resources<WidgetDto>(widgets)).build();
}
I faced a similar problem a few months ago when I was developing a project for school. The solution is to create an envelope and always return it. The envelope will contain a feild "data" which is a generic; so you will be able to bind it to any data type.
Note that even though I used it I later on read that it should be used scarecly (I think your case is a good example of usage) but technically an Exception object should be thrown if the request failed.
Anyway this is my Response class which I used to return all my responses:
public class Response <AnyData> {
private static final String SUCCESS = "success";
private static final String FAILURE = "failure";
private String status;
private AnyData data;
private String error;
private Response(String status, AnyData data, String error) {
this.status = status;
this.data = data;
this.error = error;;
}
private Response(String status, AnyData data) {
this(status, data,"");
}
private Response(String status, String error) {
this(status, null, error);
}
public static <AnyData> Response<AnyData> success(AnyData data) {
return new Response<AnyData>(SUCCESS, data);
}
public static <AnyData> Response<AnyData> failure(String error) {
return new Response<AnyData>(FAILURE, error);
}
public static <AnyData> Response<AnyData> unimplemented() {
return new Response<AnyData>(FAILURE, "Missing implementation in the backend.");
}
public static <AnyData> Response<AnyData> failureUserNotFound() {
return Response.failure("User not found!");
}
public static <AnyData> Response<AnyData> failureBusinessNotFound() {
return Response.failure("Business not found!");
}
// Removed getters and setters for simplicity.
}
After this is set we will just create the responses right from the Comtroller. I changed it a bit to make it work with the sample is should be legible enough. Note that I have static methods for my responses: 'success()', 'error()'...
#RestController
#Api(tags={"Widgets"})
public class WidgetController {
#RequestMapping(value="/api/widgets", method=RequestMethod.GET, produces=MediaType.APPLICATION_JSON)
#ApiOperation(value = "Get all widgets.")
#ApiResponses(value = {
#ApiResponse(code = 200, message = "Returns the list of widgets.")
})
public Response<List<WidgetDto>> getWidgets() {
List<WidgetDto> widgets = new LinkedList<>();
widgets.add(new WidgetDto(1234, "val1"));
widgets.add(new WidgetDto(5678, "val2"));
return Response.success(widgets);
}
}
And here is a sample of the response body:
Hope this helps.
You can define the responseContainer attribute in the #ApiOperation annotation.
The value List will wrap your WidgetDto in a container.
#ApiOperation(
value = "Get all widgets.",
response = WidgetDto.class,
responseContainer = "List"
)

Front to Back : how to pass a bean with a List attribute?

I try to test my web app application by passing a bean between my Back (Spring MVC) and my Front (Angular)
This is the Bean :
import java.util.List;
// liste des conférences choisies par un candidat
public class PartListFormDTO {
private ParticipantDTO participant;
private List<FormationDTO> listForm;
public ParticipantDTO getParticipant() {
return participant;
}
public void setParticipant(ParticipantDTO participant) {
this.participant = participant;
}
public List<FormationDTO> getListForm() {
return listForm;
}
public void setListForm(List<FormationDTO> listForm) {
this.listForm = listForm;
}
}
this is the controller:
// formation List
#RequestMapping(value = "/init", method = RequestMethod.GET)
#ResponseBody
public PartListFormDTO init() {
return service.init();
}
init() sends correctely the bean to the front.
Chrome console :
Object {participant: Object, listForm: Array[2]}
listForm: Array[2]
0: Object
1: Object
participant: Object
In my Front, i send back it immediatelly to test.
This is the controller :
// formation book
#RequestMapping(value="/book", method=RequestMethod.POST)
#ResponseBody
public StatusResponse book(PartListFormDTO partListFormDTO) {
return null;
// return service.book(partListFormDTO);
}
request parameter in Angular Service :
$http({
headers:{'Content-Type':'multipart/form-data'},
//headers:{'Content-Type':'application/json;odata=verbose'},
//headers:{'Accept': 'application/json, text/plain, */*',
// 'Content-Type':'application/json; multipart/form-data; charset=utf-8'},
method: 'POST',
url: url,
params: data
})
.success( function(data, status, headers, config) {
defData.$resolve(data);
$log.info(data);
})
Now, you must know that i have other methods in my controller who works fine. If i send a bean without List attribute, it works !!
but a bean with a list like mine, it doesn't work
public class PartListFormDTO {
private ParticipantDTO participant;
private List<FormationDTO> listForm;
.....
How can i pass it ?

spring generic json response

I am using Spring MVC and returning JSON as response. I would like to create a generic JSON response where I can put in any TYPE and want the response to look like this
{
status : "success",
data : {
"accounts" : [
{ "id" : 1, "title" : "saving", "sortcode" : "121212" },
{ "id" : 2, "title" : "current", "sortcode" : "445566" },
]
}
}
So I created a Response<T> object
public class Response<T> {
private String status;
private String message;
T data;
...
...
}
Is this the correct way of doing this, or is there a better way?.
How do you use this Response object in Spring controller to return an empty response object and/or a populated response object.
Thanks in advance GM
UPDATE:
In order to get the similar JSON output as the one described, i.e. with "accounts" key in JSON, I had to use Response<Map<String, List<Account>>> the following in the controller:
#RequestMapping(value = {"/accounts"}, method = RequestMethod.POST, produces = "application/json", headers = "Accept=application/json")
#ResponseBody
public Response<Map<String, List<Account>>> findAccounts(#RequestBody AccountsSearchRequest request) {
//
// empty accounts list
//
List<Account> accountsList = new ArrayList<Account>();
//
// response will hold a MAP with key="accounts" value="List<Account>
//
Response<Map<String, List<Account>>> response = ResponseUtil.createResponseWithData("accounts", accountsList);
try {
accountsList = searchService.findAccounts(request);
response = ResponseUtil.createResponseWithData("accounts", accountsList);
response.setStatus("success");
response.setMessage("Number of accounts ("+accounts.size()+")");
} catch (Exception e) {
response.setStatus("error");
response.setMessage("System error " + e.getMessage());
response.setData(null);
}
return response;
}
Is this the right way of doing this? i.e. in order to get the "accounts" key in JSON output?
While your example JSON is not valid (status and data are not enclosed in quotations), this approach will work.
You will want to ensure that you have the Jackson jars on your classpath, and Spring will take care of the rest.
To get this to work, I would create a constructor for your response class that looks something like this:
public class Response<T> {
private String status;
private String message;
private T data;
public Response(String status, String message, T data) {
this.status = status;
this.message = message;
this.data = data;
}
//...getter methods here
}
And then in your Spring controller, you just return this object from your method that is mapped with #RequestMapping
#Controller
public class MyController {
#RequestMapping(value="/mypath", produces="application/json")
public Response<SomeObject> myPathMethod() {
return new Response<SomeObject>("200", "success!", new SomeObject());
}
}

Categories