/*
 * Copyright (c) 1998, 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:
//     Oracle - initial API and implementation from Oracle TopLink
package org.eclipse.persistence.internal.indirection;

import java.io.Serializable;

import org.eclipse.persistence.exceptions.DatabaseException;
import org.eclipse.persistence.indirection.ValueHolderInterface;
import org.eclipse.persistence.indirection.WeavedAttributeValueHolderInterface;
import org.eclipse.persistence.internal.helper.Helper;
import org.eclipse.persistence.internal.localization.ToStringLocalization;
import org.eclipse.persistence.internal.sessions.AbstractRecord;
import org.eclipse.persistence.internal.sessions.AbstractSession;
import org.eclipse.persistence.internal.sessions.UnitOfWorkImpl;

/**
 * DatabaseValueHolder wraps a database-stored object and implements
 * behavior to access it. The object is read only once from database
 * after which is cached for faster access.
 *
 * @see ValueHolderInterface
 * @author    Dorin Sandu
 */
public abstract class DatabaseValueHolder implements WeavedAttributeValueHolderInterface, Cloneable, Serializable {

    /** Stores the object after it is read from the database. */
    protected volatile Object value;

    /** Indicates whether the object has been read from the database or not. */
    protected volatile boolean isInstantiated;

    /** Stores the session for the database that contains the object. */
    protected transient AbstractSession session;

    /** Stores the row representation of the object. */
    // Cannot be transient as may be required to extract the pk from a serialized object.
    protected AbstractRecord row;

    /**
     * The variable below is used as part of the implementation of WeavedAttributeValueHolderInterface
     * It is used to track whether a valueholder that has been weaved into a class is coordinated
     * with the underlying property
     * Set internally in EclipseLink when the state of coordination between a weaved valueholder and the underlying property is known
     */
    protected boolean isCoordinatedWithProperty = false;

    @Override
    public Object clone() {
        try {
            return super.clone();
        } catch (CloneNotSupportedException exception) {
            throw new InternalError();
        }
    }

    /**
     * Return the row.
     */
    public AbstractRecord getRow() {
        return row;
    }

    /**
     * Return the session.
     */
    public AbstractSession getSession() {
        return session;
    }

    public ValueHolderInterface getWrappedValueHolder() {
        return null;
    }

    /**
     * Return the object.
     */
    @Override
    public Object getValue() {
        boolean instantiated = this.isInstantiated;
        if (!instantiated) {
            synchronized (this) {
                instantiated = this.isInstantiated;
                if (!instantiated) {
                    // The value must be set directly because the setValue can also cause instantiation under UOW.
                    privilegedSetValue(instantiate());
                    this.isInstantiated = true;
                    postInstantiate();
                    resetFields();
                }
            }
        }
        return value;
    }

    /**
     * Process against the UOW and attempt to load a local copy before going to the shared cache
     * If null is returned then the calling UOW will instantiate as normal.
     */
    public Object getValue(UnitOfWorkImpl uow) {
        //This method simply returns null as this will cause the UOWVH to trigger
        //the relationship normally.
        return null;
    }

    /**
     * Instantiate the object.
     */
    protected abstract Object instantiate() throws DatabaseException;

    /**
     * Triggers UnitOfWork valueholders directly without triggering the wrapped
     * valueholder (this).
     * <p>
     * When in transaction and/or for pessimistic locking the UnitOfWorkValueHolder
     * needs to be triggered directly without triggering the wrapped valueholder.
     * However only the wrapped valueholder knows how to trigger the indirection,
     * i.e. it may be a batchValueHolder, and it stores all the info like the row
     * and the query.
     * Note: Implementations of this method are not necessarily thread-safe.  They must
     * be used in a synchronized manner
     */
    public abstract Object instantiateForUnitOfWorkValueHolder(UnitOfWorkValueHolder unitOfWorkValueHolder);

    /**
     * This method is used as part of the implementation of WeavedAttributeValueHolderInterface
     * It is used to check whether a valueholder that has been weaved into a class is coordinated
     * with the underlying property
     */
    @Override
    public boolean isCoordinatedWithProperty(){
        return isCoordinatedWithProperty;
    }

    /**
     * This method is used as part of the implementation of WeavedAttributeValueHolderInterface.
     *
     * A DatabaseValueHolder is set up by TopLink and will never be a newly weaved valueholder.
     * As a result, this method is stubbed out.
     */
    @Override
    public boolean isNewlyWeavedValueHolder(){
        return false;
    }

    /**
     * INTERNAL:
     * Answers if this valueholder is easy to instantiate.
     * @return true if getValue() won't trigger a database read.
     */
    public boolean isEasilyInstantiated() {
        return this.isInstantiated;
    }

    /**
     * Return a boolean indicating whether the object
     * has been read from the database or not.
     */
    @Override
    public boolean isInstantiated() {
        return isInstantiated;
    }

    /**
     * Answers if this valueholder is a pessimistic locking one.  Such valueholders
     * are special in that they can be triggered multiple times by different
     * UnitsOfWork.  Each time a lock query will be issued.  Hence even if
     * instantiated it may have to be instantiated again, and once instantiated
     * all fields can not be reset.
     * Note: Implementations of this method are not necessarily thread-safe.  They must
     * be used in a synchronizaed manner
     */
    public abstract boolean isPessimisticLockingValueHolder();

    /**
     * Answers if this valueholder is referenced only by a UnitOfWork valueholder.
     * I.e. it was built in valueFromRow which was called by buildCloneFromRow.
     * <p>
     * Sometimes in transaction a UnitOfWork clone, and all valueholders, are built
     * directly from the row; however a UnitOfWorkValueHolder does not know how to
     * instantiate itself so wraps this which does.
     * <p>
     * On a successful merge must be released to the session cache with
     * releaseWrappedValueHolder.
     */
    protected boolean isTransactionalValueHolder() {
        return ((session != null) && session.isUnitOfWork());
    }

    /**
     * Used to determine if this is a remote uow value holder that was serialized to the server.
     * It has no reference to its wrapper value holder, so must find its original object to be able to instantiate.
     */
    public boolean isSerializedRemoteUnitOfWorkValueHolder() {
        return false;
    }

    /**
     * INTERNAL:
     * Run any extra code required after the valueholder instantiates
     * @see QueryBasedValueHolder
     */
    public void postInstantiate(){
        //noop
    }

    /**
     * Set the object. This is used only by the privileged methods. One must be very careful in using this method.
     */
    public void privilegedSetValue(Object value) {
        this.value = value;
        isCoordinatedWithProperty = false;
    }

    /**
     * Releases a wrapped valueholder privately owned by a particular unit of work.
     * <p>
     * When unit of work clones are built directly from rows no object in the shared
     * cache points to this valueholder, so it can store the unit of work as its
     * session.  However once that UnitOfWork commits and the valueholder is merged
     * into the shared cache, the session needs to be reset to the root session, ie.
     * the server session.
     */
    public void releaseWrappedValueHolder(AbstractSession targetSession) {
        AbstractSession session = this.session;
        if ((session != null) && session.isUnitOfWork()) {
            this.session = targetSession;
        }
    }

    /**
     * Reset all the fields that are not needed after instantiation.
     */
    protected void resetFields() {
        this.row = null;
        this.session = null;
    }

    /**
     * This method is used as part of the implementation of WeavedAttributeValueHolderInterface
     * It is used internally by EclipseLink to set whether a valueholder that has been weaved into a class is coordinated
     * with the underlying property
     */
    @Override
    public void setIsCoordinatedWithProperty(boolean coordinated){
        this.isCoordinatedWithProperty = coordinated;
    }

    /**
     * This method is used as part of the implementation of WeavedAttributeValueHolderInterface
     *
     * A DatabaseValueHolder is set up by EclipseLink and will never be a newly weaved valueholder
     * As a result, this method is stubbed out.
     */
    @Override
    public void setIsNewlyWeavedValueHolder(boolean isNew){
    }

    /**
     * Set the instantiated flag to true.
     */
    public void setInstantiated() {
        isInstantiated = true;
    }

    /**
     * Set the row.
     */
    public void setRow(AbstractRecord row) {
        this.row = row;
    }

    /**
     * Set the session.
     */
    public void setSession(AbstractSession session) {
        this.session = session;
    }

    /**
     * Set the instantiated flag to false.
     */
    public void setUninstantiated() {
        isInstantiated = false;
    }

    /**
     * Set the object.
     */
    @Override
    public void setValue(Object value) {
        this.value = value;
        setInstantiated();
    }

    /**
     * INTERNAL:
     * Return if add/remove should trigger instantiation or avoid.
     * Current instantiation is avoided is using change tracking.
     */
    @Override
    public boolean shouldAllowInstantiationDeferral() {
        return true;
    }

    @Override
    public String toString() {
        if (isInstantiated()) {
            return "{" + getValue() + "}";
        } else {
            return "{" + Helper.getShortClassName(getClass()) + ": " + ToStringLocalization.buildMessage("not_instantiated", (Object[])null) + "}";
        }
    }
}
