How can I manipulate the REST API GET generated by YANG in Opendaylight? - opendaylight

PUT, DELETE, POST can be operated as shown below.
By the way, I do not know how to do GET.
Please help me.
// PUT & DELETE (mapped to WRITE, DELETE of MD-SAL)
public void onDataTreeChanged(Collection<DataTreeModification<GreetingRegistry>> changes) {
for(DataTreeModification<GreetingRegistry> change: changes) {
DataObjectModification<GreetingRegistry> rootNode = change.getRootNode();
if(rootNode.getModificationType() == WRITE) {
...
}
else if(rootNode.getModificationType() == DELETE) {
...
}
}
// POST (mapped to RPC of MD-SAL)
public Future<RpcResult<HelloWorldOutput>> helloWorld(HelloWorldInput input)
{
HelloWorldOutputBuilder helloBuilder = new HelloWorldOutputBuilder();
helloBuilder.setGreeting("Hello " + input.getName());
return RpcResultBuilder.success(helloBuilder.build()).buildFuture();
}
// GET (???)
How should I implement it?

You don't actually have to implement anything for GET in your code, when you want to read from YANG modeled MD-SAL data, the GET method is be available by default, and returns whatever data you ask for in the URL. It is important to point to the correct URL.
If you want to do some processing on data before returning it to the user, you can use RPCs with POST, and do the processing in the RPC based methods. In your example above, you could pot the search keys into HelloWorldInput, do the processing in helloWorld(), and return results in HelloWorldOutput.

Related

Performance in microservice-to-microservice data transfer

I have controller like this:
#RestController
#RequestMapping("/stats")
public class StatisticsController {
#Autowired
private LeadFeignClient lfc;
private List<Lead> list;
#GetMapping("/leads")
private int getCount(#RequestParam(value = "count", defaultValue = "1") int countType) {
list = lfc.getLeads(AccessToken.getToken());
if (countType == 1) {
return MainEngine.getCount(list);
} else if (countType == 2) {
return MainEngine.getCountRejected(list);
} else if (countType == 3) {
return MainEngine.getCountPortfolio(list);
} else if (countType == 4) {
return MainEngine.getCountInProgress(list);
} else if (countType == 5) {
return MainEngine.getCountForgotten(list);
} else if (countType == 6) {
return MainEngine.getCountAddedInThisMonth(list);
} else if (countType == 7) {
return MainEngine.getCountAddedInThisYear(list);
} else {
throw new RuntimeException("Wrong mapping param");
}
}
#GetMapping("/trends")
private boolean getTrend() {
return MainEngine.tendencyRising(list);
}
It is basically a microservice that will handle statistics basing on list of 'Business Leads'. FeignClient is GETting list of trimmed to the required data leads. Everything is working properly.
My only concern is about performance - all of this statistics (countTypes) are going to be presented on the landing page of webapp. If i will call them one by one, does every call will retrieve lead list again and again? Or list will be somehow stored in temporary memory? I can imagine that if list become longer, it could take a while to load them.
I've tried to call them outside this method, by #PostConstruct, to populate list at the start of service, but this solution has two major problems: authentication cannot be handled by oauth token, retrieved list will be insensitive to adding/deleting leads, cause it is loaded at the beginning only.
The list = lfc.getLeads(AccessToken.getToken()); will be called with each GET request. Either take a look at caching the responses which might be useful when you need to obtain a large volume of data often.
I'd start here: Baeldung's: Spring cache tutorial which gives you an idea about the caching. Then you can take a look at the EhCache implementation or implement own interceptor putting/getting from/to external storage such as Redis.
The caching is the only way I see to resolve this: Since the Feign client is called with a different request (based on the token) the data are not static and need to be cached.
You need to implement a caching layer to improve performance. What you can do is, you can have cache preloaded immediately after application starts. This way you will have the response ready in the cache. I would suggest to go with Redis cache. But any cache will do the job.
Also, it will be better if you can move the logic of getCount() to some service class.

Spring batch reader - How to avoid returning a list of objects

So I have a spring batch app that I have getting a list of ids that it then uses 'read()' on to get 1 to many results back. The issue is, I have no control over how many results I get back for each id meaning that my chunking is spotty at best. Is there a suggested way to avoid spikes in memory/cpu? An example is below:
#Before
public void getIds() {
*getListOfIds* //Usually around 10,000 or so
}
#Override
public AccountObject read() {
if(list of ids havent all been used) {
List<AccountObject> myAccounts = myService.getAccounts(id);
return myAccounts; //This could be anywhere from 1 result to 100,000 results.
} else {
return null;
}
}
So the myAccounts object above could be small or huge. This causes chunking to basically be useless because at the moment I am chunking by List. I'd really rather chunk by straight AccountObject but don't see an easy way to do this.
Is there a class, strategy, etc. that I am missing here?

.Net Web API IActionFilter.OnActionExecuted return type

I have an application on .Net Web API. After each action get executed, I want to take a look at result and change something in that.
Say my API controllerAction looks like
public Car Get()
{
...
}
After the action is executed, I want to modify the properties of return type in this case Car (but can be different for different action).
I know that IActionFilter.OnActionExecuted() gets called after an action gets executed. But I am not sure how to access the return type in this method.
You should be able to do this by looking at the response on the action executed context. If you want to get the car and modify it you could write something like this:
Car car;
if (actionExecutedContext.Response.TryGetContentValue<Car>(out car))
{
// modify the car to send back in the response
}
If you want to check the type of the response, you could write:
ObjectContent objectContent = actionExecutedContext.Response.Content as ObjectContent;
if (objectContent != null)
{
Type responseType = objectContent.ObjectType;
// do stuff with the type
}

MediaTypeFormatter WriteToStreamAsync not called unless I add to Accept headers

I have a MediaTypeFormatter that converts an internal rep of an image to a png/jpeg/etc. if someone asks for it. However, my WriteToStreamAsync never gets called unless I add an image/png or similar to the accept headers.
First, here is my webapi method, with some key bits removed for brevity:
public ImageFormatter.BinaryImage GetImage(int cId, int iId)
{
....
using (var input = iFIO.OpenRead())
{
input.Read(b.data, 0, (int)iFIO.Length);
}
// With this next line my mediatypeformatter is correctly called.
Request.Headers.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("image/png"));
return b;
}
And here is the write portion of my MediaTypeFormatter (there is also a read portion, and that works great, actually).
namespace PivotWebsite.MediaFormatters
{
public class ImageFormatter : MediaTypeFormatter
{
public class BinaryImage
{
public byte[] data;
public string metaData;
}
public ImageFormatter()
{
SupportedMediaTypes.Add(new MediaTypeHeaderValue("image/jpg"));
SupportedMediaTypes.Add(new MediaTypeHeaderValue("image/jpeg"));
SupportedMediaTypes.Add(new MediaTypeHeaderValue("image/png"));
}
public override bool CanWriteType(Type type)
{
return true;
}
public override async Task WriteToStreamAsync(Type type, object value, Stream writeStream, HttpContent content, TransportContext transportContext)
{
var b = value as BinaryImage;
if (b == null)
throw new InvalidOperationException("Can only work with BinaryImage types!");
await writeStream.WriteAsync(b.data, 0, b.data.Length);
}
}
}
What I expected to be able to do was, in WriteToStreamAsync, to alter the outgoing headers to include Content-Type as "image/png" (or whatever, depending on the data type).
However, when I call this from a web browser with a URL like "http://my.testdomain.net:57441/api/Images?cID=1&iID=1", the WriteToStreamAsync never gets called (accepted headers are listed as {text/html, application/xhtml+xml, */*}). If I add the line above that adds the proper image type, then everything is called as I would expect.
What am I missing here? The accepted header of "*/*" should have triggered my media formatter, right? Or... am I missing something basic about the plumbing in Web API.
Do you want the image formatter to always get used if the Accept header is "/"? If that's the case, then you should insert your formatter first in the Formatters collection like this:
config.Formatters.Insert(0, new ImageFormatter());
What happens when there isn't an exact Accept header match like in your case is that the first formatter that can write the type gets selected to serialize the object. So if you register your formatter first, it would get used.
This could have unintended side-effects because it would affect all your controllers though. I would suggest changing the CanWriteType implementation to only return true if it's a BinaryImage. That should make the formatter only get used when that's your return type.
Another thing you could do is select the formatter directly in your action by returning an HttpResponseMessage:
return Request.CreateResponse(HttpStatusCode.OK, image, new ImageFormatter());
That's basically saying "this action should always use the image formatter, regardless of content-type, accept headers etc". That might be reasonable in your case if you're always just returning an image and you need it serialized with your formatter.
I'm writing a CsvFormatter and I want to be able to call the API from the browser to trigger a file download. Since I didn't have control over the Accept header, I wanted to use an extension to trigger my CSV formatter, but the XML formatter kept getting the request. I found that by adding a "text/html" media type, I could handle the CSV extension. Hopefully this doesn't cause other problems down the line :).
public CsvFormatter()
{
var header = new MediaTypeHeaderValue("text/csv");
SupportedMediaTypes.Add(header);
MediaTypeMappings.Add(new UriPathExtensionMapping("csv", header));
// From Chrome: Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
// Allow the formatter to run from a standard browser request.
header = new MediaTypeHeaderValue("text/html");
SupportedMediaTypes.Add(header);
MediaTypeMappings.Add(new UriPathExtensionMapping("csv", header));
}

AJAC MVC3 Request object and raw Ajax data, where the heck is it?

If this was a regular post of a form I could go to Request.Form['somevalue'] and get the value. If this was a get with a query string I could go to Request.QueryString["somevalue"] and get the value.
Where is the raw data when you post an ajax request. I need a value out of the raw data string in a filter method.
Any help will be appreciated!!
Edits below:
public class ValidateAntiForgeryId : FilterAttribute, IAuthorizationFilter {
public void OnAuthorization(AuthorizationContext filterContext) {
if (filterContext == null) {
throw new ArgumentNullException("filterContext");
}
Guid filterGuid;
Guid.TryParse(filterContext.RequestContext.HttpContext.Request.Form["__sessionId"], out filterGuid);
if (filterGuid == Guid.Empty)
throw new AuthenticationException("Authentication failure");
try {
var cookieGuid = (Guid)filterContext.RequestContext.HttpContext.Items["SessionId"];
} catch {
throw new AuthenticationException("Authentication failure");
}
}
The posted data looks like this:
{"SsnLastFour":"2222","AccountNumber":"B112233","__sessionId":"dca0a504-3c40-4118-ae19-afefb9bfc8bd"}
I need access to the __sessionId chunk inside the filter.
There's nothing magic about AJAX posts. They're just plain old HTTP. That means you have plain old HTTP post values, and/or plainold HTTP Get values.
If you're not seeing them, it probably means you're not actually submitting them.
EDIT:
Two issues you did not include in your original question: 1) That this is JSON, and 2) That this is in an AuthorizationFilter (rather than an action method).
Both change the answers. Since ASP.NET does not natively understand JSON post values, you will have to parse them, via Request.InputStream. MVC3 by default has a JSON model binder, but AuthorizationFilters execute before model binders do, so you will be accessing things prior to the model binders being executed, and as such FormsCollection won't be populated (Request.Form[] won't work either, because as I said, asp.net doesn't natively understand JSON).
You may find that installing JSON.net via nuget may help with this task. Or you might just write a simple parse routine, since you know exactly what you're looking for.
You can accept the parameter values the same way you accept in normal form post.
Ex :
$.get("User/Get", { userId : "24"} ,function(data){
alert(data);
});
or
$("#yourDivId").load("User/Get?userId=23");
Your action method should look like
public ActionResult Get(int userId)
{
// you have value present in userId
if(Request.IsAjax())
{
return View("Partial/MyPartialView");
}
return View();
}
One thing you have to remember is, the parameter name of your action method should be same as of what your parameter/querystring name.
The fitlerContext has an ActionParameters collection which should have the parsed JSON properties (in case that helps). This may be easier than parsing the InputStream.
var sessionId = filterContext.ActionParameters["__sessionId"];

Resources