Java tutorial
/* * Copyright (c) 2010-2018 Evolveum * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.evolveum.midpoint.repo.sql.helpers; import com.evolveum.midpoint.prism.*; import com.evolveum.midpoint.prism.delta.ItemDelta; import com.evolveum.midpoint.prism.delta.ItemDeltaCollectionsUtil; import com.evolveum.midpoint.prism.path.*; import com.evolveum.midpoint.repo.api.RepositoryService; import com.evolveum.midpoint.repo.sql.data.RepositoryContext; import com.evolveum.midpoint.repo.sql.data.common.*; import com.evolveum.midpoint.repo.sql.data.common.any.*; import com.evolveum.midpoint.repo.sql.data.common.container.Container; import com.evolveum.midpoint.repo.sql.data.common.container.RAssignment; import com.evolveum.midpoint.repo.sql.data.common.dictionary.ExtItemDictionary; import com.evolveum.midpoint.repo.sql.data.common.other.RObjectType; import com.evolveum.midpoint.repo.sql.data.common.type.RAssignmentExtensionType; import com.evolveum.midpoint.repo.sql.data.common.type.RObjectExtensionType; import com.evolveum.midpoint.repo.sql.helpers.mapper.Mapper; import com.evolveum.midpoint.repo.sql.helpers.modify.*; import com.evolveum.midpoint.repo.sql.util.EntityState; import com.evolveum.midpoint.repo.sql.util.PrismIdentifierGenerator; import com.evolveum.midpoint.schema.RelationRegistry; import com.evolveum.midpoint.schema.util.FullTextSearchConfigurationUtil; import com.evolveum.midpoint.schema.util.ObjectTypeUtil; import com.evolveum.midpoint.util.DebugUtil; import com.evolveum.midpoint.util.exception.SchemaException; import com.evolveum.midpoint.util.exception.SystemException; import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; import com.evolveum.midpoint.xml.ns._public.common.common_3.*; import org.apache.commons.beanutils.PropertyUtils; import org.apache.commons.lang.StringUtils; import org.hibernate.Session; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import javax.persistence.metamodel.Attribute; import javax.persistence.metamodel.ManagedType; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.*; import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.stream.Collectors; import static com.evolveum.midpoint.repo.sql.helpers.modify.DeltaUpdaterUtils.*; /** * @author Viliam Repan (lazyman). */ @Component public class ObjectDeltaUpdater { private static final Trace LOGGER = TraceManager.getTrace(ObjectDeltaUpdater.class); @Autowired private PrismContext prismContext; @Autowired private RepositoryService repositoryService; @Autowired private EntityRegistry entityRegistry; @Autowired private RelationRegistry relationRegistry; @Autowired private PrismEntityMapper prismEntityMapper; @Autowired private ExtItemDictionary extItemDictionary; /** * modify */ public <T extends ObjectType> RObject<T> modifyObject(Class<T> type, String oid, Collection<? extends ItemDelta> modifications, PrismObject<T> prismObject, Session session) throws SchemaException { LOGGER.trace("Starting to build entity changes for {}, {}, \n{}", type, oid, DebugUtil.debugDumpLazily(modifications)); // normalize reference.relation qnames like it's done here ObjectTypeUtil.normalizeAllRelations(prismObject); // how to generate identifiers correctly now? to repo entities and to full xml, ids in full XML are generated // on different place than we later create new containers...how to match them // set proper owner/ownerOid/ownerType for containers/references/result and others // todo implement transformation from prism to entity (PrismEntityMapper), probably ROperationResult missing // validate lookup tables and certification campaigns // mark newly added containers/references as transient // validate metadata/*, assignment/metadata/*, assignment/construction/resourceRef changes PrismIdentifierGenerator<T> idGenerator = new PrismIdentifierGenerator<>( PrismIdentifierGenerator.Operation.MODIFY); idGenerator.collectUsedIds(prismObject); // preprocess modifications Collection<? extends ItemDelta> processedModifications = prismObject .narrowModifications((Collection<? extends ItemDelta<?, ?>>) modifications); if (LOGGER.isTraceEnabled()) { LOGGER.trace("Narrowed modifications:\n{}", DebugUtil.debugDumpLazily(modifications)); } // process only real modifications Class<? extends RObject> objectClass = RObjectType.getByJaxbType(type).getClazz(); RObject<T> object = session.byId(objectClass).getReference(oid); ManagedType mainEntityType = entityRegistry.getJaxbMapping(type); boolean modifiesShadowPendingOperation = false; for (ItemDelta delta : processedModifications) { ItemPath path = delta.getPath(); if (LOGGER.isTraceEnabled()) { LOGGER.trace("Processing delta with path '{}'", path); } if (isObjectExtensionDelta(path) || isShadowAttributesDelta(path)) { if (delta.getPath().size() == 1) { handleObjectExtensionWholeContainer(object, delta, idGenerator); } else { handleObjectExtensionOrAttributesDelta(object, delta, idGenerator); } continue; } AttributeStep attributeStep = new AttributeStep(); attributeStep.managedType = mainEntityType; attributeStep.bean = object; Iterator<?> segments = path.getSegments().iterator(); while (segments.hasNext()) { Object segment = segments.next(); if (!ItemPath.isName(segment)) { throw new SystemException("Segment '" + segment + "' in '" + path + "' is not a name item"); } ItemName name = ItemPath.toName(segment); String nameLocalPart = name.getLocalPart(); if (isAssignmentExtensionDelta(attributeStep, name)) { ItemName lastName = delta.getPath().lastName(); if (AssignmentType.F_EXTENSION.equals(lastName)) { handleAssignmentExtensionWholeContainer((RAssignment) attributeStep.bean, delta, idGenerator); } else { handleAssignmentExtensionDelta((RAssignment) attributeStep.bean, delta, idGenerator); } continue; } if (isOperationResult(delta)) { handleOperationResult(attributeStep.bean, delta); continue; } if (isMetadata(delta)) { handleMetadata(attributeStep.bean, delta); } if (isFocusPhoto(delta)) { handlePhoto(attributeStep.bean, delta); continue; } if (isShadowPendingOperation(object, delta)) { modifiesShadowPendingOperation = true; } Attribute attribute = findAttribute(attributeStep, nameLocalPart, path, segments, name); if (attribute == null) { // there's no table/column that needs update break; } if (segments.hasNext()) { attributeStep = stepThroughAttribute(attribute, attributeStep, segments); continue; } handleAttribute(attribute, attributeStep.bean, delta, prismObject, idGenerator); if ("name".equals(attribute.getName()) && RObject.class.isAssignableFrom(attribute.getDeclaringType().getJavaType())) { // we also need to handle "nameCopy" column, we doesn't need path/segments/nameSegment for this call Attribute nameCopyAttribute = findAttribute(attributeStep, "nameCopy", null, null, null); handleAttribute(nameCopyAttribute, attributeStep.bean, delta, prismObject, idGenerator); } } } // the following will apply deltas to prismObject handleObjectCommonAttributes(type, processedModifications, prismObject, object, idGenerator); if (modifiesShadowPendingOperation) { handleShadowPendingOperation(object, prismObject); } LOGGER.trace("Entity changes applied"); return object; } private boolean isFocusPhoto(ItemDelta delta) { return FocusType.F_JPEG_PHOTO.equivalent(delta.getPath()); } private void handlePhoto(Object bean, ItemDelta delta) throws SchemaException { if (!(bean instanceof RFocus)) { throw new SystemException("Bean is not instance of " + RFocus.class + ", shouldn't happen"); } RFocus focus = (RFocus) bean; Set<RFocusPhoto> photos = focus.getJpegPhoto(); if (delta.isDelete()) { photos.clear(); return; } MapperContext context = new MapperContext(); context.setRepositoryContext( new RepositoryContext(repositoryService, prismContext, relationRegistry, extItemDictionary)); context.setDelta(delta); context.setOwner(bean); PrismValue value = delta.getAnyValue(); RFocusPhoto photo = prismEntityMapper.map(value.getRealValue(), RFocusPhoto.class, context); if (delta.isAdd()) { if (!photos.isEmpty()) { throw new SchemaException("Object '" + focus.getOid() + "' already contains photo"); } photo.setTransient(true); photos.add(photo); return; } if (photos.isEmpty()) { photo.setTransient(true); photos.add(photo); return; } RFocusPhoto oldPhoto = photos.iterator().next(); oldPhoto.setPhoto(photo.getPhoto()); } private boolean isMetadata(ItemDelta delta) { ItemPath named = delta.getPath().namedSegmentsOnly(); return ObjectType.F_METADATA.equivalent(named); } private void handleMetadata(Object bean, ItemDelta delta) { if (!(bean instanceof Metadata)) { throw new SystemException("Bean is not instance of " + Metadata.class + ", shouldn't happen"); } PrismValue value = null; if (!delta.isDelete()) { value = delta.getAnyValue(); } MapperContext context = new MapperContext(); context.setRepositoryContext( new RepositoryContext(repositoryService, prismContext, relationRegistry, extItemDictionary)); context.setDelta(delta); context.setOwner(bean); if (value != null) { prismEntityMapper.mapPrismValue(value, Metadata.class, context); } else { // todo clean this up // we know that mapper supports mapping null value, but still this code smells Mapper mapper = prismEntityMapper.getMapper(MetadataType.class, Metadata.class); mapper.map(null, context); } } private boolean isOperationResult(ItemDelta delta) throws SchemaException { ItemDefinition def = delta.getDefinition(); if (def == null) { throw new SchemaException("No definition in delta for item " + delta.getPath()); } return OperationResultType.COMPLEX_TYPE.equals(def.getTypeName()); } private void handleOperationResult(Object bean, ItemDelta delta) { if (!(bean instanceof OperationResult)) { throw new SystemException("Bean is not instance of " + OperationResult.class + ", shouldn't happen"); } PrismValue value = null; if (!delta.isDelete()) { value = delta.getAnyValue(); } MapperContext context = new MapperContext(); context.setRepositoryContext( new RepositoryContext(repositoryService, prismContext, relationRegistry, extItemDictionary)); context.setDelta(delta); context.setOwner(bean); if (value != null) { prismEntityMapper.mapPrismValue(value, OperationResult.class, context); } else { // todo clean this up // we know that mapper supports mapping null value, but still this code smells Mapper mapper = prismEntityMapper.getMapper(OperationResultType.class, OperationResult.class); mapper.map(null, context); } } private boolean isShadowPendingOperation(RObject<?> object, ItemDelta delta) { return object instanceof RShadow && ShadowType.F_PENDING_OPERATION.equivalent(delta.getPath()); } private <T extends ObjectType> void handleShadowPendingOperation(RObject<T> bean, PrismObject<T> prismObject) throws SchemaException { if (!(bean instanceof RShadow)) { throw new SystemException("Bean is not instance of " + RShadow.class + ", shouldn't happen"); } RShadow shadow = (RShadow) bean; T objectable = prismObject.asObjectable(); if (!(objectable instanceof ShadowType)) { throw new SystemException("PrismObject is not instance of " + ShadowType.class + ", shouldn't happen"); } ShadowType shadowType = (ShadowType) objectable; shadow.setPendingOperationCount(shadowType.getPendingOperation().size()); } private <T extends ObjectType> void handleObjectCommonAttributes(Class<T> type, Collection<? extends ItemDelta> modifications, PrismObject<T> prismObject, RObject object, PrismIdentifierGenerator<T> idGenerator) throws SchemaException { // update version String strVersion = prismObject.getVersion(); int version = StringUtils.isNotEmpty(strVersion) && strVersion.matches("[0-9]*") ? Integer.parseInt(strVersion) + 1 : 1; object.setVersion(version); // apply modifications, ids' for new containers already filled in delta values ItemDeltaCollectionsUtil.applyTo(modifications, prismObject); handleObjectTextInfoChanges(type, modifications, prismObject, object); // generate ids for containers that weren't handled in previous step (not processed by repository) idGenerator.generate(prismObject); // normalize all relations ObjectTypeUtil.normalizeAllRelations(prismObject, relationRegistry); // full object column will be updated later } private <T extends ObjectType> boolean isObjectTextInfoRecomputationNeeded(Class<T> type, Collection<? extends ItemDelta> modifications) { FullTextSearchConfigurationType config = repositoryService.getFullTextSearchConfiguration(); if (!FullTextSearchConfigurationUtil.isEnabled(config)) { return false; } Set<ItemPath> paths = FullTextSearchConfigurationUtil.getFullTextSearchItemPaths(config, type); for (ItemDelta modification : modifications) { ItemPath namesOnly = modification.getPath().namedSegmentsOnly(); for (ItemPath path : paths) { if (path.startsWith(namesOnly)) { return true; } } } return false; } private <T extends ObjectType> void handleObjectTextInfoChanges(Class<T> type, Collection<? extends ItemDelta> modifications, PrismObject prismObject, RObject<T> object) { // update object text info if necessary if (!isObjectTextInfoRecomputationNeeded(type, modifications)) { return; } Set<RObjectTextInfo> newInfos = RObjectTextInfo.createItemsSet((ObjectType) prismObject.asObjectable(), object, new RepositoryContext(repositoryService, prismContext, relationRegistry, extItemDictionary)); if (newInfos == null || newInfos.isEmpty()) { object.getTextInfoItems().clear(); } else { Set<String> existingTexts = object.getTextInfoItems().stream().map(info -> info.getText()) .collect(Collectors.toSet()); Set<String> newTexts = newInfos.stream().map(info -> info.getText()).collect(Collectors.toSet()); object.getTextInfoItems().removeIf(existingInfo -> !newTexts.contains(existingInfo.getText())); for (RObjectTextInfo newInfo : newInfos) { if (!existingTexts.contains(newInfo.getText())) { object.getTextInfoItems().add(newInfo); } } } } private boolean isObjectExtensionDelta(ItemPath path) { return path.startsWithName(ObjectType.F_EXTENSION); } private boolean isShadowAttributesDelta(ItemPath path) { return path.startsWithName(ShadowType.F_ATTRIBUTES); } private boolean isAssignmentExtensionDelta(AttributeStep attributeStep, ItemName name) { if (!(attributeStep.bean instanceof RAssignment)) { return false; } if (!AssignmentType.F_EXTENSION.equals(name)) { return false; } return true; } private void handleAssignmentExtensionDelta(RAssignment assignment, ItemDelta delta, PrismIdentifierGenerator idGenerator) { RAssignmentExtension extension = assignment.getExtension(); if (extension == null) { extension = new RAssignmentExtension(); extension.setOwner(assignment); extension.setTransient(true); assignment.setExtension(extension); } processAnyExtensionDeltaValues(delta, null, null, extension, RAssignmentExtensionType.EXTENSION, idGenerator); } private void processAnyExtensionDeltaValues(Collection<PrismValue> values, RObject object, RObjectExtensionType objectOwnerType, RAssignmentExtension assignmentExtension, RAssignmentExtensionType assignmentExtensionType, BiConsumer<Collection<? extends RAnyValue>, Collection<PrismEntityPair<RAnyValue>>> processObjectValues) { RAnyConverter converter = new RAnyConverter(prismContext, extItemDictionary); if (values == null || values.isEmpty()) { return; } try { Collection<PrismEntityPair<RAnyValue>> extValues = new ArrayList<>(); for (PrismValue value : values) { RAnyValue extValue = converter.convertToRValue(value, object == null, objectOwnerType); if (extValue == null) { continue; } extValues.add(new PrismEntityPair(value, extValue)); } if (extValues.isEmpty()) { // no changes in indexed values return; // todo can't return if new "values" collection is empty, if it was REPLACE with "nothing" we have to remove proper attributes } Class type = null; if (!extValues.isEmpty()) { RAnyValue first = extValues.iterator().next().getRepository(); type = first.getClass(); } if (object != null) { extValues.stream().forEach(item -> { ROExtValue val = (ROExtValue) item.getRepository(); val.setOwner(object); val.setOwnerType(objectOwnerType); }); processObjectExtensionValues(object, type, (existing) -> processObjectValues.accept(existing, extValues)); } else { extValues.stream().forEach(item -> { RAExtValue val = (RAExtValue) item.getRepository(); val.setAnyContainer(assignmentExtension); val.setExtensionType(assignmentExtensionType); }); processAssignmentExtensionValues(assignmentExtension, type, (existing) -> processObjectValues.accept(existing, extValues)); } } catch (SchemaException ex) { throw new SystemException("Couldn't process extension attributes", ex); } } private Collection<RAnyValue> filterRAnyValues(Collection<? extends RAnyValue> existing, ItemDefinition def, RObjectExtensionType objectOwnerType, RAssignmentExtensionType assignmentExtensionType) { Collection<RAnyValue> filtered = new ArrayList<>(); RExtItem extItemDefinition = extItemDictionary.findItemByDefinition(def); if (extItemDefinition == null) { return filtered; } for (RAnyValue value : existing) { if (value.getItemId() == null) { continue; // suspicious } if (!value.getItemId().equals(extItemDefinition.getId())) { continue; } if (value instanceof ROExtValue) { ROExtValue oValue = (ROExtValue) value; if (!objectOwnerType.equals(oValue.getOwnerType())) { continue; } } else if (value instanceof RAExtValue) { RAExtValue aValue = (RAExtValue) value; if (!assignmentExtensionType.equals(aValue.getExtensionType())) { continue; } } filtered.add(value); } return filtered; } private void processAnyExtensionDeltaValues(ItemDelta delta, RObject object, RObjectExtensionType objectOwnerType, RAssignmentExtension assignmentExtension, RAssignmentExtensionType assignmentExtensionType, PrismIdentifierGenerator idGenerator) { // handle replace if (delta.getValuesToReplace() != null && !delta.getValuesToReplace().isEmpty()) { processAnyExtensionDeltaValues(delta.getValuesToReplace(), object, objectOwnerType, assignmentExtension, assignmentExtensionType, (existing, fromDelta) -> { ItemDefinition def = delta.getDefinition(); Collection<RAnyValue> filtered = filterRAnyValues(existing, def, objectOwnerType, assignmentExtensionType); if (fromDelta.isEmpty()) { // if there are not new values, we just remove existing ones existing.removeAll(filtered); return; } Collection<RAnyValue> toDelete = new ArrayList<>(); Collection<PrismEntityPair<?>> toAdd = new ArrayList<>(); Set<Object> justValuesToReplace = new HashSet<>(); for (PrismEntityPair<RAnyValue> pair : fromDelta) { justValuesToReplace.add(pair.getRepository().getValue()); } for (RAnyValue value : filtered) { if (justValuesToReplace.contains(value.getValue())) { // do not replace with the same one - don't touch justValuesToReplace.remove(value.getValue()); } else { toDelete.add(value); } } for (PrismEntityPair<RAnyValue> pair : fromDelta) { if (justValuesToReplace.contains(pair.getRepository().getValue())) { toAdd.add(pair); } } existing.removeAll(toDelete); markNewOnesTransientAndAddToExisting(existing, toAdd, idGenerator); }); return; } // handle delete processAnyExtensionDeltaValues(delta.getValuesToDelete(), object, objectOwnerType, assignmentExtension, assignmentExtensionType, (existing, fromDelta) -> { Collection filtered = fromDelta.stream().map(i -> i.getRepository()) .collect(Collectors.toList()); existing.removeAll(filtered); }); // handle add processAnyExtensionDeltaValues(delta.getValuesToAdd(), object, objectOwnerType, assignmentExtension, assignmentExtensionType, (existing, fromDelta) -> markNewOnesTransientAndAddToExisting(existing, (Collection) fromDelta, idGenerator)); } private void processAssignmentExtensionValues(RAssignmentExtension extension, Class<? extends RAExtValue> type, Consumer<Collection<? extends RAExtValue>> processObjectValues) { if (type.equals(RAExtDate.class)) { processObjectValues.accept(extension.getDates()); Short count = getCount(extension.getDates()); extension.setDatesCount(count); } else if (type.equals(RAExtLong.class)) { processObjectValues.accept(extension.getLongs()); Short count = getCount(extension.getLongs()); extension.setLongsCount(count); } else if (type.equals(RAExtReference.class)) { processObjectValues.accept(extension.getReferences()); Short count = getCount(extension.getReferences()); extension.setReferencesCount(count); } else if (type.equals(RAExtString.class)) { processObjectValues.accept(extension.getStrings()); Short count = getCount(extension.getStrings()); extension.setStringsCount(count); } else if (type.equals(RAExtPolyString.class)) { processObjectValues.accept(extension.getPolys()); Short count = getCount(extension.getPolys()); extension.setPolysCount(count); } else if (type.equals(RAExtBoolean.class)) { processObjectValues.accept(extension.getBooleans()); Short count = getCount(extension.getBooleans()); extension.setBooleansCount(count); } } private void processObjectExtensionValues(RObject object, Class<? extends ROExtValue> type, Consumer<Collection<ROExtValue>> processObjectValues) { if (type.equals(ROExtDate.class)) { processObjectValues.accept(object.getDates()); Short count = getCount(object.getDates()); object.setDatesCount(count); } else if (type.equals(ROExtLong.class)) { processObjectValues.accept(object.getLongs()); Short count = getCount(object.getLongs()); object.setLongsCount(count); } else if (type.equals(ROExtReference.class)) { processObjectValues.accept(object.getReferences()); Short count = getCount(object.getReferences()); object.setReferencesCount(count); } else if (type.equals(ROExtString.class)) { processObjectValues.accept(object.getStrings()); Short count = getCount(object.getStrings()); object.setStringsCount(count); } else if (type.equals(ROExtPolyString.class)) { processObjectValues.accept(object.getPolys()); Short count = getCount(object.getPolys()); object.setPolysCount(count); } else if (type.equals(ROExtBoolean.class)) { processObjectValues.accept(object.getBooleans()); Short count = getCount(object.getBooleans()); object.setBooleansCount(count); } } private Short getCount(Collection collection) { if (collection == null) { return 0; } return Integer.valueOf(collection.size()).shortValue(); } private void handleObjectExtensionWholeContainer(RObject object, ItemDelta delta, PrismIdentifierGenerator idGenerator) { RObjectExtensionType extType = computeObjectExtensionType(delta); if (!delta.isAdd()) { clearExtension(object, extType); } if (delta.isDelete()) { return; } PrismContainerValue extension = (PrismContainerValue) delta.getAnyValue(); for (Item item : (List<Item>) extension.getItems()) { ItemDelta itemDelta = item.createDelta(); itemDelta.setValuesToReplace(item.getClonedValues()); processAnyExtensionDeltaValues(itemDelta, object, extType, null, null, idGenerator); } } private void handleAssignmentExtensionWholeContainer(RAssignment assignment, ItemDelta delta, PrismIdentifierGenerator idGenerator) { RAssignmentExtension ext = assignment.getExtension(); if (!delta.isAdd()) { if (ext != null) { clearExtension(ext); } } if (delta.isDelete()) { return; } if (ext == null) { ext = new RAssignmentExtension(); ext.setOwner(assignment); assignment.setExtension(ext); } PrismContainerValue extension = (PrismContainerValue) delta.getAnyValue(); for (Item item : (List<Item>) extension.getItems()) { ItemDelta itemDelta = item.createDelta(); itemDelta.setValuesToReplace(item.getClonedValues()); processAnyExtensionDeltaValues(itemDelta, null, null, ext, RAssignmentExtensionType.EXTENSION, idGenerator); } } private RObjectExtensionType computeObjectExtensionType(ItemDelta delta) { if (isObjectExtensionDelta(delta.getPath())) { return RObjectExtensionType.EXTENSION; } else if (isShadowAttributesDelta(delta.getPath())) { return RObjectExtensionType.ATTRIBUTES; } throw new IllegalStateException("Unknown extension type, shouldn't happen"); } private void handleObjectExtensionOrAttributesDelta(RObject object, ItemDelta delta, PrismIdentifierGenerator idGenerator) { RObjectExtensionType ownerType = computeObjectExtensionType(delta); processAnyExtensionDeltaValues(delta, object, ownerType, null, null, idGenerator); } private Attribute findAttribute(AttributeStep attributeStep, String nameLocalPart, ItemPath path, Iterator<?> segments, ItemName name) { Attribute attribute = entityRegistry.findAttribute(attributeStep.managedType, nameLocalPart); if (attribute != null) { return attribute; } attribute = entityRegistry.findAttributeOverride(attributeStep.managedType, nameLocalPart); if (attribute != null) { return attribute; } if (!segments.hasNext()) { return null; } // try to search path overrides like metadata/* or assignment/metadata/* or assignment/construction/resourceRef Object segment; ItemPath subPath = name; while (segments.hasNext()) { if (!entityRegistry.hasAttributePathOverride(attributeStep.managedType, subPath)) { subPath = subPath.allUpToLastName(); break; } segment = segments.next(); if (!ItemPath.isName(segment)) { throw new SystemException("Segment '" + segment + "' in '" + path + "' is not a name item"); } subPath = subPath.append(segment); } return entityRegistry.findAttributePathOverride(attributeStep.managedType, subPath); } private AttributeStep stepThroughAttribute(Attribute attribute, AttributeStep step, Iterator<?> segments) { Method method = (Method) attribute.getJavaMember(); switch (attribute.getPersistentAttributeType()) { case EMBEDDED: step.managedType = entityRegistry.getMapping(attribute.getJavaType()); Object child = invoke(step.bean, method); if (child == null) { // embedded entity doesn't exist we have to create it first, so it can be populated later Class childType = getRealOutputType(attribute); try { child = childType.newInstance(); PropertyUtils.setSimpleProperty(step.bean, attribute.getName(), child); } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException ex) { throw new SystemException("Couldn't create new instance of '" + childType.getName() + "', attribute '" + attribute.getName() + "'", ex); } } step.bean = child; break; case ONE_TO_MANY: // object extension is handled separately, only {@link Container} and references are handled here Class clazz = getRealOutputType(attribute); Long id = ItemPath.toId(segments.next()); Collection c = (Collection) invoke(step.bean, method); if (!Container.class.isAssignableFrom(clazz)) { throw new SystemException( "Don't know how to go through collection of '" + getRealOutputType(attribute) + "'"); } boolean found = false; for (Container o : (Collection<Container>) c) { long l = o.getId().longValue(); if (l == id) { step.managedType = entityRegistry.getMapping(clazz); step.bean = o; found = true; break; } } if (!found) { throw new RuntimeException("Couldn't find container of type '" + getRealOutputType(attribute) + "' with id '" + id + "'"); } break; case ONE_TO_ONE: // assignment extension is handled separately break; default: // nothing to do for other cases } return step; } private void handleAttribute(Attribute attribute, Object bean, ItemDelta delta, PrismObject prismObject, PrismIdentifierGenerator idGenerator) { Method method = (Method) attribute.getJavaMember(); switch (attribute.getPersistentAttributeType()) { case BASIC: case EMBEDDED: handleBasicOrEmbedded(bean, delta, attribute); break; case MANY_TO_MANY: // not used in our mappings throw new SystemException("Don't know how to handle @ManyToMany relationship, should not happen"); case ONE_TO_ONE: // assignment extension is handled separately break; case MANY_TO_ONE: // this can't be in delta (probably) throw new SystemException("Don't know how to handle @ManyToOne relationship, should not happen"); case ONE_TO_MANY: // object extension is handled separately, only {@link Container} and references are handled here Collection oneToMany = (Collection) invoke(bean, method); handleOneToMany(oneToMany, delta, attribute, bean, prismObject, idGenerator); break; case ELEMENT_COLLECTION: Collection elementCollection = (Collection) invoke(bean, method); handleElementCollection(elementCollection, delta, attribute, bean, prismObject, idGenerator); break; } } private void handleBasicOrEmbedded(Object bean, ItemDelta delta, Attribute attribute) { Class outputType = getRealOutputType(attribute); PrismValue anyPrismValue = delta.getAnyValue(); Object value; if (delta.isDelete() || (delta.isReplace() && (anyPrismValue == null || anyPrismValue.isEmpty()))) { value = null; } else { value = anyPrismValue.getRealValue(); } value = prismEntityMapper.map(value, outputType); try { PropertyUtils.setSimpleProperty(bean, attribute.getName(), value); } catch (Exception ex) { throw new SystemException("Couldn't set simple property for '" + attribute.getName() + "'", ex); } } private void handleElementCollection(Collection collection, ItemDelta delta, Attribute attribute, Object bean, PrismObject prismObject, PrismIdentifierGenerator idGenerator) { handleOneToMany(collection, delta, attribute, bean, prismObject, idGenerator); } private void handleOneToMany(Collection collection, ItemDelta delta, Attribute attribute, Object bean, PrismObject prismObject, PrismIdentifierGenerator idGenerator) { Class outputType = getRealOutputType(attribute); Item item = prismObject.findItem(delta.getPath()); // handle replace if (delta.isReplace()) { Collection<PrismEntityPair<?>> valuesToReplace = processDeltaValues(delta.getValuesToReplace(), outputType, delta, bean); replaceValues(collection, valuesToReplace, item, idGenerator); return; } // handle add if (delta.isAdd()) { Collection<PrismEntityPair<?>> valuesToAdd = processDeltaValues(delta.getValuesToAdd(), outputType, delta, bean); addValues(collection, valuesToAdd, idGenerator); } // handle delete if (delta.isDelete()) { Collection<PrismEntityPair<?>> valuesToDelete = processDeltaValues(delta.getValuesToDelete(), outputType, delta, bean); valuesToDelete.stream().forEach(pair -> { if (pair.getRepository() instanceof EntityState) { ((EntityState) pair.getRepository()).setTransient(false); } }); deleteValues(collection, valuesToDelete, item); } } private Collection<PrismEntityPair> processDeltaValues(Collection<? extends PrismValue> values, Class outputType, ItemDelta delta, Object bean) { if (values == null) { return new ArrayList(); } Collection<PrismEntityPair> results = new ArrayList(); for (PrismValue value : values) { MapperContext context = new MapperContext(); context.setRepositoryContext( new RepositoryContext(repositoryService, prismContext, relationRegistry, extItemDictionary)); context.setDelta(delta); context.setOwner(bean); Object result = prismEntityMapper.mapPrismValue(value, outputType, context); results.add(new PrismEntityPair(value, result)); } return results; } private Class getRealOutputType(Attribute attribute) { Class type = attribute.getJavaType(); if (!Collection.class.isAssignableFrom(type)) { return type; } Method method = (Method) attribute.getJavaMember(); ParameterizedType parametrized = (ParameterizedType) method.getGenericReturnType(); Type t = parametrized.getActualTypeArguments()[0]; if (t instanceof Class) { return (Class) t; } parametrized = (ParameterizedType) t; return (Class) parametrized.getRawType(); } private Object invoke(Object object, Method method) { try { return method.invoke(object); } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { throw new SystemException( "Couldn't invoke method '" + method.getName() + "' on object '" + object + "'", ex); } } /** * add with overwrite */ public <T extends ObjectType> RObject<T> update(PrismObject<T> object, RObject<T> objectToMerge, Session session) { return merge(objectToMerge, session); // todo implement } private <T extends ObjectType> RObject<T> merge(RObject<T> object, Session session) { return (RObject) session.merge(object); } private static class AttributeStep { private ManagedType managedType; private Object bean; } }