Re: SCIM & Syncope : OptimisticLockException on user groups membership update
Thanks Francesco ! I've modified my code, but now I'm getting NullPointerException (on the second PUT request on /Group endpoint).This error isn't generated when we execute the request serially.It's generated when HTTP request are executed in parallel. i.e. PUT http://localhost:9091/groups/2564b3a7-0f5d-424d-a4b3-a70f5d624d80 => gives a 200PUT http://localhost:9091/groups/fad94db3-9245-449e-994d-b39245449e6a => gives a 500 My code is available in [1] The stacktrace is available in [2] The error in syncope is from JPAUserDAO (input is null) :public Collection findAllGroups(final User user) { return CollectionUtils.union( CollectionUtils.collect(user.getMemberships(), new Transformer() { @Override public Group transform(final UMembership input) { return input.getRightEnd(); } }, new ArrayList()), findDynGroupMemberships(user)); } [1] Here's exactly the code from the SCIM Provider side (calling Syncope REST API) : // This is the method called on PUT for Group endpoint public Group update(String id, Group group) { // 1. General group update GroupTO existingGroupTO = getGroupTOByKey(id); List usersOfThisGroup = getUsersForGroup(id); GroupTO groupTO = groupConverter.fromScim(group); groupTO.setKey(existingGroupTO.getKey()); GroupService groupService = syncopeClient.getService(GroupService.class); Response response; try { response = groupService.update(groupTO); } catch (SyncopeClientException ex) { throw syncopeExHandler.convertSyncopeGroupClientException(ex, group.getDisplayName(), ProvisioningOperation.UPDATING); } if (response == null || Response.Status.OK.getStatusCode() != response.getStatus()) { throw syncopeExHandler.convertSyncopeGroupErrorResponse(response, group.getDisplayName(), ProvisioningOperation.UPDATING); } // 2. General membership update List idsOfExistingUsers = usersOfThisGroup.stream().map(user -> user.getKey()).collect(Collectors.toList()); List idsOfNewUsers = group.getMembers().stream().map(member -> member.getValue()).collect(Collectors.toList()); for (MemberRef memberRef : group.getMembers()) { if (!idsOfExistingUsers.contains(memberRef.getValue())) { addUserToGroup(groupTO, memberRef.getValue()); } } for (String userId : idsOfExistingUsers) { if (!idsOfNewUsers.contains(userId)) { removeUserFromGroup(groupTO, userId); } } return getById(id); } private void addUserToGroup(GroupTO groupTO, String userId) { UserService userService = syncopeClient.getService(UserService.class); UserPatch userPatch = new UserPatch(); userPatch.setKey(userId); userPatch.getMemberships().add( new MembershipPatch.Builder().operation(PatchOperation.ADD_REPLACE).group(groupTO.getKey()).build()); try { userService.update(userPatch); } catch (SyncopeClientException e) { throw new SCIMException(String.format("User %s was not added to the group %s", userId, groupTO.getName()), e); } } private void removeUserFromGroup(GroupTO groupTO, String userId) { UserService userService = syncopeClient.getService(UserService.class); UserPatch userPatch = new UserPatch(); userPatch.setKey(userId); userPatch.getMemberships().add( new MembershipPatch.Builder().operation(PatchOperation.DELETE).group(groupTO.getKey()).build()); try { userService.update(userPatch); } catch (SyncopeClientException e) { throw new SCIMException(String.format("User %s was not removed from the group %s", userId, groupTO.getName()), e); } } [2] Stacktrace Sorry for the super long stack : 15:05:47.623 ERROR org.apache.syncope.core.rest.cxf.RestServiceExceptionMapper - Exception thrown java.lang.NullPointerException at org.apache.syncope.core.persistence.jpa.dao.JPAUserDAO$2.transform(JPAUserDAO.java:500) ~[syncope-core-persistence-jpa-2.0.0-SNAPSHOT.jar:2.0.0-SNAPSHOT] at org.apache.syncope.core.persistence.jpa.dao.JPAUserDAO$2.transform(JPAUserDAO.java:496) ~[syncope-core-persistence-jpa-2.0.0-SNAPSHOT.jar:2.0.0-SNAPSHOT] at org.apache.commons.collections4.CollectionUtils.collect(CollectionUtils.java:1077) ~[commons-collections4-4.1.jar:4.1] at org.apache.commons.collections4.CollectionUtils.collect(CollectionUtils.java:1049) ~[commons-collections4-4.1.jar:4.1] at org.apache.syncope.core.persistence.jpa.dao.JPAUserDAO.findAllGroups(JPAUserDAO.java:495) ~[syncope-core-persistence-jpa-2.0.0-SNAPSHOT.jar:2.0.0-SNAPSHOT] at
Re: SCIM & Syncope : OptimisticLockException on user groups membership update
On 27/09/2016 15:00, Adrian Gonzalez wrote: Hello, We're trying to build a POC on SCIM APIs on top of Syncope. That's very good to hear: looks it is for SCIM 2.0, correct? Looking forward to take a look at it! Problem is when we're using some basic SCIM APIs to update the groups membership of a given user, we got a OptimisticLockException. This is due to the fact that SCIM group membership can be updated only from the Group endpoint (not from the User endpoint). From https://tools.ietf.org/html/rfc7643#page-24 groups A list of groups to which the user belongs,... Since this attribute has a mutability of "readOnly", group membership changes MUST be applied via the "Group" Resource (Section 4.2). This attribute has a mutability of "readOnly". So, we have the following scenario : * we have a user1 member of 2 groups (group1 and group2). * we want to remove the user1 from both groups from a UI console. * the UI then needs to send 2 HTTP PUT on SCIM /Groups endpoint (one for each group). /PUT Groups/group1 /PUT Groups/group2 * we get a OptimisticLockException since both calls are made for a relation on the same user - because on the SCIM side for the Group endpoint, we must call userService.update(userTO) to update a user <-> group relation. i.e. MembershipTO membershipTO = new MembershipTO.Builder().group(userTO.getKey(), "USER").group(groupTO.getKey(), groupTO.getName()).build(); userTO.getMemberships().add(membershipTO); try { userService.update(userTO); } catch (SyncopeClientException e) { throw new SCIMException(String.format("User %s was not added to the group %s", userId, groupTO.getName()), e); } Is there an API to update user or group membership without testing @Version field ? (i.e like a syncope REST API on top of a jpql update ?) Perhaps we're not using the good API here (is there an API to handle membership from the group's side ? Do you see another possible solution (besides updating membership from the Group side/screen) ? You are right about the fact that Syncope manages groups memberships by modifying users. I am a bit confused about your snippet above, so let me rephrase it a bit. Assuming your scenario: * we have a user1 member of 2 groups (group1 and group2). * we want to remove the user1 from both groups from a UI console. * the UI then needs to send 2 HTTP PUT on SCIM /Groups endpoint (one for each group). /PUT Groups/group1 /PUT Groups/group2 the Group endpoint should perform as following: UserPatch userPatch = new UserPatch(); userPatch.setKey("the key of user1"); userPatch.getMemberships().add(new MembershipPatch.Builder(). operation(PatchOperation.DELETE). group("the key of the group passed to the endpoint"). build()); userLogic.update(userPatch, true); As you can see, I am rather using the logic layer (e.g. UserLogic as done in UserServiceImpl) to interact with Syncope core, rather than the external service layer (e.g. how REST clients do). HTH Regards. -- Francesco Chicchiriccò Tirasa - Open Source Excellence http://www.tirasa.net/ Member at The Apache Software Foundation Syncope, Cocoon, Olingo, CXF, OpenJPA, PonyMail http://home.apache.org/~ilgrosso/
SCIM & Syncope : OptimisticLockException on user groups membership update
Hello, We're trying to build a POC on SCIM APIs on top of Syncope.Problem is when we're using some basic SCIM APIs to update the groups membership of a given user, we got a OptimisticLockException. This is due to the fact that SCIM group membership can be updated only from the Group endpoint (not from the User endpoint).From https://tools.ietf.org/html/rfc7643#page-24 groups A list of groups to which the user belongs,... Since this attribute has a mutability of "readOnly", group membership changes MUST be applied via the "Group" Resource (Section 4.2). This attribute has a mutability of "readOnly". So, we have the following scenario : * we have a user1 member of 2 groups (group1 and group2). * we want to remove the user1 from both groups from a UI console. * the UI then needs to send 2 HTTP PUT on SCIM /Groups endpoint (one for each group). /PUT Groups/group1 /PUT Groups/group2 * we get a OptimisticLockException since both calls are made for a relation on the same user - because on the SCIM side for the Group endpoint, we must call userService.update(userTO) to update a user <-> group relation. i.e. MembershipTO membershipTO = new MembershipTO.Builder().group(userTO.getKey(), "USER").group(groupTO.getKey(), groupTO.getName()).build(); userTO.getMemberships().add(membershipTO); try { userService.update(userTO); } catch (SyncopeClientException e) { throw new SCIMException(String.format("User %s was not added to the group %s", userId, groupTO.getName()), e); } Is there an API to update user or group membership without testing @Version field ? (i.e like a syncope REST API on top of a jpql update ?) Perhaps we're not using the good API here (is there an API to handle membership from the group's side ? Do you see another possible solution (besides updating membership from the Group side/screen) ? Thanks,Adrian Sample stacktrace on client side :2016-09-27 09:31:46.065 DEBUG 1 --- [tp1754926770-22] o.s.web.client.RestTemplate : PUT request for "http://scim:/Groups/2564b3a7-0f5d-424d-a4b3-a70f5d624d80; resulted in 500 (Server Error); invoking error handler at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1061) [jetty-server-9.2.14.v20151106.jar!/:9.2.14.v20151106] at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141) [jetty-server-9.2.14.v20151106.jar!/:9.2.14.v20151106] at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:97) [jetty-server-9.2.14.v20151106.jar!/:9.2.14.v20151106] at org.eclipse.jetty.server.Server.handle(Server.java:499) [jetty-server-9.2.14.v20151106.jar!/:9.2.14.v20151106] at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:311) [jetty-server-9.2.14.v20151106.jar!/:9.2.14.v20151106] at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:257) [jetty-server-9.2.14.v20151106.jar!/:9.2.14.v20151106] at org.eclipse.jetty.io.AbstractConnection$2.run(AbstractConnection.java:544) [jetty-io-9.2.14.v20151106.jar!/:9.2.14.v20151106] at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:635) [jetty-util-9.2.14.v20151106.jar!/:9.2.14.v20151106] at org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:555) [jetty-util-9.2.14.v20151106.jar!/:9.2.14.v20151106] at java.lang.Thread.run(Thread.java:745) [na:1.8.0_92-internal] Caused by: org.apache.syncope.common.lib.SyncopeClientException: GenericPersistence [OptimisticLockException: An optimistic lock violation was detected when flushing object instance "org.apache.syncope.core.persistence.jpa.entity.user.JPAUPlainAttrValue-62f5f8bf-b390-4354-b5f8-bfb390a354e8" to the data store. This indicates that the object was concurrently modified in another transaction.] at org.apache.syncope.common.lib.SyncopeClientException.build(SyncopeClientException.java:37) ~[syncope-common-lib-2.0.0-SNAPSHOT.jar!/:2.0.0-SNAPSHOT] at org.apache.syncope.client.lib.RestClientExceptionMapper.checkSyncopeClientCompositeException(RestClientExceptionMapper.java:147) ~[syncope-client-lib-2.0.0-SNAPSHOT.jar!/:2.0.0-SNAPSHOT] at org.apache.syncope.client.lib.RestClientExceptionMapper.fromResponse(RestClientExceptionMapper.java:58) ~[syncope-client-lib-2.0.0-SNAPSHOT.jar!/:2.0.0-SNAPSHOT] at org.apache.syncope.client.lib.RestClientExceptionMapper.fromResponse(RestClientExceptionMapper.java:42) ~[syncope-client-lib-2.0.0-SNAPSHOT.jar!/:2.0.0-SNAPSHOT] at org.apache.cxf.jaxrs.client.ClientProxyImpl.checkResponse(ClientProxyImpl.java:306) ~[cxf-rt-rs-client-3.1.7.jar!/:3.1.7] 2016-09-27 09:31:46.068 DEBUG 1 --- [tp1754926770-22] .m.m.a.ExceptionHandlerExceptionResolver : Resolving exception from handler [public void