From eb9c43dcbcefee27509d0e1707ed67ca699f6fe1 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Tue, 6 Mar 2018 23:06:14 +0100 Subject: [PATCH] Reliably expose nested cause exception message for PersistenceException Issue: SPR-16559 --- .../core/NestedExceptionTests.java | 30 +++++++++--------- .../LocalSessionFactoryBuilder.java | 7 ++++- .../jpa/AbstractEntityManagerFactoryBean.java | 31 +++++++++++++++++-- 3 files changed, 50 insertions(+), 18 deletions(-) diff --git a/spring-core/src/test/java/org/springframework/core/NestedExceptionTests.java b/spring-core/src/test/java/org/springframework/core/NestedExceptionTests.java index c0e60ad1b2..73376ccf25 100644 --- a/spring-core/src/test/java/org/springframework/core/NestedExceptionTests.java +++ b/spring-core/src/test/java/org/springframework/core/NestedExceptionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -44,19 +44,19 @@ public class NestedExceptionTests { nex.printStackTrace(pw); pw.flush(); String stackTrace = new String(baos.toByteArray()); - assertFalse(stackTrace.indexOf(mesg) == -1); + assertTrue(stackTrace.contains(mesg)); } @Test public void nestedRuntimeExceptionWithRootCause() { String myMessage = "mesg for this exception"; - String rootCauseMesg = "this is the obscure message of the root cause"; - Exception rootCause = new Exception(rootCauseMesg); + String rootCauseMsg = "this is the obscure message of the root cause"; + Exception rootCause = new Exception(rootCauseMsg); // Making a class abstract doesn't _really_ prevent instantiation :-) NestedRuntimeException nex = new NestedRuntimeException(myMessage, rootCause) {}; assertEquals(nex.getCause(), rootCause); - assertTrue(nex.getMessage().indexOf(myMessage) != -1); - assertTrue(nex.getMessage().indexOf(rootCauseMesg) != -1); + assertTrue(nex.getMessage().contains(myMessage)); + assertTrue(nex.getMessage().endsWith(rootCauseMsg)); // check PrintStackTrace ByteArrayOutputStream baos = new ByteArrayOutputStream(); @@ -64,8 +64,8 @@ public class NestedExceptionTests { nex.printStackTrace(pw); pw.flush(); String stackTrace = new String(baos.toByteArray()); - assertFalse(stackTrace.indexOf(rootCause.getClass().getName()) == -1); - assertFalse(stackTrace.indexOf(rootCauseMesg) == -1); + assertTrue(stackTrace.contains(rootCause.getClass().getName())); + assertTrue(stackTrace.contains(rootCauseMsg)); } @Test @@ -82,19 +82,19 @@ public class NestedExceptionTests { nex.printStackTrace(pw); pw.flush(); String stackTrace = new String(baos.toByteArray()); - assertFalse(stackTrace.indexOf(mesg) == -1); + assertTrue(stackTrace.contains(mesg)); } @Test public void nestedCheckedExceptionWithRootCause() { String myMessage = "mesg for this exception"; - String rootCauseMesg = "this is the obscure message of the root cause"; - Exception rootCause = new Exception(rootCauseMesg); + String rootCauseMsg = "this is the obscure message of the root cause"; + Exception rootCause = new Exception(rootCauseMsg); // Making a class abstract doesn't _really_ prevent instantiation :-) NestedCheckedException nex = new NestedCheckedException(myMessage, rootCause) {}; assertEquals(nex.getCause(), rootCause); - assertTrue(nex.getMessage().indexOf(myMessage) != -1); - assertTrue(nex.getMessage().indexOf(rootCauseMesg) != -1); + assertTrue(nex.getMessage().contains(myMessage)); + assertTrue(nex.getMessage().endsWith(rootCauseMsg)); // check PrintStackTrace ByteArrayOutputStream baos = new ByteArrayOutputStream(); @@ -102,8 +102,8 @@ public class NestedExceptionTests { nex.printStackTrace(pw); pw.flush(); String stackTrace = new String(baos.toByteArray()); - assertFalse(stackTrace.indexOf(rootCause.getClass().getName()) == -1); - assertFalse(stackTrace.indexOf(rootCauseMesg) == -1); + assertTrue(stackTrace.contains(rootCause.getClass().getName())); + assertTrue(stackTrace.contains(rootCauseMsg)); } } diff --git a/spring-orm/src/main/java/org/springframework/orm/hibernate5/LocalSessionFactoryBuilder.java b/spring-orm/src/main/java/org/springframework/orm/hibernate5/LocalSessionFactoryBuilder.java index 38ee1717a5..16358154c2 100644 --- a/spring-orm/src/main/java/org/springframework/orm/hibernate5/LocalSessionFactoryBuilder.java +++ b/spring-orm/src/main/java/org/springframework/orm/hibernate5/LocalSessionFactoryBuilder.java @@ -425,8 +425,13 @@ public class LocalSessionFactoryBuilder extends Configuration { throw new IllegalStateException("Interrupted during initialization of Hibernate SessionFactory", ex); } catch (ExecutionException ex) { + Throwable cause = ex.getCause(); + if (cause instanceof HibernateException) { + // Rethrow a provider configuration exception (possibly with a nested cause) directly + throw (HibernateException) cause; + } throw new IllegalStateException("Failed to asynchronously initialize Hibernate SessionFactory: " + - ex.getMessage(), ex.getCause()); + ex.getMessage(), cause); } } } diff --git a/spring-orm/src/main/java/org/springframework/orm/jpa/AbstractEntityManagerFactoryBean.java b/spring-orm/src/main/java/org/springframework/orm/jpa/AbstractEntityManagerFactoryBean.java index f9768ffcbd..b196c2fd24 100644 --- a/spring-orm/src/main/java/org/springframework/orm/jpa/AbstractEntityManagerFactoryBean.java +++ b/spring-orm/src/main/java/org/springframework/orm/jpa/AbstractEntityManagerFactoryBean.java @@ -385,11 +385,32 @@ public abstract class AbstractEntityManagerFactoryBean implements } private EntityManagerFactory buildNativeEntityManagerFactory() { - EntityManagerFactory emf = createNativeEntityManagerFactory(); + EntityManagerFactory emf; + try { + emf = createNativeEntityManagerFactory(); + } + catch (PersistenceException ex) { + if (ex.getClass() == PersistenceException.class) { + // Plain PersistenceException wrapper for underlying exception? + // Make sure the nested exception message is properly exposed, + // along the lines of Spring's NestedRuntimeException.getMessage() + Throwable cause = ex.getCause(); + if (cause != null) { + String message = ex.getMessage(); + String causeString = cause.toString(); + if (!message.endsWith(causeString)) { + throw new PersistenceException(message + "; nested exception is " + causeString, cause); + } + } + } + throw ex; + } + JpaVendorAdapter jpaVendorAdapter = getJpaVendorAdapter(); if (jpaVendorAdapter != null) { jpaVendorAdapter.postProcessEntityManagerFactory(emf); } + if (logger.isInfoEnabled()) { logger.info("Initialized JPA EntityManagerFactory for persistence unit '" + getPersistenceUnitName() + "'"); } @@ -416,6 +437,7 @@ public abstract class AbstractEntityManagerFactoryBean implements ifcs.add(EntityManagerFactory.class); } ifcs.add(EntityManagerFactoryInfo.class); + try { return (EntityManagerFactory) Proxy.newProxyInstance(this.beanClassLoader, ClassUtils.toClassArray(ifcs), new ManagedEntityManagerFactoryInvocationHandler(this)); @@ -521,8 +543,13 @@ public abstract class AbstractEntityManagerFactoryBean implements throw new IllegalStateException("Interrupted during initialization of native EntityManagerFactory", ex); } catch (ExecutionException ex) { + Throwable cause = ex.getCause(); + if (cause instanceof PersistenceException) { + // Rethrow a provider configuration exception (possibly with a nested cause) directly + throw (PersistenceException) cause; + } throw new IllegalStateException("Failed to asynchronously initialize native EntityManagerFactory: " + - ex.getMessage(), ex.getCause()); + ex.getMessage(), cause); } } }