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  public 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}