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.core.lucene.engine;
018:
019: import org.apache.commons.logging.Log;
020: import org.apache.commons.logging.LogFactory;
021: import org.apache.lucene.analysis.Analyzer;
022: import org.compass.core.CompassTransaction.TransactionIsolation;
023: import org.compass.core.Resource;
024: import org.compass.core.config.CompassSettings;
025: import org.compass.core.config.RuntimeCompassSettings;
026: import org.compass.core.engine.SearchEngine;
027: import org.compass.core.engine.SearchEngineAnalyzerHelper;
028: import org.compass.core.engine.SearchEngineException;
029: import org.compass.core.engine.SearchEngineHits;
030: import org.compass.core.engine.SearchEngineInternalSearch;
031: import org.compass.core.engine.SearchEngineQuery;
032: import org.compass.core.engine.SearchEngineQueryBuilder;
033: import org.compass.core.engine.SearchEngineQueryFilterBuilder;
034: import org.compass.core.engine.SearchEngineTermFrequencies;
035: import org.compass.core.engine.event.SearchEngineEventManager;
036: import org.compass.core.lucene.engine.query.LuceneSearchEngineQueryBuilder;
037: import org.compass.core.lucene.engine.query.LuceneSearchEngineQueryFilterBuilder;
038: import org.compass.core.lucene.engine.transaction.LuceneSearchEngineTransaction;
039: import org.compass.core.lucene.engine.transaction.lucene.LuceneTransaction;
040: import org.compass.core.lucene.engine.transaction.readcommitted.ReadCommittedTransaction;
041: import org.compass.core.lucene.engine.transaction.serializable.SerializableTransaction;
042: import org.compass.core.lucene.util.LuceneUtils;
043: import org.compass.core.mapping.ResourceMapping;
044: import org.compass.core.spi.InternalResource;
045: import org.compass.core.spi.MultiResource;
046: import org.compass.core.spi.ResourceKey;
047: import org.compass.core.util.StringUtils;
048:
049: /**
050: * @author kimchy
051: */
052: public class LuceneSearchEngine implements SearchEngine {
053:
054: protected final static Log log = LogFactory
055: .getLog(LuceneSearchEngine.class);
056:
057: private static final int UNKNOWN = -1;
058:
059: private static final int STARTED = 0;
060:
061: private static final int COMMIT = 1;
062:
063: private static final int ROLLBACK = 2;
064:
065: private int transactionState;
066:
067: private LuceneSearchEngineTransaction transaction;
068:
069: private LuceneSearchEngineFactory searchEngineFactory;
070:
071: private SearchEngineEventManager eventManager = new SearchEngineEventManager();
072:
073: private boolean readOnly;
074:
075: private RuntimeCompassSettings runtimeSettings;
076:
077: public LuceneSearchEngine(RuntimeCompassSettings runtimeSettings,
078: LuceneSearchEngineFactory searchEngineFactory) {
079: this .runtimeSettings = runtimeSettings;
080: this .readOnly = true;
081: this .searchEngineFactory = searchEngineFactory;
082: this .transactionState = UNKNOWN;
083: eventManager.registerLifecycleListener(searchEngineFactory
084: .getEventManager());
085: searchEngineFactory.getLuceneIndexManager().getStore()
086: .registerEventListeners(this , eventManager);
087: }
088:
089: public SearchEngineQueryBuilder queryBuilder()
090: throws SearchEngineException {
091: return new LuceneSearchEngineQueryBuilder(this );
092: }
093:
094: public SearchEngineQueryFilterBuilder queryFilterBuilder()
095: throws SearchEngineException {
096: return new LuceneSearchEngineQueryFilterBuilder();
097: }
098:
099: public SearchEngineAnalyzerHelper analyzerHelper() {
100: return new LuceneSearchEngineAnalyzerHelper(this );
101: }
102:
103: public void begin() throws SearchEngineException {
104: this .readOnly = true;
105: if (transactionState == STARTED) {
106: throw new SearchEngineException(
107: "Transaction already started, why start it again?");
108: }
109:
110: Class transactionIsolationClass = searchEngineFactory
111: .getLuceneSettings().getTransactionIsolationClass();
112: if (transactionIsolationClass != null) {
113: transactionState = UNKNOWN;
114: try {
115: transaction = (LuceneSearchEngineTransaction) transactionIsolationClass
116: .newInstance();
117: } catch (Exception e) {
118: throw new SearchEngineException(
119: "Failed to create an instance for transaction ["
120: + transactionIsolationClass.getName()
121: + "]", e);
122: }
123: transaction.configure(this );
124: eventManager.beforeBeginTransaction();
125: transaction.begin();
126: eventManager.afterBeginTransaction();
127: transactionState = STARTED;
128: return;
129: }
130: TransactionIsolation transactionIsolation = searchEngineFactory
131: .getLuceneSettings().getTransactionIsolation();
132: begin(transactionIsolation);
133: }
134:
135: public void begin(TransactionIsolation transactionIsolation)
136: throws SearchEngineException {
137: if (transactionState == STARTED) {
138: throw new SearchEngineException(
139: "Transaction already started, why start it again?");
140: }
141: transactionState = UNKNOWN;
142: this .readOnly = true;
143: if (transactionIsolation == null) {
144: transactionIsolation = searchEngineFactory
145: .getLuceneSettings().getTransactionIsolation();
146: }
147: if (transactionIsolation == TransactionIsolation.READ_COMMITTED) {
148: transaction = new ReadCommittedTransaction();
149: } else if (transactionIsolation == TransactionIsolation.READ_ONLY_READ_COMMITTED) {
150: transaction = new ReadCommittedTransaction();
151: } else if (transactionIsolation == TransactionIsolation.BATCH_INSERT) {
152: transaction = new LuceneTransaction();
153: } else if (transactionIsolation == TransactionIsolation.LUCENE) {
154: transaction = new LuceneTransaction();
155: } else if (transactionIsolation == TransactionIsolation.SERIALIZABLE) {
156: transaction = new SerializableTransaction();
157: }
158: transaction.configure(this );
159: eventManager.beforeBeginTransaction();
160: transaction.begin();
161: eventManager.afterBeginTransaction();
162: transactionState = STARTED;
163: }
164:
165: public void verifyWithinTransaction() throws SearchEngineException {
166: if (transactionState != STARTED) {
167: throw new SearchEngineException(
168: "Search engine transaction not successfully started or already committed/rolledback");
169: }
170: }
171:
172: public boolean isWithinTransaction() throws SearchEngineException {
173: return transactionState == STARTED;
174: }
175:
176: public void prepare() throws SearchEngineException {
177: verifyWithinTransaction();
178: if (transaction != null) {
179: transaction.prepare();
180: }
181: eventManager.afterPrepare();
182: }
183:
184: public void commit(boolean onePhase) throws SearchEngineException {
185: verifyWithinTransaction();
186: if (transaction != null) {
187: transaction.commit(onePhase);
188: eventManager.afterCommit(onePhase);
189: }
190: transaction = null;
191: transactionState = COMMIT;
192: }
193:
194: public void rollback() throws SearchEngineException {
195: verifyWithinTransaction();
196: try {
197: if (transaction != null) {
198: try {
199: transaction.rollback();
200: } finally {
201: eventManager.afterRollback();
202: }
203: }
204: } finally {
205: transaction = null;
206: transactionState = ROLLBACK;
207: }
208: }
209:
210: public void flush() throws SearchEngineException {
211: verifyWithinTransaction();
212: if (transaction != null) {
213: transaction.flush();
214: }
215: }
216:
217: public boolean wasRolledBack() throws SearchEngineException {
218: return transactionState == ROLLBACK;
219: }
220:
221: public boolean wasCommitted() throws SearchEngineException {
222: return transactionState == COMMIT;
223: }
224:
225: public void close() throws SearchEngineException {
226: eventManager.close();
227: if (transactionState == STARTED) {
228: log
229: .warn("Transaction not committed/rolled backed, rolling back");
230: try {
231: rollback();
232: } catch (Exception e) {
233: log.warn("Failed to rollback transcation, ignoring", e);
234: }
235: }
236: eventManager = null;
237: }
238:
239: public void delete(Resource resource) throws SearchEngineException {
240: verifyWithinTransaction();
241: readOnly = false;
242: if (resource instanceof MultiResource) {
243: MultiResource multiResource = (MultiResource) resource;
244: for (int i = 0; i < multiResource.size(); i++) {
245: delete(((InternalResource) multiResource.resource(i))
246: .resourceKey());
247: }
248: } else {
249: delete(((InternalResource) resource).resourceKey());
250: }
251: }
252:
253: private void delete(ResourceKey resourceKey)
254: throws SearchEngineException {
255: if (resourceKey.getIds().length == 0) {
256: throw new SearchEngineException(
257: "Cannot delete a resource with no ids");
258: }
259: transaction.delete(resourceKey);
260: if (log.isDebugEnabled()) {
261: log.debug("RESOURCE DELETE {"
262: + resourceKey.getAlias()
263: + "} "
264: + StringUtils
265: .arrayToCommaDelimitedString(resourceKey
266: .getIds()));
267: }
268: }
269:
270: public void save(Resource resource) throws SearchEngineException {
271: readOnly = false;
272: createOrUpdate(resource, true);
273: }
274:
275: public void create(Resource resource) throws SearchEngineException {
276: readOnly = false;
277: createOrUpdate(resource, false);
278: }
279:
280: private void createOrUpdate(final Resource resource, boolean update)
281: throws SearchEngineException {
282: verifyWithinTransaction();
283: readOnly = false;
284: String alias = resource.getAlias();
285: ResourceMapping resourceMapping = searchEngineFactory
286: .getMapping().getRootMappingByAlias(alias);
287: if (resourceMapping == null) {
288: throw new SearchEngineException(
289: "Failed to find mapping for alias [" + alias + "]");
290: }
291: if (resource instanceof MultiResource) {
292: MultiResource multiResource = (MultiResource) resource;
293: for (int i = 0; i < multiResource.size(); i++) {
294: InternalResource resource1 = (InternalResource) multiResource
295: .resource(i);
296: Analyzer analyzer = enhanceResource(resourceMapping,
297: resource1);
298: if (update) {
299: transaction.update(resource1, analyzer);
300: if (log.isDebugEnabled()) {
301: log.debug("RESOURCE SAVE " + resource1);
302: }
303: } else {
304: transaction.create(resource1, analyzer);
305: if (log.isDebugEnabled()) {
306: log.debug("RESOURCE CREATE " + resource1);
307: }
308: }
309: }
310: } else {
311: InternalResource resource1 = (InternalResource) resource;
312: Analyzer analyzer = enhanceResource(resourceMapping,
313: resource1);
314: if (update) {
315: transaction.create(resource1, analyzer);
316: if (log.isDebugEnabled()) {
317: log.debug("RESOURCE SAVE " + resource1);
318: }
319: } else {
320: transaction.create(resource1, analyzer);
321: if (log.isDebugEnabled()) {
322: log.debug("RESOURCE CREATE " + resource1);
323: }
324: }
325: }
326: }
327:
328: private Analyzer enhanceResource(ResourceMapping resourceMapping,
329: InternalResource resource) throws SearchEngineException {
330: LuceneUtils.addExtendedProeprty(resource, resourceMapping,
331: searchEngineFactory);
332: LuceneUtils.applyBoostIfNeeded(resource, searchEngineFactory);
333: Analyzer analyzer = searchEngineFactory.getAnalyzerManager()
334: .getAnalyzerByResource(resource);
335: return LuceneUtils.addAllProperty(resource, analyzer, resource
336: .resourceKey().getResourceMapping(), this );
337: }
338:
339: public Resource get(Resource idResource)
340: throws SearchEngineException {
341: verifyWithinTransaction();
342: ResourceKey resourceKey = ((InternalResource) idResource)
343: .resourceKey();
344: if (resourceKey.getIds().length == 0) {
345: throw new SearchEngineException(
346: "Cannot load a resource with no ids");
347: }
348: Resource[] result = transaction.get(resourceKey);
349: if (result.length == 0) {
350: return null;
351: } else if (result.length > 1) {
352: log
353: .warn("Found several matches in get/load operation for resource alias ["
354: + resourceKey.getAlias()
355: + "] and ids ["
356: + StringUtils
357: .arrayToCommaDelimitedString(resourceKey
358: .getIds()) + "]");
359: return result[result.length - 1];
360: }
361: return result[0];
362: }
363:
364: public Resource load(Resource idResource)
365: throws SearchEngineException {
366: String alias = idResource.getAlias();
367: Resource resource = get(idResource);
368: if (resource == null) {
369: throw new SearchEngineException(
370: "Failed to find resource with alias ["
371: + alias
372: + "] and ids ["
373: + StringUtils
374: .arrayToCommaDelimitedString(idResource
375: .getIds()) + "]");
376: }
377: return resource;
378: }
379:
380: public SearchEngineHits find(SearchEngineQuery query)
381: throws SearchEngineException {
382: verifyWithinTransaction();
383: SearchEngineHits hits = transaction.find(query);
384: if (log.isDebugEnabled()) {
385: log.debug("RESOURCE QUERY [" + query + "] HITS ["
386: + hits.getLength() + "]");
387: }
388: return hits;
389: }
390:
391: public SearchEngineTermFrequencies termFreq(String[] propertyNames,
392: int size, SearchEngineInternalSearch internalSearch) {
393: return new LuceneSearchEngineTermFrequencies(propertyNames,
394: size, (LuceneSearchEngineInternalSearch) internalSearch);
395: }
396:
397: public SearchEngineInternalSearch internalSearch(
398: String[] subIndexes, String[] aliases)
399: throws SearchEngineException {
400: verifyWithinTransaction();
401: return transaction.internalSearch(subIndexes, aliases);
402: }
403:
404: public LuceneSearchEngineFactory getSearchEngineFactory() {
405: return searchEngineFactory;
406: }
407:
408: public CompassSettings getSettings() {
409: return runtimeSettings;
410: }
411:
412: public boolean isReadOnly() {
413: return this.readOnly;
414: }
415: }
|