Skip to content

Commit

Permalink
Limit use of ARCHIVED activation status
Browse files Browse the repository at this point in the history
This activation status was only "half-supported" from the beginning.
For example, it cannot be mapped onto ConnId __ENABLE__ attribute
(which is a boolean) and it's not supported by the simulated activation
feature either. Gradually, it became clear that this value will be
eventually removed, and replaced by the lifecycle state feature.

Hence, this commit marks the value as deprecated. Moreover, we make
sure it will not be propagated to the resources (where it isn't
supported anyway):

1. effectiveStatus property will never contain this value;
2. and the default source of the administrativeStatus outbound mapping
("Magic" computed status) will never contain it.

This resolves MID-9026.
  • Loading branch information
mederly committed Sep 20, 2023
1 parent e9ff858 commit 0a384b3
Show file tree
Hide file tree
Showing 16 changed files with 377 additions and 151 deletions.
Expand Up @@ -19,10 +19,10 @@
import com.evolveum.midpoint.xml.ns._public.common.common_3.TimeIntervalStatusType;

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

/**
* @author semancik
*
*/
public class ActivationComputer {

Expand All @@ -32,69 +32,77 @@ public ActivationComputer() {
super();
}


public ActivationComputer(Clock clock) {
super();
this.clock = clock;
}


public Clock getClock() {
return clock;
}


public void setClock(Clock clock) {
this.clock = clock;
}

public ActivationStatusType getEffectiveStatus(String lifecycleStatus, ActivationType activationType, LifecycleStateModelType stateModel) {
return getEffectiveStatus(lifecycleStatus, activationType, getValidityStatus(activationType), stateModel);
/** Never returns {@link ActivationStatusType#ARCHIVED}. */
public ActivationStatusType getEffectiveStatus(
String lifecycleStatus, ActivationType activationBean, LifecycleStateModelType stateModel) {
return getEffectiveStatus(
lifecycleStatus,
activationBean,
getValidityStatus(activationBean),
stateModel);
}

/** Never returns {@link ActivationStatusType#ARCHIVED}. */
public ActivationStatusType getEffectiveStatus(
String lifecycleStatus,
ActivationType activationType,
TimeIntervalStatusType validityStatus,
LifecycleStateModelType stateModel) {
ActivationStatusType forcedLifecycleActivationStatus = getForcedLifecycleActivationStatus(lifecycleStatus, stateModel);
if (forcedLifecycleActivationStatus != null) {
return forcedLifecycleActivationStatus;
}
@Nullable String lifecycleStatus,
@Nullable ActivationType activationBean,
@Nullable TimeIntervalStatusType validityStatus,
@Nullable LifecycleStateModelType stateModel) {

if (activationType == null) {
ActivationStatusType forcedEffectiveStatus = getForcedLifecycleEffectiveActivationStatus(lifecycleStatus, stateModel);
if (forcedEffectiveStatus != null) {
return forcedEffectiveStatus;
}
if (activationBean == null) {
return ActivationStatusType.ENABLED;
}
ActivationStatusType administrativeStatus = activationType.getAdministrativeStatus();
ActivationStatusType administrativeStatus = activationBean.getAdministrativeStatus();
if (administrativeStatus != null) {
// Explicit administrative status overrides everything
return administrativeStatus;
// Explicit administrative status overrides everything; except that ARCHIVED is converted to DISABLED
return archivedToDisabled(administrativeStatus);
}
if (validityStatus == null) {
// No administrative status, no validity. Return default.
return ActivationStatusType.ENABLED;
if (validityStatus != null) {
return validityToEffective(validityStatus);
}
switch (validityStatus) {
case AFTER:
case BEFORE:
return ActivationStatusType.DISABLED;
case IN:
return ActivationStatusType.ENABLED;
}
// This should not happen
return null;
// No administrative status, no validity. Return default.
return ActivationStatusType.ENABLED;
}

@NotNull
private static ActivationStatusType validityToEffective(@NotNull TimeIntervalStatusType validityStatus) {
return switch (validityStatus) {
case AFTER, BEFORE -> ActivationStatusType.DISABLED;
case IN -> ActivationStatusType.ENABLED;
};
}

public static ActivationStatusType archivedToDisabled(ActivationStatusType status) {
return status != ActivationStatusType.ARCHIVED ? status : ActivationStatusType.DISABLED;
}

public TimeIntervalStatusType getValidityStatus(ActivationType activationType) {
return getValidityStatus(activationType, clock.currentTimeXMLGregorianCalendar());
public TimeIntervalStatusType getValidityStatus(ActivationType activationBean) {
return getValidityStatus(activationBean, clock.currentTimeXMLGregorianCalendar());
}

public TimeIntervalStatusType getValidityStatus(ActivationType activationType, XMLGregorianCalendar referenceTime) {
if (activationType == null || referenceTime == null) {
public TimeIntervalStatusType getValidityStatus(ActivationType activationBean, XMLGregorianCalendar referenceTime) {
if (activationBean == null || referenceTime == null) {
return null;
}
XMLGregorianCalendar validFrom = activationType.getValidFrom();
XMLGregorianCalendar validTo = activationType.getValidTo();
XMLGregorianCalendar validFrom = activationBean.getValidFrom();
XMLGregorianCalendar validTo = activationBean.getValidTo();
if (validFrom == null && validTo == null) {
return null;
} else if (validTo != null && referenceTime.compare(validTo) == DatatypeConstants.GREATER) {
Expand All @@ -106,38 +114,14 @@ public TimeIntervalStatusType getValidityStatus(ActivationType activationType, X
}
}

public void computeEffective(String lifecycleStatus, ActivationType activationType, LifecycleStateModelType stateModel) {
computeEffective(lifecycleStatus, activationType, clock.currentTimeXMLGregorianCalendar(), stateModel);
}

public void computeEffective(String lifecycleStatus, ActivationType activationType, XMLGregorianCalendar referenceTime, LifecycleStateModelType stateModel) {
ActivationStatusType effectiveStatus = getForcedLifecycleActivationStatus(lifecycleStatus, stateModel);
public void setValidityAndEffectiveStatus(
String lifecycleStatus, @NotNull ActivationType activationBean, LifecycleStateModelType stateModel) {

ActivationStatusType administrativeStatus = activationType.getAdministrativeStatus();
if (effectiveStatus == null && administrativeStatus != null) {
// Explicit administrative status overrides everything
effectiveStatus = administrativeStatus;
}
TimeIntervalStatusType validityStatus = getValidityStatus(activationBean);
activationBean.setValidityStatus(validityStatus);

TimeIntervalStatusType validityStatus = getValidityStatus(activationType);
if (effectiveStatus == null) {
if (validityStatus == null) {
// No administrative status, no validity. Defaults to enabled.
effectiveStatus = ActivationStatusType.ENABLED;
} else {
switch (validityStatus) {
case AFTER:
case BEFORE:
effectiveStatus = ActivationStatusType.DISABLED;
break;
case IN:
effectiveStatus = ActivationStatusType.ENABLED;
break;
}
}
}
activationType.setEffectiveStatus(effectiveStatus);
activationType.setValidityStatus(validityStatus);
ActivationStatusType effectiveStatus = getEffectiveStatus(lifecycleStatus, activationBean, validityStatus, stateModel);
activationBean.setEffectiveStatus(effectiveStatus);
}

public boolean lifecycleHasActiveAssignments(
Expand All @@ -162,38 +146,34 @@ private boolean defaultLifecycleHasActiveAssignments(
return true; // FIXME TEMPORARY IMPLEMENTATION, need to do something smarter when we have full lifecycle model
}

ActivationStatusType forcedLifecycleActivationStatus = getForcedLifecycleActivationStatus(lifecycleStatus, stateModel);
ActivationStatusType forcedLifecycleActivationStatus =
getForcedLifecycleEffectiveActivationStatus(lifecycleStatus, stateModel);
if (forcedLifecycleActivationStatus == null) {
return true;
}
switch (forcedLifecycleActivationStatus) {
case ENABLED:
return true;
case DISABLED:
case ARCHIVED:
return false;
default:
throw new IllegalStateException("Unknown forced activation "+forcedLifecycleActivationStatus);
}
return switch (forcedLifecycleActivationStatus) {
case ENABLED -> true;
case DISABLED -> false;
case ARCHIVED -> throw new AssertionError();
};
}


private ActivationStatusType getForcedLifecycleActivationStatus(String lifecycleStatus, LifecycleStateModelType stateModel) {
/** Never returns {@link ActivationStatusType#ARCHIVED}. */
private ActivationStatusType getForcedLifecycleEffectiveActivationStatus(
String lifecycleStatus, LifecycleStateModelType stateModel) {
LifecycleStateType stateDefinition = LifecycleUtil.findStateDefinition(stateModel, lifecycleStatus);
if (stateDefinition == null) {
return getHardcodedForcedLifecycleActivationStatus(lifecycleStatus);
}
return stateDefinition.getForcedActivationStatus();
return archivedToDisabled(
stateDefinition.getForcedActivationStatus());
}


private ActivationStatusType getHardcodedForcedLifecycleActivationStatus(String lifecycleStatus) {
if (lifecycleStatus == null
|| lifecycleStatus.equals(SchemaConstants.LIFECYCLE_ACTIVE)
|| lifecycleStatus.equals(SchemaConstants.LIFECYCLE_DEPRECATED)) {
return null;
} else if (lifecycleStatus.equals(SchemaConstants.LIFECYCLE_ARCHIVED)) {
return ActivationStatusType.ARCHIVED;
} else {
return ActivationStatusType.DISABLED;
}
Expand Down
Expand Up @@ -96,7 +96,7 @@ public void testGetAdministrativeArchived() throws Exception {
activationComputer.getEffectiveStatus(null, activationType, createLifecycleModel());

// THEN
assertEquals("Unexpected effective status", ActivationStatusType.ARCHIVED, effectiveStatus);
assertEquals("Unexpected effective status", ActivationStatusType.DISABLED, effectiveStatus);
}

@Test
Expand Down Expand Up @@ -192,7 +192,7 @@ public void testGetActiveAdministrativeArchived() throws Exception {
SchemaConstants.LIFECYCLE_ACTIVE, activationType, createLifecycleModel());

// THEN
assertEquals("Unexpected effective status", ActivationStatusType.ARCHIVED, effectiveStatus);
assertEquals("Unexpected effective status", ActivationStatusType.DISABLED, effectiveStatus);
}

@Test
Expand All @@ -208,7 +208,7 @@ public void testGetArchivedAdministrativeEnabled() throws Exception {
SchemaConstants.LIFECYCLE_ARCHIVED, activationType, createLifecycleModel());

// THEN
assertEquals("Unexpected effective status", ActivationStatusType.ARCHIVED, effectiveStatus);
assertEquals("Unexpected effective status", ActivationStatusType.DISABLED, effectiveStatus);
}

@Test
Expand Down Expand Up @@ -281,7 +281,7 @@ public void testComputeAdministrativeEnabledAfter() throws Exception {
public void testComputeAdministrativeArchivedBefore() throws Exception {
testCompute(YEAR_START,
ActivationStatusType.ARCHIVED, SPRING_EQUINOX, AUTUMN_EQUINOX,
ActivationStatusType.ARCHIVED, TimeIntervalStatusType.BEFORE);
ActivationStatusType.DISABLED, TimeIntervalStatusType.BEFORE);
}

@Test
Expand Down Expand Up @@ -337,7 +337,7 @@ protected void testCompute(
testCompute(SchemaConstants.LIFECYCLE_SUSPENDED, now, administrativeStatus,
validFrom, validTo, ActivationStatusType.DISABLED, expectedValidity);
testCompute(SchemaConstants.LIFECYCLE_ARCHIVED, now, administrativeStatus,
validFrom, validTo, ActivationStatusType.ARCHIVED, expectedValidity);
validFrom, validTo, ActivationStatusType.DISABLED, expectedValidity);
testComputeLimbo(now, administrativeStatus, validFrom, validTo, expectedValidity);
testComputeCharmed(now, administrativeStatus, validFrom, validTo, expectedEffective, expectedValidity);
testComputeInhumed(now, administrativeStatus, validFrom, validTo, expectedEffective, expectedValidity);
Expand Down Expand Up @@ -407,7 +407,7 @@ protected void testCompute(String lifecycleState,
ActivationType activationType = createActivationType(administrativeStatus, validFrom, validTo);

// WHEN
activationComputer.computeEffective(lifecycleState, activationType, createLifecycleModel());
activationComputer.setValidityAndEffectiveStatus(lifecycleState, activationType, createLifecycleModel());

// THEN
assertEquals("Unexpected effective status", expectedEffective, activationType.getEffectiveStatus());
Expand Down
Expand Up @@ -64,7 +64,7 @@ public void testGetDraftAdministrativeEnabled() throws Exception {
SchemaConstants.LIFECYCLE_DRAFT, activationType, createLifecycleModel());

// THEN
assertEquals("Unexpected effective status", ActivationStatusType.ARCHIVED, effectiveStatus);
assertEquals("Unexpected effective status", ActivationStatusType.DISABLED, effectiveStatus);
}

@Override
Expand All @@ -74,7 +74,7 @@ protected void testComputeDraft(
ActivationStatusType expectedEffective, TimeIntervalStatusType expectedValidity)
throws SchemaException, IOException {
testCompute(SchemaConstants.LIFECYCLE_DRAFT, now, administrativeStatus,
validFrom, validTo, ActivationStatusType.ARCHIVED, expectedValidity);
validFrom, validTo, ActivationStatusType.DISABLED, expectedValidity);
}

@Override
Expand Down Expand Up @@ -114,6 +114,6 @@ protected void testComputeInhumed(
ActivationStatusType expectedEffective, TimeIntervalStatusType expectedValidity)
throws SchemaException, IOException {
testCompute(LIFECYCLE_STATE_INHUMED, now, administrativeStatus,
validFrom, validTo, ActivationStatusType.ARCHIVED, expectedValidity);
validFrom, validTo, ActivationStatusType.DISABLED, expectedValidity);
}
}
Expand Up @@ -6,17 +6,25 @@
*/
package com.evolveum.midpoint.schema.util;

import com.evolveum.midpoint.xml.ns._public.common.common_3.ActivationStatusType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ActivationType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.LockoutStatusType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.*;

/**
* @author semancik
*
*/
public class ActivationUtil {

public static ActivationStatusType getAdministrativeStatus(FocusType focus) {
if (focus == null) {
return null;
}
ActivationType activation = focus.getActivation();
if (activation == null) {
return null;
}
return activation.getAdministrativeStatus();
}

public static boolean hasAdministrativeActivation(ShadowType objectType) {
ActivationType activation = objectType.getActivation();
return activation != null && activation.getAdministrativeStatus() != null;
Expand Down
Expand Up @@ -3723,7 +3723,7 @@
</p><p>
It has been disabled, haven't reached the activation period, etc.
This is used to indicate that the entity is TEMPORARILY inactive
and there is an intent to enabled the entity later.
and there is an intent to enable the entity later.
</p><p>
Usually used for an employee on parental leave, sabbatical, temporarily disabled
account for security reasons, etc.
Expand All @@ -3749,10 +3749,16 @@
identifier to avoid their reuse.
</p><p>
Usually used for retired employees and similar cases.
</p><p>
DEPRECATED. The "archival" state is to be managed through the object lifecycle state instead.
Since 4.8, this value will not be put into "effectiveStatus" property anymore.
</p>
</xsd:documentation>
<xsd:appinfo>
<jaxb:typesafeEnumMember name="ARCHIVED"/>
<a:deprecated>true</a:deprecated>
<a:deprecatedSince>4.8</a:deprecatedSince>
<a:plannedRemoval>4.9</a:plannedRemoval>
</xsd:appinfo>
</xsd:annotation>
</xsd:enumeration>
Expand Down
Expand Up @@ -1021,7 +1021,7 @@ boolean isCurrentProjectionActivated()

/**
* Returns `true` if the current clockwork operation causes the current projection to have `administrativeState` switched to
* a disabled value (e.g. {@link ActivationStatusType#DISABLED} or {@link ActivationStatusType#ARCHIVED}.
* a disabled value (e.g. {@link ActivationStatusType#DISABLED} or {@link ActivationStatusType#ARCHIVED}).
*
* Not always precise - the original value may not be known.
*
Expand Down
Expand Up @@ -24,11 +24,11 @@ public class FocusComputer {

@Autowired private ActivationComputer activationComputer;

public void recompute(PrismObject<? extends FocusType> user, LifecycleStateModelType lifecycleModel) {
FocusType focusType = user.asObjectable();
ActivationType activationType = focusType.getActivation();
if (activationType != null) {
activationComputer.computeEffective(focusType.getLifecycleState(), activationType, lifecycleModel);
public void recompute(PrismObject<? extends FocusType> focusObject, LifecycleStateModelType lifecycleModel) {
FocusType focus = focusObject.asObjectable();
ActivationType activation = focus.getActivation();
if (activation != null) {
activationComputer.setValidityAndEffectiveStatus(focus.getLifecycleState(), activation, lifecycleModel);
}
}

Expand Down

0 comments on commit 0a384b3

Please sign in to comment.