001: /*
002: * Copyright 2002-2006 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: * http://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:
017: package org.springframework.orm.jdo;
018:
019: import java.io.IOException;
020: import java.lang.reflect.Method;
021: import java.util.Properties;
022:
023: import javax.jdo.JDOException;
024: import javax.jdo.JDOHelper;
025: import javax.jdo.PersistenceManagerFactory;
026:
027: import org.apache.commons.logging.Log;
028: import org.apache.commons.logging.LogFactory;
029:
030: import org.springframework.beans.factory.DisposableBean;
031: import org.springframework.beans.factory.FactoryBean;
032: import org.springframework.beans.factory.InitializingBean;
033: import org.springframework.core.io.Resource;
034: import org.springframework.core.io.support.PropertiesLoaderUtils;
035: import org.springframework.dao.DataAccessException;
036: import org.springframework.dao.support.PersistenceExceptionTranslator;
037: import org.springframework.util.ClassUtils;
038: import org.springframework.util.CollectionUtils;
039: import org.springframework.util.ReflectionUtils;
040:
041: /**
042: * {@link org.springframework.beans.factory.FactoryBean} that creates a
043: * JDO {@link javax.jdo.PersistenceManagerFactory}. This is the usual way to
044: * set up a shared JDO PersistenceManagerFactory in a Spring application context;
045: * the PersistenceManagerFactory can then be passed to JDO-based DAOs via
046: * dependency injection. Note that switching to a JNDI lookup or to a bean-style
047: * PersistenceManagerFactory instance is just a matter of configuration!
048: *
049: * <p>Configuration settings can either be read from a properties file,
050: * specified as "configLocation", or completely via this class. Properties
051: * specified as "jdoProperties" here will override any settings in a file.
052: *
053: * <p>This PersistenceManager handling strategy is most appropriate for
054: * applications which solely use JDO for data access. In this case,
055: * {@link JdoTransactionManager} is more convenient than setting up your
056: * JDO provider for JTA transactions (which might involve a JCA connector).
057: *
058: * <p><b>NOTE:</b> This class is compatible with both JDO 1.0 and JDO 2.0,
059: * as far as possible. It uses reflection to adapt to the actual API present
060: * on the class path (concretely: for the <code>getPersistenceManagerFactory</code>
061: * method with either a <code>Properties</code> or a <code>Map</code> argument).
062: * Make sure that the JDO API jar on your class path matches the one that
063: * your JDO provider has been compiled against!
064: *
065: * <p>This class also implements the
066: * {@link org.springframework.dao.support.PersistenceExceptionTranslator}
067: * interface, as autodetected by Spring's
068: * {@link org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor},
069: * for AOP-based translation of native exceptions to Spring DataAccessExceptions.
070: * Hence, the presence of a LocalPersistenceManagerFactoryBean automatically enables
071: * a PersistenceExceptionTranslationPostProcessor to translate JDO exceptions.
072: *
073: * <p><b>Alternative: Configuration of a PersistenceManagerFactory provider bean</b>
074: *
075: * <p>As alternative to the properties-driven approach that this FactoryBean offers
076: * (which is analogous to using the standard JDOHelper class with a Properties
077: * object that is populated with standard JDO properties), you can set up an
078: * instance of your PersistenceManagerFactory implementation class directly.
079: *
080: * <p>Like a DataSource, a PersistenceManagerFactory is encouraged to
081: * support bean-style configuration, which makes it very easy to set up as
082: * Spring-managed bean. The implementation class becomes the bean class;
083: * the remaining properties are applied as bean properties (starting with
084: * lower-case characters, in contrast to the corresponding JDO properties).
085: *
086: * <p>For example, in case of <a href="http://www.jpox.org">JPOX</a>:
087: *
088: * <p><pre>
089: * <bean id="persistenceManagerFactory" class="org.jpox.PersistenceManagerFactoryImpl" destroy-method="close">
090: * <property name="connectionFactory" ref="dataSource"/>
091: * <property name="nontransactionalRead" value="true"/>
092: * </bean>
093: * </pre>
094: *
095: * <p>Note that such direct setup of a PersistenceManagerFactory implementation
096: * is the only way to pass an external connection factory (i.e. a JDBC DataSource)
097: * into a JDO PersistenceManagerFactory. With the standard properties-driven approach,
098: * you can only use an internal connection pool or a JNDI DataSource.
099: *
100: * <p>The <code>close()</code> method is standardized as of JDO 1.0.1; don't forget to
101: * specify it as "destroy-method" for any PersistenceManagerFactory instance.
102: * Note that this FactoryBean will automatically invoke <code>close()</code> for
103: * the PersistenceManagerFactory that it creates, without any special configuration.
104: *
105: * @author Juergen Hoeller
106: * @since 03.06.2003
107: * @see JdoTemplate#setPersistenceManagerFactory
108: * @see JdoTransactionManager#setPersistenceManagerFactory
109: * @see org.springframework.jndi.JndiObjectFactoryBean
110: * @see javax.jdo.JDOHelper#getPersistenceManagerFactory
111: * @see javax.jdo.PersistenceManagerFactory#setConnectionFactory
112: * @see javax.jdo.PersistenceManagerFactory#close()
113: * @see org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor
114: */
115: public class LocalPersistenceManagerFactoryBean implements FactoryBean,
116: InitializingBean, DisposableBean,
117: PersistenceExceptionTranslator {
118:
119: // Determine whether the JDO 1.0 getPersistenceManagerFactory(Properties) method
120: // is available, for use in the "newPersistenceManagerFactory" implementation.
121: private final static Method getPersistenceManagerFactoryMethod = ClassUtils
122: .getMethodIfAvailable(JDOHelper.class,
123: "getPersistenceManagerFactory",
124: new Class[] { Properties.class });
125:
126: protected final Log logger = LogFactory.getLog(getClass());
127:
128: private Resource configLocation;
129:
130: private Properties jdoProperties;
131:
132: private PersistenceManagerFactory persistenceManagerFactory;
133:
134: private JdoDialect jdoDialect;
135:
136: /**
137: * Set the location of the JDO properties config file, for example
138: * as classpath resource "classpath:kodo.properties".
139: * <p>Note: Can be omitted when all necessary properties are
140: * specified locally via this bean.
141: */
142: public void setConfigLocation(Resource configLocation) {
143: this .configLocation = configLocation;
144: }
145:
146: /**
147: * Set JDO properties, such as"javax.jdo.PersistenceManagerFactoryClass".
148: * <p>Can be used to override values in a JDO properties config file,
149: * or to specify all necessary properties locally.
150: */
151: public void setJdoProperties(Properties jdoProperties) {
152: this .jdoProperties = jdoProperties;
153: }
154:
155: /**
156: * Return the JDO properties, if any. Mainly available for
157: * configuration through property paths that specify individual keys.
158: */
159: public Properties getJdoProperties() {
160: if (this .jdoProperties == null) {
161: this .jdoProperties = new Properties();
162: }
163: return this .jdoProperties;
164: }
165:
166: /**
167: * Set the JDO dialect to use for the PersistenceExceptionTranslator
168: * functionality of this factory.
169: * <p>Default is a DefaultJdoDialect based on the PersistenceManagerFactory's
170: * underlying DataSource, if any.
171: * @see JdoDialect#translateException
172: * @see #translateExceptionIfPossible
173: * @see org.springframework.dao.support.PersistenceExceptionTranslator
174: */
175: public void setJdoDialect(JdoDialect jdoDialect) {
176: this .jdoDialect = jdoDialect;
177: }
178:
179: /**
180: * Initialize the PersistenceManagerFactory for the given location.
181: * @throws IllegalArgumentException in case of illegal property values
182: * @throws IOException if the properties could not be loaded from the given location
183: * @throws JDOException in case of JDO initialization errors
184: */
185: public void afterPropertiesSet() throws IllegalArgumentException,
186: IOException, JDOException {
187: if (this .configLocation == null && this .jdoProperties == null) {
188: throw new IllegalArgumentException(
189: "Either configLocation or jdoProperties must be set");
190: }
191:
192: Properties mergedProps = new Properties();
193:
194: if (this .configLocation != null) {
195: if (logger.isInfoEnabled()) {
196: logger.info("Loading JDO config from ["
197: + this .configLocation + "]");
198: }
199: PropertiesLoaderUtils.fillProperties(mergedProps,
200: this .configLocation);
201: }
202:
203: CollectionUtils.mergePropertiesIntoMap(this .jdoProperties,
204: mergedProps);
205:
206: // Build PersistenceManagerFactory instance.
207: logger.info("Building new JDO PersistenceManagerFactory");
208: this .persistenceManagerFactory = newPersistenceManagerFactory(mergedProps);
209:
210: // Build default JdoDialect if none explicitly specified.
211: if (this .jdoDialect == null) {
212: this .jdoDialect = new DefaultJdoDialect(
213: this .persistenceManagerFactory
214: .getConnectionFactory());
215: }
216: }
217:
218: /**
219: * Subclasses can override this to perform custom initialization of the
220: * PersistenceManagerFactory instance, creating it via the given Properties
221: * that got prepared by this LocalPersistenceManagerFactoryBean.
222: * <p>The default implementation invokes JDOHelper's
223: * <code>getPersistenceManagerFactory</code> method.
224: * A custom implementation could prepare the instance in a specific way,
225: * or use a custom PersistenceManagerFactory implementation.
226: * <p>Implemented to work with either the JDO 1.0
227: * <code>getPersistenceManagerFactory(java.util.Properties)</code> method or
228: * the JDO 2.0 <code>getPersistenceManagerFactory(java.util.Map)</code> method,
229: * detected through reflection.
230: * @param props the merged Properties prepared by this LocalPersistenceManagerFactoryBean
231: * @return the PersistenceManagerFactory instance
232: * @see javax.jdo.JDOHelper#getPersistenceManagerFactory(java.util.Map)
233: */
234: protected PersistenceManagerFactory newPersistenceManagerFactory(
235: Properties props) {
236: // Use JDO 1.0 getPersistenceManagerFactory(Properties) method, if available.
237: if (getPersistenceManagerFactoryMethod != null) {
238: return (PersistenceManagerFactory) ReflectionUtils
239: .invokeMethod(getPersistenceManagerFactoryMethod,
240: null, new Object[] { props });
241: }
242: // Use JDO 2.0 getPersistenceManagerFactory(Map) method else.
243: return JDOHelper.getPersistenceManagerFactory(props);
244: }
245:
246: /**
247: * Return the singleton PersistenceManagerFactory.
248: */
249: public Object getObject() {
250: return this .persistenceManagerFactory;
251: }
252:
253: public Class getObjectType() {
254: return (this .persistenceManagerFactory != null ? this .persistenceManagerFactory
255: .getClass()
256: : PersistenceManagerFactory.class);
257: }
258:
259: public boolean isSingleton() {
260: return true;
261: }
262:
263: /**
264: * Implementation of the PersistenceExceptionTranslator interface,
265: * as autodetected by Spring's PersistenceExceptionTranslationPostProcessor.
266: * <p>Converts the exception if it is a JDOException, preferably using a specified
267: * JdoDialect. Else returns <code>null</code> to indicate an unknown exception.
268: * @see org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor
269: * @see JdoDialect#translateException
270: * @see PersistenceManagerFactoryUtils#convertJdoAccessException
271: */
272: public DataAccessException translateExceptionIfPossible(
273: RuntimeException ex) {
274: if (ex instanceof JDOException) {
275: if (this .jdoDialect != null) {
276: return this .jdoDialect
277: .translateException((JDOException) ex);
278: } else {
279: return PersistenceManagerFactoryUtils
280: .convertJdoAccessException((JDOException) ex);
281: }
282: }
283: return null;
284: }
285:
286: /**
287: * Close the PersistenceManagerFactory on bean factory shutdown.
288: */
289: public void destroy() {
290: logger.info("Closing JDO PersistenceManagerFactory");
291: this.persistenceManagerFactory.close();
292: }
293:
294: }
|