/******************************************************************************* * Copyright (c) 2011, 2015 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 v1.0 and Eclipse Distribution License v. 1.0 * which accompanies this distribution. * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html * and the Eclipse Distribution License is available at * http://www.eclipse.org/org/documents/edl-v10.php. * * 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<Object, String>(); /** Store the value partitions by name. Initialized at runtime. */ protected Map<String, String> partitionNames = new HashMap<String, String>(); /** 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<String>(); /** 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. */ 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 Object initObject(Class 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. */ 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<Accessor>(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<Accessor>(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); } } }