1   /**
2    * Copyright (c) 2000-2008 Liferay, Inc. All rights reserved.
3    *
4    * Permission is hereby granted, free of charge, to any person obtaining a copy
5    * of this software and associated documentation files (the "Software"), to deal
6    * in the Software without restriction, including without limitation the rights
7    * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8    * copies of the Software, and to permit persons to whom the Software is
9    * furnished to do so, subject to the following conditions:
10   *
11   * The above copyright notice and this permission notice shall be included in
12   * all copies or substantial portions of the Software.
13   *
14   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17   * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19   * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20   * SOFTWARE.
21   */
22  
23  package com.liferay.portal.lucene;
24  
25  import com.liferay.portal.SystemException;
26  import com.liferay.portal.kernel.util.GetterUtil;
27  import com.liferay.portal.model.Company;
28  import com.liferay.portal.model.impl.CompanyImpl;
29  import com.liferay.portal.service.CompanyLocalServiceUtil;
30  import com.liferay.portal.util.PropsUtil;
31  import com.liferay.util.CollectionFactory;
32  import com.liferay.util.SystemProperties;
33  
34  import edu.emory.mathcs.backport.java.util.concurrent.Semaphore;
35  
36  import java.io.File;
37  import java.io.IOException;
38  
39  import java.util.Iterator;
40  import java.util.List;
41  import java.util.Map;
42  
43  import org.apache.commons.logging.Log;
44  import org.apache.commons.logging.LogFactory;
45  import org.apache.lucene.analysis.SimpleAnalyzer;
46  import org.apache.lucene.index.IndexReader;
47  import org.apache.lucene.index.IndexWriter;
48  import org.apache.lucene.index.Term;
49  import org.apache.lucene.store.Directory;
50  import org.apache.lucene.store.FSDirectory;
51  
52  /**
53   * <a href="IndexWriterFactory.java.html"><b><i>View Source</i></b></a>
54   *
55   * <p>
56   * Lucene only allows one IndexWriter to be open at a time. However, multiple
57   * threads can use this single IndexWriter. This class manages a global
58   * IndexWriter and uses reference counting to determine when it can be closed.
59   * </p>
60   *
61   * <p>
62   * To delete documents, IndexReaders are used but cannot delete while another
63   * IndexWriter or IndexReader has the write lock. A semaphore is used to
64   * serialize delete and add operations. If the shared IndexWriter is open,
65   * concurrent add operations are permitted.
66   * </p>
67   *
68   * @author Harry Mark
69   * @author Brian Wing Shun Chan
70   *
71   */
72  public class IndexWriterFactory {
73  
74      public IndexWriterFactory() {
75          if (LuceneUtil.INDEX_READ_ONLY) {
76              return;
77          }
78  
79          // Create semaphores for all companies
80  
81          try {
82              List companies = CompanyLocalServiceUtil.getCompanies();
83  
84              for (int i = 0; i < companies.size(); i++) {
85                  Company company = (Company)companies.get(i);
86  
87                  _lockLookup.put(
88                      new Long(company.getCompanyId()), new Semaphore(1));
89              }
90  
91              _lockLookup.put(new Long(CompanyImpl.SYSTEM), new Semaphore(1));
92          }
93          catch (SystemException se) {
94              _log.error(se);
95          }
96      }
97  
98      public void acquireLock(long companyId, boolean needExclusive)
99          throws InterruptedException {
100 
101         if (LuceneUtil.INDEX_READ_ONLY) {
102             return;
103         }
104 
105         Semaphore lock = (Semaphore)_lockLookup.get(new Long(companyId));
106 
107         if (lock != null) {
108 
109             // Exclusive checking is used to prevent greedy IndexWriter sharing.
110             // This registers a need for exclusive lock, and causes IndexWriters
111             // to wait in FIFO order.
112 
113             if (needExclusive) {
114                 synchronized (_lockLookup) {
115                     _needExclusiveLock++;
116                 }
117             }
118 
119             try {
120                 lock.acquire();
121             }
122             finally {
123                 if (needExclusive) {
124                     synchronized (_lockLookup) {
125                         _needExclusiveLock--;
126                     }
127                 }
128             }
129         }
130         else {
131             if (_log.isWarnEnabled()) {
132                 _log.warn("IndexWriterFactory lock not found for " + companyId);
133             }
134         }
135     }
136 
137     public void deleteDocuments(long companyId, Term term)
138         throws InterruptedException, IOException {
139 
140         if (LuceneUtil.INDEX_READ_ONLY) {
141             return;
142         }
143 
144         try {
145             acquireLock(companyId, true);
146 
147             IndexReader reader = null;
148 
149             try {
150                 reader = IndexReader.open(LuceneUtil.getLuceneDir(companyId));
151 
152                 reader.deleteDocuments(term);
153             }
154             finally {
155                 if (reader != null) {
156                     reader.close();
157                 }
158             }
159         }
160         finally {
161             releaseLock(companyId);
162         }
163     }
164 
165     public IndexWriter getWriter(long companyId, boolean create)
166         throws IOException {
167 
168         if (LuceneUtil.INDEX_READ_ONLY) {
169             return getReadOnlyIndexWriter();
170         }
171 
172         Long companyIdObj = new Long(companyId);
173 
174         boolean hasError = false;
175         boolean newWriter = false;
176 
177         try {
178 
179             // If others need an exclusive lock, then wait to acquire lock
180             // before proceeding. This prevents starvation.
181 
182             if (_needExclusiveLock > 0) {
183                 acquireLock(companyId, false);
184                 releaseLock(companyId);
185             }
186 
187             synchronized(this) {
188                 IndexWriterData writerData =
189                     (IndexWriterData)_writerLookup.get(companyIdObj);
190 
191                 if (writerData == null) {
192                     newWriter = true;
193 
194                     acquireLock(companyId, false);
195 
196                     IndexWriter writer = new IndexWriter(
197                         LuceneUtil.getLuceneDir(companyId),
198                         LuceneUtil.getAnalyzer(), create);
199 
200                     writer.setMergeFactor(_MERGE_FACTOR);
201 
202                     writerData = new IndexWriterData(companyId, writer, 0);
203 
204                     _writerLookup.put(companyIdObj, writerData);
205                 }
206 
207                 writerData.setCount(writerData.getCount() + 1);
208 
209                 return writerData.getWriter();
210             }
211         }
212         catch (Exception e) {
213             hasError = true;
214 
215             _log.error("Unable to create a new writer", e);
216 
217             throw new IOException("Unable to create a new writer");
218         }
219         finally {
220             if (hasError && newWriter) {
221                 try {
222                     releaseLock(companyId);
223                 }
224                 catch (Exception e) {
225                 }
226             }
227         }
228     }
229 
230     public void releaseLock(long companyId) {
231         if (LuceneUtil.INDEX_READ_ONLY) {
232             return;
233         }
234 
235         Semaphore lock = (Semaphore)_lockLookup.get(new Long(companyId));
236 
237         if (lock != null) {
238             lock.release();
239         }
240     }
241 
242     public void write(long companyId) throws IOException {
243         if (LuceneUtil.INDEX_READ_ONLY) {
244             return;
245         }
246 
247         IndexWriterData writerData =
248             (IndexWriterData)_writerLookup.get(new Long(companyId));
249 
250         if (writerData != null) {
251             decrement(writerData);
252         }
253         else {
254             if (_log.isWarnEnabled()) {
255                 _log.warn("IndexWriterData not found for " + companyId);
256             }
257         }
258     }
259 
260     public void write(IndexWriter writer) throws IOException {
261         if (LuceneUtil.INDEX_READ_ONLY) {
262             return;
263         }
264 
265         boolean writerFound = false;
266 
267         synchronized(this) {
268             if (!_writerLookup.isEmpty()) {
269                 Iterator itr = _writerLookup.values().iterator();
270 
271                 while (itr.hasNext()) {
272                     IndexWriterData writerData = (IndexWriterData)itr.next();
273 
274                     if (writerData.getWriter() == writer) {
275                         writerFound = true;
276 
277                         decrement(writerData);
278 
279                         break;
280                     }
281                 }
282             }
283         }
284 
285         if (!writerFound) {
286             try {
287                 _optimizeCount++;
288 
289                 if ((_OPTIMIZE_INTERVAL == 0) ||
290                     (_optimizeCount >= _OPTIMIZE_INTERVAL)) {
291 
292                     writer.optimize();
293 
294                     _optimizeCount = 0;
295                 }
296             }
297             finally {
298                 writer.close();
299             }
300         }
301     }
302 
303     protected void decrement(IndexWriterData writerData) throws IOException {
304         if (writerData.getCount() > 0) {
305             writerData.setCount(writerData.getCount() - 1);
306 
307             if (writerData.getCount() == 0) {
308                 _writerLookup.remove(new Long(writerData.getCompanyId()));
309 
310                 try {
311                     IndexWriter writer = writerData.getWriter();
312 
313                     try {
314                         _optimizeCount++;
315 
316                         if ((_OPTIMIZE_INTERVAL == 0) ||
317                             (_optimizeCount >= _OPTIMIZE_INTERVAL)) {
318 
319                             writer.optimize();
320 
321                             _optimizeCount = 0;
322                         }
323                     }
324                     finally {
325                         writer.close();
326                     }
327                 }
328                 catch (Exception e) {
329                     _log.error(e, e);
330                 }
331                 finally {
332                     releaseLock(writerData.getCompanyId());
333                 }
334             }
335         }
336     }
337 
338     protected IndexWriter getReadOnlyIndexWriter() {
339         if (_readOnlyIndexWriter == null) {
340             try {
341                 if (_log.isInfoEnabled()) {
342                     _log.info("Disabling writing to index for this process");
343                 }
344 
345                 _readOnlyIndexWriter = new ReadOnlyIndexWriter(
346                     getReadOnlyLuceneDir(), new SimpleAnalyzer(), true);
347             }
348             catch (IOException ioe) {
349                 throw new RuntimeException(ioe);
350             }
351         }
352 
353         return _readOnlyIndexWriter;
354     }
355 
356     protected Directory getReadOnlyLuceneDir() throws IOException {
357         if (_readOnlyLuceneDir == null) {
358             String tmpDir = SystemProperties.get(SystemProperties.TMP_DIR);
359 
360             File dir = new File(tmpDir + "/liferay/lucene/empty");
361 
362             dir.mkdir();
363 
364             _readOnlyLuceneDir = FSDirectory.getDirectory(dir.getPath(), false);
365         }
366 
367         return _readOnlyLuceneDir;
368     }
369 
370     private static final int _MERGE_FACTOR = GetterUtil.getInteger(
371         PropsUtil.get(PropsUtil.LUCENE_MERGE_FACTOR));
372 
373     private static final int _OPTIMIZE_INTERVAL = GetterUtil.getInteger(
374         PropsUtil.get(PropsUtil.LUCENE_OPTIMIZE_INTERVAL));
375 
376     private static Log _log = LogFactory.getLog(IndexWriterFactory.class);
377 
378     private FSDirectory _readOnlyLuceneDir = null;
379     private IndexWriter _readOnlyIndexWriter = null;
380     private Map _lockLookup = CollectionFactory.getHashMap();
381     private Map _writerLookup = CollectionFactory.getHashMap();
382     private int _needExclusiveLock = 0;
383     private int _optimizeCount = 0;
384 
385 }