package com.google.gridworks; import java.io.File; import java.io.IOException; import java.util.HashMap; import java.util.Map; import java.util.Timer; import java.util.TimerTask; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.gridworks.commands.Command; import com.google.gridworks.io.FileProjectManager; import edu.mit.simile.butterfly.Butterfly; public class GridworksServlet extends Butterfly { static private final String VERSION = "1.0"; private static final long serialVersionUID = 2386057901503517403L; private static final String JAVAX_SERVLET_CONTEXT_TEMPDIR = "javax.servlet.context.tempdir"; static private GridworksServlet s_singleton; static private File s_dataDir; static final private Map commands = new HashMap(); // timer for periodically saving projects static private Timer _timer; final static Logger logger = LoggerFactory.getLogger("gridworks"); // TODO: This belongs in an external config file somewhere private static final String[][] commandNames = { {"create-project-from-upload", "com.google.gridworks.commands.project.CreateProjectCommand"}, {"import-project", "com.google.gridworks.commands.project.ImportProjectCommand"}, {"export-project", "com.google.gridworks.commands.project.ExportProjectCommand"}, {"export-rows", "com.google.gridworks.commands.project.ExportRowsCommand"}, {"get-project-metadata", "com.google.gridworks.commands.project.GetProjectMetadataCommand"}, {"get-all-project-metadata", "com.google.gridworks.commands.workspace.GetAllProjectMetadataCommand"}, {"delete-project", "com.google.gridworks.commands.project.DeleteProjectCommand"}, {"rename-project", "com.google.gridworks.commands.project.RenameProjectCommand"}, {"get-models", "com.google.gridworks.commands.project.GetModelsCommand"}, {"get-rows", "com.google.gridworks.commands.row.GetRowsCommand"}, {"get-processes", "com.google.gridworks.commands.history.GetProcessesCommand"}, {"get-history", "com.google.gridworks.commands.history.GetHistoryCommand"}, {"get-operations", "com.google.gridworks.commands.history.GetOperationsCommand"}, {"get-columns-info", "com.google.gridworks.commands.column.GetColumnsInfoCommand"}, {"get-scatterplot", "com.google.gridworks.commands.browsing.GetScatterplotCommand"}, {"undo-redo", "com.google.gridworks.commands.history.UndoRedoCommand"}, {"apply-operations", "com.google.gridworks.commands.history.ApplyOperationsCommand"}, {"cancel-processes", "com.google.gridworks.commands.history.CancelProcessesCommand"}, {"compute-facets", "com.google.gridworks.commands.browsing.ComputeFacetsCommand"}, {"compute-clusters", "com.google.gridworks.commands.browsing.ComputeClustersCommand"}, {"edit-one-cell", "com.google.gridworks.commands.cell.EditOneCellCommand"}, {"text-transform", "com.google.gridworks.commands.cell.TextTransformCommand"}, {"mass-edit", "com.google.gridworks.commands.cell.MassEditCommand"}, {"join-multi-value-cells", "com.google.gridworks.commands.cell.JoinMultiValueCellsCommand"}, {"split-multi-value-cells", "com.google.gridworks.commands.cell.SplitMultiValueCellsCommand"}, {"transpose-columns-into-rows", "com.google.gridworks.commands.cell.TransposeColumnsIntoRowsCommand"}, {"add-column", "com.google.gridworks.commands.column.AddColumnCommand"}, {"remove-column", "com.google.gridworks.commands.column.RemoveColumnCommand"}, {"rename-column", "com.google.gridworks.commands.column.RenameColumnCommand"}, {"split-column", "com.google.gridworks.commands.column.SplitColumnCommand"}, {"extend-data", "com.google.gridworks.commands.column.ExtendDataCommand"}, {"denormalize", "com.google.gridworks.commands.row.DenormalizeCommand"}, {"reconcile", "com.google.gridworks.commands.recon.ReconcileCommand"}, {"recon-match-best-candidates", "com.google.gridworks.commands.recon.ReconMatchBestCandidatesCommand"}, {"recon-mark-new-topics", "com.google.gridworks.commands.recon.ReconMarkNewTopicsCommand"}, {"recon-discard-judgments", "com.google.gridworks.commands.recon.ReconDiscardJudgmentsCommand"}, {"recon-match-specific-topic-to-cells", "com.google.gridworks.commands.recon.ReconMatchSpecificTopicCommand"}, {"recon-judge-one-cell", "com.google.gridworks.commands.recon.ReconJudgeOneCellCommand"}, {"recon-judge-similar-cells", "com.google.gridworks.commands.recon.ReconJudgeSimilarCellsCommand"}, {"annotate-one-row", "com.google.gridworks.commands.row.AnnotateOneRowCommand"}, {"annotate-rows", "com.google.gridworks.commands.row.AnnotateRowsCommand"}, {"remove-rows", "com.google.gridworks.commands.row.RemoveRowsCommand"}, {"reorder-rows", "com.google.gridworks.commands.row.ReorderRowsCommand"}, {"save-protograph", "com.google.gridworks.commands.freebase.SaveProtographCommand"}, {"get-expression-language-info", "com.google.gridworks.commands.expr.GetExpressionLanguageInfoCommand"}, {"get-expression-history", "com.google.gridworks.commands.expr.GetExpressionHistoryCommand"}, {"log-expression", "com.google.gridworks.commands.expr.LogExpressionCommand"}, {"preview-expression", "com.google.gridworks.commands.expr.PreviewExpressionCommand"}, {"preview-extend-data", "com.google.gridworks.commands.column.PreviewExtendDataCommand"}, {"preview-protograph", "com.google.gridworks.commands.freebase.PreviewProtographCommand"}, {"guess-types-of-column", "com.google.gridworks.commands.freebase.GuessTypesOfColumnCommand"}, {"check-authorization", "com.google.gridworks.commands.auth.CheckAuthorizationCommand"}, {"authorize", "com.google.gridworks.commands.auth.AuthorizeCommand"}, {"deauthorize", "com.google.gridworks.commands.auth.DeAuthorizeCommand"}, {"user-badges", "com.google.gridworks.commands.auth.GetUserBadgesCommand"}, {"upload-data", "com.google.gridworks.commands.freebase.UploadDataCommand"}, {"mqlread", "com.google.gridworks.commands.freebase.MQLReadCommand"}, {"mqlwrite", "com.google.gridworks.commands.freebase.MQLWriteCommand"}, {"get-preference", "com.google.gridworks.commands.GetPreferenceCommand"}, {"get-all-preferences", "com.google.gridworks.commands.GetAllPreferencesCommand"}, {"set-preference", "com.google.gridworks.commands.SetPreferenceCommand"}, }; public static String getVersion() { return VERSION; } final static protected long s_autoSavePeriod = 1000 * 60 * 5; // 5 minutes static protected class AutoSaveTimerTask extends TimerTask { public void run() { try { ProjectManager.singleton.save(false); // quick, potentially incomplete save } finally { _timer.schedule(new AutoSaveTimerTask(), s_autoSavePeriod); // we don't use scheduleAtFixedRate because that might result in // bunched up events when the computer is put in sleep mode } } } protected ServletConfig config; @Override public void init() throws ServletException { super.init(); s_singleton = this; logger.trace("> initialize"); String data = getInitParameter("gridworks.data"); if (data == null) { throw new ServletException("can't find servlet init config 'gridworks.data', I have to give up initializing"); } registerCommands(commandNames); s_dataDir = new File(data); FileProjectManager.initialize(s_dataDir); if (_timer == null) { _timer = new Timer("autosave"); _timer.schedule(new AutoSaveTimerTask(), s_autoSavePeriod); } logger.trace("< initialize"); } @Override public void destroy() { logger.trace("> destroy"); // cancel automatic periodic saving and force a complete save. if (_timer != null) { _timer.cancel(); _timer = null; } if (ProjectManager.singleton != null) { ProjectManager.singleton.save(true); // complete save ProjectManager.singleton = null; } this.config = null; logger.trace("< destroy"); super.destroy(); } @Override public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { if (request.getPathInfo().startsWith("/command")) { String commandName = getCommandName(request); Command command = commands.get(commandName); if (command != null) { if (request.getMethod().equals("GET")) { logger.trace("> GET {}", commandName); command.doGet(request, response); logger.trace("< GET {}", commandName); } else if (request.getMethod().equals("POST")) { logger.trace("> POST {}", commandName); command.doPost(request, response); logger.trace("< POST {}", commandName); } else { response.sendError(405); } } else { response.sendError(404); } } else { super.service(request, response); } } protected String getCommandName(HttpServletRequest request) { // Remove extraneous path segments that might be there for other purposes, // e.g., for /export-rows/filename.ext, export-rows is the command while // filename.ext is only for the browser to prompt a convenient filename. String commandName = request.getPathInfo().substring("/command/".length()); int slash = commandName.indexOf('/'); return slash > 0 ? commandName.substring(0, slash) : commandName; } private File tempDir = null; public File getTempDir() { if (tempDir == null) { File tempDir = (File) this.config.getServletContext().getAttribute(JAVAX_SERVLET_CONTEXT_TEMPDIR); if (tempDir == null) { throw new RuntimeException("This app server doesn't support temp directories"); } } return tempDir; } public File getTempFile(String name) { return new File(getTempDir(), name); } public File getCacheDir(String name) { File dir = new File(new File(s_dataDir, "cache"), name); dir.mkdirs(); return dir; } public String getConfiguration(String name, String def) { return null; } /** * Register an array of commands * * @param commands * An array of arrays containing pairs of strings with the * command name in the first element of the tuple and the fully * qualified class name of the class implementing the command in * the second. * @return false if any commands failed to load */ private boolean registerCommands(String[][] commands) { boolean status = true; for (String[] command : commandNames) { String commandName = command[0]; String className = command[1]; status |= registerOneCommand(commandName, className); } return status; } /** * Register a single command given its class name. * * @param name * command verb for command * @param className * class name of command class * @return true if command was loaded and registered successfully */ protected boolean registerOneCommand(String commandName, String className) { logger.debug("Loading command " + commandName + " class: " + className); Command cmd; try { cmd = (Command) this.getClass().getClassLoader().loadClass(className).newInstance(); return registerOneCommand(commandName, cmd); } catch (InstantiationException e) { logger.error("Failed to load command class " + className, e); return false; } catch (IllegalAccessException e) { logger.error("Failed to load command class " + className, e); return false; } catch (ClassNotFoundException e) { logger.error("Failed to load command class " + className, e); return false; } } /** * Register a single command. * * @param name * command verb for command * @param commandObject * object implementing the command * @return true if command was loaded and registered successfully */ protected boolean registerOneCommand(String name, Command commandObject) { if (commands.containsKey(name)) { return false; } commandObject.init(this); commands.put(name, commandObject); return true; } // Currently only for test purposes protected boolean unregisterCommand(String verb) { return commands.remove(verb) != null; } /** * Register a single command. Used by extensions. * * @param name * command verb for command * @param commandObject * object implementing the command * * @return true if command was loaded and registered successfully */ static public boolean registerCommand(String commandName, Command commandObject) { return s_singleton.registerOneCommand(commandName, commandObject); } static public Class getClass(String className) throws ClassNotFoundException { if (className.startsWith("com.metaweb.")) { className = "com.google." + className.substring("com.metaweb.".length()); } return Class.forName(className); } }