com.evolveum.midpoint.model.impl.util.Utils.java Source code

Java tutorial

Introduction

Here is the source code for com.evolveum.midpoint.model.impl.util.Utils.java

Source

/*
 * Copyright (c) 2010-2015 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.model.impl.util;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.xml.namespace.QName;

import com.evolveum.midpoint.common.crypto.CryptoUtil;
import com.evolveum.midpoint.common.refinery.RefinedObjectClassDefinition;
import com.evolveum.midpoint.common.refinery.RefinedResourceSchema;
import com.evolveum.midpoint.model.api.ModelExecuteOptions;
import com.evolveum.midpoint.model.common.expression.ExpressionVariables;
import com.evolveum.midpoint.model.impl.ModelConstants;
import com.evolveum.midpoint.model.impl.importer.ObjectImporter;
import com.evolveum.midpoint.model.impl.lens.LensContext;
import com.evolveum.midpoint.model.impl.lens.LensFocusContext;
import com.evolveum.midpoint.prism.PrismContext;
import com.evolveum.midpoint.prism.PrismObject;
import com.evolveum.midpoint.prism.PrismObjectDefinition;
import com.evolveum.midpoint.prism.PrismProperty;
import com.evolveum.midpoint.prism.PrismReference;
import com.evolveum.midpoint.prism.PrismReferenceDefinition;
import com.evolveum.midpoint.prism.PrismReferenceValue;
import com.evolveum.midpoint.prism.PrismValue;
import com.evolveum.midpoint.prism.Visitable;
import com.evolveum.midpoint.prism.Visitor;
import com.evolveum.midpoint.prism.crypto.EncryptionException;
import com.evolveum.midpoint.prism.crypto.Protector;
import com.evolveum.midpoint.prism.delta.ItemDelta;
import com.evolveum.midpoint.prism.delta.ObjectDelta;
import com.evolveum.midpoint.prism.parser.QueryConvertor;
import com.evolveum.midpoint.prism.query.ObjectFilter;
import com.evolveum.midpoint.prism.query.ObjectPaging;
import com.evolveum.midpoint.prism.query.ObjectQuery;
import com.evolveum.midpoint.prism.query.QueryJaxbConvertor;
import com.evolveum.midpoint.provisioning.api.ProvisioningService;
import com.evolveum.midpoint.repo.api.RepositoryService;
import com.evolveum.midpoint.schema.GetOperationOptions;
import com.evolveum.midpoint.schema.ResourceShadowDiscriminator;
import com.evolveum.midpoint.schema.SelectorOptions;
import com.evolveum.midpoint.schema.constants.ExpressionConstants;
import com.evolveum.midpoint.schema.constants.SchemaConstants;
import com.evolveum.midpoint.schema.processor.ObjectClassComplexTypeDefinition;
import com.evolveum.midpoint.schema.result.OperationResult;
import com.evolveum.midpoint.task.api.Task;
import com.evolveum.midpoint.util.DOMUtil;
import com.evolveum.midpoint.util.Handler;
import com.evolveum.midpoint.util.exception.CommunicationException;
import com.evolveum.midpoint.util.exception.ConfigurationException;
import com.evolveum.midpoint.util.exception.ObjectNotFoundException;
import com.evolveum.midpoint.util.exception.SchemaException;
import com.evolveum.midpoint.util.exception.SecurityViolationException;
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 com.evolveum.prism.xml.ns._public.query_3.SearchFilterType;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.Validate;
import org.w3c.dom.Element;

/**
 * @author lazyman
 */
public final class Utils {

    private static final Trace LOGGER = TraceManager.getTrace(Utils.class);
    private static final String OPERATION_RESOLVE_REFERENCE = ObjectImporter.class.getName() + ".resolveReference";

    private Utils() {
    }

    // inefficient (does not make use of LensContext resource cache)
    // and seemingly not used at all => commenting out before deleting forever
    //    public static void resolveResource(ShadowType shadow, ProvisioningService provisioning,
    //            OperationResult result) throws CommunicationException, SchemaException, ObjectNotFoundException, ConfigurationException,
    //            SecurityViolationException {
    //
    //        Validate.notNull(shadow, "Resource object shadow must not be null.");
    //        Validate.notNull(provisioning, "Provisioning service must not be null.");
    //
    //        ResourceType resource = getResource(shadow, provisioning, result);
    //        shadow.setResourceRef(null);
    //        shadow.setResource(resource);
    //    }
    //
    //    public static ResourceType getResource(ShadowType shadow, ProvisioningService provisioning,
    //            OperationResult result) throws CommunicationException, SchemaException, ObjectNotFoundException, ConfigurationException,
    //            SecurityViolationException {
    //
    //        if (shadow.getResource() != null) {
    //            return shadow.getResource();
    //        }
    //
    //        if (shadow.getResourceRef() == null) {
    //            throw new IllegalArgumentException("Couldn't resolve resource. Resource object shadow doesn't" +
    //                    " contain resource nor resource ref.");
    //        }
    //
    //        ObjectReferenceType resourceRef = shadow.getResourceRef();
    //        return provisioning.getObject(ResourceType.class, resourceRef.getOid(), null, null, result).asObjectable();
    //    }

    @Deprecated // use RepositoryService.objectSearchIterative instead
    public static <T extends ObjectType> void searchIterative(RepositoryService repositoryService, Class<T> type,
            ObjectQuery query, Handler<PrismObject<T>> handler, int blockSize, OperationResult opResult)
            throws SchemaException {
        ObjectQuery myQuery = query.clone();
        // TODO: better handle original values in paging
        ObjectPaging myPaging = ObjectPaging.createPaging(0, blockSize);
        myQuery.setPaging(myPaging);
        boolean cont = true;
        while (cont) {
            List<PrismObject<T>> objects = repositoryService.searchObjects(type, myQuery, null, opResult);
            for (PrismObject<T> object : objects) {
                if (!handler.handle(object)) {
                    return;
                }
            }
            cont = objects.size() == blockSize;
            myPaging.setOffset(myPaging.getOffset() + blockSize);
        }
    }

    /**
     * Resolves references contained in given PrismObject.
     *
     * @param object
     * @param repository
     * @param enforceReferentialIntegrity If true, missing reference causes fatal error when processing (if false, only warning is issued).
     * @param forceFilterReevaluation If true, references are reevaluated even if OID is present. (Given that filter is present as well, of course.)
     * @param prismContext
     * @param result
     */
    public static <T extends ObjectType> void resolveReferences(final PrismObject<T> object,
            final RepositoryService repository, final boolean enforceReferentialIntegrity,
            final boolean forceFilterReevaluation, final PrismContext prismContext, final OperationResult result) {

        Visitor visitor = new Visitor() {
            @Override
            public void visit(Visitable visitable) {
                if (!(visitable instanceof PrismReferenceValue)) {
                    return;
                }
                resolveRef((PrismReferenceValue) visitable, repository, enforceReferentialIntegrity,
                        forceFilterReevaluation, prismContext, object.toString(), result);
            }
        };
        object.accept(visitor);
    }

    /**
     * Resolves references contained in ADD and REPLACE value sets for item modifications in a given ObjectDelta.
     * (specially treats collisions with values to be deleted)
     */

    public static <T extends ObjectType> void resolveReferences(final ObjectDelta<T> objectDelta,
            final RepositoryService repository, final boolean enforceReferentialIntegrity,
            final boolean forceFilterReevaluation, final PrismContext prismContext, final OperationResult result) {

        Visitor visitor = new Visitor() {
            @Override
            public void visit(Visitable visitable) {
                if (!(visitable instanceof PrismReferenceValue)) {
                    return;
                }
                resolveRef((PrismReferenceValue) visitable, repository, enforceReferentialIntegrity,
                        forceFilterReevaluation, prismContext, objectDelta.toString(), result);
            }
        };
        // We could use objectDelta.accept(visitor), but we want to visit only values to add and replace
        // (NOT values to delete! - otherwise very strange effects could result)

        // Another problem is that it is possible that one of valuesToAdd became (after resolving)
        // a value that is meant do be deleted. The result would be deletion of that value; definitely
        // not what we would want or expect. So we have to check whether a value that was not among
        // values to be deleted accidentally becomes one of values to be deleted.
        if (objectDelta.isAdd()) {
            objectDelta.getObjectToAdd().accept(visitor);
        } else if (objectDelta.isModify()) {
            for (ItemDelta<?, ?> delta : objectDelta.getModifications()) {
                applyVisitorToValues(delta.getValuesToAdd(), delta, visitor);
                applyVisitorToValues(delta.getValuesToReplace(), delta, visitor);
            }
        }
    }

    // see description in caller
    private static void applyVisitorToValues(Collection<? extends PrismValue> values, ItemDelta<?, ?> delta,
            Visitor visitor) {
        Collection<? extends PrismValue> valuesToDelete = delta.getValuesToDelete();
        if (valuesToDelete == null) {
            valuesToDelete = new ArrayList<>(0); // just to simplify the code below
        }
        if (values != null) {
            for (PrismValue pval : values) {
                boolean isToBeDeleted = valuesToDelete.contains(pval);
                pval.accept(visitor);
                if (!isToBeDeleted && valuesToDelete.contains(pval)) {
                    // value becomes 'to be deleted' -> we remove it from toBeDeleted list
                    ((ItemDelta<PrismValue, ?>) delta).removeValueToDelete(pval);
                }
            }
        }
    }

    private static void resolveRef(PrismReferenceValue refVal, RepositoryService repository,
            boolean enforceReferentialIntegrity, boolean forceFilterReevaluation, PrismContext prismContext,
            String contextDesc, OperationResult parentResult) {
        QName refName = refVal.getParent().getElementName();
        OperationResult result = parentResult.createSubresult(OPERATION_RESOLVE_REFERENCE);
        result.addContext(OperationResult.CONTEXT_ITEM, refName);

        QName typeQName = null;
        if (refVal.getTargetType() != null) {
            typeQName = refVal.getTargetType();
        }
        if (typeQName == null) {
            PrismReferenceDefinition definition = (PrismReferenceDefinition) refVal.getParent().getDefinition();
            if (definition != null) {
                typeQName = definition.getTargetTypeName();
            }
        }
        Class<? extends ObjectType> type = ObjectType.class;
        if (typeQName != null) {
            type = (Class) prismContext.getSchemaRegistry().determineCompileTimeClass(typeQName);
            if (type == null) {
                result.recordWarning("Unknown type specified in reference or definition of reference " + refName
                        + ": " + typeQName);
                type = ObjectType.class;
            }
        }
        SearchFilterType filter = refVal.getFilter();

        if (!StringUtils.isBlank(refVal.getOid()) && (!forceFilterReevaluation || filter == null)) {
            // We have OID (and "force filter reevaluation" is not requested or not possible)
            if (filter != null) {
                // We have both filter and OID. We will choose OID, but let's at
                // least log a warning
                LOGGER.debug("Both OID and filter for property {} in {}, OID takes precedence", refName,
                        contextDesc);
            }
            // Nothing to resolve, but let's check if the OID exists
            PrismObject<? extends ObjectType> object = null;
            try {
                object = repository.getObject(type, refVal.getOid(), null, result);
            } catch (ObjectNotFoundException e) {
                String message = "Reference " + refName + " refers to a non-existing object " + refVal.getOid();
                if (enforceReferentialIntegrity) {
                    LOGGER.error(message);
                    result.recordFatalError(message);
                } else {
                    LOGGER.warn(message);
                    result.recordWarning(message);
                }
            } catch (SchemaException e) {

                result.recordPartialError(
                        "Schema error while trying to retrieve object " + refVal.getOid() + " : " + e.getMessage(),
                        e);
                LOGGER.error(
                        "Schema error while trying to retrieve object " + refVal.getOid() + " : " + e.getMessage(),
                        e);
                // But continue otherwise
            }
            if (object != null && refVal.getOriginType() != null) {
                // Check if declared and actual type matches
                if (!object.getClass().equals(type)) {
                    result.recordWarning("Type mismatch on property " + refName + ": declared:"
                            + refVal.getOriginType() + ", actual: " + object.getClass());
                }
            }
            result.recordSuccessIfUnknown();
            parentResult.computeStatus();
            return;
        }

        if (filter == null) {
            // No OID and no filter. We are lost.
            result.recordFatalError(
                    "Neither OID nor filter for property " + refName + ": cannot resolve reference");
            return;
        }
        // No OID and we have filter. Let's check the filter a bit

        ObjectFilter objFilter;
        try {
            PrismObjectDefinition objDef = prismContext.getSchemaRegistry()
                    .findObjectDefinitionByCompileTimeClass(type);
            objFilter = QueryConvertor.parseFilter(filter, objDef);
        } catch (SchemaException ex) {
            LOGGER.error("Failed to convert object filter from filter because of: " + ex.getMessage() + "; filter: "
                    + filter.debugDump(), ex);
            throw new SystemException("Failed to convert object filter from filter. Reason: " + ex.getMessage(),
                    ex);
        }

        LOGGER.trace("Resolving using filter {}", objFilter.debugDump());
        //           // Let's do resolving
        List<PrismObject<? extends ObjectType>> objects;
        QName objectType = refVal.getTargetType();
        if (objectType == null) {
            result.recordFatalError("Missing definition of type of reference " + refName);
            return;
        }
        try {
            ObjectQuery query = ObjectQuery.createObjectQuery(objFilter);
            objects = (List) repository.searchObjects(type, query, null, result);

        } catch (SchemaException e) {
            // This is unexpected, but may happen. Record fatal error
            result.recordFatalError("Repository schema error during resolution of reference " + refName, e);
            return;
        } catch (SystemException e) {
            // We don't want this to tear down entire import.
            result.recordFatalError("Repository system error during resolution of reference " + refName, e);
            return;
        }
        if (objects.isEmpty()) {
            result.recordFatalError(
                    "Repository reference " + refName + " cannot be resolved: filter matches no object");
            return;
        }
        if (objects.size() > 1) {
            result.recordFatalError("Repository reference " + refName + " cannot be resolved: filter matches "
                    + objects.size() + " objects");
            return;
        }
        // Bingo. We have exactly one object.
        String oid = objects.get(0).getOid();
        refVal.setOid(oid);
        result.recordSuccessIfUnknown();
    }

    public static ObjectClassComplexTypeDefinition determineObjectClass(RefinedResourceSchema refinedSchema,
            Task task) throws SchemaException {

        QName objectclass = null;
        PrismProperty<QName> objectclassProperty = task
                .getExtensionProperty(ModelConstants.OBJECTCLASS_PROPERTY_NAME);
        if (objectclassProperty != null) {
            objectclass = objectclassProperty.getValue().getValue();
        }

        ShadowKindType kind = null;
        PrismProperty<ShadowKindType> kindProperty = task.getExtensionProperty(ModelConstants.KIND_PROPERTY_NAME);
        if (kindProperty != null) {
            kind = kindProperty.getValue().getValue();
        }

        String intent = null;
        PrismProperty<String> intentProperty = task.getExtensionProperty(ModelConstants.INTENT_PROPERTY_NAME);
        if (intentProperty != null) {
            intent = intentProperty.getValue().getValue();
        }

        return determineObjectClassInternal(refinedSchema, objectclass, kind, intent, task);
    }

    public static ObjectClassComplexTypeDefinition determineObjectClass(RefinedResourceSchema refinedSchema,
            PrismObject<ShadowType> shadowToImport) throws SchemaException {
        ShadowType s = shadowToImport.asObjectable();
        return determineObjectClassInternal(refinedSchema, s.getObjectClass(), s.getKind(), s.getIntent(), s);
    }

    private static ObjectClassComplexTypeDefinition determineObjectClassInternal(
            RefinedResourceSchema refinedSchema, QName objectclass, ShadowKindType kind, String intent,
            Object source) throws SchemaException {

        if (kind == null && intent == null && objectclass != null) {
            // Return generic object class definition from resource schema. No kind/intent means that we want
            // to process all kinds and intents in the object class.
            ObjectClassComplexTypeDefinition objectClassDefinition = refinedSchema
                    .findObjectClassDefinition(objectclass);
            if (objectClassDefinition == null) {
                throw new SchemaException("No object class " + objectclass + " in the schema for " + source);
            }
            return objectClassDefinition;
        }

        RefinedObjectClassDefinition refinedObjectClassDefinition;

        if (kind != null) {
            refinedObjectClassDefinition = refinedSchema.getRefinedDefinition(kind, intent);
            LOGGER.trace("Determined refined object class {} by using kind={}, intent={}",
                    new Object[] { refinedObjectClassDefinition, kind, intent });
        } else if (objectclass != null) {
            refinedObjectClassDefinition = refinedSchema.getRefinedDefinition(objectclass);
            LOGGER.trace("Determined refined object class {} by using objectClass={}",
                    new Object[] { refinedObjectClassDefinition, objectclass });
        } else {
            if (LOGGER.isTraceEnabled()) {
                LOGGER.debug("No kind or objectclass specified in {}, assuming null object class", source);
            }
            refinedObjectClassDefinition = null;
        }

        return refinedObjectClassDefinition;
    }

    public static void encrypt(Collection<ObjectDelta<? extends ObjectType>> deltas, Protector protector,
            ModelExecuteOptions options, OperationResult result) {
        // Encrypt values even before we log anything. We want to avoid showing unencrypted values in the logfiles
        if (!ModelExecuteOptions.isNoCrypt(options)) {
            for (ObjectDelta<? extends ObjectType> delta : deltas) {
                try {
                    CryptoUtil.encryptValues(protector, delta);
                } catch (EncryptionException e) {
                    result.recordFatalError(e);
                    throw new SystemException(e.getMessage(), e);
                }
            }
        }
    }

    public static void setRequestee(Task task, LensContext context) {
        PrismObject<? extends ObjectType> object;
        if (context != null && context.getFocusContext() != null
                && UserType.class.isAssignableFrom(context.getFocusContext().getObjectTypeClass())) {
            object = context.getFocusContext().getObjectAny();
        } else {
            object = null;
        }
        setRequestee(task, object);
    }

    public static <F extends ObjectType> void setRequestee(Task task, LensFocusContext<F> context) {
        setRequestee(task, context.getLensContext());
    }

    public static void setRequestee(Task task, PrismObject object) {
        LOGGER.trace("setting requestee in {} to {}", task, object);
        if (task != null) {
            task.setRequesteeTransient(object);
        }
    }

    public static void clearRequestee(Task task) {
        setRequestee(task, (PrismObject) null);
    }

    public static boolean isDryRun(Task task) throws SchemaException {

        Validate.notNull(task, "Task must not be null.");

        if (task.getExtension() == null) {
            return false;
        }

        PrismProperty<Boolean> item = task.getExtensionProperty(SchemaConstants.MODEL_EXTENSION_DRY_RUN);
        if (item == null || item.isEmpty()) {
            return false;
        }

        if (item.getValues().size() > 1) {
            throw new SchemaException("Unexpected number of values for option 'dry run'.");
        }

        Boolean dryRun = item.getValues().iterator().next().getValue();

        if (dryRun == null) {
            return false;
        }

        return dryRun.booleanValue();
    }

    public static ExpressionVariables getDefaultExpressionVariables(ObjectType focusType, ShadowType shadowType,
            ResourceType resourceType, SystemConfigurationType configurationType) {
        PrismObject<? extends ObjectType> focus = null;
        if (focusType != null) {
            focus = focusType.asPrismObject();
        }
        PrismObject<? extends ShadowType> shadow = null;
        if (shadowType != null) {
            shadow = shadowType.asPrismObject();
        }
        PrismObject<ResourceType> resource = null;
        if (resourceType != null) {
            resource = resourceType.asPrismObject();
        }
        PrismObject<SystemConfigurationType> configuration = null;
        if (configurationType != null) {
            configuration = configurationType.asPrismObject();
        }
        return getDefaultExpressionVariables(focus, shadow, null, resource, configuration);
    }

    public static ExpressionVariables getDefaultExpressionVariables(PrismObject<? extends ObjectType> focus,
            PrismObject<? extends ShadowType> shadow, ResourceShadowDiscriminator discr,
            PrismObject<ResourceType> resource, PrismObject<SystemConfigurationType> configuration) {
        ExpressionVariables variables = new ExpressionVariables();
        addDefaultExpressionVariables(variables, focus, shadow, discr, resource, configuration);
        return variables;
    }

    public static void addDefaultExpressionVariables(ExpressionVariables variables,
            PrismObject<? extends ObjectType> focus, PrismObject<? extends ShadowType> shadow,
            ResourceShadowDiscriminator discr, PrismObject<ResourceType> resource,
            PrismObject<SystemConfigurationType> configuration) {

        // Legacy. And convenience/understandability.
        if (focus == null || (focus != null && focus.canRepresent(UserType.class))
                || (discr != null && discr.getKind() == ShadowKindType.ACCOUNT)) {
            variables.addVariableDefinition(ExpressionConstants.VAR_USER, focus);
            variables.addVariableDefinition(ExpressionConstants.VAR_ACCOUNT, shadow);
        }

        variables.addVariableDefinition(ExpressionConstants.VAR_FOCUS, focus);
        variables.addVariableDefinition(ExpressionConstants.VAR_SHADOW, shadow);
        variables.addVariableDefinition(ExpressionConstants.VAR_RESOURCE, resource);
        variables.addVariableDefinition(ExpressionConstants.VAR_CONFIGURATION, configuration);
    }

    public static String getPolicyDesc(ObjectSynchronizationType synchronizationPolicy) {
        if (synchronizationPolicy == null) {
            return null;
        }
        if (synchronizationPolicy.getName() != null) {
            return synchronizationPolicy.getName();
        }
        return synchronizationPolicy.toString();
    }

    private static PrismObject<SystemConfigurationType> cachedSystemConfiguration = null;
    private static long cachedSystemConfigurationRetrieveTimestamp = 0;
    private static final long CACHED_SYSTEM_CONFIGURATION_TTL = 120000L; // just to avoid stalled data if version is not incremented for any reason

    private static final String NO_SYSTEM_CONFIG_MSG = "System configuration object was not found (should not happen in production except for initial repository loading)";

    public static PrismObject<SystemConfigurationType> getSystemConfiguration(RepositoryService repositoryService,
            OperationResult result) throws SchemaException {
        PrismObject<SystemConfigurationType> systemConfiguration = null;
        if (cachedSystemConfiguration != null && cachedSystemConfigurationRetrieveTimestamp
                + CACHED_SYSTEM_CONFIGURATION_TTL >= System.currentTimeMillis()) {
            String currentVersion;
            try {
                currentVersion = repositoryService.getVersion(SystemConfigurationType.class,
                        SystemObjectsType.SYSTEM_CONFIGURATION.value(), result);
            } catch (ObjectNotFoundException e) {
                // see below
                LOGGER.warn(NO_SYSTEM_CONFIG_MSG);
                return null;
            }
            if (currentVersion != null && currentVersion.equals(cachedSystemConfiguration.getVersion())) {
                LOGGER.trace("Using cached system configuration object; version = {}", currentVersion);
                return cachedSystemConfiguration.clone();
            }
        }
        try {
            LOGGER.trace("Cache miss: reading system configuration from the repository");
            systemConfiguration = repositoryService.getObject(SystemConfigurationType.class,
                    SystemObjectsType.SYSTEM_CONFIGURATION.value(),
                    SelectorOptions.createCollection(GetOperationOptions.createAllowNotFound()), result);
        } catch (ObjectNotFoundException e) {
            // just go on ... we will return and continue
            // This is needed e.g. to set up new system configuration is the old one gets deleted
        }
        if (systemConfiguration == null) {
            // throw new SystemException("System configuration object is null (should not happen!)");
            // This should not happen, but it happens in tests. And it is a convenient short cut. Tolerate it for now.
            LOGGER.warn(NO_SYSTEM_CONFIG_MSG);
            return null;
        }
        cachedSystemConfiguration = systemConfiguration.clone();
        cachedSystemConfigurationRetrieveTimestamp = System.currentTimeMillis();
        return systemConfiguration;
    }

    public static void clearSystemConfigurationCache() {
        cachedSystemConfiguration = null;
    }
}