1   /**
2    * Copyright (c) 2000-2007 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  
33  import edu.emory.mathcs.backport.java.util.concurrent.Semaphore;
34  
35  import java.io.IOException;
36  
37  import java.util.Iterator;
38  import java.util.List;
39  import java.util.Map;
40  
41  import org.apache.commons.logging.Log;
42  import org.apache.commons.logging.LogFactory;
43  import org.apache.lucene.index.IndexReader;
44  import org.apache.lucene.index.IndexWriter;
45  import org.apache.lucene.index.Term;
46  
47  /**
48   * <a href="IndexWriterFactory.java.html"><b><i>View Source</i></b></a>
49   *
50   * <p>
51   * Lucene only allows one IndexWriter to be open at a time. However, multiple
52   * threads can use this single IndexWriter. This class manages a global
53   * IndexWriter and uses reference counting to determine when it can be closed.
54   * </p>
55   *
56   * <p>
57   * To delete documents, IndexReaders are used but cannot delete while another
58   * IndexWriter or IndexReader has the write lock. A semaphore is used to
59   * serialize delete and add operations. If the shared IndexWriter is open,
60   * concurrent add operations are permitted.
61   * </p>
62   *
63   * @author Harry Mark
64   * @author Brian Wing Shun Chan
65   *
66   */
67  public class IndexWriterFactory {
68  
69      public IndexWriterFactory() {
70  
71          // Create semaphores for all companies
72  
73          try {
74              List companies = CompanyLocalServiceUtil.getCompanies();
75  
76              for (int i = 0; i < companies.size(); i++) {
77                  Company company = (Company)companies.get(i);
78  
79                  _lockLookup.put(
80                      new Long(company.getCompanyId()), new Semaphore(1));
81              }
82  
83              _lockLookup.put(new Long(CompanyImpl.SYSTEM), new Semaphore(1));
84          }
85          catch (SystemException se) {
86              _log.error(se);
87          }
88      }
89  
90      public void acquireLock(long companyId, boolean needExclusive)
91          throws InterruptedException {
92  
93          Semaphore lock = (Semaphore)_lockLookup.get(new Long(companyId));
94  
95          if (lock != null) {
96  
97              // Exclusive checking is used to prevent greedy IndexWriter sharing.
98              // This registers a need for exclusive lock, and causes IndexWriters
99              // to wait in FIFO order.
100 
101             if (needExclusive) {
102                 synchronized (_lockLookup) {
103                     _needExclusiveLock++;
104                 }
105             }
106 
107             try {
108                 lock.acquire();
109             }
110             finally {
111                 if (needExclusive) {
112                     synchronized (_lockLookup) {
113                         _needExclusiveLock--;
114                     }
115                 }
116             }
117         }
118         else {
119             if (_log.isWarnEnabled()) {
120                 _log.warn("IndexWriterFactory lock not found for " + companyId);
121             }
122         }
123     }
124 
125     public void deleteDocuments(long companyId, Term term)
126         throws InterruptedException, IOException {
127 
128         try {
129             acquireLock(companyId, true);
130 
131             IndexReader reader = null;
132 
133             try {
134                 reader = IndexReader.open(LuceneUtil.getLuceneDir(companyId));
135 
136                 reader.deleteDocuments(term);
137             }
138             finally {
139                 if (reader != null) {
140                     reader.close();
141                 }
142             }
143         }
144         finally {
145             releaseLock(companyId);
146         }
147     }
148 
149     public IndexWriter getWriter(long companyId, boolean create)
150         throws IOException {
151 
152         Long companyIdObj = new Long(companyId);
153 
154         boolean hasError = false;
155         boolean newWriter = false;
156 
157         try {
158 
159             // If others need an exclusive lock, then wait to acquire lock
160             // before proceeding. This prevents starvation.
161 
162             if (_needExclusiveLock > 0) {
163                 acquireLock(companyId, false);
164                 releaseLock(companyId);
165             }
166 
167             synchronized(this) {
168                 IndexWriterData writerData =
169                     (IndexWriterData)_writerLookup.get(companyIdObj);
170 
171                 if (writerData == null) {
172                     newWriter = true;
173 
174                     acquireLock(companyId, false);
175 
176                     IndexWriter writer = new IndexWriter(
177                         LuceneUtil.getLuceneDir(companyId),
178                         LuceneUtil.getAnalyzer(), create);
179 
180                     writer.setMergeFactor(_MERGE_FACTOR);
181 
182                     writerData = new IndexWriterData(companyId, writer, 0);
183 
184                     _writerLookup.put(companyIdObj, writerData);
185                 }
186 
187                 writerData.setCount(writerData.getCount() + 1);
188 
189                 return writerData.getWriter();
190             }
191         }
192         catch (Exception e) {
193             hasError = true;
194 
195             _log.error("Unable to create a new writer", e);
196 
197             throw new IOException("Unable to create a new writer");
198         }
199         finally {
200             if (hasError && newWriter) {
201                 try {
202                     releaseLock(companyId);
203                 }
204                 catch (Exception e) {
205                 }
206             }
207         }
208     }
209 
210     public void releaseLock(long companyId) {
211         Semaphore lock = (Semaphore)_lockLookup.get(new Long(companyId));
212 
213         if (lock != null) {
214             lock.release();
215         }
216     }
217 
218     public void write(long companyId) throws IOException {
219         IndexWriterData writerData =
220             (IndexWriterData)_writerLookup.get(new Long(companyId));
221 
222         if (writerData != null) {
223             decrement(writerData);
224         }
225         else {
226             if (_log.isWarnEnabled()) {
227                 _log.warn("IndexWriterData not found for " + companyId);
228             }
229         }
230     }
231 
232     public void write(IndexWriter writer) throws IOException {
233         boolean writerFound = false;
234 
235         synchronized(this) {
236             if (!_writerLookup.isEmpty()) {
237                 Iterator itr = _writerLookup.values().iterator();
238 
239                 while (itr.hasNext()) {
240                     IndexWriterData writerData = (IndexWriterData)itr.next();
241 
242                     if (writerData.getWriter() == writer) {
243                         writerFound = true;
244 
245                         decrement(writerData);
246 
247                         break;
248                     }
249                 }
250             }
251         }
252 
253         if (!writerFound) {
254             try {
255                 _optimizeCount++;
256 
257                 if ((_OPTIMIZE_INTERVAL == 0) ||
258                     (_optimizeCount >= _OPTIMIZE_INTERVAL)) {
259 
260                     writer.optimize();
261 
262                     _optimizeCount = 0;
263                 }
264             }
265             finally {
266                 writer.close();
267             }
268         }
269     }
270 
271     protected void decrement(IndexWriterData writerData) throws IOException {
272         if (writerData.getCount() > 0) {
273             writerData.setCount(writerData.getCount() - 1);
274 
275             if (writerData.getCount() == 0) {
276                 _writerLookup.remove(new Long(writerData.getCompanyId()));
277 
278                 try {
279                     IndexWriter writer = writerData.getWriter();
280 
281                     try {
282                         _optimizeCount++;
283 
284                         if ((_OPTIMIZE_INTERVAL == 0) ||
285                             (_optimizeCount >= _OPTIMIZE_INTERVAL)) {
286 
287                             writer.optimize();
288 
289                             _optimizeCount = 0;
290                         }
291                     }
292                     finally {
293                         writer.close();
294                     }
295                 }
296                 catch (Exception e) {
297                     _log.error(e, e);
298                 }
299                 finally {
300                     releaseLock(writerData.getCompanyId());
301                 }
302             }
303         }
304     }
305 
306     private static final int _MERGE_FACTOR = GetterUtil.getInteger(
307         PropsUtil.get(PropsUtil.LUCENE_MERGE_FACTOR));
308 
309     private static final int _OPTIMIZE_INTERVAL = GetterUtil.getInteger(
310         PropsUtil.get(PropsUtil.LUCENE_OPTIMIZE_INTERVAL));
311 
312     private static Log _log = LogFactory.getLog(IndexWriterFactory.class);
313 
314     private Map _lockLookup = CollectionFactory.getHashMap();
315     private Map _writerLookup = CollectionFactory.getHashMap();
316     private int _needExclusiveLock = 0;
317     private int _optimizeCount = 0;
318 
319 }