/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache 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.apache.org/licenses/LICENSE-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.exoplatform.services.jcr.impl.core.query.lucene; import org.apache.lucene.document.Document; import org.apache.lucene.document.Field; import org.apache.lucene.document.FieldSelector; import org.apache.lucene.document.Fieldable; import org.apache.lucene.index.CorruptIndexException; import org.apache.lucene.index.FilterIndexReader; import org.apache.lucene.index.IndexReader; import org.apache.lucene.index.IndexWriter; import org.apache.lucene.index.IndexWriterConfig; import org.apache.lucene.index.Term; import org.apache.lucene.index.TermEnum; import org.apache.lucene.index.TermPositions; import org.apache.lucene.store.Directory; import org.apache.lucene.util.Version; import org.exoplatform.services.jcr.impl.core.query.lucene.directory.DirectoryManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; /** * <code>IndexMigration</code> implements a utility that migrates a Jackrabbit * 1.4.x index to version 1.5. Until version 1.4.x, indexes used the character * '\uFFFF' to separate the name of a property from the value. As of Lucene * 2.3 this does not work anymore. See LUCENE-1221. Jackrabbit {@literal >=} 1.5 uses * the character '[' as a separator. Whenever an index is opened from disk, a * quick check is run to find out whether a migration is required. See also * JCR-1363 for more details. */ public class IndexMigration { /** * The logger instance for this class. */ private static final Logger log = LoggerFactory.getLogger("exo.jcr.component.core.IndexMigration"); /** * Checks if the given <code>index</code> needs to be migrated. * * @param index the index to check and migration if needed. * @param directoryManager the directory manager. * @throws IOException if an error occurs while migrating the index. */ public static void migrate(PersistentIndex index, DirectoryManager directoryManager) throws IOException { Directory indexDir = index.getDirectory(); log.debug("Checking {} ...", indexDir); ReadOnlyIndexReader reader = index.getReadOnlyIndexReader(); try { if (IndexFormatVersion.getVersion(reader).getVersion() >= IndexFormatVersion.V3.getVersion()) { // index was created with Jackrabbit 1.5 or higher // no need for migration log.debug("IndexFormatVersion >= V3, no migration needed"); return; } // assert: there is at least one node in the index, otherwise the // index format version would be at least V3 TermEnum terms = reader.terms(new Term(FieldNames.PROPERTIES, "")); try { Term t = terms.term(); if (t.text().indexOf('\uFFFF') == -1) { log.debug("Index already migrated"); return; } } finally { terms.close(); } } finally { reader.release(); index.releaseWriterAndReaders(); } // if we get here then the index must be migrated log.debug("Index requires migration {}", indexDir); String migrationName = index.getName() + "_v2.3"; if (directoryManager.hasDirectory(migrationName)) { directoryManager.delete(migrationName); } Directory migrationDir = directoryManager.getDirectory(migrationName); try { IndexWriter writer = new IndexWriter(migrationDir, new IndexWriterConfig(Version.LUCENE_36, new JcrStandartAnalyzer())); try { IndexReader r = new MigrationIndexReader(IndexReader.open(index.getDirectory())); try { writer.addIndexes(new IndexReader[]{r}); writer.close(); } finally { r.close(); } } finally { writer.close(); } } finally { migrationDir.close(); } directoryManager.delete(index.getName()); if (!directoryManager.rename(migrationName, index.getName())) { throw new IOException("failed to move migrated directory " + migrationDir); } log.info("Migrated " + index.getName()); } //---------------------------< internal helper >---------------------------- /** * An index reader that migrates stored field values and term text on the * fly. */ private static class MigrationIndexReader extends FilterIndexReader { public MigrationIndexReader(IndexReader in) { super(in); } public Document document(int n, FieldSelector fieldSelector) throws CorruptIndexException, IOException { Document doc = super.document(n, fieldSelector); Fieldable[] fields = doc.getFieldables(FieldNames.PROPERTIES); if (fields != null) { doc.removeFields(FieldNames.PROPERTIES); for (int i = 0; i < fields.length; i++) { String value = fields[i].stringValue(); value = value.replace('\uFFFF', '['); doc.add(new Field(FieldNames.PROPERTIES, value, Field.Store.YES, Field.Index.NOT_ANALYZED_NO_NORMS)); } } return doc; } public TermEnum terms() throws IOException { return new MigrationTermEnum(in.terms()); } public TermPositions termPositions() throws IOException { return new MigrationTermPositions(in.termPositions()); } private static class MigrationTermEnum extends FilterTermEnum { public MigrationTermEnum(TermEnum in) { super(in); } public Term term() { Term t = super.term(); if (t == null) { return t; } if (t.field().equals(FieldNames.PROPERTIES)) { String text = t.text(); return t.createTerm(text.replace('\uFFFF', '[')); } else { return t; } } TermEnum unwrap() { return in; } } private static class MigrationTermPositions extends FilterTermPositions { public MigrationTermPositions(TermPositions in) { super(in); } public void seek(Term term) throws IOException { if (term.field().equals(FieldNames.PROPERTIES)) { char[] text = term.text().toCharArray(); text[term.text().indexOf('[')] = '\uFFFF'; super.seek(term.createTerm(new String(text))); } else { super.seek(term); } } public void seek(TermEnum termEnum) throws IOException { if (termEnum instanceof MigrationTermEnum) { super.seek(((MigrationTermEnum)termEnum).unwrap()); } else { super.seek(termEnum); } } } } }