Skip to content

Commit

Permalink
Fix eclipse-ee4j#1161: Clone appropriate fields in OneToManyMapping.c…
Browse files Browse the repository at this point in the history
…lone(). Fix ConcurrentModificationException. (backport for 2.7) (eclipse-ee4j#1208)

Signed-off-by: aserkes <[email protected]>
  • Loading branch information
aserkes authored Aug 6, 2021
1 parent eabb43c commit 474f855
Show file tree
Hide file tree
Showing 5 changed files with 323 additions and 5 deletions.
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;
}

}

0 comments on commit 474f855

Please sign in to comment.