ObjectMapping.java revision 1efcf1f855dad06a6ee70d96ffa9ea8145e6fc9b
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 2011-2014 ForgeRock AS. All rights reserved.
*
* The contents of this file are subject to the terms
* of the Common Development and Distribution License
* (the License). You may not use this file except in
* compliance with the License.
*
* You can obtain a copy of the License at
* See the License for the specific language governing
* permission and limitations under the License.
*
* When distributing Covered Code, include this CDDL
* Header Notice in each file and include the License file
* If applicable, add the following below the CDDL Header,
* with the fields enclosed by brackets [] replaced by
* your own identifying information:
* "Portions Copyrighted [year] [name of copyright owner]"
*/
// Java SE
/**
* TODO: Description.
*
* @author Paul C. Bryan
* @author aegloff
*/
class ObjectMapping {
/**
* Event names for monitoring ObjectMapping behavior
*/
public static final Name EVENT_CREATE_OBJ = Name.get("openidm/internal/discovery-engine/sync/create-object");
public static final Name EVENT_SOURCE_ASSESS_SITUATION = Name.get("openidm/internal/discovery-engine/sync/source/assess-situation");
public static final Name EVENT_SOURCE_DETERMINE_ACTION = Name.get("openidm/internal/discovery-engine/sync/source/determine-action");
public static final Name EVENT_SOURCE_PERFORM_ACTION = Name.get("openidm/internal/discovery-engine/sync/source/perform-action");
public static final Name EVENT_CORRELATE_TARGET = Name.get("openidm/internal/discovery-engine/sync/source/correlate-target");
public static final Name EVENT_UPDATE_TARGET = Name.get("openidm/internal/discovery-engine/sync/update-target");
public static final Name EVENT_DELETE_TARGET = Name.get("openidm/internal/discovery-engine/sync/delete-target");
public static final Name EVENT_TARGET_ASSESS_SITUATION = Name.get("openidm/internal/discovery-engine/sync/target/assess-situation");
public static final Name EVENT_TARGET_DETERMINE_ACTION = Name.get("openidm/internal/discovery-engine/sync/target/determine-action");
public static final Name EVENT_TARGET_PERFORM_ACTION = Name.get("openidm/internal/discovery-engine/sync/target/perform-action");
public static final String EVENT_OBJECT_MAPPING_PREFIX = "openidm/internal/discovery-engine/sync/objectmapping/";
/**
* Event names for monitoring Reconciliation behavior
*/
public static final Name EVENT_RECON = Name.get("openidm/internal/discovery-engine/reconciliation");
public static final Name EVENT_RECON_ID_QUERIES = Name.get("openidm/internal/discovery-engine/reconciliation/id-queries-phase");
public static final Name EVENT_RECON_SOURCE = Name.get("openidm/internal/discovery-engine/reconciliation/source-phase");
public static final Name EVENT_RECON_TARGET = Name.get("openidm/internal/discovery-engine/reconciliation/target-phase");
/**
* Date util used when creating ReconObject timestamps
*/
/** TODO: Description. */
/** TODO: Description. */
/** TODO: Description. */
/** The raw mapping configuration */
/** The name of the links set to use. Defaults to mapping name. */
private String linkTypeName;
/** The link type to use */
/**
* Whether to link source IDs in a case sensitive fashion.
* Only effective if this mapping defines links, is ignored if this
* mapping re-uses another mapping's links
* Default to {@code TRUE}
*/
private Boolean sourceIdsCaseSensitive;
/**
* Whether to link target IDs in a case sensitive fashion.
* Only effective if this mapping defines links, is ignored if this
* mapping re-uses another mapping's links
* Default to {@code TRUE}
*/
private Boolean targetIdsCaseSensitive;
/** TODO: Description. */
private String sourceObjectSet;
/** TODO: Description. */
private String targetObjectSet;
/** TODO: Description. */
private Script validSource;
/** TODO: Description. */
private Script validTarget;
/** TODO: Description. */
private Script correlationQuery;
/** TODO: Description. */
/** TODO: Description. */
/** TODO: Description. */
private Script onCreateScript;
/** TODO: Description. */
private Script onUpdateScript;
/** TODO: Description. */
private Script onDeleteScript;
/** TODO: Description. */
private Script onLinkScript;
/** TODO: Description. */
private Script onUnlinkScript;
/** TODO: Description. */
private Script resultScript;
private JsonValue sourceCondition;
/**
* Whether existing links should be fetched in one go along with the source and target id lists.
* false indicates links should be retrieved individually as they are needed.
*/
private Boolean prefetchLinks;
/**
* Whether when at the outset of correlation the target set is empty (query all ids returns empty),
* it should try to correlate source entries to target when necessary.
* Default to {@code FALSE}
*/
private Boolean correlateEmptyTargetSet;
/**
* Whether to maintain links for sync-d targets
* Default to {@code TRUE}
*/
private Boolean linkingEnabled;
/**
* The number of processing threads to use in reconciliation
*/
// TODO: make configurable
int taskThreads = 10;
/** TODO: Description. */
private SynchronizationService service;
/** Whether synchronization (automatic propagation of changes as they are detected) is enabled on that mapping */
private Boolean syncEnabled;
/**
* Create an instance of a mapping between source and target
*
* @param service The associated sychronization service
* @param config The configuration for this mapping
* @throws JsonValueException if there is an issue initializing based on the configuration.
*/
}
}
if (confTaskThreads != null) {
}
correlateEmptyTargetSet = config.get("correlateEmptyTargetSet").defaultTo(Boolean.FALSE).asBoolean();
}
public boolean isSyncEnabled() {
return syncEnabled.booleanValue();
}
public boolean isLinkingEnabled() {
return linkingEnabled.booleanValue();
}
/**
* Mappings can share the same link tables.
* Establish the relationship between the mappings and determine the proper
* link type to use
* @param syncSvc the associated synchronization service
* @param allMappings The list of all existing mappings
*/
}
/**
* @return the associated synchronization service
*/
return service;
}
/**
* @return The name of the object mapping
*/
return name;
}
/**
* @return The raw config associated with the object mapping
*/
return config;
}
/**
* @return The configured name of the link set to use for this object mapping
*/
public String getLinkTypeName() {
return linkTypeName;
}
/**
* @return The resolved name of the link set to use for this object mapping
*/
public LinkType getLinkType() {
return linkType;
}
/**
* @return The mapping source object set
*/
public String getSourceObjectSet() {
return sourceObjectSet;
}
/**
* @return The mapping target object set
*/
public String getTargetObjectSet() {
return targetObjectSet;
}
/**
* @return The setting for whether to link
* source IDs in a case sensitive fashion.
* Only effective if the mapping defines the links,
* not if the mapping re-uses another mapping's links
*/
public boolean getSourceIdsCaseSensitive() {
return sourceIdsCaseSensitive.booleanValue();
}
/**
* @return The setting for whether to link
* target IDs in a case sensitive fashion.
* Only effective if the mapping defines the links,
* not if the mapping re-uses another mapping's links
*/
public boolean getTargetIdsCaseSensitive() {
return targetIdsCaseSensitive.booleanValue();
}
/**
* @see doSourceSync(String id, JsonValue value)
* Convenience function with deleted defaulted to false and oldValue defaulted to null
*/
}
/**
* Source synchronization
*
* @param id fully-qualified source object identifier.
* @param value null to have it query the source state if applicable,
* or JsonValue to tell it the value of the existing source to sync
* @param sourceDeleted Whether the source object has been deleted
* @throws SynchronizationException if sync-ing fails.
*/
private void doSourceSync(String id, JsonValue value, boolean sourceDeleted, JsonValue oldValue) throws SynchronizationException {
LOGGER.trace("Start source synchronization of {} {}", id, (value == null ? "without a value" : "with a value"));
// TODO: one day bifurcate this for synchronous and asynchronous source operation
if (sourceDeleted) {
} else {
}
}
/**
* TODO: Description.
*
* @param query TODO.
* @return TODO.
* @throws SynchronizationException TODO.
*/
throws SynchronizationException {
try {
// FIXME Decide what to do wrt query constants and leading underscores
}
service.getConnectionFactory().getConnection().query(service.getRouter(), r, new QueryResultHandler() {
// ignore
}
return true;
}
//ignore
}
});
return result;
} catch (ResourceException ose) {
throw new SynchronizationException(ose);
}
}
// TODO: maybe move all this target stuff into a target object wrapper to keep this class clean
/**
* TODO: Description.
*
* @param target TODO.
* @throws SynchronizationException TODO.
*/
try {
CreateRequest cr = Requests.newCreateRequest(targetObjectSet, target.get("_id").asString(), target);
} catch (JsonValueException jve) {
throw new SynchronizationException(jve);
} catch (ResourceException ose) {
throw new SynchronizationException(ose);
} finally {
}
return targetObject;
}
// TODO: maybe move all this target stuff into a target object wrapper to keep this class clean
/**
* TODO: Description.
*
* @param target TODO.
* @throws SynchronizationException TODO.
*/
try {
} catch (JsonValueException jve) {
throw new SynchronizationException(jve);
} catch (ResourceException ose) {
throw new SynchronizationException(ose);
} finally {
}
}
// TODO: maybe move all this target stuff into a target object wrapper to keep this class clean
/**
* TODO: Description.
*
* @param target TODO.
* @throws SynchronizationException TODO.
*/
try {
} catch (JsonValueException jve) {
throw new SynchronizationException(jve);
} catch (NotFoundException nfe) {
// forgiving delete
} catch (ResourceException ose) {
throw new SynchronizationException(ose);
} finally {
}
}
}
/**
* TODO: Description.
*
* @param source TODO.
* @param target TODO.
* @throws SynchronizationException TODO.
*/
try {
}
} finally {
}
}
/**
* @return an event name for monitoring this object mapping
*/
private Name getObjectMappingEventName() {
}
/**
* Returns {@code true} if the specified object identifer is in this mapping's source
* object set.
*/
}
if (isSourceObject(id)) {
}
}
}
public void onUpdate(String id, JsonValue oldValue, JsonValue newValue) throws SynchronizationException {
if (isSourceObject(id)) {
newValue = LazyObjectAccessor.rawReadObject(service.getRouter(), service.getConnectionFactory(), id);
}
// TODO: use old value to project incremental diff without fetch of source
if (oldValue == null || oldValue.getObject() == null || JsonPatch.diff(oldValue, newValue).size() > 0) {
} else {
}
}
}
if (isSourceObject(id)) {
}
}
/**
* Perform the reconciliation action on a pre-assessed job.
* <p/>
* For the input parameters see {@link org.forgerock.openidm.sync.impl.ObjectMapping.SourceSyncOperation#toJsonValue()} or
* {@link org.forgerock.openidm.sync.impl.ObjectMapping.TargetSyncOperation#toJsonValue()}.
* <p/>
* Script example:
* <pre>
* try {
* openidm.action('sync',recon.actionParam)
* } catch(e) {
*
* };
* </pre>
* @param params the input parameters to proceed with the pre-assessed job
* includes state from previous pre-assessment (source or target sync operation),
* plus instructions of what to execute. Specifically beyond the pre-asessed state
* it expects changes to params
* - action: the desired action to execute
* - situation (optional): the situation to expect before executing the action.
* To enforce that the action is only executed if the situation didn't change,
* supply the situation from the pre-assessment.
* To attempt execution of the action without enforcing the situation check,
* supply no situation param
* @throws SynchronizationException
*/
// If reconId is set this action is part of a reconciliation run
}
try {
try {
throw exception;
}
//TODO blank check
throw exception;
}
}
} else {
throw exception;
}
}
// IF an expected situation is supplied, compare and reject if current situation changed
throw exception;
}
}
op.performAction();
} catch (SynchronizationException se) {
} else {
}
}
}
if (reconId != null && !Action.NOREPORT.equals(action) && (entry.status == Status.FAILURE || op.action != null)) {
if (op instanceof SourceSyncOperation) {
}
} else {
}
}
}
throw exception;
}
} finally {
}
}
}
if (resultScript != null) {
try {
} catch (ScriptException se) {
throw new SynchronizationException(se);
}
}
}
/**
* Execute a full reconciliation
*
* @param reconContext the context specific to the reconciliation run
* @throws SynchronizationException if any unforseen failure occurs during the reconciliation
*/
}
/**
* TEMPORARY. Future version will have this break-down into discrete units of work.
* @param reconId
* @throws SynchronizationException
*/
try {
// Get the relevant source (and optionally target) identifiers before we assess the situations
if (!sourceIdsIter.hasNext()) {
throw new SynchronizationException("Cowardly refusing to perform reconciliation with an empty source object set");
}
// If we will handle a target phase, pre-load all relevant target identifiers
} else {
}
// Optionally get all links up front as well
if (prefetchLinks) {
}
measureSource.end();
try {
} catch (SynchronizationException se) {
}
}
}
}
}
measureTarget.end();
}
} catch (InterruptedException ex) {
} finally {
}
}
// TODO: cleanup orphan link objects (no matching source or target) here
}
/**
* Reconcile a given source ID
* @param sourceId the id to reconcile
* @param reconContext reconciliation context
* @param rootContext json resource root ctx
* @param allLinks all links if pre-queried, or null for on-demand link querying
* @throws SynchronizationException if there is a failure reported in reconciling this id
*/
Map<String, Link> allLinks, Collection<String> remainingTargetIds) throws SynchronizationException {
}
try {
} catch (SynchronizationException se) {
}
}
// If target system has case insensitive IDs, remove without regard to case
}
try {
if (op.hasTargetObject()) {
}
} catch (SynchronizationException ex) {
}
}
}
}
}
} else {
}
}
}
/**
* Wrapper to submit source recon for a given id for concurrent processing
* @author aegloff
*/
this.reconContext = reconContext;
this.parentContext = parentContext;
this.rootContext = rootContext;
this.remainingTargetIds = remainingTargetIds;
}
//TODO I miss the Request Context
try {
} finally {
}
return null;
}
}
/**
* Reconcile the source phase, multi threaded or single threaded.
* @author aegloff
*/
class SourceReconPhase extends ReconFeeder {
super(sourceIdsIter, reconContext);
this.parentContext = parentContext;
this.rootContext = rootContext;
this.remainingTargetIds = remainingTargetIds;
}
}
}
/**
* @return the configured number of threads to use for processing tasks.
* 0 to process in a single thread.
*/
int getTaskThreads() {
return taskThreads;
}
/**
* TODO: Description.
*
* @param entry TODO.
* @throws SynchronizationException TODO.
*/
try {
} catch (ResourceException ose) {
throw new SynchronizationException(ose);
}
}
private void logReconStart(String reconId, Context rootContext, ServerContext context) throws SynchronizationException {
reconStartEntry.message = "Reconciliation initiated by " + context.asContext(SecurityContext.class).getAuthenticationId();
}
private void logReconEnd(ReconciliationContext reconContext, Context rootContext, ServerContext context) throws SynchronizationException {
}
/**
* Execute a sync engine action explicitly, without going through situation assessment.
* @param sourceObject the source object if applicable to the action
* @param targetObject the target object if applicable to the action
* @param situation an optional situation that was originally assessed. Null if not the result of an earlier situation assessment.
* @param action the explicit action to invoke
* @param reconId an optional identifier for the recon context if this is done in the context of reconciliation
*/
public void explicitOp(JsonValue sourceObject, JsonValue targetObject, Situation situation, Action action, String reconId)
throws SynchronizationException {
}
/**
* TODO: Description.
*/
abstract class SyncOperation {
/** TODO: Description. */
/** An optional reconciliation context */
public ReconciliationContext reconContext;
/** Access to the source object */
/** Access to the target object */
/** Optional value of the object before the change that triggered this sync, or null if not supplied */
/**
* Holds the link representation
* An initialized link can be interpreted as representing state retrieved from the repository,
* i.e. a linkObject with id of null represents a link that does not exist (yet)
*/
// This operation newly created the link.
// linkObject above may not be set for newly created links
boolean linkCreated;
/** TODO: Description. */
/** TODO: Description. */
public boolean ignorePostAction = false;
/**
* TODO: Description.
*
* @throws SynchronizationException TODO.
*/
public abstract void sync() throws SynchronizationException;
protected abstract boolean isSourceToTarget();
/**
* @throws SynchronizationException if on-demand load of the object failed
*/
return null;
} else {
return sourceObjectAccessor.getObject();
}
}
/**
* @throws SynchronizationException if on-demand load of the object failed
*/
if (targetObjectAccessor == null || (!targetObjectAccessor.isLoaded() && targetObjectAccessor.getLocalId() == null)) {
return null;
} else {
return targetObjectAccessor.getObject();
}
}
/**
* The set unqualified (local) source object ID
* That a source identifier is set does not automatically imply that the source object exists.
* @return local identifier of the source object, or null if none
*/
protected String getSourceObjectId() {
}
/**
* The set unqualified (local) targt object ID
* That a target identifier is set does not automatically imply that the target object exists.
* @return local identifier of the target object, or null if none
*/
protected String getTargetObjectId() {
}
/**
* @return Whether the target representation is loaded, i.e. the getObject represents what it found.
* IF a target was not found, the state is loaded with a payload / object of null.
*/
protected boolean isTargetLoaded() {
}
/**
* @return Whether the source object exists. May cause the loading of the (lazy) source object,
* the object existed at that point.
* @throws SynchronizationException if on-demand load of the object failed
*/
protected boolean hasSourceObject() throws SynchronizationException {
boolean defined = false;
defined = false;
} else {
defined = true;
// If available, check against all queried existing IDs
} else {
// If no lists of existing ids is available, do a load of the object to check
}
}
return defined;
}
/**
* @return Whether the target object exists. May cause the loading of the (lazy) source object,
* the object existed at that point.
* @throws SynchronizationException if on-demand load of the object failed
*/
protected boolean hasTargetObject() throws SynchronizationException {
boolean defined = false;
if (isTargetLoaded()) {
// If it's not loaded, but no id to load is available it has no target
defined = false;
} else {
// Either check against a list of all targets, or load to check for existence
// If available, check against all queried existing IDs
// If target system has case insensitive IDs, compare without regard to case
} else {
// If no lists of existing ids is available, do a load of the object to check
}
}
return defined;
}
/**
* @return true if it knows there were no objects in the target set during a bulk query
* at the outset of reconciliation.
* false if there were objects, or it does not know.
* Does not take into account objects getting added during reconciliation, or data getting added
* by another process concurrently
*/
protected boolean hadEmptyTargetObjectSet() {
// If available, check against all queried existing IDs
} else {
return false;
}
}
/**
* @return the found unqualified (local) link ID, null if none
*/
return linkObject._id;
} else {
return null;
}
}
/**
* Initializes the link representation
* @param link the link object for links that were found/exist in the repository, null to represent no existing link
*/
this.linkObject = link;
} else {
// Keep track of the fact that we did not find a link
this.linkObject.clear();
this.linkObject.initialized = true;
}
}
}
/**
* TODO: Description.
* @param sourceAction sourceAction true if the {@link Action} is determined for the {@link SourceSyncOperation}
* and false if the action is determined for the {@link TargetSyncOperation}.
* @throws SynchronizationException TODO.
*/
// TODO: Consider limiting what actions can be returned for the given situation.
break;
}
}
}
}
/**
* TODO: Description.
*
* @throws SynchronizationException TODO.
*/
@SuppressWarnings("fallthrough")
protected void performAction() throws SynchronizationException {
switch (getAction()) {
case CREATE:
case UPDATE:
case LINK:
case DELETE:
case UNLINK:
case EXCEPTION:
try {
switch (getAction()) {
case CREATE:
if (getSourceObject() == null) {
throw new SynchronizationException("no source object to create target from");
}
if (getTargetObject() != null) {
throw new SynchronizationException("target object already exists");
}
// Allow the early link creation as soon as the target identifier is known
if (isLinkingEnabled()) {
}
if (!isLinkingEnabled()) {
"Linking disabled for {} during {}, skipping additional link processing",
break;
}
if (wasLinked) {
linkCreated = true;
"Pending link for {} during {} has already been created, skipping additional link processing",
break;
} else {
"Pending link for {} during {} not yet resolved, proceed to link processing",
}
// falls through to link the newly created target
case UPDATE:
case LINK:
if (getTargetObjectId() == null) {
throw new SynchronizationException("no target object to link");
}
try {
linkCreated = true;
} catch (SynchronizationException ex) {
// Allow for link to have been created in the meantime, e.g. programmatically
// create would fail with a failed precondition for link already existing
// Try to read again to see if that is the issue
throw ex; // it was a different issue
}
}
}
linkObject.update();
}
// TODO: Detect change of source id, and update link accordingly.
break; // do not update target
}
}
}
break; // terminate UPDATE
case DELETE:
if (getTargetObjectId() != null && getTargetObject() != null) { // forgiving; does nothing if no target
// Represent as not existing anymore so it gets removed from processed targets
}
// falls through to unlink the deleted target
case UNLINK:
linkObject.delete();
}
break; // terminate DELETE and UNLINK
case EXCEPTION:
throw new SynchronizationException("Situation " + situation + " marked as EXCEPTION"); // aborts change; recon reports
}
} catch (JsonValueException jve) {
throw new SynchronizationException(jve);
}
case REPORT:
case NOREPORT:
if (!ignorePostAction) {
if (null == activePolicy) {
break;
}
}
}
}
break;
case ASYNC:
case IGNORE:
}
}
/**
* TODO: Description.
* @param sourceAction sourceAction true if the {@link Action} is determined for the {@link SourceSyncOperation}
* and false if the action is determined for the {@link TargetSyncOperation}.
* @throws SynchronizationException TODO.
*/
if (null != activePolicy) {
}
}
protected void createLink(String sourceId, String targetId, String reconId) throws SynchronizationException {
linkObject.create();
LOGGER.debug("Established link sourceId: {} targetId: {} in reconId: {}", new Object[] {sourceId, targetId, reconId});
}
/**
* Checks any declared source conditions on the source object
*
* @return true if the conditions pass, false otherwise
* @throws SynchronizationException
*/
protected boolean checkSourceConditions() throws SynchronizationException {
if (!sourceCondition.isNull()) {
return false;
}
return false;
}
} else {
return false;
}
}
}
return true;
}
/**
* Evaluated source valid on source object
* @see isSourceValid(JsonValue)
*/
protected boolean isSourceValid() throws SynchronizationException {
return isSourceValid(null);
}
/**
* Evaluates source valid for the supplied sourceObjectOverride, or the source object
* associated with the sync operation if null
*
* @return whether valid for this mapping or not.
* @throws SynchronizationException if evaluation failed.
*/
boolean result = false;
if (validSource != null) {
if (sourceObjectOverride != null) {
} else {
// TODO: This forced load into memory is necessary until we can do on demand get in script engine
}
try {
throw new SynchronizationException("Expecting boolean value from validSource");
}
} catch (ScriptException se) {
throw new SynchronizationException(se);
}
} else { // no script means true
result = true;
}
}
if (sourceObjectOverride == null) {
}
return result;
}
/**
* TODO: Description.
*
* @return TODO.
* @throws SynchronizationException TODO.
*/
protected boolean isTargetValid() throws SynchronizationException {
boolean result = false;
if (hasTargetObject()) { // must have a target object to qualify
try {
throw new SynchronizationException("Expecting boolean value from validTarget");
}
} catch (ScriptException se) {
throw new SynchronizationException(se);
}
} else { // no script means true
result = true;
}
}
return result;
}
/**
* @see #execScript with oldTarget null
*/
}
/**
* Executes the given script with the appropriate context information
*
* @param type The script hook name
* @param script The script to execute
* @param oldTarget optional old target object before any mappings were applied,
* such as before an update
* null if not applicable to this script hook
* @throws SynchronizationException TODO.
*/
private void execScript(String type, Script script, JsonValue oldTarget) throws SynchronizationException {
// TODO: Once script engine can do on-demand get replace these forced loads
if (getSourceObjectId() != null) {
}
// Target may not have ID yet, e.g. an onCreate with the target object defined,
if (getTargetObject() != null) {
}
}
}
}
try {
} catch (ScriptThrownException se) {
throw new SynchronizationException(
} catch (ScriptException se) {
throw new SynchronizationException(
}
}
}
}
/**
* Explicit execution of a sync operation where the appropriate
* action is known without having to assess the situation and apply
* policy to decide the action
*/
private class ExplicitSyncOperation extends SyncOperation {
protected boolean isSourceToTarget() {
//TODO: detect by the source id match
return true;
}
public void init(JsonValue sourceObject, JsonValue targetObject, Situation situation, Action action, String reconId) {
this.sourceObjectAccessor = new LazyObjectAccessor(service, sourceObjectSet, sourceObject.get("_id").required().asString(), sourceObject);
this.targetObjectAccessor = new LazyObjectAccessor(service, targetObjectSet, targetObject.get("_id").required().asString(), targetObject);
this.ignorePostAction = true;
}
public void sync() throws SynchronizationException {
LOGGER.debug("Complected explicit operation call for situation: {}, action: {}", situation, action);
}
}
/**
* TODO: Description.
*/
class SourceSyncOperation extends SyncOperation {
// If it can not uniquely identify a target, the list of ambiguous target ids
@SuppressWarnings("fallthrough")
public void sync() throws SynchronizationException {
EventEntry measureSituation = Publisher.start(EVENT_SOURCE_ASSESS_SITUATION, getSourceObjectId(), null);
try {
} finally {
}
EventEntry measureDetermine = Publisher.start(EVENT_SOURCE_DETERMINE_ACTION, getSourceObjectId(), null);
try {
determineAction(true);
} finally {
}
EventEntry measurePerform = Publisher.start(EVENT_SOURCE_PERFORM_ACTION, getSourceObjectId(), null);
try {
} finally {
if (reconContext != null){
// The link ID presence after the action can not be interpreted as an indication if the link has been created
}
}
}
protected boolean isSourceToTarget() {
return true;
}
/**
* @return all found matching target identifier(s), or a 0 length array if none.
* More than one target identifier is possible for ambiguous matches
*/
public String[] getTargetIds() {
if (ambiguousTargetIds != null) {
} else if (getTargetObjectId() != null) {
} else {
}
return targetIds;
}
/**
* @return the ambiguous target identifier(s), or an empty list if no ambiguous entries are present
*/
public List getAmbiguousTargetIds() {
return ambiguousTargetIds;
}
}
}
sourceObjectAccessor = new LazyObjectAccessor(service, sourceObjectSet, params.get("sourceId").required().asString());
}
public JsonValue toJsonValue() {
}
return actionParam;
}
/**
* TODO: Description.
*
* @throws SynchronizationException TODO.
*/
private void assessSituation() throws SynchronizationException {
if (!isLinkingEnabled()) {
}
if (getSourceObjectId() != null && linkObject.initialized == false) { // In case the link was not pre-read get it here
}
}
if (!hasSourceObject()) {
/*
For sync of delete. For recon these are assessed instead in target phase
no source, link, target & valid target - source missing
no source, link, target & not valid target - target ignored
no source, link, no target - link only
no source, no link - can't correlate (no previous object available) - all gone
no source, no link - (correlate)
no target - all gone
1 target & valid (source) - unassigned
1 target & not valid (source) - target ignored
> 1 target & valid (source) - ambiguous
> 1 target & not valid (source) - unqualified
*/
if (hasTargetObject()) {
if (isTargetValid()) {
} else {
// target is not valid for this mapping; ignore it
}
} else {
}
} else {
// If there is no previous value known we can not correlate
} else {
// Correlate the old value to potential target(s)
// Results null means no correlation query defined, size 0 we know there is no target
if (valid) {
} else {
// target is not valid for this mapping; ignore it
}
if (valid) {
// Note this situation is used both when there is a source and a deleted source
// with multiple matching targets
} else {
}
}
}
}
if (hasTargetObject()) {
} else {
}
} else { // source object not linked to target
} else {
// TODO: consider enhancements:
// For reporting, should it log existing link and source
// What actions should be available for a found, already linked
}
} else {
}
}
} else { // mapping does not qualify for target
} else {
// TODO: Consider if we can optimize out the read for unqualified conditions
}
}
if (reconContext != null) {
}
}
LOGGER.debug("Mapping '{}' assessed situation of {} to be {}", new Object[]{name, getSourceObjectId(), situation});
}
/**
* Correlates (finds an associated) target for the source object
* @see correlateTarget(JsonValue)
*
* @return JsonValue if found, null if none
* @throws SynchronizationException if the correlation failed.
*/
return correlateTarget(null);
}
/**
* Correlates (finds an associated) target for the given source
* @param sourceObjectOverride optional explicitly supplied source object to correlate,
* or null to use the source object associated with the sync operation
*
* @return JsonValue if found, null if none
* @throws SynchronizationException if the correlation failed.
*/
@SuppressWarnings("unchecked")
// TODO: consider if there are cases where this would better be lazy and not get the full target
if (hasTargetObject()) {
if (sourceObjectOverride != null) {
} else {
}
try {
throw new SynchronizationException("Expected correlationQuery script to yield a Map");
}
} catch (ScriptException se) {
throw new SynchronizationException(se);
} finally {
}
}
return result;
}
/**
* Given a result entry from a correlation query get the full correlated target object
* @param resultValue an entry from the correlation query result list.
* May already be the full target object, or just contain the id.
* @return the target object
* @throws SynchronizationException
*/
private LazyObjectAccessor getCorrelatedTarget(JsonValue resultValue) throws SynchronizationException {
// TODO: Optimize to get the entire object with one query if it's sufficient
fullObj = new LazyObjectAccessor(service, targetObjectSet, resultValue.get("_id").required().asString(), resultValue);
} else {
fullObj = new LazyObjectAccessor(service, targetObjectSet, resultValue.get("_id").required().asString());
//fullObj.getObject();
}
return fullObj;
}
/**
* Primitive implementation to decide if the object is a "full" or a partial.
*
* @param keys attribute names of object
* @return true if the {@code keys} has value not starting with "_" char
*/
return true;
}
}
return false;
}
}
/**
* TODO: Description.
*/
class TargetSyncOperation extends SyncOperation {
public void sync() throws SynchronizationException {
EventEntry measureSituation = Publisher.start(EVENT_TARGET_ASSESS_SITUATION, targetObjectAccessor, null);
try {
} finally {
}
EventEntry measureDetermine = Publisher.start(EVENT_TARGET_DETERMINE_ACTION, targetObjectAccessor, null);
try {
determineAction(false);
} finally {
}
EventEntry measurePerform = Publisher.start(EVENT_TARGET_PERFORM_ACTION, targetObjectAccessor, null);
try {
// TODO: Option here to just report what action would be performed?
} finally {
if (reconContext != null) {
reconContext.getStatistics().getTargetStat().processed(getSourceObjectId(), getTargetObjectId(), linkExisted, getLinkId(), linkCreated,
}
}
}
protected boolean isSourceToTarget() {
return false;
}
}
public JsonValue toJsonValue() {
}
return actionParam;
}
/**
* TODO: Description.
*
* @throws SynchronizationException TODO.
*/
private void assessSituation() throws SynchronizationException {
// May want to consider an optimization to not query
// if we don't need the link for the TARGET_IGNORED action
}
if (!isTargetValid()) { // target is not valid for this mapping; ignore it
}
return;
}
} else {
} else if (!isSourceValid()) {
LOGGER.info("Situation in target reconciliation that indicates source may have changed {} {} {} {}",
} else { // proper link
LOGGER.info("Situation in target reconciliation that indicates source may have changed {} {} {} {}",
}
}
}
}
/**
* TEMPORARY.
*/
private class ReconEntry {
public final static String RECON_ENTRY = ""; // regular reconciliation entry has an empty entry type
/** Type of the audit log entry. Allows for marking recon start / summary records */
/** TODO: Description. */
public final SyncOperation op;
/** The id identifying the reconciliation run */
/** The root invocation context */
public final Context rootContext;
/** TODO: Description. */
/** TODO: Description. */
/** TODO: Description. */
/** TODO: Description. */
/** TODO: Description. */
public String reconciling;
/** TODO: Description. */
/** TODO: Description. */
public JsonValue messageDetail;
/** TODO: Description. */
/** TODO: Description. */
// Name of the mapping
public String mappingName;
// A comma delimited formatted representation of any ambiguous identifiers
protected String ambigiousTargetIds;
if (ambiguousIds != null) {
boolean first = true;
if (!first) {
}
first = false;
}
} else {
ambigiousTargetIds = "";
}
}
private String getReconId() {
}
/**
* Constructor that allows specifying the type of reconciliation log entry
*/
this.rootContext = rootContext;
this.mappingName = name;
}
}
/**
* Constructor for regular reconciliation log entries
*/
}
/**
* TODO: Description.
*
* @return TODO.
*/
private JsonValue toJsonValue() {
return jv;
}
}
}