Hello Francesco,
I'm really really sorry, I switched to another subject and was quite busy. It's
always a pending task on my side, and I'll need to handle it in a very near
future.
De : Francesco Chicchiriccò <[email protected]>
À : [email protected]
Envoyé le : Mardi 8 novembre 2016 9h22
Objet : Re: SCIM & Syncope : OptimisticLockException on user groups membership
update
Hi Adrian,
I was wondering if you made any progress on this.
Regards.
On 28/09/2016 08:02, Francesco Chicchiriccò wrote:
On 27/09/2016 17:24, Adrian Gonzalez wrote:
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 suppose that the issue might come from the fact that you are essentially
performing concurrent modifications to the same entity (e.g. user).
i.e.
PUT http://localhost:9091/groups/2564b3a7-0f5d-424d-a4b3-a70f5d624d80 =>
gives a 200 PUT
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) :
This NPE is quite strange: two possible causes come to my mind:
1. there is some nasty transactional issue - even though I would have expected
user.getMemberships() to be null rather than such collection containing a null
membership
2. there is some null element in user.getMemberships(), possibly coming by the
fact that the first user modification is not committed yet
In order to better investigate this behaviour I would suggest to debug the
Syncope core application and to break in the findAllGroups() method to check if
user.getMemberships() effectively contains null (and possibly non-null)
elements.
Regards.
public Collection<Group> findAllGroups(final User user) {
return CollectionUtils.union(
CollectionUtils.collect(user.getMemberships(), new
Transformer<UMembership, Group>() {
@Override
public Group transform(final UMembership input) {
return input.getRightEnd();
}
}, new ArrayList<Group>()),
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<UserTO> 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<String> idsOfExistingUsers = usersOfThisGroup.stream().map(user
-> user.getKey()).collect(Collectors.toList());
List<String> 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(
newMembershipPatch.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(
newMembershipPatch.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
atorg.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]
atorg.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]
atorg.apache.commons.collections4.CollectionUtils.collect(CollectionUtils.java:1077)
~[commons-collections4-4.1.jar:4.1]
atorg.apache.commons.collections4.CollectionUtils.collect(CollectionUtils.java:1049)
~[commons-collections4-4.1.jar:4.1]
atorg.apache.syncope.core.persistence.jpa.dao.JPAUserDAO.findAllGroups(JPAUserDAO.java:495)~[syncope-core-persistence-jpa-2.0.0-SNAPSHOT.jar:2.0.0-SNAPSHOT]
atorg.apache.syncope.core.persistence.jpa.dao.JPAUserDAO.findAllResources(JPAUserDAO.java:529)~[syncope-core-persistence-jpa-2.0.0-SNAPSHOT.jar:2.0.0-SNAPSHOT]
at sun.reflect.GeneratedMethodAccessor124.invoke(Unknown Source) ~[?:?]
atsun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
~[?:1.8.0_91]
at java.lang.reflect.Method.invoke(Method.java:498) ~[?:1.8.0_91]
atorg.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:333)
~[spring-aop-4.3.2.RELEASE.jar:4.3.2.RELEASE]
atorg.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:190)
~[spring-aop-4.3.2.RELEASE.jar:4.3.2.RELEASE]
atorg.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)
~[spring-aop-4.3.2.RELEASE.jar:4.3.2.RELEASE]
atorg.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:99)
~[spring-tx-4.3.2.RELEASE.jar:4.3.2.RELEASE]
atorg.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:280)
~[spring-tx-4.3.2.RELEASE.jar:4.3.2.RELEASE]
]
atorg.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
~[spring-tx-4.3.2.RELEASE.jar:4.3.2.RELEASE--More--
atorg.apache.syncope.core.persistence.jpa.spring.DomainTransactionInterceptor.invoke(DomainTransactionInterceptor.java:64)~[syncope-core-persistence-jpa-2.0.0-SNAPSHOT.jar:2.0.0-SNAPSHOT]
atorg.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
~[spring-aop-4.3.2.RELEASE.jar:4.3.2.RELEASE]
atorg.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:213)
~[spring-aop-4.3.2.RELEASE.jar:4.3.2.RELEASE]
at com.sun.proxy.$Proxy172.findAllResources(Unknown Source) ~[?:?]
atorg.apache.syncope.core.provisioning.java.propagation.PropagationManagerImpl.createTasks(PropagationManagerImpl.java:345)~[syncope-core-provisioning-java-2.0.0-SNAPSHOT.jar:2.0.0-SNAPSHOT]
atorg.apache.syncope.core.provisioning.java.propagation.PropagationManagerImpl.getUpdateTasks(PropagationManagerImpl.java:275)~[syncope-core-provisioning-java-2.0.0-SNAPSHOT.jar:2.0.0-SNAPSHOT]
atorg.apache.syncope.core.provisioning.java.propagation.PropagationManagerImpl.getUserUpdateTasks(PropagationManagerImpl.java:204)~[syncope-core-provisioning-java-2.0.0-SNAPSHOT.jar:2.0.0-SNAPSHOT]
atorg.apache.syncope.core.provisioning.java.propagation.PropagationManagerImpl.getUserUpdateTasks(PropagationManagerImpl.java:224)~[syncope-core-provisioning-java-2.0.0-SNAPSHOT.jar:2.0.0-SNAPSHOT]
_91] at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
~[?:1.8.0--More--
atsun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
~[?:1.8.0_91]
atsun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
~[?:1.8.0_91]
at java.lang.reflect.Method.invoke(Method.java:498) ~[?:1.8.0_91]
atorg.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:333)
~[spring-aop-4.3.2.RELEASE.jar:4.3.2.RELEASE]
atorg.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:190)
~[spring-aop-4.3.2.RELEASE.jar:4.3.2.RELEASE]
atorg.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)
~[spring-aop-4.3.2.RELEASE.jar:4.3.2.RELEASE]
atorg.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:99)
~[spring-tx-4.3.2.RELEASE.jar:4.3.2.RELEASE]
atorg.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:280)
~[spring-tx-4.3.2.RELEASE.jar:4.3.2.RELEASE]
atorg.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
~[spring-tx-4.3.2.RELEASE.jar:4.3.2.RELEASE]
atorg.apache.syncope.core.persistence.jpa.spring.DomainTransactionInterceptor.invoke(DomainTransactionInterceptor.java:64)~[syncope-core-persistence-jpa-2.0.0-SNAPSHOT.jar:2.0.0-SNAPSHOT]
atorg.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
~[spring-aop-4.3.2.RELEASE.jar:4.3.2.RELEASE]
atorg.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:213)
~[spring-aop-4.3.2.RELEASE.jar:4.3.2.RELEASE]
at com.sun.proxy.$Proxy209.getUserUpdateTasks(Unknown Source) ~[?:?]
atorg.apache.syncope.core.provisioning.java.DefaultUserProvisioningManager.update(DefaultUserProvisioningManager.java:123)~[syncope-core-provisioning-java-2.0.0-SNAPSHOT.jar:2.0.0-SNAPSHOT]
atorg.apache.syncope.core.provisioning.java.DefaultUserProvisioningManager.update(DefaultUserProvisioningManager.java:57)~[syncope-core-provisioning-java-2.0.0-SNAPSHOT.jar:2.0.0-SNAPSHOT]
at org.apache.syncope.core.logic.UserLogic.doUpdate(UserLogic.java:232)
~[syncope-core-logic-2.0.0-SNAPSHOT.jar:2.0.0-SNAPSHOT]
at org.apache.syncope.core.logic.UserLogic.update(UserLogic.java:213)
~[syncope-core-logic-2.0.0-SNAPSHOT.jar:2.0.0-SNAPSHOT]
at org.apache.syncope.core.logic.UserLogic.update(UserLogic.java:68)
~[syncope-core-logic-2.0.0-SNAPSHOT.jar:2.0.0-SNAPSHOT]
at
org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)~[spring-core-4.3.2.RELEASE.jar:4.3.2.RELEASE]0-SNAPSHOT.jar:2.0.0-SNAPSHOT]--More--
atorg.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:720)
~[spring-aop-4.3.2.RELEASE.jar:4.3.2.RELEASE]
atorg.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)
~[spring-aop-4.3.2.RELEASE.jar:4.3.2.RELEASE]
atorg.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint.proceed(MethodInvocationProceedingJoinPoint.java:85)
~[spring-aop-4.3.2.RELEASE.jar:4.3.2.RELEASE]
atorg.apache.syncope.core.logic.LogicInvocationHandler.around(LogicInvocationHandler.java:72)
~[syncope-core-logic-2.0.0-SNAPSHOT.jar:2.0.0-SNAPSHOT]
at sun.reflect.GeneratedMethodAccessor133.invoke(Unknown Source) ~[?:?]
atsun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
~[?:1.8.0_91]
at java.lang.reflect.Method.invoke(Method.java:498) ~[?:1.8.0_91]
atorg.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:629)
~[spring-aop-4.3.2.RELEASE.jar:4.3.2.RELEASE]
atorg.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:618)
~[spring-aop-4.3.2.RELEASE.jar:4.3.2.RELEASE]
atorg.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:70)
~[spring-aop-4.3.2.RELEASE.jar:4.3.2.RELEASE]
atorg.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
~[spring-aop-4.3.2.RELEASE.jar:4.3.2.RELEASE]
atorg.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor.invoke(MethodSecurityInterceptor.java:69)
~[spring-security-core-4.1.3.RELEASE.jar:4.1.3.RELEASE]
atorg.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
~[spring-aop-4.3.2.RELEASE.jar:4.3.2.RELEASE]
atorg.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92)
~[spring-aop-4.3.2.RELEASE.jar:4.3.2.RELEASE]
atorg.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
~[spring-aop-4.3.2.RELEASE.jar:4.3.2.RELEASE]
atorg.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:655)
~[spring-aop-4.3.2.RELEASE.jar:4.3.2.RELEASE]
atorg.apache.syncope.core.logic.UserLogic$$EnhancerBySpringCGLIB$$f6ff0886.update(<generated>)
~[syncope-core-logic-2.0.0-SNAPSHOT.jar:2.0.0-SNAPSHOT]
stractAnyService.java:159)
~[syncope-core-rest-cxf-2.0.0-SNAPSHOT.jar:2.0.0-SNAPSHOT]e--
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
~[?:1.8.0_91]
atsun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
~[?:1.8.0_91]
atsun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
~[?:1.8.0_91]
at java.lang.reflect.Method.invoke(Method.java:498) ~[?:1.8.0_91]
atorg.apache.cxf.service.invoker.AbstractInvoker.performInvocation(AbstractInvoker.java:180)
~[cxf-core-3.1.7.jar:3.1.7]
atorg.apache.cxf.service.invoker.AbstractInvoker.invoke(AbstractInvoker.java:96)
~[cxf-core-3.1.7.jar:3.1.7]
at org.apache.cxf.jaxrs.JAXRSInvoker.invoke(JAXRSInvoker.java:189)
~[cxf-rt-frontend-jaxrs-3.1.7.jar:3.1.7]
at org.apache.cxf.jaxrs.JAXRSInvoker.invoke(JAXRSInvoker.java:99)
~[cxf-rt-frontend-jaxrs-3.1.7.jar:3.1.7]
atorg.apache.cxf.interceptor.ServiceInvokerInterceptor$1.run(ServiceInvokerInterceptor.java:59)
~[cxf-core-3.1.7.jar:3.1.7]
atorg.apache.cxf.interceptor.ServiceInvokerInterceptor.handleMessage(ServiceInvokerInterceptor.java:96)
~[cxf-core-3.1.7.jar:3.1.7]
atorg.apache.cxf.phase.PhaseInterceptorChain.doIntercept(PhaseInterceptorChain.java:308)
~[cxf-core-3.1.7.jar:3.1.7]
atorg.apache.cxf.transport.ChainInitiationObserver.onMessage(ChainInitiationObserver.java:121)
~[cxf-core-3.1.7.jar:3.1.7]
atorg.apache.cxf.transport.http.AbstractHTTPDestination.invoke(AbstractHTTPDestination.java:254)
~[cxf-rt-transports-http-3.1.7.jar:3.1.7]
atorg.apache.cxf.transport.servlet.ServletController.invokeDestination(ServletController.java:234)
~[cxf-rt-transports-http-3.1.7.jar:3.1.7]
atorg.apache.cxf.transport.servlet.ServletController.invoke(ServletController.java:208)
~[cxf-rt-transports-http-3.1.7.jar:3.1.7]
atorg.apache.cxf.transport.servlet.ServletController.invoke(ServletController.java:160)
~[cxf-rt-transports-http-3.1.7.jar:3.1.7]
atorg.apache.cxf.transport.servlet.CXFNonSpringServlet.invoke(CXFNonSpringServlet.java:180)
~[cxf-rt-transports-http-3.1.7.jar:3.1.7]
atorg.apache.cxf.transport.servlet.AbstractHTTPServlet.handleRequest(AbstractHTTPServlet.java:299)
~[cxf-rt-transports-http-3.1.7.jar:3.1.7]
atorg.apache.cxf.transport.servlet.AbstractHTTPServlet.service(AbstractHTTPServlet.java:276)
~[cxf-rt-transports-http-3.1.7.jar:3.1.7]
atorg.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:292)
~[catalina.jar:8.0.35]
atorg.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207)
~[catalina.jar:8.0.35]
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
~[tomcat-websocket.jar:8.0.35]
icationFilterChain.java:240)
~[catalina.jar:8.0.35]erChain.internalDoFilter(Appl--More--
atorg.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207)
~[catalina.jar:8.0.35]
atorg.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:317)
~[spring-security-web-4.1.3.RELEASE.jar:4.1.3.RELEASE]
atorg.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:127)
~[spring-security-web-4.1.3.RELEASE.jar:4.1.3.RELEASE]
atorg.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:91)
~[spring-security-web-4.1.3.RELEASE.jar:4.1.3.RELEASE]
atorg.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
~[spring-security-web-4.1.3.RELEASE.jar:4.1.3.RELEASE]
atorg.apache.syncope.core.spring.security.MustChangePasswordFilter.doFilter(MustChangePasswordFilter.java:77)
~[syncope-core-spring-2.0.0-SNAPSHOT.jar:2.0.0-SNAPSHOT]
atorg.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
~[spring-security-web-4.1.3.RELEASE.jar:4.1.3.RELEASE]
jar:4.1.3.RELEASE]ingframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:115)
~[spring-security-web-4.1.3.RELEASE.--More--
atorg.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
~[spring-security-web-4.1.3.RELEASE.jar:4.1.3.RELEASE]
atorg.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:137)
~[spring-security-web-4.1.3.RELEASE.jar:4.1.3.RELEASE]
atorg.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
~[spring-security-web-4.1.3.RELEASE.jar:4.1.3.RELEASE]
atorg.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:111)
~[spring-security-web-4.1.3.RELEASE.jar:4.1.3.RELEASE]
atorg.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
~[spring-security-web-4.1.3.RELEASE.jar:4.1.3.RELEASE]
atorg.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:169)
~[spring-security-web-4.1.3.RELEASE.jar:4.1.3.RELEASE]
atorg.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
~[spring-security-web-4.1.3.RELEASE.jar:4.1.3.RELEASE]
.doFilter(RequestCacheAwareFilter.java:63)
~[spring-security-web-4.1.3.RELEASE.jar:4.1.3.RELEASE]
atorg.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
~[spring-security-web-4.1.3.RELEASE.jar:4.1.3.RELEASE]
atorg.springframework.security.web.authentication.www.BasicAuthenticationFilter.doFilterInternal(BasicAuthenticationFilter.java:215)
~[spring-security-web-4.1.3.RELEASE.jar:4.1.3.RELEASE]
atorg.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
~[spring-web-4.3.2.RELEASE.jar:4.3.2.RELEASE]
atorg.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
~[spring-security-web-4.1.3.RELEASE.jar:4.1.3.RELEASE]http://localhost:9091/groups/fad94db3-9245-449e-994d-b39245449e
De : Francesco Chicchiriccò <[email protected]>
À : [email protected]
Envoyé le : Mardi 27 septembre 2016 15h20
Objet : 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/