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 java.io.IOException; 020import java.util.HashMap; 021import java.util.Map; 022import java.util.Properties; 023import javax.jdo.JDOException; 024import javax.jdo.JDOHelper; 025import javax.jdo.PersistenceManagerFactory; 026 027import org.apache.commons.logging.Log; 028import org.apache.commons.logging.LogFactory; 029 030import org.springframework.beans.factory.BeanClassLoaderAware; 031import org.springframework.beans.factory.DisposableBean; 032import org.springframework.beans.factory.FactoryBean; 033import org.springframework.beans.factory.InitializingBean; 034import org.springframework.core.io.Resource; 035import org.springframework.core.io.support.PropertiesLoaderUtils; 036import org.springframework.dao.DataAccessException; 037import org.springframework.dao.support.PersistenceExceptionTranslator; 038import org.springframework.util.CollectionUtils; 039 040/** 041 * {@link org.springframework.beans.factory.FactoryBean} that creates a 042 * JDO {@link javax.jdo.PersistenceManagerFactory}. This is the usual way to 043 * set up a shared JDO PersistenceManagerFactory in a Spring application context; 044 * the PersistenceManagerFactory can then be passed to JDO-based DAOs via 045 * dependency injection. Note that switching to a JNDI lookup or to a bean-style 046 * PersistenceManagerFactory instance is just a matter of configuration! 047 * 048 * <p><b>NOTE: This class requires JDO 3.0 or higher, as of Spring 4.0.</b> 049 * It will also expose the JPA {@link javax.persistence.EntityManagerFactory} as long 050 * as the JDO provider creates a {@link javax.jdo.JDOEntityManagerFactory} reference 051 * underneath, which means that this class can be used as a replacement for 052 * {@link org.springframework.orm.jpa.LocalEntityManagerFactoryBean} in such a scenario. 053 * 054 * <p>Configuration settings can either be read from a properties file, 055 * specified as "configLocation", or locally specified. Properties 056 * specified as "jdoProperties" here will override any settings in a file. 057 * You may alternatively specify a "persistenceManagerFactoryName", 058 * referring to a PMF definition in "META-INF/jdoconfig.xml" 059 * (see {@link #setPersistenceManagerFactoryName}). 060 * 061 * <p>This class also implements the 062 * {@link org.springframework.dao.support.PersistenceExceptionTranslator} 063 * interface, as autodetected by Spring's 064 * {@link org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor}, 065 * for AOP-based translation of native exceptions to Spring DataAccessExceptions. 066 * Hence, the presence of a LocalPersistenceManagerFactoryBean automatically enables 067 * a PersistenceExceptionTranslationPostProcessor to translate JDO exceptions. 068 * 069 * <p><b>Alternative: Configuration of a PersistenceManagerFactory provider bean</b> 070 * 071 * <p>As alternative to the properties-driven approach that this FactoryBean offers 072 * (which is analogous to using the standard JDOHelper class with a Properties 073 * object that is populated with standard JDO properties), you can set up an 074 * instance of your PersistenceManagerFactory implementation class directly. 075 * 076 * <p>Like a DataSource, a PersistenceManagerFactory is encouraged to 077 * support bean-style configuration, which makes it very easy to set up as 078 * Spring-managed bean. The implementation class becomes the bean class; 079 * the remaining properties are applied as bean properties (starting with 080 * lower-case characters, in contrast to the corresponding JDO properties). 081 * 082 * <p>For example, in case of <a href="http://www.jpox.org">JPOX</a>: 083 * 084 * <p><pre class="code"> 085 * <bean id="persistenceManagerFactory" class="org.jpox.PersistenceManagerFactoryImpl" destroy-method="close"> 086 * <property name="connectionFactory" ref="dataSource"/> 087 * <property name="nontransactionalRead" value="true"/> 088 * </bean> 089 * </pre> 090 * 091 * <p>Note that such direct setup of a PersistenceManagerFactory implementation 092 * is the only way to pass an external connection factory (i.e. a JDBC DataSource) 093 * into a JDO PersistenceManagerFactory. With the standard properties-driven approach, 094 * you can only use an internal connection pool or a JNDI DataSource. 095 * 096 * <p>The {@code close()} method is standardized in JDO; don't forget to 097 * specify it as "destroy-method" for any PersistenceManagerFactory instance. 098 * Note that this FactoryBean will automatically invoke {@code close()} for 099 * the PersistenceManagerFactory that it creates, without any special configuration. 100 * 101 * @author Juergen Hoeller 102 * @since 03.06.2003 103 * @see JdoTransactionManager#setPersistenceManagerFactory 104 * @see org.springframework.jndi.JndiObjectFactoryBean 105 * @see javax.jdo.JDOHelper#getPersistenceManagerFactory 106 * @see javax.jdo.PersistenceManagerFactory#setConnectionFactory 107 * @see javax.jdo.PersistenceManagerFactory#close() 108 * @see org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor 109 */ 110public class LocalPersistenceManagerFactoryBean implements FactoryBean<PersistenceManagerFactory>, 111 BeanClassLoaderAware, InitializingBean, DisposableBean, PersistenceExceptionTranslator { 112 113 protected final Log logger = LogFactory.getLog(getClass()); 114 115 private String persistenceManagerFactoryName; 116 117 private Resource configLocation; 118 119 private final Map<String, Object> jdoPropertyMap = new HashMap<String, Object>(); 120 121 private ClassLoader beanClassLoader; 122 123 private PersistenceManagerFactory persistenceManagerFactory; 124 125 private JdoDialect jdoDialect; 126 127 128 /** 129 * Specify the name of the desired PersistenceManagerFactory. 130 * <p>This may either be a properties resource in the classpath if such a resource 131 * exists, or a PMF definition with that name from "META-INF/jdoconfig.xml", 132 * or a JPA EntityManagerFactory cast to a PersistenceManagerFactory based on the 133 * persistence-unit name from "META-INF/persistence.xml" (JPA). 134 * <p>Default is none: Either 'persistenceManagerFactoryName' or 'configLocation' 135 * or 'jdoProperties' needs to be specified. 136 * @see #setConfigLocation 137 * @see #setJdoProperties 138 */ 139 public void setPersistenceManagerFactoryName(String persistenceManagerFactoryName) { 140 this.persistenceManagerFactoryName = persistenceManagerFactoryName; 141 } 142 143 /** 144 * Set the location of the JDO properties config file, for example 145 * as classpath resource "classpath:kodo.properties". 146 * <p>Note: Can be omitted when all necessary properties are 147 * specified locally via this bean. 148 */ 149 public void setConfigLocation(Resource configLocation) { 150 this.configLocation = configLocation; 151 } 152 153 /** 154 * Set JDO properties, such as"javax.jdo.PersistenceManagerFactoryClass". 155 * <p>Can be used to override values in a JDO properties config file, 156 * or to specify all necessary properties locally. 157 * <p>Can be populated with a String "value" (parsed via PropertiesEditor) 158 * or a "props" element in XML bean definitions. 159 */ 160 public void setJdoProperties(Properties jdoProperties) { 161 CollectionUtils.mergePropertiesIntoMap(jdoProperties, this.jdoPropertyMap); 162 } 163 164 /** 165 * Specify JDO properties as a Map, to be passed into 166 * {@code JDOHelper.getPersistenceManagerFactory} (if any). 167 * <p>Can be populated with a "map" or "props" element in XML bean definitions. 168 * @see javax.jdo.JDOHelper#getPersistenceManagerFactory(java.util.Map) 169 */ 170 public void setJdoPropertyMap(Map<String, Object> jdoProperties) { 171 if (jdoProperties != null) { 172 this.jdoPropertyMap.putAll(jdoProperties); 173 } 174 } 175 176 /** 177 * Allow Map access to the JDO properties to be passed to the JDOHelper, 178 * with the option to add or override specific entries. 179 * <p>Useful for specifying entries directly, for example via 180 * "jdoPropertyMap[myKey]". 181 */ 182 public Map<String, Object> getJdoPropertyMap() { 183 return this.jdoPropertyMap; 184 } 185 /** 186 * Set the JDO dialect to use for the PersistenceExceptionTranslator 187 * functionality of this factory. 188 * <p>Default is a DefaultJdoDialect based on the PersistenceManagerFactory's 189 * underlying DataSource, if any. 190 * @see JdoDialect#translateException 191 * @see #translateExceptionIfPossible 192 * @see org.springframework.dao.support.PersistenceExceptionTranslator 193 */ 194 public void setJdoDialect(JdoDialect jdoDialect) { 195 this.jdoDialect = jdoDialect; 196 } 197 198 @Override 199 public void setBeanClassLoader(ClassLoader beanClassLoader) { 200 this.beanClassLoader = beanClassLoader; 201 } 202 203 204 /** 205 * Initialize the PersistenceManagerFactory for the given location. 206 * @throws IllegalArgumentException in case of illegal property values 207 * @throws IOException if the properties could not be loaded from the given location 208 * @throws JDOException in case of JDO initialization errors 209 */ 210 @Override 211 public void afterPropertiesSet() throws IllegalArgumentException, IOException, JDOException { 212 if (this.persistenceManagerFactoryName != null) { 213 if (this.configLocation != null || !this.jdoPropertyMap.isEmpty()) { 214 throw new IllegalStateException("'configLocation'/'jdoProperties' not supported in " + 215 "combination with 'persistenceManagerFactoryName' - specify one or the other, not both"); 216 } 217 if (logger.isInfoEnabled()) { 218 logger.info("Building new JDO PersistenceManagerFactory for name '" + 219 this.persistenceManagerFactoryName + "'"); 220 } 221 this.persistenceManagerFactory = newPersistenceManagerFactory(this.persistenceManagerFactoryName); 222 } 223 224 else { 225 Map<String, Object> mergedProps = new HashMap<String, Object>(); 226 if (this.configLocation != null) { 227 if (logger.isInfoEnabled()) { 228 logger.info("Loading JDO config from [" + this.configLocation + "]"); 229 } 230 CollectionUtils.mergePropertiesIntoMap( 231 PropertiesLoaderUtils.loadProperties(this.configLocation), mergedProps); 232 } 233 mergedProps.putAll(this.jdoPropertyMap); 234 logger.info("Building new JDO PersistenceManagerFactory"); 235 this.persistenceManagerFactory = newPersistenceManagerFactory(mergedProps); 236 } 237 238 // Build default JdoDialect if none explicitly specified. 239 if (this.jdoDialect == null) { 240 this.jdoDialect = new DefaultJdoDialect(this.persistenceManagerFactory.getConnectionFactory()); 241 } 242 } 243 244 /** 245 * Subclasses can override this to perform custom initialization of the 246 * PersistenceManagerFactory instance, creating it for the specified name. 247 * <p>The default implementation invokes JDOHelper's 248 * {@code getPersistenceManagerFactory(String)} method. 249 * A custom implementation could prepare the instance in a specific way, 250 * or use a custom PersistenceManagerFactory implementation. 251 * @param name the name of the desired PersistenceManagerFactory 252 * @return the PersistenceManagerFactory instance 253 * @see javax.jdo.JDOHelper#getPersistenceManagerFactory(String) 254 */ 255 protected PersistenceManagerFactory newPersistenceManagerFactory(String name) { 256 return JDOHelper.getPersistenceManagerFactory(name, this.beanClassLoader); 257 } 258 259 /** 260 * Subclasses can override this to perform custom initialization of the 261 * PersistenceManagerFactory instance, creating it via the given Properties 262 * that got prepared by this LocalPersistenceManagerFactoryBean. 263 * <p>The default implementation invokes JDOHelper's 264 * {@code getPersistenceManagerFactory(Map)} method. 265 * A custom implementation could prepare the instance in a specific way, 266 * or use a custom PersistenceManagerFactory implementation. 267 * @param props the merged properties prepared by this LocalPersistenceManagerFactoryBean 268 * @return the PersistenceManagerFactory instance 269 * @see javax.jdo.JDOHelper#getPersistenceManagerFactory(java.util.Map) 270 */ 271 protected PersistenceManagerFactory newPersistenceManagerFactory(Map<?, ?> props) { 272 return JDOHelper.getPersistenceManagerFactory(props, this.beanClassLoader); 273 } 274 275 276 /** 277 * Return the singleton PersistenceManagerFactory. 278 */ 279 @Override 280 public PersistenceManagerFactory getObject() { 281 return this.persistenceManagerFactory; 282 } 283 284 @Override 285 public Class<? extends PersistenceManagerFactory> getObjectType() { 286 return (this.persistenceManagerFactory != null ? 287 this.persistenceManagerFactory.getClass() : PersistenceManagerFactory.class); 288 } 289 290 @Override 291 public boolean isSingleton() { 292 return true; 293 } 294 295 296 /** 297 * Implementation of the PersistenceExceptionTranslator interface, 298 * as autodetected by Spring's PersistenceExceptionTranslationPostProcessor. 299 * <p>Converts the exception if it is a JDOException, preferably using a specified 300 * JdoDialect. Else returns {@code null} to indicate an unknown exception. 301 * @see org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor 302 * @see JdoDialect#translateException 303 * @see PersistenceManagerFactoryUtils#convertJdoAccessException 304 */ 305 @Override 306 public DataAccessException translateExceptionIfPossible(RuntimeException ex) { 307 if (ex instanceof JDOException) { 308 if (this.jdoDialect != null) { 309 return this.jdoDialect.translateException((JDOException) ex); 310 } 311 else { 312 return PersistenceManagerFactoryUtils.convertJdoAccessException((JDOException) ex); 313 } 314 } 315 return null; 316 } 317 318 319 /** 320 * Close the PersistenceManagerFactory on bean factory shutdown. 321 */ 322 @Override 323 public void destroy() { 324 logger.info("Closing JDO PersistenceManagerFactory"); 325 this.persistenceManagerFactory.close(); 326 } 327 328}