001/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*- 002 * 003 * Copyright © 2018 microBean. 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 014 * implied. See the License for the specific language governing 015 * permissions and limitations under the License. 016 */ 017package org.microbean.jpa.weld; 018 019import java.net.MalformedURLException; 020import java.net.URL; 021 022import java.util.ArrayList; 023import java.util.Collection; 024import java.util.Collections; 025import java.util.List; 026import java.util.Map; 027import java.util.Objects; 028import java.util.Properties; 029import java.util.Set; 030 031import java.util.function.Function; 032 033import javax.persistence.SharedCacheMode; 034import javax.persistence.ValidationMode; 035 036import javax.persistence.spi.ClassTransformer; 037import javax.persistence.spi.PersistenceUnitInfo; 038import javax.persistence.spi.PersistenceUnitTransactionType; 039 040import javax.sql.DataSource; 041 042import org.microbean.jpa.jaxb.Persistence; 043import org.microbean.jpa.jaxb.PersistenceUnitCachingType; 044import org.microbean.jpa.jaxb.PersistenceUnitValidationModeType; 045import org.microbean.jpa.jaxb.Persistence.PersistenceUnit; 046 047public class PersistenceUnitInfoBean implements PersistenceUnitInfo { 048 049 private ClassLoader classLoader; 050 051 private boolean excludeUnlistedClasses; 052 053 private List<URL> jarFileUrls; 054 055 private Function<? super String, DataSource> jtaDataSourceProvider; 056 057 private List<String> managedClassNames; 058 059 private List<String> mappingFileNames; 060 061 private Function<? super String, DataSource> nonJtaDataSourceProvider; 062 063 private String persistenceProviderClassName; 064 065 private String persistenceUnitName; 066 067 private URL persistenceUnitRootUrl; 068 069 private String persistenceXMLSchemaVersion; 070 071 private Properties properties; 072 073 private SharedCacheMode sharedCacheMode; 074 075 private ClassLoader tempClassLoader; 076 077 private PersistenceUnitTransactionType transactionType; 078 079 private ValidationMode validationMode; 080 081 PersistenceUnitInfoBean() { 082 super(); 083 } 084 085 public PersistenceUnitInfoBean(final ClassLoader classLoader, 086 final boolean excludeUnlistedClasses, 087 final Collection<? extends URL> jarFileUrls, 088 final Function<? super String, DataSource> jtaDataSourceProvider, 089 final Collection<? extends String> managedClassNames, 090 final Collection<? extends String> mappingFileNames, 091 final Function<? super String, DataSource> nonJtaDataSourceProvider, 092 final String persistenceProviderClassName, 093 final String persistenceUnitName, 094 final URL persistenceUnitRootUrl, 095 final String persistenceXMLSchemaVersion, 096 final Properties properties, 097 final SharedCacheMode sharedCacheMode, 098 final ClassLoader tempClassLoader, 099 final PersistenceUnitTransactionType transactionType, 100 final ValidationMode validationMode) { 101 super(); 102 this.classLoader = classLoader; 103 this.excludeUnlistedClasses = excludeUnlistedClasses; 104 if (jarFileUrls == null || jarFileUrls.isEmpty()) { 105 this.jarFileUrls = Collections.emptyList(); 106 } else { 107 this.jarFileUrls = Collections.unmodifiableList(new ArrayList<>(jarFileUrls)); 108 } 109 if (jtaDataSourceProvider == null) { 110 this.jtaDataSourceProvider = name -> null; 111 } else { 112 this.jtaDataSourceProvider = jtaDataSourceProvider; 113 } 114 if (managedClassNames == null || managedClassNames.isEmpty()) { 115 this.managedClassNames = Collections.emptyList(); 116 } else { 117 this.managedClassNames = Collections.unmodifiableList(new ArrayList<>(managedClassNames)); 118 } 119 if (mappingFileNames == null || mappingFileNames.isEmpty()) { 120 this.mappingFileNames = Collections.emptyList(); 121 } else { 122 this.mappingFileNames = Collections.unmodifiableList(new ArrayList<>(mappingFileNames)); 123 } 124 if (nonJtaDataSourceProvider == null) { 125 this.nonJtaDataSourceProvider = name -> null; 126 } else { 127 this.nonJtaDataSourceProvider = nonJtaDataSourceProvider; 128 } 129 this.persistenceProviderClassName = persistenceProviderClassName; 130 this.persistenceUnitName = persistenceUnitName; 131 this.persistenceUnitRootUrl = persistenceUnitRootUrl; 132 this.persistenceXMLSchemaVersion = persistenceXMLSchemaVersion; 133 if (properties == null) { 134 this.properties = new Properties(); 135 } else { 136 this.properties = properties; 137 } 138 if (sharedCacheMode == null) { 139 this.sharedCacheMode = SharedCacheMode.UNSPECIFIED; 140 } else { 141 this.sharedCacheMode = sharedCacheMode; 142 } 143 this.tempClassLoader = tempClassLoader; 144 this.transactionType = Objects.requireNonNull(transactionType); 145 if (validationMode == null) { 146 this.validationMode = ValidationMode.AUTO; 147 } else { 148 this.validationMode = validationMode; 149 } 150 } 151 152 @Override 153 public List<URL> getJarFileUrls() { 154 return this.jarFileUrls; 155 } 156 157 @Override 158 public URL getPersistenceUnitRootUrl() { 159 return this.persistenceUnitRootUrl; 160 } 161 162 @Override 163 public List<String> getManagedClassNames() { 164 return this.managedClassNames; 165 } 166 167 @Override 168 public boolean excludeUnlistedClasses() { 169 return this.excludeUnlistedClasses; 170 } 171 172 @Override 173 public SharedCacheMode getSharedCacheMode() { 174 return this.sharedCacheMode; 175 } 176 177 @Override 178 public ValidationMode getValidationMode() { 179 return this.validationMode; 180 } 181 182 @Override 183 public Properties getProperties() { 184 return this.properties; 185 } 186 187 @Override 188 public ClassLoader getClassLoader() { 189 ClassLoader cl = this.classLoader; 190 if (cl == null) { 191 cl = Thread.currentThread().getContextClassLoader(); 192 if (cl == null) { 193 cl = this.getClass().getClassLoader(); 194 } 195 } 196 return cl; 197 } 198 199 @Override 200 public String getPersistenceXMLSchemaVersion() { 201 return this.persistenceXMLSchemaVersion; 202 } 203 204 @Override 205 public ClassLoader getNewTempClassLoader() { 206 ClassLoader cl = this.tempClassLoader; 207 if (cl == null) { 208 cl = this.getClassLoader(); 209 if (cl == null) { 210 cl = Thread.currentThread().getContextClassLoader(); 211 if (cl == null) { 212 cl = this.getClass().getClassLoader(); 213 } 214 } 215 } 216 return cl; 217 } 218 219 @Override 220 public void addTransformer(final ClassTransformer classTransformer) { 221 // TODO: implement, maybe. This is a very, very weird method. See 222 // https://github.com/javaee/glassfish/blob/168ce449c4ea0826842ab4129e83c4a700750970/appserver/persistence/jpa-container/src/main/java/org/glassfish/persistence/jpa/ServerProviderContainerContractInfo.java#L91. 223 // 99.99% of the implementations of this method on Github do 224 // nothing. The general idea seems to be that at 225 // createContainerEntityManagerFactory() time (see 226 // PersistenceProvider), the *provider* (e.g. EclipseLink) will 227 // call this method, which will "tunnel", somehow, the supplied 228 // ClassTransformer "through" "into" the container (in our case 229 // Weld) somehow such that at class load time this 230 // ClassTransformer will be called. 231 // 232 // So semantically addTransformer is really a method on the whole 233 // container ecosystem, and the JPA provider is saying, "Here, 234 // container ecosystem, please make this ClassTransformer be used 235 // when you, not I, load entity classes." 236 // 237 // There is also an unspoken assumption that this method will be 238 // called only once, if ever. 239 } 240 241 @Override 242 public String getPersistenceUnitName() { 243 return this.persistenceUnitName; 244 } 245 246 @Override 247 public String getPersistenceProviderClassName() { 248 return this.persistenceProviderClassName; 249 } 250 251 @Override 252 public PersistenceUnitTransactionType getTransactionType() { 253 return this.transactionType; 254 } 255 256 @Override 257 public DataSource getJtaDataSource() { 258 return this.jtaDataSourceProvider.apply(this.getPersistenceUnitName()); 259 } 260 261 @Override 262 public DataSource getNonJtaDataSource() { 263 return this.nonJtaDataSourceProvider.apply(this.getPersistenceUnitName()); 264 } 265 266 @Override 267 public List<String> getMappingFileNames() { 268 return this.mappingFileNames; 269 } 270 271 static final Collection<? extends PersistenceUnitInfoBean> fromPersistence(final Persistence persistence, 272 final URL rootUrl, 273 final Map<? extends String, ? extends Set<? extends Class<?>>> classes, 274 final Function<? super String, DataSource> jtaDataSourceProvider, 275 final Function<? super String, DataSource> nonJtaDataSourceProvider) 276 throws MalformedURLException { 277 Objects.requireNonNull(rootUrl); 278 final Collection<PersistenceUnitInfoBean> returnValue; 279 if (persistence == null) { 280 returnValue = Collections.emptySet(); 281 } else { 282 final Collection<? extends PersistenceUnit> persistenceUnits = persistence.getPersistenceUnit(); 283 if (persistenceUnits == null || persistenceUnits.isEmpty()) { 284 returnValue = Collections.emptySet(); 285 } else { 286 returnValue = new ArrayList<>(); 287 for (final PersistenceUnit persistenceUnit : persistenceUnits) { 288 assert persistenceUnit != null; 289 returnValue.add(fromPersistenceUnit(persistenceUnit, 290 rootUrl, 291 classes, 292 jtaDataSourceProvider, 293 nonJtaDataSourceProvider)); 294 } 295 } 296 } 297 return returnValue; 298 } 299 300 static final PersistenceUnitInfoBean fromPersistenceUnit(final PersistenceUnit persistenceUnit, 301 final URL rootUrl, 302 final Map<? extends String, ? extends Set<? extends Class<?>>> unlistedClasses, 303 final Function<? super String, DataSource> jtaDataSourceProvider, 304 final Function<? super String, DataSource> nonJtaDataSourceProvider) 305 throws MalformedURLException { 306 Objects.requireNonNull(persistenceUnit); 307 Objects.requireNonNull(rootUrl); 308 PersistenceUnitInfoBean returnValue = null; 309 310 final Collection<? extends String> jarFiles = persistenceUnit.getJarFile(); 311 final List<URL> jarFileUrls = new ArrayList<>(); 312 for (final String jarFile : jarFiles) { 313 if (jarFile != null) { 314 // TODO: probably won't work if rootUrl is, say, a jar URL 315 jarFileUrls.add(new URL(rootUrl, jarFile)); 316 } 317 } 318 319 final Collection<? extends String> mappingFiles = persistenceUnit.getMappingFile(); 320 321 final Properties properties = new Properties(); 322 final PersistenceUnit.Properties persistenceUnitProperties = persistenceUnit.getProperties(); 323 if (persistenceUnitProperties != null) { 324 final Collection<? extends PersistenceUnit.Properties.Property> propertyInstances = persistenceUnitProperties.getProperty(); 325 if (propertyInstances != null && !propertyInstances.isEmpty()) { 326 for (final PersistenceUnit.Properties.Property property : propertyInstances) { 327 assert property != null; 328 properties.setProperty(property.getName(), property.getValue()); 329 } 330 } 331 } 332 333 final ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); 334 335 final Collection<String> classes = persistenceUnit.getClazz(); 336 assert classes != null; 337 String name = persistenceUnit.getName(); 338 if (name == null) { 339 name = ""; 340 } 341 final Boolean excludeUnlistedClasses = persistenceUnit.isExcludeUnlistedClasses(); 342 if (!Boolean.TRUE.equals(excludeUnlistedClasses)) { 343 if (unlistedClasses != null && !unlistedClasses.isEmpty()) { 344 Collection<? extends Class<?>> myUnlistedClasses = unlistedClasses.get(name); 345 if (myUnlistedClasses != null && !myUnlistedClasses.isEmpty()) { 346 for (final Class<?> unlistedClass : myUnlistedClasses) { 347 if (unlistedClass != null) { 348 classes.add(unlistedClass.getName()); 349 } 350 } 351 } 352 // Also add "default" ones 353 if (!name.isEmpty()) { 354 myUnlistedClasses = unlistedClasses.get(""); 355 if (myUnlistedClasses != null && !myUnlistedClasses.isEmpty()) { 356 for (final Class<?> unlistedClass : myUnlistedClasses) { 357 if (unlistedClass != null) { 358 classes.add(unlistedClass.getName()); 359 } 360 } 361 } 362 } 363 } 364 } 365 366 final SharedCacheMode sharedCacheMode; 367 final PersistenceUnitCachingType persistenceUnitCachingType = persistenceUnit.getSharedCacheMode(); 368 if (persistenceUnitCachingType == null) { 369 sharedCacheMode = SharedCacheMode.UNSPECIFIED; 370 } else { 371 sharedCacheMode = SharedCacheMode.valueOf(persistenceUnitCachingType.name()); 372 } 373 374 final PersistenceUnitTransactionType transactionType; 375 final org.microbean.jpa.jaxb.PersistenceUnitTransactionType persistenceUnitTransactionType = persistenceUnit.getTransactionType(); 376 if (persistenceUnitTransactionType == null) { 377 transactionType = PersistenceUnitTransactionType.JTA; // I guess 378 } else { 379 transactionType = PersistenceUnitTransactionType.valueOf(persistenceUnitTransactionType.name()); 380 } 381 382 final ValidationMode validationMode; 383 final PersistenceUnitValidationModeType validationModeType = persistenceUnit.getValidationMode(); 384 if (validationModeType == null) { 385 validationMode = ValidationMode.AUTO; 386 } else { 387 validationMode = ValidationMode.valueOf(validationModeType.name()); 388 } 389 390 returnValue = new PersistenceUnitInfoBean(classLoader, 391 excludeUnlistedClasses == null ? true : excludeUnlistedClasses, 392 jarFileUrls, 393 jtaDataSourceProvider, 394 classes, 395 mappingFiles, 396 nonJtaDataSourceProvider, 397 persistenceUnit.getProvider(), 398 name, 399 rootUrl, 400 "2.2", 401 properties, 402 sharedCacheMode, 403 classLoader, 404 transactionType, 405 validationMode); 406 return returnValue; 407 } 408 409}