View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.syncope.core.provisioning.java.data;
20  
21  import java.text.ParseException;
22  import java.util.ArrayList;
23  import java.util.Collection;
24  import java.util.HashMap;
25  import java.util.List;
26  import java.util.Map;
27  import java.util.Objects;
28  import java.util.Optional;
29  import java.util.Set;
30  import java.util.stream.Collectors;
31  import org.apache.commons.lang3.StringUtils;
32  import org.apache.commons.lang3.tuple.Pair;
33  import org.apache.syncope.common.lib.Attr;
34  import org.apache.syncope.common.lib.SyncopeClientCompositeException;
35  import org.apache.syncope.common.lib.SyncopeClientException;
36  import org.apache.syncope.common.lib.request.AnyCR;
37  import org.apache.syncope.common.lib.request.AnyUR;
38  import org.apache.syncope.common.lib.request.AttrPatch;
39  import org.apache.syncope.common.lib.request.StringPatchItem;
40  import org.apache.syncope.common.lib.to.AnyTO;
41  import org.apache.syncope.common.lib.to.ConnObject;
42  import org.apache.syncope.common.lib.to.MembershipTO;
43  import org.apache.syncope.common.lib.to.Provision;
44  import org.apache.syncope.common.lib.to.RelationshipTO;
45  import org.apache.syncope.common.lib.types.AttrSchemaType;
46  import org.apache.syncope.common.lib.types.ClientExceptionType;
47  import org.apache.syncope.common.lib.types.PatchOperation;
48  import org.apache.syncope.common.lib.types.ResourceOperation;
49  import org.apache.syncope.core.persistence.api.attrvalue.validation.InvalidPlainAttrValueException;
50  import org.apache.syncope.core.persistence.api.attrvalue.validation.PlainAttrValidationManager;
51  import org.apache.syncope.core.persistence.api.dao.AllowedSchemas;
52  import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO;
53  import org.apache.syncope.core.persistence.api.dao.AnyTypeClassDAO;
54  import org.apache.syncope.core.persistence.api.dao.AnyTypeDAO;
55  import org.apache.syncope.core.persistence.api.dao.ExternalResourceDAO;
56  import org.apache.syncope.core.persistence.api.dao.GroupDAO;
57  import org.apache.syncope.core.persistence.api.dao.PlainAttrDAO;
58  import org.apache.syncope.core.persistence.api.dao.PlainAttrValueDAO;
59  import org.apache.syncope.core.persistence.api.dao.PlainSchemaDAO;
60  import org.apache.syncope.core.persistence.api.dao.RealmDAO;
61  import org.apache.syncope.core.persistence.api.dao.RelationshipTypeDAO;
62  import org.apache.syncope.core.persistence.api.dao.UserDAO;
63  import org.apache.syncope.core.persistence.api.entity.Any;
64  import org.apache.syncope.core.persistence.api.entity.AnyTypeClass;
65  import org.apache.syncope.core.persistence.api.entity.AnyUtils;
66  import org.apache.syncope.core.persistence.api.entity.AnyUtilsFactory;
67  import org.apache.syncope.core.persistence.api.entity.DerSchema;
68  import org.apache.syncope.core.persistence.api.entity.EntityFactory;
69  import org.apache.syncope.core.persistence.api.entity.ExternalResource;
70  import org.apache.syncope.core.persistence.api.entity.GroupablePlainAttr;
71  import org.apache.syncope.core.persistence.api.entity.GroupableRelatable;
72  import org.apache.syncope.core.persistence.api.entity.Membership;
73  import org.apache.syncope.core.persistence.api.entity.PlainAttr;
74  import org.apache.syncope.core.persistence.api.entity.PlainAttrValue;
75  import org.apache.syncope.core.persistence.api.entity.PlainSchema;
76  import org.apache.syncope.core.persistence.api.entity.Realm;
77  import org.apache.syncope.core.persistence.api.entity.VirSchema;
78  import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject;
79  import org.apache.syncope.core.provisioning.api.AccountGetter;
80  import org.apache.syncope.core.provisioning.api.DerAttrHandler;
81  import org.apache.syncope.core.provisioning.api.IntAttrName;
82  import org.apache.syncope.core.provisioning.api.IntAttrNameParser;
83  import org.apache.syncope.core.provisioning.api.MappingManager;
84  import org.apache.syncope.core.provisioning.api.PlainAttrGetter;
85  import org.apache.syncope.core.provisioning.api.PropagationByResource;
86  import org.apache.syncope.core.provisioning.api.VirAttrHandler;
87  import org.apache.syncope.core.provisioning.api.jexl.JexlUtils;
88  import org.apache.syncope.core.provisioning.java.pushpull.OutboundMatcher;
89  import org.apache.syncope.core.provisioning.java.utils.ConnObjectUtils;
90  import org.apache.syncope.core.provisioning.java.utils.MappingUtils;
91  import org.identityconnectors.framework.common.objects.Attribute;
92  import org.identityconnectors.framework.common.objects.AttributeBuilder;
93  import org.identityconnectors.framework.common.objects.ConnectorObject;
94  import org.identityconnectors.framework.common.objects.ConnectorObjectBuilder;
95  import org.identityconnectors.framework.common.objects.Uid;
96  import org.slf4j.Logger;
97  import org.slf4j.LoggerFactory;
98  
99  abstract class AbstractAnyDataBinder {
100 
101     protected static final Logger LOG = LoggerFactory.getLogger(AbstractAnyDataBinder.class);
102 
103     protected final AnyTypeDAO anyTypeDAO;
104 
105     protected final RealmDAO realmDAO;
106 
107     protected final AnyTypeClassDAO anyTypeClassDAO;
108 
109     protected final AnyObjectDAO anyObjectDAO;
110 
111     protected final UserDAO userDAO;
112 
113     protected final GroupDAO groupDAO;
114 
115     protected final PlainSchemaDAO plainSchemaDAO;
116 
117     protected final PlainAttrDAO plainAttrDAO;
118 
119     protected final PlainAttrValueDAO plainAttrValueDAO;
120 
121     protected final ExternalResourceDAO resourceDAO;
122 
123     protected final RelationshipTypeDAO relationshipTypeDAO;
124 
125     protected final EntityFactory entityFactory;
126 
127     protected final AnyUtilsFactory anyUtilsFactory;
128 
129     protected final DerAttrHandler derAttrHandler;
130 
131     protected final VirAttrHandler virAttrHandler;
132 
133     protected final MappingManager mappingManager;
134 
135     protected final IntAttrNameParser intAttrNameParser;
136 
137     protected final OutboundMatcher outboundMatcher;
138 
139     protected final PlainAttrValidationManager validator;
140 
141     protected AbstractAnyDataBinder(
142             final AnyTypeDAO anyTypeDAO,
143             final RealmDAO realmDAO,
144             final AnyTypeClassDAO anyTypeClassDAO,
145             final AnyObjectDAO anyObjectDAO,
146             final UserDAO userDAO,
147             final GroupDAO groupDAO,
148             final PlainSchemaDAO plainSchemaDAO,
149             final PlainAttrDAO plainAttrDAO,
150             final PlainAttrValueDAO plainAttrValueDAO,
151             final ExternalResourceDAO resourceDAO,
152             final RelationshipTypeDAO relationshipTypeDAO,
153             final EntityFactory entityFactory,
154             final AnyUtilsFactory anyUtilsFactory,
155             final DerAttrHandler derAttrHandler,
156             final VirAttrHandler virAttrHandler,
157             final MappingManager mappingManager,
158             final IntAttrNameParser intAttrNameParser,
159             final OutboundMatcher outboundMatcher,
160             final PlainAttrValidationManager validator) {
161 
162         this.anyTypeDAO = anyTypeDAO;
163         this.realmDAO = realmDAO;
164         this.anyTypeClassDAO = anyTypeClassDAO;
165         this.anyObjectDAO = anyObjectDAO;
166         this.userDAO = userDAO;
167         this.groupDAO = groupDAO;
168         this.plainSchemaDAO = plainSchemaDAO;
169         this.plainAttrDAO = plainAttrDAO;
170         this.plainAttrValueDAO = plainAttrValueDAO;
171         this.resourceDAO = resourceDAO;
172         this.relationshipTypeDAO = relationshipTypeDAO;
173         this.entityFactory = entityFactory;
174         this.anyUtilsFactory = anyUtilsFactory;
175         this.derAttrHandler = derAttrHandler;
176         this.virAttrHandler = virAttrHandler;
177         this.mappingManager = mappingManager;
178         this.intAttrNameParser = intAttrNameParser;
179         this.outboundMatcher = outboundMatcher;
180         this.validator = validator;
181     }
182 
183     protected void setRealm(final Any<?> any, final AnyUR anyUR) {
184         if (anyUR.getRealm() != null && StringUtils.isNotBlank(anyUR.getRealm().getValue())) {
185             Realm newRealm = realmDAO.findByFullPath(anyUR.getRealm().getValue());
186             if (newRealm == null) {
187                 LOG.debug("Invalid realm specified: {}, ignoring", anyUR.getRealm().getValue());
188             } else {
189                 any.setRealm(newRealm);
190             }
191         }
192     }
193 
194     protected Map<String, ConnObject> onResources(
195             final Any<?> any,
196             final Collection<String> resources,
197             final String password,
198             final boolean changePwd) {
199 
200         Map<String, ConnObject> onResources = new HashMap<>();
201 
202         resources.stream().map(resourceDAO::find).filter(Objects::nonNull).forEach(resource -> {
203             resource.getProvisionByAnyType(any.getType().getKey()).
204                     ifPresent(provision -> MappingUtils.getConnObjectKeyItem(provision).ifPresent(keyItem -> {
205 
206                 Pair<String, Set<Attribute>> prepared = mappingManager.prepareAttrsFromAny(
207                         any, password, changePwd, true, resource, provision);
208 
209                 ConnObject connObjectTO;
210                 if (StringUtils.isBlank(prepared.getLeft())) {
211                     connObjectTO = ConnObjectUtils.getConnObjectTO(null, prepared.getRight());
212                 } else {
213                     ConnectorObject connectorObject = new ConnectorObjectBuilder().
214                             addAttributes(prepared.getRight()).
215                             addAttribute(new Uid(prepared.getLeft())).
216                             addAttribute(AttributeBuilder.build(keyItem.getExtAttrName(), prepared.getLeft())).
217                             build();
218 
219                     connObjectTO = ConnObjectUtils.getConnObjectTO(
220                             outboundMatcher.getFIQL(connectorObject, resource, provision),
221                             connectorObject.getAttributes());
222                 }
223 
224                 onResources.put(resource.getKey(), connObjectTO);
225             }));
226         });
227 
228         return onResources;
229     }
230 
231     protected PlainSchema getPlainSchema(final String schemaName) {
232         PlainSchema schema = null;
233         if (StringUtils.isNotBlank(schemaName)) {
234             schema = plainSchemaDAO.find(schemaName);
235 
236             // safely ignore invalid schemas from Attr
237             if (schema == null) {
238                 LOG.debug("Ignoring invalid schema {}", schemaName);
239             } else if (schema.isReadonly()) {
240                 schema = null;
241                 LOG.debug("Ignoring readonly schema {}", schemaName);
242             }
243         }
244 
245         return schema;
246     }
247 
248     protected void fillAttr(
249             final List<String> values,
250             final AnyUtils anyUtils,
251             final PlainSchema schema,
252             final PlainAttr<?> attr,
253             final SyncopeClientException invalidValues) {
254 
255         // if schema is multivalue, all values are considered for addition;
256         // otherwise only the fist one - if provided - is considered
257         List<String> valuesProvided = schema.isMultivalue()
258                 ? values
259                 : (values.isEmpty() || values.get(0) == null
260                 ? List.of()
261                 : List.of(values.get(0)));
262 
263         valuesProvided.forEach(value -> {
264             if (StringUtils.isBlank(value)) {
265                 LOG.debug("Null value for {}, ignoring", schema.getKey());
266             } else {
267                 try {
268                     attr.add(validator, value, anyUtils);
269                 } catch (InvalidPlainAttrValueException e) {
270                     LOG.warn("Invalid value for attribute {}: {}",
271                             schema.getKey(), StringUtils.abbreviate(value, 20), e);
272 
273                     invalidValues.getElements().add(schema.getKey() + ": " + value + " - " + e.getMessage());
274                 }
275             }
276         });
277     }
278 
279     protected List<String> evaluateMandatoryCondition(
280             final ExternalResource resource, final Provision provision, final Any<?> any) {
281 
282         List<String> missingAttrNames = new ArrayList<>();
283 
284         MappingUtils.getPropagationItems(provision.getMapping().getItems().stream()).forEach(mapItem -> {
285             IntAttrName intAttrName = null;
286             try {
287                 intAttrName = intAttrNameParser.parse(mapItem.getIntAttrName(), any.getType().getKind());
288             } catch (ParseException e) {
289                 LOG.error("Invalid intAttrName '{}', ignoring", mapItem.getIntAttrName(), e);
290             }
291             if (intAttrName != null && intAttrName.getSchema() != null) {
292                 AttrSchemaType schemaType = intAttrName.getSchema() instanceof PlainSchema
293                         ? intAttrName.getSchema().getType()
294                         : AttrSchemaType.String;
295 
296                 Pair<AttrSchemaType, List<PlainAttrValue>> intValues = mappingManager.getIntValues(
297                         resource,
298                         provision,
299                         mapItem,
300                         intAttrName,
301                         schemaType,
302                         any,
303                         AccountGetter.DEFAULT,
304                         PlainAttrGetter.DEFAULT);
305                 if (intValues.getRight().isEmpty()
306                         && JexlUtils.evaluateMandatoryCondition(mapItem.getMandatoryCondition(), any, derAttrHandler)) {
307 
308                     missingAttrNames.add(mapItem.getIntAttrName());
309                 }
310             }
311         });
312 
313         return missingAttrNames;
314     }
315 
316     private SyncopeClientException checkMandatoryOnResources(
317             final Any<?> any, final Collection<? extends ExternalResource> resources) {
318 
319         SyncopeClientException reqValMissing = SyncopeClientException.build(ClientExceptionType.RequiredValuesMissing);
320 
321         resources.forEach(resource -> {
322             Optional<Provision> provision = resource.getProvisionByAnyType(any.getType().getKey());
323             if (resource.isEnforceMandatoryCondition() && provision.isPresent()) {
324                 List<String> missingAttrNames = evaluateMandatoryCondition(resource, provision.get(), any);
325                 if (!missingAttrNames.isEmpty()) {
326                     LOG.error("Mandatory schemas {} not provided with values", missingAttrNames);
327 
328                     reqValMissing.getElements().addAll(missingAttrNames);
329                 }
330             }
331         });
332 
333         return reqValMissing;
334     }
335 
336     private void checkMandatory(
337             final PlainSchema schema,
338             final PlainAttr<?> attr,
339             final Any<?> any,
340             final SyncopeClientException reqValMissing) {
341 
342         if (attr == null
343                 && !schema.isReadonly()
344                 && JexlUtils.evaluateMandatoryCondition(schema.getMandatoryCondition(), any, derAttrHandler)) {
345 
346             LOG.error("Mandatory schema " + schema.getKey() + " not provided with values");
347 
348             reqValMissing.getElements().add(schema.getKey());
349         }
350     }
351 
352     private SyncopeClientException checkMandatory(final Any<?> any, final AnyUtils anyUtils) {
353         SyncopeClientException reqValMissing = SyncopeClientException.build(ClientExceptionType.RequiredValuesMissing);
354 
355         // Check if there is some mandatory schema defined for which no value has been provided
356         AllowedSchemas<PlainSchema> allowedPlainSchemas = anyUtils.dao().findAllowedSchemas(any, PlainSchema.class);
357         allowedPlainSchemas.getForSelf().forEach(schema -> checkMandatory(
358                 schema, any.getPlainAttr(schema.getKey()).orElse(null), any, reqValMissing));
359         if (any instanceof GroupableRelatable) {
360             allowedPlainSchemas.getForMemberships().forEach((group, schemas) -> {
361                 GroupableRelatable<?, ?, ?, ?, ?> groupable = GroupableRelatable.class.cast(any);
362                 Membership<?> membership = groupable.getMembership(group.getKey()).orElse(null);
363                 schemas.forEach(schema -> checkMandatory(
364                         schema,
365                         groupable.getPlainAttr(schema.getKey(), membership).orElse(null),
366                         any,
367                         reqValMissing));
368             });
369         }
370 
371         return reqValMissing;
372     }
373 
374     @SuppressWarnings({ "unchecked", "rawtypes" })
375     protected void processAttrPatch(
376             final Any any,
377             final AttrPatch patch,
378             final PlainSchema schema,
379             final PlainAttr<?> attr,
380             final AnyUtils anyUtils,
381             final SyncopeClientException invalidValues) {
382 
383         switch (patch.getOperation()) {
384             case ADD_REPLACE:
385                 // 1.1 remove values
386                 if (attr.getSchema().isUniqueConstraint()) {
387                     if (attr.getUniqueValue() != null
388                             && !patch.getAttr().getValues().isEmpty()
389                             && !patch.getAttr().getValues().get(0).equals(attr.getUniqueValue().getValueAsString())) {
390 
391                         plainAttrValueDAO.deleteAll(attr, anyUtils);
392                     }
393                 } else {
394                     plainAttrValueDAO.deleteAll(attr, anyUtils);
395                 }
396 
397                 // 1.2 add values
398                 List<String> valuesToBeAdded = patch.getAttr().getValues();
399                 if (!valuesToBeAdded.isEmpty()
400                         && (!schema.isUniqueConstraint() || attr.getUniqueValue() == null
401                         || !valuesToBeAdded.get(0).equals(attr.getUniqueValue().getValueAsString()))) {
402 
403                     fillAttr(valuesToBeAdded, anyUtils, schema, attr, invalidValues);
404                 }
405 
406                 // if no values are in, the attribute can be safely removed
407                 if (attr.getValuesAsStrings().isEmpty()) {
408                     plainAttrDAO.delete(attr);
409                 }
410                 break;
411 
412             case DELETE:
413             default:
414                 any.remove(attr);
415                 plainAttrDAO.delete(attr);
416         }
417     }
418 
419     @SuppressWarnings({ "unchecked", "rawtypes" })
420     protected void fill(
421             final Any any,
422             final AnyUR anyUR,
423             final AnyUtils anyUtils,
424             final SyncopeClientCompositeException scce) {
425 
426         // 1. anyTypeClasses
427         for (StringPatchItem patch : anyUR.getAuxClasses()) {
428             AnyTypeClass auxClass = anyTypeClassDAO.find(patch.getValue());
429             if (auxClass == null) {
430                 LOG.debug("Invalid " + AnyTypeClass.class.getSimpleName() + " {}, ignoring...", patch.getValue());
431             } else {
432                 switch (patch.getOperation()) {
433                     case ADD_REPLACE:
434                         any.add(auxClass);
435                         break;
436 
437                     case DELETE:
438                     default:
439                         any.getAuxClasses().remove(auxClass);
440                 }
441             }
442         }
443 
444         // 2. resources
445         for (StringPatchItem patch : anyUR.getResources()) {
446             ExternalResource resource = resourceDAO.find(patch.getValue());
447             if (resource == null) {
448                 LOG.debug("Invalid " + ExternalResource.class.getSimpleName() + " {}, ignoring...", patch.getValue());
449             } else {
450                 switch (patch.getOperation()) {
451                     case ADD_REPLACE:
452                         any.add(resource);
453                         break;
454 
455                     case DELETE:
456                     default:
457                         any.getResources().remove(resource);
458                 }
459             }
460         }
461 
462         Set<ExternalResource> resources = anyUtils.getAllResources(any);
463         SyncopeClientException invalidValues = SyncopeClientException.build(ClientExceptionType.InvalidValues);
464 
465         // 3. plain attributes
466         anyUR.getPlainAttrs().stream().filter(patch -> patch.getAttr() != null).forEach(patch -> {
467             PlainSchema schema = getPlainSchema(patch.getAttr().getSchema());
468             if (schema == null) {
469                 LOG.debug("Invalid " + PlainSchema.class.getSimpleName() + " {}, ignoring...",
470                         patch.getAttr().getSchema());
471             } else {
472                 PlainAttr<?> attr = (PlainAttr<?>) any.getPlainAttr(schema.getKey()).orElse(null);
473                 if (attr == null) {
474                     LOG.debug("No plain attribute found for schema {}", schema);
475 
476                     if (patch.getOperation() == PatchOperation.ADD_REPLACE) {
477                         attr = anyUtils.newPlainAttr();
478                         ((PlainAttr) attr).setOwner(any);
479                         attr.setSchema(schema);
480                         any.add(attr);
481                     }
482                 }
483                 if (attr != null) {
484                     processAttrPatch(any, patch, schema, attr, anyUtils, invalidValues);
485                 }
486             }
487         });
488         if (!invalidValues.isEmpty()) {
489             scce.addException(invalidValues);
490         }
491 
492         SyncopeClientException requiredValuesMissing = checkMandatory(any, anyUtils);
493         if (!requiredValuesMissing.isEmpty()) {
494             scce.addException(requiredValuesMissing);
495         }
496         requiredValuesMissing = checkMandatoryOnResources(any, resources);
497         if (!requiredValuesMissing.isEmpty()) {
498             scce.addException(requiredValuesMissing);
499         }
500     }
501 
502     protected PropagationByResource<String> propByRes(
503             final Map<String, ConnObject> before,
504             final Map<String, ConnObject> after) {
505 
506         PropagationByResource<String> propByRes = new PropagationByResource<>();
507 
508         after.forEach((resource, connObject) -> {
509             if (before.containsKey(resource)) {
510                 ConnObject beforeObject = before.get(resource);
511                 if (!beforeObject.equals(connObject)) {
512                     propByRes.add(ResourceOperation.UPDATE, resource);
513 
514                     beforeObject.getAttr(Uid.NAME).map(attr -> attr.getValues().get(0)).
515                             ifPresent(value -> propByRes.addOldConnObjectKey(resource, value));
516                 }
517             } else {
518                 propByRes.add(ResourceOperation.CREATE, resource);
519             }
520         });
521         propByRes.addAll(
522                 ResourceOperation.DELETE,
523                 before.keySet().stream().filter(resource -> !after.containsKey(resource)).collect(Collectors.toSet()));
524 
525         return propByRes;
526     }
527 
528     @SuppressWarnings({ "unchecked", "rawtypes" })
529     protected void fill(
530             final Any any,
531             final AnyCR anyCR,
532             final AnyUtils anyUtils,
533             final SyncopeClientCompositeException scce) {
534 
535         // 0. aux classes
536         any.getAuxClasses().clear();
537         anyCR.getAuxClasses().stream().
538                 map(className -> anyTypeClassDAO.find(className)).
539                 forEach(auxClass -> {
540                     if (auxClass == null) {
541                         LOG.debug("Invalid " + AnyTypeClass.class.getSimpleName() + " {}, ignoring...", auxClass);
542                     } else {
543                         any.add(auxClass);
544                     }
545                 });
546 
547         // 1. attributes
548         SyncopeClientException invalidValues = SyncopeClientException.build(ClientExceptionType.InvalidValues);
549 
550         anyCR.getPlainAttrs().stream().
551                 filter(attrTO -> !attrTO.getValues().isEmpty()).
552                 forEach(attrTO -> {
553                     PlainSchema schema = getPlainSchema(attrTO.getSchema());
554                     if (schema != null) {
555                         PlainAttr<?> attr = (PlainAttr<?>) any.getPlainAttr(schema.getKey()).orElse(null);
556                         if (attr == null) {
557                             attr = anyUtils.newPlainAttr();
558                             ((PlainAttr) attr).setOwner(any);
559                             attr.setSchema(schema);
560                         }
561                         fillAttr(attrTO.getValues(), anyUtils, schema, attr, invalidValues);
562 
563                         if (attr.getValuesAsStrings().isEmpty()) {
564                             attr.setOwner(null);
565                         } else {
566                             any.add(attr);
567                         }
568                     }
569                 });
570 
571         if (!invalidValues.isEmpty()) {
572             scce.addException(invalidValues);
573         }
574 
575         SyncopeClientException requiredValuesMissing = checkMandatory(any, anyUtils);
576         if (!requiredValuesMissing.isEmpty()) {
577             scce.addException(requiredValuesMissing);
578         }
579 
580         // 2. resources
581         anyCR.getResources().forEach(resourceKey -> {
582             ExternalResource resource = resourceDAO.find(resourceKey);
583             if (resource == null) {
584                 LOG.debug("Invalid " + ExternalResource.class.getSimpleName() + " {}, ignoring...", resourceKey);
585             } else {
586                 any.add(resource);
587             }
588         });
589 
590         requiredValuesMissing = checkMandatoryOnResources(any, anyUtils.getAllResources(any));
591         if (!requiredValuesMissing.isEmpty()) {
592             scce.addException(requiredValuesMissing);
593         }
594     }
595 
596     @SuppressWarnings({ "unchecked", "rawtypes" })
597     protected void fill(
598             final Any any,
599             final Membership membership,
600             final MembershipTO membershipTO,
601             final AnyUtils anyUtils,
602             final SyncopeClientCompositeException scce) {
603 
604         SyncopeClientException invalidValues = SyncopeClientException.build(ClientExceptionType.InvalidValues);
605 
606         membershipTO.getPlainAttrs().stream().
607                 filter(attrTO -> !attrTO.getValues().isEmpty()).
608                 forEach(attrTO -> Optional.ofNullable(getPlainSchema(attrTO.getSchema())).ifPresent(schema -> {
609 
610             GroupablePlainAttr attr = (GroupablePlainAttr) GroupableRelatable.class.cast(any).
611                     getPlainAttr(schema.getKey(), membership).
612                     orElseGet(() -> {
613                         GroupablePlainAttr gpa = anyUtils.newPlainAttr();
614                         gpa.setOwner(any);
615                         gpa.setMembership(membership);
616                         gpa.setSchema(schema);
617                         return gpa;
618                     });
619             fillAttr(attrTO.getValues(), anyUtils, schema, attr, invalidValues);
620 
621             if (attr.getValuesAsStrings().isEmpty()) {
622                 attr.setOwner(null);
623             } else {
624                 any.add(attr);
625             }
626         }));
627 
628         if (!invalidValues.isEmpty()) {
629             scce.addException(invalidValues);
630         }
631     }
632 
633     protected static void fillTO(
634             final AnyTO anyTO,
635             final String realmFullPath,
636             final Collection<? extends AnyTypeClass> auxClasses,
637             final Collection<? extends PlainAttr<?>> plainAttrs,
638             final Map<DerSchema, String> derAttrs,
639             final Map<VirSchema, List<String>> virAttrs,
640             final Collection<? extends ExternalResource> resources) {
641 
642         anyTO.setRealm(realmFullPath);
643 
644         anyTO.getAuxClasses().addAll(auxClasses.stream().map(AnyTypeClass::getKey).collect(Collectors.toList()));
645 
646         plainAttrs
647                 .forEach(plainAttr -> anyTO.getPlainAttrs().add(new Attr.Builder(plainAttr.getSchema().getKey())
648                 .values(plainAttr.getValuesAsStrings()).build()));
649 
650         derAttrs.forEach((schema, value) -> anyTO.getDerAttrs()
651                 .add(new Attr.Builder(schema.getKey()).value(value).build()));
652 
653         virAttrs.forEach((schema, values) -> anyTO.getVirAttrs()
654                 .add(new Attr.Builder(schema.getKey()).values(values).build()));
655 
656         anyTO.getResources().addAll(resources.stream().map(ExternalResource::getKey).collect(Collectors.toSet()));
657     }
658 
659     protected static RelationshipTO getRelationshipTO(final String relationshipType, final AnyObject otherEnd) {
660         return new RelationshipTO.Builder(relationshipType).
661                 otherEnd(otherEnd.getType().getKey(), otherEnd.getKey(), otherEnd.getName()).
662                 build();
663     }
664 
665     protected static MembershipTO getMembershipTO(
666             final Collection<? extends PlainAttr<?>> plainAttrs,
667             final Map<DerSchema, String> derAttrs,
668             final Map<VirSchema, List<String>> virAttrs,
669             final Membership<? extends Any<?>> membership) {
670 
671         MembershipTO membershipTO = new MembershipTO.Builder(membership.getRightEnd().getKey())
672                 .groupName(membership.getRightEnd().getName())
673                 .build();
674 
675         plainAttrs.forEach(plainAttr -> membershipTO.getPlainAttrs()
676                 .add(new Attr.Builder(plainAttr.getSchema().getKey())
677                         .values(plainAttr.getValuesAsStrings()).
678                         build()));
679 
680         derAttrs.forEach((schema, value) -> membershipTO.getDerAttrs().add(new Attr.Builder(schema.getKey()).
681                 value(value).
682                 build()));
683 
684         virAttrs.forEach((schema, values) -> membershipTO.getVirAttrs().add(new Attr.Builder(schema.getKey()).
685                 values(values).
686                 build()));
687 
688         return membershipTO;
689     }
690 }