/*
 * Copyright (c) 2011, 2018 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0,
 * or the Eclipse Distribution License v. 1.0 which is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
 */

// Contributors:
//     11/10/2011-2.4 Guy Pelletier
//       - 357474: Address primaryKey option from tenant discriminator column
package org.eclipse.persistence.mappings;

import org.eclipse.persistence.exceptions.DescriptorException;
import org.eclipse.persistence.internal.descriptors.MultitenantPrimaryKeyAccessor;
import org.eclipse.persistence.internal.helper.DatabaseField;
import org.eclipse.persistence.internal.identitymaps.CacheKey;
import org.eclipse.persistence.internal.queries.JoinedAttributeManager;
import org.eclipse.persistence.internal.sessions.AbstractRecord;
import org.eclipse.persistence.internal.sessions.AbstractSession;
import org.eclipse.persistence.internal.sessions.ChangeRecord;
import org.eclipse.persistence.internal.sessions.MergeManager;
import org.eclipse.persistence.internal.sessions.ObjectChangeSet;
import org.eclipse.persistence.internal.sessions.UnitOfWorkImpl;
import org.eclipse.persistence.mappings.foundation.AbstractColumnMapping;
import org.eclipse.persistence.queries.ObjectBuildingQuery;
import org.eclipse.persistence.sessions.Session;

/**
 * <b>Purpose</b>: Maps a multitenant property to the corresponding database
 * field type. The list of field types that are supported by EclipseLink's
 * direct to field mapping is dependent on the relational database being used.
 *
 * @author Guy Pelletier
 * @since EclipseLink 2.4
 */
public class MultitenantPrimaryKeyMapping extends AbstractColumnMapping {
    private MultitenantPrimaryKeyAccessor accessor;

    /**
     * Constructor
     */
    public MultitenantPrimaryKeyMapping() {
        super();

        isInsertable = true;
        isUpdatable = false;
        setIsOptional(false);
        accessor = new MultitenantPrimaryKeyAccessor();
        setAttributeAccessor(accessor);
    }

    /**
     * INTERNAL:
     * Clone the attribute from the clone and assign it to the backup.
     *
     * This is an override from DatabaseMapping and must be implemented.
     */
    @Override
    public void buildBackupClone(Object clone, Object backup, UnitOfWorkImpl unitOfWork) {
        // Mapping is write only so nothing to do.
    }

    /**
     * INTERNAL:
     * Clone the attribute from the original and assign it to the clone.
     *
     * This is an override from DatabaseMapping and must be implemented.
     */
    @Override
    public void buildClone(Object original, CacheKey cacheKey, Object clone, Integer refreshCascade, AbstractSession cloningSession) {
        // Mapping is write only so nothing to do.
    }

    /**
     * INTERNAL:
     * Extract value from the row and set the attribute to this value in the
     * working copy clone.
     * In order to bypass the shared cache when in transaction a UnitOfWork must
     * be able to populate working copies directly from the row.
     */
    @Override
    public void buildCloneFromRow(AbstractRecord databaseRow, JoinedAttributeManager joinManager, Object clone, CacheKey sharedCacheKey, ObjectBuildingQuery sourceQuery, UnitOfWorkImpl unitOfWork, AbstractSession executionSession) {
        // Mapping is write only so nothing to do.
    }

    /**
     * INTERNAL:
     * Compare the clone and backup clone values and return a change record if
     * the value changed.
     *
     * This is an override from DatabaseMapping and must be implemented.
     */
    @Override
    public ChangeRecord compareForChange(Object clone, Object backUp, ObjectChangeSet owner, AbstractSession session) {
        // Mapping is write only so nothing to do.
        return null;
    }

    /**
     * INTERNAL:
     * Compare the attributes belonging to this mapping for the objects.
     *
     * This is an override from DatabaseMapping and must be implemented.
     */
    @Override
    public boolean compareObjects(Object firstObject, Object secondObject, AbstractSession session) {
        // Mapping is write only so nothing to do.
        return true;
    }

    /**
     * INTERNAL:
     */
    @Override
    public Object getFieldValue(Object propertyValue, AbstractSession session) {
        return accessor.getValue(session);
    }

    /**
     * INTERNAL:
     */
    @Override
    public Object getObjectValue(Object fieldValue, Session session) {
        return accessor.getValue(session);
    }

    /**
     * INTERNAL:
     * The mapping is initialized with the given session. This mapping is fully
     * initialized after this.
     */
    @Override
    public void initialize(AbstractSession session) throws DescriptorException {
        super.initialize(session);

        if (getField() == null) {
            session.getIntegrityChecker().handleError(DescriptorException.fieldNameNotSetInMapping(this));
        }

        setField(getDescriptor().buildField(getField()));
        setFields(collectFields());

        // Must unwrap Struct types on WLS.
        if (getField().getSqlType() == java.sql.Types.STRUCT) {
            getDescriptor().setIsNativeConnectionRequired(true);
        }
    }

    /**
     * INTERNAL:
     * Return if this mapping requires its attribute value to be cloned.
     */
    @Override
    public boolean isCloningRequired() {
        return false;
    }

    /**
     * INTERNAL
     */
    @Override
    public boolean isMultitenantPrimaryKeyMapping() {
        return true;
    }

    /**
     * INTERNAL:
     */
    public boolean isRelationalMapping() {
        return true;
    }

    /**
     * INTERNAL
     * This mapping must be write only as their is no attribute to read into.
     */
    @Override
    public boolean isWriteOnly() {
        return true;
    }

    /**
     * INTERNAL:
     * Merge changes from the source to the target object.
     *
     * This is an override from DatabaseMapping and must be implemented.
     */
    @Override
    public void mergeChangesIntoObject(Object target, ChangeRecord changeRecord, Object source, MergeManager mergeManager, AbstractSession targetSession) {
        // Mapping is write only so do nothing.
    }

    /**
     * INTERNAL:
     * Merge changes from the source to the target object. This merge is only
     * called when a changeSet for the target does not exist or the target is
     * uninitialized
     *
     * This is an override from DatabaseMapping and must be implemented.
     */
    @Override
    public void mergeIntoObject(Object target, boolean isTargetUninitialized, Object source, MergeManager mergeManager, AbstractSession targetSession) {
        // Mapping is write only so do nothing.
    }

    /**
     * INTERNAL:
     * The context property that is used to write this mapping must be set. It
     * is set as the attribute name (which gets set on the accessor)
     */
    public void setContextProperty(String contextProperty) {
        setAttributeName(contextProperty);
    }

    /**
     * INTERNAL:
     * Get a value from the object and set that in the respective field of the row.
     */
    @Override
    public void writeFromObjectIntoRow(Object object, AbstractRecord row, AbstractSession session, WriteType writeType) {
        writeValueIntoRow(row, getField(), getFieldValue(null, session));
    }

    /**
     * INTERNAL:
     * Return the Value from the object.
     */
    @Override
    public Object valueFromObject(Object anObject, DatabaseField field, AbstractSession session) {
        return accessor.getValue(session);
    }

    /**
     * INTERNAL:
     * Write fields needed for insert into the template for with null values.
     */
    @Override
    public void writeInsertFieldsIntoRow(AbstractRecord databaseRow, AbstractSession session) {
        databaseRow.add(getField(), null);
    }

    /**
     * INTERNAL:
     */
    @Override
    protected void writeValueIntoRow(AbstractRecord row, DatabaseField field, Object fieldValue) {
        row.add(getField(), fieldValue);
    }
}
