I used the following POJO to create an App Engine Endpoint.
package com.incident.incidentreporter;
import java.util.Date;
import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;
import javax.persistence.Entity;
import javax.persistence.Id;
import com.google.appengine.api.datastore.Blob;
#Entity
public class Incidents {
#Id
#PrimaryKey
#Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
private Long id;
#Persistent
private Date incidentdate;
#Persistent
private String incidentdetails;
#Persistent
private double lat;
#Persistent
private double lngtitude;
#Persistent
private String reporter;
#Persistent
private Blob incidentimage;
public Incidents() {
// TODO Auto-generated constructor stub
}
public Incidents(Long id, Date incidentdate, String incidentdetails,
double lat, double lngtitude, String reporter, Blob incidentimage) {
super();
this.id = id;
this.incidentdate = incidentdate;
this.incidentdetails = incidentdetails;
this.lat = lat;
this.lngtitude = lngtitude;
this.reporter = reporter;
this.incidentimage = incidentimage;
}
/**
* #return the incidentdate
*/
public Date getIncidentdate() {
return incidentdate;
}
/**
* #param incidentdate the incidentdate to set
*/
public void setIncidentdate(Date incidentdate) {
this.incidentdate = incidentdate;
}
/**
* #return the incidentdetails
*/
public String getIncidentdetails() {
return incidentdetails;
}
/**
* #param incidentdetails the incidentdetails to set
*/
public void setIncidentdetails(String incidentdetails) {
this.incidentdetails = incidentdetails;
}
/**
* #return the lat
*/
public double getLat() {
return lat;
}
/**
* #param lat the lat to set
*/
public void setLat(double lat) {
this.lat = lat;
}
/**
* #return the lngtitude
*/
public double getLngtitude() {
return lngtitude;
}
/**
* #param lngtitude the lngtitude to set
*/
public void setLngtitude(double lngtitude) {
this.lngtitude = lngtitude;
}
/**
* #return the reporter
*/
public String getReporter() {
return reporter;
}
/**
* #param reporter the reporter to set
*/
public void setReporter(String reporter) {
this.reporter = reporter;
}
/**
* #return the incidentimage
*/
public Blob getIncidentimage() {
return incidentimage;
}
/**
* #param incidentimage the incidentimage to set
*/
public void setIncidentimage(Blob incidentimage) {
this.incidentimage = incidentimage;
}
/**
* #return the id
*/
public Long getId() {
return id;
}
}
The auto generated endpoint code is as below.
package com.incident.incidentreporter;
import com.incident.incidentreporter.EMF;
import com.google.api.server.spi.config.Api;
import com.google.api.server.spi.config.ApiMethod;
import com.google.api.server.spi.config.ApiNamespace;
import com.google.api.server.spi.response.CollectionResponse;
import com.google.appengine.api.datastore.Cursor;
import com.google.appengine.datanucleus.query.JPACursorHelper;
import java.util.List;
import javax.annotation.Nullable;
import javax.inject.Named;
import javax.persistence.EntityExistsException;
import javax.persistence.EntityNotFoundException;
import javax.persistence.EntityManager;
import javax.persistence.Query;
#Api(name = "incidentsendpoint", namespace = #ApiNamespace(ownerDomain = "incident.com", ownerName = "incident.com", packagePath = "incidentreporter"))
public class IncidentsEndpoint {
/**
* This method lists all the entities inserted in datastore.
* It uses HTTP GET method and paging support.
*
* #return A CollectionResponse class containing the list of all entities
* persisted and a cursor to the next page.
*/
#SuppressWarnings({ "unchecked", "unused" })
#ApiMethod(name = "listIncidents")
public CollectionResponse<Incidents> listIncidents(
#Nullable #Named("cursor") String cursorString,
#Nullable #Named("limit") Integer limit) {
EntityManager mgr = null;
Cursor cursor = null;
List<Incidents> execute = null;
try {
mgr = getEntityManager();
Query query = mgr.createQuery("select from Incidents as Incidents");
if (cursorString != null && cursorString != "") {
cursor = Cursor.fromWebSafeString(cursorString);
query.setHint(JPACursorHelper.CURSOR_HINT, cursor);
}
if (limit != null) {
query.setFirstResult(0);
query.setMaxResults(limit);
}
execute = (List<Incidents>) query.getResultList();
cursor = JPACursorHelper.getCursor(execute);
if (cursor != null)
cursorString = cursor.toWebSafeString();
// Tight loop for fetching all entities from datastore and accomodate
// for lazy fetch.
for (Incidents obj : execute)
;
} finally {
mgr.close();
}
return CollectionResponse.<Incidents> builder().setItems(execute)
.setNextPageToken(cursorString).build();
}
/**
* This method gets the entity having primary key id. It uses HTTP GET method.
*
* #param id the primary key of the java bean.
* #return The entity with primary key id.
*/
#ApiMethod(name = "getIncidents")
public Incidents getIncidents(#Named("id") Long id) {
EntityManager mgr = getEntityManager();
Incidents incidents = null;
try {
incidents = mgr.find(Incidents.class, id);
} finally {
mgr.close();
}
return incidents;
}
/**
* This inserts a new entity into App Engine datastore. If the entity already
* exists in the datastore, an exception is thrown.
* It uses HTTP POST method.
*
* #param incidents the entity to be inserted.
* #return The inserted entity.
*/
#ApiMethod(name = "insertIncidents")
public Incidents insertIncidents(Incidents incidents) {
EntityManager mgr = getEntityManager();
try {
mgr.persist(incidents);
} finally {
mgr.close();
}
return incidents;
}
/**
* This method is used for updating an existing entity. If the entity does not
* exist in the datastore, an exception is thrown.
* It uses HTTP PUT method.
*
* #param incidents the entity to be updated.
* #return The updated entity.
*/
#ApiMethod(name = "updateIncidents")
public Incidents updateIncidents(Incidents incidents) {
EntityManager mgr = getEntityManager();
try {
if (!containsIncidents(incidents)) {
throw new EntityNotFoundException("Object does not exist");
}
mgr.persist(incidents);
} finally {
mgr.close();
}
return incidents;
}
/**
* This method removes the entity with primary key id.
* It uses HTTP DELETE method.
*
* #param id the primary key of the entity to be deleted.
*/
#ApiMethod(name = "removeIncidents")
public void removeIncidents(#Named("id") Long id) {
EntityManager mgr = getEntityManager();
try {
Incidents incidents = mgr.find(Incidents.class, id);
mgr.remove(incidents);
} finally {
mgr.close();
}
}
private boolean containsIncidents(Incidents incidents) {
EntityManager mgr = getEntityManager();
boolean contains = true;
try {
Incidents item = mgr.find(Incidents.class, incidents.getId());
if (item == null) {
contains = false;
}
} finally {
mgr.close();
}
return contains;
}
private static EntityManager getEntityManager() {
return EMF.get().createEntityManager();
}
}
I successfully generated client endpoint libraries and deployed the app to Google App engine.
I thought the key field will be autogenerated.
The problem i am facing now is that the code that inserts the incident fails if i do not set the id field.
Are there errors in my code which caused this problem?
since this field is of Long datatype and must be unique, is there away of adding code to generate it?
I am using an android client app .
Please advise .
Ronald
This part
Query query = mgr.createQuery("select from Incidents as Incidents");
looks very suspicious for me. It is missing the column list from the select clause and I believe you should review the work-flow of all the logistics working with your columns. This might be enough to fix your problem, however, I believe this is where you should start researching your problem.
Related
I created a library project which I built as an AAR file and later included in another project. It's in the libs folder, and the main gradle.build file includes it: implementation fileTree(include: ['*.jar','*.aar'], dir: 'libs')
When I try to use classes of this aar file, all of them are available except one class. I initially imagined it could be Proguard, but I even removed Proguard and it is still not available. It's a public class, and it's even there when I decompile the AAR file.
This is the content:
package com.onboarding;
import android.content.Context;
import android.content.Intent;
import android.support.annotation.Keep;
/**
*
*/
#Keep
public class Builder {
/**
*
*/
public static String mainColor = null;
public static String baseUrl = null;
public static Class firstActivity = null;
public static Class onboardingSettingsActivity = null;
/**
*
*/
public static String tosUrl = null;
public static String privacyUrl = null;
public static String cookieUrl = null;
public static String contactsLearnMoreUrl = null;
/**
*
*/
private static Builder builder = null;
/**
*
*/
private Builder() {}
/**
*
*/
public static Builder init() {
if (builder == null) {
builder = new Builder();
}
return builder;
}
/**
*
*/
public void start(final Context context) {
final Intent intent = new Intent(context, Onboarding1.class);
context.startActivity(intent);
}
/**
*
*/
public Builder setMainColor(final String color) {
mainColor = color;
return this;
}
/**
*
*/
public Builder setBaseUrl(final String url) {
baseUrl = url;
return this;
}
/**
*
*/
public Builder setFirstActivity(final Class c) {
firstActivity = c;
return this;
}
/**
*
*/
public Builder setOnboardingSettingsActivity(final Class c) {
onboardingSettingsActivity = c;
return this;
}
/**
*
*/
public Builder setTosUrl(final String u) {
tosUrl = u;
return this;
}
/**
*
*/
public Builder setPrivacyUrl(final String u) {
privacyUrl = u;
return this;
}
/**
*
*/
public Builder setCookieUrl(final String u) {
cookieUrl = u;
return this;
}
/**
*
*/
public Builder setContactsLearnMoreUrl(final String u) {
contactsLearnMoreUrl = u;
return this;
}
}
Any idea why I can't access this class from the main project?
Thanks!
After randomly working then not working, it appears that a File -> Invalidate caches / restart in Android Studio did the trick.
So apparently Android Studio caches some part of the aar, which even a Clean nor Rebuild would fix.
Hi did you check your dependencies? if it was added in there? what i meant is this one Also refer to this documentation for the difference between implementation and api.
I have form that can have variable number of EditText that needs to be validated before form submission. I can perform validation check if EditTexts are fixed in number like following -
Observable<CharSequence> emailObservable = RxTextView.textChanges(editEmail).skip(1);
Observable<CharSequence> passwordObservable = RxTextView.textChanges(editPassword).skip(1);
mFormValidationSubscription = Observable.combineLatest(emailObservable, passwordObservable,
(newEmail, newPassword) -> {
boolean emailValid = !TextUtils.isEmpty(newEmail) && android.util.Patterns.EMAIL_ADDRESS.matcher(newEmail).matches();
if(!emailValid) {
emailInputLayout.setError(getString(R.string.error_invalid_email));
emailInputLayout.setErrorEnabled(true);
}else {
emailInputLayout.setError(null);
emailInputLayout.setErrorEnabled(false);
}
boolean passValid = !TextUtils.isEmpty(newPassword) && newPassword.length() > 4;
if (!passValid) {
passwordInputLayout.setError(getString(R.string.error_invalid_password));
passwordInputLayout.setErrorEnabled(true);
} else {
passwordInputLayout.setError(null);
passwordInputLayout.setErrorEnabled(true);
}
return emailValid && passValid;
}).subscribe(isValid ->{
mSubmitButton.setEnabled(isValid);
});
But now as there are variable number of inputs I tried creating a list of Observable<CharSequence> and Observable.combineLatest() but I'm stuck as to proceed with that.
List<Observable<CharSequence>> observableList = new ArrayList<>();
for(InputRule inputRule : mMaterial.getRules()) {
View vInputRow = inflater.inflate(R.layout.item_material_input_row, null, false);
StyledEditText styledEditText = ((StyledEditText)vInputRow.findViewById(R.id.edit_input));
styledEditText.setHint(inputRule.getName());
Observable<CharSequence> observable = RxTextView.textChanges(styledEditText).skip(1);
observableList.add(observable);
linearLayout.addView(vInputRow);
}
Observable.combineLatest(observableList,......); // What should go in place of these "......"
How can I perform checks for a valid charsequence for each input field. I looked into flatMap(), map(), filter() methods but I don't know how to use them.
Yes, you process abitrary number of Observables in .combineLatest(), but there is still workaround. I got interested in this problem and came up with following solution- we can store information about some data source - last value and source ID (String and resource id) and tunnell all data into some common pipe. For that we can use PublishSubject. We also need to track connection state, for that we should save Subscription to each source on subscription and sever it when we unsubscribe from that source.
We store last data from each source, so we can tell user what source just emitted new value, callback will only contain source id. User can get last value of any source by source id.
I came up with the following code:
import android.util.Log;
import android.widget.EditText;
import com.jakewharton.rxbinding.widget.RxTextView;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import rx.Observable;
import rx.Subscription;
import rx.functions.Action1;
import rx.subjects.PublishSubject;
public class MultiSourceCombinator {
String LOG_TAG = MultiSourceCombinator.class.getSimpleName();
/**
* We can't handle arbitrary number of sources by CombineLatest, but we can pass data along
* with information about source (sourceId)
*/
private static class SourceData{
String data = "";
Integer sourceId = 0;
}
/**
* Keep id of source, subscription to that source and last value emitted
* by source. This value is passed when source is attached
*/
private class SourceInfo{
Subscription sourceTracking;
Integer sourceId;
SourceData lastData;
SourceInfo(int sourceId, String data){
this.sourceId = sourceId;
// initialize last data with empty value
SourceData d = new SourceData();
d.data = data;
d.sourceId = sourceId;
this.lastData = d;
}
}
/**
* We can tunnel data from all sources into single pipe. Subscriber can treat it as
* Observable<SourceData>
*/
private PublishSubject<SourceData> dataDrain;
/**
* Stores all sources by their ids.
*/
Map<Integer, SourceInfo> sources;
/**
* Callback, notified whenever source emit new data. it receives source id.
* When notification is received by client, it can get value from source by using
* getLastSourceValue(sourceId) method
*/
Action1<Integer> sourceUpdateCallback;
public MultiSourceCombinator(){
dataDrain = PublishSubject.create();
sources = new HashMap<>();
sourceUpdateCallback = null;
// We have to process data, ccoming from common pipe
dataDrain.asObservable()
.subscribe(newValue -> {
if (sourceUpdateCallback == null) {
Log.w(LOG_TAG, "Source " + newValue.sourceId + "emitted new value, " +
"but used did't set callback ");
} else {
sourceUpdateCallback.call(newValue.sourceId);
}
});
}
/**
* Disconnect from all sources (sever Connection (s))
*/
public void stop(){
Log.i(LOG_TAG, "Unsubscribing from all sources");
// copy references to aboid ConcurrentModificatioinException
ArrayList<SourceInfo> t = new ArrayList(sources.values());
for (SourceInfo si : t){
removeSource(si.sourceId);
}
// right now there must be no active sources
if (!sources.isEmpty()){
throw new RuntimeException("There must be no active sources");
}
}
/**
* Create new source from edit field, subscribe to this source and save subscription for
* further tracking.
* #param editText
*/
public void addSource(EditText editText, int sourceId){
if (sources.containsKey(sourceId)){
Log.e(LOG_TAG, "Source with id " + sourceId + " already exist");
return;
}
Observable<CharSequence> source = RxTextView.textChanges(editText).skip(1);
String lastValue = editText.getText().toString();
Log.i(LOG_TAG, "Source with id " + sourceId + " has data " + lastValue);
// Redirect data coming from source to common pipe, to do that attach source id to
// data string
Subscription sourceSubscription = source.subscribe(text -> {
String s = new String(text.toString());
SourceData nextValue = new SourceData();
nextValue.sourceId = sourceId;
nextValue.data = s;
Log.i(LOG_TAG, "Source " + sourceId + "emits new value: " + s);
// save vlast value
sources.get(sourceId).lastData.data = s;
// pass new value down pipeline
dataDrain.onNext(nextValue);
});
// create SourceInfo
SourceInfo sourceInfo = new SourceInfo(sourceId, lastValue);
sourceInfo.sourceTracking = sourceSubscription;
sources.put(sourceId, sourceInfo);
}
/**
* Unsubscribe source from common pipe and remove it from list of sources
* #param sourceId
* #throws IllegalArgumentException
*/
public void removeSource(Integer sourceId) throws IllegalArgumentException {
if (!sources.containsKey(sourceId)){
throw new IllegalArgumentException("There is no source with id: " + sourceId);
}
SourceInfo si = sources.get(sourceId);
Subscription s = si.sourceTracking;
if (null != s && !s.isUnsubscribed()){
Log.i(LOG_TAG, "source " + sourceId + " is active, unsubscribing from it");
si.sourceTracking.unsubscribe();
si.sourceTracking = null;
}
// source is disabled, remove it from list
Log.i(LOG_TAG, "Source " + sourceId + " is disabled ");
sources.remove(sourceId);
}
/**
* User can get value from any source by using source ID.
* #param sourceId
* #return
* #throws IllegalArgumentException
*/
public String getLastSourceValue(Integer sourceId) throws IllegalArgumentException{
if (!sources.containsKey(sourceId)){
throw new IllegalArgumentException("There is no source with id: " + sourceId);
}
String lastValue = sources.get(sourceId).lastData.data;
return lastValue;
}
public void setSourceUpdateCallback(Action1<Integer> sourceUpdateFeedback) {
this.sourceUpdateCallback = sourceUpdateFeedback;
}
}
And we can use it in UI like this:
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.widget.EditText;
import android.widget.Toast;
import butterknife.BindView;
import butterknife.ButterKnife;
public class EdiTextTestActivity extends Activity {
#BindView(R.id.aet_et1)
public EditText et1;
#BindView(R.id.aet_et2)
public EditText et2;
#BindView(R.id.aet_et3)
public EditText et3;
private MultiSourceCombinator multiSourceCombinator;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_edit_text_test);
ButterKnife.bind(this);
multiSourceCombinator = new MultiSourceCombinator();
multiSourceCombinator.setSourceUpdateCallback(id -> {
Toast.makeText(EdiTextTestActivity.this, "New value from source: " + id + " : " +
multiSourceCombinator.getLastSourceValue(id), Toast.LENGTH_SHORT).show();
});
}
#Override
protected void onPause() {
// stop tracking all fields
multiSourceCombinator.stop();
super.onPause();
}
#Override
protected void onResume() {
super.onResume();
// Register fields
multiSourceCombinator.addSource(et1, R.id.aet_et1);
multiSourceCombinator.addSource(et2, R.id.aet_et2);
multiSourceCombinator.addSource(et3, R.id.aet_et3);
}
}
I have a solution for you without using lambda expressions (as I could not compile it with lambdas).
Use the same operator as you wanted:
public static <T, R> Observable<R> combineLatest(List<? extends Observable<? extends T>> sources, FuncN<? extends R> combineFunction)
Observable.combineLatest(observableList, new FuncN<Boolean>() {
#Override
public Boolean call(Object... objects) {
boolean isValid = true;
CharSequence input;
for (int i = 0; i < objects.length; i++) {
input = (CharSequence) objects[i];
switch (i) {
case 1:
//First text field value
break;
case 2:
//Second text field value
break;
default:
isValid = false;
}
}
return isValid;
}
})
The reason lambda expressions don't work is probably in second parameter of the function combineLatest(...):
public interface FuncN<R> extends Function {
R call(Object... args);
}
According to this post implementing Arbitrary Number of Arguments is hard to do and workarounds need to be created.
RxJava v2 is compatible with Java 8 and has a different implementation of combineLatest
I used R. Zagórski's answer as guide on how to do this with Kotlin
This is what worked for me in the end.
val ob1 = RxTextView.textChanges(field1).skip(1)
val ob2 = RxTextView.textChanges(field2).skip(1)
val ob3 = RxTextView.textChanges(field3).skip(1)
val observableList = arrayListOf<Observable<CharSequence>>()
observableList.add(ob1)
observableList.add(ob3)
val formValidator = Observable.combineLatest(observableList, {
var isValid = true
it.forEach {
val string = it.toString()
if (string.isEmpty()) {
isValid = false
}
}
return#combineLatest isValid
})
formValidator.subscribe { isValid ->
if (isValid) {
//do something
} else {
//do something
}
}
I have amethod which returns the SSID of the strongest WiFi acces point. Tha data for the mapping is in file names"ssid_number.txt" in the raw folder. How can I parse this file in my case with GSON library to get the number 4 if the strongest WiFi access point"KD WLAN Hotspot" is?
{
"KD Privat": 1,
"KD WLAN Hotspot": 4,
"treeWifi": 9,
"cafeWifi": 5 //I have here more that 200 WIFI access point
}
I did it the next way.
I had JSON string pulled from a url. Then,
Gson gson = new Gson(); // create Gson obj
currentResponse = gson.fromJson(resultJSON, City.class);
currentResponse is a json.toString() output.
Next, create new class for your json output with all fields corresponding to json. Look at my working code:
public class City {
#SerializedName("name")
public String cityName;
public String getCityName() {
return cityName;
}
}
In your case it would be like:
public class WiFi {
#SerializedName("cafeWifi")
public int wiFiAmount;
public int getWiFiAmount() {
return wiFiAmount;
}
}
Get your wifi amount by this method:
WiFi wifi = new WiFi();
int a = wifi.getWiFiAmount();
Looking at your data it seems your identifiers are not constant.
In this case it would work if you use a typemap. So something like this:
HashMap<String, Integer> mMap = null;
Type type = new TypeToken<HashMap<String, Integer>>() {}.getType();
mMap = new Gson().fromJson(json, type);
Create POJO for your json response.
public class JsonResponsePojo {
#SerializedName("KD Privat")
#Expose
private Integer KDPrivat;
#SerializedName("KD WLAN Hotspot")
#Expose
private Integer KDWLANHotspot;
#Expose
private Integer treeWifi;
#Expose
private Integer cafeWifi;
/**
*
* #return
* The KDPrivat
*/
public Integer getKDPrivat() {
return KDPrivat;
}
/**
*
* #param KDPrivat
* The KD Privat
*/
public void setKDPrivat(Integer KDPrivat) {
this.KDPrivat = KDPrivat;
}
/**
*
* #return
* The KDWLANHotspot
*/
public Integer getKDWLANHotspot() {
return KDWLANHotspot;
}
/**
*
* #param KDWLANHotspot
* The KD WLAN Hotspot
*/
public void setKDWLANHotspot(Integer KDWLANHotspot) {
this.KDWLANHotspot = KDWLANHotspot;
}
/**
*
* #return
* The treeWifi
*/
public Integer getTreeWifi() {
return treeWifi;
}
/**
*
* #param treeWifi
* The treeWifi
*/
public void setTreeWifi(Integer treeWifi) {
this.treeWifi = treeWifi;
}
/**
*
* #return
* The cafeWifi
*/
public Integer getCafeWifi() {
return cafeWifi;
}
/**
*
* #param cafeWifi
* The cafeWifi
*/
public void setCafeWifi(Integer cafeWifi) {
this.cafeWifi = cafeWifi;
}
}
Read wifi -HotSpot
Gson gson=new Gson();
JsonResponsePojo data=gson.fromJson(responseString, JsonResponsePojo.class);
String kdWLanHotSpot=data.getKDPrivat();
I recently switched from JDO to Objectify in an effort to simplify some of my backend (I'm a beginner with App Engine and server side stuff in general).
I have an entity, AppVersion that used to look like this in Cloud Console:
When I switched to objectify, it no longer has the option to filter by minVersionRequired and looks like this:
Entity Code (Before)
import javax.persistence.Entity;
import javax.persistence.Id;
#Entity
public class AppVersion {
#Id
private String applicationName;
private int minVersionRequired;
public String getApplicationName() {
return applicationName;
}
public int getMinVersionRequired() {
return minVersionRequired;
}
public void setApplicationName(String applicationName) {
this.applicationName = applicationName;
}
public void setminVersionRequired(int minVersionRequired) {
this.minVersionRequired = minVersionRequired;
}
}
Entity Code (After)
import com.googlecode.objectify.annotation.Entity;
import com.googlecode.objectify.annotation.Id;
#Entity
public class AppVersion {
#Id
private String applicationName;
private int minVersionRequired;
public String getApplicationName() {
return applicationName;
}
public int getMinVersionRequired() {
return minVersionRequired;
}
public void setApplicationName(String applicationName) {
this.applicationName = applicationName;
}
public void setminVersionRequired(int minVersionRequired) {
this.minVersionRequired = minVersionRequired;
}
}
Endpoint Code (Before) Note that this was autogenerated in Eclipse
import com.companionfree.zooperthemeviewer.EMF;
import com.google.api.server.spi.config.Api;
import com.google.api.server.spi.config.ApiMethod;
import com.google.api.server.spi.config.ApiNamespace;
import com.google.api.server.spi.response.CollectionResponse;
import com.google.appengine.api.datastore.Cursor;
import com.google.appengine.datanucleus.query.JPACursorHelper;
import java.util.List;
import javax.annotation.Nullable;
import javax.inject.Named;
import javax.persistence.EntityExistsException;
import javax.persistence.EntityNotFoundException;
import javax.persistence.EntityManager;
import javax.persistence.Query;
#Api(name = "appversionendpoint", namespace = #ApiNamespace(ownerDomain = "company.com", ownerName = "company.com", packagePath = "app"))
public class AppVersionEndpoint {
/**
* This method lists all the entities inserted in datastore.
* It uses HTTP GET method and paging support.
*
* #return A CollectionResponse class containing the list of all entities
* persisted and a cursor to the next page.
*/
#SuppressWarnings({ "unchecked", "unused" })
#ApiMethod(name = "listAppVersion")
public CollectionResponse<AppVersion> listAppVersion(
#Nullable #Named("cursor") String cursorString,
#Nullable #Named("limit") Integer limit) {
EntityManager mgr = null;
Cursor cursor = null;
List<AppVersion> execute = null;
try {
mgr = getEntityManager();
Query query = mgr
.createQuery("select from AppVersion as AppVersion");
if (cursorString != null && cursorString != "") {
cursor = Cursor.fromWebSafeString(cursorString);
query.setHint(JPACursorHelper.CURSOR_HINT, cursor);
}
if (limit != null) {
query.setFirstResult(0);
query.setMaxResults(limit);
}
execute = (List<AppVersion>) query.getResultList();
cursor = JPACursorHelper.getCursor(execute);
if (cursor != null)
cursorString = cursor.toWebSafeString();
// Tight loop for fetching all entities from datastore and accomodate
// for lazy fetch.
for (AppVersion obj : execute)
;
} finally {
mgr.close();
}
return CollectionResponse.<AppVersion> builder().setItems(execute)
.setNextPageToken(cursorString).build();
}
/**
* This method gets the entity having primary key id. It uses HTTP GET method.
*
* #param id the primary key of the java bean.
* #return The entity with primary key id.
*/
#ApiMethod(name = "getAppVersion")
public AppVersion getAppVersion(#Named("id") String id) {
EntityManager mgr = getEntityManager();
AppVersion appversion = null;
try {
appversion = mgr.find(AppVersion.class, id);
} finally {
mgr.close();
}
return appversion;
}
/**
* This inserts a new entity into App Engine datastore. If the entity already
* exists in the datastore, an exception is thrown.
* It uses HTTP POST method.
*
* #param appversion the entity to be inserted.
* #return The inserted entity.
*/
#ApiMethod(name = "insertAppVersion")
public AppVersion insertAppVersion(AppVersion appversion) {
EntityManager mgr = getEntityManager();
try {
if (containsAppVersion(appversion)) {
throw new EntityExistsException("Object already exists");
}
mgr.persist(appversion);
} finally {
mgr.close();
}
return appversion;
}
/**
* This method is used for updating an existing entity. If the entity does not
* exist in the datastore, an exception is thrown.
* It uses HTTP PUT method.
*
* #param appversion the entity to be updated.
* #return The updated entity.
*/
#ApiMethod(name = "updateAppVersion")
public AppVersion updateAppVersion(AppVersion appversion) {
EntityManager mgr = getEntityManager();
try {
if (!containsAppVersion(appversion)) {
throw new EntityNotFoundException("Object does not exist");
}
mgr.persist(appversion);
} finally {
mgr.close();
}
return appversion;
}
/**
* This method removes the entity with primary key id.
* It uses HTTP DELETE method.
*
* #param id the primary key of the entity to be deleted.
*/
#ApiMethod(name = "removeAppVersion")
public void removeAppVersion(#Named("id") String id) {
EntityManager mgr = getEntityManager();
try {
AppVersion appversion = mgr.find(AppVersion.class, id);
mgr.remove(appversion);
} finally {
mgr.close();
}
}
private boolean containsAppVersion(AppVersion appversion) {
EntityManager mgr = getEntityManager();
boolean contains = true;
try {
AppVersion item = mgr.find(AppVersion.class,
appversion.getApplicationName());
if (item == null) {
contains = false;
}
} finally {
mgr.close();
}
return contains;
}
private static EntityManager getEntityManager() {
return EMF.get().createEntityManager();
}
}
Endpoints Code (After) Note this was created by me in Android Studio
import com.google.api.server.spi.config.Api;
import com.google.api.server.spi.config.ApiMethod;
import com.google.api.server.spi.config.ApiNamespace;
import com.google.api.server.spi.response.CollectionResponse;
import java.util.List;
import javax.inject.Named;
import static com.company.backend.OfyService.ofy;
#Api(name = "appversionendpoint", version = "v1", namespace =
#ApiNamespace(ownerDomain = "backend.company.com",
ownerName = "backend.company.com", packagePath = ""))
public class AppVersionEndpoint {
#ApiMethod(name = "listAppVersion")
public CollectionResponse<AppVersion> listAppVersion() {
List<AppVersion> execute;
execute = ofy().load().type(AppVersion.class).list();
return CollectionResponse.<AppVersion> builder().setItems(execute).build();
}
/**
* This method gets the entity having primary key id. It uses HTTP GET method.
*
* #param id the primary key of the java bean.
* #return The entity with primary key id (null if DNE).
*/
#ApiMethod(name = "getAppVersion")
public AppVersion getAppVersion(#Named("id") String id) {
return ofy().load().type(AppVersion.class).id(id).now();
}
/**
* This inserts a new entity into App Engine datastore. If the entity already
* exists in the datastore, an exception is thrown.
* It uses HTTP POST method.
*
* #param appversion the entity to be inserted.
* #return The inserted entity.
*/
#ApiMethod(name = "insertAppVersion")
public AppVersion insertAppVersion(AppVersion appversion) {
AppVersion exist = getAppVersion(appversion.getApplicationName());
AppVersion result;
if (exist == null) {
ofy().save().entity(appversion).now();
result = getAppVersion(appversion.getApplicationName());
} else {
throw new IllegalArgumentException(appversion.getApplicationName() + " exists already");
}
return result;
}
}
I would much prefer to have it filterable like it was originally but I don't know why it is different. Can anyone fill me in?
There are two things going on here: firstly, by default, Objectify assumes you don't want to index your class's properties (this keeps your datastore indexes lean and mean). Secondly, I believe the new Datastore console's filter UI only shows properties which have indexes associated with them (as you can't filter on unindexed properties).
So, if you want to be able to query or sort by minVersionRequired, just add an #Index annotation to that field in your POJO and Objectify will use the setIndexedProperty() method in underlying Entity class within the low level Datastore API.
If you want to index all the properties in your class by default, you can put the #Index annotation on the class and then #Unindex any you specifically don't want indexed.
I am currently in the process of creating a high performance mobile application. Now i am looking at various design patterns for consuming rest services. One such pattern that stands out is the Google IO discussion here. How i have am looking at the code to develop this design. I will be using Spring Rest for doing the actual HTTP Rest and serialization to POJO with the Serialization Library. I came across this implementation here, and will be using it as a blue print for my application. Now a major question is here.
public interface HttpMethods {
public Object getForObject(Object ... params);
public Object putForObject(Object ... params);
}
public class LocationsHttpMethods implements HttpMethods{
private final Context mContext;
public LocationsHttpMethods(Context context)
{
mContext=context;
}
#Override
public Location[] getForObject(Object... params) {
return null;
}
#Override
public Object putForObject(Object... params) {
return null;
}
}
My Location is just a pojo class. Now the question that troubles me is that the second link that i have given just uses Boolean to return data. I will be returning an array of something.
package com.confiz.rest.services;
import java.util.ArrayList;
import java.util.HashMap;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;
import com.confiz.rest.providers.IProvider;
import com.confiz.rest.providers.LocationsProvider;
public class ProcessorService extends Service
{
private Integer lastStartId;
private final Context mContext = this;
/**
* The keys to be used for the required actions to start this service.
*/
public static class Extras
{
/**
* The provider which the called method is on.
*/
public static final String PROVIDER_EXTRA = "PROVIDER_EXTRA";
/**
* The method to call.
*/
public static final String METHOD_EXTRA = "METHOD_EXTRA";
/**
* The action to used for the result intent.
*/
public static final String RESULT_ACTION_EXTRA = "RESULT_ACTION_EXTRA";
/**
* The extra used in the result intent to return the result.
*/
public static final String RESULT_EXTRA = "RESULT_EXTRA";
}
private final HashMap<String, AsyncServiceTask> mTasks = new HashMap<String, AsyncServiceTask>();
/**
* Identifier for each supported provider.
* Cannot use 0 as Bundle.getInt(key) returns 0 when the key does not exist.
*/
public static class Providers
{
public static final int LOATIONS_PROVIDER = 1;
}
private IProvider GetProvider(int providerId)
{
switch(providerId)
{
case Providers.LOATIONS_PROVIDER:
return new LocationsProvider(this);
}
return null;
}
/**
* Builds a string identifier for this method call.
* The identifier will contain data about:
* What processor was the method called on
* What method was called
* What parameters were passed
* This should be enough data to identify a task to detect if a similar task is already running.
*/
private String getTaskIdentifier(Bundle extras)
{
String[] keys = extras.keySet().toArray(new String[0]);
java.util.Arrays.sort(keys);
StringBuilder identifier = new StringBuilder();
for (int keyIndex = 0; keyIndex < keys.length; keyIndex++)
{
String key = keys[keyIndex];
// The result action may be different for each call.
if (key.equals(Extras.RESULT_ACTION_EXTRA))
{
continue;
}
identifier.append("{");
identifier.append(key);
identifier.append(":");
identifier.append(extras.get(key).toString());
identifier.append("}");
}
return identifier.toString();
}
#Override
public int onStartCommand(Intent intent, int flags, int startId)
{
// This must be synchronised so that service is not stopped while a new task is being added.
synchronized (mTasks)
{
// stopSelf will be called later and if a new task is being added we do not want to stop the service.
lastStartId = startId;
Bundle extras = intent.getExtras();
String taskIdentifier = getTaskIdentifier(extras);
Log.i("ProcessorService", "starting " + taskIdentifier);
// If a similar task is already running then lets use that task.
AsyncServiceTask task = mTasks.get(taskIdentifier);
if (task == null)
{
task = new AsyncServiceTask(taskIdentifier, extras);
mTasks.put(taskIdentifier, task);
// AsyncTasks are by default only run in serial (depending on the android version)
// see android documentation for AsyncTask.execute()
task.execute((Void[]) null);
}
// Add this Result Action to the task so that the calling activity can be notified when the task is complete.
String resultAction = extras.getString(Extras.RESULT_ACTION_EXTRA);
if (resultAction != "")
{
task.addResultAction(extras.getString(Extras.RESULT_ACTION_EXTRA));
}
}
return START_STICKY;
}
#Override
public IBinder onBind(Intent intent)
{
return null;
}
public class AsyncServiceTask extends AsyncTask<Void, Void, Object>
{
private final Bundle mExtras;
private final ArrayList<String> mResultActions = new ArrayList<String>();
private final String mTaskIdentifier;
/**
* Constructor for AsyncServiceTask
*
* #param taskIdentifier A string which describes the method being called.
* #param extras The Extras from the Intent which was used to start this method call.
*/
public AsyncServiceTask(String taskIdentifier, Bundle extras)
{
mTaskIdentifier = taskIdentifier;
mExtras = extras;
}
public void addResultAction(String resultAction)
{
if (!mResultActions.contains(resultAction))
{
mResultActions.add(resultAction);
}
}
#Override
protected Object doInBackground(Void... params)
{
Log.i("ProcessorService", "working " + mTaskIdentifier);
Object result = false;
final int providerId = mExtras.getInt(Extras.PROVIDER_EXTRA);
final int methodId = mExtras.getInt(Extras.METHOD_EXTRA);
if (providerId != 0 && methodId != 0)
{
final IProvider provider = GetProvider(providerId);
if (provider != null)
{
try
{
result = provider.RunTask(methodId, mExtras);
} catch (Exception e)
{
result = false;
}
}
}
return result;
}
#Override
protected void onPostExecute(Object result)
{
// This must be synchronised so that service is not stopped while a new task is being added.
synchronized (mTasks)
{
Log.i("ProcessorService", "finishing " + mTaskIdentifier);
// Notify the caller(s) that the method has finished executing
for (int i = 0; i < mResultActions.size(); i++)
{
Intent resultIntent = new Intent(mResultActions.get(i));
//What to do here
resultIntent.put(Extras.RESULT_EXTRA, true);
//What to do here ends.
resultIntent.putExtras(mExtras);
resultIntent.setPackage(mContext.getPackageName());
mContext.sendBroadcast(resultIntent);
}
// The task is complete so remove it from the running tasks list
mTasks.remove(mTaskIdentifier);
// If there are no other executing methods then stop the service
if (mTasks.size() < 1)
{
stopSelf(lastStartId);
}
}
}
}
}
Now if you browse to the code that contain the AsyncService, and puts the resultIntent.put(Extras.RESULT_EXTRA, true);
Now how should i pass the data back to the intent. I heard Serializable is bad, and Parceable is ugly code. What else can i use. Secondly, where do i add the SQL cache retrieve code. How can i add this code to the framework. Hope i make sense.