blob: d1c9f251ccd128116648a6b7b51aa167088cdbc7 [file] [log] [blame]
/*
* Copyright (c) 2011, 2021 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
// // 30/05/2012-2.4 Guy Pelletier
// - 354678: Temp classloader is still being used during metadata processing
package org.eclipse.persistence.descriptors.partitioning;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.exceptions.ValidationException;
import org.eclipse.persistence.internal.databaseaccess.Accessor;
import org.eclipse.persistence.internal.helper.ConversionManager;
import org.eclipse.persistence.internal.security.PrivilegedAccessHelper;
import org.eclipse.persistence.internal.sessions.AbstractRecord;
import org.eclipse.persistence.internal.sessions.AbstractSession;
import org.eclipse.persistence.queries.DatabaseQuery;
/**
* PUBLIC:
* ValuePartitioningPolicy partitions access to a database cluster by a field value from the object,
* such as the object's location, or tenant.
* Each value is assigned a specific server.
* 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 class ValuePartitioningPolicy extends FieldPartitioningPolicy {
/** Store the value partitions. Each partition maps a value to a connectionPool. */
protected Map<Object, String> partitions = new HashMap<>();
/** Store the value partitions by name. Initialized at runtime. */
protected Map<String, String> partitionNames = new HashMap<>();
/** The type name of the partition value names. Initialized at runtime */
protected String partitionValueTypeName;
/** The type of the partition values. Initialized from the type name at runtime. */
protected Class<?> partitionValueType;
/** Use to track order for compute UCP index. */
protected List<String> orderedPartitions = new ArrayList<>();
/** The default connection pool is used for any unmapped values. */
protected String defaultConnectionPool;
public ValuePartitioningPolicy() {
super();
}
public ValuePartitioningPolicy(String partitionField) {
super(partitionField);
}
public ValuePartitioningPolicy(String partitionField, boolean unionUnpartitionableQueries) {
super(partitionField, unionUnpartitionableQueries);
}
/**
* INTERNAL:
* Convert all the class-name-based settings to actual class-based settings.
* This method is used when converting a project that has been built with
* class names to a project with classes.
*/
@Override
public void convertClassNamesToClasses(ClassLoader classLoader) {
if (partitionValueType == null && partitionValueTypeName != null) {
partitionValueType = PrivilegedAccessHelper.callDoPrivilegedWithException(
() -> PrivilegedAccessHelper.getClassForName(partitionValueTypeName, true, classLoader),
(ex) -> ValidationException.classNotFoundWhileConvertingClassNames(partitionValueTypeName, ex)
);
}
// Once we know we have a partition value type we can convert our partition names.
if (partitionValueType != null) {
for (String valueName : this.partitionNames.keySet()) {
Object value = initObject(partitionValueType, valueName);
if (getPartitions().containsKey(value)) {
throw ValidationException.duplicatePartitionValue(getName(), value);
}
addPartition(value, partitionNames.get(valueName));
}
}
}
/**
* Convert the string value to the class type.
* This will handle numbers, string, dates, and most other classes.
*/
private <T> T initObject(Class<T> type, String value) {
return ConversionManager.getDefaultManager().convertObject(value, type);
}
/**
* INTERNAL:
*/
public void setPartitionValueTypeName(String partitionValueTypeName) {
this.partitionValueTypeName = partitionValueTypeName;
}
public List<String> getOrderedPartitions() {
return orderedPartitions;
}
public void setOrderedPartitions(List<String> orderedPartitions) {
this.orderedPartitions = orderedPartitions;
}
/**
* PUBLIC:
* Return the default connection pool used for any unmapped values.
*/
public String getDefaultConnectionPool() {
return defaultConnectionPool;
}
/**
* PUBLIC:
* Set the default connection pool used for any unmapped values.
*/
public void setDefaultConnectionPool(String defaultConnectionPool) {
this.defaultConnectionPool = defaultConnectionPool;
}
/**
* PUBLIC:
* Return the value partitions.
* Each partition maps a value to a connectionPool.
*/
public Map<Object, String> getPartitions() {
return partitions;
}
/**
* PUBLIC:
* Set the value partitions.
* Each partition maps a value to a connectionPool.
*/
public void setPartitions(Map<Object, String> partitions) {
this.partitions = partitions;
}
/**
* PUBLIC:
* Add the value partition.
*/
public void addPartition(Object value, String connectionPool) {
getPartitions().put(value, connectionPool);
getOrderedPartitions().add(connectionPool);
}
/**
* INTERNAL:
* Add partition values by name (will be initialized at runtime with the
* real class loader).
*/
public void addPartitionName(String valueName, String connectionPool) {
this.partitionNames.put(valueName, connectionPool);
}
/**
* INTERNAL:
* Get a connection from one of the pools in a round robin rotation fashion.
*/
@Override
public List<Accessor> getConnectionsForQuery(AbstractSession session, DatabaseQuery query, AbstractRecord arguments) {
Object value = arguments.get(this.partitionField);
if (value == null) {
if (this.unionUnpartitionableQueries) {
// Use all connections.
List<Accessor> accessors = new ArrayList<>(this.partitions.size());
for (String poolName : this.partitions.values()) {
accessors.add(getAccessor(poolName, session, query, false));
}
return accessors;
} else {
// Use default behavior.
return null;
}
}
// Use the mapped connection pool.
List<Accessor> accessors = new ArrayList<>(1);
String poolName = this.partitions.get(value);
if (poolName == null) {
if (this.defaultConnectionPool == null) {
// Use default behavior.
return null;
}
poolName = this.defaultConnectionPool;
}
if (session.getPlatform().hasPartitioningCallback()) {
// UCP support.
session.getPlatform().getPartitioningCallback().setPartitionId(getOrderedPartitions().indexOf(poolName));
return null;
}
accessors.add(getAccessor(poolName, session, query, false));
return accessors;
}
/**
* INTERNAL:
* Allow for the persist call to assign the partition.
*/
@Override
public void partitionPersist(AbstractSession session, Object object, ClassDescriptor descriptor) {
Object value = extractPartitionValueForPersist(session, object, descriptor);
if (value == null) {
return;
}
String poolName = this.partitions.get(value);
if (poolName == null) {
return;
}
if (session.getPlatform().hasPartitioningCallback()) {
// UCP support.
session.getPlatform().getPartitioningCallback().setPartitionId(getOrderedPartitions().indexOf(poolName));
} else {
getAccessor(poolName, session, null, false);
}
}
}