/* 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 org.fcrepo.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 org.slf4j.bridge.SLF4JBridgeHandler; import org.fcrepo.common.Constants; import org.fcrepo.server.Module; import org.fcrepo.server.Server; import org.fcrepo.server.config.Configuration; import org.fcrepo.server.config.Parameter; import org.fcrepo.server.config.ServerConfiguration; import org.fcrepo.server.config.ServerConfigurationParser; import org.fcrepo.server.errors.InitializationException; import org.fcrepo.server.storage.lowlevel.IListable; import org.fcrepo.server.storage.lowlevel.ILowlevelStorage; import org.fcrepo.server.storage.translation.DODeserializer; import org.fcrepo.server.storage.translation.DOTranslationUtility; import org.fcrepo.server.storage.translation.FOXML1_1DODeserializer; import org.fcrepo.server.storage.types.BasicDigitalObject; import org.fcrepo.server.storage.types.DigitalObject; import org.fcrepo.server.utilities.ServerUtility; import org.fcrepo.utilities.LogConfig; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Entry-point for rebuilding various aspects of the repository. * * @author Chris Wilper */ public class Rebuild implements Constants, Runnable { private static Server server; private static Logger logger = LoggerFactory.getLogger(Rebuild.class .getName()); private static final String llstoreInterface = ILowlevelStorage.class .getName(); private static final String listableInterface = IListable.class.getName(); private final Rebuilder m_rebuilder; private final Map<String, String> m_options; public Rebuild(Rebuilder rebuilder, Map<String, String> options, Server server) throws Exception { // set these here so DOTranslationUtility doesn't try to get a Server // instance System.setProperty("fedoraServerHost", server .getParameter("fedoraServerHost")); System.setProperty("fedoraServerPort", server .getParameter("fedoraServerPort")); System.setProperty("fedoraAppServerContext", server .getParameter("fedoraAppServerContext")); 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."); } m_options = options; m_rebuilder = rebuilder; if (options != null) { try { // ensure rebuilds are possible before trying anything, // as rebuilder.start() may be destructive! Module mod = server.getBean(llstoreInterface, Module.class); Class<?> clazz = mod.getClass(); 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" + " org.fcrepo.server.storage.lowlevel.IListable" + " interface."); } } finally { } } else { logger.warn("Null options for " + getClass().getName()); } } public void run() { try { if (m_options != null && m_rebuilder != null) { System.err.println(); System.err.println("Rebuilding..."); try { // looks good, so init the rebuilder m_rebuilder.start(m_options); // add each object in llstore ILowlevelStorage llstore = (ILowlevelStorage) getServer().getModule( llstoreInterface); if (llstore == null) { logger.error("No module/bean definition for " + llstoreInterface); } else { logger.info("Loaded bean/module {} with impl ", llstoreInterface, llstore.getClass().getName()); } Iterator<String> pids = ((IListable) llstore).listObjects(); int total = 0; int errors = 0; DODeserializer deser = new FOXML1_1DODeserializer(); while (pids.hasNext()) { total++; String pid = pids.next(); System.out.println("Adding object #" + total + ": " + pid); if (!addObject(m_rebuilder, llstore, deser, 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 { m_rebuilder.finish(); if (server != null) { server.shutdown(null); server = null; } System.err.print("Finished."); System.err.println(); } return; } else { System.out.println("Exiting without rebuilding."); if (server != null) { server.shutdown(null); server = null; } } } catch (Exception e) { System.err.println("Rebuild failed:"); System.err.println(e.toString()); e.printStackTrace(System.err); } finally { } } private boolean addObject(Rebuilder rebuilder, ILowlevelStorage llstore, DODeserializer deser, String pid) { InputStream in = null; try { in = llstore.retrieveObject(pid); DigitalObject obj = new BasicDigitalObject(); 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>(); if (descs != null) { 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 = 1; if (System.getProperty("rebuilder") == null) { if (options.size() > 0) { 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); } } else { c = getChoice("No options to set. Start rebuilding?", new String[] {"Yes", "No, exit."}); if (c == 0) { return options; } } } else { return options; } 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 { Server server = getServer(); String[] rebuilders = server.getBeanNamesForType(Rebuilder.class); String[] labels = new String[rebuilders.length + 1]; int i = 0; for (i = 0; i < rebuilders.length; i++) { Rebuilder r = server.getBean(rebuilders[i], Rebuilder.class); labels[i] = r.getAction(); } labels[i] = "Exit"; int choiceNum = i; if (System.getProperty("rebuilder") == null) { choiceNum = getChoice("What do you want to do?", labels); } else { System.out.println("Getting rebuilder... " + System.getProperty("rebuilder")); for (int j = 0; j < rebuilders.length; j++) { if (rebuilders[j].equals(System.getProperty("rebuilder"))) { choiceNum = j; } } } if (choiceNum == i) { return null; } else { return server.getBean(rebuilders[choiceNum], Rebuilder.class); } } 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 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(Parameter.class).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<? extends Configuration> configs, String profile) { Iterator<? extends Configuration> iter = configs.iterator(); int c = 0; while (iter.hasNext()) { c += setValuesForProfile(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(); //refactor these, as they should be injected rebuilder.setServerConfiguration(serverConfig); rebuilder.setServerDir(serverDir); rebuilder.init(); Map<String, String> options = getOptions(rebuilder.getOptions()); 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(); } if (exit) { System.exit(1); } } public static void main(String[] args) { String profile = null; if (args.length == 1) { profile = args[0]; } if (args.length > 1) { for (int i = 0; i < args.length - 1; i+=2) { if ("-p".equals(args[i])) profile = args[i+1]; if ("-r".equals(args[i])) System.setProperty("rebuilder", args[i+1]); } if (profile == null && System.getProperty("rebuilder") == null) { fail("Too many arguments", true, true); } } try { File fedoraHomeDir = new File(Constants.FEDORA_HOME); // Configure logging from file System.setProperty("fedora.home", Constants.FEDORA_HOME); System.setProperty("logfile.extension", "-rebuild.log"); LogConfig.initFromFile(new File(fedoraHomeDir, "server/config/logback.xml")); // Replace java.util.logging's default handlers with one that // redirects everything to SLF4J java.util.logging.Logger rootLogger = java.util.logging.LogManager.getLogManager().getLogger(""); java.util.logging.Handler[] handlers = rootLogger.getHandlers(); for (int i = 0; i < handlers.length; i++) { rootLogger.removeHandler(handlers[i]); } SLF4JBridgeHandler.install(); File serverDir = new File(fedoraHomeDir, "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, getServer()).run(); return; } catch (Throwable th) { String msg = th.getMessage(); if (msg == null) { msg = th.getClass().getName(); } fail(msg, false, false); th.printStackTrace(); try { getServer().shutdown(null); } catch (Throwable t) { System.err.println("Server shutdown error: " + t.toString()); } } } }