content: /*
   *  Licensed to the Apache Software Foundation (ASF) under one
   *  or more contributor license agreements.  See the NOTICE file
   *  distributed with this work for additional information
   *  regarding copyright ownership.  The ASF licenses this file
   *  to you under the Apache License, Version 2.0 (the
   *  "License"); you may not use this file except in compliance
   *  with the License.  You may obtain a copy of the License at
   *
   *        http://www.apache.org/licenses/LICENSE-2.0
   *
   *  Unless required by applicable law or agreed to in writing,
   *  software distributed under the License is distributed on an
   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
   *  KIND, either express or implied.  See the License for the
   *  specific language governing permissions and limitations
   *  under the License.
   */
  package org.apache.causeway.persistence.jdo.spring.integration;
  
  import javax.jdo.JDODataStoreException;
  import javax.jdo.JDOException;
  import javax.jdo.JDOFatalDataStoreException;
  import javax.jdo.JDOFatalUserException;
  import javax.jdo.JDOObjectNotFoundException;
  import javax.jdo.JDOOptimisticVerificationException;
  import javax.jdo.JDOUserException;
  import javax.jdo.PersistenceManager;
  import javax.jdo.PersistenceManagerFactory;
  import javax.jdo.Query;
  import javax.sql.DataSource;
  
  import org.apache.commons.logging.Log;
  import org.apache.commons.logging.LogFactory;
  
  import org.springframework.core.Ordered;
  import org.springframework.dao.DataAccessException;
  import org.springframework.dao.DataAccessResourceFailureException;
  import org.springframework.jdbc.datasource.DataSourceUtils;
  import org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator;
  import org.springframework.jdbc.support.SQLExceptionTranslator;
  import org.springframework.jdbc.support.SQLStateSQLExceptionTranslator;
  import org.springframework.transaction.support.ResourceHolderSynchronization;
  import org.springframework.transaction.support.TransactionSynchronizationManager;
  import org.springframework.util.Assert;
  
  import org.apache.causeway.persistence.jdo.spring.exceptions.JdoObjectRetrievalFailureException;
  import org.apache.causeway.persistence.jdo.spring.exceptions.JdoOptimisticLockingFailureException;
  import org.apache.causeway.persistence.jdo.spring.exceptions.JdoResourceFailureException;
  import org.apache.causeway.persistence.jdo.spring.exceptions.JdoSystemException;
  import org.apache.causeway.persistence.jdo.spring.exceptions.JdoUsageException;
  
  /**
   * Helper class featuring methods for JDO {@link PersistenceManager} handling,
   * allowing for reuse of PersistenceManager instances within transactions.
   * Also provides support for exception translation.
   *
   * <p>Used internally by {@link JdoTransactionManager}.
   * Can also be used directly in application code.
   *
   * @see JdoTransactionManager
   * @see org.springframework.transaction.jta.JtaTransactionManager
   * @see org.springframework.transaction.support.TransactionSynchronizationManager
   */
  public abstract class PersistenceManagerFactoryUtils {
  
  	/**
  	 * Order value for TransactionSynchronization objects that clean up JDO
  	 * PersistenceManagers. Return DataSourceUtils.CONNECTION_SYNCHRONIZATION_ORDER - 100
  	 * to execute PersistenceManager cleanup before JDBC Connection cleanup, if any.
  	 * @see org.springframework.jdbc.datasource.DataSourceUtils#CONNECTION_SYNCHRONIZATION_ORDER
  	 */
  	public static final int PERSISTENCE_MANAGER_SYNCHRONIZATION_ORDER =
  			DataSourceUtils.CONNECTION_SYNCHRONIZATION_ORDER - 100;
  
  	private static final Log logger = LogFactory.getLog(PersistenceManagerFactoryUtils.class);
  
  	/**
  	 * Create an appropriate SQLExceptionTranslator for the given PersistenceManagerFactory.
  	 * <p>If a DataSource is found, creates a SQLErrorCodeSQLExceptionTranslator for the
  	 * DataSource; else, falls back to a SQLStateSQLExceptionTranslator.
  	 * @param connectionFactory the connection factory of the PersistenceManagerFactory
  	 * (may be {@code null})
  	 * @return the SQLExceptionTranslator (never {@code null})
  	 * @see javax.jdo.PersistenceManagerFactory#getConnectionFactory()
  	 * @see org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator
  	 * @see org.springframework.jdbc.support.SQLStateSQLExceptionTranslator
  	 */
  	static SQLExceptionTranslator newJdbcExceptionTranslator(Object connectionFactory) {
  		// Check for PersistenceManagerFactory's DataSource.
  		if (connectionFactory instanceof DataSource) {
  			return new SQLErrorCodeSQLExceptionTranslator((DataSource) connectionFactory);
  		}
  		else {
  			return new SQLStateSQLExceptionTranslator();
  		}
  	}
  
  	/**
  	 * Obtain a JDO PersistenceManager via the given factory. Is aware of a
  	 * corresponding PersistenceManager bound to the current thread,
  	 * for example when using JdoTransactionManager. Will create a new
  	 * PersistenceManager else, if "allowCreate" is {@code true}.
  	 * @param pmf PersistenceManagerFactory to create the PersistenceManager with
  	 * @param allowCreate if a non-transactional PersistenceManager should be created
  	 * when no transactional PersistenceManager can be found for the current thread
  	 * @return the PersistenceManager
  	 * @throws DataAccessResourceFailureException if the PersistenceManager couldn't be obtained
  	 * @throws IllegalStateException if no thread-bound PersistenceManager found and
  	 * "allowCreate" is {@code false}
  	 * @see JdoTransactionManager
  	 */
  	public static PersistenceManager getPersistenceManager(PersistenceManagerFactory pmf, boolean allowCreate)
  		throws DataAccessResourceFailureException, IllegalStateException {
  
  		try {
  			return doGetPersistenceManager(pmf, allowCreate);
  		}
  		catch (JDOException ex) {
  			throw new DataAccessResourceFailureException("Could not obtain JDO PersistenceManager", ex);
  		}
  	}
  
  	/**
  	 * Obtain a JDO PersistenceManager via the given factory. Is aware of a
  	 * corresponding PersistenceManager bound to the current thread,
  	 * for example when using JdoTransactionManager. Will create a new
  	 * PersistenceManager else, if "allowCreate" is {@code true}.
  	 * <p>Same as {@code getPersistenceManager}, but throwing the original JDOException.
  	 * @param pmf PersistenceManagerFactory to create the PersistenceManager with
  	 * @param allowCreate if a non-transactional PersistenceManager should be created
  	 * when no transactional PersistenceManager can be found for the current thread
  	 * @return the PersistenceManager
  	 * @throws JDOException if the PersistenceManager couldn't be created
  	 * @throws IllegalStateException if no thread-bound PersistenceManager found and
  	 * "allowCreate" is {@code false}
  	 * @see #getPersistenceManager(javax.jdo.PersistenceManagerFactory, boolean)
  	 * @see JdoTransactionManager
  	 */
  	public static PersistenceManager doGetPersistenceManager(PersistenceManagerFactory pmf, boolean allowCreate)
  		throws JDOException, IllegalStateException {
  
  		Assert.notNull(pmf, "No PersistenceManagerFactory specified");
  
  		PersistenceManagerHolder pmHolder =
  				(PersistenceManagerHolder) TransactionSynchronizationManager.getResource(pmf);
  		if (pmHolder != null) {
  			if (!pmHolder.isSynchronizedWithTransaction() &&
  					TransactionSynchronizationManager.isSynchronizationActive()) {
  				pmHolder.setSynchronizedWithTransaction(true);
  				TransactionSynchronizationManager.registerSynchronization(
  						new PersistenceManagerSynchronization(pmHolder, pmf, false));
  			}
  			return pmHolder.getPersistenceManager();
  		}
  
  		if (!allowCreate && !TransactionSynchronizationManager.isSynchronizationActive()) {
  			throw new IllegalStateException("No JDO PersistenceManager bound to thread, " +
  					"and configuration does not allow creation of non-transactional one here");
  		}
  
  		logger.debug("Opening JDO PersistenceManager");
  		PersistenceManager pm = pmf.getPersistenceManager();
  
  		if (TransactionSynchronizationManager.isSynchronizationActive()) {
  			logger.debug("Registering transaction synchronization for JDO PersistenceManager");
  			// Use same PersistenceManager for further JDO actions within the transaction.
  			// Thread object will get removed by synchronization at transaction completion.
  			pmHolder = new PersistenceManagerHolder(pm);
  			pmHolder.setSynchronizedWithTransaction(true);
  			TransactionSynchronizationManager.registerSynchronization(
  					new PersistenceManagerSynchronization(pmHolder, pmf, true));
  			TransactionSynchronizationManager.bindResource(pmf, pmHolder);
  		}
  
  		return pm;
  	}
  
  	/**
  	 * Return whether the given JDO PersistenceManager is transactional, that is,
  	 * bound to the current thread by Spring's transaction facilities.
  	 * @param pm the JDO PersistenceManager to check
  	 * @param pmf JDO PersistenceManagerFactory that the PersistenceManager
  	 * was created with (can be {@code null})
  	 * @return whether the PersistenceManager is transactional
  	 */
  	public static boolean isPersistenceManagerTransactional(
  			PersistenceManager pm, PersistenceManagerFactory pmf) {
  
  		if (pmf == null) {
  			return false;
  		}
  		PersistenceManagerHolder pmHolder =
  				(PersistenceManagerHolder) TransactionSynchronizationManager.getResource(pmf);
  		return (pmHolder != null && pm == pmHolder.getPersistenceManager());
  	}
  
  	/**
  	 * Apply the current transaction timeout, if any, to the given JDO Query object.
  	 * @param query the JDO Query object
  	 * @param pmf JDO PersistenceManagerFactory that the Query was created for
  	 * @throws JDOException if thrown by JDO methods
  	 */
  	public static void applyTransactionTimeout(Query<?> query, PersistenceManagerFactory pmf) throws JDOException {
  		Assert.notNull(query, "No Query object specified");
  		PersistenceManagerHolder pmHolder =
  				(PersistenceManagerHolder) TransactionSynchronizationManager.getResource(pmf);
  		if (pmHolder != null && pmHolder.hasTimeout() &&
  				pmf.supportedOptions().contains("javax.jdo.option.DatastoreTimeout")) {
  			int timeout = (int) pmHolder.getTimeToLiveInMillis();
  			query.setDatastoreReadTimeoutMillis(timeout);
  			query.setDatastoreWriteTimeoutMillis(timeout);
  		}
  	}
  
  	/**
  	 * Convert the given JDOException to an appropriate exception from the
  	 * {@code org.springframework.dao} hierarchy.
  	 * <p>The most important cases like object not found or optimistic locking failure
  	 * are covered here. For more fine-granular conversion, JdoTransactionManager
  	 * supports sophisticated translation of exceptions via a JdoDialect.
  	 * @param ex JDOException that occured
  	 * @return the corresponding DataAccessException instance
  	 * @see JdoTransactionManager#convertJdoAccessException
  	 * @see JdoDialect#translateException
  	 */
  	public static DataAccessException convertJdoAccessException(JDOException ex) {
  		if (ex instanceof JDOObjectNotFoundException) {
  			throw new JdoObjectRetrievalFailureException((JDOObjectNotFoundException) ex);
  		}
  		if (ex instanceof JDOOptimisticVerificationException) {
  			throw new JdoOptimisticLockingFailureException((JDOOptimisticVerificationException) ex);
  		}
  		if (ex instanceof JDODataStoreException) {
  			return new JdoResourceFailureException((JDODataStoreException) ex);
  		}
  		if (ex instanceof JDOFatalDataStoreException) {
  			return new JdoResourceFailureException((JDOFatalDataStoreException) ex);
  		}
  		if (ex instanceof JDOUserException) {
  			return new JdoUsageException((JDOUserException) ex);
  		}
  		if (ex instanceof JDOFatalUserException) {
  			return new JdoUsageException((JDOFatalUserException) ex);
  		}
  		// fallback
  		return new JdoSystemException(ex);
  	}
  
  	/**
  	 * Close the given PersistenceManager, created via the given factory,
  	 * if it is not managed externally (i.e. not bound to the thread).
  	 * @param pm PersistenceManager to close
  	 * @param pmf PersistenceManagerFactory that the PersistenceManager was created with
  	 * (can be {@code null})
  	 */
  	public static void releasePersistenceManager(PersistenceManager pm, PersistenceManagerFactory pmf) {
  		try {
  			doReleasePersistenceManager(pm, pmf);
  		}
  		catch (JDOException ex) {
  			logger.debug("Could not close JDO PersistenceManager", ex);
  		}
  		catch (Throwable ex) {
  			logger.debug("Unexpected exception on closing JDO PersistenceManager", ex);
  		}
  	}
  
  	/**
  	 * Actually release a PersistenceManager for the given factory.
  	 * Same as {@code releasePersistenceManager}, but throwing the original JDOException.
  	 * @param pm PersistenceManager to close
  	 * @param pmf PersistenceManagerFactory that the PersistenceManager was created with
  	 * (can be {@code null})
  	 * @throws JDOException if thrown by JDO methods
  	 */
  	public static void doReleasePersistenceManager(PersistenceManager pm, PersistenceManagerFactory pmf)
  			throws JDOException {
  
  		if (pm == null) {
  			return;
  		}
  		// Only release non-transactional PersistenceManagers.
  		if (!isPersistenceManagerTransactional(pm, pmf)) {
  			logger.debug("Closing JDO PersistenceManager");
  			pm.close();
  		}
  	}
  
  	/**
  	 * Callback for resource cleanup at the end of a non-JDO transaction
  	 * (e.g. when participating in a JtaTransactionManager transaction).
  	 * @see org.springframework.transaction.jta.JtaTransactionManager
  	 */
  	private static class PersistenceManagerSynchronization
  			extends ResourceHolderSynchronization<PersistenceManagerHolder, PersistenceManagerFactory>
  			implements Ordered {
  
  		private final boolean newPersistenceManager;
  
  		public PersistenceManagerSynchronization(
  				PersistenceManagerHolder pmHolder, PersistenceManagerFactory pmf, boolean newPersistenceManager) {
  			super(pmHolder, pmf);
  			this.newPersistenceManager = newPersistenceManager;
  		}
  
  		@Override
  		public int getOrder() {
  			return PERSISTENCE_MANAGER_SYNCHRONIZATION_ORDER;
  		}
  
  		@Override
  		public void flushResource(PersistenceManagerHolder resourceHolder) {
  			try {
  				resourceHolder.getPersistenceManager().flush();
  			}
  			catch (JDOException ex) {
  				throw convertJdoAccessException(ex);
  			}
  		}
  
  		@Override
  		protected boolean shouldUnbindAtCompletion() {
  			return this.newPersistenceManager;
  		}
  
  		@Override
  		protected boolean shouldReleaseAfterCompletion(PersistenceManagerHolder resourceHolder) {
  			return !resourceHolder.getPersistenceManager().isClosed();
  		}
  
  		@Override
  		protected void releaseResource(PersistenceManagerHolder resourceHolder, PersistenceManagerFactory resourceKey) {
  			releasePersistenceManager(resourceHolder.getPersistenceManager(), resourceKey);
  		}
  	}
  
  }
  