001: /*
002: * Copyright 2004-2006 the original author or authors.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package org.compass.gps.device.jpa.embedded.toplink;
018:
019: import java.util.HashMap;
020: import java.util.Map;
021: import java.util.Properties;
022: import javax.persistence.EntityManagerFactory;
023: import javax.persistence.spi.PersistenceUnitInfo;
024: import javax.persistence.spi.PersistenceUnitTransactionType;
025:
026: import oracle.toplink.essentials.descriptors.ClassDescriptor;
027: import oracle.toplink.essentials.ejb.cmp3.EntityManagerFactoryProvider;
028: import oracle.toplink.essentials.internal.ejb.cmp3.EntityManagerFactoryImpl;
029: import oracle.toplink.essentials.internal.ejb.cmp3.EntityManagerSetupImpl;
030: import oracle.toplink.essentials.sessions.Session;
031: import oracle.toplink.essentials.threetier.ServerSession;
032: import oracle.toplink.essentials.tools.sessionconfiguration.SessionCustomizer;
033: import org.apache.commons.logging.Log;
034: import org.apache.commons.logging.LogFactory;
035: import org.compass.core.Compass;
036: import org.compass.core.CompassException;
037: import org.compass.core.config.CompassConfiguration;
038: import org.compass.core.config.CompassConfigurationFactory;
039: import org.compass.core.config.CompassEnvironment;
040: import org.compass.core.config.CompassSettings;
041: import org.compass.core.transaction.JTASyncTransactionFactory;
042: import org.compass.core.transaction.LocalTransactionFactory;
043: import org.compass.core.util.ClassUtils;
044: import org.compass.gps.device.jpa.JpaGpsDevice;
045: import org.compass.gps.device.jpa.JtaEntityManagerWrapper;
046: import org.compass.gps.device.jpa.ResourceLocalEntityManagerWrapper;
047: import org.compass.gps.device.jpa.embedded.DefaultJpaCompassGps;
048: import org.compass.gps.device.jpa.lifecycle.TopLinkEssentialsJpaEntityLifecycleInjector;
049:
050: /**
051: * A TopLink <code>SessionCustomizer</code> allowing to integrate in an "embedded" mode
052: * Compass with TopLink. The single required setting (for example, within the <code>persistence.xml</code>
053: * file) is the Compass connection property ({@link org.compass.core.config.CompassEnvironment#CONNECTION}
054: * and at least one Searchable class mapped out of the classes mapped in TopLink.
055: *
056: * <p>The embedded TopLink support uses Compass GPS and adds an "embedded" Compass, or adds a searchable
057: * feature to TopLink by registering a {@link org.compass.core.Compass} instance and a {@link org.compass.gps.device.jpa.JpaGpsDevice}
058: * instance with TopLink. It registers mirroring listeners (after delete/store/persist) to automatically
059: * mirror changes done through TopLink to the Compass index. It also registeres an event listener
060: * ({@link org.compass.gps.device.hibernate.embedded.CompassEventListener} to syncronize with transactions.
061: *
062: * <p>Use {@link TopLinkHelper} in order to access the <code>Compass</code> instance or the
063: * <code>JpaGpsDevice</code> instance attached to a given entity manager.
064: *
065: * <p>The Compass instnace used for mirroring can be configured by adding <code>compass</code> prefixed settings.
066: * Additional settings that only control the Compass instnace created for indexing should be set using
067: * <code>gps.index.compass.</code>. For more information on indexing and mirroring Compass please check
068: * {@link org.compass.gps.impl.SingleCompassGps}.
069: *
070: * <p>This customizer tries to find the persistence info in order to read the properties out of it. In order
071: * for it to find it, it uses the naming convention TopLink has at naming Sessions. Note, if you change the
072: * name of the Session using TopLink setting, this customizer will not be able to operate.
073: *
074: * <p>This session customizer will also identify if the persistence info is configured to work with JTA or
075: * with RESOURCE LOCAL transaction and adjust itsefl accordingly. If JTA is used, it will automatically
076: * use Compass {@link JTASyncTransactionFactory} and if RESOURCE LOCAL is used it will automatically use
077: * {@link LocalTransactionFactory}. Note, this is only set if the transaction factory is not explicitly set
078: * using Compass settings.
079: *
080: * <p>Specific properties that this plugin can use:
081: * <ul>
082: * <li>compass.toplink.indexQuery.[entity name/class]: Specific select query that will be used to perform the indexing
083: * for the mentioned specific entity name / class. Note, before calling {@link org.compass.gps.CompassGps#index()} there
084: * is an option the programmatically control this.</li>
085: * <li>compass.toplink.config: A classpath that points to Compass configuration.</li>
086: * <li>compass.toplink.session.customizer: If there is another TopLink <code>SessionCustomizer</code> that needs
087: * to be applied, its class FQN should be specified with this setting.</li>
088: * </ul>
089: *
090: * @author kimchy
091: */
092: public class CompassSessionCustomizer implements SessionCustomizer {
093:
094: private static final Log log = LogFactory
095: .getLog(CompassSessionCustomizer.class);
096:
097: private static final String COMPASS_PREFIX = "compass";
098:
099: private static final String COMPASS_GPS_INDEX_PREFIX = "gps.index.";
100:
101: public static final String INDEX_QUERY_PREFIX = "compass.toplink.indexQuery.";
102:
103: public static final String COMPASS_CONFIG_LOCATION = "compass.toplink.config";
104:
105: public static final String COMPASS_SESSION_CUSTOMIZER = "compass.toplink.session.customizer";
106:
107: public void customize(Session session) throws Exception {
108: if (log.isInfoEnabled()) {
109: log
110: .info("Compass embedded TopLink Essentials support enabled, initializing for session ["
111: + session + "]");
112: }
113: PersistenceUnitInfo persistenceUnitInfo = findPersistenceUnitInfo(session);
114: if (persistenceUnitInfo == null) {
115: throw new CompassException(
116: "Failed to find Persistence Unit Info");
117: }
118:
119: Map<Object, Object> toplinkProps = new HashMap();
120: toplinkProps.putAll(persistenceUnitInfo.getProperties());
121: toplinkProps.putAll(session.getProperties());
122:
123: String sessionCustomizer = (String) toplinkProps
124: .get(COMPASS_SESSION_CUSTOMIZER);
125: if (sessionCustomizer != null) {
126: ((SessionCustomizer) ClassUtils.forName(sessionCustomizer,
127: persistenceUnitInfo.getClassLoader()).newInstance())
128: .customize(session);
129: }
130:
131: Properties compassProperties = new Properties();
132: //noinspection unchecked
133: for (Map.Entry entry : toplinkProps.entrySet()) {
134: if (!(entry.getKey() instanceof String)) {
135: continue;
136: }
137: String key = (String) entry.getKey();
138: if (key.startsWith(COMPASS_PREFIX)) {
139: compassProperties.put(entry.getKey(), entry.getValue());
140: }
141: if (key.startsWith(COMPASS_GPS_INDEX_PREFIX)) {
142: compassProperties.put(entry.getKey(), entry.getValue());
143: }
144: }
145: if (compassProperties.isEmpty()) {
146: if (log.isDebugEnabled()) {
147: log
148: .debug("No Compass properties found in configuraiton, disabling Compass");
149: }
150: return;
151: }
152:
153: CompassConfiguration compassConfiguration = CompassConfigurationFactory
154: .newConfiguration();
155: // use the same class loader of the persistence info to load Compass classes
156: compassConfiguration.setClassLoader(persistenceUnitInfo
157: .getClassLoader());
158: CompassSettings settings = compassConfiguration.getSettings();
159: settings.addSettings(compassProperties);
160:
161: String configLocation = (String) compassProperties
162: .get(COMPASS_CONFIG_LOCATION);
163: if (configLocation != null) {
164: compassConfiguration.configure(configLocation);
165: }
166:
167: Map descriptors = session.getDescriptors();
168: for (Object o : descriptors.values()) {
169: ClassDescriptor classDescriptor = (ClassDescriptor) o;
170: Class mappedClass = classDescriptor.getJavaClass();
171: compassConfiguration.tryAddClass(mappedClass);
172: }
173:
174: // create some default settings
175:
176: String transactionFactory = (String) compassProperties
177: .get(CompassEnvironment.Transaction.FACTORY);
178: boolean toplinkControlledTransaction;
179: if (transactionFactory == null) {
180: if (persistenceUnitInfo.getTransactionType() == PersistenceUnitTransactionType.JTA) {
181: transactionFactory = JTASyncTransactionFactory.class
182: .getName();
183: toplinkControlledTransaction = false;
184: } else {
185: transactionFactory = LocalTransactionFactory.class
186: .getName();
187: toplinkControlledTransaction = true;
188: }
189: settings.setSetting(CompassEnvironment.Transaction.FACTORY,
190: transactionFactory);
191: } else {
192: // JPA is not controlling the transaction (using JTA Sync or XA), don't commit/rollback
193: // with Toplink transaction listeners
194: toplinkControlledTransaction = false;
195: }
196:
197: // if the settings is configured to use local transaciton, disable thread bound setting since
198: // we are using Toplink to managed transaction scope (using user objects on the em) and not thread locals
199: // will only be taken into account when using local transactions
200: if (settings
201: .getSetting(CompassEnvironment.Transaction.DISABLE_THREAD_BOUND_LOCAL_TRANSATION) == null) {
202: // if no emf is defined
203: settings
204: .setBooleanSetting(
205: CompassEnvironment.Transaction.DISABLE_THREAD_BOUND_LOCAL_TRANSATION,
206: true);
207: }
208:
209: Compass compass = compassConfiguration.buildCompass();
210:
211: boolean commitBeforeCompletion = settings
212: .getSettingAsBoolean(
213: CompassEnvironment.Transaction.COMMIT_BEFORE_COMPLETION,
214: false);
215:
216: // extract index properties so they will be used
217: Properties indexProps = new Properties();
218: for (Map.Entry entry : compassProperties.entrySet()) {
219: String key = (String) entry.getKey();
220: if (key.startsWith(COMPASS_GPS_INDEX_PREFIX)) {
221: indexProps.put(key.substring(COMPASS_GPS_INDEX_PREFIX
222: .length()), entry.getValue());
223: }
224: }
225:
226: // start an internal JPA device and Gps for mirroring
227: EntityManagerFactory emf = new EntityManagerFactoryImpl(
228: (ServerSession) session);
229:
230: JpaGpsDevice jpaGpsDevice = new JpaGpsDevice(
231: DefaultJpaCompassGps.JPA_DEVICE_NAME, emf);
232: jpaGpsDevice.setMirrorDataChanges(true);
233: jpaGpsDevice.setInjectEntityLifecycleListener(true);
234: for (Map.Entry entry : compassProperties.entrySet()) {
235: String key = (String) entry.getKey();
236: if (key.startsWith(INDEX_QUERY_PREFIX)) {
237: String entityName = key.substring(INDEX_QUERY_PREFIX
238: .length());
239: String selectQuery = (String) entry.getValue();
240: jpaGpsDevice.setIndexSelectQuery(entityName,
241: selectQuery);
242: }
243: }
244:
245: TopLinkEssentialsJpaEntityLifecycleInjector lifecycleInjector = new TopLinkEssentialsJpaEntityLifecycleInjector();
246: lifecycleInjector
247: .setEventListener(new EmbeddedToplinkEventListener(
248: jpaGpsDevice));
249: jpaGpsDevice.setLifecycleInjector(lifecycleInjector);
250:
251: // set explicitly the EntityManagerWrapper since Toplink rollback the transaction on EntityManager#getTransaction
252: // which makes it useless when using DefaultEntityManagerWrapper
253: if (persistenceUnitInfo.getTransactionType() == PersistenceUnitTransactionType.JTA) {
254: jpaGpsDevice
255: .setEntityManagerWrapper(new JtaEntityManagerWrapper());
256: } else {
257: jpaGpsDevice
258: .setEntityManagerWrapper(new ResourceLocalEntityManagerWrapper());
259: }
260:
261: DefaultJpaCompassGps jpaCompassGps = new DefaultJpaCompassGps();
262: jpaCompassGps.setCompass(compass);
263: jpaCompassGps.addGpsDevice(jpaGpsDevice);
264:
265: // before we start the Gps, open and close a broker
266: emf.createEntityManager().close();
267:
268: jpaCompassGps.start();
269:
270: session.getEventManager().addListener(
271: new CompassSessionEventListener(compass, jpaCompassGps,
272: commitBeforeCompletion,
273: toplinkControlledTransaction, indexProps));
274:
275: if (log.isDebugEnabled()) {
276: log
277: .debug("Compass embedded TopLink Essentials support active");
278: }
279: }
280:
281: protected PersistenceUnitInfo findPersistenceUnitInfo(
282: Session session) {
283: String sessionName = session.getName();
284: int index = sessionName.indexOf('-');
285: while (index != -1) {
286: String urlAndName = sessionName.substring(0, index)
287: + sessionName.substring(index + 1);
288: if (log.isDebugEnabled()) {
289: log.debug("Trying to find PersistenceInfo using ["
290: + urlAndName + "]");
291: }
292: EntityManagerSetupImpl emSetup = EntityManagerFactoryProvider
293: .getEntityManagerSetupImpl(urlAndName);
294: if (emSetup != null) {
295: if (log.isDebugEnabled()) {
296: log.debug("Found PersistenceInfo using ["
297: + urlAndName + "]");
298: }
299: return emSetup.getPersistenceUnitInfo();
300: }
301: index = sessionName.indexOf('-', index + 1);
302: }
303: return null;
304: }
305: }
|