/* The contents of this file are subject to the license and copyright terms * detailed in the license directory at the root of the source tree (also * available online at http://fedora-commons.org/license/). */ package fedora.server.utilities.rebuild; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import fedora.common.Constants; import fedora.server.Server; import fedora.server.config.Configuration; import fedora.server.config.ModuleConfiguration; import fedora.server.config.Parameter; import fedora.server.config.ServerConfiguration; import fedora.server.config.ServerConfigurationParser; import fedora.server.errors.InitializationException; import fedora.server.errors.LowlevelStorageException; import fedora.server.storage.lowlevel.FileSystem; import fedora.server.storage.lowlevel.IListable; import fedora.server.storage.lowlevel.ILowlevelStorage; import fedora.server.storage.translation.DODeserializer; import fedora.server.storage.translation.DOTranslationUtility; import fedora.server.storage.translation.FOXML1_1DODeserializer; import fedora.server.storage.types.BasicDigitalObject; import fedora.server.storage.types.DigitalObject; import fedora.server.utilities.ServerUtility; import fedora.utilities.FileComparator; /** * Entry-point for rebuilding various aspects of the repository. * * @author Chris Wilper */ public class Rebuild implements Constants { private static Server server; private FileSystem fs; private static FileComparator _REVERSE_FILE_COMPARATOR = new FileComparator(true); /** * Rebuilders that the rebuild utility knows about. */ public static String[] REBUILDERS = new String[] {"fedora.server.resourceIndex.ResourceIndexRebuilder", "fedora.server.utilities.rebuild.SQLRebuilder"}; public Rebuild(Rebuilder rebuilder, Map<String, String> options, ServerConfiguration serverConfig) throws Exception { // set these here so DOTranslationUtility doesn't try to get a Server // instance System.setProperty("fedoraServerHost", serverConfig .getParameter("fedoraServerHost").getValue()); System.setProperty("fedoraServerPort", serverConfig .getParameter("fedoraServerPort").getValue()); System.setProperty("fedoraAppServerContext", serverConfig .getParameter("fedoraAppServerContext").getValue()); boolean serverIsRunning = ServerUtility.pingServer("http", null, null); if (serverIsRunning && rebuilder.shouldStopServer()) { throw new Exception("The Fedora server appears to be running." + " It must be stopped before the rebuilder can run."); } if (options != null) { System.err.println(); System.err.println("Rebuilding..."); try { // ensure rebuilds are possible before trying anything, // as rebuilder.start() may be destructive! final String llPackage = "fedora.server.storage.lowlevel"; String llstoreInterface = llPackage + ".ILowlevelStorage"; String listableInterface = llPackage + ".IListable"; ModuleConfiguration mcfg = serverConfig.getModuleConfiguration(llstoreInterface); Class<?> clazz = Class.forName(mcfg.getClassName()); boolean isListable = false; for (Class<?> iface : clazz.getInterfaces()) { if (iface.getName().equals(listableInterface)) { isListable = true; } } if (!isListable) { throw new Exception("ERROR: Rebuilds are not supported" + " by " + clazz.getName() + " because it does not implement the" + " fedora.server.storage.lowlevel.IListable" + " interface."); } // looks good, so init the rebuilder rebuilder.start(options); // add each object in llstore ILowlevelStorage llstore = (ILowlevelStorage) getServer().getModule(llstoreInterface); Iterator<String> pids = ((IListable) llstore).listObjects(); int total = 0; int errors = 0; while (pids.hasNext()) { total++; String pid = pids.next(); System.out.println("Adding object #" + total + ": " + pid); if (!addObject(rebuilder, llstore, pid)) { errors++; } } if (errors == 0) { System.out.println("SUCCESS: " + total + " objects rebuilt."); } else { System.out.println("WARNING: " + errors + " of " + total + " objects failed to rebuild due to errors."); } } finally { rebuilder.finish(); if (server != null) { server.shutdown(null); server = null; } } System.err.print("Finished."); System.err.println(); } } private boolean addObject(Rebuilder rebuilder, ILowlevelStorage llstore, String pid) { InputStream in = null; try { in = llstore.retrieveObject(pid); DigitalObject obj = new BasicDigitalObject(); DODeserializer deser = new FOXML1_1DODeserializer(); deser.deserialize(in, obj, "UTF-8", DOTranslationUtility.SERIALIZE_STORAGE_INTERNAL); rebuilder.addObject(obj); return true; } catch (Exception e) { System.out.println("WARNING: Skipped " + pid + " due to exception: "); e.printStackTrace(); return false; } finally { if (in != null) { try { in.close(); } catch (IOException e) { } } } } /** * Gets the instance of the server appropriate for rebuilding. * If no such instance has been initialized yet, initialize one. * * @return the server instance. * @throws InitializationException if initialization fails. */ public static Server getServer() throws InitializationException { if (server == null) { server = RebuildServer.getRebuildInstance( new File(Constants.FEDORA_HOME)); } return server; } private static Map<String, String> getOptions(Map<String, String> descs) throws IOException { Map<String, String> options = new HashMap<String, String>(); Iterator<String> iter = descs.keySet().iterator(); while (iter.hasNext()) { String name = iter.next(); String desc = descs.get(name); options.put(name, getOptionValue(name, desc)); } int c = getChoice("Start rebuilding with the above options?", new String[] {"Yes", "No, let me re-enter the options.", "No, exit."}); if (c == 0) { return options; } if (c == 1) { System.err.println(); return getOptions(descs); } return null; } private static String getOptionValue(String name, String desc) throws IOException { System.err.println("[" + name + "]"); System.err.println(desc); System.err.println(); System.err.print("Enter a value --> "); String val = new BufferedReader(new InputStreamReader(System.in)).readLine(); System.err.println(); return val; } private static Rebuilder getRebuilder() throws Exception { String[] labels = new String[REBUILDERS.length + 1]; Rebuilder[] rebuilders = new Rebuilder[REBUILDERS.length]; int i = 0; for (i = 0; i < REBUILDERS.length; i++) { Rebuilder r = (Rebuilder) Class.forName(REBUILDERS[i]).newInstance(); labels[i] = r.getAction(); rebuilders[i] = r; } labels[i] = "Exit"; int choiceNum = getChoice("What do you want to do?", labels); if (choiceNum == i) { return null; } else { return rebuilders[choiceNum]; } } private static int getChoice(String title, String[] labels) throws IOException { boolean validChoice = false; int choiceIndex = -1; System.err.println(title); System.err.println(); for (int i = 1; i <= labels.length; i++) { System.err.println(" " + i + ") " + labels[i - 1]); } System.err.println(); while (!validChoice) { System.err.print("Enter (1-" + labels.length + ") --> "); BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); String line = in.readLine(); try { int choiceNum = Integer.parseInt(line); if (choiceNum > 0 && choiceNum <= labels.length) { choiceIndex = choiceNum - 1; validChoice = true; } } catch (NumberFormatException nfe) { } } return choiceIndex; } private InputStream getFile(File f, String searchString) throws IOException, LowlevelStorageException { /* * If we don't care about the existence of a search string, don't bother * looking */ if (searchString == null) { return fs.read(f); } BufferedReader reader = null; try { reader = new BufferedReader(new InputStreamReader(fs.read(f))); String line = reader.readLine(); while (line != null) { if (line.indexOf(searchString) != -1) { return fs.read(f); } else { line = reader.readLine(); } } } finally { if (reader != null) { try { reader.close(); } catch (Exception e) { } } } return null; } private static ServerConfiguration getServerConfig(File serverDir, String profile) throws IOException { ServerConfigurationParser parser = new ServerConfigurationParser(new FileInputStream(new File(serverDir, "config/fedora.fcfg"))); ServerConfiguration serverConfig = parser.parse(); // set all the values according to the profile, if specified if (profile != null) { int c = setValuesForProfile(serverConfig, profile); c += setValuesForProfile(serverConfig.getModuleConfigurations(), profile); c += setValuesForProfile(serverConfig .getDatastoreConfigurations(), profile); if (c == 0) { throw new IOException("Unrecognized server-profile: " + profile); } } return serverConfig; } private static int setValuesForProfile(Configuration config, String profile) { int c = 0; Iterator<Parameter> iter = config.getParameters().iterator(); while (iter.hasNext()) { Parameter param = iter.next(); String profileValue = param.getProfileValues().get(profile); if (profileValue != null) { param.setValue(profileValue); c++; } } return c; } private static int setValuesForProfile(List configs, String profile) { Iterator iter = configs.iterator(); int c = 0; while (iter.hasNext()) { c += setValuesForProfile((Configuration) iter.next(), profile); } return c; } private static Map<String, String> getUserInput(Rebuilder rebuilder, File serverDir, ServerConfiguration serverConfig) throws Exception { if (rebuilder != null) { System.err.println(); System.err.println(rebuilder.getAction()); System.err.println(); Map<String, String> options = getOptions(rebuilder.init(serverDir, serverConfig)); return options; } else { return new HashMap<String, String>(); } } public static void fail(String message, boolean showUsage, boolean exit) { System.err.println("Error: " + message); System.err.println(); if (showUsage) { System.err.println("Usage: fedora-rebuild [server-profile]"); System.err.println(); System.err .println("server-profile : the argument you start Fedora with, such as 'mckoi'"); System.err .println(" or 'oracle'. If you start fedora with 'fedora-start'"); System.err .println(" (without arguments), don't specify a server-profile here either."); System.err.println(); } if (exit) { System.exit(1); } } public static void main(String[] args) { // tell commons-logging to use log4j System.setProperty("org.apache.commons.logging.LogFactory", "org.apache.commons.logging.impl.Log4jFactory"); System.setProperty("org.apache.commons.logging.Log", "org.apache.commons.logging.impl.Log4JLogger"); // log4j // File log4jConfig = new File(new File(homeDir), "config/log4j.xml"); // DOMConfigurator.configure(log4jConfig.getPath()); String profile = null; if (args.length > 0) { profile = args[0]; } if (args.length > 1) { fail("Too many arguments", true, true); } try { File serverDir = new File(new File(Constants.FEDORA_HOME), "server"); ServerConfiguration serverConfig = getServerConfig(serverDir, profile); System.err.println(); System.err.println(" Fedora Rebuild Utility"); System.err .println(" .........................."); System.err.println(); System.err .println("WARNING: Live rebuilds are not currently supported."); System.err .println(" Make sure your server is stopped before continuing."); System.err.println(); System.err.println("Server directory is " + serverDir.toString()); if (profile != null) { System.err.print("Server profile is " + profile); } System.err.println(); System.err .println("---------------------------------------------------------------------"); System.err.println(); Rebuilder rebuilder = getRebuilder(); Map<String, String> options = getUserInput(rebuilder, serverDir, serverConfig); new Rebuild(rebuilder, options, serverConfig); } catch (Throwable th) { String msg = th.getMessage(); if (msg == null) { msg = th.getClass().getName(); } fail(msg, false, false); th.printStackTrace(); } } }