I'm new to DynamoDB, I'm trying to insert a new item. However, I'm getting the following exception:
com.amazonaws.services.dynamodbv2.model.AmazonDynamoDBException: The provided key element does not match the schema (Service: AmazonDynamoDBv2; Status Code: 400; Error Code: ValidationException; Request ID: XXX)
This is how my table is described:
{
"Table": {
"TableArn": "arn:aws:dynamodb:us-east-1:111:table/table-XXX",
"AttributeDefinitions": [
{
"AttributeName": "timestamp",
"AttributeType": "S"
},
{
"AttributeName": "title",
"AttributeType": "S"
}
],
"ProvisionedThroughput": {
"NumberOfDecreasesToday": 0,
"WriteCapacityUnits": 5,
"ReadCapacityUnits": 5
},
"TableSizeBytes": 0,
"TableName": "ddb-table-sst67gy",
"TableStatus": "ACTIVE",
"KeySchema": [
{
"KeyType": "HASH",
"AttributeName": "title"
},
{
"KeyType": "RANGE",
"AttributeName": "timestamp"
}
],
"ItemCount": 0,
"CreationDateTime": 1489090172.658
}
}
And this is my Java class:
#DynamoDBTable(tableName = "table-XXX")
public class Movie {
private String title;
private String timeStamp;
#DynamoDBHashKey(attributeName = "title")
#NotNull(message = "Title must not be empty")
public String getTitle() {
return title;
}
public Movie withTitle(String name) {
setTitle(name);
return this;
}
#DynamoDBAttribute(attributeName = "timestamp")
public String getTimeStamp() {
return timeStamp;
}
public Movie withTimeStamp(String address) {
setTimeStamp(address);
return this;
}
public void setTitle(String title) {
this.title = title;
}
public void setTimeStamp(String timeStamp) {
this.timeStamp = timeStamp;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Movie movie = (Movie) o;
if (title != null ? !title.equals(movie.title) : movie.title != null) return false;
return timeStamp != null ? timeStamp.equals(movie.timeStamp) : movie.timeStamp == null;
}
#Override
public int hashCode() {
int result = title != null ? title.hashCode() : 0;
result = 31 * result + (timeStamp != null ? timeStamp.hashCode() : 0);
return result;
}
}
How should I map my Java class into DynamoDB correctly ?
Please use annotation #DynamoDBRangeKey to define the range or sort key attribute.
#DynamoDBRangeKey(attributeName = "timestamp")
public String getTimeStamp() {
return timeStamp;
}
Related
I want to iterate a list of object 2 times. First time I need to use only first object and perform some operation. Second time I want to perform operation on all the items of list.
In below example, I have list of TestPojo i.e. testPojoList.
Using Java stream I have tried to achieve the same in 2 steps (Operation#1 and Operation#2) Is there any better way or I can merge both of the operations in single operation? below is the piece of code:
public void Mapping() {
TestPojo testPojo1 = TestPojo.builder().name("Mike").city("Denver").company("Tesla").build();
TestPojo testPojo2 = TestPojo.builder().name("Bob").city("Atlanta").company("Tesla").build();
TestPojo testPojo3 = TestPojo.builder().name("Steve").city("Chicago").company("Tesla").build();
TestPojo testPojo4 = TestPojo.builder().name("John").city("Boston").company("Tesla").build();
List<TestPojo> testPojoList = Arrays.asList(testPojo1, testPojo2, testPojo3, testPojo4);
//Operation1
TransformedTestPojo transformedTestPojo = testPojoList.stream().findFirst().map(testPojo -> mapCompanyName(testPojo)).orElse(null);
//Operation2
List<PersonalDetails> personalDetailsList = testPojoList.stream().map(testPojo -> mapOtherDetails(testPojo)).collect(Collectors.toList());,
transformedTestPojo.setPersonalDetailsList(personalDetailsList);
System.out.println(transformedTestPojo);
}
private PersonalDetails mapOtherDetails(TestPojo testPojo) {
return PersonalDetails.builder().name(testPojo.getName()).City(testPojo.getCity()).build();
}
private TransformedTestPojo mapCompanyName(TestPojo testPojo) {
return TransformedTestPojo.builder().company(testPojo.getCompany()).build();
}
public class TestPojo {
String name;
String city;
String company;
}
public class TransformedTestPojo {
String company;
List<PersonalDetails> personalDetailsList;
}
public class PersonalDetails {
String name;
String City;
}
Following will be the output:
//Request List
{
"testPojoList": [
{
"name": "Mike",
"city": "Denver",
"company": "Tesla"
},
{
"name": "Bob",
"city": "Atlanta",
"company": "Tesla"
},
{
"name": "Steve",
"city": "Chicago",
"company": "Tesla"
},
{
"name": "John",
"city": "Boston",
"company": "Tesla"
}
]
}
//Response Object
"TransformedTestPojo":
{
"company": "Tesla",
"personalDetailsList": [
{
"name": "Mike",
"City": "Denver"
},
{
"name": "Bob",
"City": "Atlanta"
},
{
"name": "Steve",
"City": "Chicago"
},
{
"name": "John",
"City": "Boston"
}
]
}
Here are two ways to do it. But first, I created the classes. Instead of a builder I used a constructor for TestPojo. But it will still work.
class TestPojo {
String name;
String city;
String company;
public TestPojo(String name, String city, String company) {
this.name = name;
this.city = city;
this.company = company;
}
public String getName() {
return name;
}
public String getCity() {
return city;
}
public String getCompany() {
return company;
}
#Override
public String toString() {
return String.format("[%s, %s, %s]", name, city, company);
}
}
class TransformedTestPojo {
String company;
List<PersonalDetails> personalDetailsList;
public TransformedTestPojo(String company,
List<PersonalDetails> personalDetailsList) {
this.company = company;
this.personalDetailsList = personalDetailsList;
}
public String getCompany() {
return company;
}
public List<PersonalDetails> getPersonalDetailsList() {
return personalDetailsList;
}
}
class PersonalDetails {
String name;
String City;
public PersonalDetails(String name, String city) {
this.name = name;
City = city;
}
#Override
public String toString() {
return String.format("[%s, %s]", name, City);
}
}
The Data
List<TestPojo> testPojoList =
List.of(new TestPojo("Mike", "Denver", "Tesla"),
new TestPojo("Bob", "Atlanta", "Tesla"),
new TestPojo("Steve", "Chicago", "Tesla"),
new TestPojo("John", "Boston", "Tesla"));
The Map approach using a loop
The best approach (imo) is to use Java 8 features of the Map interface
allocate a map
Iterate over the testPojoList
Map.compute will take a key, and then if the value is null, create one. Otherwise, it uses the existing value.
that value is returned and can be used in the same construct to further modify the value. In this case it does the following:
create a new TransformedTestPojo instance with the key (company) and new ArrayList<>() for the personal details.
then return that list and get the personal details list and add a new Personal details instance.
Map<String, TransformedTestPojo> map = new HashMap<>();
for (TestPojo tp : testPojoList) {
map.compute(tp.getCompany(),
(k, v) -> v == null ? new TransformedTestPojo(k,
new ArrayList<>()) : v)
.getPersonalDetailsList().add(new PersonalDetails(
tp.getName(), tp.getCity()));
}
Once the map has been created, get the map values (which has the TransformedTestPojo instances) and return as a collection.
Collection<TransformedTestPojo> collection = map.values();
Note that a Collection (super type of List) , not a List is created. If a list is required you can do the following.
List<TransformedTestPojo> list = new ArrayList<>(map.values());
Displaying the results
list.forEach(k -> {
System.out.println(k.getCompany());
k.getPersonalDetailsList()
.forEach(p -> System.out.println(" " + p));
});
prints
Tesla
[Mike, Denver]
[Bob, Atlanta]
[Steve, Chicago]
[John, Boston]
Here is a stream solution.
stream the testPojoList
Use Collectors.groupingBy with company as the key
The associated list will be of PersonalDetails instances.
then stream the entrySet of the map and build a list of TransformedTestPojo
List<TransformedTestPojo> list1 = testPojoList.stream()
.collect(Collectors.groupingBy(TestPojo::getCompany,
Collectors.mapping(
tp -> new PersonalDetails(
tp.getName(), tp.getCity()),
Collectors.toList())))
.entrySet().stream()
.map(e -> new TransformedTestPojo(e.getKey(),
e.getValue()))
.toList();
}
Note that the map itself could be used instead of a returned List<TransformedTestPojo>. The key is the company name and the value contains the list of PersonalDetails. If that is useful, then all you need is the following:
Map<String, List<PersonalDetails>> result = testPojoList.stream()
.collect(Collectors.groupingBy(TestPojo::getCompany,
Collectors.mapping(
tp -> new PersonalDetails(
tp.getName(), tp.getCity()),
Collectors.toList())))
I've trying to getting some from information with a WS ,using get method.
I did managed to get the needed information from one WS,but not from a second one.
string url = "";
url = "http://...";
List<Client> listOfClient = null;
string host = url;
WebRequest req = WebRequest.Create(#host);
try
{
HttpWebResponse resp = req.GetResponse() as HttpWebResponse;
if (resp.StatusCode == HttpStatusCode.OK)
{
using (var reader = new StreamReader(resp.GetResponseStream()))
{
JavaScriptSerializer js = new JavaScriptSerializer();
var objText = reader.ReadToEnd();
listOfClient = (List<Client>)js.Deserialize(objText, typeof(List<Client>));
}
}
name = listOfClient.FirstOrDefault().name;
return name;
}
catch (Exception)
{
throw;
}
Here is the Json :
[
{
"city": NY,
"age": 30,
"Name": "Robert",
}
]
I need to read the property offer using the same logic.
{
"contract": "480788888",
"numbers": [
{
"type": "IDEI",
"value": "5987699118"
}
],
"status": "Valid",
"offer": "PNE",
}
Using Newtonsoft.Json.Linq
dynamic obj= JObject.Parse("{ 'contract': '480788888', 'numbers': [ { 'type':
'IDEI', 'value': '5987699118' } ],'status': 'Valid', 'offer': 'PNE', }");
string offer = obj.offer;
I created a DynamoDb using NET and able to getitem, which is not an empty list. I get a status 400 error on the putitem using Postman. This is the error:
"errors": {
"id": [
"Could not convert string to integer: 9134d3a0-a6bf-4409-87b3-d9fad02bd31c. Path 'id', line 2, position 44."
]
},
This is the body I use for the post:
{
"id":"9134d3a0-a6bf-4409-87b3-d9fad02bd31c",
"replyDateTime": "63669789320007900",
"body":"a good body",
"title":"best title",
"creator": " James"
}
This is my createtable code:
var request = new CreateTableRequest
{
AttributeDefinitions = new List<AttributeDefinition>
{
new AttributeDefinition
{
AttributeName = "Id",
AttributeType = "S"
},
new AttributeDefinition
{
AttributeName = "ReplyDateTime",
AttributeType = "S"
}
},
KeySchema = new List<KeySchemaElement>
{
new KeySchemaElement
{
AttributeName = "Id",
KeyType = "HASH" // Partition Key
},
new KeySchemaElement
{
AttributeName = "ReplyDateTime",
KeyType = "Range" // Sort Key
}
},
this is the putitem code:
public async Task AddNewEntry(string id, string replyDateTime, string body, string title, string creator)
{
var queryRequest = RequestBuilder(id, replyDateTime, body, title, creator);
await PutItemAsync(queryRequest);
}
private PutItemRequest RequestBuilder(string id, string replyDateTime, string body, string title, string creator)
{
var item = new Dictionary<string, AttributeValue>
{
{"Id", new AttributeValue {S = id}},
{"ReplyDateTime", new AttributeValue {S = replyDateTime}},
{"Body", new AttributeValue {S = body}},
{"Creator", new AttributeValue {S = creator}},
{"Title", new AttributeValue {S = title}}
};
return new PutItemRequest
{
TableName = "BlogDynamoDbTable",
Item = item
};
}
private async Task PutItemAsync(PutItemRequest request)
{
await _dynamoClient.PutItemAsync(request);
}
}
I believe I made the primary key a string. Why is an integer even mentioned in the error message?
I found my error. The model file was defining id as an integer. Grrr
I changed it to string and it posts.
public class Item
{
[Amazon.DynamoDBv2.DataModel.DynamoDBHashKey]
public string Id { get; set; }
[Amazon.DynamoDBv2.DataModel.DynamoDBRangeKey]
public string ReplyDateTime { get; set; }
public string Body { get; set; }
public string Title { get; set; }
public string Creator { get; set; }
}
I'm trying to get a record from a GSI and I'm stucked.
API Schema:
type DriverInfos {
id: String!
status: Int
lastLat: Float
lastLng: Float
idDriver: String # GSI
}
type Query {
getDriverInfosByDriver(idDriver: String): DriverInfos
}
Resolver :
{
"version" : "2017-02-28",
"operation" : "Query",
"index" : "idDriver-index",
"query" : {
## Provide a query expression. **
"expression": "#idDriver = :idDriver",
"expressionNames" : {
"#idDriver" : "idDriver"
},
"expressionValues" : {
":idDriver" : {
"S" : "${ctx.args.idDriver}"
}
}
}
}
Query :
query getDriverInfosByDriver{
getDriverInfosByDriver(idDriver: "1")
{
idDriver
status
lastLat
lastLng
}
}
Return :
{
"data": {
"getDriverInfosByDriver": {
"idDriver": null,
"status": null,
"lastLat": null,
"lastLng": null
}
}
}
GSI is well activated : Name : "idDriver-index" - PartitionKey : idDriver (String)
Try with other ids : 2, 3, ...
It seems that it comes from the resolver. I tried with different resolver but it always return an error.
Thank you in advance for your answers.
The issue is that a Query operation always returns a set of results not just one. If you want to leave your query type like this:
type Query {
getDriverInfosByDriver(idDriver: String): DriverInfos
}
then you should to change your response mapping template to this:
#if($ctx.result.items.size() > 0)
$util.toJson($ctx.result.items[0])
#else
null
#end
If instead the getDriverInfosByDriver query should return multiple info objects then you should change your schema to:
type DriverInfo {
id: String!
status: Int
lastLat: Float
lastLng: Float
idDriver: String # GSI
}
type DriverInfoConnection {
items: [DriverInfo]
nextToken:String
}
type Query {
getDriverInfosByDriver(idDriver: String): DriverInfoConnection
}
You can then leave your response mapping template as the default:
$util.toJson($ctx.result)
and then query it like so
query getDriverInfosByDriver{
getDriverInfosByDriver(idDriver: "1") {
items {
idDriver
status
lastLat
lastLng
}
}
}
I don't know where is problem
{
"success": "1",
"wallpapers": [
{
"id": "1",
"image": "http://cyphersol.com/apps/ringtona/uploads/gallery/1477685052.jpg"
},
{
"id": "2",
"image": "http://cyphersol.com/apps/ringtona/uploads/gallery/14776850521.jpg"
},
{
"id": "3",
"image": "http://cyphersol.com/apps/ringtona/uploads/gallery/14776850522.jpg"
},
{
"id": "4",
"image": "http://cyphersol.com/apps/ringtona/uploads/gallery/14776850523.jpg"
},
{
"id": "5",
"image": "http://cyphersol.com/apps/ringtona/uploads/gallery/14776850524.jpg"
}
]
}
I am using retrofit2.0
interface
public interface ApiInterface {
#POST("getImages")
Call<WallPaperResponse> getWallpapers(#Query("id") int apiKey);
}
Api Client
public class ApiClient {
public static final String BASE_URL = "http://cyphersol.com/apps/ringtona/webservice/";
private static Retrofit retrofit = null;
public static Retrofit getClient() {
if (retrofit==null) {
retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build();
}
return retrofit;
}
}
Call in to MainActivity
ApiInterface apiService =
ApiClient.getClient().create(ApiInterface.class);
Call<WallPaperResponse> call = apiService.getWallpapers(1);
call.enqueue(new Callback<WallPaperResponse>() {
#Override
public void onResponse(Call<WallPaperResponse> call, Response<WallPaperResponse> response) {
int statusCode = response.code();
List<WallpapersItem> wallpaper = response.body().getWallpapers();
for (int i = 0; i < wallpaper.size(); i++) {
Log.e(TAG, wallpaper.get(i).getImage());
}
// recyclerView.setAdapter(new MoviesAdapter(movies, R.layout.list_item_movie, getApplicationContext()));
}
#Override
public void onFailure(Call<WallPaperResponse> call, Throwable t) {
// Log error here since request failed
Log.e(TAG, t.toString());
}
});
dependency
// retrofit, gson
compile 'com.google.code.gson:gson:2.6.2'
compile 'com.squareup.retrofit2:retrofit:2.0.2'
compile 'com.squareup.retrofit2:converter-gson:2.0.2'
I think This will help you.
KingController mWebController = KingController.getInstance(this);
String apiToken = "1"; mWebController.getMainCategories(apiToken);
#GET("getImages")
Call getWallpaperLis(#Header("id") String api_token);
Regargs
Rashid Ali
Your web service requires id to be sent as a HEADER
While you have rather sent it as a POST parameter.
Hence, Your web service did not return a valid response
and the error.
Let me know if this works.
public interface ApiInterface {
#GET("getImages")
Call<WallPaperResponse> getWallpapers(#Header("id") int apiKey);
}
P.S This site has solid documentation on retorfit
https://futurestud.io/tutorials/retrofit-2-manage-request-headers-in-okhttp-interceptor