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