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;
018:
019: import java.util.HashMap;
020: import java.util.Map;
021: import javax.persistence.EntityManagerFactory;
022:
023: import org.compass.core.util.Assert;
024: import org.compass.gps.CompassGpsException;
025: import org.compass.gps.PassiveMirrorGpsDevice;
026: import org.compass.gps.device.jpa.entities.EntityInformation;
027: import org.compass.gps.device.jpa.entities.JpaEntitiesLocator;
028: import org.compass.gps.device.jpa.entities.JpaEntitiesLocatorDetector;
029: import org.compass.gps.device.jpa.indexer.JpaIndexEntitiesIndexer;
030: import org.compass.gps.device.jpa.indexer.JpaIndexEntitiesIndexerDetector;
031: import org.compass.gps.device.jpa.lifecycle.JpaEntityLifecycleInjector;
032: import org.compass.gps.device.jpa.lifecycle.JpaEntityLifecycleInjectorDetector;
033: import org.compass.gps.device.jpa.queryprovider.DefaultJpaQueryProvider;
034: import org.compass.gps.device.jpa.queryprovider.JpaQueryProvider;
035: import org.compass.gps.device.jpa.support.NativeJpaHelper;
036: import org.compass.gps.device.support.parallel.AbstractParallelGpsDevice;
037: import org.compass.gps.device.support.parallel.IndexEntitiesIndexer;
038: import org.compass.gps.device.support.parallel.IndexEntity;
039:
040: /**
041: * <p>A Java Persistence API Gps Device (EJB3 Persistence).
042: *
043: * <p>The jpa device provides support for using jpa to index a database. The path can
044: * be viewed as: Database <-> EntityManager(JPA) <-> Objects <-> Compass::Gps
045: * <-> Compass::Core (Search Engine). What it means is that for every object that has both
046: * jpa and compass mappings, you will be able to index it's data, as well as real time mirroring of
047: * data changes.
048: *
049: * <p>When creating the object, an <code>EntityManagerFactory</code> must be provided to the Device.
050: *
051: * <p>Indexing uses {@link JpaEntitiesLocator} to locate all the entities that can be
052: * indexed (i.e. entities that have both Compass and JPA mappings). Most of the time
053: * the {@link org.compass.gps.device.jpa.entities.DefaultJpaEntitiesLocator} is enough, but
054: * special JPA implementation one can be provided. If none is provided, the device will use the {@link
055: * JpaEntitiesLocatorDetector} to auto detect the correct locator (which defaults to the ({@link
056: * org.compass.gps.device.jpa.entities.DefaultJpaEntitiesLocator}).
057: *
058: * <p>The indexing process itself is done through an implementation of
059: * {@link org.compass.gps.device.jpa.indexer.JpaIndexEntitiesIndexer}. There are several implemenations
060: * for it including a default one that uses plain JPA APIs. Specific implementations (such as Hibernate
061: * and OpenJPA) are used for better performance.
062: *
063: * <p>Mirroring can be done in two ways. The first one is using JPA official API, implemeting
064: * an Entity Lifecycle listener and specifing it for each entity class via annotations. Compass
065: * comes with helper base clases for it, {@link AbstractCompassJpaEntityListener} and
066: * {@link AbstractDeviceJpaEntityListener}. As far as integrating Compass with JPA for mirroring,
067: * this is the less preferable way. The second option for mirroring is to use the
068: * {@link JpaEntityLifecycleInjector}, which will use the internal JPA implementation to
069: * inject global lifecycle event listerens (sadly, there is no option to do that with the
070: * <code>EntityManagerFactory</code> API). If the {@link #setInjectEntityLifecycleListener(boolean)} is
071: * set to <code>true</code> (defaults to <code>false</code>), the device will try to use the injector to
072: * inject global event listeners. If no {@link JpaEntityLifecycleInjector} is defined, the device will
073: * try to autodetect the injector based on the current support for specific JPA implementations using
074: * the {@link JpaEntityLifecycleInjectorDetector}. See its javadoc for a list of the current JPA
075: * implementations supported.
076: *
077: * <p>Mirroring can be turned off using the {@link #setMirrorDataChanges(boolean)} to <code>false</code>.
078: * It defaults to <code>true<code>.
079: *
080: * <p>The device allows for {@link NativeJpaExtractor} to be set, for applications
081: * that use a framework or by themself wrap the actual <code>EntityManagerFactory</code> implementation.
082: *
083: * <p>For advance usage, the device allows for {@link EntityManagerWrapper} to be set,
084: * allowing to control the creation of <code>EntityManager</code>s, and transactions.
085: * The {@link DefaultEntityManagerWrapper} should suffice for most cases.
086: *
087: * <p>The device extends the parallel device provinding supprot for parallel indexing.
088: *
089: * @author kimchy
090: */
091: public class JpaGpsDevice extends AbstractParallelGpsDevice implements
092: PassiveMirrorGpsDevice {
093:
094: /**
095: * Creates a new JpaGpsDevice. Note that its name ({@link #setName(String)} and
096: * entity manager factory ({@link #setEntityManagerFactory(javax.persistence.EntityManagerFactory)}
097: * must be set.
098: */
099: public JpaGpsDevice() {
100:
101: }
102:
103: /**
104: * Creates a new device with a specific name and an entity manager factory.
105: */
106: public JpaGpsDevice(String name,
107: EntityManagerFactory entityManagerFactory) {
108: setName(name);
109: setEntityManagerFactory(entityManagerFactory);
110: }
111:
112: private boolean mirrorDataChanges = true;
113:
114: private int fetchCount = 200;
115:
116: private EntityManagerFactory entityManagerFactory;
117:
118: private EntityManagerWrapper entityManagerWrapper;
119:
120: private JpaEntityLifecycleInjector lifecycleInjector;
121:
122: private boolean injectEntityLifecycleListener;
123:
124: private NativeJpaExtractor nativeJpaExtractor;
125:
126: private EntityManagerFactory nativeEntityManagerFactory;
127:
128: private JpaEntitiesLocator entitiesLocator;
129:
130: private Map<Class<?>, JpaQueryProvider> queryProviderByClass = new HashMap<Class<?>, JpaQueryProvider>();
131:
132: private Map<String, JpaQueryProvider> queryProviderByName = new HashMap<String, JpaQueryProvider>();
133:
134: private JpaIndexEntitiesIndexer entitiesIndexer;
135:
136: protected void doStart() throws CompassGpsException {
137: Assert.notNull(entityManagerFactory,
138: buildMessage("Must set JPA EntityManagerFactory"));
139: if (entityManagerWrapper == null) {
140: entityManagerWrapper = new DefaultEntityManagerWrapper();
141: }
142: entityManagerWrapper.setUp(entityManagerFactory);
143:
144: nativeEntityManagerFactory = entityManagerFactory;
145: if (nativeJpaExtractor != null) {
146: nativeEntityManagerFactory = nativeJpaExtractor
147: .extractNative(nativeEntityManagerFactory);
148: if (nativeEntityManagerFactory == null) {
149: throw new JpaGpsDeviceException(
150: buildMessage("Native EntityManager extractor returned null"));
151: }
152: if (log.isDebugEnabled()) {
153: log
154: .debug(buildMessage("Using native EntityManagerFactory ["
155: + nativeEntityManagerFactory.getClass()
156: .getName()
157: + "] extracted by ["
158: + nativeJpaExtractor.getClass()
159: .getName() + "]"));
160: }
161: } else {
162: nativeEntityManagerFactory = NativeJpaHelper
163: .extractNativeJpa(entityManagerFactory, compassGps
164: .getMirrorCompass().getSettings());
165: if (log.isDebugEnabled()) {
166: log
167: .debug(buildMessage("Using native EntityManagerFactory ["
168: + nativeEntityManagerFactory.getClass()
169: .getName()
170: + "] using default extractor"));
171: }
172: }
173:
174: if (entitiesLocator == null) {
175: entitiesLocator = JpaEntitiesLocatorDetector.detectLocator(
176: nativeEntityManagerFactory, compassGps
177: .getMirrorCompass().getSettings());
178: if (log.isDebugEnabled()) {
179: log.debug(buildMessage("Using index entityLocator ["
180: + entitiesLocator.getClass().getName() + "]"));
181: }
182: }
183:
184: if (injectEntityLifecycleListener && mirrorDataChanges) {
185: if (lifecycleInjector == null) {
186: lifecycleInjector = JpaEntityLifecycleInjectorDetector
187: .detectInjector(nativeEntityManagerFactory,
188: compassGps.getMirrorCompass()
189: .getSettings());
190: }
191: if (lifecycleInjector == null) {
192: throw new JpaGpsDeviceException(
193: buildMessage("Failed to locate lifecycleInjector"));
194: }
195: if (log.isDebugEnabled()) {
196: log
197: .debug(buildMessage("Using lifecycleInjector ["
198: + lifecycleInjector.getClass()
199: .getName() + "]"));
200: }
201: lifecycleInjector.injectLifecycle(
202: nativeEntityManagerFactory, this );
203: }
204:
205: if (entitiesIndexer == null) {
206: entitiesIndexer = JpaIndexEntitiesIndexerDetector
207: .detectEntitiesIndexer(nativeEntityManagerFactory,
208: compassGps.getMirrorCompass().getSettings());
209: }
210: if (log.isDebugEnabled()) {
211: log.debug(buildMessage("Using entities indexer ["
212: + entitiesIndexer.getClass().getName() + "]"));
213: }
214: entitiesIndexer.setJpaGpsDevice(this );
215: }
216:
217: protected void doStop() throws CompassGpsException {
218: if (injectEntityLifecycleListener && mirrorDataChanges) {
219: lifecycleInjector.removeLifecycle(
220: nativeEntityManagerFactory, this );
221: }
222: }
223:
224: protected IndexEntity[] doGetIndexEntities()
225: throws CompassGpsException {
226: EntityInformation[] entitiesInformation = entitiesLocator
227: .locate(nativeEntityManagerFactory, this );
228: // apply specific select statements
229: for (EntityInformation entityInformation : entitiesInformation) {
230: if (queryProviderByClass.get(entityInformation
231: .getEntityClass()) != null) {
232: entityInformation.setQueryProvider(queryProviderByClass
233: .get(entityInformation.getEntityClass()));
234: }
235: if (queryProviderByName.get(entityInformation.getName()) != null) {
236: entityInformation.setQueryProvider(queryProviderByName
237: .get(entityInformation.getName()));
238: }
239: }
240: return entitiesInformation;
241: }
242:
243: protected IndexEntitiesIndexer doGetIndexEntitiesIndexer() {
244: return entitiesIndexer;
245: }
246:
247: public EntityManagerFactory getEntityManagerFactory() {
248: return this .entityManagerFactory;
249: }
250:
251: public EntityManagerFactory getNativeEntityManagerFactory() {
252: return this .nativeEntityManagerFactory;
253: }
254:
255: /**
256: * @see org.compass.gps.MirrorDataChangesGpsDevice#isMirrorDataChanges()
257: */
258: public boolean isMirrorDataChanges() {
259: return mirrorDataChanges;
260: }
261:
262: /**
263: * @see org.compass.gps.MirrorDataChangesGpsDevice#setMirrorDataChanges(boolean)
264: */
265: public void setMirrorDataChanges(boolean mirrorDataChanges) {
266: this .mirrorDataChanges = mirrorDataChanges;
267: }
268:
269: /**
270: * Sets the Jpa <code>EntityManagerFactory</code>. This is manadatory for the Jpa device.
271: *
272: * @param entityManagerFactory The entity manager factory the device will use.
273: */
274: public void setEntityManagerFactory(
275: EntityManagerFactory entityManagerFactory) {
276: this .entityManagerFactory = entityManagerFactory;
277: }
278:
279: /**
280: * Sets the Entity Manager factory wrapper to control the entity manager operations. This is optional since the
281: * device has sensible defaults for it.
282: *
283: * @param entityManagerWrapper The entity manager wrapper to control the manager operations.
284: */
285: public void setEntityManagerWrapper(
286: EntityManagerWrapper entityManagerWrapper) {
287: this .entityManagerWrapper = entityManagerWrapper;
288: }
289:
290: /**
291: * Returns the Entity Manager factory wrapper to control the entity manager operations.
292: */
293: public EntityManagerWrapper getEntityManagerWrapper() {
294: return entityManagerWrapper;
295: }
296:
297: /**
298: * <p>Sets a specialized native entity manager factory extractor.
299: * For applications that use a framework or by themself wrap the actual
300: * <code>EntityManagerFactory</code> implementation.
301: *
302: * The native extractor is mainly used for specialized {@link JpaEntityLifecycleInjector}
303: * and {@link JpaEntitiesLocator}.
304: */
305: public void setNativeExtractor(NativeJpaExtractor nativeJpaExtractor) {
306: this .nativeJpaExtractor = nativeJpaExtractor;
307: }
308:
309: /**
310: * Returns the native extractor.
311: */
312: public NativeJpaExtractor getNativeJpaExtractor() {
313: return nativeJpaExtractor;
314: }
315:
316: /**
317: * Sets if the device should try and automatically inject global entity lifecycle
318: * listeners using either the provided {@link JpaEntityLifecycleInjector}, or if not
319: * set, using the {@link JpaEntityLifecycleInjectorDetector}. Defaults to <code>false</code>.
320: */
321: public void setInjectEntityLifecycleListener(
322: boolean injectEntityLifecycleListener) {
323: this .injectEntityLifecycleListener = injectEntityLifecycleListener;
324: }
325:
326: /**
327: * If the {@link #setLifecycleInjector(org.compass.gps.device.jpa.lifecycle.JpaEntityLifecycleInjector)} is
328: * set to <code>true</code>, the global lifecycle injector that will be used to inject global lifecycle
329: * event listerens to the underlying implementation of the <code>EntityManagerFactory</code>. If not set,
330: * the {@link JpaEntitiesLocatorDetector} will be used to auto-detect it.
331: */
332: public void setLifecycleInjector(
333: JpaEntityLifecycleInjector lifecycleInjector) {
334: this .lifecycleInjector = lifecycleInjector;
335: }
336:
337: /**
338: * Sets a specific enteties locator, which is responsible for locating enteties
339: * that need to be indexed. Not a required parameter, since will use the
340: * {@link JpaEntitiesLocatorDetector} to auto detect that correct one.
341: */
342: public void setEntitiesLocator(JpaEntitiesLocator entitiesLocator) {
343: this .entitiesLocator = entitiesLocator;
344: }
345:
346: /**
347: * Sets the fetch count for the indexing process. A large number will perform the indexing faster,
348: * but will consume more memory. Defaults to <code>200</code>.
349: */
350: public void setFetchCount(int fetchCount) {
351: this .fetchCount = fetchCount;
352: }
353:
354: /**
355: * Returns the fetch count for the indexing process. A large number will perform the indexing faster,
356: * but will consume more memory. Default to <code>200</code>.
357: */
358: public int getFetchCount() {
359: return this .fetchCount;
360: }
361:
362: /**
363: * <p>Sets a specific select statement for the index process of the given
364: * entity class. The same as {@link #setIndexQueryProvider(Class,JpaQueryProvider)}
365: * using {@link org.compass.gps.device.jpa.queryprovider.DefaultJpaQueryProvider}.
366: *
367: * <p>Certain JPA implementations have specific query providers with possible
368: * enhanced functionality in regards to indexing. When using this method
369: * instead of providing their specific implementation might mean less functionality
370: * from the indexer.
371: *
372: * <p>Note, this information is used when the device starts.
373: *
374: * @param entityClass The Entity class to associate the select query with
375: * @param selectQuery The select query to execute when indexing the given entity
376: */
377: public void setIndexSelectQuery(Class<?> entityClass,
378: String selectQuery) {
379: setIndexQueryProvider(entityClass, new DefaultJpaQueryProvider(
380: selectQuery));
381: }
382:
383: /**
384: * Sets a specific select statement for the index process of the given
385: * entity name. The same as {@link #setIndexQueryProvider(String,JpaQueryProvider)}
386: * using {@link org.compass.gps.device.jpa.queryprovider.DefaultJpaQueryProvider}.
387: *
388: * <p>Certain JPA implementations have specific query providers with possible
389: * enhanced functionality in regards to indexing. When using this method
390: * instead of providing their specific implementation might mean less functionality
391: * from the indexer.
392: *
393: * <p>Note, this information is used when the device starts.
394: *
395: * @param entityName The entity name to associate the select query with
396: * @param selectQuery The select query to execute when indexing the given entity
397: */
398: public void setIndexSelectQuery(String entityName,
399: String selectQuery) {
400: setIndexQueryProvider(entityName, new DefaultJpaQueryProvider(
401: selectQuery));
402: }
403:
404: /**
405: * <p>Sets a specific query provider for the index process of the given entity class.
406: * <p>Note, this information is used when the device starts.
407: *
408: * @param entityClass The Entity class to associate the query provider with
409: * @param queryProvider The query provider to execute when indexing the given entity
410: */
411: public void setIndexQueryProvider(Class<?> entityClass,
412: JpaQueryProvider queryProvider) {
413: queryProviderByClass.put(entityClass, queryProvider);
414: }
415:
416: /**
417: * <p>Sets a specific query provider for the index process of the given entity name.
418: * <p>Note, this information is used when the device starts.
419: *
420: * @param entityName The Entity name to associate the query provider with
421: * @param queryProvider The query provider to execute when indexing the given entity
422: */
423: public void setIndexQueryProvider(String entityName,
424: JpaQueryProvider queryProvider) {
425: queryProviderByName.put(entityName, queryProvider);
426: }
427:
428: /**
429: * Sets a custom entities indexer that will be used to index the data. By default will
430: * be detected automatically based on the actual implemenation of JPA used and will try
431: * to use it.
432: */
433: public void setEntitiesIndexer(
434: JpaIndexEntitiesIndexer entitiesIndexer) {
435: this.entitiesIndexer = entitiesIndexer;
436: }
437: }
|