Skip to content

Instantly share code, notes, and snippets.

@monzou
Last active December 20, 2015 03:18
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save monzou/6062166 to your computer and use it in GitHub Desktop.
Save monzou/6062166 to your computer and use it in GitHub Desktop.
package com.usopla.gist;
import static com.google.common.base.Preconditions.checkNotNull;
import java.io.IOException;
import java.net.URL;
import javax.annotation.Nullable;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectReader;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
/**
* コンフィギュレーションをロードするユーティリティです。
* <p>
* YAML および JSON 形式に対応します。
*
* @author monzou
*/
public final class ConfigurationLoader {
/**
* コンフィギュレーションをロードします。
*
* @param <T> コンフィギュレーションのマッピング対象の型
* @param type コンフィギュレーションのマッピング対象の型
* @param configurationName コンフィギュレーションファイルの名称
* @return コンフィギュレーション
* @throws IOException コンフィギュレーションのロードに失敗した場合にスローされます。
*/
public static <T> T load(Class<T> type, String configurationName) throws IOException {
return load(type, configurationName, null);
}
/**
* コンフィギュレーションをロードします。
*
* @param <T> コンフィギュレーションのマッピング対象の型
* @param type コンフィギュレーションのマッピング対象の型
* @param configurationName コンフィギュレーションファイルの名称
* @param defaultConfigurationName デフォルトコンフィギュレーションファイルの名称
* @return コンフィギュレーション
* @throws IOException コンフィギュレーションのロードに失敗した場合にスローされます。
*/
public static <T> T load(Class<T> type, String configurationName, @Nullable String defaultConfigurationName) throws IOException {
ClassLoader classLoader = ConfigurationLoader.class.getClassLoader();
String base = defaultConfigurationName == null ? configurationName : defaultConfigurationName;
String overwrite = defaultConfigurationName == null ? null : configurationName;
try {
ObjectMapper mapper = mapper(base);
T configuration = mapper.readValue(url(classLoader, base), type);
if (overwrite != null) {
ObjectReader reader = mapper.readerForUpdating(configuration);
reader.withType(type).readValue(url(classLoader, overwrite));
}
return configuration;
} catch (JsonParseException | JsonMappingException e) {
throw new RuntimeException(e);
}
}
private static URL url(ClassLoader classLoader, String baseName) {
URL url = classLoader.getResource(checkNotNull(baseName));
if (url == null) {
throw new IllegalArgumentException("resource not found: " + baseName);
}
return url;
}
private static ObjectMapper mapper(String baseName) {
if (baseName.endsWith(".yml") || baseName.endsWith(".yaml")) {
return new ObjectMapper(new YAMLFactory());
} else if (baseName.endsWith(".json")) {
return new ObjectMapper();
}
throw new IllegalArgumentException("unexpected baseName: " + baseName);
}
private ConfigurationLoader() {
}
}
port: 9000
webapp:
dir: ../application-web
static:
dir: ../web-assets
resources:
- .tmp
- app
port: 9000
webapp:
dir: ../application-web
static:
dir: ../web-assets
resources:
- dist
package com.usopla.gist;
import java.lang.Thread.UncaughtExceptionHandler;
import java.util.List;
import com.usopla.gist.ConfigurationLoader;
import org.eclipse.jetty.plus.webapp.EnvConfiguration;
import org.eclipse.jetty.plus.webapp.PlusConfiguration;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.util.resource.ResourceCollection;
import org.eclipse.jetty.webapp.Configuration;
import org.eclipse.jetty.webapp.FragmentConfiguration;
import org.eclipse.jetty.webapp.MetaInfConfiguration;
import org.eclipse.jetty.webapp.WebAppContext;
import org.eclipse.jetty.webapp.WebInfConfiguration;
import org.eclipse.jetty.webapp.WebXmlConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.base.Function;
import com.google.common.collect.Collections2;
import com.google.common.collect.Lists;
/**
* 埋め込みの Jetty サーバを利用してスタンドアローンで起動する Web アプリケーションです。
*
* @author monzou
*/
public class JettyLauncher {
private static final Logger LOGGER = LoggerFactory.getLogger(JettyLauncher.class);
static {
Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
LOGGER.error("uncaught exception occurred.", e);
exit(false);
}
});
}
/**
* Web アプリケーションのエントリポイントです。
*
* @param args 引数
* @throws Exception サーバ起動中に発生した例外
*/
public static void main(String[] args) throws Exception {
if (args.length != 1) {
throw new IllegalArgumentException("usage: you have to specify environment (`development` or `production`).");
}
try {
JettyLauncher launcher = new JettyLauncher(args[0]);
launcher.launch();
} catch (Throwable t) {
LOGGER.error("failed to start server.", e);
exit(false);
}
}
private static void exit(boolean normally) {
System.exit(normally ? 0 : -1);
}
private final String environment;
private JettyLauncher(String environment) {
this.environment = environment;
}
private void launch() throws Exception {
Config config = ConfigurationLoader.load(Config.class, String.format("jetty-%s.yml", environment));
final WebApplication webapp = new WebApplication(config);
Thread shutdownHook = new Thread("shutdown-hook") {
@Override
public void run() {
try {
webapp.stop();
} catch (Throwable t) {
LOGGER.error("unknown error occurred.", t);
}
}
};
Runtime.getRuntime().addShutdownHook(shutdownHook);
webapp.boot();
}
private static class WebApplication {
private final Config config;
private Server server;
WebApplication(Config config) {
this.config = config;
}
void boot() throws Exception {
server = new Server(config.port);
server.setHandler(createContext());
server.start();
server.join();
}
private WebAppContext createContext() {
WebAppContext context = new WebAppContext();
context.setConfigurations(new Configuration[] { //
new WebXmlConfiguration() //
, new WebInfConfiguration() //
, new PlusConfiguration() //
, new MetaInfConfiguration() //
, new FragmentConfiguration() //
, new EnvConfiguration() //
});
context.setContextPath("/");
context.setDescriptor(config.webapp.path("src/main/webapp/WEB-INF/web.xml"));
context.setBaseResource(new ResourceCollection(config.www.resources()));
context.setParentLoaderPriority(true);
context.setInitParameter("org.eclipse.jetty.servlet.Default.cacheControl", "max-age=0, public"); // 静的ファイルをキャッシュしないようにする
context.setInitParameter("org.eclipse.jetty.servlet.Default.useFileMappedBuffer", "false"); // Windows で静的ファイルがロックされてしまう問題に対応
return context;
}
void stop() throws Exception {
if (server != null) {
try {
server.stop();
} finally {
server = null;
}
}
}
}
private static class Config {
public int port;
public WebApp webapp;
@JsonProperty("static")
public Static www;
static class WebApp {
public String dir;
String path(String relative) {
return String.format("%s/%s", dir, relative);
}
}
static class Static implements Function<String, String> {
public String dir;
public List<String> resources = Lists.newArrayList();
String[] resources() {
return Collections2.transform(resources, Static.this).toArray(new String[resources.size()]);
}
/** {@inheritDoc} */
@Override
public String apply(String relative) {
return String.format("%s/%s", dir, relative);
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment