001: /*
002: * Copyright 2002-2007 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.jpa.persistenceunit;
018:
019: import java.io.IOException;
020: import java.net.URL;
021: import java.util.HashMap;
022: import java.util.HashSet;
023: import java.util.Map;
024: import java.util.Set;
025:
026: import javax.persistence.PersistenceException;
027: import javax.persistence.spi.PersistenceUnitInfo;
028: import javax.sql.DataSource;
029:
030: import org.springframework.beans.factory.InitializingBean;
031: import org.springframework.context.ResourceLoaderAware;
032: import org.springframework.core.io.Resource;
033: import org.springframework.core.io.ResourceLoader;
034: import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
035: import org.springframework.core.io.support.ResourcePatternResolver;
036: import org.springframework.core.io.support.ResourcePatternUtils;
037: import org.springframework.instrument.classloading.LoadTimeWeaver;
038: import org.springframework.jdbc.datasource.lookup.DataSourceLookup;
039: import org.springframework.jdbc.datasource.lookup.JndiDataSourceLookup;
040: import org.springframework.jdbc.datasource.lookup.MapDataSourceLookup;
041: import org.springframework.util.ObjectUtils;
042:
043: /**
044: * Default implementation of the {@link PersistenceUnitManager} interface.
045: * Used as internal default by
046: * {@link org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean}.
047: *
048: * <p>Supports standard JPA scanning for <code>persistence.xml</code> files,
049: * with configurable file locations, JDBC DataSource lookup and load-time weaving.
050: *
051: * <p>The default XML file location is <code>classpath:META-INF/persistence.xml</code>,
052: * scanning for all matching files in the class path (as defined in the JPA specification).
053: * DataSource names are by default interpreted as JNDI names, and no load time weaving
054: * is available (which requires weaving to be turned off in the persistence provider).
055: *
056: * @author Juergen Hoeller
057: * @since 2.0
058: * @see #setPersistenceXmlLocations
059: * @see #setDataSourceLookup
060: * @see #setLoadTimeWeaver
061: * @see org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean#setPersistenceUnitManager
062: */
063: public class DefaultPersistenceUnitManager implements
064: PersistenceUnitManager, ResourceLoaderAware, InitializingBean {
065:
066: /**
067: * Default location of the <code>persistence.xml</code> file:
068: * "classpath*:META-INF/persistence.xml".
069: */
070: public final static String DEFAULT_PERSISTENCE_XML_LOCATION = "classpath*:META-INF/persistence.xml";
071:
072: /**
073: * Default location for the persistence unit root URL:
074: * "classpath:", indicating the root of the class path.
075: */
076: public final static String ORIGINAL_DEFAULT_PERSISTENCE_UNIT_ROOT_LOCATION = "classpath:";
077:
078: /** Location of persistence.xml file(s) */
079: private String[] persistenceXmlLocations = new String[] { DEFAULT_PERSISTENCE_XML_LOCATION };
080:
081: private String defaultPersistenceUnitRootLocation = ORIGINAL_DEFAULT_PERSISTENCE_UNIT_ROOT_LOCATION;
082:
083: private DataSourceLookup dataSourceLookup = new JndiDataSourceLookup();
084:
085: private DataSource defaultDataSource;
086:
087: private LoadTimeWeaver loadTimeWeaver;
088:
089: private PersistenceUnitPostProcessor[] persistenceUnitPostProcessors;
090:
091: private ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
092:
093: private final Set<String> persistenceUnitInfoNames = new HashSet<String>();
094:
095: private final Map<String, MutablePersistenceUnitInfo> persistenceUnitInfos = new HashMap<String, MutablePersistenceUnitInfo>();
096:
097: /**
098: * Set the locations of the <code>persistence.xml</code> files to load.
099: * These can be specified as Spring resource locations and/or location patterns.
100: * <p>Default is "classpath*:META-INF/persistence.xml".
101: * @param persistenceXmlLocations an array of Spring resource Strings
102: * identifying the location of the <code>persistence.xml</code> files to read
103: */
104: public void setPersistenceXmlLocations(
105: String[] persistenceXmlLocations) {
106: this .persistenceXmlLocations = persistenceXmlLocations;
107: }
108:
109: /**
110: * Set the default persistence unit root location, to be applied
111: * if no unit-specific persistence unit root could be determined.
112: * <p>Default is "classpath:", that is, the root of the current class path
113: * (nearest root directory). To be overridden if unit-specific resolution
114: * does not work and the class path root is not appropriate either.
115: */
116: public void setDefaultPersistenceUnitRootLocation(
117: String defaultPersistenceUnitRootLocation) {
118: this .defaultPersistenceUnitRootLocation = defaultPersistenceUnitRootLocation;
119: }
120:
121: /**
122: * Specify the JDBC DataSources that the JPA persistence provider is supposed
123: * to use for accessing the database, resolving data source names in
124: * <code>persistence.xml</code> against Spring-managed DataSources.
125: * <p>The specified Map needs to define data source names for specific DataSource
126: * objects, matching the data source names used in <code>persistence.xml</code>.
127: * If not specified, data source names will be resolved as JNDI names instead
128: * (as defined by standard JPA).
129: * @see org.springframework.jdbc.datasource.lookup.MapDataSourceLookup
130: */
131: public void setDataSources(Map<String, DataSource> dataSources) {
132: this .dataSourceLookup = new MapDataSourceLookup(dataSources);
133: }
134:
135: /**
136: * Specify the JDBC DataSourceLookup that provides DataSources for the
137: * persistence provider, resolving data source names in <code>persistence.xml</code>
138: * against Spring-managed DataSource instances.
139: * <p>Default is JndiDataSourceLookup, which resolves DataSource names as
140: * JNDI names (as defined by standard JPA). Specify a BeanFactoryDataSourceLookup
141: * instance if you want DataSource names to be resolved against Spring bean names.
142: * <p>Alternatively, consider passing in a map from names to DataSource instances
143: * via the "dataSources" property. If the <code>persistence.xml</code> file
144: * does not define DataSource names at all, specify a default DataSource
145: * via the "defaultDataSource" property.
146: * @see org.springframework.jdbc.datasource.lookup.JndiDataSourceLookup
147: * @see org.springframework.jdbc.datasource.lookup.BeanFactoryDataSourceLookup
148: * @see #setDataSources
149: * @see #setDefaultDataSource
150: */
151: public void setDataSourceLookup(DataSourceLookup dataSourceLookup) {
152: this .dataSourceLookup = (dataSourceLookup != null ? dataSourceLookup
153: : new JndiDataSourceLookup());
154: }
155:
156: /**
157: * Return the JDBC DataSourceLookup that provides DataSources for the
158: * persistence provider, resolving data source names in <code>persistence.xml</code>
159: * against Spring-managed DataSource instances.
160: */
161: public DataSourceLookup getDataSourceLookup() {
162: return this .dataSourceLookup;
163: }
164:
165: /**
166: * Specify the JDBC DataSource that the JPA persistence provider is supposed
167: * to use for accessing the database if none has been specified in
168: * <code>persistence.xml</code>.
169: * <p>In JPA speak, a DataSource passed in here will be uses as "nonJtaDataSource"
170: * on the PersistenceUnitInfo passed to the PersistenceProvider, provided that
171: * none has been registered before.
172: * @see javax.persistence.spi.PersistenceUnitInfo#getNonJtaDataSource()
173: */
174: public void setDefaultDataSource(DataSource defaultDataSource) {
175: this .defaultDataSource = defaultDataSource;
176: }
177:
178: /**
179: * Return the JDBC DataSource that the JPA persistence provider is supposed
180: * to use for accessing the database if none has been specified in
181: * <code>persistence.xml</code>.
182: */
183: public DataSource getDefaultDataSource() {
184: return this .defaultDataSource;
185: }
186:
187: /**
188: * Specify the Spring LoadTimeWeaver to use for class instrumentation according
189: * to the JPA class transformer contract.
190: * <p>It is not required to specify a LoadTimeWeaver: Most providers will be
191: * able to provide a subset of their functionality without class instrumentation
192: * as well, or operate with their VM agent specified on JVM startup.
193: * <p>In terms of Spring-provided weaving options, the most important ones are
194: * InstrumentationLoadTimeWeaver, which requires a Spring-specific (but very general)
195: * VM agent specified on JVM startup, and ReflectiveLoadTimeWeaver, which interacts
196: * with an underlying ClassLoader based on specific extended methods being available
197: * on it (for example, interacting with Spring's TomcatInstrumentableClassLoader).
198: * @see org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver
199: * @see org.springframework.instrument.classloading.ReflectiveLoadTimeWeaver
200: * @see org.springframework.instrument.classloading.tomcat.TomcatInstrumentableClassLoader
201: */
202: public void setLoadTimeWeaver(LoadTimeWeaver loadTimeWeaver) {
203: this .loadTimeWeaver = loadTimeWeaver;
204: }
205:
206: /**
207: * Return the Spring LoadTimeWeaver to use for class instrumentation according
208: * to the JPA class transformer contract.
209: */
210: public LoadTimeWeaver getLoadTimeWeaver() {
211: return this .loadTimeWeaver;
212: }
213:
214: /**
215: * Set the PersistenceUnitPostProcessors to be applied to each
216: * PersistenceUnitInfo that has been parsed by this manager.
217: * <p>Such post-processors can, for example, register further entity
218: * classes and jar files, in addition to the metadata read in from
219: * <code>persistence.xml</code>.
220: */
221: public void setPersistenceUnitPostProcessors(
222: PersistenceUnitPostProcessor[] postProcessors) {
223: this .persistenceUnitPostProcessors = postProcessors;
224: }
225:
226: /**
227: * Return the PersistenceUnitPostProcessors to be applied to each
228: * PersistenceUnitInfo that has been parsed by this manager.
229: */
230: public PersistenceUnitPostProcessor[] getPersistenceUnitPostProcessors() {
231: return this .persistenceUnitPostProcessors;
232: }
233:
234: public void setResourceLoader(ResourceLoader resourceLoader) {
235: this .resourcePatternResolver = (resourceLoader != null ? ResourcePatternUtils
236: .getResourcePatternResolver(resourceLoader)
237: : new PathMatchingResourcePatternResolver());
238: }
239:
240: public void afterPropertiesSet() {
241: preparePersistenceUnitInfos();
242: }
243:
244: /**
245: * Prepare the PersistenceUnitInfos according to the configuration
246: * of this manager: scanning for <code>persistence.xml</code> files,
247: * parsing all matching files, configurating and post-processing them.
248: * <p>PersistenceUnitInfos cannot be obtained before this preparation
249: * method has been invoked.
250: * @see #obtainDefaultPersistenceUnitInfo()
251: * @see #obtainPersistenceUnitInfo(String)
252: */
253: public void preparePersistenceUnitInfos() {
254: this .persistenceUnitInfoNames.clear();
255: this .persistenceUnitInfos.clear();
256: SpringPersistenceUnitInfo[] puis = readPersistenceUnitInfos();
257: for (int i = 0; i < puis.length; i++) {
258: SpringPersistenceUnitInfo pui = puis[i];
259: if (pui.getPersistenceUnitRootUrl() == null) {
260: pui
261: .setPersistenceUnitRootUrl(determineDefaultPersistenceUnitRootUrl());
262: }
263: if (pui.getNonJtaDataSource() == null) {
264: pui.setNonJtaDataSource(this .defaultDataSource);
265: }
266: pui.setLoadTimeWeaver(this .loadTimeWeaver);
267: postProcessPersistenceUnitInfo(pui);
268: String name = pui.getPersistenceUnitName();
269: this .persistenceUnitInfoNames.add(name);
270: this .persistenceUnitInfos.put(name, pui);
271: }
272: }
273:
274: /**
275: * Read all persistence unit infos from <code>persistence.xml</code>,
276: * as defined in the JPA specification.
277: */
278: private SpringPersistenceUnitInfo[] readPersistenceUnitInfos() {
279: PersistenceUnitReader reader = new PersistenceUnitReader(
280: this .resourcePatternResolver, this .dataSourceLookup);
281: return reader
282: .readPersistenceUnitInfos(this .persistenceXmlLocations);
283: }
284:
285: /**
286: * Try to determine the persistence unit root URL based on the given
287: * "defaultPersistenceUnitRootLocation".
288: * @return the persistence unit root URL to pass to the JPA PersistenceProvider
289: * @see #setDefaultPersistenceUnitRootLocation
290: */
291: private URL determineDefaultPersistenceUnitRootUrl() {
292: if (this .defaultPersistenceUnitRootLocation == null) {
293: return null;
294: }
295: try {
296: Resource res = this .resourcePatternResolver
297: .getResource(this .defaultPersistenceUnitRootLocation);
298: return res.getURL();
299: } catch (IOException ex) {
300: throw new PersistenceException(
301: "Unable to resolve persistence unit root URL", ex);
302: }
303: }
304:
305: /**
306: * Return the specified PersistenceUnitInfo from this manager's cache
307: * of processed persistence units, keeping it in the cache (i.e. not
308: * 'obtaining' it for use but rather just accessing it for post-processing).
309: * <p>This can be used in {@link #postProcessPersistenceUnitInfo} implementations,
310: * detecting existing persistence units of the same name and potentially merging them.
311: * @param persistenceUnitName the name of the desired persistence unit
312: * @return the PersistenceUnitInfo in mutable form,
313: * or <code>null</code> if not available
314: */
315: protected final MutablePersistenceUnitInfo getPersistenceUnitInfo(
316: String persistenceUnitName) {
317: return this .persistenceUnitInfos.get(persistenceUnitName);
318: }
319:
320: /**
321: * Hook method allowing subclasses to customize each PersistenceUnitInfo.
322: * <p>Default implementation delegates to all registered PersistenceUnitPostProcessors.
323: * It is usually preferable to register further entity classes, jar files etc there
324: * rather than in a subclass of this manager, to be able to reuse the post-processors.
325: * @param pui the chosen PersistenceUnitInfo, as read from <code>persistence.xml</code>.
326: * Passed in as MutablePersistenceUnitInfo.
327: * @see #setPersistenceUnitPostProcessors
328: */
329: protected void postProcessPersistenceUnitInfo(
330: MutablePersistenceUnitInfo pui) {
331: PersistenceUnitPostProcessor[] postProcessors = getPersistenceUnitPostProcessors();
332: if (postProcessors != null) {
333: for (int i = 0; i < postProcessors.length; i++) {
334: postProcessors[i].postProcessPersistenceUnitInfo(pui);
335: }
336: }
337: }
338:
339: public PersistenceUnitInfo obtainDefaultPersistenceUnitInfo() {
340: if (this .persistenceUnitInfoNames.isEmpty()) {
341: throw new IllegalStateException(
342: "No persistence units parsed from "
343: + ObjectUtils
344: .nullSafeToString(this .persistenceXmlLocations));
345: }
346: if (this .persistenceUnitInfos.isEmpty()) {
347: throw new IllegalStateException(
348: "All persistence units from "
349: + ObjectUtils
350: .nullSafeToString(this .persistenceXmlLocations)
351: + " already obtained");
352: }
353: if (this .persistenceUnitInfos.size() > 1) {
354: throw new IllegalStateException(
355: "No single default persistence unit defined in "
356: + ObjectUtils
357: .nullSafeToString(this .persistenceXmlLocations));
358: }
359: PersistenceUnitInfo pui = this .persistenceUnitInfos.values()
360: .iterator().next();
361: this .persistenceUnitInfos.clear();
362: return pui;
363: }
364:
365: public PersistenceUnitInfo obtainPersistenceUnitInfo(
366: String persistenceUnitName) {
367: PersistenceUnitInfo pui = this .persistenceUnitInfos
368: .remove(persistenceUnitName);
369: if (pui == null) {
370: if (!this .persistenceUnitInfoNames
371: .contains(persistenceUnitName)) {
372: throw new IllegalArgumentException(
373: "No persistence unit with name '"
374: + persistenceUnitName + "' found");
375: } else {
376: throw new IllegalStateException(
377: "Persistence unit with name '"
378: + persistenceUnitName
379: + "' already obtained");
380: }
381: }
382: return pui;
383: }
384:
385: }
|