spring-session-data-redis: about the SaveMode.ON_GET_ATTRIBUTE - java

I find the is just to be used in the method of org.springframework.session.data.redis.RedisIndexedSessionRepository.RedisSession#getAttribute
In my opinion, getAttribute has not function, because the cached is got form redis ather through SessionRepositoryFilter and the DispacthServlet flush session when invoke doService , and back to SessionRepositoryFilter save the RedisSession' delta to redis.
so getAttribute form cached is equal with form redis, and getAttribute will put the key-value to delta in the SaveMode.ON_GET_ATTRIBUTE and has the Attribute.
So it maybe put the some key-value back to redis.I really unkown why spring write that.
public <T> T getAttribute(String attributeName) {
T attributeValue = this.cached.getAttribute(attributeName);
if (attributeValue != null && RedisIndexedSessionRepository.this.saveMode.equals(SaveMode.ON_GET_ATTRIBUTE)) {
this.delta.put(RedisIndexedSSaveMode.ON_GET_ATTRIBUTEessionRepository.getSessionAttrNameKey(attributeName), attributeValue);
}
return attributeValue;
}
I've racked my brains for a long time for what is the function of SaveMode.ON_GET_ATTRIBUTE and asked many times to chatgpt, but it always gave me error answer and repeat error many time.
I also see the offical docment.
/**
* Same as {#link #ON_SET_ATTRIBUTE} with addition of saving attributes that have been
* read using {#link Session#getAttribute(String)}.
*/
ON_GET_ATTRIBUTE
and the Test
#Test
void saveWithSaveModeOnGetAttribute() {
given(this.redisOperations.<String, Object>boundHashOps(anyString())).willReturn(this.boundHashOperations);
given(this.redisOperations.boundSetOps(anyString())).willReturn(this.boundSetOperations);
given(this.redisOperations.boundValueOps(anyString())).willReturn(this.boundValueOperations);
this.redisRepository.setSaveMode(SaveMode.ON_GET_ATTRIBUTE);
MapSession delegate = new MapSession();
delegate.setAttribute("attribute1", "value1");
delegate.setAttribute("attribute2", "value2");
delegate.setAttribute("attribute3", "value3");
RedisSession session = this.redisRepository.new RedisSession(delegate, false);
session.getAttribute("attribute2");
session.setAttribute("attribute3", "value4");
this.redisRepository.save(session);
assertThat(getDelta()).hasSize(2);
}
I think the Test is just as I thought.It repeatly add attribute2=value2 to redis.I really unkown why the Test is writend like that.
please, who can tell me waht the function of SaveMode.ON_GET_ATTRIBUTE

Related

DynamoDB's PaginatedList via REST

For a web-application, I want to implement a paginated table. The DynamoDB "layout" is, that there are multiple items for a user, therefore I've chosen the partition key=user and the sort key=created (timestamp). The UI shall present the items in pages à 50 items from a total of a few 100 items.
The items are passed to the UI via REST-Api calls. I only want to query or scan a page of items, not the whole table. Pagination shall be possible forward and backward.
So far I've come up with the following, using the DynamoDBMapper:
/**
* Returns the next page of items DEPENDENT OF THE USER. Note: This method internally uses
* DynamoDB QUERY. Thus it requires "user" as a parameter. The "created" parameter is optional.
* If provided, both parameters form the startKey for the pagination.
*
* #param user - mandatory: The user for which to get the next page
* #param created - optional: for providing a starting point
* #param limit - the returned page will contain (up to) this number of items
* #return
*/
public List<SampleItem> getNextPageForUser(final String user, final Long created, final int limit) {
// To iterate DEPENDENT on the user we use QUERY. The DynamoDB QUERY operation
// always require the partition key (=user).
final SampleItem hashKeyObject = new SampleItem();
hashKeyObject.setUser(user);
// The created is optional. If provided, it references the starting point
if (created == null) {
final DynamoDBQueryExpression<SampleItem> pageExpression = new DynamoDBQueryExpression<SampleItem>()//
.withHashKeyValues(hashKeyObject)//
.withScanIndexForward(true) //
.withLimit(limit);
return mapper.queryPage(SampleItem.class, pageExpression).getResults();
} else {
final Map<String, AttributeValue> startKey = new HashMap<String, AttributeValue>();
startKey.put(SampleItem.USER, new AttributeValue().withS(user));
startKey.put(SampleItem.CREATED, new AttributeValue().withN(created.toString()));
final DynamoDBQueryExpression<SampleItem> pageExpression = new DynamoDBQueryExpression<SampleItem>()//
.withHashKeyValues(hashKeyObject)//
.withExclusiveStartKey(startKey)//
.withScanIndexForward(true) //
.withLimit(limit);
return mapper.queryPage(SampleItem.class, pageExpression).getResults();
}
}
The code for previous is similar, only that it uses withScanIndexForward(false).
In my REST-Api controller I offer a single method:
#RequestMapping(value = "/page/{user}/{created}", method = RequestMethod.GET)
public List<SampleDTO> listQueriesForUserWithPagination(//
#RequestParam(required = true) final String user,//
#RequestParam(required = true) final Long created,//
#RequestParam(required = false) final Integer n,//
#RequestParam(required = false) final Boolean isBackward//
) {
final int nrOfItems = n == null ? 100 : n;
if (isBackward != null && isBackward.booleanValue()) {
return item2dto(myRepo.getPrevQueriesForUser(user, created, nrOfItems));
} else {
return item2dto(myRepo.getNextQueriesForUser(user, created, nrOfItems));
}
}
I wonder if I am re-inventing the wheel with this approach.
Would it be possible to pass the DynamoDB's PaginatedQueryList or PaginatedScanList to the UI via REST, so that if the javascript pagination accesses the items, that then they are loaded lazily.
From working with other DBs I have never transferred DB entry objects, which is why my code-snippet re-packs the data (item2dto).
In addition, the pagination with DynamoDB appears a bit strange: So far I've seen no possibility to provide the UI with a total count of items. So the UI only has buttons for "next page" and "previous page", without actually knowing how many pages will follow. Directly jumping to page 5 is therefore not possible.
The AWS Console does not load all your data at once to conserve on read capacity. When you get a Scan/Query page, you only get information about how to get the next page, so that is why the console is not able to show you a-priory how many pages of data it can show. Depending on your schema, you may be able to support random page access in your application. This is done by deciding a-priori how large pages will be and second encoding something like a page number in the partition key. Please see this AWS Forum post for details.

How to wait for a method with result callback to complete before continuing (Android)?

So I am a noob at Android, and I'm writing a simple app that uses Google Fit to store the users fitness sessions and step count and then retrieve them.
I have two methods, one that fetches all the sessions from a given date range from the cloud, the next method iterates through these and adds up the step count.
Problem is, that although I call the the fetching method first, the result doesn't come back until after I've added the steps up, so step count is always zero.
Here's my code:
private ArrayList<> results;
#Override
public ArrayList<IndividualSession> readAllSessions(Date dateFrom, Date dateTo) {
/* I haven't included the following code in this question just to keep things clean, but here there was
- The initialisation of the results ArrayList
- Creating the calendar and date objects
- Building the session read request
*/
Fitness.SessionsApi.readSession(mGoogleApiClient, readRequest).setResultCallback(new ResultCallback<SessionReadResult>() {
#Override
public void onResult(SessionReadResult sessionReadResult) {
for (Session session : sessionReadResult.getSessions()) {
List<DataSet> dataSets = sessionReadResult.getDataSet(session);
for (DataSet dataSet : dataSets) {
for (DataPoint dataPoint : dataSet.getDataPoints()) {
// Create new IndividualSession object, add data to it then add it to arraylist
IndividualSession individualSessionObject = new IndividualSession();
individualSessionObject.setFromDate(new Date(session.getStartTime(TimeUnit.SECONDS)));
individualSessionObject.setToDate(new Date(session.getEndTime(TimeUnit.SECONDS)));
individualSessionObject.setStepCount(dataPoint.getValue(Field.FIELD_STEPS).asInt());
results.add(individualSessionObject);
}
}
}
Log.i(TAG, "Number of sessions found while reading: "+results.size());
}
});
return results;
}
#Override
public int getDaySteps(Date dateTo) {
int stepCount = 0; // to be returned
// Sort out the dates
Calendar calFrom = Calendar.getInstance();
calFrom.add(Calendar.HOUR, -24);
// Get the sessions for appropriate date range
ArrayList results = readAllSessions(calFrom.getTime(), dateTo);
Log.i(TAG, "Number of sessions found while trying to get total steps: "+results.size());
// Iterate through sessions to get count steps
Iterator<IndividualSession> it = results.iterator();
while(it.hasNext())
{
IndividualSession obj = it.next();
stepCount += obj.getStepCount();
}
return stepCount;
}
This outputs
"Number of sessions found while trying to get total steps: 0"
"Number of sessions found while reading: 8"
There are two solutions to this :
Option 1 : Use a blocking colleciton
Change the ArrayList<> results to an ArrayBlockingQueue<> results.
After the call to the readAllSessions method in the getDaySteps method, call while(results.take()!=null) { //rest of the logic }
You need some kind of mechanistm to exit the while loop in step 2 when all results are read
Option 2 : Use the await method from PendingResult
Looking at the documentation for SessionsAPI class, the readSessions method seems to return a PendingResult :
public abstract PendingResult readSession
(GoogleApiClient client, SessionReadRequest request)
Reads data from the user's Google Fit store of the specific type(s)
and for the specific session(s) selected from the request parameters.
Looking at the documentation of the await method in PendingResult class :
public abstract R await ()
Blocks until the task is completed. This is not allowed on the UI thread. The returned result object can have an additional failure mode
of INTERRUPTED.
This is what you can do. Instead of chaining the entire call to setResultCallBack, first call readSessions :
results = Fitness.SessionsApi.readSession(mGoogleApiClient, readRequest);
And then wait for the results in the getDaySteps method :
SessionReadResults sessionResults = results.await();
for (Session session : sessionReadResult.getSessions()) {
List<DataSet> dataSets = sessionReadResult.getDataSet(session);
for (DataSet dataSet : dataSets) {
for (DataPoint dataPoint : dataSet.getDataPoints()) {
// Create new IndividualSession object, add data to it then add it to arraylist
IndividualSession individualSessionObject = new IndividualSession();
individualSessionObject.setFromDate(new Date(session.getStartTime(TimeUnit.SECONDS)));
individualSessionObject.setToDate(new Date(session.getEndTime(TimeUnit.SECONDS)));
individualSessionObject.setStepCount(dataPoint.getValue(Field.FIELD_STEPS).asInt());
//use the results
}
}
}
*results must be declared as an instance/class level variable to be accessible in all the methods in the class. The variable result is of type PendingResult<SessionReadResults>. Also, looks like you can do away with the results ArrayList since everything you want can be extracted from the SessionReadResults returned by the await method. One last note, this answer has not been tested with your code because your code sample is not complete.

Java: Code Understanding

I'm new to JAVA, but I know Objective-C. I have to write a server side Custom Code and I'm having trouble with the code below:
/**
* This example will show a user how to write a custom code method
* with two parameters that updates the specified object in their schema
* when given a unique ID and a `year` field on which to update.
*/
public class UpdateObject implements CustomCodeMethod {
#Override
public String getMethodName() {
return "CRUD_Update";
}
#Override
public List<String> getParams() {
return Arrays.asList("car_ID","year");
}
#Override
public ResponseToProcess execute(ProcessedAPIRequest request, SDKServiceProvider serviceProvider) {
String carID = "";
String year = "";
LoggerService logger = serviceProvider.getLoggerService(UpdateObject.class);
logger.debug(request.getBody());
Map<String, String> errMap = new HashMap<String, String>();
/* The following try/catch block shows how to properly fetch parameters for PUT/POST operations
* from the JSON request body
*/
JSONParser parser = new JSONParser();
try {
Object obj = parser.parse(request.getBody());
JSONObject jsonObject = (JSONObject) obj;
// Fetch the values passed in by the user from the body of JSON
carID = (String) jsonObject.get("car_ID");
year = (String) jsonObject.get("year");
//Q1: This is assigning the values to fields in the fetched Object?
} catch (ParseException pe) {
logger.error(pe.getMessage(), pe);
return Util.badRequestResponse(errMap, pe.getMessage());
}
if (Util.hasNulls(year, carID)){
return Util.badRequestResponse(errMap);
}
//Q2: Is this creating a new HashMap? If so, why is there a need?
Map<String, SMValue> feedback = new HashMap<String, SMValue>();
//Q3: This is taking the key "updated year" and assigning a value (year)? Why?
feedback.put("updated year", new SMInt(Long.parseLong(year)));
DataService ds = serviceProvider.getDataService();
List<SMUpdate> update = new ArrayList<SMUpdate>();
/* Create the changes in the form of an Update that you'd like to apply to the object
* In this case I want to make changes to year by overriding existing values with user input
*/
update.add(new SMSet("year", new SMInt(Long.parseLong(year))));
SMObject result;
try {
// Remember that the primary key in this car schema is `car_id`
//Q4: If the Object is updated earlier with update.add... What is the code below doing?
result = ds.updateObject("car", new SMString(carID), update);
//Q5: What's the need for the code below?
feedback.put("updated object", result);
} catch (InvalidSchemaException ise) {
return Util.internalErrorResponse("invalid_schema", ise, errMap); // http 500 - internal server error
} catch (DatastoreException dse) {
return Util.internalErrorResponse("datastore_exception", dse, errMap); // http 500 - internal server error
}
return new ResponseToProcess(HttpURLConnection.HTTP_OK, feedback);
}
}
Q1: Code below is assigning the values to fields in the fetched Object?
carID = (String) jsonObject.get("car_ID");
year = (String) jsonObject.get("year");
Q2: Is this creating a new HashMap? If so, why is there a need?
Map<String, SMValue> feedback = new HashMap<String, SMValue>();
Q3: This is taking the key "updated year" and assigning a value (year)? Why?
feedback.put("updated year", new SMInt(Long.parseLong(year)));
Q4: If the Object is updated earlier with update.add... What is the code below doing?
result = ds.updateObject("car", new SMString(carID), update);
Q5: What's the code below doing?
feedback.put("updated object", result);
Original Code
SMSet
SMInt
Q1: They read from the fetched JSON object and stores the values of the fields car_ID and year in two local variables with the same names.
Q2: Yes. The feedback seems to be a map that will be sent back to the client as JSON
Q3: It stores the value read into the local variable 'year' (as described earlier) in the newly created hashmap 'feedback'
Q4: Not sure, I assume the ds object is some sort of database. If so it looks like it takes the updated values stored in the hashmap 'update' and pushes it to the database.
Q5: It stores the "result" object under the key "updated object" in the feedback hashmap.
Hope this helps :)
Q1
No, it does not appear to be setting a class member variable, but rather a variable local to the execute() method. As soon as the method returns, those local vars are cleaned up by the GC. Well, not really, but they are now subject to GC, but that's getting really technical.
Q2
Yes, you are creating a HashMap and putting it's reference into a Map. Map is an interface, and it's good practice in Java to reference thing like this. This way you are not tying your code to a specific implementation. I believe in Objective-C they are know as Prototypes???
Q3
I am not sure why they are doing this. I assume somewhere in the code the feedback Map is used, and that value is plucked back out. Think of Maps as an NSDictionary. It looks like "year" is a String, so they use Long.parseLong() to convert it. Not sure what SMInt is...from the name it looks like a custom class that represents a "small int"???
Q4
I don't know what DataService is, but I have to guess its some sort of service the reads/write data??? From the method, I am guessing its calling the service to update the values you just changed.
Q5
Again, feedback is a Map...it's putting result in the "updated object" key of that map.

Deadbolt - Play Framework - How to check a #RestrictedResource with parameters in a controller?

With Deadbolt's module we can check the restrictedResource with a ressource name and parameters in the view.
For example in my view, I have it, and it works well:
#{deadbolt.restrictedResource resourceKeys:['Domain'] , resourceParameters:['domainid':domain.id]}
<li>${domain.title}</li>
#{/deadbolt.restrictedResource}
But in my controller, I just can check the ressource name but I don't find a way to check it in my RestrictedResourcesHandler passing the domainid with.
I am looking for a solution to do something like that:
#RestrictedResource(name = {"Domain"}, params = {domainid})
public static void showDomain(String domainid)
{
}
Thanks in advance
It's not possible to have dynamic information in an annotation, but you can use params to define the name of an incoming value in the request. However, this information isn't passed into the handler at the moment because it expects a map. While you can pass in a map of parameters from the restrictedResource tag, you can't do this from an annotation so an empty map is passed into the handler.
Your best approach here is to pull a well-known parameter name from the request object. I need to have a rethink about the best way to do this without breaking backwards compatibility.
Steve (author of Deadbolt)
I've found a way the solved the problem, not the best I think, but it is the Steve Chaloner's solution (Deadbolt's creator), and it works.
For example, if your Controller's method argument is named "id", and you want to check this id inside your checkAccess method :
// Controller's method :
#RestrictedResource(name = {"Domain"})
public static void showDomain(String id){}
Just check at the beginning of your checkAccess method the Map "resourceParameters" is empty, and use the request object to get the parameters:
public AccessResult checkAccess(List<String> resourceNames,
Map<String, String> resourceParameters)
{
Map<String, String> hashm = new HashMap<String,String>();
if(resourceParameters != null && !resourceParameters.isEmpty()){
hashm = resourceParameters;
}else if(Http.Request.current().routeArgs!= null && !Http.Request.current().routeArgs.isEmpty()){
hashm = Http.Request.current().routeArgs;
}
}
Then just have to foreach your hashmap inside your checkAccess method to get your Controller's method argument and check the Access as you wish.
for (Map.Entry<String,String> mp : hashm.entrySet())
{
// Get the id argument
if(mp.getKey().equals("id"))
{
// Do something with the value..
mp.getValue()
}
}

Is there a way to iterate through HttpServletRequest.getAttributeNames() more than once?

I'm trying to log the contents of the HttpServletRequest attributes collection. I need to do this when the servlet first starts, and again right before the servlet is finished. I'm doing this in an attempt to understand a crufty and ill-maintained servlet. Because I need to have as little impact as possible, servlet filters are not an option.
So here's the problem. When the servlet starts, I'll iterate through the enumeration returned by HttpServletRequest.getAttributeNames(). However, when I want to iterate through it again, getAttributeNames().hasMoreElements() returns "false"! I can't find any way to "reset" the enumeration. What's worse is that, even if I add attributes to the collection using HttpServletRequest.setAttribute(), I still get a result of "false" when I call getAttributeNames().hasMoreElements().
Is this really possible? Is there really no way to iterate through the attribute names more than once?
By request, here's my code. It's pretty straightforward -- don't think I'm doing any funny stuff.
/**
*
* Returns the contents of the Attributes collection, formatted for the InterfaceTracker loglines
*
*/
#SuppressWarnings("unchecked")
public static String getAttributes(HttpServletRequest request) {
try {
StringBuilder toLog = new StringBuilder();
Enumeration attributeNames = request.getAttributeNames();
while(attributeNames.hasMoreElements()) {
String current = (String) attributeNames.nextElement();
toLog.append(current + "=" + request.getAttribute(current));
if(attributeNames.hasMoreElements()) {
toLog.append(", ");
}
}
return "TRACKER_ATTRIBUTES={"+ toLog.toString() + "}";
}
catch (Exception ex) {
return "TRACKER_ATTRIBUTES={" + InterfaceTrackerValues.DATA_UNKNOWN_EXCEPTION_THROWN + "}";
}
}
Perhaps you should post the code where you call HttpServletRequest.setAttribute().
At this point it would seem that your crufty and ill-maintained servlet is removing attributes between your two calls to getAttributeNames(), but without any code samples it's hard to say.
UPDATE
Nothing in your code is jumping out at me as being faulty... so I crafted an extremely simple test case inside handleRequest() and gave it a whirl (using jboss-eap-4.3 as my container). I had to manually set an attribute first, as my understanding of request attributes is they are always set server side (i.e. if I didn't set it then I didn't get any output as the Enumeration returned by getAttributeNames() was empty).
request.setAttribute("muckingwattrs", "Strange");
Enumeration attrs = request.getAttributeNames();
while(attrs.hasMoreElements()) {
System.out.println(attrs.nextElement());
}
System.out.println("----------------------------");
Enumeration attrs2 = request.getAttributeNames();
while(attrs2.hasMoreElements()) {
System.out.println(attrs2.nextElement());
}
output
INFO [STDOUT] muckingwattrs
INFO [STDOUT] ----------------------------
INFO [STDOUT] muckingwattrs
So perhaps your container doesn't implement getAttributeNames() correctly? Maybe try an extremely simple test case like mine directly in handleRequest() or doGet()/doPost().

Categories