1
1
/*
2
- * Copyright 2002-2010 the original author or authors.
2
+ * Copyright 2002-2011 the original author or authors.
3
3
*
4
4
* Licensed under the Apache License, Version 2.0 (the "License");
5
5
* you may not use this file except in compliance with the License.
21
21
import java .lang .reflect .Method ;
22
22
import java .lang .reflect .Proxy ;
23
23
import java .net .URL ;
24
+ import java .util .Arrays ;
24
25
import java .util .HashMap ;
25
26
import java .util .HashSet ;
27
+ import java .util .LinkedList ;
28
+ import java .util .List ;
26
29
import java .util .Map ;
27
30
import java .util .Set ;
31
+ import javax .persistence .Embeddable ;
32
+ import javax .persistence .Entity ;
33
+ import javax .persistence .MappedSuperclass ;
28
34
import javax .persistence .PersistenceException ;
29
35
import javax .persistence .spi .PersistenceUnitInfo ;
30
36
import javax .sql .DataSource ;
37
43
import org .springframework .core .io .support .PathMatchingResourcePatternResolver ;
38
44
import org .springframework .core .io .support .ResourcePatternResolver ;
39
45
import org .springframework .core .io .support .ResourcePatternUtils ;
46
+ import org .springframework .core .type .classreading .CachingMetadataReaderFactory ;
47
+ import org .springframework .core .type .classreading .MetadataReader ;
48
+ import org .springframework .core .type .classreading .MetadataReaderFactory ;
49
+ import org .springframework .core .type .filter .AnnotationTypeFilter ;
50
+ import org .springframework .core .type .filter .TypeFilter ;
40
51
import org .springframework .instrument .classloading .InstrumentationLoadTimeWeaver ;
41
52
import org .springframework .instrument .classloading .LoadTimeWeaver ;
42
53
import org .springframework .jdbc .datasource .lookup .DataSourceLookup ;
@@ -80,13 +91,26 @@ public class DefaultPersistenceUnitManager
80
91
*/
81
92
public final static String ORIGINAL_DEFAULT_PERSISTENCE_UNIT_ROOT_LOCATION = "classpath:" ;
82
93
94
+ public final static String ORIGINAL_DEFAULT_PERSISTENCE_UNIT_NAME = "default" ;
95
+
96
+ private static final String RESOURCE_PATTERN = "/**/*.class" ;
97
+
83
98
84
99
private static final boolean jpa2ApiPresent = ClassUtils .hasMethod (PersistenceUnitInfo .class , "getSharedCacheMode" );
85
100
101
+ private static final TypeFilter [] entityTypeFilters = new TypeFilter [] {
102
+ new AnnotationTypeFilter (Entity .class , false ),
103
+ new AnnotationTypeFilter (Embeddable .class , false ),
104
+ new AnnotationTypeFilter (MappedSuperclass .class , false )};
105
+
86
106
private String [] persistenceXmlLocations = new String [] {DEFAULT_PERSISTENCE_XML_LOCATION };
87
107
88
108
private String defaultPersistenceUnitRootLocation = ORIGINAL_DEFAULT_PERSISTENCE_UNIT_ROOT_LOCATION ;
89
109
110
+ private String defaultPersistenceUnitName = ORIGINAL_DEFAULT_PERSISTENCE_UNIT_NAME ;
111
+
112
+ private String [] packagesToScan ;
113
+
90
114
private DataSourceLookup dataSourceLookup = new JndiDataSourceLookup ();
91
115
92
116
private DataSource defaultDataSource ;
@@ -133,6 +157,30 @@ public void setDefaultPersistenceUnitRootLocation(String defaultPersistenceUnitR
133
157
this .defaultPersistenceUnitRootLocation = defaultPersistenceUnitRootLocation ;
134
158
}
135
159
160
+ /**
161
+ * Specify the name of the default persistence unit, if any. Default is "default".
162
+ * <p>Primarily applied to a scanned persistence unit without <code>persistence.xml</code>.
163
+ * Also applicable to selecting a default unit from several persistence units available.
164
+ * @see #setPackagesToScan
165
+ * @see #obtainDefaultPersistenceUnitInfo
166
+ */
167
+ public void setDefaultPersistenceUnitName (String defaultPersistenceUnitName ) {
168
+ this .defaultPersistenceUnitName = defaultPersistenceUnitName ;
169
+ }
170
+
171
+ /**
172
+ * Set whether to use Spring-based scanning for entity classes in the classpath
173
+ * instead of using JPA's standard scanning of jar files with <code>persistence.xml</code>
174
+ * markers in them. In case of Spring-based scanning, no <code>persistence.xml</code>
175
+ * is necessary; all you need to do is to specify base packages to search here.
176
+ * <p>Default is none. Specify packages to search for autodetection of your entity
177
+ * classes in the classpath. This is analogous to Spring's component-scan feature
178
+ * ({@link org.springframework.context.annotation.ClassPathBeanDefinitionScanner}).
179
+ */
180
+ public void setPackagesToScan (String [] packagesToScan ) {
181
+ this .packagesToScan = packagesToScan ;
182
+ }
183
+
136
184
/**
137
185
* Specify the JDBC DataSources that the JPA persistence provider is supposed
138
186
* to use for accessing the database, resolving data source names in
@@ -273,7 +321,7 @@ public void afterPropertiesSet() {
273
321
public void preparePersistenceUnitInfos () {
274
322
this .persistenceUnitInfoNames .clear ();
275
323
this .persistenceUnitInfos .clear ();
276
- SpringPersistenceUnitInfo [] puis = readPersistenceUnitInfos ();
324
+ List < SpringPersistenceUnitInfo > puis = readPersistenceUnitInfos ();
277
325
for (SpringPersistenceUnitInfo pui : puis ) {
278
326
if (pui .getPersistenceUnitRootUrl () == null ) {
279
327
pui .setPersistenceUnitRootUrl (determineDefaultPersistenceUnitRootUrl ());
@@ -289,7 +337,13 @@ public void preparePersistenceUnitInfos() {
289
337
}
290
338
postProcessPersistenceUnitInfo (pui );
291
339
String name = pui .getPersistenceUnitName ();
292
- this .persistenceUnitInfoNames .add (name );
340
+ if (!this .persistenceUnitInfoNames .add (name )) {
341
+ StringBuilder msg = new StringBuilder ();
342
+ msg .append ("Conflicting persistence unit definitions for name '" ).append (name ).append ("': " );
343
+ msg .append (pui .getPersistenceUnitRootUrl ()).append (", " );
344
+ msg .append (this .persistenceUnitInfos .get (name ).getPersistenceUnitRootUrl ());
345
+ throw new IllegalStateException (msg .toString ());
346
+ }
293
347
PersistenceUnitInfo puiToStore = pui ;
294
348
if (jpa2ApiPresent ) {
295
349
puiToStore = (PersistenceUnitInfo ) Proxy .newProxyInstance (SmartPersistenceUnitInfo .class .getClassLoader (),
@@ -303,9 +357,58 @@ public void preparePersistenceUnitInfos() {
303
357
* Read all persistence unit infos from <code>persistence.xml</code>,
304
358
* as defined in the JPA specification.
305
359
*/
306
- private SpringPersistenceUnitInfo [] readPersistenceUnitInfos () {
360
+ private List < SpringPersistenceUnitInfo > readPersistenceUnitInfos () {
307
361
PersistenceUnitReader reader = new PersistenceUnitReader (this .resourcePatternResolver , this .dataSourceLookup );
308
- return reader .readPersistenceUnitInfos (this .persistenceXmlLocations );
362
+ List <SpringPersistenceUnitInfo > infos = new LinkedList <SpringPersistenceUnitInfo >();
363
+ infos .addAll (Arrays .asList (reader .readPersistenceUnitInfos (this .persistenceXmlLocations )));
364
+ if (this .packagesToScan != null ) {
365
+ infos .add (scanPackages ());
366
+ }
367
+ return infos ;
368
+ }
369
+
370
+ /**
371
+ * Perform Spring-based scanning for entity classes.
372
+ * @see #setPackagesToScan
373
+ */
374
+ private SpringPersistenceUnitInfo scanPackages () {
375
+ SpringPersistenceUnitInfo scannedUnit = new SpringPersistenceUnitInfo ();
376
+ try {
377
+ scannedUnit .setPersistenceUnitName (this .defaultPersistenceUnitName );
378
+ scannedUnit .excludeUnlistedClasses ();
379
+ for (String pkg : this .packagesToScan ) {
380
+ String pattern = ResourcePatternResolver .CLASSPATH_ALL_URL_PREFIX +
381
+ ClassUtils .convertClassNameToResourcePath (pkg ) + RESOURCE_PATTERN ;
382
+ Resource [] resources = this .resourcePatternResolver .getResources (pattern );
383
+ MetadataReaderFactory readerFactory = new CachingMetadataReaderFactory (this .resourcePatternResolver );
384
+ for (Resource resource : resources ) {
385
+ if (resource .isReadable ()) {
386
+ MetadataReader reader = readerFactory .getMetadataReader (resource );
387
+ String className = reader .getClassMetadata ().getClassName ();
388
+ if (matchesFilter (reader , readerFactory )) {
389
+ scannedUnit .addManagedClassName (className );
390
+ }
391
+ }
392
+ }
393
+ }
394
+ return scannedUnit ;
395
+ }
396
+ catch (IOException ex ) {
397
+ throw new PersistenceException ("Failed to scan classpath for unlisted classes" , ex );
398
+ }
399
+ }
400
+
401
+ /**
402
+ * Check whether any of the configured entity type filters matches
403
+ * the current class descriptor contained in the metadata reader.
404
+ */
405
+ private boolean matchesFilter (MetadataReader reader , MetadataReaderFactory readerFactory ) throws IOException {
406
+ for (TypeFilter filter : entityTypeFilters ) {
407
+ if (filter .match (reader , readerFactory )) {
408
+ return true ;
409
+ }
410
+ }
411
+ return false ;
309
412
}
310
413
311
414
/**
@@ -327,6 +430,7 @@ private URL determineDefaultPersistenceUnitRootUrl() {
327
430
}
328
431
}
329
432
433
+
330
434
/**
331
435
* Return the specified PersistenceUnitInfo from this manager's cache
332
436
* of processed persistence units, keeping it in the cache (i.e. not
@@ -378,8 +482,7 @@ public PersistenceUnitInfo obtainDefaultPersistenceUnitInfo() {
378
482
ObjectUtils .nullSafeToString (this .persistenceXmlLocations ) + " already obtained" );
379
483
}
380
484
if (this .persistenceUnitInfos .size () > 1 ) {
381
- throw new IllegalStateException ("No single default persistence unit defined in " +
382
- ObjectUtils .nullSafeToString (this .persistenceXmlLocations ));
485
+ return obtainPersistenceUnitInfo (this .defaultPersistenceUnitName );
383
486
}
384
487
PersistenceUnitInfo pui = this .persistenceUnitInfos .values ().iterator ().next ();
385
488
this .persistenceUnitInfos .clear ();
0 commit comments