I set a BeforeInsertEntityListener to set a SessionAttribute automatically, after some tries I managed to have the new group with the Session Attribute stored in the DB, but when I do, I get a unique id violation since I’m persisting “entity” before insert
Here is the code
@Component("girasole_NewGroupEntityListener")
public class NewGroupEntityListener implements BeforeInsertEntityListener<Group> {
@Inject
private Metadata metadata;
@Inject
private Persistence persistence;
@Override
public void onBeforeInsert(Group entity, EntityManager entityManager) {
Group parent = entity.getParent();
if (parent.getName().equals("Girasole")) {
TypedQuery<Group> query = persistence.getEntityManager().createQuery(
"select g from sec$Group g", Group.class
);
List<Group> groups = query.getResultList();
Integer maxCompanySessionId = 0;
for (Group group: groups) {
Set<SessionAttribute> attributes = group.getSessionAttributes();
for (SessionAttribute attribute: attributes) {
if (attribute.getName().equals("companySessionId")) {
Integer value = Integer.parseInt(attribute.getStringValue());
if (value > maxCompanySessionId){
maxCompanySessionId = value;
}
}
}
}
Set<SessionAttribute> sessionAttributes = new HashSet<SessionAttribute>();
SessionAttribute companySessionId = metadata.create(SessionAttribute.class);
companySessionId.setName("companySessionId");
companySessionId.setStringValue(Integer.toString(maxCompanySessionId + 1));
companySessionId.setDatatype("int");
sessionAttributes.add(companySessionId);
try (Transaction tx = persistence.createTransaction()) {
entity.setSessionAttributes(sessionAttributes);
persistence.getEntityManager().persist(entity);
companySessionId.setGroup(entity);
persistence.getEntityManager().persist(companySessionId);
tx.commit();
}
}
}
}
Besides I have to refresh the page to see the new group.
I’ve also tried with an AfterInsertEntityListener but the Session Attribute is not set.
It’s a proper exception caused by your code. You cannot commit the entity passed to the listener. Here the example of working code:
@Component(GroupEntityListener.NAME)
public class GroupEntityListener implements BeforeInsertEntityListener<Group> {
public static final String NAME = "sample_GroupEntityListener";
@Inject
private Metadata metadata;
@Inject
private UniqueNumbersAPI uniqueNumbers;
@Override
public void onBeforeInsert(Group entity, EntityManager entityManager) {
SessionAttribute sessionAttribute = metadata.create(SessionAttribute.class);
sessionAttribute.setName("someNumber");
sessionAttribute.setStringValue(Long.toString(uniqueNumbers.getNextNumber(GroupEntityListener.NAME)));
sessionAttribute.setDatatype(IntegerDatatype.NAME);
sessionAttribute.setGroup(entity);
entityManager.persist(sessionAttribute);
}
}
So, the main difference with your code is that I don’t commit the Group entity, but commit the SessionAttribute entity.
Pay attention that I set value for the Group attribute of the SessionAttribute entity, instead of adding session attributes to the group entity.
sessionAttribute.setGroup(entity);
After that, I persist the SessionAttribute entity:
entityManager.persist(sessionAttribute);
Also, I’ve noticed that you need the “next” number for every new group. If so, I would suggest you using UniqueNumbersAPI for that purpose. You can read more about Sequence Generation in the documentation.
Regards,
Gleb
@Component("girasole_NewGroupEntityListener")
public class NewGroupEntityListener implements BeforeInsertEntityListener<Group>, AfterInsertEntityListener<Group> {
@Inject
private Metadata metadata;
@Inject
private Persistence persistence;
private Integer maxCompanySessionId = 0;
private ExecutorService executorService = Executors.newFixedThreadPool(10);
@Override
public void onBeforeInsert(Group entity, EntityManager entityManager) {
}
@Override
public void onAfterInsert(Group entity, Connection connection) {
Group parent = entity.getParent();
if (parent.getName().equals("Parent_Group") && entity.getSessionAttributes().isEmpty()
&& entity.getName().contains("Template_Group_Name")) {
TypedQuery<Group> query = persistence.getEntityManager().createQuery(
"select g from sec$Group g", Group.class
);
List<Group> groups = query.getResultList();
for (Group group : groups) {
Set<SessionAttribute> attributes = group.getSessionAttributes();
for (SessionAttribute attribute : attributes) {
if (attribute.getName().equals("companySessionId") &! group.getName().equals(entity.getName())) {
Integer value = Integer.parseInt(attribute.getStringValue());
if (value > maxCompanySessionId) {
maxCompanySessionId = value;
}
}
}
}
updateSessionAttribute(entity, maxCompanySessionId + 1);
}
}
private void updateSessionAttribute(Group group, Integer value) {
executorService.submit(new SecurityContextAwareRunnable(() -> {
// some artificial delay
sleep();
// your business logic in a new transaction
Set<SessionAttribute> entitySessionAttributes = group.getSessionAttributes();
boolean create = true;
// this is never executed sessionAttributes is always empty
for (SessionAttribute sa : entitySessionAttributes) {
if (sa.getName().equals("companySessionId")) {
create = false;
sa.setStringValue(Integer.toString(maxCompanySessionId + 1));
try (Transaction tx = persistence.createTransaction()) {
persistence.getEntityManager().merge(sa);
tx.commit();
}
break;
}
}
if (create) {
SessionAttribute companySessionId = metadata.create(SessionAttribute.class);
companySessionId.setName("companySessionId");
companySessionId.setStringValue(Integer.toString(value));
companySessionId.setDatatype("int");
companySessionId.setGroup(group);
try (Transaction tx = persistence.createTransaction()) {
group.getSessionAttributes().add(companySessionId);
companySessionId.setGroup(group);
persistence.getEntityManager().persist(companySessionId);
tx.commit();
}
}
}));
}
private void sleep() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
Forget the BeforeInsertEntityListener part, there are some minor issues anyway:
Everything works fine when you create a new group from scratch, but when you copy a group from another, you get always an empty sessionAttributes Set in the Runnable context . Probably because the Group Entity status at onAfterInsert is the one you get before all the remaining attributes and FKs are set no matter how much I sleep().
Since you cannot check/update sessionAttributes this way, two Session Attributes with the same name("companySessionId") are created at the end of the copy transaction
Attempts to query for groups with EntityManager in the SecurityContextAwareRunnable hangs the system
I managed to solve the problem creating a "Template" Group with the intended sub-groups hierarchy but without the sessionAttribute.
With this:
if (parent.getName().equals("Parent_Group") && entity.getSessionAttributes().isEmpty()
&& entity.getName().contains("Template_Group_Name")) {
The companySessionId generation is automated only if the new group has a given parent and is copied from the Template Group.
Of course checking on the name only, does not prevent accidental Template copies-of-copies with double companySessionId, but this is the only thing I could get now…
So why isn't it possible to have the updated Session Attributes Set or put a listener at the end of the copy operation?
Also realted, why do I need a separate thread onAfterInsert and not onBeforeInsert?
Setting Session Atrribute onBeforeInsert and the UniqueNumbersAPI can save me a lot of work, thanks!
But what about the “copy” group problem? I think I’ll still have doubles in the Session Attribute Set
While copying, original session attributes are added to a new group
Due to Group copying logic onBeforeInsert is fired twice (We have created a YouTrack issue, see the link on the right.)
As the result, we have three session attributes with the same name for one instance.
For the first problem, you can create a custom realization of UserManagementServiceBean and change group copying logic to skip session attributes with a certain name.
For the second problem, you can check if there are session attributes with a certain name for the creating group, e.g.
@Override
public void onBeforeInsert(Group entity, EntityManager entityManager) {
// In case of https://youtrack.cuba-platform.com/issue/PL-9350
// we need to check if session attribute has been added
TypedQuery<SessionAttribute> query = persistence.getEntityManager().createQuery(
"select e from sec$SessionAttribute e where e.group.id = ?1 and e.name = ?2", SessionAttribute.class);
query.setParameter(1, entity.getId());
query.setParameter(2, "someNumber");
List<SessionAttribute> sessionAttributes = query.getResultList();
if (CollectionUtils.isEmpty(sessionAttributes)) {
SessionAttribute sessionAttribute = metadata.create(SessionAttribute.class);
sessionAttribute.setName("someNumber");
sessionAttribute.setStringValue(Long.toString(uniqueNumbers.getNextNumber(GroupEntityListener.NAME)));
sessionAttribute.setDatatype(IntegerDatatype.NAME);
sessionAttribute.setGroup(entity);
entityManager.persist(sessionAttribute);
}
}
I’ve created a project at GitHub. Take a loot at com.company.sample.listener.GroupEntityListener and com.company.sample.security.app.CustomUserManagementServiceBean.