/*
* 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.
*
* $Id: DatabaseRebuild.java 533253 2007-04-27 23:12:12Z vgritsenko $
*/
package org.apache.xindice.tools;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.xindice.core.Collection;
import org.apache.xindice.core.DBException;
import org.apache.xindice.core.Database;
import org.apache.xindice.core.data.Key;
import org.apache.xindice.core.data.Value;
import org.apache.xindice.core.filer.BTreeCallback;
import org.apache.xindice.core.filer.BTreeException;
import org.apache.xindice.core.filer.BTreeFiler;
import org.apache.xindice.core.filer.Filer;
import org.apache.xindice.core.filer.HashFiler;
import org.apache.xindice.core.indexer.Indexer;
import org.apache.xindice.util.Configuration;
import org.apache.xindice.util.XindiceException;
import org.apache.xindice.xml.dom.DOMParser;
import org.apache.xindice.server.Xindice;
import java.io.File;
import java.io.IOException;
/**
* Command line utility to re-build all btree and hash filer based collections
* of the database, and re-index.
*
* @version $Revision: 533253 $, $Date: 2007-04-27 19:12:12 -0400 (Fri, 27 Apr 2007) $
*/
public class DatabaseRebuild {
private static final Log log = LogFactory.getLog(DatabaseRebuild.class);
private static final int CMD_ALL = 0;
private static final int CMD_COPY = 1;
private static final int CMD_INDEX = 2;
private static final char NOT_RAN = '_';
private static final char SUCCESS = '*';
private static final char ERROR = '!';
public static void main(String[] args) throws Exception {
if (args.length < 2 || args[1] == null || args[1].length() == 0 ||
!("copy".equals(args[0]) || "index".equals(args[0]) || "rebuild".equals(args[0]))) {
usage();
System.exit(1);
}
int command;
if ("copy".equals(args[0])) {
command = CMD_COPY;
} else if ("index".equals(args[0])) {
command = CMD_INDEX;
} else {
command = CMD_ALL;
}
File location = new File(args[1]);
String path = location.getAbsolutePath();
String name = location.getName();
if ("".equals(name) || !location.exists() || !location.isDirectory()) {
System.out.println("Database path must point to existing database directory");
System.exit(1);
}
// create minimal database configuration instead of trying to locate system.xml
String config = "<root-collection dbroot='" + path + "/' name='" + name + "'/>";
Database db = new Database();
boolean success = true;
try {
System.out.println("Processing database " + path);
System.out.println();
System.out.println("DI\tCollection name");
db.setConfig(new Configuration(DOMParser.toDocument(config)));
if (log.isInfoEnabled()) {
log.info("Rebuilding collections...");
}
success = processCollection(db, command);
} finally {
db.close();
System.out.println();
System.out.println("Legend:");
System.out.println(" D Data file");
System.out.println(" I Index files");
System.out.println(" _ File was skipped");
System.out.println(" * File was processed successfuly");
System.out.println(" ! File processing failed");
System.out.println();
if (success) {
System.out.println("Rebuilding database was successfull.");
} else {
System.out.println("Rebuilding database failed. Please check logs for more detail.");
}
System.exit(success ? 0 : 2);
}
}
private static void usage() {
System.out.println("Xindice " + Xindice.Version + " Database Rebuild Utility");
System.out.println("Usage:");
System.out.println(" xindice_rebuild copy <db location>");
System.out.println(" xindice_rebuild index <db location>");
System.out.println(" xindice_rebuild rebuild <db location>");
System.out.println();
System.out.println("DB Location should point to the directory containing Xindice database files.");
System.out.println();
System.out.println("Important: Shutdown and backup database before proceeding!");
System.out.println();
}
private static boolean processCollection(Collection col, int command) {
String name = col.getCanonicalName();
boolean status;
try {
if (log.isInfoEnabled()) {
log.info("Processing collection " + name);
}
char copy = NOT_RAN;
char index = NOT_RAN;
switch (command) {
case CMD_COPY:
status = rebuildCollection(col);
copy = status ? SUCCESS : ERROR;
break;
case CMD_INDEX:
status = rebuildIndex(col);
index = status ? SUCCESS : ERROR;
break;
default:
status = rebuildCollection(col);
copy = status ? SUCCESS : ERROR;
if (status) {
status = rebuildIndex(col);
index = status ? SUCCESS : ERROR;
}
break;
}
System.out.println(String.valueOf(copy) + String.valueOf(index) + "\t" + name);
String[] colNames = col.listCollections();
for (int i = 0; i < colNames.length; i++) {
boolean result = processCollection(col.getCollection(colNames[i]), command);
status = status && result;
}
} catch (DBException e) {
log.error("Got an exception when processing collection " + name, e);
return false;
}
return status;
}
private static boolean rebuildCollection(Collection col) {
String canonicalName = col.getCanonicalName();
// close collection's filer
try {
if (col.getFiler() != null) {
col.getFiler().close();
}
} catch (DBException e) {
log.error("Could not close filer for collection " + canonicalName, e);
return false;
}
// prepare
Filer itsFiler = col.getFiler();
FilerCopy oldFiler;
FilerCopy newFiler;
if (itsFiler == null) {
if (log.isInfoEnabled()) {
log.info("Collection " + col.getCanonicalName() + " has no filer. Skipping...");
}
return true;
} if (itsFiler instanceof BTreeFiler) {
oldFiler = new BTreeCopy();
newFiler = new BTreeCopy();
} else if (itsFiler instanceof HashFiler) {
oldFiler = new HashCopy();
newFiler = new HashCopy();
} else {
if (log.isInfoEnabled()) {
log.info("Collection " + col.getCanonicalName() + " has unrecognized filer '" + itsFiler.getClass().getName() + "'. Skipping...");
}
return true;
}
String oldFileName;
String newFileName;
try {
oldFiler.setLocation(col.getCollectionRoot(), col.getName());
oldFiler.setConfig(col.getFiler().getConfig());
oldFileName = oldFiler.getFilerFile().getAbsolutePath();
if (!oldFiler.exists()) {
log.error("Filer for " + oldFileName + " does not exists");
return false;
}
newFiler.setLocation(col.getCollectionRoot(), col.getName() + ".rebuild");
newFiler.setConfig(col.getFiler().getConfig());
newFileName = newFiler.getFilerFile().getAbsolutePath();
if (newFiler.exists()) {
log.error("Filer for " + newFileName + " already exists");
return false;
}
} catch (XindiceException e) {
log.error("Got an exception when preparing to rebuild " + canonicalName, e);
return false;
}
// copy
if (!copy(oldFiler, newFiler, canonicalName)) {
newFiler.deleteFile();
return false;
}
oldFiler.deleteFile();
if (!newFiler.getFilerFile().renameTo(oldFiler.getFilerFile())) {
log.error("Could not rename successfully rebuilt file " + newFileName + " to " + oldFileName);
return false;
}
try {
col.getFiler().open();
} catch (DBException e) {
log.error("Could not open new file " + oldFileName, e);
return false;
}
return true;
}
private static boolean copy(FilerCopy oldFiler, FilerCopy newFiler, String canonicalName) {
try {
newFiler.create();
oldFiler.open();
newFiler.open();
oldFiler.copy(newFiler);
} catch (Exception e) {
log.error("Error copying collection " + canonicalName, e);
return false;
} finally {
try {
oldFiler.close();
} catch (DBException e) {
if (log.isWarnEnabled()) log.warn(e);
}
try {
newFiler.close();
} catch (DBException e) {
if (log.isWarnEnabled()) log.warn(e);
}
}
return true;
}
private static boolean rebuildIndex(Collection col) {
if (col.getFiler() == null) {
return true;
}
try {
String[] list = col.listIndexers();
for (int i = 0; i < list.length; i++) {
Indexer idx = col.getIndexer(list[i]);
Configuration idxConf = idx.getConfig();
if (log.isInfoEnabled()) {
log.info("Rebuilding index " + list[i] + " for collection " + col.getCanonicalName());
}
col.dropIndexer(idx);
col.createIndexer(idxConf);
}
} catch (DBException e) {
log.error("Could not rebuild index for collection " + col.getCanonicalName(), e);
return false;
}
return true;
}
private interface FilerCopy extends Filer {
public Value getValue(long pointer) throws IOException;
public void copy(Filer newFiler) throws IOException, DBException;
public boolean deleteFile();
public File getFilerFile();
}
private static class BTreeCopy extends BTreeFiler implements FilerCopy {
public Value getValue(long pointer) throws IOException {
return super.readValue(pointer);
}
public void copy(Filer newFiler) throws IOException, BTreeException {
query(null, new CopyCallback(this, newFiler));
}
public boolean deleteFile() {
return getFile().delete();
}
public File getFilerFile() {
return getFile();
}
}
private static class HashCopy extends HashFiler implements FilerCopy {
public Value getValue(long pointer) throws IOException {
return super.readValue(pointer);
}
public void copy(Filer newFiler) throws IOException, DBException {
long hashSize = getFileHeader().getPageCount();
for (long i = 0; i < hashSize; i++) {
Page page = getPage(i);
while (true) {
HashPageHeader ph = (HashPageHeader) page.getPageHeader();
if (ph.getStatus() == RECORD) {
Value value = readValue(page);
newFiler.writeRecord(page.getKey(), value);
long next = ph.getNextCollision();
if (next != NO_PAGE) {
page = getPage(ph.getNextCollision());
} else {
break;
}
} else {
break;
}
}
}
}
public boolean deleteFile() {
return getFile().delete();
}
public File getFilerFile() {
return getFile();
}
}
private static class CopyCallback implements BTreeCallback {
private BTreeCopy filer;
private Filer newFiler;
public CopyCallback(BTreeCopy filer, Filer newFiler) {
this.filer = filer;
this.newFiler = newFiler;
}
public boolean indexInfo(Value value, long pointer) {
try {
Value v = filer.getValue(pointer);
newFiler.writeRecord(new Key(value), v);
} catch (Exception e) {
log.error(e);
}
return true;
}
}
}