How to adapt a child node in sling model of aem6 - aem

I am learning to use one of the new features of AEM6 - Sling Models. I have already fetched the properties of a node following the steps described here
#Model(adaptables = Resource.class)
public class UserInfo {
#Inject #Named("jcr:title")
private String title;
#Inject #Default(values = "xyz")
private String firstName;
#Inject #Default(values = "xyz")
private String lastName;
#Inject #Default(values = "xyz")
private String city;
#Inject #Default(values = "aem")
private String technology;
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
public String getTechnology() {
return technology;
}
public String getTitle() {
return title;
}
}
and adapted it from a resource
UserInfo userInfo = resource.adaptTo(UserInfo.class);
Now i have the hierarchy as -
+ UserInfo (firstName, lastName, technology)
|
+ UserAddress (houseNo, locality, city, state)
Now I want to fetch the properties of the UserAddress.
I had got some hints from the documentation page, such as -
If the injected object does not match the desired type and the object implements the Adaptable interface, Sling Models will try to adapt it. This provides the ability to create rich object graphs. For example:
#Model(adaptables = Resource.class)
public interface MyModel {
#Inject
ImageModel getImage();
}
#Model(adaptables = Resource.class)
public interface ImageModel {
#Inject
String getPath();
}
When a resource is adapted to MyModel, a child resource named image is automatically adapted to an instance of ImageModel.
but I don't know how to implement it in my own classes. Please help me out with this.

It sounds like you need a separate class for the UserAddress to wrap the houseNo, city, state and locality properties.
+ UserInfo (firstName, lastName, technology)
|
+ UserAddress (houseNo, locality, city, state)
Just mirror the structure you outlined in your Sling Models.
Create the UserAddress model:
#Model(adaptables = Resource.class)
public class UserAddress {
#Inject
private String houseNo;
#Inject
private String locality;
#Inject
private String city;
#Inject
private String state;
//getters
}
This model can then be used in your UserInfo class:
#Model(adaptables = Resource.class)
public class UserInfo {
/*
* This assumes the hierarchy you described is
* mirrored in the content structure.
* The resource you're adapting to UserInfo
* is expected to have a child resource named
* userAddress. The #Named annotation should
* also work here if you need it for some reason.
*/
#Inject
#Optional
private UserAddress userAddress;
public UserAddress getUserAddress() {
return this.userAddress;
}
//simple properties (Strings and built-in types) omitted for brevity
}
You can tweak the behaviour with additional annotations for default values and optional fields but this is the general idea.
In general, Sling Models should be able to handle an injection of another model as long as it finds an appropriate adaptable. In this case, it's another Sling Model but I've done it with legacy classes based on adapter factories as well.

Related

mapstruct - Update existing bean - ignoring 'id' field in all the child/nested beans (arraylists, sets etc..)

I have a parent class with many child entities.
There are 2 instances of this parent class.
Want to copy data of 1 instance to another one (ignoring the 'id' property in all the child entities)
---- hiding getters and setters for brevity
public class IdBean {
private Long id;
}
public class City extends IdBean {
private String name;
}
public class Country extends IdBean {
private String name;
private List<City> cities;
}
public class Student extends IdBean {
private String name;
}
public class School extends IdBean {
private String name;
private List<Student> students;
private List<Country> countries;
}
#MapperConfig(mappingInheritanceStrategy = MappingInheritanceStrategy.AUTO_INHERIT_FROM_CONFIG)
public interface SchoolCentralConfig {
#Mapping(ignore = true, target = "id")
IdBean updateBeanEntityFromDto(IdBean dto);
}
#Mapper(config = SchoolCentralConfig.class)
public interface SchoolMapper {
SchoolMapper INSTANCE = Mappers.getMapper( SchoolMapper.class );
#Mapping(target = "companies.id", ignore = true )
void updateSchoolFromDto(School schoolDTO, #MappingTarget School schoolEntity);
}
I want to ignore all the 'id' property from all the nested fields.
MappingInheritanceStrategy.AUTO_INHERIT_FROM_CONFIG does (unfortunately) not work for nested methods yet.
What you can do is write a signature for Country and Student as well. Then it will work.
So:
#Mapper(config = SchoolCentralConfig.class)
public interface SchoolMapper {
SchoolMapper INSTANCE = Mappers.getMapper( SchoolMapper.class );
void updateSchoolFromDto(School schoolDTO, #MappingTarget School schoolEntity);
void updateStudentFromDto(Student studentDTO, #MappingTarget Student studentEntity);
// etc
}

Picketlink with custom model and long Id

I have a existing Model and want to use it with Picketlink. But I am using Long as #Id field. But Picketlink expect this to be a String field. I have found some hints to use another entity which maps to the corresponding entity of my model. But actually I don't now how to do it.
I have a base class, which all entities derive from:
#MappedSuperclass
public abstract class AbstractEntity implements Serializable, Cloneable {
#Id
#Identifier
#Column(name = "SID")
private Long sid;
#Column(name = "INSERT_TIME")
private Date insertTime;
#Column(name = "UPDATE_TIME")
private Date updateTime;
// getters and setters
}
And a derived realm entity:
#Entity
#IdentityManaged(Realm.class)
public class RealmEntity extends AbstractEntity {
#AttributeValue
private String name;
#PartitionClass
private String typeName;
#ConfigurationName
private String configurationName;
#AttributeValue
private boolean enforceSSL;
#AttributeValue
private int numberFailedLoginAttempts;
// getters and setters
}
And the mapping class for Picketlink looks as follows:
#IdentityPartition(supportedTypes = {
Application.class,
User.class,
Role.class
})
public class Realm extends AbstractPartition {
#AttributeProperty
private boolean enforceSSL;
#AttributeProperty
private int numberFailedLoginAttempts;
private Realm() {
this(null);
}
public Realm(String name) {
super(name);
}
}
The PartitionManager is defined as follows:
builder
.named("default.config")
.stores()
.jpa()
.supportType(User.class, Role.class, Application.class, Realm.class)
.supportGlobalRelationship(Grant.class, ApplicationAccess.class)
.mappedEntity(App.class, AppUserRole.class, AppRole.class, AppUser.class, UserEntity.class, RelationshipIdentityTypeEntity.class, RealmEntity.class)
.addContextInitializer((context, store) -> {
if (store instanceof JPAIdentityStore) {
if (!context.isParameterSet(JPAIdentityStore.INVOCATION_CTX_ENTITY_MANAGER)) {
context.setParameter(JPAIdentityStore.INVOCATION_CTX_ENTITY_MANAGER, entityManager);
}
}
});
When I try to create a new Realm Hibernate throws an error while trying to load the Realm because the #Id is defined as Long but the #Identifier of the Picketlink model is a String.
this.shsRealm = new Realm(REALM_SHS_NAME);
this.shsRealm.setEnforceSSL(true);
this.shsRealm.setNumberFailedLoginAttempts(3);
this.partitionManager.add(this.shsRealm);
java.lang.IllegalArgumentException: Provided id of the wrong type for class de.logsolut.common.picketlink.model.RealmEntity. Expected: class java.lang.Long, got class java.lang.String
How can I map the JPA model correctly to Picketlink?

spring data jpa fine granular auditing, custom audit

I have requirement where I need to insert user name and group name to which the user belongs (both available in SecurityContext) in the same table.
class Entity
{
#createdBy
String username
#createdBy
String groupname
other fields ...
}
As per requirement. I cant solve this issue by making a user class and referencing it through a foreign key.
With current implementation of AuditingHandler both fields are getting the same value. How do I make sure they get respective values.
Can this be achieved using current implementation ?
If not thn how can I provide custom implementation of AuditingHandler ?
You could make a separate embeddable class and annotate it with #CreatedBy in your parent class. One way is to define a bean implementing AuditorAware, then you can make it return custom object, containing your two required fields. For example, your parent class would look like this (note the listener annotation):
#Entity
#EntityListeners(AuditingEntityListener.class)
public class AuditedEntity {
#Id
#GeneratedValue(generator = "uuid")
#GenericGenerator(name = "uuid", strategy = "uuid")
private String id;
#Embedded
#CreatedBy
private AuditorDetails createdBy;
// setters and getters
}
where AuditorDetails is:
#Embeddable
public class AuditorDetails {
private String username;
private String groupname;
// setters and getters
}
and finally, your AuditorAware bean:
#Component
class AuditorAwareImpl implements AuditorAware<AuditorDetails> {
#Override
public AuditorDetails getCurrentAuditor() {
return new AuditorDetails()
.setUsername("someUser")
.setGroupname("someGroup");
}
}
AuditingHandler fetches your custom AuditorDetails from your AuditorAware bean (it must be single bean implementing it) and sets it in your auditable entity.

JPA and JAXB annotations in the same class cause com.sun.xml.bind.v2.runtime.IllegalAnnotationsException

I have 2 classes: Person and PersonAdapter. Person is generated from wsdl and cannot be changed.
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "Person")
public class Person {
#XmlAttribute(name = "name")
protected String name;
// getters and setters
}
PersonAdapter is an object-adapter for Person and has some additional properties. Objects of this class are provided to clients of my service. I add all JPA and JAXB annotations to PersonAdapter class as I can't change Person class.
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "Person")
#Entity
#Table(name = "T_PERSON")
public class PersonAdapter {
private final Person person;
#XmlAttribute(name = "description")
private String description;
#XmlAttribute(name = "name", required = true)
#Column(name = "C_NAME")
public String getName() {
return person.getName();
}
#Column(name = "C_DESCRIPTION")
public String getDescription() {
return description;
}
// getters, setters, contructors
}
I can't annotate name property because it's in Person class, so I annotate getName method with both JAXB and JPA annotations. But such usage of these annotations on the same method/property causes IllegalAnnotationsException:
Caused by: com.sun.xml.bind.v2.runtime.IllegalAnnotationsException: 14 counts of IllegalAnnotationExceptions
Is it possible to solve this problem?
Solved! The problem wasn't in conflict between JPA and JAXB annotations. The problem was in clash of JAXB contexts: #XmlAccessorType(XmlAccessType.FIELD) on PersonAdapter with public getter of Person provides access of my service's JAXB context to Person object from other context. And Person class is unknown for my context. I solved this problem replacing XmlAccessType.FIELD with XmlAccessType.NONE. Also it can be done with #XmlTransient.

querydsl instance variables

From QueryDSL JPA Tutorial, I could not find differences between default instance variable generated by querydsl and custom variable.
For the entity Customer defined as
#Entity
public class Customer {
private String firstName;
private String lastName;
public String getFirstName(){
return firstName;
}
public String getLastName(){
return lastName;
}
public void setFirstName(String fn){
firstName = fn;
}
public void setLastName(String ln)[
lastName = ln;
}
}
What is the difference between using default instance variable and custom as follows ?
QCustomer customer = QCustomer.customer;
VS
QCustomer customer = new QCustomer("myCustomer");
What could be the possible use cases for custom variable as in second one?
The variable name is used as such in the serialization. If you need to refer to multiple instances of the same type in your query, you need to use multiple variables.
Here is an example
QCustomer customer = QCustomer.customer;
QCustomer customer2 = new QCustomer("customer2");
List<Customer> customers = query.from(customer)
.where(new JPASubQuery()
.from(customer2)
.where(customer2.id.ne(customer.id),
customer2.lastName.eq(customer.lastName),
customer2.firstName.eq(customer.firstName))
.exists())
.list(customer);