KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > alfresco > repo > search > impl > lucene > LuceneIndexerAndSearcherFactory


1 /*
2  * Copyright (C) 2005 Alfresco, Inc.
3  *
4  * Licensed under the Mozilla Public License version 1.1
5  * with a permitted attribution clause. You may obtain a
6  * copy of the License at
7  *
8  * http://www.alfresco.org/legal/license.txt
9  *
10  * Unless required by applicable law or agreed to in writing,
11  * software distributed under the License is distributed on an
12  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
13  * either express or implied. See the License for the specific
14  * language governing permissions and limitations under the
15  * License.
16  */

17 package org.alfresco.repo.search.impl.lucene;
18
19 import java.io.File JavaDoc;
20 import java.util.ArrayList JavaDoc;
21 import java.util.HashMap JavaDoc;
22 import java.util.List JavaDoc;
23 import java.util.Map JavaDoc;
24
25 import javax.transaction.RollbackException JavaDoc;
26 import javax.transaction.SystemException JavaDoc;
27 import javax.transaction.Transaction JavaDoc;
28 import javax.transaction.xa.XAException JavaDoc;
29 import javax.transaction.xa.XAResource JavaDoc;
30 import javax.transaction.xa.Xid JavaDoc;
31
32 import org.alfresco.error.AlfrescoRuntimeException;
33 import org.alfresco.repo.search.IndexerException;
34 import org.alfresco.repo.search.QueryRegisterComponent;
35 import org.alfresco.repo.search.SearcherException;
36 import org.alfresco.repo.search.impl.lucene.fts.FullTextSearchIndexer;
37 import org.alfresco.repo.search.transaction.LuceneIndexLock;
38 import org.alfresco.repo.search.transaction.SimpleTransaction;
39 import org.alfresco.repo.search.transaction.SimpleTransactionManager;
40 import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
41 import org.alfresco.repo.transaction.TransactionUtil;
42 import org.alfresco.repo.transaction.TransactionUtil.TransactionWork;
43 import org.alfresco.service.cmr.dictionary.DictionaryService;
44 import org.alfresco.service.cmr.repository.ContentService;
45 import org.alfresco.service.cmr.repository.NodeService;
46 import org.alfresco.service.cmr.repository.StoreRef;
47 import org.alfresco.service.namespace.NamespaceService;
48 import org.alfresco.service.transaction.TransactionService;
49 import org.alfresco.util.GUID;
50 import org.apache.commons.io.FileUtils;
51 import org.apache.commons.logging.Log;
52 import org.apache.commons.logging.LogFactory;
53 import org.apache.lucene.index.IndexWriter;
54 import org.apache.lucene.search.BooleanQuery;
55 import org.apache.lucene.store.Lock;
56 import org.quartz.Job;
57 import org.quartz.JobDataMap;
58 import org.quartz.JobExecutionContext;
59 import org.quartz.JobExecutionException;
60
61 /**
62  * This class is resource manager LuceneIndexers and LuceneSearchers.
63  *
64  * It supports two phase commit inside XA transactions and outside transactions
65  * it provides thread local transaction support.
66  *
67  * TODO: Provide pluggable support for a transaction manager TODO: Integrate
68  * with Spring transactions
69  *
70  * @author andyh
71  *
72  */

73
74 public class LuceneIndexerAndSearcherFactory implements LuceneIndexerAndSearcher, XAResource JavaDoc
75 {
76     private DictionaryService dictionaryService;
77
78     private NamespaceService nameSpaceService;
79
80     private int queryMaxClauses;
81
82     private int indexerBatchSize;
83
84     private int indexerMinMergeDocs;
85
86     private int indexerMergeFactor;
87
88     private int indexerMaxMergeDocs;
89
90     private String JavaDoc lockDirectory;
91
92     /**
93      * A map of active global transactions . It contains all the indexers a
94      * transaction has used, with at most one indexer for each store within a
95      * transaction
96      */

97
98     private static Map JavaDoc<Xid JavaDoc, Map JavaDoc<StoreRef, LuceneIndexer>> activeIndexersInGlobalTx = new HashMap JavaDoc<Xid JavaDoc, Map JavaDoc<StoreRef, LuceneIndexer>>();
99
100     /**
101      * Suspended global transactions.
102      */

103     private static Map JavaDoc<Xid JavaDoc, Map JavaDoc<StoreRef, LuceneIndexer>> suspendedIndexersInGlobalTx = new HashMap JavaDoc<Xid JavaDoc, Map JavaDoc<StoreRef, LuceneIndexer>>();
104
105     /**
106      * Thread local indexers - used outside a global transaction
107      */

108
109     private static ThreadLocal JavaDoc<Map JavaDoc<StoreRef, LuceneIndexer>> threadLocalIndexers = new ThreadLocal JavaDoc<Map JavaDoc<StoreRef, LuceneIndexer>>();
110
111     /**
112      * The dafault timeout for transactions TODO: Respect this
113      */

114
115     private int timeout = DEFAULT_TIMEOUT;
116
117     /**
118      * Default time out value set to 10 minutes.
119      */

120     private static final int DEFAULT_TIMEOUT = 600000;
121
122     /**
123      * The node service we use to get information about nodes
124      */

125
126     private NodeService nodeService;
127
128     private LuceneIndexLock luceneIndexLock;
129
130     private FullTextSearchIndexer luceneFullTextSearchIndexer;
131
132     private String JavaDoc indexRootLocation;
133
134     private ContentService contentService;
135
136     private QueryRegisterComponent queryRegister;
137
138     private int indexerMaxFieldLength;
139
140     /**
141      * Private constructor for the singleton TODO: FIt in with IOC
142      */

143
144     public LuceneIndexerAndSearcherFactory()
145     {
146         super();
147     }
148
149     /**
150      * Setter for getting the node service via IOC Used in the Spring container
151      *
152      * @param nodeService
153      */

154
155     public void setNodeService(NodeService nodeService)
156     {
157         this.nodeService = nodeService;
158     }
159
160     public void setDictionaryService(DictionaryService dictionaryService)
161     {
162         this.dictionaryService = dictionaryService;
163     }
164
165     public void setNameSpaceService(NamespaceService nameSpaceService)
166     {
167         this.nameSpaceService = nameSpaceService;
168     }
169
170     public void setLuceneIndexLock(LuceneIndexLock luceneIndexLock)
171     {
172         this.luceneIndexLock = luceneIndexLock;
173     }
174
175     public void setLuceneFullTextSearchIndexer(FullTextSearchIndexer luceneFullTextSearchIndexer)
176     {
177         this.luceneFullTextSearchIndexer = luceneFullTextSearchIndexer;
178     }
179
180     public void setIndexRootLocation(String JavaDoc indexRootLocation)
181     {
182         this.indexRootLocation = indexRootLocation;
183     }
184
185     public void setQueryRegister(QueryRegisterComponent queryRegister)
186     {
187         this.queryRegister = queryRegister;
188     }
189
190     /**
191      * Check if we are in a global transactoin according to the transaction
192      * manager
193      *
194      * @return
195      */

196
197     private boolean inGlobalTransaction()
198     {
199         try
200         {
201             return SimpleTransactionManager.getInstance().getTransaction() != null;
202         }
203         catch (SystemException JavaDoc e)
204         {
205             return false;
206         }
207     }
208
209     /**
210      * Get the local transaction - may be null oif we are outside a transaction.
211      *
212      * @return
213      * @throws IndexerException
214      */

215     private SimpleTransaction getTransaction() throws IndexerException
216     {
217         try
218         {
219             return SimpleTransactionManager.getInstance().getTransaction();
220         }
221         catch (SystemException JavaDoc e)
222         {
223             throw new IndexerException("Failed to get transaction", e);
224         }
225     }
226
227     /**
228      * Get an indexer for the store to use in the current transaction for this
229      * thread of control.
230      *
231      * @param storeRef -
232      * the id of the store
233      */

234     public LuceneIndexer getIndexer(StoreRef storeRef) throws IndexerException
235     {
236         // register to receive txn callbacks
237
// TODO: make this conditional on whether the XA stuff is being used
238
// directly on not
239
AlfrescoTransactionSupport.bindLucene(this);
240
241         if (inGlobalTransaction())
242         {
243             SimpleTransaction tx = getTransaction();
244             // Only find indexers in the active list
245
Map JavaDoc<StoreRef, LuceneIndexer> indexers = activeIndexersInGlobalTx.get(tx);
246             if (indexers == null)
247             {
248                 if (suspendedIndexersInGlobalTx.containsKey(tx))
249                 {
250                     throw new IndexerException("Trying to obtain an index for a suspended transaction.");
251                 }
252                 indexers = new HashMap JavaDoc<StoreRef, LuceneIndexer>();
253                 activeIndexersInGlobalTx.put(tx, indexers);
254                 try
255                 {
256                     tx.enlistResource(this);
257                 }
258                 // TODO: what to do in each case?
259
catch (IllegalStateException JavaDoc e)
260                 {
261                     throw new IndexerException("", e);
262                 }
263                 catch (RollbackException JavaDoc e)
264                 {
265                     throw new IndexerException("", e);
266                 }
267                 catch (SystemException JavaDoc e)
268                 {
269                     throw new IndexerException("", e);
270                 }
271             }
272             LuceneIndexer indexer = indexers.get(storeRef);
273             if (indexer == null)
274             {
275                 indexer = createIndexer(storeRef, getTransactionId(tx, storeRef));
276                 indexers.put(storeRef, indexer);
277             }
278             return indexer;
279         }
280         else
281         // A thread local transaction
282
{
283             return getThreadLocalIndexer(storeRef);
284         }
285
286     }
287
288     private LuceneIndexer getThreadLocalIndexer(StoreRef storeRef)
289     {
290         Map JavaDoc<StoreRef, LuceneIndexer> indexers = threadLocalIndexers.get();
291         if (indexers == null)
292         {
293             indexers = new HashMap JavaDoc<StoreRef, LuceneIndexer>();
294             threadLocalIndexers.set(indexers);
295         }
296         LuceneIndexer indexer = indexers.get(storeRef);
297         if (indexer == null)
298         {
299             indexer = createIndexer(storeRef, GUID.generate());
300             indexers.put(storeRef, indexer);
301         }
302         return indexer;
303     }
304
305     /**
306      * Get the transaction identifier uised to store it in the transaction map.
307      *
308      * @param tx
309      * @return
310      */

311     private static String JavaDoc getTransactionId(Transaction JavaDoc tx, StoreRef storeRef)
312     {
313         if (tx instanceof SimpleTransaction)
314         {
315             SimpleTransaction simpleTx = (SimpleTransaction) tx;
316             return simpleTx.getGUID();
317         }
318         else
319         {
320             Map JavaDoc<StoreRef, LuceneIndexer> indexers = threadLocalIndexers.get();
321             if (indexers != null)
322             {
323                 LuceneIndexer indexer = indexers.get(storeRef);
324                 if (indexer != null)
325                 {
326                     return indexer.getDeltaId();
327                 }
328             }
329             return null;
330         }
331     }
332
333     /**
334      * Encapsulate creating an indexer
335      *
336      * @param storeRef
337      * @param deltaId
338      * @return
339      */

340     private LuceneIndexerImpl createIndexer(StoreRef storeRef, String JavaDoc deltaId)
341     {
342         LuceneIndexerImpl indexer = LuceneIndexerImpl.getUpdateIndexer(storeRef, deltaId, this);
343         indexer.setNodeService(nodeService);
344         indexer.setDictionaryService(dictionaryService);
345         indexer.setLuceneIndexLock(luceneIndexLock);
346         indexer.setLuceneFullTextSearchIndexer(luceneFullTextSearchIndexer);
347         indexer.setContentService(contentService);
348         return indexer;
349     }
350
351     /**
352      * Encapsulate creating a searcher over the main index
353      */

354     public LuceneSearcher getSearcher(StoreRef storeRef, boolean searchDelta) throws SearcherException
355     {
356         String JavaDoc deltaId = null;
357         LuceneIndexer indexer = null;
358         if (searchDelta)
359         {
360             deltaId = getTransactionId(getTransaction(), storeRef);
361             if (deltaId != null)
362             {
363                 indexer = getIndexer(storeRef);
364             }
365         }
366         LuceneSearcher searcher = getSearcher(storeRef, indexer);
367         return searcher;
368     }
369
370     /**
371      * Get a searcher over the index and the current delta
372      *
373      * @param storeRef
374      * @param deltaId
375      * @return
376      * @throws SearcherException
377      */

378     private LuceneSearcher getSearcher(StoreRef storeRef, LuceneIndexer indexer) throws SearcherException
379     {
380         LuceneSearcherImpl searcher = LuceneSearcherImpl.getSearcher(storeRef, indexer, this);
381         searcher.setNamespacePrefixResolver(nameSpaceService);
382         searcher.setLuceneIndexLock(luceneIndexLock);
383         searcher.setNodeService(nodeService);
384         searcher.setDictionaryService(dictionaryService);
385         searcher.setQueryRegister(queryRegister);
386         return searcher;
387     }
388
389     /*
390      * XAResource implementation
391      */

392
393     public void commit(Xid JavaDoc xid, boolean onePhase) throws XAException JavaDoc
394     {
395         try
396         {
397             // TODO: Should be remembering overall state
398
// TODO: Keep track of prepare responses
399
Map JavaDoc<StoreRef, LuceneIndexer> indexers = activeIndexersInGlobalTx.get(xid);
400             if (indexers == null)
401             {
402                 if (suspendedIndexersInGlobalTx.containsKey(xid))
403                 {
404                     throw new XAException JavaDoc("Trying to commit indexes for a suspended transaction.");
405                 }
406                 else
407                 {
408                     // nothing to do
409
return;
410                 }
411             }
412
413             if (onePhase)
414             {
415                 if (indexers.size() == 0)
416                 {
417                     return;
418                 }
419                 else if (indexers.size() == 1)
420                 {
421                     for (LuceneIndexer indexer : indexers.values())
422                     {
423                         indexer.commit();
424                     }
425                     return;
426                 }
427                 else
428                 {
429                     throw new XAException JavaDoc("Trying to do one phase commit on more than one index");
430                 }
431             }
432             else
433             // two phase
434
{
435                 for (LuceneIndexer indexer : indexers.values())
436                 {
437                     indexer.commit();
438                 }
439                 return;
440             }
441         } finally
442         {
443             activeIndexersInGlobalTx.remove(xid);
444         }
445     }
446
447     public void end(Xid JavaDoc xid, int flag) throws XAException JavaDoc
448     {
449         Map JavaDoc<StoreRef, LuceneIndexer> indexers = activeIndexersInGlobalTx.get(xid);
450         if (indexers == null)
451         {
452             if (suspendedIndexersInGlobalTx.containsKey(xid))
453             {
454                 throw new XAException JavaDoc("Trying to commit indexes for a suspended transaction.");
455             }
456             else
457             {
458                 // nothing to do
459
return;
460             }
461         }
462         if (flag == XAResource.TMSUSPEND)
463         {
464             activeIndexersInGlobalTx.remove(xid);
465             suspendedIndexersInGlobalTx.put(xid, indexers);
466         }
467         else if (flag == TMFAIL)
468         {
469             activeIndexersInGlobalTx.remove(xid);
470             suspendedIndexersInGlobalTx.remove(xid);
471         }
472         else if (flag == TMSUCCESS)
473         {
474             activeIndexersInGlobalTx.remove(xid);
475         }
476     }
477
478     public void forget(Xid JavaDoc xid) throws XAException JavaDoc
479     {
480         activeIndexersInGlobalTx.remove(xid);
481         suspendedIndexersInGlobalTx.remove(xid);
482     }
483
484     public int getTransactionTimeout() throws XAException JavaDoc
485     {
486         return timeout;
487     }
488
489     public boolean isSameRM(XAResource JavaDoc xar) throws XAException JavaDoc
490     {
491         return (xar instanceof LuceneIndexerAndSearcherFactory);
492     }
493
494     public int prepare(Xid JavaDoc xid) throws XAException JavaDoc
495     {
496         // TODO: Track state OK, ReadOnly, Exception (=> rolled back?)
497
Map JavaDoc<StoreRef, LuceneIndexer> indexers = activeIndexersInGlobalTx.get(xid);
498         if (indexers == null)
499         {
500             if (suspendedIndexersInGlobalTx.containsKey(xid))
501             {
502                 throw new XAException JavaDoc("Trying to commit indexes for a suspended transaction.");
503             }
504             else
505             {
506                 // nothing to do
507
return XAResource.XA_OK;
508             }
509         }
510         boolean isPrepared = true;
511         boolean isModified = false;
512         for (LuceneIndexer indexer : indexers.values())
513         {
514             try
515             {
516                 isModified |= indexer.isModified();
517                 indexer.prepare();
518             }
519             catch (IndexerException e)
520             {
521                 isPrepared = false;
522             }
523         }
524         if (isPrepared)
525         {
526             if (isModified)
527             {
528                 return XAResource.XA_OK;
529             }
530             else
531             {
532                 return XAResource.XA_RDONLY;
533             }
534         }
535         else
536         {
537             throw new XAException JavaDoc("Failed to prepare: requires rollback");
538         }
539     }
540
541     public Xid JavaDoc[] recover(int arg0) throws XAException JavaDoc
542     {
543         // We can not rely on being able to recover at the moment
544
// Avoiding for performance benefits at the moment
545
// Assume roll back and no recovery - in the worst case we get an unused
546
// delta
547
// This should be there to avoid recovery of partial commits.
548
// It is difficult to see how we can mandate the same conditions.
549
return new Xid JavaDoc[0];
550     }
551
552     public void rollback(Xid JavaDoc xid) throws XAException JavaDoc
553     {
554         // TODO: What to do if all do not roll back?
555
try
556         {
557             Map JavaDoc<StoreRef, LuceneIndexer> indexers = activeIndexersInGlobalTx.get(xid);
558             if (indexers == null)
559             {
560                 if (suspendedIndexersInGlobalTx.containsKey(xid))
561                 {
562                     throw new XAException JavaDoc("Trying to commit indexes for a suspended transaction.");
563                 }
564                 else
565                 {
566                     // nothing to do
567
return;
568                 }
569             }
570             for (LuceneIndexer indexer : indexers.values())
571             {
572                 indexer.rollback();
573             }
574         } finally
575         {
576             activeIndexersInGlobalTx.remove(xid);
577         }
578     }
579
580     public boolean setTransactionTimeout(int timeout) throws XAException JavaDoc
581     {
582         this.timeout = timeout;
583         return true;
584     }
585
586     public void start(Xid JavaDoc xid, int flag) throws XAException JavaDoc
587     {
588         Map JavaDoc<StoreRef, LuceneIndexer> active = activeIndexersInGlobalTx.get(xid);
589         Map JavaDoc<StoreRef, LuceneIndexer> suspended = suspendedIndexersInGlobalTx.get(xid);
590         if (flag == XAResource.TMJOIN)
591         {
592             // must be active
593
if ((active != null) && (suspended == null))
594             {
595                 return;
596             }
597             else
598             {
599                 throw new XAException JavaDoc("Trying to rejoin transaction in an invalid state");
600             }
601
602         }
603         else if (flag == XAResource.TMRESUME)
604         {
605             // must be suspended
606
if ((active == null) && (suspended != null))
607             {
608                 suspendedIndexersInGlobalTx.remove(xid);
609                 activeIndexersInGlobalTx.put(xid, suspended);
610                 return;
611             }
612             else
613             {
614                 throw new XAException JavaDoc("Trying to rejoin transaction in an invalid state");
615             }
616
617         }
618         else if (flag == XAResource.TMNOFLAGS)
619         {
620             if ((active == null) && (suspended == null))
621             {
622                 return;
623             }
624             else
625             {
626                 throw new XAException JavaDoc("Trying to start an existing or suspended transaction");
627             }
628         }
629         else
630         {
631             throw new XAException JavaDoc("Unkown flags for start " + flag);
632         }
633
634     }
635
636     /*
637      * Thread local support for transactions
638      */

639
640     /**
641      * Commit the transaction
642      */

643
644     public void commit() throws IndexerException
645     {
646         try
647         {
648             Map JavaDoc<StoreRef, LuceneIndexer> indexers = threadLocalIndexers.get();
649             if (indexers != null)
650             {
651                 for (LuceneIndexer indexer : indexers.values())
652                 {
653                     try
654                     {
655                         indexer.commit();
656                     }
657                     catch (IndexerException e)
658                     {
659                         rollback();
660                         throw e;
661                     }
662                 }
663             }
664         } finally
665         {
666             if (threadLocalIndexers.get() != null)
667             {
668                 threadLocalIndexers.get().clear();
669                 threadLocalIndexers.set(null);
670             }
671         }
672     }
673
674     /**
675      * Prepare the transaction TODO: Store prepare results
676      *
677      * @return
678      */

679     public int prepare() throws IndexerException
680     {
681         boolean isPrepared = true;
682         boolean isModified = false;
683         Map JavaDoc<StoreRef, LuceneIndexer> indexers = threadLocalIndexers.get();
684         if (indexers != null)
685         {
686             for (LuceneIndexer indexer : indexers.values())
687             {
688                 try
689                 {
690                     isModified |= indexer.isModified();
691                     indexer.prepare();
692                 }
693                 catch (IndexerException e)
694                 {
695                     isPrepared = false;
696                     throw new IndexerException("Failed to prepare: requires rollback", e);
697                 }
698             }
699         }
700         if (isPrepared)
701         {
702             if (isModified)
703             {
704                 return XAResource.XA_OK;
705             }
706             else
707             {
708                 return XAResource.XA_RDONLY;
709             }
710         }
711         else
712         {
713             throw new IndexerException("Failed to prepare: requires rollback");
714         }
715     }
716
717     /**
718      * Roll back the transaction
719      */

720     public void rollback()
721     {
722         Map JavaDoc<StoreRef, LuceneIndexer> indexers = threadLocalIndexers.get();
723
724         if (indexers != null)
725         {
726             for (LuceneIndexer indexer : indexers.values())
727             {
728                 try
729                 {
730                     indexer.rollback();
731                 }
732                 catch (IndexerException e)
733                 {
734
735                 }
736             }
737         }
738
739         if (threadLocalIndexers.get() != null)
740         {
741             threadLocalIndexers.get().clear();
742             threadLocalIndexers.set(null);
743         }
744
745     }
746
747     public void flush()
748     {
749         // TODO: Needs fixing if we expose the indexer in JTA
750
Map JavaDoc<StoreRef, LuceneIndexer> indexers = threadLocalIndexers.get();
751
752         if (indexers != null)
753         {
754             for (LuceneIndexer indexer : indexers.values())
755             {
756                 indexer.flushPending();
757             }
758         }
759     }
760
761     public void setContentService(ContentService contentService)
762     {
763         this.contentService = contentService;
764     }
765
766     public String JavaDoc getIndexRootLocation()
767     {
768         return indexRootLocation;
769     }
770
771     public int getIndexerBatchSize()
772     {
773         return indexerBatchSize;
774     }
775
776     public void setIndexerBatchSize(int indexerBatchSize)
777     {
778         this.indexerBatchSize = indexerBatchSize;
779     }
780
781     public int getIndexerMaxMergeDocs()
782     {
783         return indexerMaxMergeDocs;
784     }
785
786     public void setIndexerMaxMergeDocs(int indexerMaxMergeDocs)
787     {
788         this.indexerMaxMergeDocs = indexerMaxMergeDocs;
789     }
790
791     public int getIndexerMergeFactor()
792     {
793         return indexerMergeFactor;
794     }
795
796     public void setIndexerMergeFactor(int indexerMergeFactor)
797     {
798         this.indexerMergeFactor = indexerMergeFactor;
799     }
800
801     public int getIndexerMinMergeDocs()
802     {
803         return indexerMinMergeDocs;
804     }
805
806     public void setIndexerMinMergeDocs(int indexerMinMergeDocs)
807     {
808         this.indexerMinMergeDocs = indexerMinMergeDocs;
809     }
810
811     public String JavaDoc getLockDirectory()
812     {
813         return lockDirectory;
814     }
815
816     public void setLockDirectory(String JavaDoc lockDirectory)
817     {
818         this.lockDirectory = lockDirectory;
819         // Set the lucene lock file via System property
820
// org.apache.lucene.lockdir
821
System.setProperty("org.apache.lucene.lockdir", lockDirectory);
822         // Make sure the lock directory exists
823
File JavaDoc lockDir = new File JavaDoc(lockDirectory);
824         if (!lockDir.exists())
825         {
826             lockDir.mkdirs();
827         }
828         // clean out any existing locks when we start up
829

830         File JavaDoc[] children = lockDir.listFiles();
831         if (children != null)
832         {
833             for (int i = 0; i < children.length; i++)
834             {
835                 File JavaDoc child = children[i];
836                 if (child.isFile())
837                 {
838                     if (child.exists() && !child.delete() && child.exists())
839                     {
840                         throw new IllegalStateException JavaDoc("Failed to delete " + child);
841                     }
842                 }
843             }
844         }
845     }
846
847     public int getQueryMaxClauses()
848     {
849         return queryMaxClauses;
850     }
851
852     public void setQueryMaxClauses(int queryMaxClauses)
853     {
854         this.queryMaxClauses = queryMaxClauses;
855         BooleanQuery.setMaxClauseCount(this.queryMaxClauses);
856     }
857
858     public void setWriteLockTimeout(long timeout)
859     {
860         IndexWriter.WRITE_LOCK_TIMEOUT = timeout;
861     }
862     
863     public void setCommitLockTimeout(long timeout)
864     {
865         IndexWriter.COMMIT_LOCK_TIMEOUT = timeout;
866     }
867     
868     public void setLockPollInterval(long time)
869     {
870         Lock.LOCK_POLL_INTERVAL = time;
871     }
872     
873     public int getIndexerMaxFieldLength()
874     {
875         return indexerMaxFieldLength;
876     }
877
878     public void setIndexerMaxFieldLength(int indexerMaxFieldLength)
879     {
880         this.indexerMaxFieldLength = indexerMaxFieldLength;
881         System.setProperty("org.apache.lucene.maxFieldLength", "" + indexerMaxFieldLength);
882     }
883     
884     /**
885      * This component is able to <i>safely</i> perform backups of the Lucene indexes while
886      * the server is running.
887      * <p>
888      * It can be run directly by calling the {@link #backup() } method, but the convenience
889      * {@link LuceneIndexBackupJob} can be used to call it as well.
890      *
891      * @author Derek Hulley
892      */

893     public static class LuceneIndexBackupComponent
894     {
895         private static Log logger = LogFactory.getLog(LuceneIndexerAndSearcherFactory.class);
896         
897         private TransactionService transactionService;
898         private LuceneIndexerAndSearcherFactory factory;
899         private NodeService nodeService;
900         private String JavaDoc targetLocation;
901         
902         public LuceneIndexBackupComponent()
903         {
904         }
905
906         /**
907          * Provides transactions in which to perform the work
908          *
909          * @param transactionService
910          */

911         public void setTransactionService(TransactionService transactionService)
912         {
913             this.transactionService = transactionService;
914         }
915
916         /**
917          * Set the Lucene index factory that will be used to control the index locks
918          *
919          * @param factory the index factory
920          */

921         public void setFactory(LuceneIndexerAndSearcherFactory factory)
922         {
923             this.factory = factory;
924         }
925
926         /**
927          * Used to retrieve the stores
928          *
929          * @param nodeService the node service
930          */

931         public void setNodeService(NodeService nodeService)
932         {
933             this.nodeService = nodeService;
934         }
935
936         /**
937          * Set the directory to which the backup will be copied
938          *
939          * @param targetLocation the backup directory
940          */

941         public void setTargetLocation(String JavaDoc targetLocation)
942         {
943             this.targetLocation = targetLocation;
944         }
945         
946         /**
947          * Backup the Lucene indexes
948          */

949         public void backup()
950         {
951             TransactionWork<Object JavaDoc> backupWork = new TransactionWork<Object JavaDoc>()
952             {
953                 public Object JavaDoc doWork() throws Exception JavaDoc
954                 {
955                     backupImpl();
956                     return null;
957                 }
958             };
959             TransactionUtil.executeInUserTransaction(transactionService, backupWork);
960         }
961
962         private void backupImpl()
963         {
964             // create the location to copy to
965
File JavaDoc targetDir = new File JavaDoc(targetLocation);
966             if (targetDir.exists() && !targetDir.isDirectory())
967             {
968                 throw new AlfrescoRuntimeException("Target location is a file and not a directory: " + targetDir);
969             }
970             File JavaDoc targetParentDir = targetDir.getParentFile();
971             if (targetParentDir == null)
972             {
973                 throw new AlfrescoRuntimeException("Target location may not be a root directory: " + targetDir);
974             }
975             File JavaDoc tempDir = new File JavaDoc(targetParentDir, "indexbackup_temp");
976
977             // get all the available stores
978
List JavaDoc<StoreRef> storeRefs = nodeService.getStores();
979             
980             // lock all the stores
981
List JavaDoc<StoreRef> lockedStores = new ArrayList JavaDoc<StoreRef>(storeRefs.size());
982             try
983             {
984                 for (StoreRef storeRef : storeRefs)
985                 {
986                     factory.luceneIndexLock.getWriteLock(storeRef);
987                     lockedStores.add(storeRef);
988                 }
989                 File JavaDoc indexRootDir = new File JavaDoc(factory.indexRootLocation);
990                 // perform the copy
991
backupDirectory(indexRootDir, tempDir, targetDir);
992             }
993             catch (Throwable JavaDoc e)
994             {
995                 throw new AlfrescoRuntimeException("Failed to copy Lucene index root: \n" +
996                         " Index root: " + factory.indexRootLocation + "\n" +
997                         " Target: " + targetDir,
998                         e);
999             }
1000            finally
1001            {
1002                for (StoreRef storeRef : lockedStores)
1003                {
1004                    try
1005                    {
1006                        factory.luceneIndexLock.releaseWriteLock(storeRef);
1007                    }
1008                    catch (Throwable JavaDoc e)
1009                    {
1010                        logger.error("Failed to release index lock for store " + storeRef, e);
1011                    }
1012                }
1013            }
1014            if (logger.isDebugEnabled())
1015            {
1016                logger.debug("Backed up Lucene indexes: \n" +
1017                        " Target directory: " + targetDir);
1018            }
1019        }
1020        
1021        /**
1022         * Makes a backup of the source directory via a temporary folder
1023         * @param storeRef
1024         */

1025        private void backupDirectory(File JavaDoc sourceDir, File JavaDoc tempDir, File JavaDoc targetDir) throws Exception JavaDoc
1026        {
1027            if (!sourceDir.exists())
1028            {
1029                // there is nothing to copy
1030
return;
1031            }
1032            // delete the files from the temp directory
1033
if (tempDir.exists())
1034            {
1035                FileUtils.deleteDirectory(tempDir);
1036                if (tempDir.exists())
1037                {
1038                    throw new AlfrescoRuntimeException("Temp directory exists and cannot be deleted: " + tempDir);
1039                }
1040            }
1041            // copy to the temp directory
1042
FileUtils.copyDirectory(sourceDir, tempDir, true);
1043            // check that the temp directory was created
1044
if (!tempDir.exists())
1045            {
1046                throw new AlfrescoRuntimeException("Copy to temp location failed");
1047            }
1048            // delete the target directory
1049
FileUtils.deleteDirectory(targetDir);
1050            if (targetDir.exists())
1051            {
1052                throw new AlfrescoRuntimeException("Failed to delete older files from target location");
1053            }
1054            // rename the temp to be the target
1055
tempDir.renameTo(targetDir);
1056            // make sure the rename worked
1057
if (!targetDir.exists())
1058            {
1059                throw new AlfrescoRuntimeException("Failed to rename temporary directory to target backup directory");
1060            }
1061        }
1062    }
1063
1064    /**
1065     * Job that lock uses the {@link LuceneIndexBackupComponent} to perform safe backups of the Lucene indexes.
1066     *
1067     * @author Derek Hulley
1068     */

1069    public static class LuceneIndexBackupJob implements Job
1070    {
1071        /** KEY_LUCENE_INDEX_BACKUP_COMPONENT = 'luceneIndexBackupComponent' */
1072        public static final String JavaDoc KEY_LUCENE_INDEX_BACKUP_COMPONENT = "luceneIndexBackupComponent";
1073        
1074        /**
1075         * Locks the Lucene indexes and copies them to a backup location
1076         */

1077        public void execute(JobExecutionContext context) throws JobExecutionException
1078        {
1079            JobDataMap jobData = context.getJobDetail().getJobDataMap();
1080            LuceneIndexBackupComponent backupComponent = (LuceneIndexBackupComponent) jobData.get(KEY_LUCENE_INDEX_BACKUP_COMPONENT);
1081            if (backupComponent == null)
1082            {
1083                throw new JobExecutionException("Missing job data: " + KEY_LUCENE_INDEX_BACKUP_COMPONENT);
1084            }
1085            // perform the backup
1086
backupComponent.backup();
1087        }
1088    }
1089}
1090
Popular Tags