Re: SCIM & Syncope : OptimisticLockException on user groups membership update

2016-09-27 Thread Adrian Gonzalez
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

2016-09-27 Thread Francesco Chicchiriccò

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

2016-09-27 Thread Adrian Gonzalez
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