I have an authorization filter. While trying to retrieve pathparameters, I am unable to get the values.
could someone please tell me, what is wrong with the below code.
#AuthorizedAccess //named exception
#Provider
public class AuthorizedAccessFilter implements ContainerRequestFilter {
/** The Constant logger. */
private final static Logger logger = LoggerFactory.getLogger(AuthorizedAccessFilter.class);
public void filter(ContainerRequestContext requestContext) {
logger.info(AUTH_MESSAGE, requestContext.getUriInfo().getAbsolutePath());
MultivaluedMap<String, String> pathParameters = requestContext.getUriInfo().getPathParameters();
for (Entry<String, List<String>> e : pathParameters.entrySet()) {
logger.info( " key :value " +e.getKey(), e.getValue());
}
logger.info("pathparammap = {} ",pathParameters.size());
}
and it returns:
it returns key name but it doesn't return the value.
AuthorizedAccessFilter - key value entity-id
AuthorizedAccessFilter - pathparammap = 1
Well it looks like a simple incorrect format (1-st argument) in logger. You need to specify at least one {} that would be replaced with value of 2-nd logger.info argument
logger.info( " key :value {}" + e.getKey(), e.getValue());
or
logger.info( " key :value {} {}" , e.getKey() , e.getValue());
Also take a look at SLF4J documentation
Related
I want to identify numerical values inserted without quotation marks (as strings) in JSON sent through the request body of a POST request:
For example, this would be the wrong JSON format as the age field does not contain quotation marks:
{
"Student":{
"Name": "John",
"Age": 12
}
}
The correct JSON format would be:
{
"Student":{
"Name": "John",
"Age": "12"
}
}
In my code, I've defined the datatype of the age field as a String, hence "12" should be the correct input. However, no error message is thrown, even when 12 is used.
It seems Jackson automatically converts the numerical values into strings. How can I identify numerical values and return a message?
This is what I tried so far to identify these numerical values:
public List<Student> getMultiple(StudentDTO Student) {
if(Student.getAge().getClass()==String.class) {
System.out.println("Age entered correctly as String");
} else{
System.out.println("Please insert age value inside inverted commas");
}
}
However, this is not printing "Please insert age value inside inverted commas" to the console when the age is inserted without quotation marks.
If you're using Spring boot, by default it uses Jackson to parse JSON. There's no configuration option within Jackson to disable this feature, as mentioned within this issue. The solution is to register a custom JsonDeserializer that throws an exception as soon as it encounters any other token than JsonToken.VALUE_STRING
public class StringOnlyDeserializer extends JsonDeserializer<String> {
#Override
public String deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
if (!JsonToken.VALUE_STRING.equals(jsonParser.getCurrentToken())) {
throw deserializationContext.wrongTokenException(jsonParser, String.class, JsonToken.VALUE_STRING, "No type conversion is allowed, string expected");
} else {
return jsonParser.getValueAsString();
}
}
}
If you only want to apply this to certain classes or fields, you can annotate those with the #JsonDeserialize annotation. For example:
public class Student {
private String name;
#JsonDeserialize(using = StringOnlyDeserializer.class)
private String age;
// TODO: Getters + Setters
}
Alternatively, you can register a custom Jackson module by registering a SimpleModule bean that automatically deserializes all strings using the StringOnlyDeserializer. For example:
#Bean
public Module customModule() {
SimpleModule customModule = new SimpleModule();
customModule.addDeserializer(String.class, new StringOnlyDeserializer());
return customModule;
}
This is similar to what Eugen suggested.
If you run your application now, and you pass an invalid age, such as 12, 12.3 or [12]it will throw an exception with a message like:
JSON parse error: Unexpected token (VALUE_NUMBER_FLOAT), expected VALUE_STRING: Not allowed to parse numbers to string; nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Unexpected token (VALUE_NUMBER_FLOAT), expected VALUE_STRING: Not allowed to parse numbers to string\n at [Source: (PushbackInputStream); line: 3, column: 9] (through reference chain: com.example.xyz.Student[\"age\"])
By default, Jackson converts the scalar values to String when the target field is of String type. The idea is to create a custom deserializer for String type and comment out the conversion part:
package jackson.deserializer;
import java.io.IOException;
import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.deser.std.StringDeserializer;
public class CustomStringDeserializer extends StringDeserializer
{
public final static CustomStringDeserializer instance = new CustomStringDeserializer();
#Override
public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
if (p.hasToken(JsonToken.VALUE_STRING)) {
return p.getText();
}
JsonToken t = p.getCurrentToken();
// [databind#381]
if (t == JsonToken.START_ARRAY) {
return _deserializeFromArray(p, ctxt);
}
// need to gracefully handle byte[] data, as base64
if (t == JsonToken.VALUE_EMBEDDED_OBJECT) {
Object ob = p.getEmbeddedObject();
if (ob == null) {
return null;
}
if (ob instanceof byte[]) {
return ctxt.getBase64Variant().encode((byte[]) ob, false);
}
// otherwise, try conversion using toString()...
return ob.toString();
}
// allow coercions for other scalar types
// 17-Jan-2018, tatu: Related to [databind#1853] avoid FIELD_NAME by ensuring it's
// "real" scalar
/*if (t.isScalarValue()) {
String text = p.getValueAsString();
if (text != null) {
return text;
}
}*/
return (String) ctxt.handleUnexpectedToken(_valueClass, p);
}
}
Now register this deserializer:
#Bean
public Module customStringDeserializer() {
SimpleModule module = new SimpleModule();
module.addDeserializer(String.class, CustomStringDeserializer.instance);
return module;
}
When an integer is send and String is expected, here is the error:
{"timestamp":"2019-04-24T15:15:58.968+0000","status":400,"error":"Bad
Request","message":"JSON parse error: Cannot deserialize instance of
java.lang.String out of VALUE_NUMBER_INT token; nested exception is
com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot
deserialize instance of java.lang.String out of VALUE_NUMBER_INT
token\n at [Source: (PushbackInputStream); line: 3, column: 13]
(through reference chain:
org.hello.model.Student[\"age\"])","path":"/hello/echo"}
I'm struggling how to find out the session when receiving the onWebSocketClose event.
Official docs: https://www.eclipse.org/jetty/javadoc/current/org/eclipse/jetty/websocket/api/WebSocketAdapter.html#onWebSocketClose(int,java.lang.String)
It says there are two parameters: onWebSocketClose​(int statusCode, java.lang.String reason)
Another site says, that there are 3 parameters:
https://www.eclipse.org/jetty/documentation/current/jetty-websocket-api-annotations.html
#OnWebSocketClose
An optional method level annotation.
Flags one method in the class as receiving the On Close event.
Method signature must be public, not abstract, and return void.
The method parameters:
Session (optional)
int closeCode (required)
String closeReason (required)
Currently on WebSocketOpen I add the session to a collection like this:
private static Set<Session> connections = Collections.synchronizedSet(new HashSet<Session>());
private static final Logger logger = LogManager.getLogger(WebSocketEvent.class);
#Override
public void onWebSocketConnect(Session session)
{
super.onWebSocketConnect(session);
connections.add(session);
logger.debug("Socket Connected: " + session);
}
And on WebSocketClose I want to delete this session again like this:
#Override
public void onWebSocketClose(Session session, int statusCode, String reason)
{
super.onWebSocketClose(session,statusCode,reason);
logger.debug("Connection closed. IP: " + session.getRemoteAddress().getAddress().toString()+ "; rc: "+statusCode+ "; reason: "+ reason);
connections.remove(session);
logger.debug("Session with following IP removed from list: " + session.getRemoteAddress().getAddress().toString());
}
But it seems that the optional session parameter is not available like described in official docs so it marks it as an error.
Any idea how to solve it? Thanks in advance!
Ok I found it out. On WebSocketClose Event in this object there is a method getSession() available.
#Override
public void onWebSocketClose(int statusCode, String reason)
{
connections.remove(this.getSession());
super.onWebSocketClose(statusCode,reason);
logger.debug("Connection closed. rc: "+statusCode+ "; reason: "+ reason);
}
I'm playing w/ EE and want to persist a user session state. I have the session bean here:
#Stateful(mappedName = "UserSessionState")
#Named("UserSessionState")
#SessionScoped
#StatefulTimeout(value = 5, unit = TimeUnit.MINUTES)
public class UserSessionState implements Serializable
{
private boolean hasPlayerId = false;
private String playerId = "";
public void setRandomPlayerId()
{
playerId = UUID.uuid();
hasPlayerId = true;
}
public boolean hasPlayerId()
{
return hasPlayerId;
}
public String getPlayerId()
{
return playerId;
}
}
And a servlet here (GameState is an Application Scoped bean that is working as expected, CustomExtendedHttpServlet is just a simple extension of HttpServlet)
public class NewUserJoined extends CustomExtendedHttpServlet
{
#Inject
protected GameState gameState;
#Inject
protected UserSessionState user;
#Override
protected String doGetImpl(HttpServletRequest request, HttpServletResponse response, UserContext userLoginContext)
{
if (!user.hasPlayerId())
{
user.setRandomPlayerId();
}
String userId = user.getPlayerId();
if (!gameState.hasUser(userId))
{
gameState.addUser(userId, user);
return "Hi, your ID is: " + user.getPlayerId() + ", there are " + gameState.getUserCount() + " other players here";
}
else
{
return user.getPlayerId() + " you're already in the game, there are: " + gameState.getUserCount() + " other players here";
}
}
}
I'm not sure what's going on, but whenever I call the New User Joined servlet from the same HTTP session, I get this response on the first call (as expected):
"Hi, your ID is: , there are 1 other players here"
Repeating the same servlet call in the same session gives me the same message:
"Hi, your ID is: , there are 2 other players here"
...
It looks like a new instance of User Session State is getting created over and over. Am I doing this correctly?
EDIT 1: Here the code I use to send a request. It appears I'm getting a new session ID with each request, what could cause that?
RequestCallback callback = new RequestCallback()
{
#Override
public void onResponseReceived(Request request, Response response)
{
log(response.getText());
}
#Override
public void onError(Request request, Throwable exception)
{
log(
"Response Error | "
+ "Exception: " + exception);
}
};
RequestBuilder rb = new RequestBuilder(RequestBuilder.GET, SERVLET_URL);
rb.setCallback(callback);
try
{
rb.send();
}
catch (RequestException e)
{
log("Response Error | "
+ "Exception: " + e);
}
Figured out the issue,
Turns out I had an old workaround in the GWT client that was changing the host to get around a CORS issue. Because the response didn't match up to the origin, the cookie wasn't getting sent with future servlet GET calls.
Have you tried a call to request.getSession(true) to make sure an EE HTTPSession is established here?
Is there a way to configure the default Message<T> headers when the message is generated from the method return value:
#Publisher(channel = "theChannelname")
public MyObject someMethod(Object param) {
...
return myObject;
}
or
#SendTo("theChannelname")
public MyObject someMethod(Object param) {
...
return myObject;
}
In the examples above the Message<MyObject> will be automatically generated.
So, how can I control the default message generation?
Not really - the assumption is that if you return a payload then you don't care much about the headers. You can have the method return a Message and add your own headers there.
You can do that via #Header annotation for the method arguments:
#Publisher(channel="testChannel")
public String defaultPayload(String fname, #Header("last") String lname) {
return fname + " " + lname;
}
http://docs.spring.io/spring-integration/reference/html/message-publishing.html#publisher-annotation
I am working with Spring-websocket and I have the following problem:
I am trying to put a placeholder inside a #MessageMapping annotation in order to get the url from properties. It works with #RequestMapping but not with #MessageMapping.
If I use this placeholder, the URL is null. Any idea or suggestion?
Example:
#RequestMapping(value= "${myProperty}")
#MessageMapping("${myProperty}")
Rossen Stoyanchev added placeholder support for #MessageMapping and #SubscribeMapping methods.
See Jira issue: https://jira.spring.io/browse/SPR-13271
Spring allows you to use property placeholders in #RequestMapping, but not in #MessageMapping. This is 'cause the MessageHandler. So, we need to override the default MessageHandler to do this.
WebSocketAnnotationMethodMessageHandler does not support placeholders and you need add this support yourself.
For simplicity I just created another WebSocketAnnotationMethodMessageHandler class in my project at the same package of the original, org.springframework.web.socket.messaging, and override getMappingForMethod method from SimpAnnotationMethodMessageHandler with same content, changing only how SimpMessageMappingInfo is contructed using this with this methods (private in WebSocketAnnotationMethodMessageHandler):
private SimpMessageMappingInfo createMessageMappingCondition(final MessageMapping annotation) {
return new SimpMessageMappingInfo(SimpMessageTypeMessageCondition.MESSAGE, new DestinationPatternsMessageCondition(
this.resolveAnnotationValues(annotation.value()), this.getPathMatcher()));
}
private SimpMessageMappingInfo createSubscribeCondition(final SubscribeMapping annotation) {
final SimpMessageTypeMessageCondition messageTypeMessageCondition = SimpMessageTypeMessageCondition.SUBSCRIBE;
return new SimpMessageMappingInfo(messageTypeMessageCondition, new DestinationPatternsMessageCondition(
this.resolveAnnotationValues(annotation.value()), this.getPathMatcher()));
}
These methods now will resolve value considering properties (calling resolveAnnotationValues method), so we need use something like this:
private String[] resolveAnnotationValues(final String[] destinationNames) {
final int length = destinationNames.length;
final String[] result = new String[length];
for (int i = 0; i < length; i++) {
result[i] = this.resolveAnnotationValue(destinationNames[i]);
}
return result;
}
private String resolveAnnotationValue(final String name) {
if (!(this.getApplicationContext() instanceof ConfigurableApplicationContext)) {
return name;
}
final ConfigurableApplicationContext applicationContext = (ConfigurableApplicationContext) this.getApplicationContext();
final ConfigurableBeanFactory configurableBeanFactory = applicationContext.getBeanFactory();
final String placeholdersResolved = configurableBeanFactory.resolveEmbeddedValue(name);
final BeanExpressionResolver exprResolver = configurableBeanFactory.getBeanExpressionResolver();
if (exprResolver == null) {
return name;
}
final Object result = exprResolver.evaluate(placeholdersResolved, new BeanExpressionContext(configurableBeanFactory, null));
return result != null ? result.toString() : name;
}
You still need to define a PropertySourcesPlaceholderConfigurer bean in your configuration.
If you are using XML based configuration, include something like this:
<context:property-placeholder location="classpath:/META-INF/spring/url-mapping-config.properties" />
If you are using Java based configuration, you can try in this way:
#Configuration
#PropertySources(value = #PropertySource("classpath:/META-INF/spring/url-mapping-config.properties"))
public class URLMappingConfig {
#Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
Obs.: in this case, url-mapping-config.properties file are in a gradle/maven project in src\main\resources\META-INF\spring folder and content look like this:
myPropertyWS=urlvaluews
This is my sample controller:
#Controller
public class WebSocketController {
#SendTo("/topic/test")
#MessageMapping("${myPropertyWS}")
public String test() throws Exception {
Thread.sleep(4000); // simulated delay
return "OK";
}
}
With default MessageHandler startup log will print something like this:
INFO: Mapped "{[/${myPropertyWS}],messageType=[MESSAGE]}" onto public java.lang.String com.brunocesar.controller.WebSocketController.test() throws java.lang.Exception
And with our MessageHandler now print this:
INFO: Mapped "{[/urlvaluews],messageType=[MESSAGE]}" onto public java.lang.String com.brunocesar.controller.WebSocketController.test() throws java.lang.Exception
See in this gist the full WebSocketAnnotationMethodMessageHandler implementation.
EDIT: this solution resolves the problem for versions before 4.2 GA. For more information, see this jira.
Update :
Now I understood what you mean, but I think that is not possible(yet).
Documentation does not mention anything related to Path mapping URIs.
Old answer
Use
#MessageMapping("/handler/{myProperty}")
instead of
#MessageMapping("/handler/${myProperty}")
And use it like this:
#MessageMapping("/myHandler/{username}")
public void handleTextMessage(#DestinationVariable String username,Message message) {
//do something
}
#MessageMapping("/chat/{roomId}")
public Message handleMessages(#DestinationVariable("roomId") String roomId, #Payload Message message, Traveler traveler) throws Exception {
System.out.println("Message received for room: " + roomId);
System.out.println("User: " + traveler.toString());
// store message in database
message.setAuthor(traveler);
message.setChatRoomId(Integer.parseInt(roomId));
int id = MessageRepository.getInstance().save(message);
message.setId(id);
return message;
}