SDR: Exclude fields from json for associated object - spring-data-rest

I have an entities like this:
Restaurant
String name
User manager
User
String name
Date lastLogin
When I do GET /restaurants I want to inline the manager, but only with name, not with lastLogin.
When I do GET /users I want to see the full user: name + lastLogin
So I created a Restaurant-Projection and applied it as excerpt to the RestaurantRepo. But this embeds the full user into the restaurant.
When I create an excerpt project for user too, which omits the lastLogin field then it works for restaurants as expected but not for users because here the lastLogin is missing.
How can I solve this problem?
Thank you

You can do something like this (example from the spring data rest documentation)
#Projection(name = "virtual", types = { Person.class })
public interface VirtualProjection {
#Value("#{target.firstName} #{target.lastName}")
String getFullName();
}
So in your case this would result into this projection:
#Projection(name = "restaurantWithManagerName", types = { Restaurant.class })
public interface RestaurantWithManagerNameProjection {
String getName();
#Value("#{target.manager.name}")
String getManagerName();
}
If you want to keep the orginal structure with a nested User object you could achieve this by creating a custom reduced interface for the user and use it in your projection:
public interface UserLight {
String getName();
}
#Projection(name = "restaurantWithReducedManager", types = Restaurant.class)
public interface RestaurantWithManagerNameProjection {
String getName();
UserLight getManager();
}

Related

Is there an automated mechanism where I can detect if any email field is missing?

Let's assume this class:
public class AccountInfo
{
public string Email;
public string Username;
public string Password;
}
and this ASP api:
[HttpPost, Route("create")]
public IActionResult CreateUser([FromBody]AccountInfo Info)
{
...
}
If a user passes something like this:
{
"eail" : "ndienw", <--- notice the mispelling
"username" : "djiw",
"password" : "dow"
}
The email field will be null, so I need in each call to check for every fields.
Is there an automated mechanism where I can detect if any field is missing? I'm looking for something generic that can be applied through all calls.
Being able to opt out and mark some parameters optional would be great, but in our case, everything is always needed so far.
In this scenario, the ModelState is still valid; is that the expected behavior?
You can use data annotations on your Email property. RegularExpression attribute will check the field that located on, whether the value which provided is matching with this pattern. Required attribute checks whether this field is empty or not.
[RegularExpression(#"\w+([-+.']\w+)*#\w+([-.]\w+)*\.\w+([-.]\w+)*", ErrorMessage = "Email was invalid.")]
[Required]
public string Email;

Querying user profile repository ATG

I have added few custom properties on top of ATG's profile. When i want to read the profile values in jsp, i am just importing ATG profile and then accessing the property as profile.name.
I am facing one scenario where i need to return profile.lastName for one type of users and profile.firstName for other type of users. This is based on say profile.userType property.
Is it possible to add the userType check in repository so that when i read profile.name it should return either firstName or lastName.
Since name is referred in many places (1000) i cannot add the user type check everywhere and display name accordingly. So if possible, we can handle this in repo.
Yes you can. Simply use a RepositoryPropertyDescriptor. I hastily cobbled together a version below (not tested but should be enough to get you going):
import atg.repository.RepositoryItem;
import atg.repository.RepositoryItemImpl;
import atg.repository.RepositoryPropertyDescriptor;
public class AcmeRealUserName extends RepositoryPropertyDescriptor{
#Override
public Object getPropertyValue(RepositoryItemImpl profile, Object pValue) {
String userType = (String) profile.getPropertyValue("userType");
String lastName = (String) profile.getPropertyValue("lastName");
String firstName = (String) profile.getPropertyValue("firstName");
if ("firstNameUser".equals(userType)) {
return firstName;
} else {
return lastName;
}
}
#Override
public void setValue(String pAttributeName, Object pValue) {
//Probably Do Nothing since this is a derived property
}
/**
* A class specific logDebug method to output log messages. Unfortunately
* using System.out as the mechanism.
*
* #param pMessage
*/
protected void logDebug(String pMessage) {
System.out.println("### DEBUG(:" + getClass().getName() + getName() + ")" + pMessage);
}
}
You then refer to this new property in your userprofiling.xml as follow:
<property name="name" property-type="com.acme.propertydescriptor.AcmeRealUserName" item-type="string" writable="false" readable="true" />

How to write dynamic SQL Queries in Entity Framework?

I have a page where the user can input a SQL query. This query can be against any table in my database. I need to execute this query against the corresponding table and show the result in my view. How to do this?
For eg: user can input Select * from ABC or select max(price) from items.
I tried:
var results = DbContext.Database.SqlQuery<string>(query).ToList();
But this throws an error:
The data reader has more than one field. Multiple fields are not valid
for EDM primitive or enumeration types.
Whatever the result I should be able to pass it to the view and display it.
Please help.
The error message states the problem exactly:
The data reader has more than one field. Multiple fields are not valid for EDM primitive or enumeration types.
To correct the problem, you can either submit a SQL string that returns a single string column from the database, or supply a type parameter that allows EF to map multiple columns.
For example:
SELECT someStringColumn FROM ABC
OR
var results = DbContext.Database.SqlQuery<MyDto>(query).ToList();
Where MyDTO looks something like this:
class MyDto
{
public int AddressID { get; set; }
public string Address { get; set; }
public string City { get; set; }
public string State { get; set; }
public string Zip { get; set; }
}
you can use
SqlQuery<dynamic>"
this will resolve the error but you will only be able to get the count of the result returned. So you can just verify the query has returned some data. But still will need to know the type of the returned data.
It is a risk of providing the user to input query to database.

Get all roles for the current user

Once I have a principal logged in, how can I obtain all roles for a user?
I'm creating a Java EE 6 application, and I'm writing a JAX-RS service to return all the roles for the current user, which will be consumed by a front-end to properly render the screen.
I know that there are multiple interfaces that can return whether the user is a member of a certain role, but what I want is a interface that would allow me to get all roles for that particular user.
Given that all the role names are known at compile time, you can do something like the following :
public final class SecurityRoles {
static final String USER_ROLE = "user";
static final String ADMIN_ROLE = "admin";
static final String SUPPORT_ROLE = "support";
}
and
#DeclareRoles({
USER_ROLE,
ADMIN_ROLE,
SUPPORT_ROLE
})
#Path("/rest")
public class SomeRS {
#Context
SecurityContext securityContext;
#GET
#PermitAll
#Produces(MediaType.APPLICATION_JSON)
public List<String> lookupUserRoles() {
return Arrays.stream(SomeRS.class.getAnnotation(DeclareRoles.class).value())
.filter(roleName -> securityContext.isUserInRole(roleName))
.collect(Collectors.toList());
}
}
which tests to see if the user is in each of the known roles before adding the role name to a list that is returned.
Note that if you do not use #DeclareRoles then the roles used by the application must be declared in either the web.xml or application.xml files (and you will need to declare the names in a String[] array somewhere).

Providing Jackson Mapper multiple ways to deserialize the same object

I'm trying to deserialize two types of json:
{
name: "bob",
worksAt: {
name: "Bobs department store",
location: "downtown"
},
age: 46
}
and
{
name: "Tom",
worksAt: "company:Bobs department store",
age: 27
}
into these objects:
The first way creates two new objects, the second way requests the object from the database based on the contents of a string.
sort of like how jackson mapper can deserialize an arbitrary string into an object, for objects like this:
public class Company{
public String name;
public Employee[] employees
public Company(){}
public Company(String json){
//turn string into object using whatever encoding you want blah blah blah...
}
}
The trouble is I need both. I need it to handle objects and strings. Both could arrive from the same input.
The first think I tried was making a Converter
It says these create a delegate type to pass to the deserializer, but the converter is always applied even when the datatype isn't a string. So that didn't work.
I've also tried a normal deserializer, but I can't find a way to defer to the BeanDeserializer. The beanDeserializer is so complicated that I can't manually instantiate it. I also see no way to defer to a default deserializer in jackson mapper.
Do I have to re-implement jackson mappers deserialization to do this? Is there any way for a deserializer to say "I can't do this, use the default implementation."?
Edit: Some further progress. Based on the Jackson Mapper source code, it looks like you can instatiate bean deserializers like this:
DeserializationConfig config = ctxt.getConfig();
JavaType type = config.constructType(_valueClass);
BeanDescription introspect = config.introspect(type);
JsonDeserializer<Object> beanDeserializer = ctxt.getFactory().createBeanDeserializer(ctxt, type , introspect);
but for some reason all the _beanProperties have the FailingDeserializer set for their _valueDeserializer and the whole thing fails. So I have no idea why that happens...
Have you tried writing a custom deserializer? This gives you the most control on how Jackson deserializes the object. You may be able to try to deserialize one way, and if there's an error, try another way.
Jackson can also handle polymorphic deserialization, though this would require a small change to the json to include type information, and it sounds like your problem constraints might not allow that.
If I understand the problem correctly, I would recommend using JsonNode. You can define a setter in your top-level type like this:
setWorksAt(JsonNode node) {
if (node.getNodeType == JsonNodeType.STRING) {
String name = node.getText();
name = name.substring(name.lastIndexOf(':'));
this.company = new Company(name);
} else if (node.getNodeType == JsonNodeType.OBJECT) {
this.company = mapper.treeToValue(node, Company.class);
}
}
That allows you to handle the two separate worksFor inputs, while still allowing the standard mapper to handle any substructures for the OBJECT case.
With recent versions of Jackson (2.8+ I think, definitely works with 2.9) you can use multiple #JsonCreator and do something like this:
public class Company {
private String name;
private String location;
private Company(String name, String location) {
this.name = name;
this.location = location;
}
private Company(String stringRepresentation) {
// add code here to parse string and extract name and location
}
#JsonCreator
private static Company fromJson(
#JsonProperty("name") String name,
#JsonProperty("location") String location)
{
return new Company(name, location);
}
#JsonCreator
private static Company fromJson(String str) {
return Company(str);
}
}