/*
 * 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.lang.reflect.InvocationTargetException;
import org.eclipse.persistence.exceptions.DescriptorException;
import org.eclipse.persistence.internal.sessions.AbstractRecord;
import org.eclipse.persistence.internal.sessions.AbstractSession;
import org.eclipse.persistence.mappings.transformers.*;

/**
 *  @version $Header: TransformerBasedValueHolder.java 30-aug-2006.11:32:36 gyorke Exp $
 *  @author  mmacivor
 *  @since   release specific (what release of product did this appear in)
 *  This class is to replace the MethodBasedValueHolder for TransformationMappings
 *  Holds on to an AttributeTransformer and uses it to generate the attribute value when triggered.
 *  That Transformer may be a MethodBasedAttributeTransformer or a user defined implementation.
 *
 */
public class TransformerBasedValueHolder extends DatabaseValueHolder {

    /**
     * Stores the method to be executed. The method can take
     * one (the row) or two parameters (the row and session).
     */
    protected transient AttributeTransformer transformer;

    /**
     * Stores the object which owns this attribute.
     */
    protected transient Object object;

    /**
     * Initialize the method-based value holder.
     * @param theMethod The method that returns the object when executed.
     * @param object the Object which owns this attribute.
     * @param theRow The row representation of the object.
     * @param theSession The session to the database that stores the object.
     */
    public TransformerBasedValueHolder(AttributeTransformer theTransformer, Object theObject, AbstractRecord theRow, AbstractSession theSession) {
        super();
        row = theRow;
        session = theSession;

        // Make sure not to put a ClientSession or IsolatedClientSession in
        // the shared cache (indirectly).
        // Skip this if unitOfWork, for we use session.isUnitOfWork() to implement
        // isTransactionalValueholder(), saving us from needing a boolean instance variable.
        // If unitOfWork this safety measure is deferred until merge time with
        // releaseWrappedValuehHolder.
        // Note that if isolated session & query will return itself, which is safe
        // for if isolated this valueholder is not in the shared cache.
        if (!session.isUnitOfWork()) {
            this.session = session.getRootSession(null);
        }
        transformer = theTransformer;
        object = theObject;
    }

    /**
     * Return the method.
     */
    protected AttributeTransformer getTransformer() {
        return transformer;
    }

    /**
    * Return the receiver.
    */
    protected Object getObject() {
        return object;
    }

    /**
     * Instantiate the object by executing the method on the transformer.
     */
    protected Object instantiate() throws DescriptorException {
        return instantiate(getObject(), getSession());
    }

    protected Object instantiate(Object object, AbstractSession session) throws DescriptorException {
        try {
            return transformer.buildAttributeValue(getRow(), object, session);
        } catch (DescriptorException ex) {
            Throwable nestedException = ex.getInternalException();
            if (nestedException instanceof IllegalAccessException) {
                throw DescriptorException.illegalAccessWhileInstantiatingMethodBasedProxy(nestedException);
            } else if (nestedException instanceof IllegalArgumentException) {
                throw DescriptorException.illegalArgumentWhileInstantiatingMethodBasedProxy(nestedException);
            } else if (nestedException instanceof InvocationTargetException) {
                throw DescriptorException.targetInvocationWhileInstantiatingMethodBasedProxy(nestedException);
            } else {
                throw ex;
            }
        }
    }

    /**
     * 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: This method is not thread-safe.  It must be used in a synchronized manner
     */
    public Object instantiateForUnitOfWorkValueHolder(UnitOfWorkValueHolder unitOfWorkValueHolder) {
        return instantiate(getObject(), unitOfWorkValueHolder.getUnitOfWork());
    }

    /**
     * INTERNAL:
     * 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: This method is not thread-safe.  It must be used in a synchronized manner
     */
    public boolean isPessimisticLockingValueHolder() {
        // there is no way to tell, as a transformation mapping may have
        // a reference class or query to check, but by design there is no
        // way we can access at it.
        return false;
    }

    /**
     * Set the transformer.
     */
    protected void setTransformer(AttributeTransformer theTransformer) {
        transformer = theTransformer;
    }

    /**
     * Set the receiver.
     */
    protected void setObject(Object theObject) {
        object = theObject;
    }
}
