Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix #1161: Clone appropriate fields in OneToManyMapping.clone(). Fix ConcurrentModificationException. (backport for 2.7) #1208

Merged
merged 4 commits into from
Aug 6, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* Copyright (c) 1998, 2020 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1998, 2020 IBM Corporation. All rights reserved.
* Copyright (c) 1998, 2021 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1998, 2021 IBM Corporation. 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
Expand Down Expand Up @@ -1369,7 +1369,7 @@ public Object clone() {
clonedDescriptor.setFields(NonSynchronizedVector.newInstance());

// Referencing classes
referencingClasses = new HashSet<>();
clonedDescriptor.referencingClasses = new HashSet<>(referencingClasses);

// Post-calculate changes
if (this.mappingsPostCalculateChanges != null) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 1998, 2018 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1998, 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
Expand Down Expand Up @@ -309,7 +309,12 @@ public void collectQueryParameters(Set<DatabaseField> cacheFields){
@Override
public Object clone() {
OneToManyMapping clone = (OneToManyMapping)super.clone();
clone.setTargetForeignKeysToSourceKeys(new HashMap(getTargetForeignKeysToSourceKeys()));

Map<DatabaseField, DatabaseField> old2cloned = new HashMap<>();
clone.sourceKeyFields = cloneDatabaseFieldVector(sourceKeyFields, old2cloned);
clone.targetForeignKeyFields = cloneDatabaseFieldVector(targetForeignKeyFields, old2cloned);
clone.setTargetForeignKeysToSourceKeys(cloneKeysMap(getTargetForeignKeysToSourceKeys(), old2cloned));
clone.sourceKeysToTargetForeignKeys = cloneKeysMap(getSourceKeysToTargetForeignKeys(), old2cloned);

if (addTargetQuery != null){
clone.addTargetQuery = (DataModifyQuery) this.addTargetQuery.clone();
Expand All @@ -320,6 +325,33 @@ public Object clone() {
return clone;
}

private Map<DatabaseField, DatabaseField> cloneKeysMap(Map<DatabaseField, DatabaseField> toClone,
Map<DatabaseField, DatabaseField> old2cloned) {
if (toClone == null) {
return null;
}
Map<DatabaseField, DatabaseField> cloneTarget2Src = new HashMap<>(toClone.size());
for (Map.Entry<DatabaseField, DatabaseField> e : toClone.entrySet()) {
cloneTarget2Src.put(old2cloned.get(e.getKey()), old2cloned.get(e.getValue()));
}
return cloneTarget2Src;
}

private Vector<DatabaseField> cloneDatabaseFieldVector(Vector<DatabaseField> oldFlds,
Map<DatabaseField, DatabaseField> old2cloned) {
Vector<DatabaseField> clonedSourceKeyFields = null;
if (oldFlds != null) {
clonedSourceKeyFields =
org.eclipse.persistence.internal.helper.NonSynchronizedVector.newInstance(oldFlds.size());
for (DatabaseField old : oldFlds) {
DatabaseField cf = old.clone();
clonedSourceKeyFields.add(cf);
old2cloned.put(old, cf);
}
}
return clonedSourceKeyFields;
}

/**
* INTERNAL
* Called when a DatabaseMapping is used to map the key in a collection. Returns the key.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
/*
* Copyright (c) 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
*/

package org.eclipse.persistence.jpa.test.mapping;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import org.eclipse.persistence.config.PersistenceUnitProperties;
import org.eclipse.persistence.jpa.test.framework.DDLGen;
import org.eclipse.persistence.jpa.test.framework.Emf;
import org.eclipse.persistence.jpa.test.framework.EmfRunner;
import org.eclipse.persistence.jpa.test.mapping.model.ChildMultitenant;
import org.eclipse.persistence.jpa.test.mapping.model.ParentMultitenant;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

/**
* Test for the fix issue #1161
* Multitenancy schema discriminator with OneToMany relationships - Wrong tenant reference leading to QueryException
*/
@RunWith(EmfRunner.class)
public class TestMultitenantOneToMany {

@Emf(
createTables = DDLGen.NONE,
classes = {ChildMultitenant.class, ParentMultitenant.class}
)
private EntityManagerFactory emf;

@Before
public void setup() {
EntityManager em = emf.createEntityManager();
try {
em.getTransaction().begin();
em.createNativeQuery("CREATE SCHEMA tenant_1").executeUpdate();
em.createNativeQuery("CREATE SCHEMA tenant_2").executeUpdate();
em.createNativeQuery("CREATE TABLE tenant_1.parent(id bigint primary key)").executeUpdate();
em.createNativeQuery("CREATE TABLE tenant_2.parent(id bigint primary key)").executeUpdate();
em.createNativeQuery("CREATE TABLE tenant_1.children(id bigint NOT NULL, parent_id bigint, PRIMARY KEY " +
"(id), CONSTRAINT parent_fkey FOREIGN KEY (parent_id) REFERENCES tenant_1.parent (id))")
.executeUpdate();
em.createNativeQuery("CREATE TABLE tenant_2.children(id bigint NOT NULL, parent_id bigint, PRIMARY KEY " +
"(id), CONSTRAINT parent_fkey FOREIGN KEY (parent_id) REFERENCES tenant_2.parent (id))")
.executeUpdate();
em.createNativeQuery("INSERT INTO tenant_1.parent(id) VALUES(1)").executeUpdate();
em.createNativeQuery("INSERT INTO tenant_2.parent(id) VALUES(2)").executeUpdate();
em.createNativeQuery("INSERT INTO tenant_1.children(id, parent_id) VALUES(10, 1)").executeUpdate();
em.createNativeQuery("INSERT INTO tenant_2.children(id, parent_id) VALUES(11, 2)").executeUpdate();
em.getTransaction().commit();
} finally {
if (em.getTransaction().isActive()) {
em.getTransaction().rollback();
}
if (em.isOpen()) {
em.close();
}
}
}

@After
public void cleanUp() {
EntityManager em = emf.createEntityManager();
try {
em.getTransaction().begin();
em.createNativeQuery("DROP SCHEMA IF EXISTS tenant_1").executeUpdate();
em.createNativeQuery("DROP SCHEMA IF EXISTS tenant_2").executeUpdate();
em.getTransaction().commit();
} finally {
if (em.getTransaction().isActive()) {
em.getTransaction().rollback();
}
if (em.isOpen()) {
em.close();
}
}
}

@Test
public void testMultitenancySchemaDescriminatorWithOneToMany() {
boolean awaitTermination = false;
List<Future<ParentMultitenant>> parent1Results = new ArrayList<>();
List<Future<ParentMultitenant>> parent2Results = new ArrayList<>();
try {
ExecutorService es = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
for (int i = 1; i <= 10000; i++) {
parent1Results.add(es.submit(() -> load("tenant_1", 1L)));
parent2Results.add(es.submit(() -> load("tenant_2", 2L)));
}
es.shutdown();
awaitTermination = es.awaitTermination(10, TimeUnit.MINUTES);
for (Future<ParentMultitenant> parentFuture : parent1Results) {
ParentMultitenant parent = parentFuture.get();
assertEquals(1L, (long) parent.getId());
assertEquals(10L, (long) parent.getChildren().get(0).getId());
}
for (Future<ParentMultitenant> parentFuture : parent2Results) {
ParentMultitenant parent = parentFuture.get();
assertEquals(2L, (long) parent.getId());
assertEquals(11L, (long) parent.getChildren().get(0).getId());
}
} catch (Exception e) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
e.printStackTrace(pw);
fail("Exception was caught: " + sw.toString());
}
if (!awaitTermination) {
fail("timeout elapsed before termination of the threads");
}
}

private ParentMultitenant load(String tenant, Long id) {
HashMap<String, String> properties = new HashMap<>();
properties.put(PersistenceUnitProperties.MULTITENANT_PROPERTY_DEFAULT, tenant);
EntityManager em;
if (!properties.isEmpty()) {
em = emf.createEntityManager(properties);
} else {
em = emf.createEntityManager();
}
ParentMultitenant parent = em.find(ParentMultitenant.class, id);
List<ChildMultitenant> children = parent.getChildren();
em.close();
return parent;

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* Copyright (c) 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
*/

package org.eclipse.persistence.jpa.test.mapping.model;

import org.eclipse.persistence.annotations.Multitenant;
import org.eclipse.persistence.annotations.MultitenantType;
import org.eclipse.persistence.annotations.TenantTableDiscriminator;
import org.eclipse.persistence.annotations.TenantTableDiscriminatorType;

import javax.persistence.Basic;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import java.io.Serializable;

@Entity
@Table(name = "children")
@Multitenant(MultitenantType.TABLE_PER_TENANT)
@TenantTableDiscriminator(type = TenantTableDiscriminatorType.SCHEMA)
public class ChildMultitenant implements Serializable {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Basic(optional = false)
@Column(name = "id")
private Long id;

@ManyToOne(fetch = FetchType.LAZY)
private ParentMultitenant parent;

public ChildMultitenant() {
}

public Long getId() {
return id;
}

public void setId(Long id) {
this.id = id;
}

public ParentMultitenant getParent() {
return parent;
}

public void setParent(ParentMultitenant parentMultitenant) {
this.parent = parentMultitenant;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* Copyright (c) 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
*/

package org.eclipse.persistence.jpa.test.mapping.model;

import org.eclipse.persistence.annotations.Multitenant;
import org.eclipse.persistence.annotations.MultitenantType;
import org.eclipse.persistence.annotations.TenantTableDiscriminator;
import org.eclipse.persistence.annotations.TenantTableDiscriminatorType;

import javax.persistence.Basic;
import javax.persistence.Cacheable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import java.io.Serializable;
import java.util.List;

@Entity
@Table(name = "parent")
@Cacheable(false)
@Multitenant(MultitenantType.TABLE_PER_TENANT)
@TenantTableDiscriminator(type = TenantTableDiscriminatorType.SCHEMA)
public class ParentMultitenant implements Serializable {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Basic(optional = false)
@Column(name = "id")
private Long id;

@OneToMany(mappedBy = "parent", fetch = FetchType.LAZY)
private List<ChildMultitenant> children;

public ParentMultitenant() {
}

public Long getId() {
return id;
}

public void setId(Long id) {
this.id = id;
}

public List<ChildMultitenant> getChildren() {
return children;
}

public void setChildren(List<ChildMultitenant> childMultitenants) {
this.children = childMultitenants;
}

}