I have to start Jenkins parameterized build programmatically using Jersey REST API and the values for the parameters must be provided as a JSON object. Any hint or example is welcome.
So, seems like you have not tried it yourself. I can give you a fast 5 minute solution, that should be reworked to be clear and not so ugly, but it works :)
import java.util.ArrayList;
import java.util.List;
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.WebResource;
public class JenkinsJob {
public static void main(String[] args) {
runParamJob("http://jenkins.host/", "SOME_JOB", "{\"object\":\"test\"}");
}
public static String runParamJob(String url, String jobName, String paramsJSON) {
String USERNAME = "user";
String PASSWORD = "pass";
Client client = Client.create();
client.addFilter(new com.sun.jersey.api.client.filter.HTTPBasicAuthFilter(USERNAME, PASSWORD));
WebResource webResource = client.resource(url + jobName + "/buildWithParameters?PARAMETER=" + paramsJSON);
ClientResponse response = webResource.type("application/json").get(ClientResponse.class, paramsJSON);
String jsonResponse = response.getEntity(String.class);
client.destroy();
System.out.println("Server response:" + jsonResponse);
return jsonResponse;
}
}
In order to use rest API for parameterized build you should use POST and not get according to Jenkins wiki. Here is an example. Make sure that the json you send is as instructed at the documentation.
take this json for example:
{"parameter": [{"name":"id", "value":"123"}, {"name":"verbosity", "value":"high"}]}
You have two parameters with name and value for each. If I will use what was written at the former answer by #stanjer the json should look like that:
{"parameter": [{"name":"object", "value":"test"}]}
In addition there is a good discussion about it here
I would not recommend to use USER:PASSWORD but use token that could be configured in Jenkins job UI. Here is a class that implements builder pattern for with/without parameters.
import java.util.Map;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.core.util.MultivaluedMapImpl;
public class JenkinsTrigger {
private String host;
private String jenkinsToken;
private String jobParams;
private MultivaluedMap<String,String> queryParams = new MultivaluedMapImpl();
private Client client = Client.create();
private WebResource webResource;
private JenkinsTrigger(JenkinsTriggerBuilder jenkinsTriggerBuilder){
this.host = jenkinsTriggerBuilder.host;
this.jenkinsToken = jenkinsTriggerBuilder.jenkinsToken;
this.jobParams = getJobParams(jenkinsTriggerBuilder.jobParams);
webResource = client.resource(this.host);
queryParams.add("token", jenkinsToken);
}
public void trigger(){
ClientResponse response = webResource.path(this.host).queryParams(queryParams)
.type(MediaType.APPLICATION_FORM_URLENCODED_TYPE)
.header("Content-type", "application/x-www-form-urlencoded")
.post(ClientResponse.class, jobParams);
if (response.getStatus() != 201) {
throw new RuntimeException("Failed : HTTP error code : "
+ response.toString());
} else {
System.out.println("Job Trigger: " + host);
}
}
private String getJobParams(Map<String,String> jobParams){
StringBuilder sb = new StringBuilder();
sb.append("json={\"parameter\":[");
jobParams.keySet().forEach(param -> {
sb.append("{\"name\":\""+param+"\",");
sb.append("\"value\":\""+ jobParams.get(param) + "\"},");
});
sb.setLength(sb.length() - 1);
sb.append("]}");
System.out.println("Job Parameters:" + sb.toString());
return sb.toString();
}
public static class JenkinsTriggerBuilder {
private String host;
private String jenkinsToken;
private Map<String,String> jobParams = null;
public JenkinsTriggerBuilder(String host, String jenkinsToken){
this.host = host;
this.jenkinsToken = jenkinsToken;
}
public JenkinsTriggerBuilder jobParams(Map<String,String> jobParams){
this.jobParams = jobParams;
return this;
}
public JenkinsTrigger build(){
return new JenkinsTrigger(this);
}
}
}
And here is usage sample:
Map<String, String> params = new HashMap<>();
params.put("ENV", "DEV103");
JenkinsTrigger trigger = new JenkinsTriggerBuilder("https://JENKINS_HOST/job/JOB_NAME/buildWithParameters","JOB_TOKEN").jobParams(params).build();
trigger.trigger();
best of luck
Related
I'm exploring reactive programming with Spring Webflux and therefore, I'm trying to make my code completely nonblocking to get all the benefits of a reactive application.
Currently my code for the method to parse a Json String to a JsonNode to get specific values (in this case the elementId) looks like this:
public Mono<String> readElementIdFromJsonString(String jsonString){
final JsonNode jsonNode;
try {
jsonNode = MAPPER.readTree(jsonString);
} catch (IOException e) {
return Mono.error(e);
}
final String elementId = jsonNode.get("elementId").asText();
return Mono.just(elementId);
}
However, IntelliJ notifies me that I'm using an inappropriate blocking method call with this code:
MAPPER.readTree(jsonString);
How can I implement this code in a nonblocking way? I have seen that since Jackson 2.9+, it is possible to parse a Json String in a nonblocking async way, but I don't know how to use that API and I couldn't find an example how to do it correctly.
I am not sure why it is saying it is a blocking call since Jackson is non blocking as far as I know. Anyway one way to resolve this issue is to use schedulers if you do not want to use any other library. Like this.
public Mono<String> readElementIdFromJsonString(String input) {
return Mono.just(Mapper.readTree(input))
.map(it -> it.get("elementId").asText())
.onErrorResume( it -> Mono.error(it))
.subscribeOn(Schedulers.boundedElastic());
}
Something along that line.
import reactor.core.publisher.Mono;
import java.nio.charset.StandardCharsets;
import org.springframework.core.ResolvableType;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.core.io.buffer.DefaultDataBuffer;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.http.codec.json.AbstractJackson2Decoder;
import org.springframework.util.MimeType;
import org.springframework.util.MimeTypeUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
#FunctionalInterface
public interface MessageParser<T> {
Mono<T> parse(String message);
}
public class JsonNodeParser extends AbstractJackson2Decoder implements MessageParser<JsonNode> {
private static final MimeType MIME_TYPE = MimeTypeUtils.APPLICATION_JSON;
private static final ObjectMapper OBJECT_MAPPER = allocateDefaultObjectMapper();
private final DefaultDataBufferFactory factory;
private final ResolvableType resolvableType;
public JsonNodeParser(final Environment env) {
super(OBJECT_MAPPER, MIME_TYPE);
this.factory = new DefaultDataBufferFactory();
this.resolvableType = ResolvableType.forClass(JsonNode.class);
this.setMaxInMemorySize(100000); // 1MB
canDecodeJsonNode();
}
#Override
public Mono<JsonNode> parse(final String message) {
final byte[] bytes = message.getBytes(StandardCharsets.UTF_8);
return decode(bytes);
}
private Mono<JsonNode> decode(final byte[] bytes) {
final DefaultDataBuffer defaultDataBuffer = this.factory.wrap(bytes);
return this.decodeToMono(Mono.just(defaultDataBuffer), this.resolvableType, MIME_TYPE, Map.of())
.ofType(JsonNode.class)
.subscribeOn(Schedulers.boundedElastic())
.doFinally((t) -> DataBufferUtils.release(defaultDataBuffer));
}
private void canDecodeJsonNode() {
if (!canDecode(this.resolvableType, MIME_TYPE)) {
throw new IllegalStateException(String.format("JsonNodeParser doesn't supports the given tar`enter code here`get " +
"element type [%s] and the MIME type [%s]", this.resolvableType, MIME_TYPE));
}
}
}
We have aes-256 encryption for some data in one of the tables and we are migrating this to sql server. The problem is that we cannot decrypt the data in sql server due to incompatibility. Is there any way we can encrypt data in MYSQL in a way which is compatible with sql server aswell. Any advise ?
if you know the secretkey then you can decrypt the data see following code for encryption and decryption of AES-256 . the code is written in JAVA
check this link AES-256 Password Based Encryption/Decryption in Java
import java.security.AlgorithmParameters;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
public class EncryptionDecryption {
private static String salt;
private static int iterations = 65536 ;
private static int keySize = 256;
private static byte[] ivBytes;
private static SecretKey secretKey;
public static void main(String []args) throws Exception {
salt = getSalt();
char[] message = "PasswordToEncrypt".toCharArray();
System.out.println("Message: " + String.valueOf(message));
System.out.println("Encrypted: " + encrypt(message));
System.out.println("Decrypted: " + decrypt(encrypt(message).toCharArray()));
}
public static String encrypt(char[] plaintext) throws Exception {
byte[] saltBytes = salt.getBytes();
SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
PBEKeySpec spec = new PBEKeySpec(plaintext, saltBytes, iterations, keySize);
secretKey = skf.generateSecret(spec);
SecretKeySpec secretSpec = new SecretKeySpec(secretKey.getEncoded(), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secretSpec);
AlgorithmParameters params = cipher.getParameters();
ivBytes = params.getParameterSpec(IvParameterSpec.class).getIV();
byte[] encryptedTextBytes = cipher.doFinal(String.valueOf(plaintext).getBytes("UTF-8"));
return DatatypeConverter.printBase64Binary(encryptedTextBytes);
}
public static String decrypt(char[] encryptedText) throws Exception {
System.out.println(encryptedText);
byte[] encryptedTextBytes = DatatypeConverter.parseBase64Binary(new String(encryptedText));
SecretKeySpec secretSpec = new SecretKeySpec(secretKey.getEncoded(), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secretSpec, new IvParameterSpec(ivBytes));
byte[] decryptedTextBytes = null;
try {
decryptedTextBytes = cipher.doFinal(encryptedTextBytes);
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
} catch (BadPaddingException e) {
e.printStackTrace();
}
return new String(decryptedTextBytes);
}
public static String getSalt() throws Exception {
SecureRandom sr = SecureRandom.getInstance("SHA1PRNG");
byte[] salt = new byte[20];
sr.nextBytes(salt);
return new String(salt);
}
}
I need to perform load testing for my REST web service using Cucumber and Java. This REST web service accepts one input which is a String called id and returns complex JSON object.
I wrote a .feature file with Given, When and Then annotations which are defined in java.
The skeleton definition of the class and annotations are here under.
1) Feature (UserActivity.feature)
#functional #integration
Feature: User System Load Test
Scenario Outline: Load test for user data summary from third party UserSystem
Given Simultaneously multiple users are hitting XYZ services with an id=<ids>
When I invoke third party link with above id for multiple users simultaneously
Then I should get response code and response message for all users
Examples:
| ids |
| "pABC123rmqst" |
| "fakXYZ321rmv" |
| "bncMG4218jst" |
2) LoadTestStepDef.java (Feature definition)
package com.system.test.cucumber.steps;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.junit.runners.model.InitializationError;
import com.system.test.restassured.LoadTestUtil;
import cucumber.api.java.en.Given;
import cucumber.api.java.en.Then;
import cucumber.api.java.en.When;
public class LoadTestStepDef
{
private static Logger LOG = LogManager.getLogger( LoadTestStepDef.class );
private String id = null;
private LoadTestUtil service = null;
#Given("^Simultaneously multiple users are hitting XYZ services with an a id=\"(.*?)\"$" )
public void Simultaneously_multiple_users_are_hitting_XYZ_services_with_a_id( String id )
{
LOG.debug( "ID {}", id );
LOG.info( "ID {}", id );
this.id = id;
}
#When( "^I invoke third party link with above id for multiple users simultaneously$" )
public void invoke_third_party_link_With_Above_ID_for_multiple_users_simultaneously() throws InitializationError
{
LOG.debug( " *** Calling simulatenously {} ", id );
LOG.info( " *** Calling simulatenously {}", id );
//Create object of service
service = new LoadTestUtil();
//Set the id to the created service and invoke method
service.setData(id);
service.invokeSimultaneosCalls(10);
}
#Then( "^I should get response code and response message for all users$" )
public void Should_get_response_code_and_response_message_for_all_users()
{
LOG.info( "*** Assert for response Code" );
service.assertHeaderResponseCodeAndMessage();
}
}
3) LoadTestUtil.java
package com.system.test.restassured;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import java.util.concurrent.Callable;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.jayway.restassured.path.json.JsonPath;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
public class LoadTestUtil
{
private String id = null;
private int numberofTimes;
//Create List to hold all Future<Long>
private List<JsonPath> jsonResponseList = new ArrayList<JsonPath>();
//No arg Constructor
public LoadTestUtil()
{
}
//Set data method to set the initial id
public void setData(String id)
{
LOG.info( "LoadTestUtil.setData()", id );
this.id = id;
}
//This method is used call the REST webservice N times using threads and get response
public void invokeSimultaneosCalls(int numberofTimes)
{
LOG.info( "LoadTestUtil.invokeSimultaneosCalls() - Start" );
this.numberofTimes = numberofTimes;
try
{
long start = System.nanoTime();
int numberOfThreads = Runtime.getRuntime().availableProcessors();
LOG.info("Number of processor available {}" , numberOfThreads);
//Create pool for the Executor Service with numberOfThreads.
ExecutorService executor = Executors.newFixedThreadPool(numberOfThreads);
//Create a list to hold the Future object associated with Callable
List<Future<JsonPath>> futureList = new ArrayList<Future<JsonPath>>();
//Create new RESTServiceCallTask instance
Callable<JsonPath> callable = new RESTServiceCallTask(id);
Future<JsonPath> future = null;
//Iterate N number of times to submit the callable object
for(int count=1; count<=numberofTimes;count++)
{
//Submit Callable tasks to the executor
future = executor.submit(callable);
//Add Future to the list to get return value using Future
futureList.add(future);
}
//Create a flag to monitor the thread status. Check whether all worker threads are completed or not
boolean threadStatus = true;
while (threadStatus)
{
if (future.isDone())
{
threadStatus = false;
//Iterate the response obtained from the futureList
for(Future<JsonPath> futuree : futureList)
{
try
{
//print the return value of Future, notice the output delay in console
// because Future.get() waits for task to get completed
JsonPath response = futuree.get();
jsonResponseList.add(response);
}
catch(InterruptedException ie)
{
ie.printStackTrace();
}
catch(ExecutionException ee)
{
ee.printStackTrace();
}
catch(Exception e)
{
e.printStackTrace();
}
}//End of for to iterate the futuree list
} //End of future.isDone()
} //End of while (threadStatus)
//shut down the executor service now
executor.shutdown();
//Calculate the time taken by the threads for execution
executor.awaitTermination(1, TimeUnit.HOURS); // or longer.
long time = System.nanoTime() - start;
logger.info("Tasks took " + time/1e6 + " ms to run");
long milliSeconds = time / 1000000;
long seconds, minutes, hours;
seconds = milliSeconds / 1000;
hours = seconds / 3600;
seconds = seconds % 3600;
seconds = seconds / 60;
minutes = seconds % 60;
logger.info("Task took " + hours + " hours, " + minutes + " minutes and " + seconds + " seconds to complete");
} //End of try block
catch (Exception e)
{
e.printStackTrace();
}
LOG.info("LoadTestUtil.invokeSimultaneosCalls() - jsonResponseList {} " , jsonResponseList);
System.out.println("LoadTestUtil.invokeSimultaneosCalls() - jsonResponseList {} " + jsonResponseList);
LOG.info( "*** LoadTestUtil.invokeSimultaneosCalls() - End" );
}
public void assertHeaderResponseCodeAndMessage(){
//Number of response objects available
int size = jsonResponseList.size();
LOG.info("Number of REST service calls made = ", size);
for(JsonPath jsonResponse : jsonResponseList)
{
String responseCode = jsonResponse.get( "header.response_code").toString();
String responseMessage = jsonResponse.get( "header.response_message").toString();
assertEquals( "200", responseCode);
assertEquals( "success", responseMessage);
}
}
}
4) RESTServiceCallTask.java
This class implements Callable and override the call() method.
In the call() method, the response in the form of JsonPath is returned for each call
package com.system.test.restassured;
import static com.jayway.restassured.RestAssured.basePath;
import static com.jayway.restassured.RestAssured.baseURI;
import static com.jayway.restassured.RestAssured.given;
import static com.jayway.restassured.RestAssured.port;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.system.test.restassured.TestUtil;
import com.jayway.restassured.path.json.JsonPath;
import com.jayway.restassured.response.Response;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
public class RESTServiceCallTask implements Callable<JsonPath>
{
private static Logger LOG = LogManager.getLogger(RESTServiceCallTask.class);
private Response response = null;
private String id;
private String environment;
//private JsonPath jsonPath;
/**
* Constructor initializes the call to third party system
*
* #param id
*/
public RESTServiceCallTask(String id)
{
LOG.info("In RESTServiceCallTask() constructor ");
this.id = id;
//Read the environment variable ENV to get the corresponding environment's REST URL to call
this.environment = System.getProperty("ENV");
baseURI = TestUtil.getbaseURL(environment);
basePath = "/bluelink/tracker/member_summary";
port = 80;
LOG.info(" *** Environment : {}, URI: {} and Resource {} ", environment, baseURI, basePath);
}
//This method is called by the threads to fire the REST service and returns JSONPath for each execution
#Override
public JsonPath call() throws Exception
{
LOG.info(" *** In call() method ");
try
{
response = given().headers("id", this.id).log().all().get();
} catch (Exception e)
{
LOG.error("System Internal Server Error", e);
}
String strResponse = this.response.asString();
LOG.info("Response : {}", strResponse);
JsonPath jsonResponse = new JsonPath(strResponse);
return jsonResponse;
}
}
5) TestUtil.java
This utility class is used to get the REST URL corresponding to the passed environment
package com.system.test.restassured;
import java.util.HashMap;
import java.util.Map;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class TestUtil
{
private static Logger LOG = LogManager.getLogger(TestUtil.class);
private static final Map<String, String> ENVIRONMENT_MAP = new HashMap<String, String>();
static
{
ENVIRONMENT_MAP.put("LOCAL", "http://localhost:9080");
ENVIRONMENT_MAP.put("ENV1", "http://localhost:9080");
ENVIRONMENT_MAP.put("ENV2", "http://localhost:9080");
ENVIRONMENT_MAP.put("ENV3", "http://localhost:9080");
}
public static String getbaseURL(String environment)
{
LOG.info("Environment value fetched = {}", environment);
return ENVIRONMENT_MAP.get(environment);
}
}
The problem here is that the multi-threading feature is not getting executed.
I used the MavenSurefire Plugin and tried with parallel classes and methods. In those cases also the above scenario doesn't work.
Does Cucumber support java multi-threading? If so what is wrong with the above feature definition?
Note - The same task is performed with stand alone program and able to run for 10,000 times
using 4 threads without any issues. However not able to run the above code for 2000 times using Maven. With 2000 times, the system crashed abruptly.
I am using Rational Application Developer 8.5, Websphere Server 8.0 with Maven 3.x for the above setup.
Thanks for your response.
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
It seems that, recent released Google Drive SDK supports directory listing.
https://developers.google.com/drive/v2/reference/files/list
I try to integrate it into my Android app.
package com.jstock.cloud;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import android.util.Log;
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
import com.google.api.client.googleapis.services.GoogleKeyInitializer;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.gson.GsonFactory;
import com.google.api.client.extensions.android2.AndroidHttp;
import com.google.api.services.drive.Drive;
import com.google.api.services.drive.Drive.Files;
import com.google.api.services.drive.model.File;
import com.google.api.services.drive.model.FileList;
import com.jstock.gui.Utils;
public class CloudFile {
public final java.io.File file;
public final long checksum;
public final long date;
public final int version;
private CloudFile(java.io.File file, long checksum, long date, int version) {
this.file = file;
this.checksum = checksum;
this.date = date;
this.version = version;
}
public static CloudFile newInstance(java.io.File file, long checksum, long date, int version) {
return new CloudFile(file, checksum, date, version);
}
public static CloudFile loadFromGoogleDrive(String authToken) {
final HttpTransport transport = AndroidHttp.newCompatibleTransport();
final JsonFactory jsonFactory = new GsonFactory();
GoogleCredential credential = new GoogleCredential();
credential.setAccessToken(authToken);
Drive service = new Drive.Builder(transport, jsonFactory, credential)
.setApplicationName(Utils.getApplicationName())
.setJsonHttpRequestInitializer(new GoogleKeyInitializer(ClientCredentials.KEY))
.build();
List<File> files = retrieveAllFiles(service);
Log.i("CHEOK", "size is " + files.size());
for (File file : files) {
Log.i(TAG, "title = " + file.getTitle());
}
return null;
}
/**
* Retrieve a list of File resources.
*
* #param service Drive API service instance.
* #return List of File resources.
*/
private static List<File> retrieveAllFiles(Drive service) {
List<File> result = new ArrayList<File>();
Files.List request = null;
try {
request = service.files().list();
} catch (IOException e) {
Log.e(TAG, "", e);
return result;
}
do {
try {
FileList files = request.execute();
result.addAll(files.getItems());
request.setPageToken(files.getNextPageToken());
} catch (IOException e) {
Log.e(TAG, "", e);
request.setPageToken(null);
}
} while (request.getPageToken() != null && request.getPageToken().length() > 0);
Log.i("CHEOK", "yup!");
return result;
}
private static final String TAG = "CloudFile";
}
I always get 0 file returned from server, and there isn't any exception being thrown. Is there anything I had missed out?
You have to request access to the full Drive scope to list all files: https://developers.google.com/drive/scopes#requesting_full_drive_scope_for_an_app
If you use the default (and recommended) scope https://www.googleapis.com/auth/drive.file, you will only be able to see files created or opened by the app.