blob: 52273bac66742e7661ab53f8f740093bb9f4bb0a [file] [log] [blame]
/*
* 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
* 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:
// Oracle - initial API and implementation from Oracle TopLink
package org.eclipse.persistence.testing.tests.jpa.customfeatures;
import java.io.*;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;
import java.util.List;
import org.w3c.dom.*;
import javax.xml.parsers.*;
import javax.xml.transform.stream.StreamResult;
import jakarta.persistence.EntityManager;
import junit.framework.*;
import org.eclipse.persistence.testing.framework.TestCase;
import org.eclipse.persistence.testing.framework.junit.JUnitTestCase;
import org.eclipse.persistence.testing.models.jpa.customfeatures.*;
import org.eclipse.persistence.tools.schemaframework.PackageDefinition;
import org.eclipse.persistence.tools.schemaframework.StoredProcedureDefinition;
import org.eclipse.persistence.tools.schemaframework.SchemaManager;
import org.eclipse.persistence.internal.databaseaccess.Accessor;
import org.eclipse.persistence.internal.helper.Helper;
import org.eclipse.persistence.platform.xml.XMLPlatformFactory;
import org.eclipse.persistence.platform.xml.XMLTransformer;
import org.eclipse.persistence.sessions.DatabaseSession;
import org.eclipse.persistence.sessions.Session;
import org.eclipse.persistence.sessions.server.ServerSession;
public class CustomFeaturesJUnitTestSuite extends JUnitTestCase {
private static int empId;
protected static int NUM_INSERTS = 200;
public static String dbVersion;
public CustomFeaturesJUnitTestSuite() {
super();
}
public CustomFeaturesJUnitTestSuite(String name) {
super(name);
}
public static Test suite() {
TestSuite suite = new TestSuite();
suite.setName("CustomFeaturesJUnitTestSuite");
suite.addTest(new CustomFeaturesJUnitTestSuite("testSetup"));
suite.addTest(new CustomFeaturesJUnitTestSuite("testNCharXMLType"));
suite.addTest(new CustomFeaturesJUnitTestSuite("testBatchInserts"));
suite.addTest(new CustomFeaturesJUnitTestSuite("testBatchUpdates"));
suite.addTest(new CustomFeaturesJUnitTestSuite("testNamedStoredProcedureInOutQuery"));
suite.addTest(new CustomFeaturesJUnitTestSuite("testNamedStoredProcedureCursorQuery"));
return suite;
}
public void testSetup() throws SQLException {
ServerSession session = JUnitTestCase.getServerSession("customfeatures");
new EmployeeTableCreator().replaceTables(session);
buildOraclePackage(session);
buildOracleStoredProcedureReadFromEmployeeInOut(session);
buildOracleStoredProcedureReadFromEmployeeCursor(session);
Accessor accessor = session.getDefaultConnectionPool().acquireConnection();
try {
accessor.incrementCallCount(session);
DatabaseMetaData metaData = accessor.getConnection().getMetaData();
String dbMajorMinorVersion = Integer.toString(metaData.getDatabaseMajorVersion()) + '.' + metaData.getDatabaseMinorVersion();
String dbProductionVersion = metaData.getDatabaseProductVersion();
// For Helper.compareVersions to work the first digit in the passed version String should be part of the version,
// i.e. "10.2.0.2 ..." is ok, but "Oracle 10g ... 10.2.0.2..." is not.
dbVersion = dbProductionVersion.substring(dbProductionVersion.indexOf(dbMajorMinorVersion));
} finally {
accessor.decrementCallCount();
session.getDefaultConnectionPool().releaseConnection(accessor);
}
}
/**
* Tests a NChar and XML Type with Document.
*/
public void testNCharXMLType() {
EntityManager em = createEntityManager("customfeatures");
beginTransaction(em);
Employee emp = null;
try {
emp = new Employee();
emp.setResume_xml(resume0());
char nCh = '\u0410';
emp.setEmpNChar(nCh);
emp.setResume_dom(documentFromString(resume0()));
em.persist(emp);
empId = emp.getId();
commitTransaction(em);
} catch (RuntimeException e) {
if (isTransactionActive(em)) {
rollbackTransaction(em);
}
closeEntityManager(em);
throw e;
}
try {
em.clear();
clearCache("customfeatures");
if(isOnServer()) {
beginTransaction(em);
}
Employee readEmp = em.find(Employee.class, empId);
compare(readEmp, emp);
} finally {
if (isTransactionActive(em)) {
rollbackTransaction(em);
}
closeEntityManager(em);
}
}
/**
* Tests a Native Batch Writing as batch inserts.
*/
public void testBatchInserts() {
EntityManager em = createEntityManager("customfeatures");
beginTransaction(em);
try {
for (int i = 0; i < NUM_INSERTS; i++) {
Employee emp = new Employee();
emp.setResume_xml(resume0());
emp.setResume_dom(documentFromString(resume0()));
char nCh = '\u0410';
emp.setEmpNChar(nCh);
em.persist(emp);
}
commitTransaction(em);
} finally {
if (isTransactionActive(em)) {
rollbackTransaction(em);
}
closeEntityManager(em);
}
}
/**
* Tests a Native Batch Writing as batch updates with
* OptimisticLockingException.
*/
public void testBatchUpdates() {
EntityManager em = createEntityManager("customfeatures");
beginTransaction(em);
List emps = em.createQuery("SELECT OBJECT(e) FROM Employee e").getResultList();
try {
for (int i = 0; i < emps.size(); i++) {
Employee e = (Employee) emps.get(i);
String newName = ((Employee) emps.get(i)).getName() + i + "test";
e.setName(newName);
e.setVersion(e.getVersion() - 1);
}
em.flush();
commitTransaction(em);
fail("OptimisticLockingException is not thrown!");
} catch (Exception exception) {
if (exception.getMessage().indexOf("org.eclipse.persistence.exceptions.OptimisticLockException") == -1) {
fail("it's not the right exception");
}
} finally {
if (isTransactionActive(em)) {
rollbackTransaction(em);
}
closeEntityManager(em);
}
}
/**
* Tests a @NamedStoredProcedureQuery with store procedure IN_OUT parameter,
* and XML Type using String
*/
public void testNamedStoredProcedureInOutQuery() {
if(Helper.compareVersions(dbVersion, "11.2.0.2") < 0) {
// Oracle db 11.2.0.2 or later is required for this test
return;
}
EntityManager em = createEntityManager("customfeatures");
beginTransaction(em);
try {
Employee emp = new Employee();
emp.setResume_xml(resume1());
emp.setResume_dom(documentFromString(resume0()));
char nCh = '\u0400';
emp.setEmpNChar(nCh);
em.persist(emp);
commitTransaction(em);
em.clear();
clearCache("customfeatures");
if(isOnServer()) {
beginTransaction(em);
}
// note that readEmployee will have only two attributes set: id and empNChar
Employee readEmp = (Employee) em.createNamedQuery("ReadEmployeeInOut").setParameter("ID", emp.getId()).getSingleResult();
if (emp.getEmpNChar() != readEmp.getEmpNChar()) {
fail("readEmp.getEmpNChar() == " + readEmp.getEmpNChar() + ", does not match empNChar of the object that was written: " + emp.getEmpNChar());
}
} finally {
if (isTransactionActive(em)) {
rollbackTransaction(em);
}
closeEntityManager(em);
}
}
/**
* Tests a @NamedStoredProcedureQuery with store procedure ref Cursor, and
* XML Type using String
*/
public void testNamedStoredProcedureCursorQuery() {
EntityManager em = createEntityManager("customfeatures");
beginTransaction(em);
try {
Employee emp = new Employee();
emp.setResume_xml(resume1());
emp.setResume_dom(documentFromString(resume0()));
char nCh = '\u0400';
emp.setEmpNChar(nCh);
emp.setName("Edward Xu");
em.persist(emp);
commitTransaction(em);
em.clear();
clearCache("customfeatures");
if(isOnServer()) {
beginTransaction(em);
}
Employee readEmp = (Employee) em.createNamedQuery("ReadEmployeeCursor").setParameter("ID", emp.getId()).getSingleResult();
compare(readEmp, emp);
} finally {
if (isTransactionActive(em)) {
rollbackTransaction(em);
}
closeEntityManager(em);
}
}
public static String resume0() {
String resume = "<resume>\n";
resume += " <first-name>Bob</first-name>\n";
resume += " <last-name>Jones</last-name>\n";
resume += " <age>45</age>\n";
resume += " <education>\n";
resume += " <degree>BCS</degree>\n";
resume += " <degree>MBA</degree>\n";
resume += " </education>\n";
resume += "</resume>";
return resume;
}
public static String resume1() {
String resume = "<resume>\n";
resume += " <first-name>Frank</first-name>\n";
resume += " <last-name>Cotton</last-name>\n";
resume += " <age>27</age>\n";
resume += " <education>\n";
resume += " <degree>BCS</degree>\n";
resume += " </education>\n";
resume += "</resume>";
return resume;
}
public static Document documentFromString(String xmlString) {
try {
ByteArrayInputStream stream = new ByteArrayInputStream(xmlString.getBytes());
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(stream);
return doc;
} catch (Exception ex) {
fail("Unable to create document due to: " + ex.getMessage());
}
return null;
}
public PackageDefinition buildOraclePackage(Session session) {
if (TestCase.supportsStoredProcedures(session)) {
PackageDefinition types = new PackageDefinition();
types.setName("Cursor_Type");
types.addStatement("Type Any_Cursor is REF CURSOR");
SchemaManager schema = new SchemaManager(((DatabaseSession) session));
schema.replaceObject(types);
return types;
} else {
fail("store procedure is not supported!");
return null;
}
}
public void buildOracleStoredProcedureReadFromEmployeeInOut(Session session) {
if (TestCase.supportsStoredProcedures(session)) {
StoredProcedureDefinition proc = new StoredProcedureDefinition();
proc.setName("Read_Employee_InOut");
proc.addInOutputArgument("employee_id_v", Integer.class);
proc.addOutputArgument("nchar_v", "NCHAR");
String statement = "SELECT NCHARTYPE INTO nchar_v FROM CUSTOM_FEATURE_EMPLOYEE WHERE (ID = employee_id_v)";
proc.addStatement(statement);
SchemaManager schema = new SchemaManager(((DatabaseSession) session));
schema.replaceObject(proc);
} else
fail("store procedure is not supported!");
}
public void buildOracleStoredProcedureReadFromEmployeeCursor(Session session) {
if (TestCase.supportsStoredProcedures(session)) {
StoredProcedureDefinition proc = new StoredProcedureDefinition();
proc.setName("Read_Employee_Cursor");
proc.addArgument("employee_id_v", Integer.class);
proc.addOutputArgument("RESULT_CURSOR", "CURSOR_TYPE.ANY_CURSOR");
proc.addStatement("OPEN RESULT_CURSOR FOR SELECT * FROM CUSTOM_FEATURE_EMPLOYEE WHERE (ID = employee_id_v)");
SchemaManager schema = new SchemaManager(((DatabaseSession) session));
schema.replaceObject(proc);
} else
fail("store procedure is not supported!");
}
/*
* This method is necessary because of a bug in Oracle xdb 11.2.0.2: XDB - 11.2.0.2 DB FORMATS RETURNED XML
* This bug describes the following workaround:
* In init.ora, add "31151 trace name context forever, level 0x100"
* When the bug is fixed (or 11.2.0.2 db configurured as described in the workaround))
* the special case for 11.2.0.2 Oracle db should be removed:
*
* void compare(Employee readEmp, Employee emp) {
* if (!getServerSession("customfeatures").compareObjects(readEmp, emp)) {
* fail("Object: " + readEmp + " does not match object that was written: " + emp + ". See log (on finest) for what did not match.");
* }
* }
*/
void compare(Employee readEmp, Employee emp) {
if(Helper.compareVersions(dbVersion, "11.2.0.2") >= 0) {
// Oracle db 11.2.0.2 returns formatted xml, therefore the original and the read back strings might differ.
String originalReadResume_xml = null;
String originalResume_xml = null;
if(!readEmp.getResume_xml().equals(emp.getResume_xml())) {
originalReadResume_xml = readEmp.getResume_xml();
originalResume_xml = emp.getResume_xml();
String unformattedReadResume_xml = removeWhiteSpaceFromString(originalReadResume_xml);
String unformattedResume_xml = removeWhiteSpaceFromString(originalResume_xml);
if(unformattedReadResume_xml.equals(unformattedResume_xml)) {
// xml docs defined by the two strings are equivalent
// temporary remove the strings from their owner Employees so that it could pass compareObjects
readEmp.setResume_xml(null);
emp.setResume_xml(null);
} else {
fail("unformattedReadResume_xml == " + unformattedReadResume_xml + "\nunformattedResume_xml == " + unformattedResume_xml);
}
}
// Oracle db 11.2.0.2 returns formatted xml, therefore the original and the read back doms might differ.
Document originalReadResume_dom = null;
Document originalResume_dom = null;
if(!readEmp.getResume_dom().equals(emp.getResume_dom())) {
originalReadResume_dom = readEmp.getResume_dom();
originalResume_dom = emp.getResume_dom();
Document unformattedReadResume_dom = (Document)originalReadResume_dom.cloneNode(true);
removeEmptyTextNodes(unformattedReadResume_dom);
Document unformattedResume_dom = (Document)originalResume_dom.cloneNode(true);
removeEmptyTextNodes(unformattedResume_dom);
String unformattedReadResume_dom_toString = convertDocumentToString(unformattedReadResume_dom);
String unformattedResume_dom_toString = convertDocumentToString(unformattedResume_dom);
if(unformattedReadResume_dom_toString.equals(unformattedResume_dom_toString)) {
// xml docs defined by the two strings are equivalent
// temporary remove the doms from their owner Employees so that it could pass compareObjects
readEmp.setResume_dom(null);
emp.setResume_dom(null);
} else {
fail("unformattedReadResume_dom_toString == " + unformattedReadResume_dom_toString + "\nunformattedResume_dom_toString == " + unformattedResume_dom_toString);
}
}
try {
if (!getServerSession("customfeatures").compareObjects(readEmp, emp)) {
fail("Object: " + readEmp + " does not match object that was written: " + emp + ". See log (on finest) for what did not match.");
}
} finally {
if(emp.getResume_xml() == null && originalResume_xml != null) {
// set back the temporary removed resume_xml into both objects
readEmp.setResume_xml(originalReadResume_xml);
emp.setResume_xml(originalResume_xml);
}
if(emp.getResume_dom() == null && originalResume_dom != null) {
// set back the temporary removed resume_dom into both objects
readEmp.setResume_dom(originalReadResume_dom);
emp.setResume_dom(originalResume_dom);
}
}
} else {
// Before version 11.2.0.2 Oracle db returned xml string exactly as it was written (keeping the same format: white spaces, \r, \n etc).
// No special comparison for resume_xml is required.
if (!getServerSession("customfeatures").compareObjects(readEmp, emp)) {
fail("Object: " + readEmp + " does not match object that was written: " + emp + ". See log (on finest) for what did not match.");
}
}
}
// Contributed by Blaise
public static void removeEmptyTextNodes(Node node) {
NodeList nodeList = node.getChildNodes();
Node childNode;
for (int x = nodeList.getLength() - 1; x >= 0; x--) {
childNode = nodeList.item(x);
if (childNode.getNodeType() == Node.TEXT_NODE) {
if (childNode.getNodeValue().trim().equals("")) {
node.removeChild(childNode);
}
} else if (childNode.getNodeType() == Node.ELEMENT_NODE) {
removeEmptyTextNodes(childNode);
}
}
}
// Contributed by Blaise
public static String removeWhiteSpaceFromString(String s) {
String returnString = s.replaceAll(" ", "");
returnString = returnString.replaceAll("\n", "");
returnString = returnString.replaceAll("\t", "");
returnString = returnString.replaceAll("\r", "");
return returnString;
}
static String convertDocumentToString(Document doc) {
XMLTransformer xmlTransformer = XMLPlatformFactory.getInstance().getXMLPlatform().newXMLTransformer();
StringWriter writer = new StringWriter();
StreamResult result = new StreamResult(writer);
xmlTransformer.transform(doc, result);
return writer.getBuffer().toString();
}
}