Amazon Product Advertising API signed request with Java - java

After many hours of tinkering and reading the whole internet several times I just can't figure out how to sign requests for use with the Product Advertising API.
So far I managed to generate a client from the provided WSDL file. I used a tutorial by Amazon for this. You can find it here:
Tutorial for generating the web service client
So far no problems. To test the client I wrote a small piece of code. The code is intended to simply get some information about a product. The product is specified by its ASIN.
The code:
package client;
import com.ECS.client.jax.AWSECommerceService;
import com.ECS.client.jax.AWSECommerceServicePortType;
import com.ECS.client.jax.ItemLookup;
import com.ECS.client.jax.ItemLookupResponse;
import com.ECS.client.jax.ItemLookupRequest;
public class Client {
public static void main(String[] args) {
System.out.println("API Test startet");
AWSECommerceService service = new AWSECommerceService();
AWSECommerceServicePortType port = service.getAWSECommerceServicePort();
ItemLookupRequest itemLookup = new ItemLookupRequest();
itemLookup.setIdType("ASIN");
itemLookup.getItemId().add("B000RE216U");
ItemLookup lookup = new ItemLookup();
lookup.setAWSAccessKeyId("<mykeyishere>");
lookup.getRequest().add(itemLookup);
ItemLookupResponse response = port.itemLookup(lookup);
String r = response.toString();
System.out.println("response: " + r);
System.out.println("API Test stopped");
}
}
As you can see there is no part where I sign the request. I have worked my way through a lot of the classes used and found no methods for signing the request.
So, how to sign a request?
I actually found something in the documentation: request authentication
But they don't use their own API. The proposed solutions are more or less for manual use only. So I looked in the client classes to sort out if I could get the request URL and put all the parts needed for request signing in myself. But there are no such methods.
I hope someone can point out what I am doing wrong.
This is what I did to solve the problem. All the credit goes to Jon and the guys of the Amazon forums.
Before I outline what I did, here is a link to the post which helped me to solve the problem: Forum Post on Amazon forums.
I downloaded the awshandlerresolver.java which is linked in the post. Than I modified my own code so it looks like this:
package client;
import com.ECS.client.jax.AWSECommerceService;
import com.ECS.client.jax.AWSECommerceServicePortType;
import com.ECS.client.jax.ItemLookup;
import com.ECS.client.jax.ItemLookupResponse;
import com.ECS.client.jax.ItemLookupRequest;
public class Client {
public static void main(String[] args) {
System.out.println("API Test startet");
AWSECommerceService service = new AWSECommerceService();
service.setHandlerResolver(new AwsHandlerResolver("<Secret Key>")); // important
AWSECommerceServicePortType port = service.getAWSECommerceServicePort();
ItemLookupRequest itemLookup = new ItemLookupRequest();
itemLookup.setIdType("ASIN");
itemLookup.getItemId().add("B000RE216U");
ItemLookup lookup = new ItemLookup();
lookup.setAWSAccessKeyId("<Access Key>"); // important
lookup.getRequest().add(itemLookup);
ItemLookupResponse response = port.itemLookup(lookup);
String r = response.toString();
System.out.println("response: " + r);
System.out.println("API Test stopped");
}
}
The println on the end are more or less useless. But it works. I also used the WSDL Jon linked to generate a new webservice client. I just changed the URLs in the tutorial I posted in my question.

Try this afer you create the service
service.setHandlerResolver(new AwsHandlerResolver(my_AWS_SECRET_KEY));
You'll need this class and this jar file to add as a reference to your project as AwsHandlerResolver uses Base64 encoding.
You'll need to rename the AwsHandlerResolver file to the name of the class as the file name is all lower case.
I think the rest of the code you have is fine.
The WSDL is http://webservices.amazon.com/AWSECommerceService/AWSECommerceService.wsdl

This discussion and the related Amazon post helped me get the client working. That being said, I felt that the solution could be improved with regards to the following:
Setting WebService handlers in code is discouraged. A XML configuration file and a corresponding #HandlerChain annotation are recommended.
A SOAPHandler is not required in this case, LogicalHandler would do just fine. A SOAPHandler has more reach than a LogicalHandler and when it comes to code, more access is not always good.
Stuffing the signature generation, addition of a Node and printing the request in one handler seems like a little too much. These could be separated out for separation of responsibility and ease of testing. One approach would be to add the Node using a XSLT transformation so that the handler could remain oblivious of the transformation logic. Another handler could then be chained which just prints the request.
Example

i did this in spring it's working fine.
package com.bookbub.application;
import com.ECS.client.jax.*;
import com.ECS.client.jax.ItemSearch;
import javax.xml.ws.Holder;
import java.math.BigInteger;
import java.util.List;
public class TestClient {
private static final String AWS_ACCESS_KEY_ID = "AI*****2Y7Z****DIHQ";
private static final String AWS_SECRET_KEY = "lIm*****dJuiy***YA+g/vnj/Ix*****Oeu";
private static final String ASSOCIATE_TAG = "****-**";
public static void main(String[] args) {
TestClient ist = new TestClient();
ist.runSearch();
}
public void runSearch()
{
AWSECommerceService service = new AWSECommerceService();
service.setHandlerResolver(new AwsHandlerResolver(AWS_SECRET_KEY));
AWSECommerceServicePortType port = service.getAWSECommerceServicePort();
ItemSearchRequest request = new ItemSearchRequest();
request.setSearchIndex("Books");
request.setKeywords("java web services up and running oreilly");
ItemSearch search = new ItemSearch();
search.getRequest().add(request);
search.setAWSAccessKeyId(AWS_ACCESS_KEY_ID);
Holder<OperationRequest> operation_request =null;
Holder<List<Items>> items = new Holder<List<Items>>();
port.itemSearch(
search.getMarketplaceDomain(),
search.getAWSAccessKeyId(),
search.getAssociateTag(),
search.getXMLEscaping(),
search.getValidate(),
search.getShared(),
search.getRequest(),
operation_request,
items);
java.util.List<Items> result = items.value;
BigInteger totalPages = result.get(0).getTotalResults();
System.out.println(totalPages);
for (int i = 0; i < result.get(0).getItem().size(); ++i)
{ Item myItem = result.get(0).getItem().get(i);
System.out.print(myItem.getASIN());
System.out.print(", ");
System.out.println(myItem.getDetailPageURL());
System.out.print(", ");
System.out.println(myItem.getSmallImage() == null ? "" : myItem.getSmallImage().getURL());
}
}
}

You could achieve the same monetization outcomes with the IntentBrite API as well

Related

How to fix "Error: Invalid GCS Path Specified" when using Java with Google's Vision API?

I am currently following this example on the Vision API docs:found here
import com.google.cloud.vision.v1.*;
import com.google.cloud.vision.v1.Feature.Type;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.List;
public class VisionApiTest {
public static void main(String... args) throws Exception {
PrintStream stream = new PrintStream(new File("src/test.txt"));
detectTextGcs("https://www.w3.org/TR/SVGTiny12/examples/textArea01.png", stream);
}
public static void detectTextGcs(String gcsPath, PrintStream out) throws Exception, IOException {
List<AnnotateImageRequest> requests = new ArrayList<>();
ImageSource imgSource = ImageSource.newBuilder().setGcsImageUri(gcsPath).build();
Image img = Image.newBuilder().setSource(imgSource).build();
Feature feat = Feature.newBuilder().setType(Type.TEXT_DETECTION).build();
AnnotateImageRequest request =
AnnotateImageRequest.newBuilder().addFeatures(feat).setImage(img).build();
requests.add(request);
try (ImageAnnotatorClient client = ImageAnnotatorClient.create()) {
BatchAnnotateImagesResponse response = client.batchAnnotateImages(requests);
List<AnnotateImageResponse> responses = response.getResponsesList();
for (AnnotateImageResponse res : responses) {
if (res.hasError()) {
out.printf("Error: %s\n", res.getError().getMessage());
return;
}
// For full list of available annotations, see http://g.co/cloud/vision/docs
for (EntityAnnotation annotation : res.getTextAnnotationsList()) {
out.printf("Text: %s\n", annotation.getDescription());
out.printf("Position : %s\n", annotation.getBoundingPoly());
}
}
}
}
}
After passing in the gcsPath String into the detectTextGcs method in the example, I am given the error: "Error: Invalid GCS path specified: https://www.w3.org/TR/SVGTiny12/examples/textArea01.png"
I am expecting for the PrintStream object to write to the file the text that is held within the picture which will be "Tomorrow, and\ntomorrow, and\ntomorrow; blah blah blah...". After trying the API on Vision API doc page mentioned above, it works fine, but not within IntelliJ.
Any help is greatly appreciated. Thank you. (Also forgive me if this isn't a well worded question, it's my first time posting)
I actually figured out the problem. The problem lies within the in line 3 of the detectGcsText() method.
ImageSource imgSource = imageSource.newBuilder().setGcsImageUri(gcsPath).build();
If you would like to use a regular HTTP URI, you must use setImageUri(path) instead of setGcsImageUri(gcsPath).
Thank you for everyone's help!
Google Cloud Storage (GCS) is a storage system where you can persistently save data as blob storage. In GCS, we have the concept of buckets which are "named" containers of data and objects which are named instances of data. To specify a Blob, we Google has invented the notion of a GCS URL of the form:
gs://[BUCKET_NAME]/[OBJECT_NAME]
In your story, you have specified an HTTP URL where a GCS Url was expected. You must not specify an HTTP URL where a GCS URL is required.

Rally Rest API fetch conversation post by defect FormattedID

I am trying to pull discussions for a given defect. I understand from a prior question I asked that it is not possible to pull the discussion data as a property of the defect itself rather I must run a separate fetch request.
The problem is that I can not identify any query filter to use when pulling conversation posts. This leads me to believe I would have to loop through every single conversation post and try to find the matching defect number in the actual data returned which would be highly inefficient.
Rather I would prefer to simply run a query fetch for each defect that uses a query filter for the formatted ID that will only return the conversation posts that apply for that defect.
import com.google.gson.JsonElement;
import com.rallydev.rest.RallyRestApi;
import com.rallydev.rest.request.QueryRequest;
import com.rallydev.rest.response.QueryResponse;
import com.rallydev.rest.util.Fetch;
import com.rallydev.rest.util.QueryFilter;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
public class ExtractFull {
#SuppressWarnings("unchecked")
public static void main(String args[]) throws URISyntaxException, IOException {
RallyRestApi restApi = new RallyRestApi(new URI("https://rally1.rallydev.com"), "_myapikey");
restApi.setApplicationName("DANA Example");
restApi.setProxy(new URI("http://myproxy:8080"), "username", "pass");
System.out.println("Querying Rally for defects, this may take some time");
try {
QueryRequest defectRequest = new QueryRequest("ConversationPost");
defectRequest.setPageSize(2000);
defectRequest.setLimit(5000);
QueryFilter filter = new QueryFilter("FormattedID","=","DE10101");
defectRequest.setQueryFilter(filter);
defectRequest.setFetch(new Fetch());
QueryResponse queryResponse = restApi.query(defectRequest);
for(JsonElement result: queryResponse.getResults()){
System.out.println(result);
}
} finally {
restApi.close();
}
}
}
This code doesn't work. I assume because "FormattedId" isn't a valid object of the "ConversationPost" type. I don't know if it's possible to filter for parent defect ID when querying a conversation post but that is what I need to do.
Specifically the code I am referring to is here:
QueryRequest defectRequest = new QueryRequest("ConversationPost");
defectRequest.setPageSize(2000);
defectRequest.setLimit(5000);
QueryFilter filter = new QueryFilter("FormattedID","=","DE10101");
defectRequest.setQueryFilter(filter);
Use the standard WSAPI, I can query like this:
(Artifact.FormattedID = "US123")
Was able to solve this issue on my own. The problem was that I was attempting to use "QueryRequest" without having a valid "type" to pass to the constructor.
The correct solution was to use "GetRequest" with the path to the defect discussion page being passed as the url (without requiring me to set an object type in the constructor). This returned a GetRequest object which contained a result set with all of the conversation posts.
GetRequest getRequest = new GetRequest(discussionURL);
GetResponse getResponse = restApi.get(getRequest);
The "discussion url" did not contain the "https://rally1.rallydev.com" which was declared when creating the rallyApi - the discussionURL variable contains the entire defect discussion page URL but without the above rally api url so for example "/slm/webservice/v2.0/Defect/106032660792/Discussion"

401 Unauthorized error when using a server account to impersonate a user in order to access their Google Drive

I am writing a back-end process in Java that will impersonate a user and add/remove documents on their Google Drive.
The server account seems to authenticate correctly but when I try to impersonate a user, I get a 401 Unauthorized error. Please see below for details.
Configuration
I have configured the server account as follows:
Created a project under Google APIs and enabled Google Drive API
Created a service account called anothertest#yyyyyyyyy.iam.gserviceaccount.com, set the role as Service Account Actor and given it domain-wide delegation. It has Client ID 110xxxxxxxxx342
I have download the P12 key file
I have configured the domain using the Manage API client access screen to authorize 110xxxxxxxxx342 to have the scope: https://www.googleapis.com/auth/drive.
Google Support have looked at my configuration and have given it the thumbs up.
My code then looks as follows:
package com.dcm.sharingdocuments;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.Map.Entry;
import com.google.api.client.auth.oauth2.TokenErrorResponse;
import com.google.api.client.auth.oauth2.TokenResponseException;
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.api.services.drive.Drive;
import com.google.api.services.drive.DriveScopes;
import com.google.api.services.drive.model.FileList;
public class SharingDocumentsTest3 {
private static final String SERVICE_ACCOUNT_EMAIL = " anothertest#yyyyyyyyy.iam.gserviceaccount.com";
public static Drive getDriveService(String userEmail) throws Exception {
File keyFile = new File("E:\\Projects\\Workspace\\Sharing Documents\\authentication\\AnotherTestKeyFile.p12");
HttpTransport httpTransport = new NetHttpTransport();
JacksonFactory jsonFactory = new JacksonFactory();
List<String> SCOPES = Arrays.asList(DriveScopes.DRIVE_METADATA_READONLY);
GoogleCredential credential = null;
if (userEmail == null) {
credential = new GoogleCredential.Builder().setTransport(httpTransport).setJsonFactory(jsonFactory)
.setServiceAccountId(SERVICE_ACCOUNT_EMAIL).setServiceAccountScopes(SCOPES)
.setServiceAccountPrivateKeyFromP12File(keyFile).build();
credential.refreshToken();
} else {
credential = new GoogleCredential.Builder().setTransport(httpTransport).setJsonFactory(jsonFactory)
.setServiceAccountId(SERVICE_ACCOUNT_EMAIL).setServiceAccountScopes(SCOPES)
.setServiceAccountPrivateKeyFromP12File(keyFile).setServiceAccountUser(userEmail).build();
credential.refreshToken();
}
Drive service = new Drive.Builder(httpTransport, jsonFactory, null).setHttpRequestInitializer(credential)
.build();
return service;
}
public static void main(String[] args) {
SharingDocumentsTest3 sdt3 = new SharingDocumentsTest3();
sdt3.execute();
}
private void execute() {
try {
Drive service = getDriveService(null);
Drive services = getDriveService("anzzzze#zzzzz.me.uk");
displayFiles(services);
} catch (Exception e) {
e.printStackTrace();
}
}
private void displayFiles(Drive service) throws Exception {
FileList result = service.files().list().setPageSize(10).execute();
List<com.google.api.services.drive.model.File> files = result.getFiles();
if (files == null || files.size() == 0) {
System.out.println("No files found.");
} else {
System.out.println("Files:");
for (com.google.api.services.drive.model.File file : files) {
Set<Entry<String, Object>> entries = file.entrySet();
Iterator<Entry<String, Object>> it = entries.iterator();
while (it.hasNext()) {
Entry<String, Object> entry = it.next();
String key = entry.getKey();
Object value = entry.getValue();
if (value instanceof String) {
System.out.println("\tKey = " + key + ", Value = " + (String) value);
} else {
System.out.println("\tKey = " + key + ", Value = " + value.toString());
}
}
System.out.printf("%s (%s)\n", file.getName(), file.getId());
}
}
}
}
When I run the code as is above, I get the error:
Mar 29, 2017 9:55:27 AM com.google.api.client.googleapis.services.AbstractGoogleClient <init>
WARNING: Application name is not set. Call Builder#setApplicationName.
com.google.api.client.auth.oauth2.TokenResponseException: 401 Unauthorized
at com.google.api.client.auth.oauth2.TokenResponseException.from(TokenResponseException.java:105)
at com.google.api.client.auth.oauth2.TokenRequest.executeUnparsed(TokenRequest.java:287)
at com.google.api.client.auth.oauth2.TokenRequest.execute(TokenRequest.java:307)
at com.google.api.client.googleapis.auth.oauth2.GoogleCredential.executeRefreshToken(GoogleCredential.java:384)
at com.google.api.client.auth.oauth2.Credential.refreshToken(Credential.java:489)
at com.dcm.sharingdocuments.SharingDocumentsTest3.getDriveService(SharingDocumentsTest3.java:50)
at com.dcm.sharingdocuments.SharingDocumentsTest3.execute(SharingDocumentsTest3.java:75)
at com.dcm.sharingdocuments.SharingDocumentsTest3.main(SharingDocumentsTest3.java:65)
So the code fails at credential.refreshToken() when I set the setServiceAccountUser. It appears to have successfully refreshed the token when I do not. I have tried various combinations of this code – e.g. commented out the refreshToken() lines, commented out the getDriveService(null) line – but whenever I try to use/refresh the credential obtained for the impersonated user I get the 401 Unauthorized error.
If I modify the code so that the drive obtained by getDriveService(null) is passed to DisplayFiles(...), then I get one file listed called “Getting Started”. So it seems that the service account authorization is working and Google have added their default file to the Drive for the server account.
I am using google-*1.22.0.jar files and Java 1.8 to the run the above code
The problem I think is in the way I have configured the domain or the way I am trying to impersonate the user but my code looks as many examples on the web do and Google Support appear to say that I have configured the domain correctly.
Anything you can suggest as a resolution or next step would be much appreciated!
I have been stuck on this problem for a long time and I finally found my problem. There is definitely a bug in the "Manage API client access" Admin console...
You must put the "Client ID" (e.g. 110xxxxxxxxx342) for the client name and NOT the "Service Account ID" (the one that looks like an email). Now, their documentation is correct, and they do say in the documentation to use the Client ID, I have to give them that.
So here is the bug. When I arrived to the Manage API screen, I saw "Example: www.example.com". I typed in the Service Account ID there, thinking that the email address format matched "www.example.com" better than the Client ID. I pressed "Authorize", and the entry had clearly been accepted and everything was good. The result looks like this:
It even generated the Client ID from the Service ID! Great! Except my code gets a 401 error every time I try to connect with setServiceUser().
If I return to the Manage API Client Access console and if I remove the previous entry and perform the same actions except use the Client ID instead of the Service ID. The result is this:
Exactly the same, but now I don't get a 401 error. So there is NO WAY to look at the console, and know if you have it successfully configured it or not. I tested this 3 times to make sure I wasn't losing my mind...

Troubleshoot UnknownResourceException when following AWS tutorial

I'm attempting to follow this AWS tutorial. But I'm having trouble at "You can run GreeterWorker successfully at this point." as I'm getting an UnknownResourceException.
Exception in thread "main" com.amazonaws.services.simpleworkflow.model.UnknownResourceException: Unknown domain: helloWorldWalkthrough (Service: AmazonSimpleWorkflow; Status Code: 400; Error Code: UnknownResourceFault; Request ID: xxxxx)
Steps taken
Resolved permission exception by attaching the SimpleWorkflowFullAccess IAM Policy to my AWS user.
Verified that the helloWorldWalkthrough is registered on the SWF dashboard
registered new helloWorldWalkthrough2 domain, same error occured
The tutorial didn't cover the step about attaching the SimpleWorkflowFullAccess policy to the AWS user, so I'm wondering if there is a similar undocumented step to allow my user to find this domain.
My code is copy/pasted from the GreeterWorker class in the tutorial.
import com.amazonaws.ClientConfiguration;
import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.services.simpleworkflow.AmazonSimpleWorkflow;
import com.amazonaws.services.simpleworkflow.AmazonSimpleWorkflowClient;
import com.amazonaws.services.simpleworkflow.flow.ActivityWorker;
import com.amazonaws.services.simpleworkflow.flow.WorkflowWorker;
public class GreeterWorker {
public static void main(String[] args) throws Exception {
ClientConfiguration config = new ClientConfiguration().withSocketTimeout(70*1000);
String swfAccessId = System.getenv("AWS_ACCESS_KEY_ID");
String swfSecretKey = System.getenv("AWS_SECRET_KEY");
AWSCredentials awsCredentials = new BasicAWSCredentials(swfAccessId, swfSecretKey);
AmazonSimpleWorkflow service = new AmazonSimpleWorkflowClient(awsCredentials, config);
service.setEndpoint("https://swf.us-east-1.amazonaws.com");
String domain = "helloWorldWalkthrough";
String taskListToPoll = "HelloWorldList";
ActivityWorker aw = new ActivityWorker(service, domain, taskListToPoll);
aw.addActivitiesImplementation(new GreeterActivitiesImpl());
aw.start();
WorkflowWorker wfw = new WorkflowWorker(service, domain, taskListToPoll);
wfw.addWorkflowImplementationType(GreeterWorkflowImpl.class);
wfw.start();
}
}
You need to create the domain using the console or through an api call. Domain is not created automatically.
I was also facing the same issue and then I found that the region is hard coded in the main method inside GreeterWorker class as shown below:
service.setEndpoint("https://swf.us-east-1.amazonaws.com");
However my SWF account was in west-2 region.
I was also faving same problem. region is hard coded in tutorial.
I changed code as flllows
service.setEndpoint("https://swf.us-west-2.amazonaws.com");

How to create request from WADL programatically in java

Is there any way we can create request from WSDL for REST services in java?.programatically Read/parse WSDL and append the values to query paramets and execure request.I was able to find example for WSDL as in here
package com.bbog.soap;
import com.eviware.soapui.impl.wsdl.WsdlInterface;
import com.eviware.soapui.impl.wsdl.WsdlOperation;
import com.eviware.soapui.impl.wsdl.WsdlProject;
import com.eviware.soapui.impl.wsdl.support.wsdl.WsdlImporter;
import com.eviware.soapui.model.iface.Operation;
public class WsdlAnalyzer {
public static void main(String[] args) throws Exception {
WsdlProject project = new WsdlProject();
WsdlInterface[] wsdls = WsdlImporter.importWsdl(project, "http://localhost:7000/Solicitud?wsdl");
WsdlInterface wsdl = wsdls[0];
for (Operation operation : wsdl.getOperationList()) {
WsdlOperation op = (WsdlOperation) operation;
System.out.println("OP:"+op.getName());
System.out.println(op.createRequest(true));
System.out.println("Response:");
System.out.println(op.createResponse(true));
}
}
}
The link are:
how to generate a SOAP message with a fully populated request from WSDL without code gen
how to create a SOAP UI project and run requests to it in Java
Is there something similar in REST to read WADL?. Any help is appreciated.Thanks.

Categories

Resources