/* * #%L * Wisdom-Framework * %% * Copyright (C) 2013 - 2014 Wisdom Framework * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package org.wisdom.framework.jpa; import org.apache.commons.io.IOUtils; import org.apache.felix.ipojo.annotations.*; import org.osgi.framework.BundleContext; import org.osgi.framework.ServiceRegistration; import org.osgi.framework.wiring.BundleWiring; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.wisdom.framework.jpa.crud.JPARepository; import org.wisdom.framework.jpa.model.Persistence; import org.wisdom.framework.jpa.model.PersistenceUnitCachingType; import org.wisdom.framework.jpa.model.PersistenceUnitValidationModeType; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.SharedCacheMode; import javax.persistence.ValidationMode; import javax.persistence.spi.ClassTransformer; import javax.persistence.spi.PersistenceProvider; import javax.persistence.spi.PersistenceUnitInfo; import javax.persistence.spi.PersistenceUnitTransactionType; import javax.sql.DataSource; import javax.transaction.TransactionManager; import javax.validation.ValidatorFactory; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.util.*; /** * This class is the interface between the bridge (the manager) and the Persistence Provider. * It is used by the Persistence Provider to get all context information. We create one * instance of these for each persistence unit found in a bundle. */ @Component @Provides public class PersistenceUnitComponent implements PersistenceUnitInfo { private final static Logger LOGGER = LoggerFactory.getLogger(PersistenceUnitComponent.class); //TODO right now we don't check that we have data sources, which may be an issue, // We should enforce their availability and track them private static final String UNIT_PROVIDER_PROP = "persistent.unit.provider"; private static final String UNIT_VERSION_PROP = "persistent.unit.version"; private static final String UNIT_NAME_PROP = "persistent.unit.name"; private static final String UNIT_ENTITIES_PROP = "persistent.unit.entities"; private static final String UNIT_TRANSACTION_PROP = "persistent.unit.transaction.mode"; private final Persistence.PersistenceUnit persistenceUnitXml; /** * Injected in the instance configuration. */ private final PersistentBundle sourceBundle; private final String location; private final BundleContext bundleContext; private EntityManager entityManager; private EntityManagerFactory entityManagerFactory; private JPARepository repository; @Requires private ValidatorFactory validator; /** * Filter injected in the instance configuration. */ @Requires(proxy = false, id = "jta-ds") DataSource jtaDataSource; /** * Filter injected in the instance configuration. */ @Requires(proxy = false, id = "ds") DataSource nonJtaDataSource; /** * The transformer. */ @Requires JPATransformer transformer; /** * The service exposed by JPA provider. * Only one provider is supported. */ @Requires(proxy = false) PersistenceProvider provider; /** * Transaction manager. */ @Requires TransactionManager transactionManager; ServiceRegistration<EntityManager> emRegistration; ServiceRegistration<EntityManagerFactory> emfRegistration; /** * Create a new Persistence Unit Info * * @param bundle the source bundle * @param xml The xml of the persistence unit * @param context the bundle context */ public PersistenceUnitComponent(@Property(name = "bundle") PersistentBundle bundle, @Property(name = "unit") Persistence.PersistenceUnit xml, @Context BundleContext context) throws Exception { this.sourceBundle = bundle; this.persistenceUnitXml = xml; this.bundleContext = context; // Retrieve the location set while parsing the persistence unit. this.location = (String) getProperties().get("location"); } /** * Shutdown this persistence unit */ @Invalidate void shutdown() { if (repository != null) { repository.dispose(); } if (emRegistration != null) { emRegistration.unregister(); } if (entityManager != null) { entityManager.close(); } if (emfRegistration != null) { emfRegistration.unregister(); } if (entityManagerFactory != null) { entityManagerFactory.close(); } if (transformer != null) { transformer.unregister(sourceBundle.bundle); } } /** * Starts the unit. It creates the entity manager factory and entity manager, as well as the repository and crud * services. */ @Validate public void start() { try { Map<String, Object> map = new HashMap<>(); for (Persistence.PersistenceUnit.Properties.Property p : persistenceUnitXml.getProperties().getProperty()) { map.put(p.getName(), p.getValue()); } if (isOpenJPA()) { map.put("openjpa.ManagedRuntime", "invocation(TransactionManagerMethod=org.wisdom.framework.jpa.accessor" + ".TransactionManagerAccessor.get)"); } // This is not going to work with OpenJPA because the current version of OpenJPA requires an old version // of javax.validation. The wisdom one is too recent. map.put("javax.persistence.validation.factory", validator); Dictionary<String, Object> properties = new Hashtable<>(); properties.put(UNIT_NAME_PROP, persistenceUnitXml.getName()); properties.put(UNIT_VERSION_PROP, sourceBundle.bundle.getVersion()); properties.put(UNIT_PROVIDER_PROP, provider.getClass().getName()); List<String> entities = persistenceUnitXml.getClazz(); properties.put(UNIT_ENTITIES_PROP, entities.toArray(new String[entities.size()])); properties.put(UNIT_TRANSACTION_PROP, getTransactionType().toString()); // If the unit set the transaction to RESOURCE_LOCAL, no JTA involved. if (persistenceUnitXml.getTransactionType() == org.wisdom.framework.jpa.model.PersistenceUnitTransactionType.RESOURCE_LOCAL) { entityManagerFactory = provider.createContainerEntityManagerFactory(this, map); entityManager = entityManagerFactory.createEntityManager(); emfRegistration = bundleContext.registerService(EntityManagerFactory.class, entityManagerFactory, properties); emRegistration = bundleContext.registerService(EntityManager.class, entityManager, properties); repository = new JPARepository(persistenceUnitXml, entityManager, entityManagerFactory, transactionManager, sourceBundle.bundle.getBundleContext()); } else { // JTA entityManagerFactory = provider.createContainerEntityManagerFactory(this, map); entityManager = new TransactionalEntityManager(transactionManager, entityManagerFactory, this); emRegistration = bundleContext.registerService(EntityManager.class, entityManager, properties); emfRegistration = bundleContext.registerService(EntityManagerFactory.class, entityManagerFactory, properties); repository = new JPARepository(persistenceUnitXml, entityManager, entityManagerFactory, transactionManager, sourceBundle.bundle.getBundleContext()); } } catch (Exception e) { LOGGER.error("Error while initializing the JPA services for unit {}", persistenceUnitXml.getName(), e); } } private boolean isOpenJPA() { return provider.getClass().getName().contains("openjpa"); } /** * Add a new transformer. * * @see javax.persistence.spi.PersistenceUnitInfo#addTransformer(javax.persistence.spi.ClassTransformer) */ @Override public void addTransformer(ClassTransformer transformer) { this.transformer.register(sourceBundle.bundle, transformer); } /* * @see javax.persistence.spi.PersistenceUnitInfo#excludeUnlistedClasses() */ @Override public boolean excludeUnlistedClasses() { Boolean b = persistenceUnitXml.isExcludeUnlistedClasses(); return b == null || b; } /* * @see javax.persistence.spi.PersistenceUnitInfo#getClassLoader() */ @Override public synchronized ClassLoader getClassLoader() { return sourceBundle.bundle.adapt(BundleWiring.class).getClassLoader(); } /* * @see javax.persistence.spi.PersistenceUnitInfo#getJarFileUrls() */ @Override public List<URL> getJarFileUrls() { List<URL> urls = new ArrayList<>(); for (String url : persistenceUnitXml.getJarFile()) { try { urls.add(new URL(url)); } catch (MalformedURLException e) { LOGGER.error("Cannot create an URL object from {}", url, e); } } return urls; } /** * We hand out a proxy that automatically enlists any connections on the * current transaction. * * @see javax.persistence.spi.PersistenceUnitInfo#getJtaDataSource() */ @Override public DataSource getJtaDataSource() { return jtaDataSource; } /* * @see javax.persistence.spi.PersistenceUnitInfo#getManagedClassNames() */ @Override public List<String> getManagedClassNames() { return persistenceUnitXml.getClazz(); } /* * @see javax.persistence.spi.PersistenceUnitInfo#getMappingFileNames() */ @Override public List<String> getMappingFileNames() { return persistenceUnitXml.getMappingFile(); } /** * In this method we just create a simple temporary class loader. This class * loader uses the bundle's class loader as parent but defines the classes * in this class loader. This has the implicit assumption that the temp * class loader is used BEFORE any bundle's classes are loaded since a class * loader does parent delegation first. Sigh, guess it works most of the * time. There is however, no good alternative though in OSGi we could * actually refresh the bundle. * * @see javax.persistence.spi.PersistenceUnitInfo#getNewTempClassLoader() */ @Override public ClassLoader getNewTempClassLoader() { return new ClassLoader(getClassLoader()) { //NOSONAR /** * Searches for the .class file and define it using the current class loader. * @param className the class name * @return the class object * @throws ClassNotFoundException if the class cannot be found */ @Override protected Class findClass(String className) throws ClassNotFoundException { // Use path of class, then get the resource String path = className.replace('.', '/').concat(".class"); URL resource = getParent().getResource(path); if (resource == null) { throw new ClassNotFoundException(className + " as resource " + path + " in " + getParent()); } try { ByteArrayOutputStream bout = new ByteArrayOutputStream(); IOUtils.copy(resource.openStream(), bout); byte[] buffer = bout.toByteArray(); return defineClass(className, buffer, 0, buffer.length); } catch (Exception e) { throw new ClassNotFoundException(className + " as resource" + path + " in " + getParent(), e); } } /** * Finds a resource in the bundle. * @param resource the resource * @return the url of the resource from the bundle, {@code null} if not found. */ @Override protected URL findResource(String resource) { return getParent().getResource(resource); } /** * Finds resources in the bundle. * @param resource the resource * @return the url of the resources from the bundle, empty if not found. */ @Override protected Enumeration<URL> findResources(String resource) throws IOException { return getParent().getResources(resource); } }; } /** * This Data Source is based on a XA Data Source but will not be enlisted in * a transaction. * * @see javax.persistence.spi.PersistenceUnitInfo#getNonJtaDataSource() */ @Override public DataSource getNonJtaDataSource() { return nonJtaDataSource; } /* * @see * javax.persistence.spi.PersistenceUnitInfo#getPersistenceProviderClassName * () */ @Override public String getPersistenceProviderClassName() { return provider.getClass().getName(); } /* * @see javax.persistence.spi.PersistenceUnitInfo#getPersistenceUnitName() */ @Override public String getPersistenceUnitName() { return persistenceUnitXml.getName(); } /* * @see * javax.persistence.spi.PersistenceUnitInfo#getPersistenceUnitRootUrl() */ @Override public URL getPersistenceUnitRootUrl() { // Make one that is OSGi based, it relies on the 'location' property String loc = location; int n = loc.lastIndexOf('/'); if (n > 0) { loc = loc.substring(0, n); } if (loc.isEmpty()) { loc = "/"; } return sourceBundle.bundle.getResource(loc); } /* * @see * javax.persistence.spi.PersistenceUnitInfo#getPersistenceXMLSchemaVersion * () */ @Override public String getPersistenceXMLSchemaVersion() { return "2.1"; } /* * @see javax.persistence.spi.PersistenceUnitInfo#getProperties() */ @Override public Properties getProperties() { Properties properties = new Properties(); if (persistenceUnitXml.getProperties() != null && persistenceUnitXml.getProperties().getProperty() != null) { for (Persistence.PersistenceUnit.Properties.Property p : persistenceUnitXml.getProperties().getProperty()) { properties.put(p.getName(), p.getValue()); } } return properties; } /* * @see javax.persistence.spi.PersistenceUnitInfo#getSharedCacheMode() */ @Override public SharedCacheMode getSharedCacheMode() { PersistenceUnitCachingType sharedCacheMode = persistenceUnitXml.getSharedCacheMode(); if (sharedCacheMode == null) { return null; } return SharedCacheMode.valueOf(sharedCacheMode.name()); } /* * @see javax.persistence.spi.PersistenceUnitInfo#getTransactionType() */ @Override public PersistenceUnitTransactionType getTransactionType() { if (persistenceUnitXml.getTransactionType() == org.wisdom.framework.jpa.model.PersistenceUnitTransactionType .RESOURCE_LOCAL) { return PersistenceUnitTransactionType.RESOURCE_LOCAL; } else { return PersistenceUnitTransactionType.JTA; } } /* * @see javax.persistence.spi.PersistenceUnitInfo#getValidationMode() */ @Override public ValidationMode getValidationMode() { PersistenceUnitValidationModeType validationMode = persistenceUnitXml.getValidationMode(); if (validationMode == null) { return null; } return ValidationMode.valueOf(validationMode.name()); } }