Put and Get JSON object from Nashorn to MongoDB - java

i want to store JSON Objects from js-scripts running in Nashorn in a MongoDB and get them back again.
The provided API functions look like:
db.put("key", {"mykey":[1,2,3]})
var result = db.get("key")
There are two issues i don't know how to deal with them:
On the Java-side i'm getting a ScriptObjectMirror that implements Map. So if a got a JSON-Object with an Array inside it's already broken here.
e.g. {"key":[1,2,3]} -> {"key": {"0":1, "1":2, "2": 3}}
When JSON object is read from DB it's not possible to JSON.stringify the object. It just returns undefined. Isn' threre any possiblity to inject a JSON object from Java into Nashorn so that is compatible for JSON.stringify?
Do you have any suggestions for my problem?
Thanks

From JS, you can use var jsonResult = Java.asJSONCompatible(result) to get back a custom wrapper where JS Arrays are exposed as Java Lists.
From Java, you can use ScriptObjectMirror.wrapAsJSONCompatible(obj).
Hope that helps.

Your first solutions, from JS to Java, works as expected. The problem is that we are providing an API for other developers and we don't want to tell them to use Java.asJSONCompatible in the JS-Code.
I can't get the 2nd solution, from Java to JS, work.
Here's my Test:
import jdk.nashorn.api.scripting.ScriptObjectMirror;
import org.junit.Assert;
import org.junit.Test;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class NashornJsonConversionTest {
ScriptEngineManager scriptEngineManager = new ScriptEngineManager();
ScriptEngine scriptEngine = scriptEngineManager.getEngineByName("nashorn");
private class MyJsonWrapper extends HashMap<String, Object>{
}
#Test
public void shouldStringifyCorrectly() throws ScriptException {
String exepectedStringified = "{\"value\":1}";
MyJsonWrapper myJsonWrapper = new MyJsonWrapper();
myJsonWrapper.put("value", 1);
scriptEngine.put("jsonObject", ScriptObjectMirror.wrapAsJSONCompatible(myJsonWrapper, null));
Assert.assertEquals(1, scriptEngine.eval("jsonObject.value"));
String result = (String) scriptEngine.eval("JSON.stringify(jsonObject)");
Assert.assertEquals(exepectedStringified, result);
//Expected :{"value":1}
//Actual :null
}
#Test
public void shouldGetJSONWithArrayAsList() throws ScriptException {
Map<String, Object> result = (Map<String, Object>) scriptEngine.eval("Java.asJSONCompatible({value:[1,2,3]})");
List<Integer> values = (List<Integer>) result.get("value");
Assert.assertEquals(values.size(), 3);
// works as expected
}
}

you have to convert the object to a "plain" object in nashorn. Currently your script engine see a mirror.
Here is a jsfiddle: http://jsfiddle.net/Bernicc/00g6acp1/
/**
* This function convert an object coming from nashorn to a plain object.
* Instead of using <code>JSON.parse(value);</code> this method does only create new instances for objects.
* There is no need to parse a number, string or boolean.
*
* #param value the object which should be returned as clean object
* #returns {Object} the clean object
*/
toCleanObject: function (value) {
switch (typeof value) {
case "object":
var ret = {};
for (var key in value) {
ret[key] = this.toCleanObject(value[key]);
}
return ret;
default: //number, string, boolean, null, undefined
return value;
}
}
Above is your test with a minified version of the function. For this example wrapAsJSONCompatible is not necessary.
import jdk.nashorn.api.scripting.ScriptObjectMirror;
import org.junit.Assert;
import org.junit.Test;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class NashornJsonConversionTest {
ScriptEngineManager scriptEngineManager = new ScriptEngineManager();
ScriptEngine scriptEngine = scriptEngineManager.getEngineByName("nashorn");
private class MyJsonWrapper extends HashMap<String, Object>{
}
#Test
public void shouldStringifyCorrectly() throws ScriptException {
String exepectedStringified = "{\"value\":1}";
MyJsonWrapper myJsonWrapper = new MyJsonWrapper();
myJsonWrapper.put("value", 1);
// for this example wrapAsJSONCompatible is not necessary
//scriptEngine.put("jsonObject", ScriptObjectMirror.wrapAsJSONCompatible(myJsonWrapper, null));
scriptEngine.put("jsonObject", myJsonWrapper);
scriptEngine.eval("function toCleanObject(t){switch(typeof t){case\"object\":var e={};for(var n in t)e[n]=toCleanObject(t[n]);return e;default:return t}}");
scriptEngine.eval("jsonObject = toCleanObject(jsonObject);");
Assert.assertEquals(1, scriptEngine.eval("jsonObject.value"));
String result = (String) scriptEngine.eval("JSON.stringify(jsonObject)");
Assert.assertEquals(exepectedStringified, result);
//Expected :{"value":1}
//Actual :null
}
#Test
public void shouldGetJSONWithArrayAsList() throws ScriptException {
Map<String, Object> result = (Map<String, Object>) scriptEngine.eval("Java.asJSONCompatible({value:[1,2,3]})");
List<Integer> values = (List<Integer>) result.get("value");
Assert.assertEquals(values.size(), 3);
// works as expected
}
}
Kind regards from Berlin, Bernard

Related

how to convert arbitrary JSON to XML using BaseX?

How is arbitrary JSON converted to arbitrary XML using BaseX?
I'm looking at JsonParser from BaseX for this specific solution.
In this case, I have tweets using Twitter4J:
package twitterBaseX;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.logging.Logger;
import main.LoadProps;
import org.basex.core.BaseXException;
import twitter4j.JSONException;
import twitter4j.JSONObject;
import twitter4j.Query;
import twitter4j.QueryResult;
import twitter4j.Status;
import twitter4j.Twitter;
import twitter4j.TwitterException;
import twitter4j.TwitterFactory;
import twitter4j.TwitterObjectFactory;
import twitter4j.conf.ConfigurationBuilder;
public class TwitterOps {
private static final Logger log = Logger.getLogger(TwitterOps.class.getName());
public TwitterOps() {
}
private TwitterFactory configTwitterFactory() throws IOException {
LoadProps loadTwitterProps = new LoadProps("twitter");
Properties properties = loadTwitterProps.loadProperties();
log.fine(properties.toString());
ConfigurationBuilder configurationBuilder = new ConfigurationBuilder();
configurationBuilder.setDebugEnabled(true)
.setJSONStoreEnabled(true)
.setOAuthConsumerKey(properties.getProperty("oAuthConsumerKey"))
.setOAuthConsumerSecret(properties.getProperty("oAuthConsumerSecret"))
.setOAuthAccessToken(properties.getProperty("oAuthAccessToken"))
.setOAuthAccessTokenSecret(properties.getProperty("oAuthAccessTokenSecret"));
return new TwitterFactory(configurationBuilder.build());
}
public List<JSONObject> getTweets() throws TwitterException, IOException, JSONException {
Twitter twitter = configTwitterFactory().getInstance();
Query query = new Query("lizardbill");
QueryResult result = twitter.search(query);
String string = null;
JSONObject tweet = null;
List<JSONObject> tweets = new ArrayList<>();
for (Status status : result.getTweets()) {
tweet = jsonOps(status);
tweets.add(tweet);
}
return tweets;
}
private JSONObject jsonOps(Status status) throws JSONException, BaseXException {
String string = TwitterObjectFactory.getRawJSON(status);
JSONObject json = new JSONObject(string);
String language = json.getString("lang");
log.fine(language);
return json;
}
}
The JSONObject from Twitter4J cannot just get jammed into XML?
There are a number of online converters which purport to accomplish this, and, which, at least at first glance, seem quite adequate.
see also:
Converting JSON to XML in Java
Java implementation of JSON to XML conversion
Use the (excellent) JSON-Java library from json.org then
JSONObject json = new JSONObject(str);
String xml = XML.toString(json);
toString can take a second argument to provide the name of the XML root node.
This library is also able to convert XML to JSON using XML.toJSONObject(java.lang.String string)
Check the Javadoc for more information

Google Cloud Dataflow issue with writing the data (TextIO or DatastoreIO)

OK, everyone. Another Dataflow question from a Dataflow newbie. (Just started playing with it this week..)
I'm creating a datapipe to take in a list of product names and generate autocomplete data. The data processing part is all working fine, it seems, but I'm missing something obvious because when I add my last ".apply" to use either DatastoreIO or TextIO to write the data out, I'm getting a syntax error in my IDE that says the following:
"The method apply(DatastoreV1.Write) is undefined for the type ParDo.SingleOutput>,Entity>"
If gives me an option add a cast to the method receiver, but that obviously isn't the answer. Do I need to do some other step before I try to write the data out? My last step before trying to write the data is a call to an Entity helper for Dataflow to change my Pipeline structure from > to , which seems to me like what I'd need to write to Datastore.
I got so frustrated with this thing the last few days, I even decided to write the data to some AVRO files instead so I could just load it in Datastore by hand. Imagine how ticked I was when I got all that done and got the exact same error in the exact same place on my call to TextIO. That is why I think I must be missing something very obvious here.
Here is my code. I included it all for reference, but you probably just need to look at the main[] at the bottom. Any input would be greatly appreciated! Thanks!
MrSimmonsSr
package com.client.autocomplete;
import com.client.autocomplete.AutocompleteOptions;
import com.google.datastore.v1.Entity;
import com.google.datastore.v1.Key;
import com.google.datastore.v1.Value;
import static com.google.datastore.v1.client.DatastoreHelper.makeKey;
import static com.google.datastore.v1.client.DatastoreHelper.makeValue;
import org.apache.beam.sdk.coders.DefaultCoder;
import org.apache.beam.sdk.Pipeline;
import org.apache.beam.sdk.PipelineResult;
import org.apache.beam.sdk.io.gcp.bigquery.BigQueryIO;
import org.apache.beam.sdk.values.PCollection;
import org.apache.beam.sdk.values.PCollectionList;
import com.google.api.services.bigquery.model.TableRow;
import com.google.common.base.MoreObjects;
import org.apache.beam.sdk.io.TextIO;
import org.apache.beam.sdk.io.gcp.datastore.DatastoreIO;
import org.apache.beam.sdk.options.PipelineOptionsFactory;
import org.apache.beam.sdk.transforms.PTransform;
import org.apache.beam.sdk.transforms.Create;
import org.apache.beam.sdk.transforms.DoFn;
import org.apache.beam.sdk.transforms.MapElements;
import org.apache.beam.sdk.transforms.ParDo;
import org.apache.beam.sdk.transforms.SimpleFunction;
import org.apache.beam.sdk.transforms.GroupByKey;
import org.apache.beam.sdk.transforms.DoFn.ProcessContext;
import org.apache.beam.sdk.transforms.DoFn.ProcessElement;
import org.apache.beam.sdk.extensions.jackson.ParseJsons;
import org.apache.beam.sdk.values.KV;
import org.apache.beam.sdk.options.Default;
import org.apache.beam.sdk.options.Description;
import org.apache.beam.sdk.options.PipelineOptionsFactory;
import org.apache.beam.sdk.options.StreamingOptions;
import org.apache.beam.sdk.options.Validation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.List;
import java.util.ArrayList;
/*
* A simple Dataflow pipeline to create autocomplete data from a list of
* product names. It then loads that prefix data into Google Cloud Datastore for consumption by
* a Google Cloud Function. That function will take in a prefix and return a list of 10 product names
*
* Pseudo Code Steps
* 1. Load a list of product names from Cloud Storage
* 2. Generate prefixes for use with autocomplete, based on the product names
* 3. Merge the prefix data together with 10 products per prefix
* 4. Write that prefix data to the Cloud Datastore as a KV with a <String>, List<String> structure
*
*/
public class ClientAutocompletePipeline {
private static final Logger LOG = LoggerFactory.getLogger(ClientAutocompletePipeline.class);
/**
* A DoFn that keys each product name by all of its prefixes.
* This creates one row in the PCollection for each prefix<->product_name pair
*/
private static class AllPrefixes
extends DoFn<String, KV<String, String>> {
private final int minPrefix;
private final int maxPrefix;
public AllPrefixes(int minPrefix) {
this(minPrefix, 10);
}
public AllPrefixes(int minPrefix, int maxPrefix) {
this.minPrefix = minPrefix;
this.maxPrefix = maxPrefix;
}
#ProcessElement
public void processElement(ProcessContext c) {
String productName= c.element().toString();
for (int i = minPrefix; i <= Math.min(productName.length(), maxPrefix); i++) {
c.output(KV.of(productName.substring(0, i), c.element()));
}
}
}
/**
* Takes as input the top product names per prefix, and emits an entity
* suitable for writing to Cloud Datastore.
*
*/
static class FormatForDatastore extends DoFn<KV<String, List<String>>, Entity> {
private String kind;
private String ancestorKey;
public FormatForDatastore(String kind, String ancestorKey) {
this.kind = kind;
this.ancestorKey = ancestorKey;
}
#ProcessElement
public void processElement(ProcessContext c) {
// Initialize an EntityBuilder and get it a valid key
Entity.Builder entityBuilder = Entity.newBuilder();
Key key = makeKey(kind, ancestorKey).build();
entityBuilder.setKey(key);
// New HashMap to hold all the properties of the Entity
Map<String, Value> properties = new HashMap<>();
String prefix = c.element().getKey();
String productsString = "Products[";
// iterate through the product names and add each one to the productsString
for (String productName : c.element().getValue()) {
// products.add(productName);
productsString += productName + ", ";
}
productsString += "]";
properties.put("prefix", makeValue(prefix).build());
properties.put("products", makeValue(productsString).build());
entityBuilder.putAllProperties(properties);
c.output(entityBuilder.build());
}
}
/**
* Options supported by this class.
*
* <p>Inherits standard Beam example configuration options.
*/
public interface Options
extends AutocompleteOptions {
#Description("Input text file")
#Validation.Required
String getInputFile();
void setInputFile(String value);
#Description("Cloud Datastore entity kind")
#Default.String("prefix-product-map")
String getKind();
void setKind(String value);
#Description("Whether output to Cloud Datastore")
#Default.Boolean(true)
Boolean getOutputToDatastore();
void setOutputToDatastore(Boolean value);
#Description("Cloud Datastore ancestor key")
#Default.String("root")
String getDatastoreAncestorKey();
void setDatastoreAncestorKey(String value);
#Description("Cloud Datastore output project ID, defaults to project ID")
String getOutputProject();
void setOutputProject(String value);
}
public static void main(String[] args) throws IOException{
Options options = PipelineOptionsFactory.fromArgs(args).withValidation().as(Options.class);
// create the pipeline
Pipeline p = Pipeline.create(options);
PCollection<String> toWrite = p
// A step to read in the product names from a text file on GCS
.apply(TextIO.read().from("gs://sample-product-data/clean_product_names.txt"))
// Next expand the product names into KV pairs with prefix as key (<KV<String, String>>)
.apply("Explode Prefixes", ParDo.of(new AllPrefixes(2)))
// Apply a GroupByKey transform to the PCollection "flatCollection" to create "productsGroupedByPrefix".
.apply(GroupByKey.<String, String>create())
// Now format the PCollection for writing into the Google Datastore
.apply("FormatForDatastore", ParDo.of(new FormatForDatastore(options.getKind(),
options.getDatastoreAncestorKey()))
// Write the processed data to the Google Cloud Datastore
// NOTE: This is the line that I'm getting the error on!!
.apply(DatastoreIO.v1().write().withProjectId(MoreObjects.firstNonNull(
options.getOutputProject(), options.getOutputProject()))));
// Run the pipeline.
PipelineResult result = p.run();
}
}
I think you need another closing parenthesis. I've removed some of the extraneous bits and reindent according to the parentheses:
PCollection<String> toWrite = p
.apply(TextIO.read().from("..."))
.apply("Explode Prefixes", ...)
.apply(GroupByKey.<String, String>create())
.apply("FormatForDatastore", ParDo.of(new FormatForDatastore(
options.getKind(), options.getDatastoreAncestorKey()))
.apply(...);
Specifically, you need another parenthesis to close the apply("FormatForDatastore", ...). Right now, it is trying to call ParDo.of(...).apply(...) which doesn't work.

Parsing nested JSON nodes to POJOs using Google Http Java Client

For example I have a response with the following JSON:
{
response: {
id: 20,
name: Stas
}
}
And I want to parse it to the following object:
class User {
private int id;
private String name;
}
How to skip the response node?
I use Google Http Java Client and it will be good if someone will answer how to do it there.
How will this lines have changed?
request.setParser(new JacksonFactory().createJsonObjectParser());
return request.execute().parseAs(getResultType());
You can now implement this in one step:
new JsonObjectParser.Builder(jsonFactory)
.setWrapperKeys(Arrays.asList("response"))
.build()
http://javadoc.google-http-java-client.googlecode.com/hg/1.15.0-rc/index.html
I do not know the Google Http Java Client, but if you can access the Jackson ObjectMapper you could do the following:
1.) Enable root unwrapping:
objectMapper.enable(DeserializationFeature.UNWRAP_ROOT_VALUE);
2.) Add annotation to User.class:
#JsonRootName("response")
class User {
…
}
I hope you can use this approach.
Edit: I dug through the google-http-java-client API and have come to the conclusion that you cannot access the ObjectMapper directly. In order to use the full power of Jackson you would have to write your own implementation of JsonObjectParser to wrap a 'real' Jackson parser. Sorry about that, maybe someone else could come up with a better solution.
I didn't find a native way (for this library) to solve my task. As a result I solved this problem by extending the functionality of JsonObjectParser. It entails expanding of the JacksonFactory, but it's a final class, so I used aggregation.
I wrote the following classes:
JacksonFilteringFactory
import com.google.api.client.json.JsonObjectParser;
import com.google.api.client.json.jackson2.JacksonFactory;
public class JacksonFilteringFactory {
private final JacksonFactory factory = new JacksonFactory();
public JsonObjectParser createJsonObjectParser(String filteringNode) {
return new FilteringJsonObjectParser(factory, filteringNode);
}
}
FilteringJsonObjectParser
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Type;
import java.nio.charset.Charset;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONTokener;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.JsonObjectParser;
import com.vkredmessenger.AppController;
import com.vkredmessenger.util.StringUtils;
public class FilteringJsonObjectParser extends JsonObjectParser {
private String mFilteringNode;
public FilteringJsonObjectParser(JsonFactory jsonFactory,
String filteringNode) {
super(jsonFactory);
mFilteringNode = filteringNode;
}
#Override
public Object parseAndClose(InputStream in,
Charset charset, Type dataType)
throws IOException {
String originalResponse =
StringUtils.convertStreamToString(in, charset);
String response = null;
try {
JSONTokener tokener = new JSONTokener(originalResponse);
JSONObject originalResponseObject =
(JSONObject) tokener.nextValue();
JSONObject responseObject =
originalResponseObject.getJSONObject(mFilteringNode);
response = responseObject.toString();
} catch (JSONException e) {
e.printStackTrace();
}
InputStream filteredIn =
new ByteArrayInputStream(response.getBytes(charset));
return super.parseAndClose(filteredIn, charset, dataType);
}
}
So, for example from my question, the result parsing code will be the following:
request.setParser(new JacksonFilteringFactory().createJsonObjectParser("response"));
return request.execute().parseAs(getResultType());

How to type cast using Sun/Oracle CodeModel library?

I'm trying to use the Java CodeModel library to generate some code. In my generated code I need to perform a type cast. I want something like this...
foo.setBar( ((TypeCastToThis)someVariable).getBar() );
The only support I've found in the library is by using JCast JExpr.cast(JType type, JExpression expr). However according to Eclipse the return type, JCast, is not public. The exact error is: "The type com.sun.codemodel.JCast is not visible".
Here's a simple example of what I'm doing.
import java.io.File;
import com.sun.codemodel.JBlock;
import com.sun.codemodel.JCast; //<-- Eclipse flags this as an error
import com.sun.codemodel.JClass;
import com.sun.codemodel.JClassAlreadyExistsException;
import com.sun.codemodel.JCodeModel;
import com.sun.codemodel.JDefinedClass;
import com.sun.codemodel.JExpr;
import com.sun.codemodel.JMethod;
import com.sun.codemodel.JMod;
import com.sun.codemodel.JVar;
public class CastTest
{
public static void main(String[] args) throws JClassAlreadyExistsException
{
// TODO Auto-generated method stub
JCodeModel codeModel = new JCodeModel();
JDefinedClass testClass = codeModel._class("MyTestClass");
JMethod testMeth = testClass.method(JMod.PUBLIC, codeModel.VOID, "TypeCastTestMethod");
JBlock testMethBody = testMeth.body();
JVar castMeVar = testMethBody.decl(codeModel.INT, "castMe", JExpr.lit(42));
JClass typeCastToThisClass = codeModel.directClass("TypeCastToThis");
JCast castResult = JExpr.cast(typeCastToThisClass, castMeVar);
testMethBody.decl(typeCastToThisClass, "theTypeCastedObject", castResult);
codeModel.build(new File("/path/to/output/directory"));
}
/*
The generated code should look like this.
public void TypeCastTestMethod()
{
int castMe = 42;
TypeCastToThis theTypeCastedObject = (TypeCastToThis)castMe;
}
*/
}
Am I using the library incorrectly and/or is there another way to achieve my goal?
In case anyone else has this issue later it can be by-passed by using implicit upcasting to a JExpression from a JCast on the return value of JExpr.cast(....). JCast is a subtype of JExpression.
From....
JCast castResult = JExpr.cast(typeCastToThisClass, castMeVar);
To.....
JExpression castResult = JExpr.cast(typeCastToThisClass, castMeVar);

Amazon Product Advertising API through Java/SOAP

I have been playing with Amazon's Product Advertising API, and I cannot get a request to go through and give me data. I have been working off of this: http://docs.amazonwebservices.com/AWSECommerceService/2011-08-01/GSG/ and this: Amazon Product Advertising API signed request with Java
Here is my code. I generated the SOAP bindings using this: http://docs.amazonwebservices.com/AWSECommerceService/2011-08-01/GSG/YourDevelopmentEnvironment.html#Java
On the Classpath, I only have: commons-codec.1.5.jar
import com.ECS.client.jax.AWSECommerceService;
import com.ECS.client.jax.AWSECommerceServicePortType;
import com.ECS.client.jax.Item;
import com.ECS.client.jax.ItemLookup;
import com.ECS.client.jax.ItemLookupRequest;
import com.ECS.client.jax.ItemLookupResponse;
import com.ECS.client.jax.ItemSearchResponse;
import com.ECS.client.jax.Items;
public class Client {
public static void main(String[] args) {
String secretKey = <my-secret-key>;
String awsKey = <my-aws-key>;
System.out.println("API Test started");
AWSECommerceService service = new AWSECommerceService();
service.setHandlerResolver(new AwsHandlerResolver(
secretKey)); // important
AWSECommerceServicePortType port = service.getAWSECommerceServicePort();
// Get the operation object:
com.ECS.client.jax.ItemSearchRequest itemRequest = new com.ECS.client.jax.ItemSearchRequest();
// Fill in the request object:
itemRequest.setSearchIndex("Books");
itemRequest.setKeywords("Star Wars");
// itemRequest.setVersion("2011-08-01");
com.ECS.client.jax.ItemSearch ItemElement = new com.ECS.client.jax.ItemSearch();
ItemElement.setAWSAccessKeyId(awsKey);
ItemElement.getRequest().add(itemRequest);
// Call the Web service operation and store the response
// in the response object:
com.ECS.client.jax.ItemSearchResponse response = port
.itemSearch(ItemElement);
String r = response.toString();
System.out.println("response: " + r);
for (Items itemList : response.getItems()) {
System.out.println(itemList);
for (Item item : itemList.getItem()) {
System.out.println(item);
}
}
System.out.println("API Test stopped");
}
}
Here is what I get back.. I was hoping to see some Star Wars books available on Amazon dumped out to my console :-/:
API Test started
response: com.ECS.client.jax.ItemSearchResponse#7a6769ea
com.ECS.client.jax.Items#1b5ac06e
API Test stopped
What am I doing wrong (Note that no "item" in the second for loop is being printed out, because its empty)? How can I troubleshoot this or get relevant error information?
I don't use the SOAP API but your Bounty requirements didn't state that it had to use SOAP only that you wanted to call Amazon and get results. So, I'll post this working example using the REST API which will at least fulfill your stated requirements:
I would like some working example code that hits the amazon server and returns results
You'll need to download the following to fulfill the signature requirements:
http://associates-amazon.s3.amazonaws.com/signed-requests/samples/amazon-product-advt-api-sample-java-query.zip
Unzip it and grab the com.amazon.advertising.api.sample.SignedRequestsHelper.java file and put it directly into your project. This code is used to sign the request.
You'll also need to download Apache Commons Codec 1.3 from the following and add it to your classpath i.e. add it to your project's library. Note that this is the only version of Codec that will work with the above class (SignedRequestsHelper)
http://archive.apache.org/dist/commons/codec/binaries/commons-codec-1.3.zip
Now you can copy and paste the following making sure to replace your.pkg.here with the proper package name and replace the SECRET and the KEY properties:
package your.pkg.here;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;
public class Main {
private static final String SECRET_KEY = "<YOUR_SECRET_KEY>";
private static final String AWS_KEY = "<YOUR_KEY>";
public static void main(String[] args) {
SignedRequestsHelper helper = SignedRequestsHelper.getInstance("ecs.amazonaws.com", AWS_KEY, SECRET_KEY);
Map<String, String> params = new HashMap<String, String>();
params.put("Service", "AWSECommerceService");
params.put("Version", "2009-03-31");
params.put("Operation", "ItemLookup");
params.put("ItemId", "1451648537");
params.put("ResponseGroup", "Large");
String url = helper.sign(params);
try {
Document response = getResponse(url);
printResponse(response);
} catch (Exception ex) {
Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
}
}
private static Document getResponse(String url) throws ParserConfigurationException, IOException, SAXException {
DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
Document doc = builder.parse(url);
return doc;
}
private static void printResponse(Document doc) throws TransformerException, FileNotFoundException {
Transformer trans = TransformerFactory.newInstance().newTransformer();
Properties props = new Properties();
props.put(OutputKeys.INDENT, "yes");
trans.setOutputProperties(props);
StreamResult res = new StreamResult(new StringWriter());
DOMSource src = new DOMSource(doc);
trans.transform(src, res);
String toString = res.getWriter().toString();
System.out.println(toString);
}
}
As you can see this is much simpler to setup and use than the SOAP API. If you don't have a specific requirement for using the SOAP API then I would highly recommend that you use the REST API instead.
One of the drawbacks of using the REST API is that the results aren't unmarshaled into objects for you. This could be remedied by creating the required classes based on the wsdl.
This ended up working (I had to add my associateTag to the request):
public class Client {
public static void main(String[] args) {
String secretKey = "<MY_SECRET_KEY>";
String awsKey = "<MY AWS KEY>";
System.out.println("API Test started");
AWSECommerceService service = new AWSECommerceService();
service.setHandlerResolver(new AwsHandlerResolver(secretKey)); // important
AWSECommerceServicePortType port = service.getAWSECommerceServicePort();
// Get the operation object:
com.ECS.client.jax.ItemSearchRequest itemRequest = new com.ECS.client.jax.ItemSearchRequest();
// Fill in the request object:
itemRequest.setSearchIndex("Books");
itemRequest.setKeywords("Star Wars");
itemRequest.getResponseGroup().add("Large");
// itemRequest.getResponseGroup().add("Images");
// itemRequest.setVersion("2011-08-01");
com.ECS.client.jax.ItemSearch ItemElement = new com.ECS.client.jax.ItemSearch();
ItemElement.setAWSAccessKeyId(awsKey);
ItemElement.setAssociateTag("th0426-20");
ItemElement.getRequest().add(itemRequest);
// Call the Web service operation and store the response
// in the response object:
com.ECS.client.jax.ItemSearchResponse response = port
.itemSearch(ItemElement);
String r = response.toString();
System.out.println("response: " + r);
for (Items itemList : response.getItems()) {
System.out.println(itemList);
for (Item itemObj : itemList.getItem()) {
System.out.println(itemObj.getItemAttributes().getTitle()); // Title
System.out.println(itemObj.getDetailPageURL()); // Amazon URL
}
}
System.out.println("API Test stopped");
}
}
It looks like the response object does not override toString(), so if it contains some sort of error response, simply printing it will not tell you what the error response is. You'll need to look at the api for what fields are returned in the response object and individually print those. Either you'll get an obvious error message or you'll have to go back to their documentation to try to figure out what is wrong.
You need to call the get methods on the Item object to retrieve its details, e.g.:
for (Item item : itemList.getItem()) {
System.out.println(item.getItemAttributes().getTitle()); //Title of item
System.out.println(item.getDetailPageURL()); // Amazon URL
//etc
}
If there are any errors you can get them by calling getErrors()
if (response.getOperationRequest().getErrors() != null) {
System.out.println(response.getOperationRequest().getErrors().getError().get(0).getMessage());
}

Categories

Resources