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