package com.google.refine; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; 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.refine.commands.Command; import com.google.refine.io.FileProjectManager; import edu.mit.simile.butterfly.Butterfly; import edu.mit.simile.butterfly.ButterflyModule; public class RefineServlet extends Butterfly { static public String VERSION = ""; static public String REVISION = ""; static public String FULLNAME = "Google Refine "; static public final String AGENT_ID = "/en/google_refine"; static final long serialVersionUID = 2386057901503517403L; static private final String JAVAX_SERVLET_CONTEXT_TEMPDIR = "javax.servlet.context.tempdir"; static private RefineServlet s_singleton; static private File s_dataDir; static final private Map commands = new HashMap(); // timer for periodically saving projects static private Timer _timer; static final Logger logger = LoggerFactory.getLogger("refine"); static final 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(); VERSION = getInitParameter("refine.version"); REVISION = getInitParameter("refine.revision"); FULLNAME += VERSION + " [" + REVISION + "]"; logger.info("Starting " + FULLNAME + "..."); s_singleton = this; logger.trace("> initialize"); String data = getInitParameter("refine.data"); if (data == null) { throw new ServletException("can't find servlet init config 'refine.data', I have to give up initializing"); } 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.dispose(); 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 commandKey = getCommandKey(request); Command command = commands.get(commandKey); if (command != null) { if (request.getMethod().equals("GET")) { if (!logger.isTraceEnabled()) logger.info("GET {}", request.getPathInfo()); logger.trace("> GET {}", commandKey); command.doGet(request, response); logger.trace("< GET {}", commandKey); } else if (request.getMethod().equals("POST")) { if (!logger.isTraceEnabled()) logger.info("POST {}", request.getPathInfo()); logger.trace("> POST {}", commandKey); command.doPost(request, response); logger.trace("< POST {}", commandKey); } else { response.sendError(405); } } else { response.sendError(404); } } else { super.service(request, response); } } protected String getCommandKey(HttpServletRequest request) { // A command path has this format: /command/module-name/command-name/... String path = request.getPathInfo().substring("/command/".length()); int slash1 = path.indexOf('/'); if (slash1 >= 0) { int slash2 = path.indexOf('/', slash1 + 1); if (slash2 > 0) { path = path.substring(0, slash2); } } return path; } 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 a single command. * * @param module the module the command belongs to * @param name command verb for command * @param commandObject object implementing the command * @return true if command was loaded and registered successfully */ protected boolean registerOneCommand(ButterflyModule module, String name, Command commandObject) { return registerOneCommand(module.getName() + "/" + name, commandObject); } /** * Register a single command. * * @param path path for command * @param commandObject object implementing the command * @return true if command was loaded and registered successfully */ protected boolean registerOneCommand(String path, Command commandObject) { if (commands.containsKey(path)) { return false; } commandObject.init(this); commands.put(path, 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 module the module the command belongs to * @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(ButterflyModule module, String commandName, Command commandObject) { return s_singleton.registerOneCommand(module, commandName, commandObject); } static private class ClassMapping { final String from; final String to; ClassMapping(String from, String to) { this.from = from; this.to = to; } } static final private List classMappings = new ArrayList(); /** * Add a mapping that determines how old class names can be updated to newer * class names. Such updates are desirable as the Java code changes from version * to version. If the "from" argument ends with *, then it's considered a prefix; * otherwise, it's an exact string match. * * @param from * @param to */ static public void registerClassMapping(String from, String to) { classMappings.add(new ClassMapping(from, to.endsWith("*") ? to.substring(0, to.length() - 1) : to)); } static { registerClassMapping("com.metaweb.*", "com.google.*"); registerClassMapping("com.google.gridworks.*", "com.google.refine.*"); } static final private Map classMappingsCache = new HashMap(); static public Class getClass(String className) throws ClassNotFoundException { String toClassName = classMappingsCache.get(className); if (toClassName == null) { toClassName = className; for (ClassMapping m : classMappings) { if (m.from.endsWith("*")) { if (toClassName.startsWith(m.from.substring(0, m.from.length() - 1))) { toClassName = m.to + toClassName.substring(m.from.length() - 1); } } else { if (m.from.equals(toClassName)) { toClassName = m.to; } } } classMappingsCache.put(className, toClassName); } return Class.forName(toClassName); } }