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