| /******************************************************************************* |
| * Copyright (c) 2010-2014 SAP AG and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * SAP AG - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.skalli.services.persistence; |
| |
| import java.io.IOException; |
| import java.lang.reflect.Field; |
| import java.lang.reflect.Modifier; |
| import java.text.MessageFormat; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import javax.persistence.EntityManager; |
| import javax.persistence.EntityManagerFactory; |
| |
| import org.apache.commons.lang.StringUtils; |
| import org.apache.commons.lang.math.NumberUtils; |
| import org.eclipse.persistence.config.PersistenceUnitProperties; |
| import org.eclipse.skalli.commons.CollectionUtils; |
| import org.eclipse.skalli.services.BundleProperties; |
| import org.osgi.service.component.ComponentContext; |
| import org.osgi.service.jpa.EntityManagerFactoryBuilder; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| /** |
| * Base implementation of {@link EntityManagerService} that uses {@link EntityManagerFactoryBuilder} |
| * from the OSGI JPA service to create {@link EntityManager entity managers}. |
| * <p> |
| * Note, entity manager service implementations derived from this class must be registered as |
| * declarative service and must define a reference to <tt>org.osgi.service.jpa.EntityManagerFactoryBuilder</tt> |
| * of the following form: |
| * <pre> |
| * <reference |
| * target="(osgi.unit.name=<your persistence unit name>)" |
| * interface="org.osgi.service.jpa.EntityManagerFactoryBuilder" |
| * name="EntityManagerFactoryBuilder" |
| * policy="dynamic" |
| * cardinality="1..1" |
| * bind="bindEntityManagerFactoryBuilder" |
| * unbind="unbindEntityManagerFactoryBuilder"/> |
| * </pre> |
| * <p> |
| * Furthermore, derived implementations must call {@link #activate(ComponentContext)} and |
| * {@link #deactivate(ComponentContext)}. |
| */ |
| public class EntityManagerServiceBase implements EntityManagerService { |
| |
| private static final Logger LOG = LoggerFactory.getLogger(EntityManagerServiceBase.class); |
| |
| private static final Set<String> ALL_PERSISTENCE_PROPERTIES = |
| getPublicStaticFinalFieldValues(PersistenceUnitProperties.class); |
| |
| static final String[] REQUIRED_PROPERTIES = { |
| PersistenceUnitProperties.JDBC_DRIVER, |
| PersistenceUnitProperties.JDBC_URL, |
| PersistenceUnitProperties.JDBC_USER, |
| PersistenceUnitProperties.JDBC_PASSWORD, |
| PersistenceUnitProperties.TARGET_DATABASE, |
| }; |
| |
| static final String SKALLI_PERSISTENCE = "skalli.persistence."; //$NON-NLS-1$ |
| static final String SKALLI_EMF_TIMEOUT = SKALLI_PERSISTENCE + "timeout"; //$NON-NLS-1$ |
| static final long SKALLI_EMF_DEFAULT_TIMEOUT = -1L; // no timeout |
| |
| private ComponentContext context; |
| private EntityManagerFactoryBuilder emfb; |
| private String persistenceUnitName; |
| |
| // Available JPA properties |
| // key: persistenceUnitName value: the property map for the persistenceUnitName |
| private Map<String, Map<String, Object>> propertiesCache = new HashMap<String, Map<String, Object>>(); |
| |
| protected void activate(ComponentContext context) { |
| this.context = context; |
| } |
| |
| protected void deactivate(ComponentContext context) { |
| this.context = null; |
| } |
| |
| protected EntityManagerFactory locateEntityManagerFactory() { |
| return (EntityManagerFactory)context.locateService("EntityManagerFactory"); //$NON-NLS-1$ |
| } |
| |
| protected void bindEntityManagerFactoryBuilder(EntityManagerFactoryBuilder emfb, Map<Object, Object> properties) { |
| LOG.info(MessageFormat.format("bindEntityManagerFactoryBuilder({0})", emfb.getClass().getName())); //$NON-NLS-1$ |
| this.emfb = emfb; |
| Object value = properties.get("osgi.unit.name"); //$NON-NLS-1$ |
| if (value != null) { |
| persistenceUnitName = value.toString(); |
| } |
| } |
| |
| protected void unbindEntityManagerFactoryBuilder(EntityManagerFactoryBuilder emfb) { |
| LOG.info(MessageFormat.format("unbindEntityManagerFactoryBuilder({0})", emfb.getClass().getName())); //$NON-NLS-1$ |
| this.emfb = null; |
| } |
| |
| /** |
| * Retrieves an {@link EntityManager}. |
| * <p> |
| * If JPA configuration parameters are available (see {@link PersistenceUnitProperties}), |
| * the entity manager is created based on these configuration parameters. |
| * First checks whether a property matching the pattern |
| * <tt>"skalli.persistence.<jpaUnitName>.<propertyKey>"</tt> |
| * can be found. Alternatively, searches for a property matching the pattern |
| * <tt>"skalli.persistence.<propertyKey>"</tt>. |
| * <p> |
| * If no explicit configuration is provided, the OSGi service registry is searched for an |
| * implementation of the service interface {@link EntityManagerFactory}. |
| * A timeout can be specified with the property <tt>skalli.persistence.timeout</tt> |
| * to allow a platform persistence service coming up and be injected into the service registry. |
| * By default, the timeout is set to <tt>-1</tt> meaning that no timeout is applied. |
| * A timeout of zero causes this method to wait indefinitely. All other values define a |
| * timeout in milliseconds. |
| * |
| * @return an entity manager instance, never <code>null</code>. |
| * |
| * @throws IOException if neither the platform could provide a suitable entity manager |
| * factory, nor suitable configuration properties have been provided that would allow to |
| * construct one, or the creation of the entity manager failed. |
| **/ |
| @Override |
| public EntityManager getEntityManager() throws IOException { |
| return getEntityManager(getConfiguredProperties(persistenceUnitName)); |
| } |
| |
| // package protected for tests |
| EntityManager getEntityManager(Map<String, Object> properties) throws IOException { |
| if (StringUtils.isBlank(persistenceUnitName)) { |
| throw new IOException("Failed to create an entity manager: no persistence unit name available"); |
| } |
| |
| // if explicit JPA properties are provided, create the entity manager based |
| // on these properties - even if the runtime has injected an entity manager factory! |
| // note: properties always contains EntityManagerFactoryBuilder.JPA_UNIT_NAME, therefore |
| // we check for additional properties |
| if (properties != null && properties.size() > 1) { |
| return createEntityManager(properties); |
| } |
| |
| // otherwise: use the entity manager factory provided by the runtime - if any! |
| EntityManagerFactory entityManagerFactory = getEntityManagerFactory(); |
| if (entityManagerFactory == null) { |
| throw new IOException(MessageFormat.format("Failed to create an entity manager: no entity manager" + |
| "factory available for persistence unit {0}", persistenceUnitName)); |
| } |
| return createEntityManager(entityManagerFactory); |
| } |
| |
| private EntityManager createEntityManager(Map<String, Object> properties) throws IOException { |
| if (emfb == null) { |
| throw new IOException("Failed to create entity manager: no entity manager factory builder available"); |
| } |
| List<String> missingProperties = getMissingRequiredProperties(properties); |
| if (missingProperties.size() > 0) { |
| throw new IOException(MessageFormat.format( |
| "Failed to create an entity manager: required persistence properties missing ({0})", |
| CollectionUtils.toString(missingProperties, ','))); |
| } |
| return createEntityManager(emfb.createEntityManagerFactory(properties)); |
| } |
| |
| private EntityManagerFactory getEntityManagerFactory() { |
| EntityManagerFactory entityManagerFactory = null; |
| long timeout = NumberUtils.toLong(BundleProperties.getProperty(SKALLI_EMF_TIMEOUT), |
| SKALLI_EMF_DEFAULT_TIMEOUT); |
| try { |
| entityManagerFactory = waitEntityManagerFactory(timeout); |
| } catch (InterruptedException e) { |
| LOG.warn("Thread has been interrupted while waiting for an entity manager factory to appear", e); |
| } |
| return entityManagerFactory; |
| } |
| |
| private EntityManagerFactory waitEntityManagerFactory(long timeout) throws InterruptedException { |
| long sleep = 0; |
| long waited = 0; |
| EntityManagerFactory instance = locateEntityManagerFactory(); |
| if (timeout > 0) { |
| while (instance == null && waited < timeout) { |
| if (sleep == 0) { |
| sleep = timeout; |
| while (sleep > 10) { |
| sleep >>= 1; |
| } |
| } |
| if (sleep > 0) { |
| Thread.sleep(sleep); |
| waited += sleep; |
| sleep <<= 1; |
| } |
| instance = locateEntityManagerFactory(); |
| } |
| } |
| return instance; |
| } |
| |
| private EntityManager createEntityManager(EntityManagerFactory entityManagerFactory) throws IOException { |
| try { |
| return entityManagerFactory.createEntityManager(); |
| } catch (IllegalStateException e) { |
| throw new IOException(MessageFormat.format("Failed to create an entity manager using factory {0}", |
| entityManagerFactory.getClass()), e); |
| } |
| } |
| |
| private List<String> getMissingRequiredProperties(Map<String, Object> properties) { |
| List<String> missing = new ArrayList<String>(); |
| for (String key: REQUIRED_PROPERTIES) { |
| if (!properties.containsKey(key)) { |
| missing.add(key); |
| } |
| } |
| return missing; |
| } |
| |
| private Map<String, Object> getConfiguredProperties(String jpaUnitName) { |
| Map<String, Object> cachedProps = propertiesCache.get(jpaUnitName); |
| if (cachedProps != null) { |
| return cachedProps; |
| } |
| |
| Map<String, Object> properties = new HashMap<String, Object>(); |
| properties.putAll(getConfiguredPropertyMap(jpaUnitName)); |
| properties.put(EntityManagerFactoryBuilder.JPA_UNIT_NAME, jpaUnitName); |
| |
| propertiesCache.put(jpaUnitName, properties); |
| |
| logConfiguredProperties(jpaUnitName, properties); |
| return properties; |
| } |
| |
| @SuppressWarnings("nls") |
| private void logConfiguredProperties(String jpaUnitName, Map<String, Object> properties) { |
| if (LOG.isInfoEnabled()) { |
| StringBuilder msg = new StringBuilder("persistence.unit = '" + jpaUnitName + "': "); |
| appendConfiguredProperty(msg, PersistenceUnitProperties.TARGET_DATABASE, properties); |
| appendConfiguredProperty(msg, PersistenceUnitProperties.JDBC_DRIVER, properties); |
| appendConfiguredProperty(msg, PersistenceUnitProperties.JDBC_URL, properties); |
| appendConfiguredProperty(msg, PersistenceUnitProperties.JDBC_USER, properties); |
| LOG.info(msg.toString()); |
| } |
| } |
| |
| @SuppressWarnings("nls") |
| private void appendConfiguredProperty(StringBuilder msg, String propertyName, Map<String, Object> properties) { |
| String propertyValue = (String)properties.get(propertyName); |
| if (StringUtils.isNotBlank(propertyValue)) { |
| msg.append(propertyName).append(" = '").append(propertyValue).append("'; "); |
| } |
| } |
| |
| private Map<String, String> getConfiguredPropertyMap(String jpaUnitName) { |
| HashMap<String, String> properties = new HashMap<String, String>(); |
| for (String propertyKey : ALL_PERSISTENCE_PROPERTIES) { |
| putNonEmptyProperty(properties, jpaUnitName, propertyKey); |
| } |
| return properties; |
| } |
| |
| private void putNonEmptyProperty(HashMap<String, String> properties, String jpaUnitName, String propertyKey) { |
| String propertyValue = getPropertyValue(jpaUnitName, propertyKey); |
| if (StringUtils.isNotBlank(propertyValue)) { |
| properties.put(propertyKey, propertyValue); |
| } |
| } |
| |
| private String getPropertyValue(String jpaUnitName, String propertyKey) { |
| String result = BundleProperties.getProperty(SKALLI_PERSISTENCE + jpaUnitName + "." + propertyKey); //$NON-NLS-1$ |
| if (result == null) { |
| result = BundleProperties.getProperty(SKALLI_PERSISTENCE + propertyKey); |
| } |
| return result; |
| } |
| |
| static Set<String> getPublicStaticFinalFieldValues(Class<?> clazz) { |
| Set<String> properties = new HashSet<String>(); |
| Field[] fields = clazz.getDeclaredFields(); |
| for (Field field : fields) { |
| if (field.getType().isAssignableFrom(String.class)) { |
| int mod = field.getModifiers(); |
| if (Modifier.isPublic(mod) && Modifier.isStatic(mod) && Modifier.isFinal(mod)) { |
| try { |
| String propertyValue = field.get(null).toString(); |
| if (StringUtils.isNotBlank(propertyValue)) { |
| properties.add(propertyValue); |
| } |
| } catch (Exception e) { |
| LOG.error("Failed to read public static final fields of class " + clazz, e); |
| } |
| } |
| } |
| } |
| return properties; |
| } |
| } |