001/* 002 * Copyright 2002-2013 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.orm.jdo; 018 019import javax.jdo.JDODataStoreException; 020import javax.jdo.JDOException; 021import javax.jdo.JDOFatalDataStoreException; 022import javax.jdo.JDOFatalUserException; 023import javax.jdo.JDOObjectNotFoundException; 024import javax.jdo.JDOOptimisticVerificationException; 025import javax.jdo.JDOUserException; 026import javax.jdo.PersistenceManager; 027import javax.jdo.PersistenceManagerFactory; 028import javax.jdo.Query; 029import javax.sql.DataSource; 030 031import org.apache.commons.logging.Log; 032import org.apache.commons.logging.LogFactory; 033 034import org.springframework.core.Ordered; 035import org.springframework.dao.DataAccessException; 036import org.springframework.dao.DataAccessResourceFailureException; 037import org.springframework.jdbc.datasource.DataSourceUtils; 038import org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator; 039import org.springframework.jdbc.support.SQLExceptionTranslator; 040import org.springframework.jdbc.support.SQLStateSQLExceptionTranslator; 041import org.springframework.transaction.support.ResourceHolderSynchronization; 042import org.springframework.transaction.support.TransactionSynchronizationManager; 043import org.springframework.util.Assert; 044 045/** 046 * Helper class featuring methods for JDO {@link PersistenceManager} handling, 047 * allowing for reuse of PersistenceManager instances within transactions. 048 * Also provides support for exception translation. 049 * 050 * <p>Used internally by {@link JdoTransactionManager}. 051 * Can also be used directly in application code. 052 * 053 * @author Juergen Hoeller 054 * @since 03.06.2003 055 * @see JdoTransactionManager 056 * @see org.springframework.transaction.jta.JtaTransactionManager 057 * @see org.springframework.transaction.support.TransactionSynchronizationManager 058 */ 059public abstract class PersistenceManagerFactoryUtils { 060 061 /** 062 * Order value for TransactionSynchronization objects that clean up JDO 063 * PersistenceManagers. Return DataSourceUtils.CONNECTION_SYNCHRONIZATION_ORDER - 100 064 * to execute PersistenceManager cleanup before JDBC Connection cleanup, if any. 065 * @see org.springframework.jdbc.datasource.DataSourceUtils#CONNECTION_SYNCHRONIZATION_ORDER 066 */ 067 public static final int PERSISTENCE_MANAGER_SYNCHRONIZATION_ORDER = 068 DataSourceUtils.CONNECTION_SYNCHRONIZATION_ORDER - 100; 069 070 private static final Log logger = LogFactory.getLog(PersistenceManagerFactoryUtils.class); 071 072 073 /** 074 * Create an appropriate SQLExceptionTranslator for the given PersistenceManagerFactory. 075 * <p>If a DataSource is found, creates a SQLErrorCodeSQLExceptionTranslator for the 076 * DataSource; else, falls back to a SQLStateSQLExceptionTranslator. 077 * @param connectionFactory the connection factory of the PersistenceManagerFactory 078 * (may be {@code null}) 079 * @return the SQLExceptionTranslator (never {@code null}) 080 * @see javax.jdo.PersistenceManagerFactory#getConnectionFactory() 081 * @see org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator 082 * @see org.springframework.jdbc.support.SQLStateSQLExceptionTranslator 083 */ 084 static SQLExceptionTranslator newJdbcExceptionTranslator(Object connectionFactory) { 085 // Check for PersistenceManagerFactory's DataSource. 086 if (connectionFactory instanceof DataSource) { 087 return new SQLErrorCodeSQLExceptionTranslator((DataSource) connectionFactory); 088 } 089 else { 090 return new SQLStateSQLExceptionTranslator(); 091 } 092 } 093 094 /** 095 * Obtain a JDO PersistenceManager via the given factory. Is aware of a 096 * corresponding PersistenceManager bound to the current thread, 097 * for example when using JdoTransactionManager. Will create a new 098 * PersistenceManager else, if "allowCreate" is {@code true}. 099 * @param pmf PersistenceManagerFactory to create the PersistenceManager with 100 * @param allowCreate if a non-transactional PersistenceManager should be created 101 * when no transactional PersistenceManager can be found for the current thread 102 * @return the PersistenceManager 103 * @throws DataAccessResourceFailureException if the PersistenceManager couldn't be obtained 104 * @throws IllegalStateException if no thread-bound PersistenceManager found and 105 * "allowCreate" is {@code false} 106 * @see JdoTransactionManager 107 */ 108 public static PersistenceManager getPersistenceManager(PersistenceManagerFactory pmf, boolean allowCreate) 109 throws DataAccessResourceFailureException, IllegalStateException { 110 111 try { 112 return doGetPersistenceManager(pmf, allowCreate); 113 } 114 catch (JDOException ex) { 115 throw new DataAccessResourceFailureException("Could not obtain JDO PersistenceManager", ex); 116 } 117 } 118 119 /** 120 * Obtain a JDO PersistenceManager via the given factory. Is aware of a 121 * corresponding PersistenceManager bound to the current thread, 122 * for example when using JdoTransactionManager. Will create a new 123 * PersistenceManager else, if "allowCreate" is {@code true}. 124 * <p>Same as {@code getPersistenceManager}, but throwing the original JDOException. 125 * @param pmf PersistenceManagerFactory to create the PersistenceManager with 126 * @param allowCreate if a non-transactional PersistenceManager should be created 127 * when no transactional PersistenceManager can be found for the current thread 128 * @return the PersistenceManager 129 * @throws JDOException if the PersistenceManager couldn't be created 130 * @throws IllegalStateException if no thread-bound PersistenceManager found and 131 * "allowCreate" is {@code false} 132 * @see #getPersistenceManager(javax.jdo.PersistenceManagerFactory, boolean) 133 * @see JdoTransactionManager 134 */ 135 public static PersistenceManager doGetPersistenceManager(PersistenceManagerFactory pmf, boolean allowCreate) 136 throws JDOException, IllegalStateException { 137 138 Assert.notNull(pmf, "No PersistenceManagerFactory specified"); 139 140 PersistenceManagerHolder pmHolder = 141 (PersistenceManagerHolder) TransactionSynchronizationManager.getResource(pmf); 142 if (pmHolder != null) { 143 if (!pmHolder.isSynchronizedWithTransaction() && 144 TransactionSynchronizationManager.isSynchronizationActive()) { 145 pmHolder.setSynchronizedWithTransaction(true); 146 TransactionSynchronizationManager.registerSynchronization( 147 new PersistenceManagerSynchronization(pmHolder, pmf, false)); 148 } 149 return pmHolder.getPersistenceManager(); 150 } 151 152 if (!allowCreate && !TransactionSynchronizationManager.isSynchronizationActive()) { 153 throw new IllegalStateException("No JDO PersistenceManager bound to thread, " + 154 "and configuration does not allow creation of non-transactional one here"); 155 } 156 157 logger.debug("Opening JDO PersistenceManager"); 158 PersistenceManager pm = pmf.getPersistenceManager(); 159 160 if (TransactionSynchronizationManager.isSynchronizationActive()) { 161 logger.debug("Registering transaction synchronization for JDO PersistenceManager"); 162 // Use same PersistenceManager for further JDO actions within the transaction. 163 // Thread object will get removed by synchronization at transaction completion. 164 pmHolder = new PersistenceManagerHolder(pm); 165 pmHolder.setSynchronizedWithTransaction(true); 166 TransactionSynchronizationManager.registerSynchronization( 167 new PersistenceManagerSynchronization(pmHolder, pmf, true)); 168 TransactionSynchronizationManager.bindResource(pmf, pmHolder); 169 } 170 171 return pm; 172 } 173 174 /** 175 * Return whether the given JDO PersistenceManager is transactional, that is, 176 * bound to the current thread by Spring's transaction facilities. 177 * @param pm the JDO PersistenceManager to check 178 * @param pmf JDO PersistenceManagerFactory that the PersistenceManager 179 * was created with (can be {@code null}) 180 * @return whether the PersistenceManager is transactional 181 */ 182 public static boolean isPersistenceManagerTransactional( 183 PersistenceManager pm, PersistenceManagerFactory pmf) { 184 185 if (pmf == null) { 186 return false; 187 } 188 PersistenceManagerHolder pmHolder = 189 (PersistenceManagerHolder) TransactionSynchronizationManager.getResource(pmf); 190 return (pmHolder != null && pm == pmHolder.getPersistenceManager()); 191 } 192 193 /** 194 * Apply the current transaction timeout, if any, to the given JDO Query object. 195 * @param query the JDO Query object 196 * @param pmf JDO PersistenceManagerFactory that the Query was created for 197 * @throws JDOException if thrown by JDO methods 198 */ 199 public static void applyTransactionTimeout(Query query, PersistenceManagerFactory pmf) throws JDOException { 200 Assert.notNull(query, "No Query object specified"); 201 PersistenceManagerHolder pmHolder = 202 (PersistenceManagerHolder) TransactionSynchronizationManager.getResource(pmf); 203 if (pmHolder != null && pmHolder.hasTimeout() && 204 pmf.supportedOptions().contains("javax.jdo.option.DatastoreTimeout")) { 205 int timeout = (int) pmHolder.getTimeToLiveInMillis(); 206 query.setDatastoreReadTimeoutMillis(timeout); 207 query.setDatastoreWriteTimeoutMillis(timeout); 208 } 209 } 210 211 /** 212 * Convert the given JDOException to an appropriate exception from the 213 * {@code org.springframework.dao} hierarchy. 214 * <p>The most important cases like object not found or optimistic locking failure 215 * are covered here. For more fine-granular conversion, JdoTransactionManager 216 * supports sophisticated translation of exceptions via a JdoDialect. 217 * @param ex JDOException that occured 218 * @return the corresponding DataAccessException instance 219 * @see JdoTransactionManager#convertJdoAccessException 220 * @see JdoDialect#translateException 221 */ 222 public static DataAccessException convertJdoAccessException(JDOException ex) { 223 if (ex instanceof JDOObjectNotFoundException) { 224 throw new JdoObjectRetrievalFailureException((JDOObjectNotFoundException) ex); 225 } 226 if (ex instanceof JDOOptimisticVerificationException) { 227 throw new JdoOptimisticLockingFailureException((JDOOptimisticVerificationException) ex); 228 } 229 if (ex instanceof JDODataStoreException) { 230 return new JdoResourceFailureException((JDODataStoreException) ex); 231 } 232 if (ex instanceof JDOFatalDataStoreException) { 233 return new JdoResourceFailureException((JDOFatalDataStoreException) ex); 234 } 235 if (ex instanceof JDOUserException) { 236 return new JdoUsageException((JDOUserException) ex); 237 } 238 if (ex instanceof JDOFatalUserException) { 239 return new JdoUsageException((JDOFatalUserException) ex); 240 } 241 // fallback 242 return new JdoSystemException(ex); 243 } 244 245 /** 246 * Close the given PersistenceManager, created via the given factory, 247 * if it is not managed externally (i.e. not bound to the thread). 248 * @param pm PersistenceManager to close 249 * @param pmf PersistenceManagerFactory that the PersistenceManager was created with 250 * (can be {@code null}) 251 */ 252 public static void releasePersistenceManager(PersistenceManager pm, PersistenceManagerFactory pmf) { 253 try { 254 doReleasePersistenceManager(pm, pmf); 255 } 256 catch (JDOException ex) { 257 logger.debug("Could not close JDO PersistenceManager", ex); 258 } 259 catch (Throwable ex) { 260 logger.debug("Unexpected exception on closing JDO PersistenceManager", ex); 261 } 262 } 263 264 /** 265 * Actually release a PersistenceManager for the given factory. 266 * Same as {@code releasePersistenceManager}, but throwing the original JDOException. 267 * @param pm PersistenceManager to close 268 * @param pmf PersistenceManagerFactory that the PersistenceManager was created with 269 * (can be {@code null}) 270 * @throws JDOException if thrown by JDO methods 271 */ 272 public static void doReleasePersistenceManager(PersistenceManager pm, PersistenceManagerFactory pmf) 273 throws JDOException { 274 275 if (pm == null) { 276 return; 277 } 278 // Only release non-transactional PersistenceManagers. 279 if (!isPersistenceManagerTransactional(pm, pmf)) { 280 logger.debug("Closing JDO PersistenceManager"); 281 pm.close(); 282 } 283 } 284 285 286 /** 287 * Callback for resource cleanup at the end of a non-JDO transaction 288 * (e.g. when participating in a JtaTransactionManager transaction). 289 * @see org.springframework.transaction.jta.JtaTransactionManager 290 */ 291 private static class PersistenceManagerSynchronization 292 extends ResourceHolderSynchronization<PersistenceManagerHolder, PersistenceManagerFactory> 293 implements Ordered { 294 295 private final boolean newPersistenceManager; 296 297 public PersistenceManagerSynchronization( 298 PersistenceManagerHolder pmHolder, PersistenceManagerFactory pmf, boolean newPersistenceManager) { 299 super(pmHolder, pmf); 300 this.newPersistenceManager = newPersistenceManager; 301 } 302 303 @Override 304 public int getOrder() { 305 return PERSISTENCE_MANAGER_SYNCHRONIZATION_ORDER; 306 } 307 308 @Override 309 public void flushResource(PersistenceManagerHolder resourceHolder) { 310 try { 311 resourceHolder.getPersistenceManager().flush(); 312 } 313 catch (JDOException ex) { 314 throw convertJdoAccessException(ex); 315 } 316 } 317 318 @Override 319 protected boolean shouldUnbindAtCompletion() { 320 return this.newPersistenceManager; 321 } 322 323 @Override 324 protected boolean shouldReleaseAfterCompletion(PersistenceManagerHolder resourceHolder) { 325 return !resourceHolder.getPersistenceManager().isClosed(); 326 } 327 328 @Override 329 protected void releaseResource(PersistenceManagerHolder resourceHolder, PersistenceManagerFactory resourceKey) { 330 releasePersistenceManager(resourceHolder.getPersistenceManager(), resourceKey); 331 } 332 } 333 334}