Skip to content

Commit

Permalink
Add documentation related to shadow queries
Browse files Browse the repository at this point in the history
This resolves MID-7470.
  • Loading branch information
mederly committed Jun 29, 2022
1 parent 4c2fb43 commit 39256a0
Show file tree
Hide file tree
Showing 7 changed files with 115 additions and 51 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,11 @@ public class ResourceSchemaUtil {
*
* Basic schema is:
*
* . No kind, intent, nor object class: No definition (the `null` value) is returned.
* . No kind (or kind=UNKNOWN, if allowed) -> the decision is based on the object class name:
* . No kind, intent, nor object class is present: No definition (the `null` value) is returned.
* . Kind is not present -> the decision is based on the object class name:
* .. if there is a type definition (for given object class name) marked as "default for object class", it is returned;
* .. otherwise, the object class definition (refined, if there's any; raw otherwise) is returned.
* . Kind is present
* . Kind is present:
* .. if intent is specified, then the type definition for given kind/intent is found (and its object class is
* checked for equality with the specified one, if there's any);
* .. if no intent is specified, then "default for kind" definition is looked for.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6666,21 +6666,57 @@
<xsd:element name="kind" type="tns:ShadowKindType" minOccurs="0">
<xsd:annotation>
<xsd:documentation>
Kind of objects to be processed. TODO default value
Kind of objects to be processed.

If specified, a resource object type is looked up using provided kind and intent values. The activity
is then executed against objects of given type.

If not specified, the scope of the operation is driven by the object class.

For activities that do not select objects right on the resource (e.g. shadow cleanup),
the kind, intent, and objectclass are interpreted "literally", i.e. as filters against
values of kind, intent, and object class stored in the repository.

See documentation of searchObjects and synchronize methods in ProvisioningService for more information.
</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="intent" type="xsd:string" minOccurs="0">
<xsd:annotation>
<xsd:documentation>
Intent of objects of given kind to be processed.
Complements the kind in specifying resource object type that should be processed by the activity.
Must not be specified if kind is missing.

If kind is specified but intent is not, the object type marked as "default for kind" is selected.

For activities that do not select objects right on the resource (e.g. shadow cleanup),
the kind, intent, and objectclass are interpreted "literally", i.e. as filters against
values of kind, intent, and object class stored in the repository.

See documentation of searchObjects and synchronize methods in ProvisioningService for more information.
</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="objectclass" type="xsd:QName" minOccurs="0">
<xsd:annotation>
<xsd:documentation>
Class of objects to be processed.
Denotes the object class to be processed by the activity.

If kind (with or without intent) is specified, then the value provided here must match the object class
connected to specified resource object type. Therefore, it is not necessary nor advisable to specify
object class if kind is specified.

If kind is not specified, then the value provided here specifies the class of objects to be processed
by the activity. (For example, all of "inetOrgPerson".)

If neither kind nor object class is specified (and no custom query is provided), then the activity
is assumed to be executed on all objects on given resource. Not all activities support this mode.

For activities that do not select objects right on the resource (e.g. shadow cleanup),
the kind, intent, and objectclass are interpreted "literally", i.e. as filters against
values of kind, intent, and object class stored in the repository.

See documentation of searchObjects and synchronize methods in ProvisioningService for more information.
</xsd:documentation>
</xsd:annotation>
</xsd:element>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import com.evolveum.midpoint.prism.path.ItemPath;
import com.evolveum.midpoint.prism.query.ObjectQuery;
import com.evolveum.midpoint.provisioning.api.DiscoveredConfiguration;
import com.evolveum.midpoint.provisioning.api.ProvisioningService;
import com.evolveum.midpoint.schema.GetOperationOptions;
import com.evolveum.midpoint.schema.ObjectDeltaOperation;
import com.evolveum.midpoint.schema.ResultHandler;
Expand Down Expand Up @@ -265,25 +266,23 @@ PrismObject<? extends FocusType> searchShadowOwner(String shadowOid, Collection<
throws ObjectNotFoundException, SecurityViolationException, SchemaException, ConfigurationException, ExpressionEvaluationException, CommunicationException;

/**
* <p>
* Search for objects.
* </p>
* <p>
*
* Searches through all object of a specified type. Returns a list of objects that match
* search criteria.
* </p>
* <p>
*
* Note that this method has a very limited scaling capability
* as all the results are stored in the memory. DO NOT USE on large datasets.
* Recommended usage is only when using queries that cannot return large number
* of results (e.g. queries for unique values) or when combined with paging capability.
* For other cases use searchObjectsIterative instead.
* </p>
* <p>
*
* Returns empty list if object type is correct but there are no objects of
* that type. Fails if object type is wrong. Should fail if unknown property is
* specified in the query.
* </p>
*
* When searching for objects of {@link ShadowType}, there are specific requirements related to the query. Please see
* {@link ProvisioningService#searchObjects(Class, ObjectQuery, Collection, Task, OperationResult)} for more information.
*
* @param type
* (class) of an object to search
Expand Down Expand Up @@ -330,17 +329,16 @@ <T extends Containerable> Integer countContainers(Class<T> type, ObjectQuery que
throws SchemaException, SecurityViolationException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException;

/**
* <p>
* Search for objects in iterative fashion (using callback).
* </p>
* <p>
*
* Searches through all object of a specified type. A handler is invoked for each object found.
* </p>
* <p>
*
* The handler is not called at all if object type is correct but there are no objects of
* that type. Fails if object type is wrong. Should fail if unknown property is
* specified in the query.
* </p>
*
* When searching for objects of {@link ShadowType}, there are specific requirements related to the query. Please see
* {@link ProvisioningService#searchObjects(Class, ObjectQuery, Collection, Task, OperationResult)} for more information.
*
* @param type
* (class) of an object to search
Expand Down Expand Up @@ -375,14 +373,14 @@ <T extends ObjectType> SearchResultMetadata searchObjectsIterative(Class<T> type
SecurityViolationException, ExpressionEvaluationException;

/**
* <p>
* Count objects.
* </p>
* <p>
*
* Searches through all object of a specified type and returns a count of such objects.
* This method is usually much more efficient than equivalent search method. It is used mostly for
* presentation purposes, e.g. displaying correct number of pages in the GUI listings.
* </p>
*
* When counting objects of {@link ShadowType}, there are specific requirements related to the query. Please see
* {@link ProvisioningService#searchObjects(Class, ObjectQuery, Collection, Task, OperationResult)} for more information.
*
* @param type
* (class) of an object to search
Expand Down Expand Up @@ -420,7 +418,7 @@ <T extends ObjectType> Integer countObjects(Class<T> type, ObjectQuery query, Co
* Test the resource connection and basic resource connector functionality.
* </p>
* <p>
* Work same as {@link com.evolveum.midpoint.provisioning.api.ProvisioningService#testResource(PrismObject, Task, OperationResult)}.
* Work same as {@link ProvisioningService#testResource(PrismObject, Task, OperationResult)}.
* </p>
*
* @param resourceOid OID of resource to test
Expand All @@ -438,7 +436,7 @@ OperationResult testResource(String resourceOid, Task task, OperationResult pare
* </p>
* <p>
* Method work with OperationResult same as method
* {@link com.evolveum.midpoint.provisioning.api.ProvisioningService#testResource(PrismObject, Task, OperationResult)}.
* {@link ProvisioningService#testResource(PrismObject, Task, OperationResult)}.
* </p>
*
* @param resource resource to test
Expand All @@ -451,7 +449,7 @@ OperationResult testResourcePartialConfiguration(PrismObject<ResourceType> resou
/**
* <p>
* Method work same as
* {@link com.evolveum.midpoint.provisioning.api.ProvisioningService#discoverConfiguration(PrismObject, OperationResult)}.
* {@link ProvisioningService#discoverConfiguration(PrismObject, OperationResult)}.
* </p>
*
* @param resource resource with minimal connector configuration
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ public ObjectQuery customizeQuery(ObjectQuery configuredQuery, OperationResult r

@Override
public Collection<SelectorOptions<GetOperationOptions>> customizeSearchOptions(
Collection<SelectorOptions<GetOperationOptions>> configuredOptions, OperationResult result) throws CommonException {
Collection<SelectorOptions<GetOperationOptions>> configuredOptions, OperationResult result) {
return GetOperationOptions.updateToNoFetch(configuredOptions);
}

Expand All @@ -189,10 +189,10 @@ public static class MyWorkDefinition extends AbstractWorkDefinition implements R
@NotNull private final Duration interval;

MyWorkDefinition(WorkDefinitionSource source) {
if (source instanceof LegacyWorkDefinitionSource) {
LegacyWorkDefinitionSource legacy = (LegacyWorkDefinitionSource) source;
if (source instanceof LegacyWorkDefinitionSource legacy) {
shadows = ResourceObjectSetUtil.fromLegacySource(legacy);
interval = legacy.getExtensionItemRealValue(SchemaConstants.LEGACY_NOT_UPDATED_DURATION_PROPERTY_NAME, Duration.class);
interval = legacy.getExtensionItemRealValue(
SchemaConstants.LEGACY_NOT_UPDATED_DURATION_PROPERTY_NAME, Duration.class);
} else {
ShadowCleanupWorkDefinitionType typedDefinition = (ShadowCleanupWorkDefinitionType)
((TypedWorkDefinitionWrapper) source).getTypedDefinition();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,13 @@
import java.util.List;
import java.util.Set;

import com.evolveum.midpoint.prism.query.ObjectFilter;
import com.evolveum.midpoint.schema.constants.TestResourceOpNames;

import com.evolveum.midpoint.schema.processor.*;

import com.evolveum.midpoint.schema.util.ObjectQueryUtil;

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

Expand Down Expand Up @@ -254,30 +257,43 @@ void processAsynchronousUpdates(@NotNull ResourceOperationCoordinates coordinate
* The method call should fail if object type is wrong. Should fail if the query is wrong, e.g. if it contains
* a reference to an unknown attribute.
*
* == Specifying the coordinates
* == Specifying the "coordinates"
*
* When dealing with shadow queries in non-raw mode, there are the following requirements on the query:
*
* When dealing with shadow queries in non-raw mode, there are the following requirements:
* . there must be exactly one `resourceRef` obtainable from the query (i.e. present in the conjunction at the root level),
* . and
* .. either `kind` is specified (optionally with `intent` and/or `objectClass`),
* .. or `objectClass` is specified (and both `kind` and `intent` are not specified).
*
* - there must be exactly one `resourceRef` obtainable from the query (i.e. present in the conjunction at the root level),
* - there must be either `objectclass` or `kind` (optionally with `intent`) obtainable from the query (or both).
* See also {@link ObjectQueryUtil#getOperationCoordinates(ObjectFilter)}.
*
* (For the raw mode the requirements are currently the same; however, we may relax them in the future.)
*
* == Determining the object class or type to be searched for
* == Interpreting the query for on-resource search
*
* When issuing the on-resource search, the object class of objects must be known; and sometimes even more information
* (like whose attributes are returned by default and whose have to be requested explicitly) needs to be known.
* === If `kind` is specified
*
* Technically speaking, we have to know the object class name, and - sometimes - even the refined object class
* (or object type) definition.
* In this case, a specific _object type_ definition is looked up: either by `kind` and `intent` (if the latter is present),
* or - if `intent` is not specified - by looking for a type marked as "default for its kind". The search is then executed
* against this object type, taking its delineation (object class, base context, additional filters) into account.
*
* The object class or type used for on-resource search is then determined like this:
* If `objectClass` is specified as well, it is just checked against the value in given object type definition. (Hence it
* is not necessary nor advisable to specify object class when kind is specified.)
*
* - if `kind` and `intent` are specified, the appropriate type is looked for;
* - if `kind` is specified but `intent` is not, the default type for given kind is looked for;
* - if `kind` is not specified, `objectclass` is used to find the most appropriate object class or object type definition.
* === If only `objectClass` is specified
*
* See {@link ResourceSchemaUtil#findDefinitionForBulkOperation(ResourceType, ShadowKindType, String, QName)} for
* Here the implementation searches for all objects of given `objectClass`. However, there are few things that must be done
* during the search, for example determining "attributes to get" i.e. what attributes should be explicitly requested.
* These things depend on object class or object type definition. Therefore, the implementation has to look up appropriate
* definition first.
*
* It does so by looking up raw or refined object class definition. If there is a type definition marked as "default for
* object class", it is used. However, if such a type definition is found, the delineation (base context, filters, and so on)
* are ignored: as stated above, all objects of given object class are searched for.
*
* See {@link ResourceSchemaUtil#findDefinitionForBulkOperation(ResourceType, ShadowKindType, String, QName)}
* and {@link ResourceSchemaUtil#findObjectDefinitionPrecisely(ResourceType, ShadowKindType, String, QName)} for
* the details.
*
* == Extra resource objects
Expand Down Expand Up @@ -619,6 +635,10 @@ void determineShadowState(PrismObject<ShadowType> shadow, Task task, OperationRe

/**
* Applies appropriate definition to the query.
*
* The query (for shadows) must comply with requirements similar to ones of {@link #searchObjects(Class, ObjectQuery,
* Collection, Task, OperationResult)} method. See also {@link ResourceSchemaUtil#findObjectDefinitionPrecisely(ResourceType,
* ShadowKindType, String, QName)}.
*/
<T extends ObjectType> void applyDefinition(Class<T> type, ObjectQuery query, Task task, OperationResult parentResult)
throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, ExpressionEvaluationException;
Expand Down Expand Up @@ -687,9 +707,17 @@ <O extends ObjectType, T> ItemComparisonResult compare(Class<O> type, String oid
void setSynchronizationSorterEvaluator(SynchronizationSorterEvaluator evaluator);

/**
* TODO
* Classifies resource object, i.e. determines its kind and intent (not the tag!).
*
* . Ignores existing shadow classification.
* . Invokes synchronization sorter, if it's defined for the resource.
* . Even if new classification is determined, does _not_ update shadow in the repository.
*
* This method does _not_ update shadow in the repository (with newly determined classification).
* If you need to classify an unclassified (and not fetched yet) shadow, it may be generally better
* to call {@link #getObject(Class, String, Collection, Task, OperationResult)} method. It attempts
* to classify any unclassified objects retrieved.
*
* @param combinedObject Resource object combined with its shadow. Full "shadowization" is not required.
*/
@NotNull ResourceObjectClassification classifyResourceObject(
@NotNull ShadowType combinedObject,
Expand All @@ -700,7 +728,10 @@ <O extends ObjectType, T> ItemComparisonResult compare(Class<O> type, String oid
SecurityViolationException, ConfigurationException, ObjectNotFoundException;

/**
* TODO
* Generates shadow tag (for multi-account scenarios).
*
* . Ignores existing shadow tag.
* . Does _not_ update shadow in the repository.
*/
@Nullable String generateShadowTag(
@NotNull ShadowType combinedObject,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,6 @@
*
* Note about the "unknown" values for kind/intent: They should come _only_ when determining
* a definition for given shadow. They should never be requested by the client of provisioning API.
*
* TODO implement MID-7470
*/
@Component
public class ProvisioningContextFactory {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,8 @@
* * Creating and closing the operation-level {@link OperationResult} (and recording any exceptions in the standard manner).
* More complex methods (e.g. `getObject`, `searchObjects`, `searchObjectsIterative`) may *clean up* the result before returning.
* (Except for auxiliary methods like {@link #classifyResourceObject(ShadowType, ResourceType,
* ObjectSynchronizationDiscriminatorType, Task, OperationResult)} and {@link ProvisioningService#generateShadowTag(ShadowType, ResourceType, ResourceObjectDefinition, Task, OperationResult)}).
* ObjectSynchronizationDiscriminatorType, Task, OperationResult)} and {@link ProvisioningService#generateShadowTag(ShadowType,
* ResourceType, ResourceObjectDefinition, Task, OperationResult)}).
*
* * Logging at the operation level.
*
Expand Down

0 comments on commit 39256a0

Please sign in to comment.