I have a database back-end that I've thoroughly tested with several unit tests. The controller looks like this:
#RequestMapping(value = "/create", method = RequestMethod.POST, produces = "application/json", headers = "content-type=application/json")
public #ResponseBody UserDTO createUser(#RequestBody UserDTO user)
{
UserEntity userEntity = service.add(user);
return mappingUser(userEntity);
}
The unit test looks like:
#Test
public void testCreateUser() throws Exception
{
UserDTO userDto = createUserDto();
String url = BASE_URL + "/rest/users/create";
UserDTO newUserDto = restTemplate
.postForObject(url, userDto, UserDTO.class, new Object[]{});
}
I have confirmed that the unit test works great, and the actual web-service is called correctly, and data is put into the database.
Now, I am using a SmartGWT RestDataSource, and I am trying to configure the RestDataSource correctly to pass a new user in the request body, and return the new object. I want to send the data as JSON in the body, and return JSON from this call. So, it might be that I need to change the controller itself to match up with the datasource.
Here is the AbstractDataSource which extends RestDataSource:
import java.util.Map;
import com.google.gwt.http.client.URL;
import com.smartgwt.client.data.DSRequest;
import com.smartgwt.client.data.OperationBinding;
import com.smartgwt.client.data.Record;
import com.smartgwt.client.data.RestDataSource;
import com.smartgwt.client.types.DSOperationType;
import com.smartgwt.client.types.DSProtocol;
public abstract class AbstractRestDataSource extends RestDataSource
{
public AbstractRestDataSource(String id)
{
setID(id);
setClientOnly(false);
// set up FETCH to use GET requests
OperationBinding fetch = new OperationBinding();
fetch.setOperationType(DSOperationType.FETCH);
fetch.setDataProtocol(DSProtocol.GETPARAMS);
DSRequest fetchProps = new DSRequest();
fetchProps.setHttpMethod("GET");
fetch.setRequestProperties(fetchProps);
// set up ADD to use POST requests
OperationBinding add = new OperationBinding();
add.setOperationType(DSOperationType.ADD);
add.setDataProtocol(DSProtocol.POSTMESSAGE);
DSRequest addProps = new DSRequest();
addProps.setHttpMethod("POST");
addProps.setContentType("application/json");
add.setRequestProperties(addProps);
// set up UPDATE to use PUT
OperationBinding update = new OperationBinding();
update.setOperationType(DSOperationType.UPDATE);
update.setDataProtocol(DSProtocol.POSTMESSAGE);
DSRequest updateProps = new DSRequest();
updateProps.setHttpMethod("PUT");
update.setRequestProperties(updateProps);
// set up REMOVE to use DELETE
OperationBinding remove = new OperationBinding();
remove.setOperationType(DSOperationType.REMOVE);
DSRequest removeProps = new DSRequest();
removeProps.setHttpMethod("DELETE");
remove.setRequestProperties(removeProps);
// apply all the operational bindings
setOperationBindings(fetch, add, update, remove);
init();
}
#Override
protected Object transformRequest(DSRequest request)
{
super.transformRequest(request);
// now post process the request for our own means
postProcessTransform(request);
return request.getData();
}
/*
* Implementers can override this method to create a
* different override.
*/
#SuppressWarnings("rawtypes")
protected void postProcessTransform(DSRequest request)
{
StringBuilder url = new StringBuilder(getServiceRoot());
Map dataMap = request.getAttributeAsMap("data");
if (request.getOperationType() == DSOperationType.REMOVE)
{
// in case of remove, append the primary key
url.append(getPrimaryKeyProperty()).append("/").append(dataMap.get(getPrimaryKeyProperty()));
}
else if (request.getOperationType() == DSOperationType.UPDATE)
{
url.append("update");
appendParameters(url, request);
}
else if (request.getOperationType() == DSOperationType.FETCH && dataMap.size() > 0)
{
url.append(getPrimaryKeyProperty()).append("/").append(dataMap.get(getPrimaryKeyProperty()));
}
else if (request.getOperationType() == DSOperationType.ADD)
{
url.append("create");
}
System.out.println("AbstractRestDataSource: postProcessTransform: url=" + url.toString());
request.setActionURL(URL.encode(url.toString()));
}
/*
* This simply appends parameters that have changed to the URL
* so that PUT requests go through successfully. This is usually
* necessary because when smart GWT updates a row using a form,
* it sends the data as form parameters. Most servers cannot
* understand this and will simply disregard the form data
* sent to the server via PUT. So we need to transform the form
* data into URL parameters.
*/
#SuppressWarnings("rawtypes")
protected void appendParameters(StringBuilder url, DSRequest request)
{
Map dataMap = request.getAttributeAsMap("data");
Record oldValues = request.getOldValues();
boolean paramsAppended = false;
if (!dataMap.isEmpty())
{
url.append("?");
}
for (Object keyObj : dataMap.keySet())
{
String key = (String) keyObj;
if (!dataMap.get(key).equals(oldValues.getAttribute(key)) || isPrimaryKey(key))
{
// only append those values that changed or are primary keys
url.append(key).append('=').append(dataMap.get(key)).append('&');
paramsAppended = true;
}
}
if (paramsAppended)
{
// delete the last '&'
url.deleteCharAt(url.length() - 1);
}
}
private boolean isPrimaryKey(String property)
{
return getPrimaryKeyProperty().equals(property);
}
/*
* The implementer can override this to change the name of the
* primary key property.
*/
protected String getPrimaryKeyProperty()
{
return "id";
}
protected abstract String getServiceRoot();
protected abstract void init();
}
And here is the UserDataSource which extends AbstractRestDataSource:
import java.util.Map;
import com.google.gwt.http.client.URL;
import com.opensource.restful.shared.Constants;
import com.smartgwt.client.data.DSRequest;
import com.smartgwt.client.data.fields.DataSourceBooleanField;
import com.smartgwt.client.data.fields.DataSourceDateField;
import com.smartgwt.client.data.fields.DataSourceIntegerField;
import com.smartgwt.client.data.fields.DataSourceTextField;
import com.smartgwt.client.types.DSDataFormat;
import com.smartgwt.client.types.DSOperationType;
public class UserDataSource extends AbstractRestDataSource
{
private static UserDataSource instance = null;
public static UserDataSource getInstance()
{
if (instance == null)
{
instance = new UserDataSource("restUserDS");
}
return instance;
}
private UserDataSource(String id)
{
super(id);
}
private DataSourceIntegerField userIdField;
private DataSourceBooleanField userActiveField;
private DataSourceTextField usernameField;
private DataSourceTextField passwordField;
private DataSourceTextField firstnameField;
private DataSourceTextField lastnameField;
private DataSourceTextField emailField;
private DataSourceTextField securityQuestion1Field;
private DataSourceTextField securityAnswer1Field;
private DataSourceTextField securityQuestion2Field;
private DataSourceTextField securityAnswer2Field;
private DataSourceDateField birthdateField;
private DataSourceIntegerField positionIdField;
protected void init()
{
setDataFormat(DSDataFormat.JSON);
setJsonRecordXPath("/");
// set the values for the datasource
userIdField = new DataSourceIntegerField(Constants.USER_ID, Constants.TITLE_USER_ID);
userIdField.setPrimaryKey(true);
userIdField.setCanEdit(false);
userActiveField = new DataSourceBooleanField(Constants.USER_ACTIVE, Constants.TITLE_USER_ACTIVE);
usernameField = new DataSourceTextField(Constants.USER_USERNAME, Constants.TITLE_USER_USERNAME);
passwordField = new DataSourceTextField(Constants.USER_PASSWORD, Constants.TITLE_USER_PASSWORD);
firstnameField = new DataSourceTextField(Constants.USER_FIRST_NAME, Constants.TITLE_USER_FIRST_NAME);
lastnameField = new DataSourceTextField(Constants.USER_LAST_NAME, Constants.TITLE_USER_LAST_NAME);
emailField = new DataSourceTextField(Constants.USER_EMAIL, Constants.TITLE_USER_EMAIL);
securityQuestion1Field =
new DataSourceTextField(Constants.USER_SECURITY_QUESTION_1, Constants.TITLE_USER_SECURITY_QUESTION_1);
securityAnswer1Field =
new DataSourceTextField(Constants.USER_SECURITY_ANSWER_1, Constants.TITLE_USER_SECURITY_ANSWER_1);
securityQuestion2Field =
new DataSourceTextField(Constants.USER_SECURITY_QUESTION_2, Constants.TITLE_USER_SECURITY_QUESTION_2);
securityAnswer2Field =
new DataSourceTextField(Constants.USER_SECURITY_ANSWER_2, Constants.TITLE_USER_SECURITY_ANSWER_2);
birthdateField = new DataSourceDateField(Constants.USER_BIRTHDATE, Constants.TITLE_USER_BIRTHDATE);
positionIdField = new DataSourceIntegerField(Constants.USER_POSITION_ID, Constants.TITLE_USER_POSITION_ID);
// positionActiveField = new DataSourceBooleanField(Constants.USER_ACTIVE, Constants.TITLE_USER_ACTIVE);
// positionCodeField;
// positionDescriptionField;
setFields(userIdField, userActiveField, usernameField, passwordField, firstnameField, lastnameField,
emailField, birthdateField, securityQuestion1Field, securityAnswer1Field, securityQuestion2Field,
securityAnswer2Field, positionIdField);
}
protected String getServiceRoot()
{
return "rest/users/";
}
protected String getPrimaryKeyProperty()
{
return "userId";
}
/*
* Implementers can override this method to create a
* different override.
*/
#SuppressWarnings("rawtypes")
protected void postProcessTransform(DSRequest request)
{
// request.setContentType("application/json");
StringBuilder url = new StringBuilder(getServiceRoot());
Map dataMap = request.getAttributeAsMap("data");
if (request.getOperationType() == DSOperationType.REMOVE)
{
// in case of remove, append the primary key
url.append(getPrimaryKeyProperty()).append("/").append(dataMap.get(getPrimaryKeyProperty()));
}
else if (request.getOperationType() == DSOperationType.UPDATE)
{
url.append("update");
System.out.println("UserDataSource: postProcessTransform: update: url=" + url.toString());
}
else if (request.getOperationType() == DSOperationType.FETCH && dataMap.size() > 0)
{
url.append(getPrimaryKeyProperty()).append("/").append(dataMap.get(getPrimaryKeyProperty()));
}
else if (request.getOperationType() == DSOperationType.ADD)
{
url.append("create");
}
System.out.println("UserDataSource: postProcessTransform: url=" + url.toString());
request.setActionURL(URL.encode(url.toString()));
}
}
If I can find out how to get the UserDTO as JSON into the requestBody, I think I will have solved all my issues. For extra information you should know, I am using Spring 3.2 and the Jackson Message Converters configured in the springmvc-servlet.xml file.
At one point I did see all the data getting appended to the URL, but I would prefer if the data was not in the URL as parameters, but rather in the request body. SO, I need to know if this is possible, and how to do it.
Thanks for any help!!!
You probably want to undo all of these modifications and just implement the default RestDataSource protocol, which already passes the request body as JSON if you just call RestDataSource.setDataFormat(). There are sample JSON messages in the docs:
http://www.smartclient.com/smartgwtee/javadoc/com/smartgwt/client/data/RestDataSource.html
Among other problems you've created:
the different CRUD operations now go to distinct URLs and use different HTTP verbs, so they can no longer be combined into a single queue and sent together. This means you can't do basic things like perform a mixture of create and update operations together in a transaction, save a new Order along with it's OrderItems, or save data and also fetch dependent data needed to transition to a new screen.
you're assuming the "fetch" will be based on HTTP GET, but this requires awkward encoding of nested criteria structures (AdvancedCriteria) into URL parameters, which can easily hit the maximum URL length
For these and other reasons, most generated Java services do not meet the needs of a modern UI, and the auto-generation approach should not be used. Deeper explanation in the FAQ:
http://forums.smartclient.com/showthread.php?t=8159#aExistingRest
Related
Right now, I am in the process of "optimizing" my app. I am still a beginner, so what I am doing is basically moving methods from my MainActivity.class to their separate class. I believe it's called Encapsulation (Please correct me if I'm wrong).
My application needs to :
Get a YouTube Playlist Link from the YouTube App (with an Intent, android.intent.action.SEND).
Use the link to fetch data from the Google Servers with the YouTubeApi and Volley.
Read the data received and add it to an arrayList<String>.
What my YouTubeUsage.java class is supposed to do, is fetch data with the YouTubeApi and Volley then store the data using SharedPreferences. Once the data is saved, the data is being read in my ConvertActivity.class (It's an activity specifically created for android.intent.action.SEND) with my method getVideoIds() before setting an adapter for my listView in my createRecyclerView() method.
YouTubeUsage.java
public class YoutubeUsage {
private Boolean results = false;
private String mResponse;
private ArrayList<String> videoIds = new ArrayList<>();
String Url;
public String getUrl(String signal) {
String playlistId = signal.substring(signal.indexOf("=") + 1);
this.Url = "https://www.googleapis.com/youtube/v3/playlistItems?part=contentDetails%2C%20snippet%2C%20id&playlistId=" +
playlistId + "&maxResults=25&key=" + "API_KEY";
return this.Url;
}
public void fetch(String Url, final Context context){
RequestQueue queue = Volley.newRequestQueue(context);
StringRequest request = new StringRequest(Request.Method.GET, Url,
new Response.Listener<String>() {
#Override
public void onResponse(String response) {
sharedPreferences(response, context);
}
}, new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
Log.e("VolleyError", Objects.requireNonNull(error.getMessage()));
}
});
queue.add(request);
}
private void sharedPreferences(String response, Context context){
SharedPreferences m = PreferenceManager.getDefaultSharedPreferences(context);
SharedPreferences.Editor editor = m.edit();
if (m.contains("serverResponse")){
if (!m.getString("serverResponse", "").equals(response)){
editor.remove("serverResponse");
editor.apply();
updateSharedPreferences(response, context);
}
} else{
updateSharedPreferences(response, context);
}
}
private void updateSharedPreferences(String mResponse, Context mContext){
SharedPreferences m = PreferenceManager.getDefaultSharedPreferences(mContext);
SharedPreferences.Editor editor = m.edit();
editor.putString("serverResponse", mResponse);
editor.apply();
}
}
ConvertActivity.java
public class ConvertActivity extends AppCompatActivity {
YoutubeUsage youtubeUsage = new YoutubeUsage();
ArrayList<String> videoIDs = new ArrayList<>();
String Url = "";
ListView listView;
MyCustomAdapter myCustomAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_convert);
listView = findViewById(R.id.listview_convert);
Intent intent = getIntent();
String action = intent.getAction();
String type = intent.getType();
if ("android.intent.action.SEND".equals(action) && "text/plain".equals(type)) {
Url = youtubeUsage.getUrl(Objects.requireNonNull(intent.getStringExtra("android.intent.extra.TEXT")));
}
//I would like to avoid the try/catch below
try {
videoIDs = getVideoIDs(Url, this);
createRecyclerView(videoIDs);
Log.i("ResponseVideoIDs", String.valueOf(videoIDs.size()));
} catch (JSONException e) {
e.printStackTrace();
}
}
private ArrayList<String> getVideoIDs(String Url, Context context) throws JSONException {
ArrayList<String> rawVideoIDs = new ArrayList<>();
youtubeUsage.fetch(Url, context);
SharedPreferences m = PreferenceManager.getDefaultSharedPreferences(context);
String serverResponse = m.getString("serverResponse", "");
JSONObject jsonObject = new JSONObject(serverResponse);
JSONArray jsonArray = jsonObject.getJSONArray("items");
for (int i = 0; i<jsonArray.length(); i++){
JSONObject jsonObject1 = jsonArray.getJSONObject(i);
JSONObject jsonVideoId = jsonObject1.getJSONObject("contentDetails");
rawVideoIDs.add(jsonVideoId.getString("videoId"));
}
return rawVideoIDs;
}
private void createRecyclerView(ArrayList<String> videoIDs){
myCustomAdapter = new MyCustomAdapter(this, videoIDs);
listView.setAdapter(myCustomAdapter);
myCustomAdapter.notifyDataSetChanged();
}
}
Everything works fine, however, my sharedPreferences never gets updated. Which means, if I share a YouTube playlist from the YouTube App to my app with 3 items in it, it will work fine. The Listview will show 3 items with their corresponding IDs as it should. But, if I share a YouTube playlist again, my app will still hold on to the data of the previous playlist I shared (even if I close it), showing the item number and the IDs of the previous link. If i continue to share the same playlist over and over, it will eventually show the correct number of items and the correct IDs.
I could totally put all my methods from the YouTubeUsage.java in my ConvertActivity.class preventing me from using SharedPreferences to transfer data between the two java classes. However, JSON throws an exception. That means I have to encapsulate my code with try/catch. I would like to avoid those since I need to do a lot of operations on the data just received by Volley (check a class size, look for certains strings). I find that doing this in these try/catch don't work like I want. (i.e. outside the try/catch, the values remains the same even if I updated them in the try/catch).
I want to know two things.
How can I correct this problem?
Is this the most efficient way to do this (optimization)? (I though of maybe
converting the VolleyResponse to a string with Gson then store the String file, but I don't know if that's the best way to do it since it's supposed to be
provisional data. It feels like just more of the same).
Thank You!
There is an issue with making assumptions about order of events. Volley will handle requests asynchronously, so it is advisable to implement the observer pattern here.
Create a new Java file that just contains:
interface MyNetworkResponse {
void goodResponse(String responseString);
}
Then make sure ConvertActivity implements MyNetworkResponse and create method:
void goodResponse(String responseString) {
// handle a positive response here, i.e. extract the JSON and send to your RecyclerView.
}
within your Activity.
In your YoutubeUsage constructor, pass in the Activity context (YoutubeUsage) and then store this in a YoutubeUsage instance variable called ctx.
In onCreate, create an instance of YoutubeUsage and pass in this.
In onResponse just call ctx.goodResponse(response).
Amend the following block to:
if ("android.intent.action.SEND".equals(action) && "text/plain".equals(type)) {
Url = youtubeUsage.getUrl(Objects.requireNonNull(intent.getStringExtra("android.intent.extra.TEXT")));
youtubeUsage.fetch(Url);
}
Delete the try/catch from onCreate.
And no need to use SharedPreferences at all.
UPDATE
Try this code:
MyNetworkResponse.java
interface MyNetworkResponse {
void goodResponse(String responseString);
void badResponse(VolleyError error);
}
YoutubeUsage.java
class YoutubeUsage {
private RequestQueue queue;
private MyNetworkResponse callback;
YoutubeUsage(Object caller) {
this.callback = (MyNetworkResponse) caller;
queue = Volley.newRequestQueue((Context) caller);
}
static String getUrl(String signal) {
String playlistId = signal.substring(signal.indexOf("=") + 1);
return "https://www.googleapis.com/youtube/v3/playlistItems?part=contentDetails%2C%20snippet%2C%20id&playlistId=" + playlistId + "&maxResults=25&key=" + "API_KEY";
}
void fetch(String url){
StringRequest request = new StringRequest(Request.Method.GET, url,
new Response.Listener<String>() {
#Override
public void onResponse(String response) {
callback.goodResponse(response);
}
}, new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
callback.badResponse(error);
}
});
queue.add(request);
}
}
ConvertActivity.java
public class ConvertActivity extends AppCompatActivity implements MyNetworkResponse {
YoutubeUsage youtubeUsage;
ArrayList<String> videoIDs = new ArrayList<>();
ListView listView;
MyCustomAdapter myCustomAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_convert);
listView = findViewById(R.id.listview_convert);
youtubeUsage = new YoutubeUsage(this);
Intent intent = getIntent();
String action = intent.getAction();
String type = intent.getType();
if ("android.intent.action.SEND".equals(action) && "text/plain".equals(type)) {
String url = YoutubeUsage.getUrl(Objects.requireNonNull(intent.getStringExtra("android.intent.extra.TEXT")));
youtubeUsage.fetch(url);
}
}
private ArrayList<String> getVideoIDs(String serverResponse) throws JSONException {
ArrayList<String> rawVideoIDs = new ArrayList<>();
JSONObject jsonObject = new JSONObject(serverResponse);
JSONArray jsonArray = jsonObject.getJSONArray("items");
for (int i = 0; i < jsonArray.length(); i++) {
JSONObject jsonObject1 = jsonArray.getJSONObject(i);
JSONObject jsonVideoId = jsonObject1.getJSONObject("contentDetails");
rawVideoIDs.add(jsonVideoId.getString("videoId"));
}
return rawVideoIDs;
}
private void createRecyclerView(ArrayList<String> videoIDs) {
myCustomAdapter = new MyCustomAdapter(this, videoIDs);
listView.setAdapter(myCustomAdapter);
myCustomAdapter.notifyDataSetChanged();
}
#Override
public void goodResponse(String responseString) {
Log.d("Convert:goodResp", "[" + responseString + "]");
try {
ArrayList<String> rawVideoIDs = getVideoIDs(responseString);
createRecyclerView(rawVideoIDs);
} catch (JSONException e) {
// handle JSONException, e.g. malformed response from server.
}
}
#Override
public void badResponse(VolleyError error) {
// handle unwanted server response.
}
}
I am currently building an API gateway for a new microservices system, using the Spring Netflix Zuul library.
So far my gateway contains PRE and POST filters that intercept the requests and perform the required logic, etc.
One thing that I see is that REST calls to specific microservices require invoking an API endpoint (either GET or POST) containing JSON payload data that is very complex.
For an end-user sending a request to a microservice containing this JSON would not be user friendly.
I had an idea such that the API gateway act as a mediator, where the user can submit a more "simplified/ user-friendly" JSON to the API gateway, which will transform the JSON payload with the correct "complex" JSON structure that the target microservice can understand in order to handle the request efficiently.
My understanding of how Netflix Zuul is that this can be done by creating a RouteFilter and then including this logic here.
Can anyone explain if (or how) this transformation could be done using Netflix Zuul?
Any advice is appreciated.
Thanks.
No doubt you can do it with Zuul, i am currently trying to do almost the same. i'd suggest you take a look at this repo :
sample-zuul-filters
and the official doc on github.
Filters have to extend ZuulFilter and implement the following methods :
/**
*return a string defining when your filter must execute during zuul's
*lyfecyle ('pre'/'post' routing
**/
#Override
public String filterType(){
return 'pre'; // run this filter before sending the final request
}
/**
* return an int describing the order that the filter should run on,
* (relative to the other filters and the current 'pre' or 'post' context)
**/
#Override
public int filterOrder {
return 1; //this filter runs first in a pre-request context
}
/**
* return a boolean indicating if the filter should run or not
**/
#Override
public boolean shouldFilter() {
RequestContext ctx = RequestContext.getCurrentContext();
if(ctx.getRequest().getRequestURI().equals("/theRouteIWantToFilter"))
{
return true;
}
else {
return false;
}
}
/**
* After all the config stuffs you can set what your filter actually does
* here. This is where your json logic goes.
*/
#Override
public Object run() {
try {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
InputStream stream = ctx.getResponseDataStream();
String body = StreamUtils.copyToString(stream, Charset.forName("UTF-8"));
// transform your json and send it to the api.
ctx.setResponseBody(" Modified body : " + body);
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
I am not sure my answer is 100% accurate since i am working on it but it's a start.
I've done payload conversion in pre filter but this should work in route filter as well. Use com.netflix.zuul.http.HttpServletRequestWrapper to capture and modify the original request payload before forwarding the request to target microservice.
Sample code:
package com.sample.zuul.filters.pre;
import com.google.common.io.CharStreams;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.http.HttpServletRequestWrapper;
import com.netflix.zuul.http.ServletInputStreamWrapper;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
public class JsonConverterFilter extends ZuulFilter {
#Override
public String filterType() {
return "pre";
}
#Override
public int filterOrder() {
return 0; // Set it to whatever the order of your filter is
}
#Override
public boolean shouldFilter() {
return true;
}
#Override
public Object run() {
RequestContext context = RequestContext.getCurrentContext();
HttpServletRequest request = new HttpServletRequestWrapper(context.getRequest());
String requestData = null;
JSONParser jsonParser = new JSONParser();
JSONObject requestJson = null;
try {
if (request.getContentLength() > 0) {
requestData = CharStreams.toString(request.getReader());
}
if (requestData == null) {
return null;
}
requestJson = (JSONObject) jsonParser.parse(requestData);
} catch (Exception e) {
//Add your exception handling code here
}
JSONObject modifiedRequest = modifyJSONRequest(requestJson);
final byte[] newRequestDataBytes = modifiedRequest.toJSONString().getBytes();
request = getUpdatedHttpServletRequest(request, newRequestDataBytes);
context.setRequest(request);
return null;
}
private JSONObject modifyJSONRequest(JSONObject requestJSON) {
JSONObject jsonObjectDecryptedPayload = null;
try {
jsonObjectDecryptedPayload = (JSONObject) new JSONParser()
.parse("Your new complex json");
} catch (ParseException e) {
e.printStackTrace();
}
return jsonObjectDecryptedPayload;
}
private HttpServletRequest getUpdatedHttpServletRequest(HttpServletRequest request, final byte[] newRequestDataBytes) {
request = new javax.servlet.http.HttpServletRequestWrapper(request) {
#Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(
new InputStreamReader(new ByteArrayInputStream(newRequestDataBytes)));
}
#Override
public ServletInputStream getInputStream() throws IOException {
return new ServletInputStreamWrapper(newRequestDataBytes);
}
/*
* Forcing any calls to HttpServletRequest.getContentLength to return the accurate length of bytes
* from a modified request
*/
#Override
public int getContentLength() {
return newRequestDataBytes.length;
}
};
return request;
}
}
Hi I have a json request which I have to pass in my junit testcase but the request is delete as delete do not support setEntity method. How can I pass the request in my testcase.
Json request which I have to pass
{
"userId":"AJudd",
"siteId":"131",
"alternateSiteId":"186"
}
mytest case for this
#Test
public void testdeleteAltSite() throws ClientProtocolException, IOException {
String resultCode = "001";
String resultText = "Success";
String url = "http://localhost:8080/adminrest1/alternatesite";
HttpClient client = HttpClientBuilder.create().build();
HttpDelete delete = new HttpDelete(url);
// add header
delete.addHeader("Transaction-Id", "11");
delete.addHeader("content-type", "application/json");
LOG.info(url);
HttpResponse response = client.execute(delete);
byte[] buf = new byte[512];
InputStream is = response.getEntity().getContent();
int count = 0;
StringBuilder builder = new StringBuilder(1024);
while ((count = is.read(buf, 0, 512)) > 0) {
builder.append(new String(buf, 0, count));
}
String output = builder.toString();
System.out.println(output);
}`
How to pass the json value so that the passed value data can be deleted?
IMHO this is a problem with your design.
If your intent is to delete an alternate site and its id is unique then passing the alternateSiteId as part of the URI should sufficient:
Method: DELETE
URL: http://localhost:8080/adminrest1/alternatesite/{alternateSiteId}
If alternateSiteId is not unique then you are updating a relationship. In that case you should use a PUT which allows you to include a body in your request. Please note you should pass the id of the resource you are updating as part of your URI, for example:
Method: PUT
URL: http://localhost:8080/adminrest1/alternatesite/{userId}
Body:{
"siteId":"131",
"alternateSiteId":"186"
}
Ok, first of all: Sending a body with a DELETE is not what usually happens around the internet. Nevertheless, it is not forbidden (Is an entity body allowed for an HTTP DELETE request?). So, two ideas:
1) New class
I assume you use org.apache.http.client: Just extend HttpEntityEnclosingRequestBase:
public class HttpDeleteWithEntity extends HttpEntityEnclosingRequestBase {
public final static String METHOD_NAME = "DELETE";
public HttpDeleteWithEntity() {
super();
}
public HttpDeleteWithEntity(final URI uri) {
super();
setURI(uri);
}
public HttpDeleteWithEntity(final String uri) {
super();
setURI(URI.create(uri));
}
#Override
public String getMethod() {
return METHOD_NAME;
}
}
This is basically c&p'ed from the HttpPost class. I did not test this, tho.
Then use your HttpDeleteWithEntity class instead of HttpDelete.
2) Use custom headers
If you can modify your server code that might be a good alternative.
delete.addHeader("testwith", jsonString);
or
delete.addHeader("userId","AJudd");
delete.addHeader("siteId","131");
delete.addHeader("alternateSiteId","186);
Finally, if you are in charge of the server implementation I would recommend to implement DELETE requests without any body (see artemisian's answer).
I have a web-based application with GWT 2.5.1, SmartGWT 4.0, Spring 3.2.3.Release, and Hibernate 4.1. The front-end uses a SmartGWT RestDataSource that passes data to RESTful web-service, a Spring MVC Controller, that passes data to the front end with Java.
The Controller is unit tested and works great, I use a GET to pass back the data in JSON, the controller calls the back-end, gets my data and I return a UserEntity back to the RestDataSource in JSON format.
The error is:
[ERROR] [TestAdmin] - 22:01:22.432:XRP8:WARN:RestDataSource:restLoginDS:RestDataSouce transformResponse(): JSON response text does not appear to be in standard response format.
I have done a lot of Googling, and looking on this site, and I can find people with similar issues, but no good solutions.
Here is the RestDataSource:
public class LoginDataSource extends RestDataSource
{
private static LoginDataSource instance = null;
public static LoginDataSource getInstance()
{
if (instance == null)
{
instance = new LoginDataSource("restLoginDS");
}
return instance;
}
private LoginDataSource(String id)
{
setID(id);
setClientOnly(false);
// set up FETCH to use GET requests
OperationBinding fetch = new OperationBinding();
fetch.setOperationType(DSOperationType.FETCH);
fetch.setDataProtocol(DSProtocol.GETPARAMS);
DSRequest fetchProps = new DSRequest();
fetchProps.setHttpMethod("GET");
fetch.setRequestProperties(fetchProps);
// set up ADD to use POST requests
OperationBinding add = new OperationBinding();
add.setOperationType(DSOperationType.ADD);
add.setDataProtocol(DSProtocol.POSTMESSAGE);
// ===========================================
DSRequest addProps = new DSRequest();
addProps.setHttpMethod("POST");
// addProps.setContentType("application/json");
add.setRequestProperties(addProps);
// set up UPDATE to use PUT
OperationBinding update = new OperationBinding();
update.setOperationType(DSOperationType.UPDATE);
update.setDataProtocol(DSProtocol.POSTMESSAGE);
// ===========================================
DSRequest updateProps = new DSRequest();
updateProps.setHttpMethod("PUT");
// updateProps.setContentType("application/json");
update.setRequestProperties(updateProps);
// set up REMOVE to use DELETE
OperationBinding remove = new OperationBinding();
remove.setOperationType(DSOperationType.REMOVE);
DSRequest removeProps = new DSRequest();
removeProps.setHttpMethod("DELETE");
remove.setRequestProperties(removeProps);
// apply all the operational bindings
setOperationBindings(fetch, add, update, remove);
init();
}
private DataSourceIntegerField userIdField; // "userId":"1",
private DataSourceTextField usernameField; // "username":"myusername",
private DataSourceTextField passwordField; // "password":"mypassword",
private DataSourceBooleanField userActiveField; // "active":true,
private DataSourceTextField fullnameField; // "fullname":"Thomas Holmes",
private DataSourceDateField birthdateField; // "birthdate":"1960-10-30",
private DataSourceTextField emailField; // "email":"myemail#test.net",
private DataSourceTextField cellPhoneField; // "cellPhone":"111-222-1234"
private DataSourceIntegerField updatedByField; // "updatedBy":1,
private DataSourceDateField updatedDateField; // "updatedDate":"2013-01-01",
private DataSourceIntegerField createdByField; // "createdBy":1,
private DataSourceDateField createdDateField; // "createdDate":"2013-01-01",
private DataSourceTextField securityQuestion1Field; // "securityQuestion1":"peanuts",
private DataSourceTextField securityAnswer1Field; // "securityAnswer1":"linus"
protected void init()
{
System.out.println("init: START");
setDataFormat(DSDataFormat.JSON);
setJsonRecordXPath("/");
// set the values for the datasource
userIdField = new DataSourceIntegerField(Constants.USER_ID, Constants.TITLE_USER_ID);
userIdField.setPrimaryKey(true);
userIdField.setCanEdit(false);
usernameField = new DataSourceTextField(Constants.USER_USERNAME, Constants.TITLE_USER_USERNAME);
usernameField.setCanEdit(false);
passwordField = new DataSourceTextField(Constants.USER_PASSWORD, Constants.TITLE_USER_PASSWORD);
passwordField.setCanEdit(false);
userActiveField = new DataSourceBooleanField(Constants.USER_ACTIVE, Constants.TITLE_USER_ACTIVE);
fullnameField = new DataSourceTextField(Constants.USER_FULLNAME, Constants.TITLE_USER_FULLNAME);
birthdateField = new DataSourceDateField(Constants.USER_BIRTHDATE, Constants.TITLE_USER_BIRTHDATE);
emailField = new DataSourceTextField(Constants.USER_EMAIL, Constants.TITLE_USER_EMAIL);
cellPhoneField = new DataSourceTextField(Constants.USER_CELL_PHONE, Constants.TITLE_USER_CELL_PHONE);
securityQuestion1Field =
new DataSourceTextField(Constants.USER_SECURITY_QUESTION_1, Constants.TITLE_USER_SECURITY_QUESTION_1);
securityAnswer1Field =
new DataSourceTextField(Constants.USER_SECURITY_ANSWER_1, Constants.TITLE_USER_SECURITY_ANSWER_1);
updatedByField = new DataSourceIntegerField(Constants.USER_UPDATED_BY, Constants.TITLE_USER_UPDATED_BY);
updatedDateField = new DataSourceDateField(Constants.USER_UPDATED_DATE, Constants.TITLE_USER_UPDATED_DATE);
createdByField = new DataSourceIntegerField(Constants.USER_CREATED_BY, Constants.TITLE_USER_CREATED_BY);
createdDateField = new DataSourceDateField(Constants.USER_CREATED_DATE, Constants.TITLE_USER_CREATED_DATE);
System.out.println("init: FINISH");
setFields(userIdField, usernameField, passwordField, userActiveField, emailField, cellPhoneField,
fullnameField, birthdateField, securityQuestion1Field, securityAnswer1Field, updatedByField,
updatedDateField, createdByField, createdDateField);
// setFetchDataURL(getServiceRoot() + "/userId/{id}");
// setFetchDataURL(getServiceRoot() + "/contactId/{id}");
setAddDataURL(getServiceRoot() + "/create");
setUpdateDataURL(getServiceRoot() + "/update");
setRemoveDataURL(getServiceRoot() + "/remove/{id}");
}
protected String getServiceRoot()
{
return "rest/login/";
}
protected String getPrimaryKeyProperty()
{
return "userId";
}
/*
* Implementers can override this method to create a different override.
*/
#SuppressWarnings("rawtypes")
protected void postProcessTransform(DSRequest request)
{
System.out.println("LoginDataSource: postProcessTransform: START");
StringBuilder url = new StringBuilder(getServiceRoot());
System.out.println("LoginDataSource: postProcessTransform: url=" + url);
Map dataMap = request.getAttributeAsMap("data");
System.out.println("LoginDataSource: postProcessTransform: dataMap=" + dataMap.toString());
if (request.getOperationType() == DSOperationType.FETCH && dataMap.size() > 0)
{
if (dataMap.get(Constants.USER_USERNAME) != null && dataMap.get(Constants.USER_PASSWORD) != null)
{
url.append("user/" + dataMap.get(Constants.USER_USERNAME));
url.append("/pwd/" + dataMap.get(Constants.USER_PASSWORD));
}
else if (dataMap.get(Constants.USER_USERNAME) != null && dataMap.get(Constants.USER_PASSWORD) == null)
{
url.append("user/" + dataMap.get(Constants.USER_USERNAME));
url.append("/pwd/" + dataMap.get(Constants.USER_PASSWORD));
}
else if (dataMap.get(Constants.USER_EMAIL) != null)
{
url.append("email/" + dataMap.get(Constants.USER_EMAIL));
}
}
System.out.println("LoginDataSource: postProcessTransform: url=" + url.toString());
request.setActionURL(URL.encode(url.toString()));
}
#Override
protected Object transformRequest(DSRequest dsRequest)
{
// now post process the request for our own means
postProcessTransform(dsRequest);
System.out.println("LoginDataSource: transformRequest: START");
dsRequest.setContentType("application/json");
JavaScriptObject jso = dsRequest.getData();
String jsoText = JSON.encode(jso);
System.out.println("LoginDataSource: transformRequest: START: jsoText=" + jsoText);
// this code is used only when there is a password change, otherwise this will be skipped
String userPassword = JSOHelper.getAttribute(jso, Constants.USER_NEW_PASSWORD);
if (userPassword != null)
{
// This creates the new JSON attribute:
// ... , "position":{"id":x}
JSOHelper.setAttribute(jso, "password", userPassword);
// remove the JSON Attribute: ... , "userPassword":"newPassword"
JSOHelper.deleteAttribute(jso, Constants.USER_NEW_PASSWORD);
}
System.out.println("LoginDataSource: transformRequest: FINISH: url=" + dsRequest.getActionURL());
String s1 = JSON.encode(jso);
System.out.println("LoginDataSource: transformRequest: FINISH: s1=" + s1);
return s1;
}
protected void transformResponse(DSResponse response, DSRequest request, Object jsonData)
{
System.out.println("LoginDataSource: transformResponse: START");
JavaScriptObject jsObj = (JavaScriptObject) jsonData;
String jsoText1 = JSON.encode(jsObj);
System.out.println("LoginDataSource: transformResponse: jsoText=" + jsoText1);
System.out.println("LoginDataSource: transformResponse: jsonData=" + jsonData.getClass());
for (String attr : response.getAttributes())
{
System.out.println("LoginDataSource: transformResponse: attr=" + attr + " value="
+ response.getAttribute(attr));
}
super.transformResponse(response, request, jsonData);
}
}
The error comes up on the line:
super.transformResponse(response, request, jsonData);
I know the data coming back from the Controller is JSON data as follows:
{
"userId":1,
"username":"my_username",
"password":"my_password",
"active":true,
"fullname":"Thomas Holmes",
"birthdate":"1960-10-13",
"email":"test#test.net",
"cellPhone":"111-222-1234",
"updatedBy":1,
"updatedDate":"2013-01-01",
"createdBy":1,
"createdDate":"2013-01-01",
"securityQuestion1":"peanuts",
"securityAnswer1":"linus"
}
I have tested that these names match the datasource fields in the json data.
We should be able to see that by checking the datasource fields above.
I have also unit tested with JUnit and Jackson Mapper 2.0 that the JSON string data can be used to create a UserDTO object and a UserEntity object.
I am very much aware of the SmartClient documentation about the data that comes back from a controller and how it must match the required format. This is a SmartGWT RestDataSource and I looked at the response which looks ok.
In the transformResponse code:
for (String attr : response.getAttributes())
{
System.out.println("transformResponse: attr=" + attr + " value="
+ response.getAttribute(attr));
}
Which yields:
transformResponse: jsonData=class com.google.gwt.core.client.JavaScriptObject$
transformResponse: attr=data value=[object Object]
transformResponse: attr=startRow value=0
transformResponse: attr=status value=0
transformResponse: attr=endRow value=1
transformResponse: attr=totalRows value=1
transformResponse: attr=httpResponseCode value=200
transformResponse: attr=transactionNum value=0
transformResponse: attr=clientContext value=null
transformResponse: attr=httpHeaders value=[object Object]
transformResponse: attr=context value=[object Object]
It looks like the "Object jsonData" is a JavaScriptObject.
Ultimately, the JSON that comes back, in a JavascriptObject, I'd like to convert to a UserDTO object.
So, if I can remove this error and solve this objective that would be great.
Thanks!
UPATE
I've been testing this out, and I finally have a small test app which I think shows that Isomorphic broke the RestDataSource in SmartGWT. I say that because my application was working before-hand, and now it doesn't work.
I confirmed hat all this is 100% valid JSON data. Running through various tests on the Net.
test1.json: {"userId":1}
test2.json: {"userId":"1"}
test3.json: [{"userId":1}]
test4.json: [{"userId":"1"}]
Then I isolated to a very small app to test. This is something similar to what is on the SmartGWT Showcase.
public class TestApp implements EntryPoint
{
private DataSourceIntegerField userIdField;
public void onModuleLoad()
{
RestDataSource dataSource = new RestDataSource();
dataSource.setDataFormat(DSDataFormat.JSON);
dataSource.setDataURL("data/single_user.json");
// set the values for the datasource
userIdField = new DataSourceIntegerField("userId", "User Id");
userIdField.setPrimaryKey(true);
userIdField.setCanEdit(false);
dataSource.setFields(userIdField);
ListGrid grid = new ListGrid();
grid.setDataSource(dataSource);
grid.setWidth100();
grid.setHeight(150);
grid.setAutoFetchData(true);
grid.draw();
}
}
In every case, I get the same error message:
[ERROR] [SoccerAdmin] - 15:20:55.945:XRP6:WARN:RestDataSource:isc_RestDataSource_0:RestDataSouce transformResponse(): JSON response text does not appear to be in standard response format.
However, if I change from a RestDataSource to a DataSource, then I don't have any issues with TransformResponse.
I guess maybe I don't know what TransformResponse is supposed to do with RestDataSource, but I did read the SmartClient Docs for what it's worth.
If I find a great workaround, then I will post an answer.
Ultimately I changed my LoginDataSource from extending RestDataSource to extending DataSource and all my issues went away.
Per the documentation, my response data was complete and accurate.
Also, my actual data was a valid JSON object and verified that against several sites.
Not sure what he bug with SmartGWT RestDataSource is, but I feel that it is a bug ... unless they can explain to me why it isn't.
Hope this helps someone else!
I'm using AsyncTask in order to display data provided from database through PHP and JSON, so when I try to run out my application I got that errors :
09-20 15:31:51.330: E/Buffer Error(4484): Error converting result java.lang.NullPointerException
09-20 15:31:51.330: E/JSON Parser(4484): Error parsing data org.json.JSONException: End of input at character 0 of
This is my Java Class :
package com.androidhive.dashboard;
import android.app.Activity;
import android.app.ListActivity;
import android.os.Bundle;
import androidhive.dashboard.R;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import org.apache.http.NameValuePair;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.app.ProgressDialog;
import android.content.Intent;
import android.os.AsyncTask;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.SimpleAdapter;
import android.widget.TextView;
public class PlacesActivity extends ListActivity {
// Progress Dialog
private ProgressDialog pDialog;
// Creating JSON Parser object
JSONParser jParser = new JSONParser();
ArrayList<HashMap<String, String>> productsList;
// url to get all products list
private static String url_all_products = "http://192.168.1.74/test/focus.php";
// JSON Node names
private static final String TAG_SUCCESS = "success";
private static final String TAG_PRODUCTS = "products";
private static final String TAG_PID = "pid";
private static final String LIB_ART = "LibArt";
private static final String COD_ART = "CodArt";
// products JSONArray
JSONArray products = null;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.places_layout);
// Hashmap for ListView
productsList = new ArrayList<HashMap<String, String>>();
// Loading products in Background Thread
new LoadAllProducts().execute();
// Get listview
ListView lv = getListView();
}//onCreate finish
// Response from Edit Product Activity
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
// if result code 100
if (resultCode == 100) {
// if result code 100 is received
// means user edited/deleted product
// reload this screen again
Intent intent = getIntent();
finish();
startActivity(intent);
}
}
/**
* Background Async Task to Load all product by making HTTP Request
* */
class LoadAllProducts extends AsyncTask<String, String, String> {
/**
* Before starting background thread Show Progress Dialog
* */
#Override
protected void onPreExecute() {
super.onPreExecute();
pDialog = new ProgressDialog(PlacesActivity.this);
pDialog.setMessage("Loading products. Please wait...");
pDialog.setIndeterminate(false);
pDialog.setCancelable(false);
pDialog.show();
}
/**
* getting All products from url
* */
protected String doInBackground(String... args) {
// Building Parameters
List<NameValuePair> params = new ArrayList<NameValuePair>();
// getting JSON string from URL
JSONObject json = jParser.makeHttpRequest(url_all_products, "GET", params);
// Check your log cat for JSON reponse
Log.d("All Products: ", json.toString());
try {
// Checking for SUCCESS TAG
int success = json.getInt(TAG_SUCCESS);
if (success == 1) {
// products found
// Getting Array of Products
products = json.getJSONArray(TAG_PRODUCTS);
// looping through All Products
for (int i = 0; i < products.length(); i++) {
JSONObject c = products.getJSONObject(i);
// Storing each json item in variable
String id = c.getString(TAG_PID);
String LibArt = c.getString(LIB_ART);
String CodArt = c.getString(COD_ART);
// creating new HashMap
HashMap<String, String> map = new HashMap<String, String>();
// adding each child node to HashMap key => value
map.put(TAG_PID, id);
map.put(LIB_ART, LibArt);
map.put(COD_ART,CodArt);
// adding HashList to ArrayList
productsList.add(map);
}
}
} catch (JSONException e) {
e.printStackTrace();
}
return null;
}
/**
* After completing background task Dismiss the progress dialog
* **/
protected void onPostExecute(String file_url) {
// dismiss the dialog after getting all products
pDialog.dismiss();
// updating UI from Background Thread
runOnUiThread(new Runnable() {
public void run() {
/**
* Updating parsed JSON data into ListView
* */
ListAdapter adapter = new SimpleAdapter(
PlacesActivity.this, productsList,
R.layout.list_item, new String[] { COD_ART,TAG_PID,
LIB_ART},
new int[] { R.id.codart ,R.id.pid, R.id.libart });
// updating listview
setListAdapter(adapter);
}
});
}
}
}
And here it is the PHP File :
<?php
require 'FastJSON.class.php';
$db = mssql_connect ('HPWALID', '', '');
$ret = mssql_select_db ('Focus', $db) or die ('Echec lors de la connexion: '.mysql_error ());
$result = mssql_query("SELECT * FROM TabStock");
if (mssql_num_rows($result) > 0) {
// looping through all results
// products node
$response["products"] = array();
while ($row = mssql_fetch_array($result)) {
// temp user array
$product = array();
$product["CodArt"] = $row["CodArt"];
$product["LibArt"] = $row["LibArt"];
$product["pid"] = $row["pid"];
// push single product into final response array
array_push($response["products"], $product);
}
// success
$response["success"] = 1;
// echoing JSON response
$var = FastJSON::encode($response);
echo $var;
} else {
// no products found
$response["success"] = 0;
$response["message"] = "No products found";
// echo no users JSON
// echo json_encode($response);
$var = FastJSON::encode($response);
echo $var;
}
?>
change your IP address(192.168.1.74) to (10.0.2.2) if you are using emualtor