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.spring;
018:
019: import java.io.File;
020: import java.lang.reflect.InvocationHandler;
021: import java.lang.reflect.InvocationTargetException;
022: import java.lang.reflect.Method;
023: import java.lang.reflect.Proxy;
024: import java.util.Iterator;
025: import java.util.Map;
026: import java.util.Properties;
027: import javax.sql.DataSource;
028:
029: import org.apache.commons.logging.Log;
030: import org.apache.commons.logging.LogFactory;
031: import org.compass.core.Compass;
032: import org.compass.core.CompassException;
033: import org.compass.core.config.CompassConfiguration;
034: import org.compass.core.config.CompassConfigurationFactory;
035: import org.compass.core.config.CompassEnvironment;
036: import org.compass.core.config.InputStreamMappingResolver;
037: import org.compass.core.converter.Converter;
038: import org.compass.core.lucene.LuceneEnvironment;
039: import org.compass.core.lucene.engine.store.jdbc.ExternalDataSourceProvider;
040: import org.compass.core.spi.InternalCompass;
041: import org.compass.core.util.ClassUtils;
042: import org.compass.spring.transaction.SpringSyncTransactionFactory;
043: import org.springframework.beans.factory.BeanNameAware;
044: import org.springframework.beans.factory.DisposableBean;
045: import org.springframework.beans.factory.FactoryBean;
046: import org.springframework.beans.factory.InitializingBean;
047: import org.springframework.core.io.Resource;
048: import org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy;
049: import org.springframework.transaction.PlatformTransactionManager;
050:
051: /**
052: * @author kimchy
053: */
054: public class LocalCompassBean implements FactoryBean, InitializingBean,
055: DisposableBean, BeanNameAware {
056:
057: protected static final Log log = LogFactory
058: .getLog(LocalCompassBean.class);
059:
060: private Resource connection;
061:
062: private Resource configLocation;
063:
064: private Resource[] configLocations;
065:
066: private Resource[] resourceLocations;
067:
068: private Resource[] resourceJarLocations;
069:
070: private Resource[] resourceDirectoryLocations;
071:
072: private String[] classMappings;
073:
074: private InputStreamMappingResolver[] mappingResolvers;
075:
076: private Properties compassSettings;
077:
078: private DataSource dataSource;
079:
080: private PlatformTransactionManager transactionManager;
081:
082: private Map convertersByName;
083:
084: private Compass compass;
085:
086: private String beanName;
087:
088: private CompassConfiguration config;
089:
090: private LocalCompassBeanPostProcessor postProcessor;
091:
092: /**
093: * Allows to register a post processor for the Compass configuration.
094: *
095: * @param postProcessor
096: */
097: public void setPostProcessor(
098: LocalCompassBeanPostProcessor postProcessor) {
099: this .postProcessor = postProcessor;
100: }
101:
102: public void setBeanName(String beanName) {
103: this .beanName = beanName;
104: }
105:
106: /**
107: * Sets an optional connection based on Spring <code>Resource</code>
108: * abstraction. Will be used if none is set as part of other possible
109: * configuration of Compass connection.
110: * <p/>
111: * Will use <code>Resource#getFile</code> in order to get the absolute
112: * path.
113: */
114: public void setConnection(Resource connection) {
115: this .connection = connection;
116: }
117:
118: /**
119: * Set the location of the Compass XML config file, for example as classpath
120: * resource "classpath:compass.cfg.xml".
121: * <p/>
122: * Note: Can be omitted when all necessary properties and mapping resources
123: * are specified locally via this bean.
124: */
125: public void setConfigLocation(Resource configLocation) {
126: this .configLocation = configLocation;
127: }
128:
129: /**
130: * Set the location of the Compass XML config file, for example as classpath
131: * resource "classpath:compass.cfg.xml".
132: * <p/>
133: * Note: Can be omitted when all necessary properties and mapping resources
134: * are specified locally via this bean.
135: */
136: public void setConfigLocations(Resource[] configLocations) {
137: this .configLocations = configLocations;
138: }
139:
140: public void setCompassSettings(Properties compassSettings) {
141: this .compassSettings = compassSettings;
142: }
143:
144: /**
145: * Set locations of Compass resource files (mapping and common metadata),
146: * for example as classpath resource "classpath:example.cpm.xml". Supports
147: * any resource location via Spring's resource abstraction, for example
148: * relative paths like "WEB-INF/mappings/example.hbm.xml" when running in an
149: * application context.
150: * <p/>
151: * Can be used to add to mappings from a Compass XML config file, or to
152: * specify all mappings locally.
153: */
154: public void setResourceLocations(Resource[] resourceLocations) {
155: this .resourceLocations = resourceLocations;
156: }
157:
158: /**
159: * Set locations of jar files that contain Compass resources, like
160: * "WEB-INF/lib/example.jar".
161: * <p/>
162: * Can be used to add to mappings from a Compass XML config file, or to
163: * specify all mappings locally.
164: */
165: public void setResourceJarLocations(Resource[] resourceJarLocations) {
166: this .resourceJarLocations = resourceJarLocations;
167: }
168:
169: /**
170: * Set locations of directories that contain Compass mapping resources, like
171: * "WEB-INF/mappings".
172: * <p/>
173: * Can be used to add to mappings from a Compass XML config file, or to
174: * specify all mappings locally.
175: */
176: public void setResourceDirectoryLocations(
177: Resource[] resourceDirectoryLocations) {
178: this .resourceDirectoryLocations = resourceDirectoryLocations;
179: }
180:
181: /**
182: * Sets the fully qualified class names for mappings. Useful when using annotations
183: * for example. Will also try to load the matching "[Class].cpm.xml" file.
184: */
185: public void setClassMappings(String[] classMappings) {
186: this .classMappings = classMappings;
187: }
188:
189: /**
190: * Sets the mapping resolvers the resolved Compass mapping definitions.
191: */
192: public void setMappingResolvers(
193: InputStreamMappingResolver[] mappingResolvers) {
194: this .mappingResolvers = mappingResolvers;
195: }
196:
197: /**
198: * Sets a <code>DataSource</code> to be used when the index is stored within a database.
199: * The data source must be used with {@link org.compass.core.lucene.engine.store.jdbc.ExternalDataSourceProvider}
200: * for externally configured data sources (such is the case some of the time with spring). If set, Compass data source provider
201: * does not have to be set, since it will automatically default to <code>ExternalDataSourceProvider</code>. If the
202: * compass data source provider is set as a compass setting, it will be used.
203: * <p/>
204: * Note, that it will be automatically wrapped with Spring's <literal>TransactionAwareDataSourceProxy</literal> if not
205: * already wrapped by one.
206: * {@link org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy}.
207: * <p/>
208: * Also note that setting the data source is not enough to configure Compass to store the index
209: * within the database, the Compass connection string should also be set to <code>jdbc://</code>.
210: */
211: public void setDataSource(DataSource dataSource) {
212: this .dataSource = dataSource;
213: if (!(dataSource instanceof TransactionAwareDataSourceProxy)) {
214: this .dataSource = new TransactionAwareDataSourceProxy(
215: dataSource);
216: }
217: }
218:
219: /**
220: * Sets Spring <code>PlatformTransactionManager</code> to be used with compass. If using
221: * {@link org.compass.spring.transaction.SpringSyncTransactionFactory}, it must be set.
222: */
223: public void setTransactionManager(
224: PlatformTransactionManager transactionManager) {
225: this .transactionManager = transactionManager;
226: }
227:
228: /**
229: * Sets a map of global converters to be registered with compass. The map key will be
230: * the name that the converter will be registered against, and the value should be the
231: * Converter itself (natuarally configured using spring DI).
232: */
233: public void setConvertersByName(Map convertersByName) {
234: this .convertersByName = convertersByName;
235: }
236:
237: public void setCompassConfiguration(CompassConfiguration config) {
238: this .config = config;
239: }
240:
241: public void afterPropertiesSet() throws Exception {
242: CompassConfiguration config = this .config;
243: if (config == null) {
244: config = newConfiguration();
245: }
246:
247: if (this .configLocation != null) {
248: config.configure(this .configLocation.getURL());
249: }
250:
251: if (this .configLocations != null) {
252: for (int i = 0; i < configLocations.length; i++) {
253: config.configure(configLocations[i].getURL());
254: }
255: }
256:
257: if (this .compassSettings != null) {
258: config.getSettings().addSettings(this .compassSettings);
259: }
260:
261: if (resourceLocations != null) {
262: for (int i = 0; i < resourceLocations.length; i++) {
263: config.addInputStream(resourceLocations[i]
264: .getInputStream(), resourceLocations[i]
265: .getDescription());
266: }
267: }
268:
269: if (resourceJarLocations != null) {
270: for (int i = 0; i < resourceJarLocations.length; i++) {
271: config.addJar(resourceJarLocations[i].getFile());
272: }
273: }
274:
275: if (classMappings != null) {
276: for (int i = 0; i < classMappings.length; i++) {
277: config.addClass(ClassUtils.forName(classMappings[i],
278: getClassLoader()));
279: }
280: }
281:
282: if (resourceDirectoryLocations != null) {
283: for (int i = 0; i < resourceDirectoryLocations.length; i++) {
284: File file = resourceDirectoryLocations[i].getFile();
285: if (!file.isDirectory()) {
286: throw new IllegalArgumentException(
287: "Resource directory location ["
288: + this .resourceDirectoryLocations[i]
289: + "] does not denote a directory");
290: }
291: config.addDirectory(file);
292: }
293: }
294:
295: if (mappingResolvers != null) {
296: for (int i = 0; i < mappingResolvers.length; i++) {
297: config.addMappingResover(mappingResolvers[i]);
298: }
299: }
300:
301: if (dataSource != null) {
302: ExternalDataSourceProvider.setDataSource(dataSource);
303: if (config
304: .getSettings()
305: .getSetting(
306: LuceneEnvironment.JdbcStore.DataSourceProvider.CLASS) == null) {
307: config
308: .getSettings()
309: .setSetting(
310: LuceneEnvironment.JdbcStore.DataSourceProvider.CLASS,
311: ExternalDataSourceProvider.class
312: .getName());
313: }
314: }
315:
316: String compassTransactionFactory = config.getSettings()
317: .getSetting(CompassEnvironment.Transaction.FACTORY);
318: if (compassTransactionFactory == null
319: && transactionManager != null) {
320: // if the transaciton manager is set and a transcation factory is not set, default to the SpringSync one.
321: config.getSettings().setSetting(
322: CompassEnvironment.Transaction.FACTORY,
323: SpringSyncTransactionFactory.class.getName());
324: }
325: if (compassTransactionFactory != null
326: && compassTransactionFactory
327: .equals(SpringSyncTransactionFactory.class
328: .getName())) {
329: if (transactionManager == null) {
330: throw new IllegalArgumentException(
331: "When using SpringSyncTransactionFactory the transactionManager property must be set");
332: }
333: }
334: SpringSyncTransactionFactory
335: .setTransactionManager(transactionManager);
336:
337: if (convertersByName != null) {
338: for (Iterator it = convertersByName.keySet().iterator(); it
339: .hasNext();) {
340: String converterName = (String) it.next();
341: config
342: .registerConverter(converterName,
343: (Converter) convertersByName
344: .get(converterName));
345: }
346: }
347: if (config.getSettings().getSetting(CompassEnvironment.NAME) == null) {
348: config.getSettings().setSetting(CompassEnvironment.NAME,
349: beanName);
350: }
351:
352: if (config.getSettings().getSetting(
353: CompassEnvironment.CONNECTION) == null
354: && connection != null) {
355: config.getSettings().setSetting(
356: CompassEnvironment.CONNECTION,
357: connection.getFile().getAbsolutePath());
358: }
359:
360: if (postProcessor != null) {
361: postProcessor.process(config);
362: }
363: this .compass = newCompass(config);
364: this .compass = (Compass) Proxy.newProxyInstance(
365: SpringCompassInvocationHandler.class.getClassLoader(),
366: new Class[] { InternalCompass.class },
367: new SpringCompassInvocationHandler(this .compass));
368: }
369:
370: protected CompassConfiguration newConfiguration() {
371: return CompassConfigurationFactory.newConfiguration();
372: }
373:
374: protected Compass newCompass(CompassConfiguration config)
375: throws CompassException {
376: return config.buildCompass();
377: }
378:
379: public Object getObject() throws Exception {
380: return this .compass;
381: }
382:
383: public Class getObjectType() {
384: return (compass != null) ? compass.getClass() : Compass.class;
385: }
386:
387: public boolean isSingleton() {
388: return true;
389: }
390:
391: public void destroy() throws Exception {
392: this .compass.close();
393: }
394:
395: protected ClassLoader getClassLoader() {
396: return Thread.currentThread().getContextClassLoader();
397: }
398:
399: /**
400: * Invocation handler that handles close methods.
401: */
402: private class SpringCompassInvocationHandler implements
403: InvocationHandler {
404:
405: private static final String GET_TARGET_COMPASS_METHOD_NAME = "getTargetCompass";
406:
407: private static final String CLONE_METHOD = "clone";
408:
409: private Compass targetCompass;
410:
411: public SpringCompassInvocationHandler(Compass targetCompass) {
412: this .targetCompass = targetCompass;
413: }
414:
415: public Object invoke(Object proxy, Method method, Object[] args)
416: throws Throwable {
417: // Invocation on ConnectionProxy interface coming in...
418:
419: if (method.getName().equals(GET_TARGET_COMPASS_METHOD_NAME)) {
420: return compass;
421: }
422:
423: if (method.getName().equals(CLONE_METHOD)
424: && args.length == 1) {
425: if (dataSource != null) {
426: ExternalDataSourceProvider
427: .setDataSource(dataSource);
428: }
429:
430: SpringSyncTransactionFactory
431: .setTransactionManager(transactionManager);
432: }
433:
434: // Invoke method on target connection.
435: try {
436: return method.invoke(targetCompass, args);
437: } catch (InvocationTargetException ex) {
438: throw ex.getTargetException();
439: }
440: }
441: }
442:
443: }
|