We serve json objects over a OData REST API. The rough structure of an object looks like below
{
"Property1": "Value1",
"Property2": {
"InterestingProperty": "/text1/part1/text2/part2"
}
}
The values "part1" and "part2" are the ones I am interested in and from the back end they are indexed together.
I need to design the List API which can allow filtering of the objects which contain both the part1 and part2 in that particular property.
Any guidance on how to go about this ?
You could solve this on the client side with the $filter query option and the built-in contains function. For example,
GET http://host/MyObjects?$filter=contains(Property2/InterestingProperty,'part1') and contains(Property2/InterestingProperty,'part2')
However, this approach does allow for false positives; e.g., if the value of InterestingProperty is something like /text1/part1/text2/part234.
Perhaps a better approach is to define a server-side OData function to do custom filtering. This would allow you to have fine-grain control over matching against InterestingProperty. A function named CustomFilter in the MyService namespace and bound to the MyObjects entity set, would be invoked as follows:
GET http://host/MyObjects/MyService.CustomFilter
If you are using the .NET implementation of OData, there is a tutorial to get you started writing OData functions.
Related
Usually an AEM component is retrieving its data from a JCR node, but I was wondering whether it's possible to pass data to it in HTL. Sure, there's data-sly-resource, but as far as I know this way you can only pass a JCR node.
So in an actual case I've got data in a model that's retrieved from elsewhere. Yet I'd like to use existing components. I'm aware that the data must at least match the component-types' model.
But what if the component I'd like to use is using an model that got its data injected like
#Inject
#Optional
String[] itemList;
So in my stubborn thoughts it should be possible to somehow pass a string array like
<div data-sly-resource="${myModel.aStringArray # resourceType='my/component' }"></div>
But like mentioned above this seems to be meant for passing nodes only.
Is there any way to accomplish passing data directly to a component (other than creating a template)?
You can pass additional information in the form of request attributes or selectors
Selectors
Selectors are the most straight forward of passing simple information. This is an array of strings that can be passed. This is quite useful to data that can act as flags ex:
Variant/Mode of the component
index of the component in a list if it is being included in a loop.
ID of the parent when building things like accordion, tabs
This approach is an abuse of selectors, but IMHO as long as you know what you are doing this shouldn't be a major concern.
<article data-sly-resource="${'path/to/resource' # selectors=['s1', 's2']}"></article>
You can add, replace or remove selectors while including the component. Checkout the documentation for the syntax. https://docs.adobe.com/content/help/en/experience-manager-htl/using/htl/block-statements.html#resource
Request Attributes
This option allows you to add custom request attributes to the component request. This can be used to pass objects as parameters to the component while including them.
These are standard http request attributes with convince of scoping them to a particular instance of script/resource inclusion. To use this you will end up needing a model class or use-js as there is little support to compose the data to be passed along in sightly.
<sly data-sly-use.settings="com.adobe.examples.htl.core.hashmap.Settings"
data-sly-include="${ 'productdetails.html' # requestAttributes=settings.settings}" />
https://docs.adobe.com/content/help/en/experience-manager-htl/using/htl/block-statements.html#request-attributes
There is another way. You can pass additional parameters to the Sling Model on initialization using data-sly-use. For example:
<div data-sly-use.model="${'com.model.Teaser' # test='abc'}"
You can read then the variable "test" in model from request:
#PostConstruct
private void initModel() {
String value = request.getAttribute("test");
// value is 'abc'
}
In order this to work correctly you need to make sure your Sling Model is adaptable from request #Model(adaptables = SlingHttpServletRequest.class}
I'm having an issue with the HAL output that I get when combining Spring Data Web Support, Pageables, Projections, and HATEOAS.
I have a JPA entity, with an interface projection. I have a JPA repository with a findAllProjectedBy(Pageable).
public interface FeatureRepository extends JpaRepository<Feature, Long> {
Page<FeatureDataLoad> findAllProjectedBy(Pageable pageable);
}
I have a REST controller like this.
#GetMapping("/features")
public ResponseEntity<PagedResources<Resource<FeatureDataLoad>>> exportFeatures(
Pageable pageable,
PagedResourcesAssembler<FeatureDataLoad> assembler) {
Page<FeatureDataLoad> page = featureRepository.findAllProjectedBy(pageable);
PagedResources<Resource<FeatureDataLoad>> resource = assembler.toResource(page);
return new ResponseEntity<PagedResources<Resource<FeatureDataLoad>>>(resource, HttpStatus.OK);
}
This works well and is surprisingly easily, but when combining all of these features, I get something odd in the HAL rendering.
{
"_embedded": {
"tupleBackedMapList": [
{
"property": "value" /* etc. */
}
]
},
"_links": { /* standard links */ },
"page": { /* standard pagination info */ }
}
Pretty amazing, given the amount of extra work I had to do to get HAL and REST support for my existing entities. But, what's up with "tupleBackedMapList"? Without the projection, I get what I expected, "featureList".
I can't find how I would either fix this, or "customize" the generation of that part.
I seems minor, but I'm trying to "sell" the organization of adopting HAL and I'm this seems too weird.
I'm not wanting to drop the projection because the Feature entity has a computed column that I need for the rest of the app, but it almost doubles the query time.
For what it's worth, I'm on Spring Boot 2.1.6.RELEASE.
Edit
After some experimentation, found that if I make the entity class implement the projection interface, I get what I want. The documentation for Spring Data doesn't say you should put the interface on the entity, but I'm OK with that.
(It looks OK if I implement the interface, but it ignores the projection.)
Also, for what it's worth, I tried using #Relation to name the collection and that didn't help, but it did make the property name change. Oddly enough, if I use the #Relation annotation, it turns the property name to content which is what documentation says it should be.
Maybe I'll make a feature request to honor HATEOAS metadata annotations.
Edit 2
I worked around this by putting the common attributes in a #MappedSuperclass that I called FeatureBase and then made a Feature that extended that super class and added the expensive column computation and made a FeatureDataLoad that just extended the base. It was annoying, so I'd still like a real solution because projections are too easy otherwise.
And again, if this was the third or fourth service in HAL, I'd probably not worry too much about it, but since this is the first and I'm trying to drive adoption, I felt that this was important enough to spend the time on.
FeatureDataLoad is not an entity, so PagedResourceAssembler cannot create links for it. (And cannot find out the name for it).
But if FeatureDataLoad is a projection for Feature, then - in theory - you can use PagedResourcesAssembler<Feature> assembler in your method. (Instead of PagedResourcesAssembler<FeatureDataLoad>)
What do I want:
I want to be able to generate swagger documentation that passes a key/value into the URL. This so that I can use generic arguments controller to handle my requests like Dictionary.
If swagger can't generate it, is there a way to generate the documentation by using reflection on my objects? This so that I can still use generic methods
If not, what would be the best way to let everyone know what the correct approach would be.
Why do I want it
I'm developing a new API and I'm using swagger to create the documentation. In this API I want to work with some generic methods to prevent hardcoding things. For example on the PATCH method I use a Dictionary<string, string> to get the property/value combination and in the GET I use a custom object as the argument. In both cases, swagger can't generate the correct parameter fields, because it takes the argument as url key.
Example action & form - incorrect
public async Task<IActionResult> Patch(int id, Dictionary<string, string> viewModel)
{
return await ConnectionWrapper(() => connector.Patch(id, viewModel));
}
This uses the body, not the query
Other examples - incorrect
In the GET I have a model with a custom modelbinder to handle all the rest URL arguments. The problem is because the model is defined it sees the filter as a property.
Then it is in the URL, but it will look like http://example.com/controller/method/id?sort=prop_asc&filter=propTwo%3D=value, instead of http://example.com/controller/method/id?sort=prop_asc&propTwo=value
Desired output
I've modified the HTML to simulate what I would like in the picture above. The URL that would be called would be http://example.com/controller/method/id?propertyName=propertyValue.
I don't mind if there would be only one option to add a generic key/value pair because with it I can demonstrate what I want.
Expected solution
I think the solution lies in the MapType startup method of swagger or in an implementation of the IOperationFilter, but I haven't been able to figure it out.
I am building a sample project with Angular2/Typescript in order to use it as a new frontend for an existing backend code.
I really like the ability to create types and consider to use some of the JSON objects served by the backend as typed classes like this:
module DomainObjects {
export class SomeDomainObject {
constructor(attr1:string, attr2:number) {...}
...
}
}
Some of the JSON code that is returned by the backend is very large, so I don't want to work with a huge amount of parameters in the constructor. At best i just pass a JSON object as a single parameter to the constructor which does some checks (or not). On the other hand I'd like to access the JSON object directly. Is something like this possible:
myobject:SomeDomainObject;
...
this.myobject = new SomeDomainObject({id:10,color:'green'});
and access myobject in a template like this
{{myobject.color}}
without having another reference like {{myobject.json.color}}
You can model your JSON data with interfaces (instead of classes). Then you can simply assign the received JSON to it (instead of calling a constructor).
For validation you will most likely need to build a validation function that checks if the received JSONs structure really equals the type that you described in your interface. You could also build a validate and assign function with a signature like
function toTypedData(input: any):SomeDomainObject { ... }
You can also use JSON schema and a suitable validation library to perform that step.
Basically I'm writing an API using Web API 2 and Entity Framework on the backend.
The thing I'm unsure about is what to do in regards to foreign keys on my models.
Say I got a person property with a foreign key to an order property.
Sometimes the client needs the person property, sometimes it does not. How should I go about this?
Should I create 2 methods:
/api/person/1 <-- returns person without order
/api/personwithorder/1 <-- returns person with order
Seems like an awful lot of methods in my opinion.
I know the queryable attribute exists as well which provides support for the client to use the $extend argument to include properties at will - however I would rather not use the queryable attribute if I can avoid it.
What are your suggestions?
Off the top of my head, here are some options.
Option 1
Multiple methods in API
public Person GetPerson() { ... }
public Person GetPersonWithOrders() { ... }
Option 2
Pass a flag to the method:
public Person GetPerson(bool getOrders) { ... }
Option 3
You could use OData to allow the caller to query the data.
To expand on DavidG's option 3 of using OData, here is how you'd do it:
Install-package Microsoft.AspNet.WebApi.OData
Create a PeopleController inheriting from ODataController
Configure the Web API OData model like so:
modelBuilder.EntitySet<Person>("People");
Define your Get method as returning an IQueryable<Person>
In your calling code, add the expand clause to the URL to specify the child object you would like to expose, like this: /api/People(1)?$expand=Orders
There's a little bit more to it around registering the OData route, but this is all standard configuration that you can find in any sample project.
OData is really very flexible and takes care of tonnes of issues about how you should build your URLs.