PrometheusEncryptedDataItem.java

/*
 * Prometheus: Application Framework
 * Copyright 2012-2026. Tony Washer
 *
 * 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 io.github.tonywasher.joceanus.prometheus.data;

import io.github.tonywasher.joceanus.oceanus.base.OceanusException;
import io.github.tonywasher.joceanus.oceanus.format.OceanusDataFormatter;
import io.github.tonywasher.joceanus.metis.data.MetisDataDifference;
import io.github.tonywasher.joceanus.metis.data.MetisDataItem.MetisDataFieldId;
import io.github.tonywasher.joceanus.metis.field.MetisFieldSet;
import io.github.tonywasher.joceanus.metis.field.MetisFieldVersionedSet;
import io.github.tonywasher.joceanus.metis.list.MetisListKey;
import io.github.tonywasher.joceanus.prometheus.data.PrometheusDataKeySet.PrometheusDataKeySetList;
import io.github.tonywasher.joceanus.prometheus.data.PrometheusDataSet.PrometheusCryptographyDataType;
import io.github.tonywasher.joceanus.tethys.api.thread.TethysUIThreadStatusReport;

import java.util.Iterator;

/**
 * Encrypted Data Item and List.
 *
 * @author Tony Washer
 */
public abstract class PrometheusEncryptedDataItem
        extends PrometheusDataItem {
    /**
     * Null Encryptor.
     */
    private static final PrometheusEncryptor NULL_ENCRYPTOR = new PrometheusEncryptor(new OceanusDataFormatter(), null);

    /**
     * Report fields.
     */
    private static final MetisFieldVersionedSet<PrometheusEncryptedDataItem> FIELD_DEFS = MetisFieldVersionedSet.newVersionedFieldSet(PrometheusEncryptedDataItem.class);

    /*
     * FieldIds.
     */
    static {
        FIELD_DEFS.declareLinkField(PrometheusCryptographyDataType.DATAKEYSET);
    }

    /**
     * Standard Constructor. This creates a null encryption generator. This will be overridden when
     * a DataKeySet is assigned to the item.
     *
     * @param pList the list that this item is associated with
     * @param pId   the Id of the new item (or 0 if not yet known)
     */
    protected PrometheusEncryptedDataItem(final PrometheusEncryptedList<?> pList,
                                          final Integer pId) {
        super(pList, pId);
    }

    /**
     * Copy Constructor. This picks up the generator from the source item.
     *
     * @param pList   the list that this item is associated with
     * @param pSource the source item
     */
    protected PrometheusEncryptedDataItem(final PrometheusEncryptedList<?> pList,
                                          final PrometheusEncryptedDataItem pSource) {
        super(pList, pSource);
    }

    /**
     * Values Constructor. This creates a null encryption generator. This will be overridden when a
     * ControlKey is assigned to the item.
     *
     * @param pList   the list that this item is associated with
     * @param pValues the data values
     * @throws OceanusException on error
     */
    protected PrometheusEncryptedDataItem(final PrometheusEncryptedList<?> pList,
                                          final PrometheusDataValues pValues) throws OceanusException {
        super(pList, pValues);

        /* Access dataKeySet id */
        final Integer myId = pValues.getValue(PrometheusCryptographyDataType.DATAKEYSET, Integer.class);
        if (myId != null) {
            setDataKeySet(myId);
        }
    }

    @Override
    protected PrometheusEncryptedValues newVersionValues() {
        return new PrometheusEncryptedValues(this);
    }

    @Override
    public PrometheusEncryptedValues getValues() {
        return (PrometheusEncryptedValues) super.getValues();
    }

    @Override
    public PrometheusEncryptedValues getOriginalValues() {
        return (PrometheusEncryptedValues) super.getOriginalValues();
    }

    /**
     * Get the DataKeySet for this item.
     *
     * @return the DataKeySet
     */
    public final PrometheusDataKeySet getDataKeySet() {
        return getValues().getValue(PrometheusCryptographyDataType.DATAKEYSET, PrometheusDataKeySet.class);
    }

    /**
     * Get the DataKeySetId for this item.
     *
     * @return the DataKeySetId
     */
    public final Integer getDataKeySetId() {
        final PrometheusDataKeySet mySet = getDataKeySet();
        return (mySet == null)
                ? null
                : mySet.getIndexedId();
    }

    /**
     * Set the DataKeySet for this item.
     *
     * @param pSet the dataKeySet
     */
    private void setValueDataKeySet(final PrometheusDataKeySet pSet) {
        getValues().setUncheckedValue(PrometheusCryptographyDataType.DATAKEYSET, pSet);
    }

    /**
     * Set the KeySet id for this item.
     *
     * @param pId the keySet id
     */
    private void setValueDataKeySet(final Integer pId) {
        getValues().setUncheckedValue(PrometheusCryptographyDataType.DATAKEYSET, pId);
    }

    /**
     * Get the Encryptor.
     *
     * @return the encryptor
     */
    public PrometheusEncryptor getEncryptor() {
        final PrometheusDataKeySet myKeySet = getDataKeySet();
        return myKeySet == null ? NULL_ENCRYPTOR : myKeySet.getEncryptor();
    }

    @Override
    public PrometheusEncryptedList<?> getList() {
        return (PrometheusEncryptedList<?>) super.getList();
    }

    /**
     * Set DataKeySet.
     *
     * @param pKeySet the DataKeySet
     */
    protected final void setDataKeySet(final PrometheusDataKeySet pKeySet) {
        setValueDataKeySet(pKeySet);
    }

    /**
     * Set Next DataKeySet.
     */
    protected final void setNextDataKeySet() {
        setDataKeySet(getList().getNextDataKeySet());
    }

    /**
     * Set DataKeySet id.
     *
     * @param pKeySetId the KeySet Id
     * @throws OceanusException on error
     */
    protected final void setDataKeySet(final Integer pKeySetId) throws OceanusException {
        /* Store the id */
        setValueDataKeySet(pKeySetId);

        /* Resolve the ControlKey */
        final PrometheusDataSet myData = getDataSet();
        resolveDataLink(PrometheusCryptographyDataType.DATAKEYSET, myData.getDataKeySets());
    }

    /**
     * Set encrypted value.
     *
     * @param pFieldId the fieldId to set
     * @param pValue   the value to set
     * @throws OceanusException on error
     */
    protected final void setEncryptedValue(final MetisDataFieldId pFieldId,
                                           final Object pValue) throws OceanusException {
        /* Obtain the existing value */
        final MetisFieldDef myFieldDef = getDataFieldSet().getField(pFieldId);
        final PrometheusEncryptedValues myValueSet = getValues();
        Object myCurrent = myValueSet.getValue(myFieldDef);

        /* Handle switched usage */
        if (myCurrent != null && !(myCurrent instanceof PrometheusEncryptedPair)) {
            myCurrent = null;
        }

        /* Create the new encrypted value */
        final PrometheusEncryptedPair myCurr = (PrometheusEncryptedPair) myCurrent;
        final PrometheusEncryptedPair myField = getEncryptor().encryptValue(myCurr, pValue);

        /* Store the new value */
        myValueSet.setUncheckedValue(myFieldDef, myField);
    }

    /**
     * Set encrypted value.
     *
     * @param pFieldId   the fieldId to set
     * @param pEncrypted the encrypted value to set
     * @throws OceanusException on error
     */
    protected final void setEncryptedValue(final MetisDataFieldId pFieldId,
                                           final byte[] pEncrypted) throws OceanusException {
        /* Create the new encrypted value */
        final MetisFieldDef myFieldDef = getDataFieldSet().getField(pFieldId);
        final PrometheusEncryptedPair myField = getEncryptor().decryptValue(pEncrypted, myFieldDef);

        /* Store the new value */
        getValues().setValue(myFieldDef, myField);
    }

    /**
     * Set encrypted value.
     *
     * @param pFieldId   the fieldId to set
     * @param pEncrypted the encrypted value to set
     * @param pClazz     the class to decrypt to
     * @throws OceanusException on error
     */
    protected final void setEncryptedValue(final MetisDataFieldId pFieldId,
                                           final byte[] pEncrypted,
                                           final Class<?> pClazz) throws OceanusException {
        /* Create the new encrypted value */
        final MetisFieldDef myFieldDef = getDataFieldSet().getField(pFieldId);
        final PrometheusEncryptedPair myField = getEncryptor().decryptValue(pEncrypted, pClazz);

        /* Store the new value */
        getValues().setUncheckedValue(myFieldDef, myField);
    }

    /**
     * Determine whether two ValuePair objects differ.
     *
     * @param pCurr The current Pair
     * @param pNew  The new Pair
     * @return <code>true</code> if the objects differ, <code>false</code> otherwise
     */
    public static MetisDataDifference getDifference(final PrometheusEncryptedPair pCurr,
                                                    final PrometheusEncryptedPair pNew) {
        /* Handle case where current value is null */
        if (pCurr == null) {
            return (pNew != null)
                    ? MetisDataDifference.DIFFERENT
                    : MetisDataDifference.IDENTICAL;
        }

        /* Handle case where new value is null */
        if (pNew == null) {
            return MetisDataDifference.DIFFERENT;
        }

        /* Handle Standard cases */
        return pCurr.differs(pNew);
    }

    @Override
    public void resolveDataSetLinks() throws OceanusException {
        /* Resolve the ControlKey */
        final PrometheusDataSet myData = getDataSet();
        resolveDataLink(PrometheusCryptographyDataType.DATAKEYSET, myData.getDataKeySets());
    }

    /**
     * Adopt security for all encrypted values.
     *
     * @param pKeySet the new KeySet
     * @param pBase   the base item
     * @throws OceanusException on error
     */
    protected void adoptSecurity(final PrometheusDataKeySet pKeySet,
                                 final PrometheusEncryptedDataItem pBase) throws OceanusException {
        /* Set the DataKeySet */
        setValueDataKeySet(pKeySet);

        /* Access underlying values if they exist */
        final PrometheusEncryptedValues myBaseValues = pBase.getValues();

        /* Try to adopt the underlying */
        getValues().adoptSecurity(myBaseValues);
    }

    /**
     * Initialise security for all encrypted values.
     *
     * @param pKeySet the new KeySet
     * @throws OceanusException on error
     */
    protected void initialiseSecurity(final PrometheusDataKeySet pKeySet) throws OceanusException {
        /* Set the DataKeySet */
        setValueDataKeySet(pKeySet);

        /* Initialise security */
        getValues().adoptSecurity(null);
    }

    /**
     * Update security for all encrypted values.
     *
     * @param pKeySet the new KeySet
     * @throws OceanusException on error
     */
    protected void updateSecurity(final PrometheusDataKeySet pKeySet) throws OceanusException {
        /* Ignore call if we have the same keySet */
        if (pKeySet.equals(getDataKeySet())) {
            return;
        }

        /* Store the current detail into history */
        pushHistory();

        /* Set the DataKeySet */
        setDataKeySet(pKeySet);

        /* Update all elements */
        getValues().updateSecurity();
    }

    /**
     * Encrypted DataList.
     *
     * @param <T> the item type
     */
    public abstract static class PrometheusEncryptedList<T extends PrometheusEncryptedDataItem>
            extends PrometheusDataList<T> {
        /*
         * Report fields.
         */
        static {
            MetisFieldSet.newFieldSet(PrometheusEncryptedList.class);
        }

        /**
         * Construct an empty CORE encrypted list.
         *
         * @param pBaseClass the class of the underlying object
         * @param pData      the DataSet for the list
         * @param pItemType  the item type
         */
        protected PrometheusEncryptedList(final Class<T> pBaseClass,
                                          final PrometheusDataSet pData,
                                          final MetisListKey pItemType) {
            this(pBaseClass, pData, pItemType, PrometheusListStyle.CORE);
        }

        /**
         * Construct a generic encrypted list.
         *
         * @param pBaseClass the class of the underlying object
         * @param pData      the DataSet for the list
         * @param pItemType  the list type
         * @param pStyle     the style of the list
         */
        protected PrometheusEncryptedList(final Class<T> pBaseClass,
                                          final PrometheusDataSet pData,
                                          final MetisListKey pItemType,
                                          final PrometheusListStyle pStyle) {
            super(pBaseClass, pData, pItemType, pStyle);
        }

        /**
         * Constructor for a cloned List.
         *
         * @param pSource the source List
         */
        protected PrometheusEncryptedList(final PrometheusEncryptedList<T> pSource) {
            super(pSource);
        }

        /**
         * Get the active controlKey.
         *
         * @return the active controlKey
         */
        private PrometheusControlKey getControlKey() {
            final PrometheusControlData myControl = getDataSet().getControl();
            return (myControl == null)
                    ? null
                    : myControl.getControlKey();
        }

        /**
         * Obtain the DataKeySet to use.
         *
         * @return the DataKeySet
         */
        private PrometheusDataKeySet getNextDataKeySet() {
            final PrometheusControlKey myKey = getControlKey();
            return (myKey == null)
                    ? null
                    : myKey.getNextDataKeySet();
        }

        /**
         * Update Security for items in the list.
         *
         * @param pReport  the report
         * @param pControl the control key to apply
         * @throws OceanusException on error
         */
        public void updateSecurity(final TethysUIThreadStatusReport pReport,
                                   final PrometheusControlKey pControl) throws OceanusException {
            /* Declare the new stage */
            pReport.setNewStage(listName());

            /* Count the Number of items */
            pReport.setNumSteps(size());

            /* Loop through the items */
            final Iterator<T> myIterator = iterator();
            while (myIterator.hasNext()) {
                final T myCurr = myIterator.next();

                /* Only update if we are using the wrong controlKey */
                if (!pControl.equals(myCurr.getDataKeySet().getControlKey())) {
                    /* Update the security */
                    myCurr.updateSecurity(pControl.getNextDataKeySet());
                }

                /* Report the progress */
                pReport.setNextStep();
            }
        }

        /**
         * Adopt security from underlying list. If a match for the item is found in the underlying
         * list, its security is adopted. If no match is found then the security is initialised.
         *
         * @param pReport the report
         * @param pBase   The base list to adopt from
         * @throws OceanusException on error
         */
        protected void adoptSecurity(final TethysUIThreadStatusReport pReport,
                                     final PrometheusEncryptedList<?> pBase) throws OceanusException {
            /* Declare the new stage */
            pReport.setNewStage(listName());

            /* Count the Number of items */
            pReport.setNumSteps(size());

            /* Obtain DataKeySet list */
            final PrometheusDataSet myData = getDataSet();
            final PrometheusDataKeySetList mySets = myData.getDataKeySets();

            /* Create an iterator for our new list */
            final Iterator<T> myIterator = iterator();
            final Class<T> myClass = getBaseClass();

            /* Loop through this list */
            while (myIterator.hasNext()) {
                /* Locate the item in the base list */
                final PrometheusEncryptedDataItem myCurr = myIterator.next();
                final PrometheusEncryptedDataItem myBase = pBase.findItemById(myCurr.getIndexedId());

                /* Access target correctly */
                final T myTarget = myClass.cast(myCurr);

                /* If we have a base */
                if (myBase != null) {
                    /* Access base correctly */
                    final T mySource = myClass.cast(myBase);

                    /* Obtain required KeySet */
                    PrometheusDataKeySet mySet = myBase.getDataKeySet();
                    mySet = mySets.findItemById(mySet.getIndexedId());

                    /* Adopt the security */
                    myTarget.adoptSecurity(mySet, mySource);
                } else {
                    /* Initialise the security */
                    myTarget.initialiseSecurity(getNextDataKeySet());
                }

                /* Report the progress */
                pReport.setNextStep();
            }
        }
    }
}