| /* |
| * 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.security.AccessController; |
| import java.security.PrivilegedActionException; |
| 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.security.PrivilegedClassForName; |
| 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) { |
| try { |
| if (PrivilegedAccessHelper.shouldUsePrivilegedAccess()){ |
| try { |
| partitionValueType = AccessController.doPrivileged(new PrivilegedClassForName<>(partitionValueTypeName, true, classLoader)); |
| } catch (PrivilegedActionException e) { |
| throw ValidationException.classNotFoundWhileConvertingClassNames(partitionValueTypeName, e.getException()); |
| } |
| } else { |
| partitionValueType = org.eclipse.persistence.internal.security.PrivilegedAccessHelper.getClassForName(partitionValueTypeName, true, classLoader); |
| } |
| } catch (Exception exception) { |
| throw ValidationException.classNotFoundWhileConvertingClassNames(partitionValueTypeName, exception); |
| } |
| } |
| |
| // 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); |
| } |
| } |
| } |