/*
* Copyright 2004-2009 the original author or authors.
*
* 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.
*/
package org.compass.gps.device.jpa.embedded.eclipselink;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import javax.persistence.EntityManagerFactory;
import javax.persistence.spi.PersistenceUnitInfo;
import javax.persistence.spi.PersistenceUnitTransactionType;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.compass.core.Compass;
import org.compass.core.CompassException;
import org.compass.core.config.CompassConfiguration;
import org.compass.core.config.CompassConfigurationFactory;
import org.compass.core.config.CompassEnvironment;
import org.compass.core.config.CompassSettings;
import org.compass.core.transaction.JTASyncTransactionFactory;
import org.compass.core.transaction.LocalTransactionFactory;
import org.compass.core.util.ClassUtils;
import org.compass.gps.device.jpa.JpaGpsDevice;
import org.compass.gps.device.jpa.JtaEntityManagerWrapper;
import org.compass.gps.device.jpa.ResourceLocalEntityManagerWrapper;
import org.compass.gps.device.jpa.embedded.DefaultJpaCompassGps;
import org.compass.gps.device.jpa.lifecycle.EclipseLinkJpaEntityLifecycleInjector;
import org.eclipse.persistence.Version;
import org.eclipse.persistence.config.SessionCustomizer;
import org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.internal.jpa.EntityManagerFactoryImpl;
import org.eclipse.persistence.internal.jpa.EntityManagerFactoryProvider;
import org.eclipse.persistence.internal.jpa.EntityManagerSetupImpl;
import org.eclipse.persistence.sessions.Session;
import org.eclipse.persistence.sessions.server.ServerSession;
/**
* A EclipseLink <code>SessionCustomizer</code> allowing to integrate in an
* "embedded" mode Compass with EclipseLink. The single required setting (for
* example, within the <code>persistence.xml</code> file) is the Compass
* connection property (
* {@link org.compass.core.config.CompassEnvironment#CONNECTION} and at least
* one Searchable class mapped out of the classes mapped in EclipseLink.
*
* <p>
* The embedded EclipseLink support uses Compass GPS and adds an "embedded"
* Compass, or adds a searchable feature to EclipseLink by registering a
* {@link org.compass.core.Compass} instance and a
* {@link org.compass.gps.device.jpa.JpaGpsDevice} instance with EclipseLink. It
* registers mirroring listeners (after delete/store/persist) to automatically
* mirror changes done through EclipseLink to the Compass index. It also
* registeres an event listener (
* {@link org.compass.gps.device.hibernate.embedded.CompassEventListener} to
* syncronize with transactions.
*
* <p>
* Use {@link EclipseLinkHelper} in order to access the <code>Compass</code>
* instance or the <code>JpaGpsDevice</code> instance attached to a given entity
* manager.
*
* <p>
* The Compass instnace used for mirroring can be configured by adding
* <code>compass</code> prefixed settings. Additional settings that only control
* the Compass instnace created for indexing should be set using
* <code>gps.index.compass.</code>. For more information on indexing and
* mirroring Compass please check {@link org.compass.gps.impl.SingleCompassGps}.
*
* <p>
* This customizer tries to find the persistence info in order to read the
* properties out of it. In order for it to find it, it uses the naming
* convention EclipseLink has at naming Sessions. Note, if you change the name
* of the Session using EclipseLink setting, this customizer will not be able to
* operate.
*
* <p>
* This session customizer will also identify if the persistence info is
* configured to work with JTA or with RESOURCE LOCAL transaction and adjust
* itsefl accordingly. If JTA is used, it will automatically use Compass
* {@link org.compass.core.transaction.JTASyncTransactionFactory} and if
* RESOURCE LOCAL is used it will automatically use
* {@link org.compass.core.transaction.LocalTransactionFactory}. Note, this is
* only set if the transaction factory is not explicitly set using Compass
* settings.
*
* <p>
* Specific properties that this plugin can use:
* <ul>
* <li>compass.EclipseLink.indexQuery.[entity name/class]: Specific select query
* that will be used to perform the indexing for the mentioned specific entity
* name / class. Note, before calling {@link org.compass.gps.CompassGps#index()}
* there is an option the programmatically control this.</li>
* <li>compass.EclipseLink.config: A classpath that points to Compass
* configuration.</li>
* <li>compass.EclipseLink.session.customizer: If there is another EclipseLink
* <code>SessionCustomizer</code> that needs to be applied, its class FQN should
* be specified with this setting.</li>
* </ul>
*
* @author kimchy
*/
public class CompassSessionCustomizer implements SessionCustomizer {
private static final Log log = LogFactory.getLog(CompassSessionCustomizer.class);
private static final String COMPASS_PREFIX = "compass";
private static final String COMPASS_GPS_INDEX_PREFIX = "gps.index.";
public static final String INDEX_QUERY_PREFIX = "compass.eclipselink.indexQuery.";
public static final String COMPASS_CONFIG_LOCATION = "compass.eclipselink.config";
public static final String COMPASS_SESSION_CUSTOMIZER = "compass.eclipselink.session.customizer";
public void customize(Session session) throws Exception {
if (log.isInfoEnabled()) {
log.info("Compass embedded EclipseLink support enabled, initializing for session ["
+ session + "]");
}
PersistenceUnitInfo persistenceUnitInfo = findPersistenceUnitInfo(session);
if (persistenceUnitInfo == null) {
throw new CompassException("Failed to find Persistence Unit Info");
}
Map<Object, Object> eclipselinkProps = new HashMap();
eclipselinkProps.putAll(persistenceUnitInfo.getProperties());
eclipselinkProps.putAll(session.getProperties());
String sessionCustomizer = (String) eclipselinkProps.get(COMPASS_SESSION_CUSTOMIZER);
if (sessionCustomizer != null) {
((SessionCustomizer) ClassUtils.forName(sessionCustomizer,
persistenceUnitInfo.getClassLoader()).newInstance()).customize(session);
}
Properties compassProperties = new Properties();
// noinspection unchecked
for (Map.Entry entry : eclipselinkProps.entrySet()) {
if (!(entry.getKey() instanceof String)) {
continue;
}
String key = (String) entry.getKey();
if (key.startsWith(COMPASS_PREFIX)) {
compassProperties.put(entry.getKey(), entry.getValue());
}
if (key.startsWith(COMPASS_GPS_INDEX_PREFIX)) {
compassProperties.put(entry.getKey(), entry.getValue());
}
}
if (compassProperties.isEmpty()) {
if (log.isDebugEnabled()) {
log.debug("No Compass properties found in configuraiton, disabling Compass");
}
return;
}
if (compassProperties.getProperty(CompassEnvironment.CONNECTION) == null) {
if (log.isDebugEnabled()) {
log.debug("No Compass [" + CompassEnvironment.CONNECTION
+ "] property defined, disabling Compass");
}
return;
}
CompassConfiguration compassConfiguration = CompassConfigurationFactory.newConfiguration();
// use the same class loader of the persistence info to load Compass
// classes
compassConfiguration.setClassLoader(persistenceUnitInfo.getClassLoader());
CompassSettings settings = compassConfiguration.getSettings();
settings.addSettings(compassProperties);
String configLocation = (String) compassProperties.get(COMPASS_CONFIG_LOCATION);
if (configLocation != null) {
compassConfiguration.configure(configLocation);
}
Map descriptors = session.getDescriptors();
for (Object o : descriptors.values()) {
ClassDescriptor classDescriptor = (ClassDescriptor) o;
Class mappedClass = classDescriptor.getJavaClass();
compassConfiguration.tryAddClass(mappedClass);
}
// create some default settings
String transactionFactory = (String) compassProperties
.get(CompassEnvironment.Transaction.FACTORY);
boolean eclipselinkControlledTransaction;
if (transactionFactory == null) {
if (persistenceUnitInfo.getTransactionType() == PersistenceUnitTransactionType.JTA) {
transactionFactory = JTASyncTransactionFactory.class.getName();
eclipselinkControlledTransaction = false;
} else {
transactionFactory = LocalTransactionFactory.class.getName();
eclipselinkControlledTransaction = true;
}
settings.setSetting(CompassEnvironment.Transaction.FACTORY, transactionFactory);
} else {
// JPA is not controlling the transaction (using JTA Sync or XA),
// don't commit/rollback
// with EclipseLink transaction listeners
eclipselinkControlledTransaction = false;
}
// if the settings is configured to use local transaciton, disable
// thread bound setting since
// we are using EclipseLink to managed transaction scope (using user
// objects on the em) and not thread locals
// will only be taken into account when using local transactions
if (settings
.getSetting(CompassEnvironment.Transaction.DISABLE_THREAD_BOUND_LOCAL_TRANSATION) == null) {
// if no emf is defined
settings.setBooleanSetting(
CompassEnvironment.Transaction.DISABLE_THREAD_BOUND_LOCAL_TRANSATION, true);
}
Compass compass = compassConfiguration.buildCompass();
boolean commitBeforeCompletion = settings.getSettingAsBoolean(
CompassEnvironment.Transaction.COMMIT_BEFORE_COMPLETION, false);
// extract index properties so they will be used
Properties indexProps = new Properties();
for (Map.Entry entry : compassProperties.entrySet()) {
String key = (String) entry.getKey();
if (key.startsWith(COMPASS_GPS_INDEX_PREFIX)) {
indexProps.put(key.substring(COMPASS_GPS_INDEX_PREFIX.length()), entry.getValue());
}
}
// start an internal JPA device and Gps for mirroring
EntityManagerFactory emf = new EntityManagerFactoryImpl((ServerSession) session);
JpaGpsDevice jpaGpsDevice = new JpaGpsDevice(DefaultJpaCompassGps.JPA_DEVICE_NAME, emf);
jpaGpsDevice.setMirrorDataChanges(true);
jpaGpsDevice.setInjectEntityLifecycleListener(true);
for (Map.Entry entry : compassProperties.entrySet()) {
String key = (String) entry.getKey();
if (key.startsWith(INDEX_QUERY_PREFIX)) {
String entityName = key.substring(INDEX_QUERY_PREFIX.length());
String selectQuery = (String) entry.getValue();
jpaGpsDevice.setIndexSelectQuery(entityName, selectQuery);
}
}
EclipseLinkJpaEntityLifecycleInjector lifecycleInjector = new EclipseLinkJpaEntityLifecycleInjector();
lifecycleInjector.setEventListener(new EclipseLinkEventListener(jpaGpsDevice));
jpaGpsDevice.setLifecycleInjector(lifecycleInjector);
// set explicitly the EntityManagerWrapper since EclipseLink rollback
// the transaction on EntityManager#getTransaction
// which makes it useless when using DefaultEntityManagerWrapper
if (persistenceUnitInfo.getTransactionType() == PersistenceUnitTransactionType.JTA) {
jpaGpsDevice.setEntityManagerWrapper(new JtaEntityManagerWrapper());
} else {
jpaGpsDevice.setEntityManagerWrapper(new ResourceLocalEntityManagerWrapper());
}
DefaultJpaCompassGps jpaCompassGps = new DefaultJpaCompassGps();
jpaCompassGps.setCompass(compass);
jpaCompassGps.addGpsDevice(jpaGpsDevice);
// before we start the Gps, open and close a broker
emf.createEntityManager().close();
jpaCompassGps.start();
session.getEventManager().addListener(
new CompassSessionEventListener(compass, jpaCompassGps, commitBeforeCompletion,
eclipselinkControlledTransaction, indexProps));
if (log.isDebugEnabled()) {
log.debug("Compass embedded EclipseLink support active");
}
}
protected PersistenceUnitInfo findPersistenceUnitInfo(Session session) {
String sessionName = session.getName();
if (Version.getVersion().compareTo("2.3.0") <= 0) {
int index = sessionName.indexOf('-');
while (index != -1) {
String urlAndName = sessionName.substring(0, index) + sessionName.substring(index + 1);
if (log.isDebugEnabled()) {
log.debug("Trying to find PersistenceInfo using [" + urlAndName + "]");
}
EntityManagerSetupImpl emSetup = EntityManagerFactoryProvider.getEntityManagerSetupImpl(urlAndName);
if (emSetup != null) {
if (log.isDebugEnabled()) {
log.debug("Found PersistenceInfo using [" + urlAndName + "]");
}
return emSetup.getPersistenceUnitInfo();
}
index = sessionName.indexOf('-', index + 1);
}
} else {
EntityManagerSetupImpl emSetup = EntityManagerFactoryProvider
.getEntityManagerSetupImpl(sessionName);
if (emSetup != null) {
if (log.isDebugEnabled()) {
log.debug("Found PersistenceInfo using [" + sessionName + "]");
}
return emSetup.getPersistenceUnitInfo();
}
}
return null;
}
}