Skip to content

Commit

Permalink
repo-sqale: first successful extension modifyObject test
Browse files Browse the repository at this point in the history
Added ExtensionUpdateContext and ExtensionItemDeltaProcessor.
Extension support stuff moved mostly to new ExtensionProcessor.
Still WIP, no JSONB key delete, and it now overwrites the whole stuff.
  • Loading branch information
virgo47 committed Jun 25, 2021
1 parent 3168217 commit 4687aa0
Show file tree
Hide file tree
Showing 23 changed files with 424 additions and 198 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
/*
* Copyright (C) 2010-2021 Evolveum and contributors
*
* This work is dual-licensed under the Apache License 2.0
* and European Union Public License. See LICENSE file for details.
*/
package com.evolveum.midpoint.repo.sqale;

import static com.evolveum.midpoint.repo.sqale.ExtUtils.*;

import java.io.IOException;
import java.util.*;
import javax.xml.datatype.XMLGregorianCalendar;
import javax.xml.namespace.QName;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import com.evolveum.midpoint.prism.*;
import com.evolveum.midpoint.prism.polystring.PolyString;
import com.evolveum.midpoint.repo.sqale.jsonb.Jsonb;
import com.evolveum.midpoint.repo.sqale.qmodel.ext.MExtItem;
import com.evolveum.midpoint.repo.sqale.qmodel.ext.MExtItemCardinality;
import com.evolveum.midpoint.repo.sqale.qmodel.ext.MExtItemHolderType;
import com.evolveum.midpoint.repo.sqale.qmodel.object.MObjectType;
import com.evolveum.midpoint.util.exception.SystemException;

public class ExtensionProcessor {

private final SqaleRepoContext repositoryContext;

public ExtensionProcessor(SqaleRepoContext repositoryContext) {
this.repositoryContext = repositoryContext;
}

public Jsonb processExtensions(
@NotNull Containerable extContainer, MExtItemHolderType holderType) {
Map<String, Object> extMap = new LinkedHashMap<>();
PrismContainerValue<?> prismContainerValue = extContainer.asPrismContainerValue();
for (Item<?, ?> item : prismContainerValue.getItems()) {
try {
Objects.requireNonNull(item, "Object for converting must not be null.");
ExtItemInfo extItemInfo = findExtensionItem(item.getDefinition(), holderType);
if (extItemInfo == null) {
continue; // not-indexed, skipping this item
}

Object value = extItemValue(item, extItemInfo);
extMap.put(extItemInfo.getId(), value);
} catch (RuntimeException e) {
// If anything happens (like NPE in Map.of) we want to capture the "bad" item.
throw new SystemException(
"Unexpected exception while processing extension item " + item, e);
}
}

try {
return Jsonb.from(extMap);
} catch (IOException e) {
throw new SystemException(e);
}
}

/** Returns ext item definition or null if the item is not indexed and should be skipped. */
@Nullable
public ExtensionProcessor.ExtItemInfo findExtensionItem(
ItemDefinition<?> definition, MExtItemHolderType holderType) {
MExtItem extItem = resolveExtensionItem(definition, holderType);
if (extItem == null) {
return null; // not-indexed, returning null
}

// TODO review any need for shadow attributes, now they are stored fine, but the code here
// is way too simple compared to the old repo.

ExtItemInfo info = new ExtItemInfo();
info.item = extItem;
if (definition instanceof PrismReferenceDefinition) {
info.defaultRefTargetType = ((PrismReferenceDefinition) definition).getTargetTypeName();
}

return info;
}

public Object extItemValue(Item<?, ?> item, ExtItemInfo extItemInfo) {
MExtItem extItem = extItemInfo.item;
if (extItem.cardinality == MExtItemCardinality.ARRAY) {
List<Object> vals = new ArrayList<>();
for (Object realValue : item.getRealValues()) {
vals.add(convertExtItemValue(realValue, extItemInfo));
}
return vals;
} else {
return convertExtItemValue(item.getRealValue(), extItemInfo);
}
}

private Object convertExtItemValue(Object realValue, ExtItemInfo extItemInfo) {
if (realValue instanceof String
|| realValue instanceof Number
|| realValue instanceof Boolean) {
return realValue;
}

if (realValue instanceof PolyString) {
PolyString poly = (PolyString) realValue;
return Map.of(EXT_POLY_ORIG_KEY, poly.getOrig(),
EXT_POLY_NORM_KEY, poly.getNorm());
}

if (realValue instanceof Referencable) {
Referencable ref = (Referencable) realValue;
// we always want to store the type for consistent search results
QName targetType = ref.getType();
if (targetType == null) {
targetType = extItemInfo.defaultRefTargetType;
}
if (targetType == null) {
throw new IllegalArgumentException(
"Reference without target type can't be stored: " + ref);
}
return Map.of(EXT_REF_TARGET_OID_KEY, ref.getOid(),
EXT_REF_TARGET_TYPE_KEY, MObjectType.fromTypeQName(targetType),
EXT_REF_RELATION_KEY, repositoryContext.processCacheableRelation(ref.getRelation()));
}

if (realValue instanceof Enum) {
return realValue.toString();
}

if (realValue instanceof XMLGregorianCalendar) {
// XMLGregorianCalendar stores only millis, but we cut it to 3 fraction digits
// to make the behavior explicit and consistent.
return ExtUtils.extensionDateTime((XMLGregorianCalendar) realValue);
}

throw new IllegalArgumentException(
"Unsupported type '" + realValue.getClass() + "' for value '" + realValue + "'.");
}

/**
* Finds extension item for the provided definition and holder type.
* Returns null if the item is not indexed.
*/
public MExtItem resolveExtensionItem(
ItemDefinition<?> definition, MExtItemHolderType holderType) {
Objects.requireNonNull(definition,
"Item '" + definition.getItemName() + "' without definition can't be saved.");

if (definition instanceof PrismPropertyDefinition) {
Boolean indexed = ((PrismPropertyDefinition<?>) definition).isIndexed();
// null is default which is "indexed"
if (indexed != null && !indexed) {
return null;
}
// enum is recognized by having allowed values
if (!ExtUtils.SUPPORTED_INDEXED_EXTENSION_TYPES.contains(definition.getTypeName())
&& !ExtUtils.isEnumDefinition(((PrismPropertyDefinition<?>) definition))) {
return null;
}
} else if (!(definition instanceof PrismReferenceDefinition)) {
throw new UnsupportedOperationException("Unknown definition type '" + definition
+ "', can't say if '" + definition.getItemName() + "' is indexed or not.");
} // else it's reference which is indexed implicitly

return repositoryContext.resolveExtensionItem(MExtItem.keyFrom(definition, holderType));
}

/** Contains ext item from catalog and additional info needed for processing. */
public static class ExtItemInfo {
public MExtItem item;
public QName defaultRefTargetType;

public String getId() {
return item.id.toString();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,13 @@
*/
package com.evolveum.midpoint.repo.sqale;

import java.util.Objects;
import javax.annotation.PostConstruct;
import javax.sql.DataSource;
import javax.xml.namespace.QName;

import com.querydsl.sql.types.EnumAsObjectType;
import org.jetbrains.annotations.NotNull;

import com.evolveum.midpoint.prism.ItemDefinition;
import com.evolveum.midpoint.prism.PrismPropertyDefinition;
import com.evolveum.midpoint.prism.PrismReferenceDefinition;
import com.evolveum.midpoint.repo.sqale.jsonb.QuerydslJsonbType;
import com.evolveum.midpoint.repo.sqale.qmodel.common.MContainerType;
import com.evolveum.midpoint.repo.sqale.qmodel.common.QUri;
Expand Down Expand Up @@ -113,27 +109,7 @@ public Integer processCacheableRelation(QName qName) {
QNameUtil.qNameToUri(normalizeRelation(qName)));
}

public MExtItem resolveExtensionItem(
ItemDefinition<?> definition, MExtItemHolderType holderType) {
Objects.requireNonNull(definition,
"Item '" + definition.getItemName() + "' without definition can't be saved.");

if (definition instanceof PrismPropertyDefinition) {
Boolean indexed = ((PrismPropertyDefinition<?>) definition).isIndexed();
// null is default which is "indexed"
if (indexed != null && !indexed) {
return null;
}
// enum is recognized by having allowed values
if (!ExtUtils.SUPPORTED_INDEXED_EXTENSION_TYPES.contains(definition.getTypeName())
&& !ExtUtils.isEnumDefinition(((PrismPropertyDefinition<?>) definition))) {
return null;
}
} else if (!(definition instanceof PrismReferenceDefinition)) {
throw new UnsupportedOperationException("Unknown definition type '" + definition
+ "', can't say if '" + definition.getItemName() + "' is indexed or not.");
} // else it's reference which is indexed implicitly

return extItemCache.resolveExtensionItem(MExtItem.keyFrom(definition, holderType));
public @NotNull MExtItem resolveExtensionItem(@NotNull MExtItem.Key extItemKey) {
return extItemCache.resolveExtensionItem(extItemKey);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
import com.evolveum.midpoint.prism.path.ItemPath;
import com.evolveum.midpoint.repo.sqale.mapping.ContainerTableRelationResolver;
import com.evolveum.midpoint.repo.sqale.mapping.SqaleItemRelationResolver;
import com.evolveum.midpoint.repo.sqale.mapping.SqaleItemSqlMapper;
import com.evolveum.midpoint.repo.sqale.mapping.UpdatableItemSqlMapper;
import com.evolveum.midpoint.repo.sqale.update.SqaleUpdateContext;
import com.evolveum.midpoint.repo.sqlbase.RepositoryException;
import com.evolveum.midpoint.repo.sqlbase.filtering.ValueFilterProcessor;
Expand Down Expand Up @@ -51,8 +51,8 @@ public void process(ItemDelta<?, ?> modification) throws RepositoryException, Sc

QueryModelMapping<?, ?, ?> mapping = context.mapping();
ItemSqlMapper<?, ?> itemSqlMapper = mapping.getItemMapper(itemName);
if (itemSqlMapper instanceof SqaleItemSqlMapper) {
((SqaleItemSqlMapper<?, ?, ?>) itemSqlMapper)
if (itemSqlMapper instanceof UpdatableItemSqlMapper) {
((UpdatableItemSqlMapper<?, ?>) itemSqlMapper)
.createItemDeltaProcessor(context)
.process(modification);
} else if (itemSqlMapper != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public <Q extends FlexibleRelationalPathBase<R>, R> ArrayItemDeltaProcessor(
Class<E> elementType,
@Nullable Function<V, E> conversionFunction) {
super(context);
this.path = rootToQueryItem.apply(context.path());
this.path = rootToQueryItem.apply(context.entityPath());
this.elementType = elementType;
this.conversionFunction = conversionFunction;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* Copyright (C) 2010-2021 Evolveum and contributors
*
* This work is dual-licensed under the Apache License 2.0
* and European Union Public License. See LICENSE file for details.
*/
package com.evolveum.midpoint.repo.sqale.delta.item;

import java.util.Collection;

import com.evolveum.midpoint.prism.Item;
import com.evolveum.midpoint.prism.ItemDefinition;
import com.evolveum.midpoint.prism.PrismValue;
import com.evolveum.midpoint.prism.delta.ItemDelta;
import com.evolveum.midpoint.prism.path.ItemPath;
import com.evolveum.midpoint.repo.sqale.ExtensionProcessor;
import com.evolveum.midpoint.repo.sqale.delta.ItemDeltaProcessor;
import com.evolveum.midpoint.repo.sqale.qmodel.ext.MExtItemHolderType;
import com.evolveum.midpoint.repo.sqale.update.ExtensionUpdateContext;
import com.evolveum.midpoint.repo.sqale.update.SqaleUpdateContext;
import com.evolveum.midpoint.repo.sqlbase.RepositoryException;
import com.evolveum.midpoint.util.exception.SchemaException;

public class ExtensionItemDeltaProcessor implements ItemDeltaProcessor {

private final ExtensionUpdateContext<?, ?, ?> context;
private final MExtItemHolderType holderType;

/**
* Constructs delta processor for extension item inside JSONB column.
* Takes more general context type for caller's sake, but it is {@link ExtensionUpdateContext}.
*/
public ExtensionItemDeltaProcessor(
SqaleUpdateContext<?, ?, ?> context, MExtItemHolderType holderType) {
this.context = (ExtensionUpdateContext<?, ?, ?>) context;
this.holderType = holderType;
}

@Override
public void process(ItemDelta<?, ?> modification) throws RepositoryException, SchemaException {
ItemPath itemPath = modification.getPath();
Item<PrismValue, ?> item = context.findItem(itemPath);
Collection<?> realValues = item != null ? item.getRealValues() : null;
ItemDefinition<?> definition = modification.getDefinition();

ExtensionProcessor extProcessor = new ExtensionProcessor(context.repositoryContext());
ExtensionProcessor.ExtItemInfo extItemInfo =
extProcessor.findExtensionItem(definition, holderType);
if (extItemInfo == null) {
return; // not-indexed, no action
}

if (realValues == null || realValues.isEmpty()) {
context.deleteItem(extItemInfo.getId());
return;
}

// changed value
context.setChangedItem(extItemInfo.getId(), extProcessor.extItemValue(item, extItemInfo));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ public <Q extends FlexibleRelationalPathBase<R>, R> PolyStringItemDeltaProcessor
Function<Q, StringPath> origMapping,
Function<Q, StringPath> normMapping) {
super(context);
this.origPath = origMapping.apply(context.path());
this.normPath = normMapping.apply(context.path());
this.origPath = origMapping.apply(context.entityPath());
this.normPath = normMapping.apply(context.entityPath());
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ public <Q extends FlexibleRelationalPathBase<R>, R> RefItemDeltaProcessor(
Function<Q, EnumPath<MObjectType>> rootToTypePath,
Function<Q, NumberPath<Integer>> rootToRelationIdPath) {
this(context,
rootToOidPath.apply(context.path()),
rootToTypePath != null ? rootToTypePath.apply(context.path()) : null,
rootToRelationIdPath != null ? rootToRelationIdPath.apply(context.path()) : null);
rootToOidPath.apply(context.entityPath()),
rootToTypePath != null ? rootToTypePath.apply(context.entityPath()) : null,
rootToRelationIdPath != null ? rootToRelationIdPath.apply(context.entityPath()) : null);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public class SinglePathItemDeltaProcessor<T, P extends Path<T>>
public <Q extends FlexibleRelationalPathBase<R>, R> SinglePathItemDeltaProcessor(
SqaleUpdateContext<?, Q, R> context, Function<Q, P> rootToQueryItem) {
super(context);
this.path = rootToQueryItem.apply(context.path());
this.path = rootToQueryItem.apply(context.entityPath());
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@
import com.evolveum.midpoint.prism.query.RefFilter;
import com.evolveum.midpoint.prism.query.ValueFilter;
import com.evolveum.midpoint.repo.sqale.ExtUtils;
import com.evolveum.midpoint.repo.sqale.ExtensionProcessor;
import com.evolveum.midpoint.repo.sqale.SqaleQueryContext;
import com.evolveum.midpoint.repo.sqale.SqaleRepoContext;
import com.evolveum.midpoint.repo.sqale.jsonb.JsonbPath;
import com.evolveum.midpoint.repo.sqale.qmodel.ext.MExtItem;
import com.evolveum.midpoint.repo.sqale.qmodel.ext.MExtItemHolderType;
Expand Down Expand Up @@ -68,7 +70,7 @@ public class ExtensionItemFilterProcessor extends ItemFilterProcessor<ValueFilte
public static final String POLY_STRING_TYPE = QNameUtil.qNameToUri(PolyStringType.COMPLEX_TYPE);

private final MExtItemHolderType holderType;
protected final JsonbPath path;
private final JsonbPath path;

public ExtensionItemFilterProcessor(
SqlQueryContext<?, ?, ?> context,
Expand All @@ -83,7 +85,7 @@ public ExtensionItemFilterProcessor(
@Override
public Predicate process(ValueFilter<?, ?> filter) throws RepositoryException {
ItemDefinition<?> definition = filter.getDefinition();
MExtItem extItem = ((SqaleQueryContext<?, ?, ?>) context).repositoryContext()
MExtItem extItem = new ExtensionProcessor((SqaleRepoContext) context.repositoryContext())
.resolveExtensionItem(definition, holderType);
assert definition != null;
if (extItem == null) {
Expand Down

0 comments on commit 4687aa0

Please sign in to comment.