I am new to REST APIS and I am trying to build a local memory Cache and writing REST APIS using java and spring framework to get and retrieve data below is the detailed description of what I want to achieve:
- Building the REST API to store key-value in local memory cache
- API must have 2 endpoints following specification below
• /cache/add (This must store unique key only (existing key must be ignored), This will return true if the element was successfully added )
•/cache/take (This method retrieves and removes the most recently added element from the cache and waits if necessary until an element becomes available)
I am not able to figure out how to implement the /cache/take method. Any suggestions will be appreciated.
for your first requirement Building the REST API to store key-value in local memory cache I've used a linkedHashMap as it'll store your key value pairs, and since it is static you could use it as a localcache
for second requirement created two end points one is storing value in linkedHashMap and other one is removing the last entry you entered in linkedHashMap, One way to get last entry of a LinkedHashMap is to use "toArray" method of Set interface. that's what I did, you could have a better approach, this answer could be improved
#RestController
public class CacheController {
private static LinkedHashMap<String, String> localCache = new LinkedHashMap<String, String>();
#RequestMapping(value = { "/cache/add" }, method = RequestMethod.GET)
public Boolean cacheAdd(#RequestParam(value = "key", required = true) String key, #RequestParam(value = "value", required = true) String value) {
if (localCache.containsKey(key)) {
return false;
}
localCache.put(key, value);
return true;
}
#RequestMapping(value = { "/cache/take" }, method = RequestMethod.GET)
public String cacheTake() {
Set<Entry<String, String>> mapValues = localCache.entrySet();
int maplength = mapValues.size();
Entry<String, String>[] cacheArray = new Entry[maplength];
mapValues.toArray(cacheArray);
System.out.print("Last Key:" + cacheArray[maplength - 1].getKey());
System.out.println(" Last Value:" + cacheArray[maplength - 1].getValue());
localCache.remove(cacheArray[maplength - 1].getKey());
return cacheArray[maplength - 1].getValue();
}
}
Related
Is there a way to get or set an array element stored in a Java Map?
Example:
If we have a map like this:
{
name: "Blah",
friends: ["Foo", "Bar"]
}
Map<String, Object> myMap = new HashMap<>();
List<String> friends = new ArrayList<>();
myMap.put("name", "Blah");
myMap.put("friends", friends);
Is it possible to use Reflection to get or set the first element in the friends array in the "myMap" from the string: "myMap.friends[0]"
Your question is not very clearly written and I believe that's why you are not getting the answer you expect but, If I understood your question correctly, you need to parse the following input string at runtime that you don't know beforehand:
myMap.friends[0]
And this should be parsed into components like:
mapName = "myMap"
mapKey = "friends"
valueIndex = 0
And with this information, you need to manipulate data in a Map at runtime through reflection.
Note: This only makes sense if you could potentially have more complex expressions, using different sort of objects and accessing nested properties of retrieved objects, otherwise you wouldn't need reflection at all.
Note 2: You may want to have a look at JXPath which already does a lot of this for you based on a XPath-like syntax for navigating object graphs.
That said, if my assumptions are correct and you still want to do it yourself, consider the following example.
For the sake of demonstration, let's consider our map is returned by a method myMap inside a Context.
private static class Context {
public Map<String, Object> myMap() {
Map<String, Object> myMap = new HashMap<>();
List<String> friends = new ArrayList<>();
friends.add("Foo");
friends.add("Bar");
myMap.put("name", "Blah");
myMap.put("friends", friends);
return myMap;
}
}
I'm assuming you are already parsing the input string into the different components. If not, for this simple string you could do it with simple regular expressions. If you already have the components, let's consider the following method:
public static Object readContextMap(Context context,
String mapName, String mapKey, Integer mapValueIndex) throws Exception {
// gets Context class for inspection
Class<?> cls = context.getClass();
// search for a method based on supplied mapName
Method mapMethod = cls.getDeclaredMethod(mapName);
// get a value from the retrieved map based on mapKey
Object mapValue = mapMethod.getReturnType()
.getDeclaredMethod("get", Object.class)
.invoke(mapMethod.invoke(context), mapKey);
// if the result is of type list, use the index to return the indexed element
if (List.class.isAssignableFrom(mapValue.getClass())) {
return ((List<?>)mapValue).get(mapValueIndex);
}
// otherwise return the object itself
return mapValue;
}
For testing purposes, consider the following main method:
public static void main(String[] args) throws Exception {
Context context = new Context();
String input = "myMap.friends[0]";
// parse input into...
String mapName = "myMap";
String mapKey = "friends";
Integer valueIndex = 0;
Object firstFriend = readContextMap(context, mapName, mapKey, valueIndex);
System.out.println(firstFriend);
// prints Foo
Object name = readContextMap(context, "myMap", "name", null);
System.out.println(name);
// prints Blah
}
This should be approximately what you want. You can easily create variations of this to set values as well. Please bear in mind that this code is just for demo purposes and needs a better error handling (e.g. verify if the context is really returning a map and nothing else).
This should be something along the lines you are looking for.
There's no need to use reflection here. You can simply cast it (which is also unsafe, but less so).
You can just do this:
List<String> friends = (List<String>) myMap.get("friends");
friends.set(0, "Bob");
So I'm building a group of dropdowns that rely upon each other and built a query to get the code and description for a Product Type, Family, and Model object. I used nested hashmaps to story all of the data and objects. This was fine because I can just call all of the information that I need from the hashmaps. However, when it comes to the REST API's, it's going to display all of the nested information for each of the hashmaps when I call them. For each map I have it's key, and then the value consists of a Code, Desc, and the hashmap of the next object.
So, it would be like:
Main hashmap
- Key
- value
-> code
-> desc
-> product family hashmap
-- key
-- value
--> code
--> desc
--> product model hashmap
--- key
--- value
---> code
---> desc
My main question is how can I either strip these additional hashmaps from being displayed in the json format when viewing the REST API via web browser? Or can/do I need to just completely strip the additional information altogether?
#Service
public class ProductDAOImpl implements ProductDAO {
#PersistenceContext
private EntityManager em;
#Override
public Map<String, ProductType> getProductTypeStructure() {
HashMap<String, ProductType> prodTypes = new HashMap<>();
Query q = em.createNativeQuery("<query>");
List<Object[]> prodTypeEntities = q.getResultList();
final String badData = "XX-BAD-XX";
ProductType prodType = new ProductType(badData, "");
ProductFamily prodFamily = new ProductFamily(badData, "");
for(Object[] prodTypeEntity : prodTypeEntities) {
if (prodTypeEntity[1] == null || prodTypeEntity[3] == null || prodTypeEntity[5] == null) {
continue;
}
String prodTypeCd = prodTypeEntity[0].toString().toUpperCase();
String prodTypeDesc = StringUtils.trimTrailingWhitespace(prodTypeEntity[1].toString()).toUpperCase();
String prodFamilyCd = prodTypeEntity[2].toString().toUpperCase();
String prodFamilyDesc = StringUtils.trimTrailingWhitespace(prodTypeEntity[3].toString()).toUpperCase();
String prodModelCd = prodTypeEntity[4].toString().toUpperCase();
String prodModelDesc = StringUtils.trimTrailingWhitespace(prodTypeEntity[5].toString()).toUpperCase();
if(!prodType.getCode().equalsIgnoreCase(prodTypeCd)) {
prodType = new ProductType(prodTypeCd, prodTypeDesc);
prodType.setProdFamilies(new HashMap<String, ProductFamily>());
prodTypes.put(prodType.getCode(), prodType);
prodFamily.setCode(badData);
}
if(!prodFamily.getCode().equalsIgnoreCase(prodFamilyCd)) {
prodFamily = new ProductFamily(prodFamilyCd, prodFamilyDesc);
prodFamily.setProdModels(new HashMap<String, ProductModel>());
prodType.getProdFamilies().put(prodFamily.getCode(), prodFamily);
}
prodFamily.getProdModels().put(prodModelCd, new ProductModel(prodModelCd, prodModelDesc));
}
return prodTypes;
}
}
If I understood your question correctly, I think a DTO object might be the answer here. You add to it only the values that the dropdown might need and return it from the REST API.
Here's more on DTOs.
I have a class with two methods: the startAPI() calls the API classes to extract entities and returns the entities and the occurrence of the entities. I need this return value in two different methods from another class, but as soon as I call the second method (countApiOcc()) the map I pass is empty. How can I use the returned map in two different methods?
public class Topic {
public void calculateNoFeedback(String language, List<String> api, List<String> corr) {
Map<String, Object> apis = startAPI(api, textList);
CountTopics countT = new CountTopics();
ArrayList<String> topics = countT.getTopics(apis);
countT.countApiOcc(topics, apis);
}
public Map<String, Object> startAPI(List<String> selectedAPI, List<String> text) {
Map<String, Object> apisValues = new HashMap<String, Object>();
//do stuff to extract the entities and return entities
return apisValues;
}
}
The CountTopic() class looks as follows, and, explained in short, user can select which or how many APIs he wants to use to extract entities and in the class CountTopic() the method getTopics() should find the topics every selected API found and countApiOcc() I need the frequency of the selected entities (all of this works) it is just the map I need in the second method.
public ArrayList<String> getTopics(Map<String, Object> apiV) {
System.out.println("apiV: "+apiV);
Iterator iterator = apiV.entrySet().iterator();
mapSize = apiV.size();
System.out.println("Size of the map: "+ mapSize);
while (iterator.hasNext()) {
Map.Entry entries = (Map.Entry)iterator.next();
String key = entries.getKey().toString();
switch(key) {
case "valuesMS":
Map<String, Object> mapMicrosoft = (Map<String, Object>) apiV.get(key);
ArrayList<String> microsoft = (ArrayList<String>) mapMicrosoft.get("topicArrayMS");
microsoftTopicLowerCase.addAll(microsoft);
topicsMultiset.addAll(microsoft);
break;
case "valuesGate":
Map<String, Object> mapGate = (Map<String, Object>) apiV.get(key);
ArrayList<String> gate = (ArrayList<String>) mapGate.get("topicArrayGA");
//store the values for finding the topics which are found from every selected API
//store the values from the api to lower case to find the index later (needed for how often this api found the topic
gateTopicLowerCase.addAll(gate);
topicsMultiset.addAll(gate);
break;
}
iterator.remove();
}
//rest code: compare the Arrays to find the same topics
iterator.remove();
There's your culprit. You're emptying your map. Don't do this, and it will work. From my limited view on your code, there doesn't seem to be any reason to modify your map. But in case it would be necessary, you should make a copy of the map at the beginning of your method, and work on this copy. Generally it's a bad idea to modify your input parameters, unless that is the specific purpose of that method.
Here is SpringMVC Controller code snippet:
#RequestMapping(value = "/getCityList", method = RequestMethod.POST)
public #ResponseBody LinkedHashMap<String, String> getCityList(#RequestParam(value = "countryCode") String countryCode, HttpServletRequest request) throws Exception {
//gets ordered city list of country [sorted by city name]
LinkedHashMap<String, String> cityList = uiOperationsService.getCityList(countryCode);
for (String s : cityList.values()) {
System.out.println(s); //prints sorted list [sorted by name]
}
return cityList;
}
Here is ajax call:
function fillCityList(countryCode) {
$.ajax({
type: "POST",
url: '/getCityList',
data: {countryCode:countryCode},
beforeSend:function(){
$('#city').html("<option value=''>-- SELECT --</option>" );
}
}).done(function (data) {
console.log(data); // UNSORTED JSON STRING [Actually sorted by key... not by city name]
})
}
Sorted LinkedHashMap returns as unsorted JSON object from getCityList method. Why order is changed during return process ?
Is LinkedHashMap converted to HashMap because of ResponseBody annotation?
I can convert my sorted object to json string via Gson library and return json string from my getCityList method but i don't like this solution. What can i do to provide javascript callback method with sorted list?
You're expecting a JSON object's entries to have the same order as the LinkedHashMap entries. That won't happen, because JavaScript object keys have no intrinsic order. They're just like Java HashMaps.
If you need a JavaScript data structure that maintains an order, you should use an array, not an object. Return a sorted List<City> from your method, where City has a key and a value.
If you wanted to send sorted data then pass List of Your object or City as List<City> which have sorted data.
I have also face same problem when I have used HashMap in my application, it doesn't guarantee that it will receive in same order in which we add them. We have to access it via it's key.
so it's better to use List instead of LinkedHashMap.
Most implementations of JSON make no effort to preserve the order of an object's name/value pairs, since it is (by definition) not significant.
I have come across this question on StackOverflow which asks about converting JSON to Java. The answer shows that another class is modelled to represent the JSON data as well as an object being created and I don't understand why.
Does that object now contain all the information after Gson reads the content or only one key/value pair? If it only contains 1 key/value pair, I'm assuming I would need to create multiple objects for the JSON that I have below which I can the use a loop to iterate over and add the values to a drop down menu?
{
"1": "Annie",
"2": "Olaf",
"3": "Galio",
"4": "TwistedFate",
"5": "XinZhao",
"6": "Urgot",
"7": "Leblanc",
"8": "Vladimir",
"9": "FiddleSticks",
"10": "Kayle",
"11": "MasterYi",
"12": "Alistar",
"13": "Ryze",
"14": "Sion",
"15": "Sivir",
"16": "Soraka",
"17": "Teemo",
"18": "Tristana",
"19": "Warwick",
"20": "Nunu"
}
Essentially what I am aiming to do is:
1) Create a list of names with the Values.
2) Sort the list of names (as it comes unsorted) in alphabetical order
3) Loop through the list and add each name to a drop down menu
4) When a name in the drop down menu is selected, the key associated with that value is passed to another url which receives more data.
Sorry if this is unclear. I've spent a couple of hours trying to understand how to get elements from JSON and display it, as well as trying to create a list where I can use the key to display information the name but have had no luck except for using a for-each loop.
Let's use Jackson's feature that allows you to map any property to a single method (you don't really need a getter here I believe). Just swap the key and value in this universal setter, and add to a TreeMap, which is already sorted by key (name). Then you can output the keys (names) in the alphabetical order and get an ID by name easily.
public static void main(String[] args) throws IOException {
String json = "....."; // your JSON string here
com.fasterxml.jackson.databind.ObjectMapper mapper =
new com.fasterxml.jackson.databind.ObjectMapper();
ReverseMap pairs = mapper.readValue(json, ReverseMap.class);
for (Map.Entry<Object, String> entry : pairs.getValues().entrySet()) {
System.out.println(entry.getKey() + ":" + entry.getValue());
}
}
public class ReverseMap {
private TreeMap<Object, String> mapping = new TreeMap<>();
#com.fasterxml.jackson.annotation.JsonAnySetter
public void add(String name, Object value) {
mapping.put(value, name);
}
public Map<Object, String> getValues() {
return mapping;
}
}
Gson Bean Mapping Solution
Okay, what you have is a bit unusual for a JSON object; the keys (the numbers in your case) essentially represent properties of their contained object. That's workable, but you have to understand that, for example, when looking for "Annie" in the JSON object, if you use Gson to map to a "bean" class, which we'll call Data (as in the linked example), then you'd have to create a data object like so:
class Data {
private String _1;
// ...
private String _20;
public String get1() { return _1; }
public void set1(String _1) { this._1 = _1; }
// ...
public String get20() { return _20; }
public void set20(String _20) { this._20 = _20; }
}
And by using Data data = new Gson().fromJson(myJsonString, Data.class); on the given string, you'd be able to find "Annie" by calling... uh... data.get1()?
Clearly, this isn't a good solution.
Better Solutions
Since your data doesn't follow the typical format for a JSON object, you have two options:
If you can, refactor your JSON representation to a more verbose, but better representation for parsing.
Use a different approach to parse the existing JSON.
Solution 1: Changing the JSON representation
Refactoring the JSON would result in an object that (preferably) would look like this:
{
"champions" : [
{
"index" : 1,
"name" : "Annie"
},
{
"index" : 2,
"name" : "Olaf"
},
// ...
]
}
This could map easily to a couple of beans that look like this:
class Data {
private List<Champion> champions;
// TODO getters and setters
}
class Champion {
private int index;
private String name;
// TODO getters and setters
}
However, this adds a lot of unnecessary clutter to the JSON object, and isn't really necessary with only two fields per champion (the name, and their index).
You could simplify that further like so:
{
"champions" : [
"Annie",
"Olaf",
// ...
]
}
The bean class for that would then be:
class Data {
private List<String> champions;
// TODO getters and setters
}
Much simpler, but still requires a change to the JSON you're getting, which in some situations isn't possible. If you used this, though, you could also get rid of the "bean" class entirely, via:
List<String> champions = (List<String>) new Gson().fromJson(myJsonString, new TypeToken<List<String>>(){}.getType());
Solution 2: Changing how the JSON is parsed
The arguably better and cleaner solution is just to change how the JSON is parsed.
The goal here (if I understand you correctly) is to parse the JSON and spit out a collection of strings representing each champion's name, accessible by the numeric index of the champion in the JSON representation.
As such, and because of the way the JSON object is laid out as a simple mapping of strings to strings, we can use Gson to pipe directly into a Map<String, Object>, like so:
Map<String, String> mappedValues = new Gson().fromJson(myJsonString, Map.class);
String anniesName = mappedValues.get("1"); // "Annie"
String olafsName = mappedValues.get("2"); // "Olaf"
boolean hasTwentyOneElements = mappedValues.containsKey("21"); // false
This is shorter, requires no "bean" classes, and keeps the original JSON representation. The downside is that you can't easily tell whether the indices of each entry are correct and consistent; ie. if someone types in the wrong number, or deletes one of the entries.
To get a container of all keys, you just use mappedValues.keySet(), and to get a container of all key-value pairs, you use mappedValues.entrySet(), which gives you a Set<Map.Entry<String, String>>. Both of those can be iterated over, and may be in random order (I'm not sure whether the underlying Map implementation preserves insertion order or not).
To get the index for a given name (ie. champ), you'd use something similar to the following:
String index = null;
for (Map.Entry<String, String> entry : mappedValues.entrySet()) {
if (champ.equals(entry.getValue())) {
index = entry.getKey();
break;
}
}
Of course, you'd have to check to see if index is null after this, and handle that appropriately, but it's easily doable.
EDIT: #vempo's answer provides a cleaner, more efficient lookup strategy by means of inverting the map (although the answer is written for Jackson, instead of Gson); an adaptation of this for Gson is as follows (and yes, there is a vastly superior version in java-8, left out for sake of availability):
public Map<String, String> invertMap(Map<String, String> input) {
Map<String, String> newMap = new LinkedTreeMap<String, String>(); // TODO Pick optimal storage class
for (Map.Entry<String, String> entry : input.entrySet()) {
newMap.put(entry.getValue(), entry.getKey());
}
return newMap;
}
// ...
Map<String, String> mappedValues = invertMap(new Gson().fromJson(myJsonString, Map.class));
String annieIndex = mappedValues.get("Annie"); // "1"
String olafIndex = mappedValues.get("Olaf"); // "2"
It's worth noting that this sacrifices efficiency of constructing the map by effectively building it twice (once by Gson and once more to invert), but it makes value lookup much more efficient.