1 16 17 package org.springframework.orm.jpa.persistenceunit; 18 19 import java.io.IOException ; 20 import java.io.InputStream ; 21 import java.net.URL ; 22 import java.util.LinkedList ; 23 import java.util.List ; 24 25 import javax.persistence.spi.PersistenceUnitTransactionType; 26 import javax.xml.XMLConstants ; 27 import javax.xml.parsers.DocumentBuilder ; 28 import javax.xml.parsers.DocumentBuilderFactory ; 29 import javax.xml.parsers.ParserConfigurationException ; 30 31 import org.apache.commons.logging.Log; 32 import org.apache.commons.logging.LogFactory; 33 import org.w3c.dom.Document ; 34 import org.w3c.dom.Element ; 35 import org.xml.sax.ErrorHandler ; 36 import org.xml.sax.SAXException ; 37 38 import org.springframework.core.io.Resource; 39 import org.springframework.core.io.support.ResourcePatternResolver; 40 import org.springframework.jdbc.datasource.lookup.DataSourceLookup; 41 import org.springframework.util.Assert; 42 import org.springframework.util.ResourceUtils; 43 import org.springframework.util.StringUtils; 44 import org.springframework.util.xml.DomUtils; 45 import org.springframework.util.xml.SimpleSaxErrorHandler; 46 47 54 class PersistenceUnitReader { 55 56 private static final String MAPPING_FILE_NAME = "mapping-file"; 57 58 private static final String JAR_FILE_URL = "jar-file"; 59 60 private static final String MANAGED_CLASS_NAME = "class"; 61 62 private static final String PROPERTIES = "properties"; 63 64 private static final String PROVIDER = "provider"; 65 66 private static final String EXCLUDE_UNLISTED_CLASSES = "exclude-unlisted-classes"; 67 68 private static final String NON_JTA_DATA_SOURCE = "non-jta-data-source"; 69 70 private static final String JTA_DATA_SOURCE = "jta-data-source"; 71 72 private static final String TRANSACTION_TYPE = "transaction-type"; 73 74 private static final String PERSISTENCE_UNIT = "persistence-unit"; 75 76 private static final String UNIT_NAME = "name"; 77 78 private static final String SCHEMA_NAME = "persistence_1_0.xsd"; 79 80 private static final String JAXP_SCHEMA_LANGUAGE = "http://java.sun.com/xml/jaxp/properties/schemaLanguage"; 81 82 private static final String JAXP_SCHEMA_SOURCE = "http://java.sun.com/xml/jaxp/properties/schemaSource"; 83 84 private static final String META_INF = "META-INF"; 85 86 87 private final Log logger = LogFactory.getLog(getClass()); 88 89 private final ResourcePatternResolver resourcePatternResolver; 90 91 private final DataSourceLookup dataSourceLookup; 92 93 94 100 public PersistenceUnitReader(ResourcePatternResolver resourcePatternResolver, DataSourceLookup dataSourceLookup) { 101 Assert.notNull(resourcePatternResolver, "ResourceLoader must not be null"); 102 Assert.notNull(dataSourceLookup, "DataSourceLookup must not be null"); 103 this.resourcePatternResolver = resourcePatternResolver; 104 this.dataSourceLookup = dataSourceLookup; 105 } 106 107 108 113 public SpringPersistenceUnitInfo[] readPersistenceUnitInfos(String persistenceXmlLocation) { 114 return readPersistenceUnitInfos(new String [] {persistenceXmlLocation}); 115 } 116 117 122 public SpringPersistenceUnitInfo[] readPersistenceUnitInfos(String [] persistenceXmlLocations) { 123 ErrorHandler handler = new SimpleSaxErrorHandler(logger); 124 List <SpringPersistenceUnitInfo> infos = new LinkedList <SpringPersistenceUnitInfo>(); 125 String resourceLocation = null; 126 try { 127 for (int i = 0; i < persistenceXmlLocations.length; i++) { 128 Resource[] resources = this.resourcePatternResolver.getResources(persistenceXmlLocations[i]); 129 for (Resource resource : resources) { 130 resourceLocation = resource.toString(); 131 InputStream stream = resource.getInputStream(); 132 try { 133 Document document = validateResource(handler, stream); 134 parseDocument(resource, document, infos); 135 } 136 finally { 137 stream.close(); 138 } 139 } 140 } 141 } 142 catch (IOException ex) { 143 throw new IllegalArgumentException ("Cannot parse persistence unit from " + resourceLocation, ex); 144 } 145 catch (SAXException ex) { 146 throw new IllegalArgumentException ("Invalid XML in persistence unit from " + resourceLocation, ex); 147 } 148 catch (ParserConfigurationException ex) { 149 throw new IllegalArgumentException ("Internal error parsing persistence unit from " + resourceLocation); 150 } 151 152 return infos.toArray(new SpringPersistenceUnitInfo[infos.size()]); 153 } 154 155 156 159 protected Document validateResource(ErrorHandler handler, InputStream stream) 160 throws ParserConfigurationException , SAXException , IOException { 161 162 DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); 163 dbf.setNamespaceAware(true); 164 165 Resource schemaLocation = findSchemaResource(SCHEMA_NAME); 167 if (schemaLocation != null) { 168 if (logger.isDebugEnabled()) { 169 logger.debug("Found schema resource: " + schemaLocation.getURL()); 170 } 171 dbf.setValidating(true); 172 dbf.setAttribute(JAXP_SCHEMA_LANGUAGE, XMLConstants.W3C_XML_SCHEMA_NS_URI); 173 dbf.setAttribute(JAXP_SCHEMA_SOURCE, schemaLocation.getURL().toString()); 174 } 175 else { 176 logger.debug("Schema resource [" + SCHEMA_NAME + 177 "] not found - falling back to XML parsing without schema validation"); 178 } 179 180 DocumentBuilder parser = dbf.newDocumentBuilder(); 181 parser.setErrorHandler(handler); 182 return parser.parse(stream); 183 } 184 185 189 protected Resource findSchemaResource(String schemaName) { 190 try { 191 Resource schemaLocation = this.resourcePatternResolver.getResource("classpath:" + schemaName); 193 if (schemaLocation.exists()) { 194 return schemaLocation; 195 } 196 197 Resource[] resources = this.resourcePatternResolver.getResources("classpath*:org/**/" + schemaName); 199 if (resources.length > 0) { 200 return resources[0]; 201 } 202 203 resources = this.resourcePatternResolver.getResources("classpath*:com/**/" + schemaName); 205 if (resources.length > 0) { 206 return resources[0]; 207 } 208 209 resources = this.resourcePatternResolver.getResources("classpath*:**/" + schemaName); 214 if (resources.length > 0) { 215 return resources[0]; 216 } 217 } 218 catch (IOException ex) { 219 if (logger.isDebugEnabled()) { 220 logger.debug("Could not search for JPA schema resource [" + schemaName + "] in class path", ex); 221 } 222 } 223 return null; 224 } 225 226 227 231 protected List <SpringPersistenceUnitInfo> parseDocument( 232 Resource resource, Document document, List <SpringPersistenceUnitInfo> infos) throws IOException { 233 234 Element persistence = document.getDocumentElement(); 235 URL unitRootURL = determinePersistenceUnitRootUrl(resource); 236 List <Element > units = (List <Element >) DomUtils.getChildElementsByTagName(persistence, PERSISTENCE_UNIT); 237 for (Element unit : units) { 238 SpringPersistenceUnitInfo info = parsePersistenceUnitInfo(unit); 239 info.setPersistenceUnitRootUrl(unitRootURL); 240 infos.add(info); 241 } 242 243 return infos; 244 } 245 246 253 protected URL determinePersistenceUnitRootUrl(Resource resource) throws IOException { 254 URL originalURL = resource.getURL(); 255 String urlToString = originalURL.toExternalForm(); 256 257 if (ResourceUtils.isJarURL(originalURL)) { 259 return ResourceUtils.extractJarFileURL(originalURL); 260 } 261 262 else { 263 if (!urlToString.contains(META_INF)) { 265 if (logger.isInfoEnabled()) { 266 logger.info(resource.getFilename() + 267 " should be located inside META-INF directory; cannot determine persistence unit root URL for " + 268 resource); 269 } 270 return null; 271 } 272 if (urlToString.lastIndexOf(META_INF) == urlToString.lastIndexOf('/') - (1 + META_INF.length())) { 273 if (logger.isInfoEnabled()) { 274 logger.info(resource.getFilename() + 275 " is not located in the root of META-INF directory; cannot determine persistence unit root URL for " + 276 resource); 277 } 278 return null; 279 } 280 281 String persistenceUnitRoot = urlToString.substring(0, urlToString.lastIndexOf(META_INF)); 282 return new URL (persistenceUnitRoot); 283 } 284 } 285 286 289 protected SpringPersistenceUnitInfo parsePersistenceUnitInfo(Element persistenceUnit) throws IOException { 290 SpringPersistenceUnitInfo unitInfo = new SpringPersistenceUnitInfo(); 291 292 unitInfo.setPersistenceUnitName(persistenceUnit.getAttribute(UNIT_NAME).trim()); 294 295 String txType = persistenceUnit.getAttribute(TRANSACTION_TYPE).trim(); 297 if (StringUtils.hasText(txType)) { 298 unitInfo.setTransactionType(PersistenceUnitTransactionType.valueOf(txType)); 299 } 300 301 String jtaDataSource = DomUtils.getChildElementValueByTagName(persistenceUnit, JTA_DATA_SOURCE); 303 if (StringUtils.hasText(jtaDataSource)) { 304 unitInfo.setJtaDataSource(this.dataSourceLookup.getDataSource(jtaDataSource.trim())); 305 } 306 307 String nonJtaDataSource = DomUtils.getChildElementValueByTagName(persistenceUnit, NON_JTA_DATA_SOURCE); 308 if (StringUtils.hasText(nonJtaDataSource)) { 309 unitInfo.setNonJtaDataSource(this.dataSourceLookup.getDataSource(nonJtaDataSource.trim())); 310 } 311 312 String provider = DomUtils.getChildElementValueByTagName(persistenceUnit, PROVIDER); 314 if (StringUtils.hasText(provider)) { 315 unitInfo.setPersistenceProviderClassName(provider.trim()); 316 } 317 318 Element excludeUnlistedClasses = DomUtils.getChildElementByTagName(persistenceUnit, EXCLUDE_UNLISTED_CLASSES); 320 if (excludeUnlistedClasses != null) { 321 unitInfo.setExcludeUnlistedClasses(true); 322 } 323 324 parseMappingFiles(persistenceUnit, unitInfo); 326 parseJarFiles(persistenceUnit, unitInfo); 327 parseClass(persistenceUnit, unitInfo); 328 parseProperty(persistenceUnit, unitInfo); 329 return unitInfo; 330 } 331 332 335 @SuppressWarnings ("unchecked") 336 protected void parseProperty(Element persistenceUnit, SpringPersistenceUnitInfo unitInfo) { 337 Element propRoot = DomUtils.getChildElementByTagName(persistenceUnit, PROPERTIES); 338 if (propRoot == null) { 339 return; 340 } 341 List <Element > properties = DomUtils.getChildElementsByTagName(propRoot, "property"); 342 for (Element property : properties) { 343 String name = property.getAttribute("name"); 344 String value = property.getAttribute("value"); 345 unitInfo.addProperty(name, value); 346 } 347 } 348 349 352 @SuppressWarnings ("unchecked") 353 protected void parseClass(Element persistenceUnit, SpringPersistenceUnitInfo unitInfo) { 354 List <Element > classes = DomUtils.getChildElementsByTagName(persistenceUnit, MANAGED_CLASS_NAME); 355 for (Element element : classes) { 356 String value = DomUtils.getTextValue(element).trim(); 357 if (StringUtils.hasText(value)) 358 unitInfo.addManagedClassName(value); 359 } 360 } 361 362 365 @SuppressWarnings ("unchecked") 366 protected void parseJarFiles(Element persistenceUnit, SpringPersistenceUnitInfo unitInfo) throws IOException { 367 List <Element > jars = DomUtils.getChildElementsByTagName(persistenceUnit, JAR_FILE_URL); 368 for (Element element : jars) { 369 String value = DomUtils.getTextValue(element).trim(); 370 if (StringUtils.hasText(value)) { 371 Resource resource = this.resourcePatternResolver.getResource(value); 372 unitInfo.addJarFileUrl(resource.getURL()); 373 } 374 } 375 } 376 377 380 @SuppressWarnings ("unchecked") 381 protected void parseMappingFiles(Element persistenceUnit, SpringPersistenceUnitInfo unitInfo) { 382 List <Element > files = DomUtils.getChildElementsByTagName(persistenceUnit, MAPPING_FILE_NAME); 383 for (Element element : files) { 384 String value = DomUtils.getTextValue(element).trim(); 385 if (StringUtils.hasText(value)) 386 unitInfo.addMappingFileName(value); 387 } 388 } 389 390 } 391 | Popular Tags |