diff options
Diffstat (limited to 'subsonic-booter/src/main/java/net/sourceforge/subsonic/booter/deployer/SubsonicDeployer.java')
-rw-r--r-- | subsonic-booter/src/main/java/net/sourceforge/subsonic/booter/deployer/SubsonicDeployer.java | 326 |
1 files changed, 326 insertions, 0 deletions
diff --git a/subsonic-booter/src/main/java/net/sourceforge/subsonic/booter/deployer/SubsonicDeployer.java b/subsonic-booter/src/main/java/net/sourceforge/subsonic/booter/deployer/SubsonicDeployer.java new file mode 100644 index 00000000..b066c635 --- /dev/null +++ b/subsonic-booter/src/main/java/net/sourceforge/subsonic/booter/deployer/SubsonicDeployer.java @@ -0,0 +1,326 @@ +package net.sourceforge.subsonic.booter.deployer; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.Writer; +import java.net.BindException; +import java.util.Date; +import java.util.jar.JarFile; +import java.util.zip.ZipEntry; + +import org.apache.commons.io.IOUtils; +import org.mortbay.jetty.Server; +import org.mortbay.jetty.security.Constraint; +import org.mortbay.jetty.security.ConstraintMapping; +import org.mortbay.jetty.security.SslSocketConnector; +import org.mortbay.jetty.nio.SelectChannelConnector; +import org.mortbay.jetty.webapp.WebAppContext; + +/** + * Responsible for deploying the Subsonic web app in + * the embedded Jetty container. + * <p/> + * The following system properties may be used to customize the behaviour: + * <ul> + * <li><code>subsonic.contextPath</code> - The context path at which Subsonic is deployed. Default "/".</li> + * <li><code>subsonic.port</code> - The port Subsonic will listen to. Default 4040.</li> + * <li><code>subsonic.httpsPort</code> - The port Subsonic will listen to for HTTPS. Default 0, which disables HTTPS.</li> + * <li><code>subsonic.war</code> - Subsonic WAR file, or exploded directory. Default "subsonic.war".</li> + * <li><code>subsonic.createLinkFile</code> - If set to "true", a Subsonic.url file is created in the working directory.</li> + * <li><code>subsonic.ssl.keystore</code> - Path to an alternate SSL keystore.</li> + * <li><code>subsonic.ssl.password</code> - Password of the alternate SSL keystore.</li> + * </ul> + * + * @author Sindre Mehus + */ +public class SubsonicDeployer implements SubsonicDeployerService { + + public static final String DEFAULT_HOST = "0.0.0.0"; + public static final int DEFAULT_PORT = 4040; + public static final int DEFAULT_HTTPS_PORT = 0; + public static final int DEFAULT_MEMORY_LIMIT = 150; + public static final String DEFAULT_CONTEXT_PATH = "/"; + public static final String DEFAULT_WAR = "subsonic.war"; + private static final int MAX_IDLE_TIME_MILLIS = 7 * 24 * 60 * 60 * 1000; // One week. + private static final int HEADER_BUFFER_SIZE = 64 * 1024; + + // Subsonic home directory. + private static final File SUBSONIC_HOME_WINDOWS = new File("c:/subsonic"); + private static final File SUBSONIC_HOME_OTHER = new File("/var/subsonic"); + + private Throwable exception; + private File subsonicHome; + private final Date startTime; + + public SubsonicDeployer() { + + // Enable shutdown hook for Ehcache. + System.setProperty("net.sf.ehcache.enableShutdownHook", "true"); + + startTime = new Date(); + createLinkFile(); + deployWebApp(); + } + + private void createLinkFile() { + if ("true".equals(System.getProperty("subsonic.createLinkFile"))) { + Writer writer = null; + try { + writer = new FileWriter("subsonic.url"); + writer.append("[InternetShortcut]"); + writer.append(System.getProperty("line.separator")); + writer.append("URL=").append(getUrl()); + writer.flush(); + } catch (Throwable x) { + System.err.println("Failed to create subsonic.url."); + x.printStackTrace(); + } finally { + if (writer != null) { + try { + writer.close(); + } catch (IOException x) { + // Ignored + } + } + } + } + } + + private void deployWebApp() { + try { + Server server = new Server(); + SelectChannelConnector connector = new SelectChannelConnector(); + connector.setMaxIdleTime(MAX_IDLE_TIME_MILLIS); + connector.setHeaderBufferSize(HEADER_BUFFER_SIZE); + connector.setHost(getHost()); + connector.setPort(getPort()); + if (isHttpsEnabled()) { + connector.setConfidentialPort(getHttpsPort()); + } + server.addConnector(connector); + + if (isHttpsEnabled()) { + SslSocketConnector sslConnector = new SslSocketConnector(); + sslConnector.setMaxIdleTime(MAX_IDLE_TIME_MILLIS); + sslConnector.setHeaderBufferSize(HEADER_BUFFER_SIZE); + sslConnector.setHost(getHost()); + sslConnector.setPort(getHttpsPort()); + sslConnector.setKeystore(System.getProperty("subsonic.ssl.keystore", getClass().getResource("/subsonic.keystore").toExternalForm())); + sslConnector.setPassword(System.getProperty("subsonic.ssl.password", "subsonic")); + server.addConnector(sslConnector); + } + + WebAppContext context = new WebAppContext(); + context.setTempDirectory(getJettyDirectory()); + context.setContextPath(getContextPath()); + context.setWar(getWar()); + + if (isHttpsEnabled()) { + ConstraintMapping constraintMapping = new ConstraintMapping(); + Constraint constraint = new Constraint(); + constraint.setDataConstraint(Constraint.DC_CONFIDENTIAL); + constraintMapping.setPathSpec("/"); + constraintMapping.setConstraint(constraint); + context.getSecurityHandler().setConstraintMappings(new ConstraintMapping[]{constraintMapping}); + } + + server.addHandler(context); + server.start(); + + System.err.println("Subsonic running on: " + getUrl()); + if (isHttpsEnabled()) { + System.err.println(" and: " + getHttpsUrl()); + } + + } catch (Throwable x) { + x.printStackTrace(); + exception = x; + } + } + + private File getJettyDirectory() { + File dir = new File(getSubsonicHome(), "jetty"); + String buildNumber = getSubsonicBuildNumber(); + if (buildNumber != null) { + dir = new File(dir, buildNumber); + } + System.err.println("Extracting webapp to " + dir); + + if (!dir.exists() && !dir.mkdirs()) { + System.err.println("Failed to create directory " + dir); + } + + return dir; + } + + private String getSubsonicBuildNumber() { + File war = new File(getWar()); + InputStream in = null; + try { + if (war.isFile()) { + JarFile jar = new JarFile(war); + ZipEntry entry = jar.getEntry("WEB-INF\\classes\\build_number.txt"); + if (entry == null) { + entry = jar.getEntry("WEB-INF/classes/build_number.txt"); + } + in = jar.getInputStream(entry); + } else { + in = new FileInputStream(war.getPath() + "/WEB-INF/classes/build_number.txt"); + } + return IOUtils.toString(in); + + } catch (Exception x) { + System.err.println("Failed to resolve build number from WAR: " + war); + x.printStackTrace(); + return null; + } finally { + IOUtils.closeQuietly(in); + } + } + + private String getContextPath() { + return System.getProperty("subsonic.contextPath", DEFAULT_CONTEXT_PATH); + } + + + private String getWar() { + String war = System.getProperty("subsonic.war"); + if (war == null) { + war = DEFAULT_WAR; + } + + File file = new File(war); + if (file.exists()) { + System.err.println("Using WAR file: " + file.getAbsolutePath()); + } else { + System.err.println("Error: WAR file not found: " + file.getAbsolutePath()); + } + + return war; + } + + private String getHost() { + return System.getProperty("subsonic.host", DEFAULT_HOST); + } + + private int getPort() { + int port = DEFAULT_PORT; + + String portString = System.getProperty("subsonic.port"); + if (portString != null) { + port = Integer.parseInt(portString); + } + + // Also set it so that the webapp can read it. + System.setProperty("subsonic.port", String.valueOf(port)); + + return port; + } + + private int getHttpsPort() { + int port = DEFAULT_HTTPS_PORT; + + String portString = System.getProperty("subsonic.httpsPort"); + if (portString != null) { + port = Integer.parseInt(portString); + } + + // Also set it so that the webapp can read it. + System.setProperty("subsonic.httpsPort", String.valueOf(port)); + + return port; + } + + private boolean isHttpsEnabled() { + return getHttpsPort() > 0; + } + + public String getErrorMessage() { + if (exception == null) { + return null; + } + if (exception instanceof BindException) { + return "Address already in use. Please change port number."; + } + + return exception.toString(); + } + + public int getMemoryUsed() { + long freeBytes = Runtime.getRuntime().freeMemory(); + long totalBytes = Runtime.getRuntime().totalMemory(); + long usedBytes = totalBytes - freeBytes; + return (int) Math.round(usedBytes / 1024.0 / 1024.0); + } + + private String getUrl() { + String host = DEFAULT_HOST.equals(getHost()) ? "localhost" : getHost(); + StringBuffer url = new StringBuffer("http://").append(host); + if (getPort() != 80) { + url.append(":").append(getPort()); + } + url.append(getContextPath()); + return url.toString(); + } + + private String getHttpsUrl() { + if (!isHttpsEnabled()) { + return null; + } + + String host = DEFAULT_HOST.equals(getHost()) ? "localhost" : getHost(); + StringBuffer url = new StringBuffer("https://").append(host); + if (getHttpsPort() != 443) { + url.append(":").append(getHttpsPort()); + } + url.append(getContextPath()); + return url.toString(); + } + + /** + * Returns the Subsonic home directory. + * + * @return The Subsonic home directory, if it exists. + * @throws RuntimeException If directory doesn't exist. + */ + private File getSubsonicHome() { + + if (subsonicHome != null) { + return subsonicHome; + } + + File home; + + String overrideHome = System.getProperty("subsonic.home"); + if (overrideHome != null) { + home = new File(overrideHome); + } else { + boolean isWindows = System.getProperty("os.name", "Windows").toLowerCase().startsWith("windows"); + home = isWindows ? SUBSONIC_HOME_WINDOWS : SUBSONIC_HOME_OTHER; + } + + // Attempt to create home directory if it doesn't exist. + if (!home.exists() || !home.isDirectory()) { + boolean success = home.mkdirs(); + if (success) { + subsonicHome = home; + } else { + String message = "The directory " + home + " does not exist. Please create it and make it writable. " + + "(You can override the directory location by specifying -Dsubsonic.home=... when " + + "starting the servlet container.)"; + System.err.println("ERROR: " + message); + } + } else { + subsonicHome = home; + } + + return home; + } + + public DeploymentStatus getDeploymentInfo() { + return new DeploymentStatus(startTime, getUrl(), getHttpsUrl(), getMemoryUsed(), getErrorMessage()); + } +} |