/*
 * 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:
//     James Sutherland (Oracle) - initial API and implementation
package org.eclipse.persistence.descriptors.partitioning;

import org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.internal.helper.DatabaseField;
import org.eclipse.persistence.internal.sessions.AbstractSession;
import org.eclipse.persistence.sessions.server.ClientSession;

/**
 * PUBLIC:
 * FieldPartitioningPolicy partitions access to a database cluster by a field value from the object,
 * such as the object's id, location, or tenant.
 * All write or read request for object's with that value are sent to the server.
 * If a query does not include the field as a parameter, then it can either be sent
 * to all server's and unioned, or left to the sesion's default behavior.
 * @author James Sutherland
 * @since EclipseLink 2.2
 */
public abstract class FieldPartitioningPolicy extends PartitioningPolicy {

    /** The column to partition by. */
    protected DatabaseField partitionField;

    /** If query does not have the partition field in it, should the query be sent to all databases. */
    protected boolean unionUnpartitionableQueries = false;

    public FieldPartitioningPolicy() {
        super();
    }

    public FieldPartitioningPolicy(String partitionField) {
        super();
        this.partitionField = new DatabaseField(partitionField);
    }

    public FieldPartitioningPolicy(String partitionField, boolean unionUnpartitionableQueries) {
        this(partitionField);
        this.unionUnpartitionableQueries = unionUnpartitionableQueries;
    }

    /**
     * PUBLIC:
     * Return the database column or query parameter to partition queries by.
     * This is the table column name, not the class attribute name.
     * The column value must be included in the query and should normally be part of the object's Id.
     * This can also be the name of a query parameter.
     * If a query does not contain the field the query will not be partitioned.
     */
    public DatabaseField getPartitionField() {
        return partitionField;
    }

    /**
     * PUBLIC:
     * Set the database column or query parameter to partition queries by.
     * This is the table column name, not the class attribute name.
     * The column value must be included in the query and should normally be part of the object's Id.
     * This can also be the name of a query parameter.
     * If a query does not contain the field the query will not be partitioned.
     */
    public void setPartitionField(DatabaseField partitionField) {
        this.partitionField = partitionField;
    }

    /**
     * PUBLIC:
     * Return the database column or query parameter to partition queries by.
     * This is the table column name, not the class attribute name.
     * The column value must be included in the query and should normally be part of the object's Id.
     * This can also be the name of a query parameter.
     * If a query does not contain the field the query will not be partitioned.
     */
    public String getPartitionFieldName() {
        return getPartitionField().getName();
    }

    /**
     * PUBLIC:
     * Set the database column or query parameter to partition queries by.
     * This is the table column name, not the class attribute name.
     * The column value must be included in the query and should normally be part of the object's Id.
     * This can also be the name of a query parameter.
     * If a query does not contain the field the query will not be partitioned.
     */
    public void setPartitionField(String partitionField) {
        this.partitionField = new DatabaseField(partitionField);
    }

    /**
     * PUBLIC:
     * Return if queries that do not contain the partition field should be sent
     * to every database and have the result unioned.
     */
    public boolean getUnionUnpartitionableQueries() {
        return unionUnpartitionableQueries;
    }

    /**
     * PUBLIC:
     * Set if queries that do not contain the partition field should be sent
     * to every database and have the result unioned.
     */
    public void setUnionUnpartitionableQueries(boolean unionUnpartitionableQueries) {
        this.unionUnpartitionableQueries = unionUnpartitionableQueries;
    }

    /**
     * INTERNAL:
     * If persist should be partitioned, extra value from object.
     */
    protected Object extractPartitionValueForPersist(AbstractSession session, Object object, ClassDescriptor descriptor) {
        if (!session.isClientSession()) {
            return null;
        }
        ClientSession client = (ClientSession)session;
        // Only assign the connection if exclusive.
        if (!client.isExclusiveIsolatedClientSession() || client.hasWriteConnection()) {
            return null;
        }
        return descriptor.getObjectBuilder().extractValueFromObjectForField(object, this.partitionField, session);
    }
}
