All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eclipse.persistence.descriptors.TablePerMultitenantPolicy Maven / Gradle / Ivy

There is a newer version: 5.0.0-B03
Show newest version
/*
 * Copyright (c) 2012, 2024 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:
//     14/05/2012-2.4 Guy Pelletier
//       - 376603: Provide for table per tenant support for multitenant applications
package org.eclipse.persistence.descriptors;

import org.eclipse.persistence.annotations.TenantTableDiscriminatorType;
import org.eclipse.persistence.config.EntityManagerProperties;
import org.eclipse.persistence.exceptions.DescriptorException;
import org.eclipse.persistence.internal.helper.DatabaseTable;
import org.eclipse.persistence.internal.sessions.AbstractRecord;
import org.eclipse.persistence.internal.sessions.AbstractSession;
import org.eclipse.persistence.mappings.DatabaseMapping;
import org.eclipse.persistence.mappings.DirectCollectionMapping;
import org.eclipse.persistence.mappings.ManyToManyMapping;
import org.eclipse.persistence.mappings.OneToOneMapping;
import org.eclipse.persistence.tools.schemaframework.TableDefinition;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * A table per tenant multitenant policy. Tables can either be per schema
 * or augmented with a prefix or suffix per tenant.
 *
 * @author Guy Pelletier
 * @since EclipseLink 2.4
 */
public class TablePerMultitenantPolicy implements MultitenantPolicy, Cloneable {
    protected ClassDescriptor descriptor;

    // Maps original tables with tenant per table ones. This map is used when
    // building fields that referred to the original table.
    protected Map tablePerTenantTables;
    protected TenantTableDiscriminatorType type;
    protected String contextProperty;
    protected String contextTenant;

    TablePerMultitenantPolicy() {
    }

    public TablePerMultitenantPolicy(ClassDescriptor desc) {
        descriptor = desc;
        type = TenantTableDiscriminatorType.SUFFIX;
        tablePerTenantTables = new HashMap<>(4);
        contextProperty = EntityManagerProperties.MULTITENANT_PROPERTY_DEFAULT;
    }

    /**
     * INTERNAL:
     */
    @Override
    public void addFieldsToRow(AbstractRecord row, AbstractSession session) {
        // No fields to add in table per tenant policy.
    }

    /**
     * INTERNAL:
     */
    @Override
    public void addToTableDefinition(TableDefinition tableDefinition) {
        // Nothing to do here. Called during DDL generation.
    }

    /**
     * INTERNAL:
     * Multitenant policies are cloned per inheritance subclass.
     */
    @Override
    public MultitenantPolicy clone(ClassDescriptor descriptor) {
        try {
            TablePerMultitenantPolicy clonedPolicy = (TablePerMultitenantPolicy) super.clone();

            clonedPolicy.descriptor = descriptor;

            // Create a separate hashmap per clone.
            clonedPolicy.tablePerTenantTables = new HashMap<>(4);
            for (DatabaseTable table : this.tablePerTenantTables.keySet()) {
                clonedPolicy.tablePerTenantTables.put(table, this.tablePerTenantTables.get(table));
            }
            return clonedPolicy;
        } catch (CloneNotSupportedException exception) {
            throw new InternalError(exception.getMessage());
        }
    }

    /**
     * INTERNAL:
     * Return the context property for this table per tenant policy.
     */
    public String getContextProperty() {
        return contextProperty;
    }

    /**
     * INTERNAL:
     * Return the new database table associated with this tenant.
     */
    public DatabaseTable getTable(String tableName) {
        return tablePerTenantTables.get(new DatabaseTable(tableName));
    }

    /**
     * INTERNAL:
     * Return the new database table associated with this tenant.
     */
    public DatabaseTable getTable(DatabaseTable table) {
        return tablePerTenantTables.get(table);
    }

    /**
     * INTERNAL:
     * Return the tenant table name.
     */
    protected String getTableName(DatabaseTable table, String tenant) {
        if (isPrefixPerTable()) {
            return tenant + "_" + table.getName();
        } else {
            return table.getName() + "_" + tenant;
        }
    }

    /**
     * INTERNAL:
     * Return true if the tenant has been set for this policy.
     */
    public boolean hasContextTenant() {
        return this.contextTenant != null;
    }

    /**
     * INTERNAL:
     */
    @Override
    public void initialize(AbstractSession session) throws DescriptorException {
        // Add the context property to the session set.
        session.addMultitenantContextProperty(contextProperty);
    }

    /**
     * PUBLIC:
     * Return true if this descriptor requires a prefix to the table per tenant.
     */
    public boolean isPrefixPerTable() {
        return type == TenantTableDiscriminatorType.PREFIX;
    }

    /**
     * PUBLIC:
     * Return true if this descriptor requires a table schema per tenant.
     */
    public boolean isSchemaPerTable() {
        return type == TenantTableDiscriminatorType.SCHEMA;
    }

    /**
     * INTERNAL:
     */
    @Override
    public boolean isSingleTableMultitenantPolicy() {
        return false;
    }

    /**
     * INTERNAL:
     */
    @Override
    public boolean isSchemaPerMultitenantPolicy() {
        return false;
    }

    /**
     * PUBLIC:
     * Return true if this descriptor requires a suffix to the table per tenant.
     */
    public boolean isSuffixPerTable() {
        return (type == null || type == TenantTableDiscriminatorType.SUFFIX);
    }

    /**
     * INTERNAL:
     */
    @Override
    public boolean isTablePerMultitenantPolicy() {
        return true;
    }

    /**
     * INTERNAL:
     */
    @Override
    public void postInitialize(AbstractSession session) {
        // Nothing to do here.
    }

    /**
     * INTERNAL:
     */
    @Override
    public void preInitialize(AbstractSession session) throws DescriptorException {
        // Nothing to do here.
    }

    /**
     * PUBLIC:
     * Set the tenant table discriminator type.
     */
    public void setTenantTableDiscriminatorType(TenantTableDiscriminatorType type) {
        this.type = type;
    }

    /**
     * PUBLIC:
     * Set the context property used to define the table per tenant. If it is
     * not set by the user, the policy defaults it to the multitenant property
     * default of "eclipselink.tenant-id"
     */
    public void setContextProperty(String contextProperty) {
        this.contextProperty = contextProperty;
    }

    /**
     * INTERNAL:
     * This method is used to update the table per tenant descriptor with
     * a table per tenant prefix or suffix on its associated tables. This
     * includes any relation tables from mappings.
     * 

* If the given session is a client session than we must clone the tables. * Outside of a client session, assume global usage and no cloning is * needed. *

* This method should only be called at the start of a client session * lifecycle and should only be called once. */ protected void setTablePerTenant() { // Update the descriptor tables. List tables = new ArrayList<>(3); for (DatabaseTable table : descriptor.getTables()) { tables.add(updateTable(table)); } descriptor.setTables(tables); // Multitple table foreign keys need to be updated as well Map> existingMultipleTables = descriptor.getMultipleTableForeignKeys(); if (existingMultipleTables != null && ! existingMultipleTables.isEmpty()) { Map> updatedMultipleTables = new HashMap<>(); Set secondaryTables = new HashSet<>(); for (DatabaseTable table : existingMultipleTables.keySet()) { for (DatabaseTable secondaryTable : existingMultipleTables.get(table)) { DatabaseTable updatedSecondaryTable = getTable(secondaryTable); if (updatedSecondaryTable == null) { secondaryTables.add(secondaryTable); } else { secondaryTables.add(updatedSecondaryTable); } } DatabaseTable updatedTable = getTable(table); if (updatedTable == null) { updatedMultipleTables.put(table, secondaryTables); } else { updatedMultipleTables.put(updatedTable, secondaryTables); } } descriptor.setMultipleTableForeignKeys(updatedMultipleTables); } // Any mapping (owning) with a relation table will need to be updated. // Non-owning sides of a bidirectional mapping will be updated during // descriptor initialization. for (DatabaseMapping mapping : descriptor.getMappings()) { if (mapping.isManyToManyMapping()) { // If the mapping is read only we are not the owner of the // relationship meaning we need to look up the relation table // name from the reference descriptor. This will be done later // on in the initialization phase of the table mechanism (when // a reference descriptor has been set and we can look up the // correct relation table) if (! mapping.isReadOnly()) { ((ManyToManyMapping) mapping).setRelationTable(updateTable(((ManyToManyMapping) mapping).getRelationTable())); } } else if (mapping.isOneToOneMapping() && ((OneToOneMapping) mapping).hasRelationTable()) { ((OneToOneMapping) mapping).setRelationTable(updateTable(((OneToOneMapping) mapping).getRelationTable())); } else if (mapping.isDirectCollectionMapping()) { ((DirectCollectionMapping) mapping).setReferenceTable(updateTable(((DirectCollectionMapping) mapping).getReferenceTable())); } } } /** * INTERNAL: * This method is used to update the table per tenant descriptor with * a table per tenant schema. This includes any relation tables from * mappings. This will be done through the setting of a table qualifier on * the tables. *

* This method should only be called at the start of a client session * lifecycle and should only be called once. */ protected void setTableSchemaPerTenant() { descriptor.setTableQualifier(contextTenant); // Any mapping with a relation table will need to be updated. for (DatabaseMapping mapping : descriptor.getMappings()) { if (mapping.isManyToManyMapping()) { ((ManyToManyMapping) mapping).getRelationTable().setTableQualifier(contextTenant); } else if (mapping.isOneToOneMapping() && ((OneToOneMapping) mapping).hasRelationTable()) { ((OneToOneMapping) mapping).getRelationTable().setTableQualifier(contextTenant); } else if (mapping.isDirectCollectionMapping()) { ((DirectCollectionMapping) mapping).getReferenceTable().setTableQualifier(contextTenant); } } } /** * INTERNAL: */ public void setContextTenant(String contextTenant) { this.contextTenant = contextTenant; if (isSchemaPerTable()) { setTableSchemaPerTenant(); } else { setTablePerTenant(); } } /** * INTERNAL: * This method is called during regular descriptor initialization. When * initializing at that level no cloning should be done on when setting * the context tenant. */ public boolean shouldInitialize(AbstractSession session) { if (! hasContextTenant()) { // If we find our tenant property off the given session, update // our descriptors tables and return true for initialization. String tenant = (String) session.getProperty(contextProperty); if (tenant != null) { setContextTenant(tenant); } } return hasContextTenant(); } /** * INTERNAL: * This method will update the table by cloning it and setting a new name * on it. The table association will be stored here as well for ease of * future look up. */ protected DatabaseTable updateTable(DatabaseTable table) { DatabaseTable tableClone = table.clone(); tablePerTenantTables.put(table, tableClone); tableClone.setName(getTableName(tableClone, contextTenant)); return tableClone; } /** * INTERNAL: * Return true if this policy accepts the given property. */ public boolean usesContextProperty(String property) { return contextProperty.equals(property); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy