/********************************************************************************** * $URL: https://source.sakaiproject.org/svn/search/trunk/search-impl/impl/src/java/org/sakaiproject/search/journal/impl/JournaledFSIndexStorageUpdateTransactionListener.java $ * $Id: JournaledFSIndexStorageUpdateTransactionListener.java 105078 2012-02-24 23:00:38Z ottenhoff@longsight.com $ *********************************************************************************** * * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009 The Sakai Foundation * * Licensed under the Educational Community License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.opensource.org/licenses/ECL-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * **********************************************************************************/ package org.sakaiproject.search.journal.impl; import java.io.File; import java.io.IOException; import java.util.List; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.lucene.index.IndexReader; import org.apache.lucene.index.Term; import org.sakaiproject.search.api.SearchService; import org.sakaiproject.search.indexer.impl.SearchBuilderItemSerializer; import org.sakaiproject.search.journal.api.IndexMergeTransaction; import org.sakaiproject.search.journal.api.JournalErrorException; import org.sakaiproject.search.journal.api.JournalManager; import org.sakaiproject.search.journal.api.JournalStorage; import org.sakaiproject.search.journal.api.JournaledIndex; import org.sakaiproject.search.journal.api.JournaledObject; import org.sakaiproject.search.journal.api.MergeTransactionListener; import org.sakaiproject.search.model.SearchBuilderItem; import org.sakaiproject.search.transaction.api.IndexTransaction; import org.sakaiproject.search.transaction.api.IndexTransactionException; /** * Listens for Transaction changes in the 2PC associated with an index update, * This adds new segments into the current index reader, it does not merge the * indexes this is performed by a seperate index merge operation, run * periodically to take the new inbound segments into a permanent space * * @author ieb TODO Unit test */ public class JournaledFSIndexStorageUpdateTransactionListener implements MergeTransactionListener { private static final Log log = LogFactory .getLog(JournaledFSIndexStorageUpdateTransactionListener.class); private JournaledIndex journaledIndex; private JournalManager journalManager; private SearchBuilderItemSerializer searchBuilderItemSerializer = new SearchBuilderItemSerializer(); private JournalStorage journalStorage; public void init() { } public void destroy() { } /** * @see org.sakaiproject.search.transaction.api.TransactionListener#open(org.sakaiproject.search.indexer.api.IndexUpdateTransaction) */ public void open(IndexTransaction transaction) throws IndexTransactionException { long lastJournalEntry = journaledIndex.getLastJournalEntry(); long thisJournalEntry = journaledIndex.getJournalSavePoint(); long nextJournalEntry = journalManager.getNextSavePoint(thisJournalEntry); if (nextJournalEntry == lastJournalEntry) { throw new JournalErrorException("Journal is stalled at ID " + lastJournalEntry); } journaledIndex.setLastJournalEntry(nextJournalEntry); ((IndexMergeTransaction) transaction).setJournalEntry(nextJournalEntry); transaction.put(JournaledObject.class.getName() + ".thisJournalEntry", thisJournalEntry); } /** * @see org.sakaiproject.search.transaction.api.TransactionListener#prepare(org.sakaiproject.search.indexer.api.IndexUpdateTransaction) */ public void prepare(IndexTransaction transaction) throws IndexTransactionException { long journalEntry = ((IndexMergeTransaction) transaction).getJournalEntry(); if (journalEntry == -1) { throw new JournalErrorException("No target journal entry "); } try { // update should perform the delete operations and add the segments // into the current reader. String workingSpace = journaledIndex.getWorkingSpace(); journalStorage.retrieveSavePoint(journalEntry, workingSpace); File f = new File(workingSpace, String.valueOf(journalEntry)); File segments = new File(f, "segments.gen"); if (segments.exists()) { // apply the deletes to everything that went before List<SearchBuilderItem> deleteDocuments = searchBuilderItemSerializer .loadTransactionList(f); if (deleteDocuments.size() > 0) { IndexReader deleteIndexReader = journaledIndex .getDeletionIndexReader(); log.debug("Deletion index reader is " + deleteIndexReader); transaction.put( JournaledFSIndexStorageUpdateTransactionListener.class .getName() + ".deleteIndexReader", deleteIndexReader); log.debug("Deleting documents for savePoint " + journalEntry); for (SearchBuilderItem sbi : deleteDocuments) { if (SearchBuilderItem.ACTION_DELETE.equals(sbi.getSearchaction()) || SearchBuilderItem.ACTION_ADD.equals(sbi.getSearchaction())) { log.debug("Deleting " + sbi.getName() + " for savePoint " + journalEntry); int ndel = deleteIndexReader.deleteDocuments(new Term( SearchService.FIELD_REFERENCE, sbi.getName())); if (ndel == 0) { log.debug("Tried to delete " + sbi.getName() + " but it was not found in the local index "); } else { log.debug("Deleted "+ndel+" in merge"); } } } } else { log.debug("No Documents to delete for savePoint " + journalEntry); } // add the index in log.debug("Adding segment to journaledIndex " + journaledIndex); journaledIndex.addSegment(f); } } catch (IOException ioex) { throw new IndexTransactionException("Failed to delete documents ", ioex); } log.debug("Finished"); } /** * @see org.sakaiproject.search.transaction.api.TransactionListener#rollback(org.sakaiproject.search.indexer.api.IndexUpdateTransaction) */ public void rollback(IndexTransaction transaction) throws IndexTransactionException { IndexReader deleteIndexReader = (IndexReader) transaction .get(JournaledFSIndexStorageUpdateTransactionListener.class.getName() + ".deleteIndexReader"); transaction.clear(JournaledFSIndexStorageUpdateTransactionListener.class .getName() + ".deleteIndexReader"); try // /TODO: make this work correctly, roll back the index { if (deleteIndexReader != null) { deleteIndexReader.close(); } } catch (IOException e) { throw new IndexTransactionException("Failed to close index with deletions ", e); } // how do we perform a roll back ? // undo the delete operations and remove the index from the reader } /* * (non-Javadoc) * * @see org.sakaiproject.search.transaction.api.TransactionListener#close(org.sakaiproject.search.transaction.api.IndexTransaction) */ public void close(IndexTransaction transaction) throws IndexTransactionException { } /* * (non-Javadoc) * * @see org.sakaiproject.search.transaction.api.TransactionListener#commit(org.sakaiproject.search.transaction.api.IndexTransaction) */ public void commit(IndexTransaction transaction) throws IndexTransactionException { IndexReader deleteIndexReader = (IndexReader) transaction .get(JournaledFSIndexStorageUpdateTransactionListener.class.getName() + ".deleteIndexReader"); long journalEntry = ((IndexMergeTransaction) transaction).getJournalEntry(); if (journalEntry == -1) { throw new JournalErrorException("No target journal entry "); } try { if (deleteIndexReader != null) { deleteIndexReader.close(); } journaledIndex.setJournalIndexEntry(journalEntry); journaledIndex.saveSegmentList(); // this will open the index but not bind it to the thread, hence if re-opened // it will close imediately journaledIndex.loadIndexReader(); } catch (IOException e) { throw new IndexTransactionException("Failed to close index with deletions", e); } transaction.clear(JournaledFSIndexStorageUpdateTransactionListener.class .getName() + ".deleteIndexReader"); } /** * @return the journaledIndex */ public JournaledIndex getJournaledIndex() { return journaledIndex; } /** * @param journaledIndex * the journaledIndex to set */ public void setJournaledIndex(JournaledIndex journaledIndex) { this.journaledIndex = journaledIndex; } /** * @return the journalManager */ public JournalManager getJournalManager() { return journalManager; } /** * @param journalManager * the journalManager to set */ public void setJournalManager(JournalManager journalManager) { this.journalManager = journalManager; } /** * @return the journalStorage */ public JournalStorage getJournalStorage() { return journalStorage; } /** * @param journalStorage * the journalStorage to set */ public void setJournalStorage(JournalStorage journalStorage) { this.journalStorage = journalStorage; } }