How to search for multiply(batch) params in Spring boot? - rest

I would like to batch search for entities like that:
GET api/stuff?ids=123+456+789+101112+...
I have found out that it is possible to get the request param like that:
#RequestMapping(method = RequestMethod.GET, value = "/stuff")
public String controllerMethod(#RequestParam Map<String, String> customQuery) {
//After that I could get 123+456+789+101112+... and I could parse them.
String ids = customQuery.get("ids");
}
Is there an alternative for the above solution which I could get the request param as a List, or any other solution?

#GetMapping("/stuff")
public String controllerMethod(#RequestParam("ids") List<Integer> ids) {
}
Then you should be able to call this as api/stuff?ids=123,456,789 or as api/stuff?ids=123&ids=456&ids=678

Related

storing object in cosmos db returns bad request?

I seem to be unable to store a simple object to cosmos db?
this is the database model.
public class HbModel
{
public Guid id { get; set; }
public string FormName { get; set; }
public Dictionary<string, object> Form { get; set; }
}
and this is how I store the data into the database
private static void SeedData(HbModelContext dbContext)
{
var cosmosClient = dbContext.Database.GetCosmosClient();
cosmosClient.ClientOptions.AllowBulkExecution = true;
if (dbContext.Set<HbModel>().FirstOrDefault() == null)
{
// No items could be picked hence try seeding.
var container = cosmosClient.GetContainer("hb", "hb_forms");
HbModel first = new HbModel()
{
Id = Guid.NewGuid(),//Guid.Parse(x["guid"] as string),
FormName = "asda",//x["name"] as string,
Form = new Dictionary<string, object>() //
}
string partitionKey = await GetPartitionKey(container.Database, container.Id);
var response = await container.CreateItemAsync(first, new PartitionKey(partitionKey));
}
else
{
Console.WriteLine("Already have data");
}
}
private static async Task<string> GetPartitionKey(Database database, string containerName)
{
var query = new QueryDefinition("select * from c where c.id = #id")
.WithParameter("#id", containerName);
using var iterator = database.GetContainerQueryIterator<ContainerProperties>(query);
while (iterator.HasMoreResults)
{
foreach (var container in await iterator.ReadNextAsync())
{
return container.PartitionKeyPath;
}
}
return null;
}
but when creating the item I get this error message
A host error has occurred during startup operation '3b06df1f-000c-4223-a374-ca1dc48d59d1'.
[2022-07-11T15:02:12.071Z] Microsoft.Azure.Cosmos.Client: Response status code does not indicate success: BadRequest (400); Substatus: 0; ActivityId: 24bac0ba-f1f7-411f-bc57-3f91110c4528; Reason: ();.
Value cannot be null. (Parameter 'provider')
no idea why it fails?
the data should not be formatted incorreclty?
It also fails in case there is data in the dictionary.
What is going wrong?
There are several things wrong with the attached code.
You are enabling Bulk but you are not following the Bulk pattern
cosmosClient.ClientOptions.AllowBulkExecution = true is being set, but you are not parallelizing work. If you are going to use Bulk, make sure you are following the documentation and creating lists of concurrent Tasks. Reference: https://learn.microsoft.com/azure/cosmos-db/sql/tutorial-sql-api-dotnet-bulk-import#step-6-populate-a-list-of-concurrent-tasks. Otherwise don't use Bulk.
You are blocking threads.
The call to container.CreateItemAsync(first, new PartitionKey("/__partitionKey")).Result; is a blocking call, this can lead you to deadlocks. When using async operations (such as CreateItemAsync) please use the async/await pattern. Reference: https://github.com/davidfowl/AspNetCoreDiagnosticScenarios/blob/master/AsyncGuidance.md#avoid-using-taskresult-and-taskwait
The PartitionKey parameter should be the value not the definition.
On the call container.CreateItemAsync(first, new PartitionKey("/__partitionKey")) the Partition Key (second parameter) should be the value. Assuming your container has a Partition Key Definition of /__partitionKey then your documents should have a __partitionKey property and you should pass the Value in this parameter of such property in the current document. Reference: https://learn.microsoft.com/azure/cosmos-db/sql/troubleshoot-bad-request#wrong-partition-key-value
Optionally, if your documents do not contain such a value, just remove the parameter from the call:
container.CreateItemAsync(first)
Be advised though that this solution will not scale, you need to design your database with Partitioning in mind: https://learn.microsoft.com/azure/cosmos-db/partitioning-overview#choose-partitionkey
Missing id
The model has Id but Cosmos DB requires id, make sure the content of the document contains id when serialized.

CacheBuilder using guava cache for query resultant

To reduce the DB hits to read the data from DB using the query, I am planning to keep resultant in the cache. To do this I am using guava caching.
studentController.java
public Map<String, Object> getSomeMethodName(Number departmentId, String departmentType){
ArrayList<Student> studentList = studentManager.getStudentListByDepartmentType(departmentId, departmentType);
----------
----------
}
StudentHibernateDao.java(criteria query )
#Override
public ArrayList<Student> getStudentListByDepartmentType(Number departmentId, String departmentType) {
Criteria criteria =sessionFactory.getCurrentSession().createCriteria(Student.class);
criteria.add(Restrictions.eq("departmentId", departmentId));
criteria.add(Restrictions.eq("departmentType", departmentType));
ArrayList<Student> studentList = (ArrayList)criteria.list();
return studentList;
}
To cache the criteria query resultant i started off with building CacheBuilder, like below.
private static LoadingCache<Number departmentId, String departmentType, ArrayList<Student>> studentListCache = CacheBuilder
.newBuilder().expireAfterAccess(1, TimeUnit.MINUTES)
.maximumSize(1000)
.build(new CacheLoader<Number departmentId, String departmentType, ArrayList<Student>>() {
public ArrayList<Student> load(String key) throws Exception {
return getStudentListByDepartmentType(departmentId, departmentType);
}
});
Here I dont know where to put CacheBuilder function and how to pass multiple key parameters(i.e departmentId and departmentType) to CacheLoader and call it.
Is this the correct way of caching using guava. Am I missing anything?
Guava's cache only accepts two type parameters, a key and a value type. If you want your key to be a compound key then you need to build a new compound type to encapsulate it. Effectively it would need to look like this (I apologize for my syntax, I don't use Java that often):
// Compound Key type
class CompoundDepartmentId {
public CompoundDepartmentId(Long departmentId, String departmentType) {
this.departmentId = departmentId;
this.departmentType = departmentType;
}
}
private static LoadingCache<CompoundDepartmentId, ArrayList<Student>> studentListCache =
CacheBuilder
.newBuilder().expireAfterAccess(1, TimeUnit.MINUTES)
.maximumSize(1000)
.build(new CacheLoader<CompoundDepartmentId, ArrayList<Student>>() {
public ArrayList<Student> load(CompoundDepartmentId key) throws Exception {
return getStudentListByDepartmentType(key.departmentId, key.departmentType);
}
});

Map multiple query parameters in jax-rs

I am using Jersey as the implementation library.
#Path("books")
public class Sample {
#GET
public List<Book> getBooks(#Context UriInfo uriInfo)
{
MultivaluedMap<String,String> params = uriInfo.getQueryParameters();
String pageStart = params.getFirst("p");
String pageSize = params.getFirst("s");
}
}
It works fine with /books but doesn't work for /books?p=1&s=10 (http 404 error)
I don't want to define my path like "books?p={p}&s={s}" since there will be dynamic parameters that cannot be predefined.
question:
Is there any way that i can map /books?p=1&s=10 to the getBooks method??
can check by having you #Path("books") changed to #Path("/books")

JERSEY - Accessing Generic List in Response

Im facing isssue in getting Jersey Generic List in client response. I need to get it as Entity for some reason.
#XmlRootElement(name="list")
#XmlSeeAlso({RESTDomain.class})
public class JAXBContainer<T> {
private List<T> items = new ArrayList<T>();
public JAXBContainer() { }
public JAXBContainer(List<T> items) {
this.items = items;
}
#XmlElementWrapper(name="items")
#XmlAnyElement(lax=true)
public List<T> getItems() {
return items;
}
public void setItems(List<T> items) {
this.items = items;
}
#XmlAttribute
public int getItemsSize() {
return this.items.size();
}
above is my Generic List to the resopnse
#GET
#Produces({MediaType.APPLICATION_XML})
public Response getREST(){
RESTDomain domain = new RESTDomain();
domain.setName("Adams");
domain.setPlace("Zurich");
List<RESTDomain> restDomains = new ArrayList<RESTDomain>();
restDomains.add(domain);
JAXBContainer<RESTDomain> jAXBContainer= new JAXBContainer<RESTDomain>(restDomains);
GenericEntity<JAXBContainer<RESTDomain>> genericEntity = new GenericEntity<JAXBContainer<RESTDomain>>(jAXBContainer){};
return Response.ok(genericEntity).build();
}
Im returning the container with genericEntity. I know with just List inside genericEntity i can get my Entity at my client but the problem is i need to Use my JAXBContainer for some reason.
#Test
public void restGet() throws JAXBException{
ClientConfig cc = new DefaultClientConfig();
client = Client.create(cc);
String baseURI ="http://localhost:3555/SampleREST/rest/sample";
WebResource webResource = client.resource(baseURI);
JAXBContainer<RESTDomain> jAXBContainer= webResource.get(new GenericType<JAXBContainer<RESTDomain>>(){});
System.out.println("response:: "+jAXBContainer.getItemsSize());
}
My problem is im getting the response as JAXBContainer with GenericType as desired but the size of container is 0. What am i missing here? do i have to Use any marshalling and unmarshalling Mechanisms.
But When i request this URI in browser i get the well formed XML, But it fails in client or do we have any other ways to extract entity in client. Thanks in advance
I don't see that you're setting the accept content type anywhere on the client.
Try with: webResource.accept("application/xml")

Pass a parameter to REST web service via URL

I'm creating a small REST web service using Netbeans. This is my code:
private UriInfo context;
private String name;
public GenericResource() {
}
#GET
#Produces("text/html")
public String getHtml() {
//TODO return proper representation object
return "Hello "+ name;
}
#PUT
#Consumes("text/html")
public void putHtml(String name) {
this.name = name;
}
I'm calling the get method ok since when I call http://localhost:8080/RestWebApp/resources/greeting I get "Hello null" but I'm trying to pass a parameter using http://localhost:8080/RestWebApp/resources/greeting?name=Krt_Malta but the PUT method is not being called... Is this the correct way to pass a parameter or am I missing something?
I'm a newbie to Rest bdw, so sry if it's a simple question.
Thanks! :)
Krt_Malta
The second URL is a plain GET request. To pass data to a PUT request you have to pass it using a form. The URL is reserved for GET as far as I know.
If you build the HTTP-header yourself, you must use POST instead of GET:
GET /RestWebApp/resources/greeting?name=Krt_Malta HTTP/1.0
versus
POST /RestWebApp/resources/greeting?name=Krt_Malta HTTP/1.0
If you use a HTML-form, you must set the method-attribute to "PUT":
<form action="/RestWebApp/resources/greeting" method="PUT">
For JAX-RS to mactch a method annotated with #PUT, you need to submit a PUT request. Normal browsers don't do this but cURL or a HTTP client library can be used.
To map a query parameter to a method argument, JAX-RS provides the #QueryParam annotation.
public void putWithQueryParam(#QueryParam("name") String name) {
// do something
}
You can set:
#PUT
#path{/putHtm}
#Consumes("text/html")
public void putHtml(String name) {
this.name = name;
}
and if you use something like google`s Volley library you can do.
GsonRequest<String> asdf = new GsonRequest<String>(ConnectionProperties.happyhourURL + "/putHtm", String.class, yourString!!, true,
new Response.Listener<Chain>() {
#Override
public void onResponse(Chain response) {
}
}, new CustomErrorListener(this));
MyApplication.getInstance().addToRequestQueue(asdf);
and GsonRequest will look like:
public GsonRequest(String url, Class<T> _clazz, T object, boolean needLogin, Listener<T> successListener, Response.ErrorListener errorlistener) {
super(Method.PUT, url, errorlistener);
_headers = new HashMap<String, String>();
this._clazz = _clazz;
this.successListener = successListener;
this.needsLogin = needLogin;
_object = object;
setTimeout();
}