Exception evaluating SpringEL with Spring MVC - spring-mvc

i start to use Spring MVC and i have a trouble. I want to get the value of a hashmap with the key. The object ProtoStatus contains a hashmap who i want to get value. I have this error :
org.thymeleaf.exceptions.TemplateProcessingException: Exception
evaluating SpringEL expression: "protoStatus.status.get(30000)"
(template: "protoStatusPage" - line 18, col 21)
public class ProtoStatus
{
public HashMap<String, String> status;
public void computeStatus()
{
this.status = new HashMap();
for (int i=30000; i<30032; i++)
{
this.status.put(String.valueOf(i), String.valueOf(ServerChecker.Check("192.168.0.1", i)));
}
}
public void setStatus(HashMap status)
{
this.status = status;
}
public HashMap getStatus()
{
return this.status;
}
public String getStatus(int key)
{
return (String) this.status.get(key);
}
}
The Spring MVC part :
#PostMapping("/")
public String submit(#ModelAttribute User user, #ModelAttribute ProtoStatus protoStatus)
{
protoStatus = new ProtoStatus();
protoStatus.computeStatus();
return "protoStatusPage";
}
And Finaly, in the template protoStatusPage.html, i want to get the value for key 30000:
<p th:text="${protoStatus.status.get(30000)}" />

Your status attribute is null by default. It only becomes non-null when you call computeStatus(). But you're never calling it on the ProtoStatus used by the view:
protoStatus = new ProtoStatus();
protoStatus.computeStatus();
Instead, you create a different one, and call computeStatus() on that one, that is then immediately eligible to garbage collection.
Also, you have a Map<String, String>, but your getStatus() method, and the expression in your view, tries to get something out of it usig a key of type Integer. That will always return null.

Your HashMap<String,String> contains both the String object. So you have to call using
Replace this line
<p th:text="${protoStatus.status.get(30000)}" />
with
<p th:text="${protoStatus.status.get('30000')}" />

Related

Migrate Newtonsoft JsonConverter to JsonConverter<T>

I have the following interface
public interface IApiResult<TResult> : IApiResult
{
TResult Result { get; set; }
}
with a concrete class like this
public class ApiResult<TResult> : ApiResult, IApiResult<TResult>
{
public ApiResult( TResult result ) : base() {
Result = result;
}
public TResult Result { get; set; }
}
When I used Newtonsoft json library I used a JsonConverter to manage polimorphic serialization and deserialization this way
public override object ReadJson( JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer ) {
var obj = JObject.Load( reader );
var apiResult = new ApiResult.ApiResult().As<IApiResult>();
//Check if is an IApiResult<T>
if (obj.TryGetValue( nameof( ApiResult<object>.Result ), StringComparison.InvariantCultureIgnoreCase, out var jResult )) {
//Retrieve the result property type in order to create the proper apiResult object
var prop = objectType.GetProperty( nameof( ApiResult<object>.Result ) );
var value = jResult.ToObject( prop.PropertyType, serializer );
var rType = typeof( ApiResult<> ).MakeGenericType( prop.PropertyType );
apiResult = Activator.CreateInstance( rType ).As<IApiResult>();
prop.SetValue( apiResult, value );
}
//Set the messages
var jMessages = obj.GetValue( nameof( ApiResult.ApiResult.Messages ), StringComparison.InvariantCultureIgnoreCase ).ToObject<JObject[]>();
apiResult.Messages = DeserializeReasons( jMessages );
return apiResult;
}
How can I migrate this code to System.Text.Json?
UPDATE
My biggest problem is with the JObject.TryGetvalue function. This function would have returned a deserialized object that let me understood the type. Since that, I was only using some reflection to understand the type of ApiResult<T>.
With the actual UTF8JsonReader class I am only able to read token by token so I can not replicate the previous behavior.
Your question boils down to, Inside JsonConverter<T>.Read(), how can I scan forward in, or load the contents of, a Utf8JsonReader to determine the polymorphic type of object to deserialize without having to manually deserialize token by token as shown in the documentation example?
As of .NET 6 you have a couple options to accomplish this.
Firstly you can copy the Utf8JsonReader struct and scan forward in the copy until you find the property or properties you want. The original, incoming Utf8JsonReader will be unchanged and still point to the beginning of the incoming JSON value. System.Text.Json will always preload the entire JSON object, array or primitive to be deserialized before calling JsonConverter<T>.Read() so you can be certain the require values are present.
To do this, introduce the following extension methods:
public static partial class JsonExtensions
{
public delegate TValue? DeserializeValue<TValue>(ref Utf8JsonReader reader, JsonSerializerOptions options);
///Scan forward in a copy of the Utf8JsonReader to find a property with the specified name at the current depth, and return its value.
///The Utf8JsonReader is not passed by reference so the state of the caller's reader is unchanged.
///This method should only be called inside JsonConverter<T>.Read(), at which point the entire JSON for the object being read should have been pre-loaded
public static bool TryGetPropertyValue<TValue>(this Utf8JsonReader reader, string name, StringComparison comparison, JsonSerializerOptions options, out TValue? value) =>
reader.TryGetPropertyValue<TValue>(name, comparison, options, (ref Utf8JsonReader r, JsonSerializerOptions o) => JsonSerializer.Deserialize<TValue>(ref r, o), out value);
public static bool TryGetPropertyValue<TValue>(this Utf8JsonReader reader, string name, StringComparison comparison, JsonSerializerOptions options, DeserializeValue<TValue> deserialize, out TValue? value)
{
if (reader.TokenType == JsonTokenType.Null)
goto fail;
else if (reader.TokenType == JsonTokenType.StartObject)
reader.ReadAndAssert();
do
{
if (reader.TokenType != JsonTokenType.PropertyName)
throw new JsonException();
var currentName = reader.GetString();
reader.ReadAndAssert();
if (String.Equals(name, currentName, comparison))
{
value = deserialize(ref reader, options);
return true;
}
else
{
reader.Skip();
}
}
while (reader.Read() && reader.TokenType != JsonTokenType.EndObject);
fail:
value = default;
return false;
}
static void ReadAndAssert(ref this Utf8JsonReader reader)
{
if (!reader.Read())
throw new JsonException();
}
}
public static partial class ObjectExtensions
{
public static T ThrowOnNull<T>(this T? value) where T : class => value ?? throw new ArgumentNullException();
}
And now your Newtonsoft converter might be rewritten to look something like:
public class ApiResultConverter : System.Text.Json.Serialization.JsonConverter<IApiResult>
{
record MessagesDTO(Message [] Messages); // Message is the presumed type the array elements of ApiResult.ApiResult.Messages, which is not shown in your question.
public override IApiResult? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
IApiResult? apiResult = null;
if (reader.TryGetPropertyValue(nameof( ApiResult<object>.Result ), StringComparison.OrdinalIgnoreCase, options,
(ref Utf8JsonReader r, JsonSerializerOptions o) =>
{
var prop = typeToConvert.GetProperty( nameof( ApiResult<object>.Result ) ).ThrowOnNull();
return (Value : JsonSerializer.Deserialize(ref r, prop.PropertyType, o), Property : prop);
},
out var tuple))
{
var rType = typeof( ApiResult<> ).MakeGenericType( tuple.Property.PropertyType );
apiResult = Activator.CreateInstance( rType ).As<IApiResult>().ThrowOnNull();
tuple.Property.SetValue( apiResult, tuple.Value );
}
if (apiResult == null)
apiResult = new ApiResult.ApiResult().As<IApiResult>();
// Now consume the contents of the Utf8JsonReader by deserializing to MessagesDTO.
var dto = JsonSerializer.Deserialize<MessagesDTO>(ref reader, options);
apiResult.Messages = dto?.Messages ?? Array.Empty<Message>();
return apiResult;
}
public override void Write(Utf8JsonWriter writer, IApiResult value, JsonSerializerOptions options) => JsonSerializer.Serialize(writer, value, value.GetType(), options);
}
Notes:
This approach works well when scanning forward for a single simple property, e.g. a type discriminator string. If the type discriminator string is likely to be at the beginning of the JSON object it will be quite efficient. (This does not seem to apply in your case.)
JsonConverter<T>.Read() must completely consume the incoming token. E.g. if the incoming token is of type JsonTokenType.StartObject then, when exiting, the reader must be positioned on a token of type JsonTokenType.EndObject at the same depth. Thus if you only scan forward in copies of the incoming Utf8JsonWriter you must advance the incoming reader to the end of the current token by calling reader.Skip() before exiting.
Both Json.NET and System.Text.Json use StringComparison.OrdinalIgnoreCase for case-invariant property name matching, so I recommend doing so as well.
Secondly, you can load the contents of your Utf8JsonReader into a JsonDocument or JsonNode, query its properties, then deserialize to your final desired type with one of the JsonSerializer.Deserialzie() overloads that accepts a JSON document or node.
Using this approach with JsonObject in place of JObject, your converter might look something like:
public class ApiResultConverter : System.Text.Json.Serialization.JsonConverter<IApiResult>
{
public override IApiResult? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var obj = JsonNode.Parse(ref reader, new JsonNodeOptions { PropertyNameCaseInsensitive = true })?.AsObject();
if (obj == null)
return null; // Or throw JsonException() if you prefer
IApiResult? apiResult = null;
if (obj.TryGetPropertyValue( nameof( ApiResult<object>.Result ), out var jResult ))
{
//Retrieve the result property type in order to create the proper apiResult object
var prop = typeToConvert.GetProperty( nameof( ApiResult<object>.Result ) ).ThrowOnNull();
var value = JsonSerializer.Deserialize(jResult, prop.PropertyType, options);
var rType = typeof( ApiResult<> ).MakeGenericType( prop.PropertyType );
apiResult = Activator.CreateInstance( rType ).As<IApiResult>();
prop.SetValue( apiResult, value );
}
if (apiResult == null)
apiResult = new ApiResult.ApiResult().As<IApiResult>();
//Set the messages
JsonObject? []? messages = obj[nameof( ApiResult.Messages )]?.AsArray()?.Select(i => i?.AsObject())?.ToArray();
apiResult.Messages = DeserializeReasons(messages); // Not shown in your question
return apiResult;
}
static JsonObject? [] DeserializeReasons(JsonObject? []? messages) => messages == null ? Array.Empty<JsonObject>() : messages;
public override void Write(Utf8JsonWriter writer, IApiResult value, JsonSerializerOptions options) => JsonSerializer.Serialize(writer, value, value.GetType(), options);
}
public static partial class ObjectExtensions
{
public static T ThrowOnNull<T>(this T? value) where T : class => value ?? throw new ArgumentNullException();
}
Notes:
This approach works well when you have multiple properties (possibly with complex values) to search for and load during the conversion process.
By loading the JsonObject with JsonNodeOptions.PropertyNameCaseInsensitive = true, all property name lookups in the deserialized JsonNode hierarchy will be case-insensitive (using StringComparer.OrdinalIgnoreCase matching as shown in the source).
Since your question does not include a compilable example, the above converters are untested.

ASP.NET Core ModelBindingContext Uses Only 1 Value Provider (Ignores RouteDataValueProvider)

I'm currently converting a ASP.NET library to ASP.NET Core that's heavy on Model Binding, and one issue I'm unable to resolve is that for cases where a model binder wants to try and get values via Value Providers both from (From)Query and (From)Route (as in, they have different BindingSource) it can only access one, resulting in the binding to fail since it fails to find the target value.
While debugging the application I can see that the ModelBindingContext does have both value providers in its private OriginalValueProvider, but when trying to access the ValueProvider it only returns one of the providers - in this case, only the QueryStringValueProvider and not the RouteValueProvider).
This seems not to have been the behavior back in regular ASP.NET, as there the existing application happily uses both value providers to find its value - and does so successfully. Is this some breaking change between System.Web.Http.ModelBinding -> Microsoft.AspNetCore.Mvc.ModelBinding? Is there some option that must be enabled? Some guidance would be appreciated.
Here follows some screen caps from my debugging:
System.Web.Http original:
Microsoft.AspNetCore.Mvc rework:
Microsoft.AspNetCore.Mvc OriginalValueProvider(s) proof:
Edit:
And here follows some code snippets, as requested:
Controller:
[HttpGet]
[Route("accounts/{accountId}/catalogs/{catalogId}/medias", Name = "GetCatalogMedias")]
[ProducesResponseType(typeof(IList<MediaResponse>), (int)HttpStatusCode.OK)]
public async Task<IActionResult> GetCatalogMedias(
[FromRoute] string accountId,
[FromRoute] string catalogId,
[FromQuery, SortingFieldDefaultValue("created", SortingDirection.Desc)] IEnumerable<SortingField> sort,
[FromQuery] QueryTree<MediaResponse> q = null,
[FromQuery, Range(0, int.MaxValue)] int offset = 0,
[FromQuery, Range(1, 200)] int limit = 200)
{
if (!ModelState.IsValid)
{
return await _responseFactory.CreateBadRequest(ModelState);
}
Binder:
public class CatalogMediasQueryModelBinder<T> : BaseBinder, IModelBinder
{
private readonly QueryModelBinder _queryModelBinder;
public CatalogMediasQueryModelBinder(QueryModelBinder queryModelBinder)
{
_queryModelBinder = queryModelBinder;
}
public async Task BindModelAsync(ModelBindingContext bindingContext)
{
var success = TryGetValueProviderResult(bindingContext, out var catalogId, "catalogId");
if (!success)
{
bindingContext.ModelState.AddModelError(bindingContext.ModelName, "Missing catalogId");
return;
}
//...
Base Binder:
public class BaseBinder
{
protected bool TryGetValueProviderResult(ModelBindingContext bindingContext, out string result, string findValue = null)
{
if (bindingContext?.ValueProvider == null)
{
throw new ArgumentNullException(nameof(bindingContext));
}
result = null;
var modelName = findValue ?? bindingContext.ModelName;
var valueProviderResult = bindingContext.ValueProvider.GetValue(modelName);
if (valueProviderResult == ValueProviderResult.None)
{
return false;
}
bindingContext.ModelState.SetModelValue(modelName, valueProviderResult);
var value = valueProviderResult.FirstValue;
if (string.IsNullOrWhiteSpace(value))
{
return false;
}
result = value;
return true;
}
protected Task SetModelBindingResult<T>(ModelBindingContext bindingContext, T value)
{
bindingContext.Result = ModelBindingResult.Success(value);
return Task.CompletedTask;
}
}

cannot be cast to java.lang.Comparable while sorting composite json

Problem statement: I am trying to sort a composite class using comparable and collections.sort but getting exception.
class BmetData implements Comparable<BmetData>{ Long mId;
String mName;
String mPath;
String frequency;
List<MValue> mValues;
public int compareTo(BmetData o) {
return mId.compareTo(o.mId);
}}
SubClass -
class MValue implements Comparable<MValue> { Date startTimeInMillis;
Long current;
Long min;
Long max;
Long count;
Long sum;
Long value;
public int compareTo(MValue o) {
return startTimeInMillis.compareTo(o.startTimeInMillis);
}}
I am getting response list of BmetData from API, parcing api response string using method -
public static <T> List<T> getJavaListFromJson(ApiResponseHolder apiResponse, Class<T> clazz) {
if (apiResponse.getResponseCode() == 200) {
Gson gson = new Gson();
TypeToken<List<T>> token = new TypeToken<List<T>>() {
};
List<T> pojoList = gson.fromJson(apiResponse.getMessage(), token.getType());
return pojoList;
} else {
return null;
}
}
Which returns me List<BmetData> list
Now trying to sort it using Collections.sort(list) getting exception -
Exception in thread "main" java.lang.ClassCastException: com.google.gson.internal.LinkedTreeMap cannot be cast to java.lang.Comparable
at java.util.ComparableTimSort.countRunAndMakeAscending(ComparableTimSort.java:320)
at java.util.ComparableTimSort.sort(ComparableTimSort.java:188)
at java.util.Arrays.sort(Arrays.java:1312)
at java.util.Arrays.sort(Arrays.java:1506)
at java.util.ArrayList.sort(ArrayList.java:1462)
at java.util.Collections.sort(Collections.java:141)
at Helper.ApiExecuterUsingToken.main(ApiExecuterUsingToken.java:70)
Instead of comparable use Comparator for sorting.

Associate async task's completion/progress monitor with session

I want to be able to perform an asynchronous task in java and be able to keep a completion (and if possible progress) monitor associated to the user's session. Is this possible, and if yes what is the way to do it?
Currently the task is implemented synchronously as a stateless session bean method, which is called from a jax-rs endpoint.
I looked at https://docs.oracle.com/javaee/7/tutorial/ejb-async001.htm but AsyncResult is not serializable so I guess I cannot add it to session.
Using the Spring annotation #Async, you can make any bean/method asynchronous.
The container will create a new thread and method will be executed asynchronously. You can as well pass a session object into this method and upon completion, you can mark an attribute in the session object.
Example:- https://spring.io/guides/gs/async-method/
JSF example, works in Wildfly:
1 inside in view (xhtml) we have an upload form and progress meter
<h:form>
<div align="justify">
<p:fileUpload style="width: auto" fileUploadListener="#{fileUploadCtrl.handleFileUpload}" mode="advanced" label="Please pick XLS file" update="messages" auto="true" sizeLimit="1000000" allowTypes="/(\.|\/)(xls|xlsx)$/" />
<p:growl id="messages" showDetail="false" life="4000"/>
</div>
</h:form>
<h:form id="wholeform">
<h:outputText id="statusot" value="#{fileUploadCtrl.message}" />
<p:spacer width="10" height="10"/>
<p:poll interval="1" listener="#{fileUploadCtrl.updateStatus}" update="wholeform" />
</h:form>
2 in controller, which is a managed bean, we process file and once a second update status
#ManagedBean
#ViewScoped
public class FileUploadCtrl {
#EJB
private SomeBusinessLogicClass model;
#EJB
private ProgressTracker progress;
private Future<List<String>> asyncResult;
private int progressId = 0;
private String message;
private boolean busy = false;
public void handleFileUpload(FileUploadEvent event) {
Set<String> ids = model.populate(event.getFile().getContents());
progressId = progress.newIndex();
asyncResult = model.process(ids);
busy = true;
FacesMessage message = new FacesMessage("Loaded " + ids.size() + " objects", "");
FacesContext.getCurrentInstance().addMessage(null, message);
}
public void updateStatus() {
if (!busy)
return;
try {
if (asyncResult.isDone()) {
List<String> r = asyncResult.get();
message = "Job done";
busy = false;
progress.delIndex(progressId);
} else {
message = progress.getIndex(progressId)+"-th element in work";
}
} catch (Exception e) {
System.out.println("updateStatus " + e.toString());
}
}
3 All business logic is in EJBs like SomeBusinessLogicClass or many others. Also we need a simple progress-manager EJB, I post it completely
#Singleton
public class ProgressTracker {
private Map<Integer,Integer> indexes = new HashMap<>();
public Map<Integer, Integer> getIndexes() {
return indexes;
}
public void setIndexes(Map<Integer, Integer> indexes) {
this.indexes = indexes;
}
public Integer newIndex() {
Integer size = indexes.size();
indexes.put(size,0);
return size;
}
public void incIndex(final Integer index) {
int old = indexes.get(index);
old++;
indexes.put(index,old);
}
public Integer getIndex(final Integer index) {
return indexes.get(index);
}
public void delIndex(Integer index) {
indexes.remove(index);
}
}
Maybe this example is not elegant, I'm almost newbie with frontends, but it is working and better, than nothing.

List to Map giving compilation error

I have the following code :
List<Object> result = new ArrayList<Object>();
//Object is actually a Map<String,Object>
return Maps.uniqueIndex(result, new Function<Map<String, Object>, Long>() {
#Override
public Long apply(Map<String, Object> input) {
return (Long) input.remove("id");
}
});
I get compilation error.
The method uniqueIndex(Iterable<V>, Function<? super V,K>) in the type Maps is not applicable for the arguments (List, new Function<Map<String,Object>,Long>(){}).
How do I rewrite this piece of code such that I don't get into this issue?
The first generic parameter of Function must match the type of elements held by List.
So, if you have a List<T>, a Function will be used for doing something with elements from that List, hence it needs to be a Function<T, WHATEVER>.
So, in your case:
List<Object> result = new ArrayList<>();
Maps.uniqueIndex(result, new Function<Object, WHATEVER>() {
#Nullable
#Override
public WHATEVER apply(#Nullable Object s) {
return null; // do whatever you want here
}
});
If you want to store Map<String,Object> in a List why not use List<Map<String,Object>>?
List<Map<String,Object>> result = new ArrayList<>();
Maps.uniqueIndex(result, new Function<Map<String,Object>, WHATEVER>() {
#Nullable
#Override
public WHATEVER apply(#Nullable Map<String,Object> s) {
return null; // do whatever you want here
}
});

Resources