package ameba.core; import ameba.Ameba; import ameba.container.Container; import ameba.container.event.StartupEvent; import ameba.container.server.Connector; import ameba.core.event.RequestEvent; import ameba.event.Listener; import ameba.event.SystemEventBus; import ameba.exception.AmebaException; import ameba.exception.ConfigErrorException; import ameba.feature.AmebaFeature; import ameba.i18n.Messages; import ameba.scanner.Acceptable; import ameba.scanner.ClassFoundEvent; import ameba.scanner.ClassInfo; import ameba.scanner.PackageScanner; import ameba.util.*; import ch.qos.logback.classic.LoggerContext; import ch.qos.logback.classic.gaffer.GafferUtil; import com.google.common.base.Charsets; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import com.google.common.primitives.Ints; import javassist.CtClass; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; import org.glassfish.hk2.api.ServiceLocator; import org.glassfish.jersey.internal.inject.AbstractBinder; import org.glassfish.jersey.model.ContractProvider; import org.glassfish.jersey.server.ResourceConfig; import org.glassfish.jersey.server.ResourceFinder; import org.glassfish.jersey.server.ServerConfig; import org.glassfish.jersey.server.ServerProperties; import org.glassfish.jersey.server.model.Resource; import org.glassfish.jersey.server.monitoring.ApplicationEvent; import org.glassfish.jersey.server.monitoring.ApplicationEventListener; import org.glassfish.jersey.server.monitoring.RequestEventListener; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.slf4j.bridge.SLF4JBridgeHandler; import javax.inject.Inject; import javax.inject.Singleton; import javax.ws.rs.Path; import javax.ws.rs.RuntimeType; import javax.ws.rs.core.Feature; import javax.ws.rs.ext.Provider; import java.io.*; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.net.*; import java.nio.charset.Charset; import java.nio.file.Paths; import java.util.*; import java.util.logging.Handler; import java.util.logging.Level; import java.util.logging.LogManager; import java.util.stream.Stream; import static ameba.util.IOUtils.*; /** * 应用程序启动配置 * * @author icode * @since 13-8-6 下午8:42 */ @Singleton public class Application { /** * Constant <code>DEFAULT_APP_NAME="Ameba"</code> */ public static final String DEFAULT_APP_NAME = "Ameba"; /** * Constant <code>DEFAULT_APP_CONF="conf/application.conf"</code> */ public static final String DEFAULT_APP_CONF = "conf/application.conf"; private static final String REGISTER_CONF_PREFIX = "register."; private static final String ADDON_CONF_PREFIX = "addon."; private static final String JERSEY_CONF_NAME_PREFIX = "sys.core."; private static final String DEFAULT_LOGBACK_CONF = "log.groovy"; private static final String EXCLUDES_KEY = "exclude.classes"; private static final String EXCLUDES_KEY_PREFIX = EXCLUDES_KEY + "."; private static final String LINE_SEPARATOR = System.getProperty("line.separator", "\n"); private static final Logger logger = LoggerFactory.getLogger(Application.class); private static String SCAN_CLASSES_CACHE_FILE; private static String INFO_SPLITTER = "---------------------------------------------------"; protected boolean jmxEnabled; private String[] configFiles; private long timestamp = System.currentTimeMillis(); private boolean initialized = false; private Mode mode; private CharSequence applicationVersion; private Container container; private Set<Addon> addons = Sets.newLinkedHashSet(); private Set<String> excludes = Sets.newLinkedHashSet(); private ResourceConfig config = new ExcludeResourceConfig(excludes); private Map<String, Object> srcProperties = Maps.newLinkedHashMap(); private Set<String> scanPackages; private String[] ids; /** * <p>Constructor for Application.</p> */ protected Application() { } /** * <p>Constructor for Application.</p> * * @param ids a {@link java.lang.String} object. */ public Application(String... ids) { if (Ameba.getApp() != null) { throw new AmebaException(Messages.get("info.application.exists")); } this.ids = ids; Set<String> configFiles = parseIds2ConfigFile(ids); this.configFiles = configFiles.toArray(new String[configFiles.size()]); configure(); } /** * <p>parseIds2ConfigFile.</p> * * @param ids a {@link java.lang.String} object. * @return a {@link java.util.Set} object. */ public static Set<String> parseIds2ConfigFile(String... ids) { Set<String> configFiles = Sets.newLinkedHashSet(); configFiles.add(DEFAULT_APP_CONF); if (ids != null && ids.length > 0) { String[] conf = DEFAULT_APP_CONF.split("\\."); String idPrefix = conf[0]; String idSuffix = "." + conf[1]; for (String id : ids) { if (StringUtils.isNotBlank(id)) { String confFile = idPrefix + "_" + id + idSuffix; configFiles.add(confFile); } } } return configFiles; } private static String toExternalForm(URL url) { if (url == null) return null; try { return URLDecoder.decode(url.toExternalForm(), Charset.defaultCharset().name()); } catch (Exception e) { return url.toExternalForm(); } } /** * <p>readModuleConfig.</p> * * @param properties a {@link java.util.Properties} object. * @param isDev a boolean. */ @SuppressWarnings("unchecked") public static void readModuleConfig(Properties properties, boolean isDev) { logger.info(Messages.get("info.module.load.conf")); //读取模块配置 Enumeration<URL> moduleUrls = IOUtils.getResources("conf/module.conf"); List<String> readedModule = Lists.newArrayList(); if (moduleUrls.hasMoreElements()) { while (moduleUrls.hasMoreElements()) { InputStream in = null; URL url = moduleUrls.nextElement(); try { String fileName = url.getFile(); String modelName = fileName; int jarFileIndex = modelName.lastIndexOf("!"); if (jarFileIndex != -1) { modelName = modelName.substring(0, jarFileIndex); } jarFileIndex = modelName.lastIndexOf("."); if (jarFileIndex != -1) { modelName = modelName.substring(0, jarFileIndex); } int fileIndex = modelName.lastIndexOf("/"); modelName = modelName.substring(fileIndex + 1); if (url.getProtocol().equals("file") && "module".equals(modelName)) { //projectName/target/classes/conf/module //projectName/src/main/resources/conf/module if (fileName.endsWith("/target/classes/conf/module.conf")) { try { modelName = Paths.get(url.toURI()) .resolveSibling("../../../").normalize().getFileName().toString(); } catch (URISyntaxException e) { // no op } } else if (fileName.endsWith("/src/main/resources/conf/module.conf")) { try { modelName = Paths.get(url.toURI()).resolveSibling("../../../../") .normalize().getFileName().toString(); } catch (URISyntaxException e) { // no op } } } if (readedModule.contains(modelName)) continue; readedModule.add(modelName); logger.info(Messages.get("info.module.load", modelName)); logger.debug(Messages.get("info.module.load.item.conf", toExternalForm(url))); URLConnection connection = url.openConnection(); if (isDev) { connection.setUseCaches(false); } in = connection.getInputStream(); } catch (IOException e) { logger.error(Messages.get("info.load.error", toExternalForm(url))); } loadProperties(in, properties, url); } } else { logger.info(Messages.get("info.module.none")); } } private static void loadProperties(InputStream in, Properties properties, URL url) { if (in != null) { try { properties.load(in); } catch (Exception e) { logger.error(Messages.get("info.load.error", toExternalForm(url))); } } else { logger.error(Messages.get("info.load.error", toExternalForm(url))); } closeQuietly(in); } /** * <p>readModeConfig.</p> * * @param properties a {@link java.util.Properties} object. * @param mode a {@link ameba.core.Application.Mode} object. */ @SuppressWarnings("unchecked") public static void readModeConfig(Properties properties, Mode mode) { //读取相应模式的配置文件 Enumeration<java.net.URL> confs = IOUtils.getResources("conf/" + mode.name().toLowerCase() + ".conf"); while (confs.hasMoreElements()) { InputStream in = null; try { URLConnection connection = confs.nextElement().openConnection(); if (mode.isDev()) { connection.setUseCaches(false); } in = connection.getInputStream(); properties.load(in); } catch (IOException e) { logger.warn(Messages.get("info.module.conf.error", "conf/" + mode.name().toLowerCase() + ".conf"), e); } finally { closeQuietly(in); } } } /** * <p>readDefaultConfig.</p> * * @return a {@link java.util.Properties} object. */ public static Properties readDefaultConfig() { Properties properties = new Props(); //读取系统默认配置 try { properties.load(getResourceAsStream("conf/default.conf")); } catch (Exception e) { logger.warn(Messages.get("info.module.conf.error", "conf/default.conf"), e); } return properties; } /** * <p>readAppConfig.</p> * * @param properties a {@link java.util.Properties} object. * @param confFile a {@link java.lang.String} object. * @return a {@link java.net.URL} object. */ public static URL readAppConfig(Properties properties, String confFile) { Enumeration<URL> urls = IOUtils.getResources(confFile); URL url = null; if (urls.hasMoreElements()) { InputStream in = null; url = urls.nextElement(); if (urls.hasMoreElements()) { List<String> urlList = Lists.newArrayList(toExternalForm(url)); while (urls.hasMoreElements()) { urlList.add(urls.nextElement().toExternalForm()); } String errorMsg = Messages.get("info.load.config.multi.error", StringUtils.join(urlList, LINE_SEPARATOR)); logger.error(errorMsg); throw new ConfigErrorException(errorMsg); } try { logger.trace(Messages.get("info.load", toExternalForm(url))); in = url.openStream(); } catch (IOException e) { logger.error(Messages.get("info.load.error", toExternalForm(url))); } loadProperties(in, properties, url); } else { logger.warn(Messages.get("info.load.error.not.found", confFile)); } return url; } /** * <p>reconfigure.</p> */ public void reconfigure() { addons = Sets.newLinkedHashSet(); excludes = Sets.newLinkedHashSet(); config = new ExcludeResourceConfig(excludes); srcProperties = Maps.newLinkedHashMap(); configure(); } /** * <p>configure.</p> */ @SuppressWarnings("unchecked") protected void configure() { Properties properties = readDefaultConfig(); Properties appProperties = new Props(); List<String> appConf = Lists.newArrayListWithExpectedSize(configFiles.length); for (String conf : configFiles) { //读取应用程序配置 URL appCfgUrl = readAppConfig(appProperties, conf); appConf.add(toExternalForm(appCfgUrl)); } //获取应用程序模式 try { mode = Mode.valueOf(appProperties.getProperty("app.mode").toUpperCase()); } catch (Exception e) { mode = Mode.PRODUCT; } //设置应用程序名称 setApplicationName(StringUtils.defaultString(appProperties.getProperty("app.name"), DEFAULT_APP_NAME)); applicationVersion = appProperties.getProperty("app.version"); if (StringUtils.isBlank(applicationVersion)) { applicationVersion = new UnknownVersion(); } //配置日志器 configureLogger(appProperties); if (!isInitialized()) Ameba.printInfo(); logger.info(Messages.get("info.init")); logger.info(Messages.get("info.app.conf", appConf)); //读取模式配置 readModeConfig(properties, mode); //读取模块配置 readModuleConfig(properties, getMode().isDev()); properties.putAll(appProperties); srcProperties.putAll((Map) properties); setEnvironmentConfig(srcProperties); addOnSetup(srcProperties); //转换jersey配置项 convertJerseyConfig(srcProperties); //将临时配置对象放入应用程序配置 addProperties(srcProperties); srcProperties = Collections.unmodifiableMap(srcProperties); registerBinder(); configureExclude(srcProperties); //配置资源 configureResource(); //配置特性 configureFeature(srcProperties); //配置服务器相关 configureServer(); //清空临时读取的配置 properties.clear(); scanClasses(); addOnDone(); addons = Collections.unmodifiableSet(addons); excludes = Collections.unmodifiableSet(excludes); logger.info(Messages.get("info.feature.load")); } private void configureExclude(Map<String, Object> configMap) { String ex = (String) configMap.get(EXCLUDES_KEY); if (StringUtils.isNotBlank(ex)) { addExcludes(ex); } configMap.keySet().stream() .filter(key -> key.startsWith(EXCLUDES_KEY_PREFIX)) .forEachOrdered(key -> addExcludes((String) configMap.get(key))); logger.debug(Messages.get("info.exclude.classes", excludes)); } private void addExcludes(String ex) { if (StringUtils.isNotBlank(ex)) { for (String e : ex.split(",")) { if (StringUtils.isNotBlank(e)) { excludes.add(e); } } } } private void scanClasses() { if (SCAN_CLASSES_CACHE_FILE == null) { SCAN_CLASSES_CACHE_FILE = IOUtils.getResource("/").getPath() + "conf/classes_" + getApplicationVersion() + ".list"; } URL cacheList = IOUtils.getResource(SCAN_CLASSES_CACHE_FILE); if (cacheList == null || getMode().isDev()) { logger.debug(Messages.get("info.scan.classes")); PackageScanner scanner = new PackageScanner(scanPackages); scanner.scan(); if (getMode().isDev()) return; OutputStream out = null; try { File cacheFile = new File(SCAN_CLASSES_CACHE_FILE); if (cacheFile.isDirectory()) { FileUtils.deleteQuietly(cacheFile); } out = FileUtils.openOutputStream(cacheFile); IOUtils.writeLines(scanner.getAcceptClasses(), null, out, Charsets.UTF_8); } catch (IOException e) { logger.error(Messages.get("info.write.class.cache.error"), e); } finally { closeQuietly(out); } scanner.clear(); } else { logger.debug(Messages.get("info.read.class.cache")); InputStream in = null; try { in = cacheList.openStream(); BufferedReader reader = new BufferedReader(new InputStreamReader(in)); if (reader.ready()) { String className = reader.readLine(); while (className != null) { if (StringUtils.isBlank(className)) continue; String fileName = className.replace(".", "/").concat(".class"); final InputStream fin = IOUtils.getResourceAsStream(fileName); ClassInfo info = new ClassInfo(className.substring(className.lastIndexOf(".") + 1).concat(".class")) { @Override public InputStream getFileStream() { return fin; } @Override public void closeFileStream() { closeQuietly(fin); } }; SystemEventBus.publish(new ClassFoundEvent(info, true)); info.closeFileStream(); className = reader.readLine(); } } closeQuietly(reader); } catch (IOException e) { logger.error(Messages.get("info.read.class.cache.error"), e); } finally { closeQuietly(in); } } } private void registerBinder() { register(Requests.BindRequest.class); register(SysEventListener.class); register(new AbstractBinder() { @Override protected void configure() { bind(Application.this).to(Application.class).proxy(false); bind(mode).to(Mode.class).proxy(false); } }); } /** * <p>getApplicationName.</p> * * @return a {@link java.lang.String} object. */ public String getApplicationName() { return config.getApplicationName(); } /** * <p>setApplicationName.</p> * * @param applicationName a {@link java.lang.String} object. * @return a {@link ameba.core.Application} object. */ public Application setApplicationName(String applicationName) { config.setApplicationName(applicationName); return this; } /** * <p>Getter for the field <code>config</code>.</p> * * @return a {@link org.glassfish.jersey.server.ResourceConfig} object. */ public ResourceConfig getConfig() { return config; } /** * <p>addOnSetup.</p> * * @param configMap a {@link java.util.Map} object. */ protected void addOnSetup(Map<String, Object> configMap) { Set<SortEntry> addOnSorts = Sets.newTreeSet(); for (String key : configMap.keySet()) { if (key.startsWith(ADDON_CONF_PREFIX)) { String className = (String) configMap.get(key); if (StringUtils.isNotBlank(className)) { String name = key.substring(ADDON_CONF_PREFIX.length()); int sortSp = name.lastIndexOf(">"); Integer sortPriority = 1000; if (sortSp != -1) { String sortStr = name.substring(sortSp + 1); if (sortStr.equalsIgnoreCase("last")) { sortPriority = Integer.MAX_VALUE; } else { sortPriority = Ints.tryParse(sortStr); if (sortPriority == null || sortPriority < 0 || sortPriority > Integer.MAX_VALUE) { throw new ConfigErrorException( Messages.get("info.addon.key.priority.error", Integer.MAX_VALUE, key) ); } } } if (sortSp != -1) { name = name.substring(0, sortSp); } addOnSorts.add(new SortEntry(sortPriority, className, name, key)); } } } for (SortEntry entry : addOnSorts) { logger.debug(Messages.get("info.addon.register.item", entry.key, entry.className)); try { Class addOnClass = ClassUtils.getClass(entry.className); if (Addon.class.isAssignableFrom(addOnClass)) { Addon addon = (Addon) addOnClass.newInstance(); if (addon.isEnabled(this) && addons.add(addon)) { addon.setup(this); } } else { throw new ConfigErrorException(Messages.get("info.addon.register.error.interface", entry.name, entry.key)); } } catch (ClassNotFoundException e) { throw new ConfigErrorException(Messages.get("info.addon.register.error.not.found", entry.name, entry.key)); } catch (InstantiationException | IllegalAccessException e) { throw new ConfigErrorException(Messages.get("info.addon.register.error.init", entry.name, entry.key)); } catch (Exception e) { logger.error(Messages.get("info.addon.register.error", entry.name, entry.key), e); } } } /** * <p>addOnDone.</p> */ protected void addOnDone() { for (Addon addon : addons) { try { addon.done(this); } catch (Exception e) { logger.error(Messages.get("info.addon.error", addon.getClass().getName()), e); } } } /** * <p>Getter for the field <code>timestamp</code>.</p> * * @return a long. */ public long getTimestamp() { return timestamp; } /** * <p>configureFeature.</p> * * @param configMap a {@link java.util.Map} object. */ protected void configureFeature(Map<String, Object> configMap) { logger.debug(Messages.get("info.feature.register")); int suc = 0, fail = 0, beak = 0; Set<FeatureEntry> featureEntries = Sets.newTreeSet(); for (String key : configMap.keySet()) { if (key.startsWith(REGISTER_CONF_PREFIX)) { String className = (String) configMap.get(key); if (StringUtils.isNotBlank(className)) { String name = key.substring(REGISTER_CONF_PREFIX.length()); int sortSp = name.lastIndexOf(">"); Integer sortPriority = 1000; if (sortSp != -1) { String sortStr = name.substring(sortSp + 1); sortSp = name.lastIndexOf("!"); if (sortSp != -1) sortStr = sortStr.substring(0, sortSp); if (sortStr.equalsIgnoreCase("last")) { sortPriority = Integer.MAX_VALUE; } else { sortPriority = Ints.tryParse(sortStr); if (sortPriority == null || sortPriority < 0 || sortPriority > Integer.MAX_VALUE) { throw new ConfigErrorException( Messages.get("info.feature.key.priority.error", Integer.MAX_VALUE, key) ); } } } int prioritySp = name.lastIndexOf("!"); Integer diPriority = ContractProvider.NO_PRIORITY; if (prioritySp != -1) { diPriority = Ints.tryParse(name.substring(prioritySp + 1)); if (diPriority == null || diPriority < 0 || diPriority > Integer.MAX_VALUE) { throw new ConfigErrorException( Messages.get("info.feature.key.priority.error", Integer.MAX_VALUE, key) ); } } if (prioritySp != -1 || sortSp != -1) { if (prioritySp > sortSp) { name = name.substring(0, sortSp); } else if (prioritySp < sortSp) { if (prioritySp != -1) { name = name.substring(0, prioritySp); } else { name = name.substring(0, sortSp); } } } featureEntries.add(new FeatureEntry(diPriority, sortPriority, className, name)); } } } for (FeatureEntry entry : featureEntries) { try { logger.debug(Messages.get("info.feature.register.item", entry.name, entry.className)); Class clazz = ClassUtils.getClass(entry.className); if (isRegistered(clazz)) { beak++; logger.warn(Messages.get("info.feature.exists", entry.name, clazz)); continue; } register(clazz, entry.diPriority); suc++; } catch (ClassNotFoundException e) { fail++; if (!entry.name.startsWith("default.")) logger.error(Messages.get("info.feature.sys.get.error"), e); else logger.warn(Messages.get("info.feature.sys.not.found", entry.className), e); } } String registerStr = StringUtils.deleteWhitespace( StringUtils.defaultIfBlank((String) getProperty("registers"), "")); String[] registers; if (StringUtils.isNotBlank(registerStr)) { registers = registerStr.split(","); for (String register : registers) { try { logger.debug(Messages.get("info.feature.register.item", "registers", register)); Class clazz = ClassUtils.getClass(register); if (isRegistered(clazz)) { beak++; logger.warn(Messages.get("info.feature.exists", register)); continue; } register(clazz); suc++; } catch (ClassNotFoundException e) { fail++; if (!register.startsWith("default.")) logger.error(Messages.get("info.feature.sys.get.error"), e); else logger.warn(Messages.get("info.feature.not.found.error", register), e); } } } logger.info(Messages.get("info.feature.collect", suc, fail, beak)); } private void subscribeResourceEvent() { final Set<ClassInfo> resources = Sets.newLinkedHashSet(); SystemEventBus.subscribe(ClassFoundEvent.class, event -> event.accept(new Acceptable<ClassInfo>() { private boolean isResource(CtClass ctClass) { int modifiers = ctClass.getModifiers(); return !javassist.Modifier.isAbstract(modifiers) && !javassist.Modifier.isInterface(modifiers) && !javassist.Modifier.isAnnotation(modifiers) && !javassist.Modifier.isEnum(modifiers); } @Override public boolean accept(ClassInfo info) { if (info.isPublic()) { CtClass thisClass = info.getCtClass(); if (isResource(thisClass)) { if (info.accpet(ctClass -> ctClass.hasAnnotation(Path.class) || ctClass.hasAnnotation(Provider.class))) { resources.add(info); return true; } } } return false; } })); addons.add(new Addon() { @Override public void done(Application application) { for (ClassInfo info : resources) { Class clazz = info.toClass(); if (!isRegistered(clazz)) register(clazz); } resources.clear(); } }); } @SuppressWarnings("unchecked") private void configureResource() { String[] packages = StringUtils.deleteWhitespace( StringUtils.defaultIfBlank((String) getProperty("resource.packages"), "")).split(","); for (String key : getPropertyNames()) { if (key.startsWith("resource.packages.")) { Object pkgObj = getProperty(key); if (pkgObj instanceof String) { String pkgStr = (String) pkgObj; if (StringUtils.isNotBlank(pkgStr)) { String[] pkgs = StringUtils.deleteWhitespace(pkgStr).split(","); for (String pkg : pkgs) { if (!ArrayUtils.contains(packages, pkg)) packages = ArrayUtils.add(packages, pkg); } } } } } packages = ArrayUtils.removeElement(packages, ""); if (packages.length > 0) { logger.info(Messages.get("info.configure.resource.package", StringUtils.join(packages, "," + LINE_SEPARATOR))); } else { logger.warn(Messages.get("info.configure.resource.package.none")); } packages(packages); subscribeResourceEvent(); } private void setEnvironmentConfig(Map<String, Object> configMap) { configMap.forEach((key, value) -> { if (key.startsWith("env.") && value instanceof String) { System.setProperty(key.substring(4), (String) value); } }); } private void convertJerseyConfig(Map<String, Object> configMap) { Map<String, Field> staticFieldsMap = Maps.newLinkedHashMap(); Stream.of(ServerProperties.class.getDeclaredFields()) .filter((field) -> Modifier.isStatic(field.getModifiers())) .forEach((field) -> staticFieldsMap.put(field.getName(), field)); List<String> removeKeys = Lists.newArrayList(); Map<String, Object> map = Maps.newLinkedHashMap(); //进行jersey配置项转化 configMap.forEach((key, value) -> { if (key.startsWith(JERSEY_CONF_NAME_PREFIX)) { String name = key.substring(JERSEY_CONF_NAME_PREFIX.length()); //转化键到jersey配置 name = name.replace(".", "_").toUpperCase(); Field filed = staticFieldsMap.get(name); if (null != filed) { filed.setAccessible(true); try { map.put((String) filed.get(null), value); removeKeys.add(key); } catch (IllegalAccessException e) { logger.error(Messages.get("info.config.error.key", key), e); } } } }); map.put(ServerProperties.PROCESSING_RESPONSE_ERRORS_ENABLED, "true"); map.put(ServerProperties.MOXY_JSON_FEATURE_DISABLE, "true"); map.put(ServerProperties.JSON_PROCESSING_FEATURE_DISABLE, "true"); //移除转化需要的临时属性 removeKeys.forEach(configMap::remove); //将转化的jersey配置放入临时配置对象 configMap.putAll(map); //清空转化的配置 map.clear(); } private void configureServer() { jmxEnabled = Boolean.parseBoolean((String) getProperty("jmx.enabled")); if (jmxEnabled && getProperty(ServerProperties.MONITORING_STATISTICS_MBEANS_ENABLED) == null) property(ServerProperties.MONITORING_STATISTICS_MBEANS_ENABLED, jmxEnabled); subscribeServerEvent(); } private void subscribeServerEvent() { SystemEventBus.subscribe(StartupEvent.class, new Listener<StartupEvent>() { final String lineStart = "- "; final String lineChild = " >"; final StringBuilder builder = new StringBuilder(); void appendInfo(String key, Object... value) { builder.append(lineStart) .append(Messages.get(key, value)) .append(LINE_SEPARATOR); } void appendVisitUrl(Connector connector) { String httpStart = "http" + (connector.isSecureEnabled() ? "s" : "") + "://"; if (connector.getHost().equals("0.0.0.0")) { try { Enumeration netInterfaces = NetworkInterface.getNetworkInterfaces(); while (netInterfaces.hasMoreElements()) { NetworkInterface ni = (NetworkInterface) netInterfaces .nextElement(); Enumeration nii = ni.getInetAddresses(); while (nii.hasMoreElements()) { InetAddress ip = (InetAddress) nii.nextElement(); if (ip instanceof Inet4Address) { String ipAddr = ip.getHostAddress(); if (ipAddr != null && !ipAddr.equals("127.0.0.1")) { builder.append(LINE_SEPARATOR) .append(lineStart) .append(lineChild) .append(httpStart) .append(ip.getHostAddress()) .append(":") .append(connector.getPort()) .append("/"); } } } } } catch (SocketException e) { // noop } builder.append(LINE_SEPARATOR) .append(lineStart) .append(lineChild) .append(httpStart) .append("localhost:") .append(connector.getPort()) .append("/") .append(LINE_SEPARATOR) .append(lineStart) .append(lineChild) .append(httpStart) .append("127.0.0.1:") .append(connector.getPort()) .append("/"); } else { builder.append(LINE_SEPARATOR) .append(lineStart) .append(lineChild) .append(connector.getHttpServerBaseUri()); } } @Override public void onReceive(StartupEvent event) { Application.this.container = event.getContainer(); if (!isInitialized()) { Runtime r = Runtime.getRuntime(); r.gc(); final String startUsedTime = Times.toDuration(System.currentTimeMillis() - timestamp); builder.append(LINE_SEPARATOR) .append(INFO_SPLITTER) .append(LINE_SEPARATOR); appendInfo("info.ameba.version", Ameba.getVersion()); appendInfo("info.http.container", StringUtils.defaultString(container.getType(), "Unknown")); appendInfo("info.start.time", startUsedTime); appendInfo("info.app.name", getApplicationName()); appendInfo("info.app.version", getApplicationVersion()); appendInfo( "info.memory.usage", FileUtils.byteCountToDisplaySize((r.totalMemory() - r.freeMemory())), FileUtils.byteCountToDisplaySize(r.maxMemory()) ); appendInfo("info.jmx.enabled", Messages.get("info.enabled." + isJmxEnabled())); appendInfo("info.app.mode", Messages.get("info.app.mode." + getMode().name().toLowerCase())); builder.append(lineStart) .append(Messages.get("info.locations")); List<Connector> connectors = getConnectors(); if (connectors != null && connectors.size() > 0) { connectors.forEach(this::appendVisitUrl); } else { builder.append(LINE_SEPARATOR) .append(lineStart) .append(lineChild) .append(Messages.get("info.locations.none")); logger.warn(Messages.get("info.connector.none")); } builder.append(LINE_SEPARATOR) .append(INFO_SPLITTER); logger.info(Messages.get("info.started")); logger.info(builder.toString()); } initialized = true; } }); } /** * <p>register.</p> * * @param componentClass a {@link java.lang.Class} object. * @return a {@link ameba.core.Application} object. */ public Application register(Class<?> componentClass) { config.register(componentClass); return this; } /** * <p>register.</p> * * @param component a {@link java.lang.Object} object. * @return a {@link ameba.core.Application} object. */ public Application register(Object component) { config.register(component); return this; } /** * <p>registerClasses.</p> * * @param classes a {@link java.lang.Class} object. * @return a {@link ameba.core.Application} object. */ public Application registerClasses(Class<?>... classes) { config.registerClasses(classes); return this; } /** * <p>register.</p> * * @param component a {@link java.lang.Object} object. * @param bindingPriority a int. * @return a {@link ameba.core.Application} object. */ public Application register(Object component, int bindingPriority) { config.register(component, bindingPriority); return this; } /** * <p>getConfiguration.</p> * * @return a {@link org.glassfish.jersey.server.ServerConfig} object. */ public ServerConfig getConfiguration() { return config.getConfiguration(); } /** * <p>getClassLoader.</p> * * @return a {@link java.lang.ClassLoader} object. */ public ClassLoader getClassLoader() { return config.getClassLoader(); } /** * <p>setClassLoader.</p> * * @param classLoader a {@link java.lang.ClassLoader} object. * @return a {@link ameba.core.Application} object. */ public Application setClassLoader(ClassLoader classLoader) { config.setClassLoader(classLoader); return this; } /** * <p>registerInstances.</p> * * @param instances a {@link java.lang.Object} object. * @return a {@link ameba.core.Application} object. */ public Application registerInstances(Object... instances) { config.registerInstances(instances); return this; } /** * <p>packages.</p> * * @param packages a {@link java.lang.String} object. * @return a {@link ameba.core.Application} object. */ public Application packages(String... packages) { if (scanPackages == null) { scanPackages = Sets.newHashSet(); } Collections.addAll(scanPackages, packages); return this; } /** * <p>getPackages.</p> * * @return a {@link java.util.Set} object. */ public Set<String> getPackages() { return scanPackages; } /** * <p>register.</p> * * @param component a {@link java.lang.Object} object. * @param contracts a {@link java.util.Map} object. * @return a {@link ameba.core.Application} object. */ public Application register(Object component, Map<Class<?>, Integer> contracts) { config.register(component, contracts); return this; } /** * <p>getResources.</p> * * @return a {@link java.util.Set} object. */ public Set<Resource> getResources() { return config.getResources(); } /** * <p>getSingletons.</p> * * @return a {@link java.util.Set} object. */ public Set<Object> getSingletons() { return config.getSingletons(); } /** * <p>getPropertyNames.</p> * * @return a {@link java.util.Collection} object. */ public Collection<String> getPropertyNames() { return config.getPropertyNames(); } /** * <p>register.</p> * * @param componentClass a {@link java.lang.Class} object. * @param contracts a {@link java.lang.Class} object. * @return a {@link ameba.core.Application} object. */ public Application register(Class<?> componentClass, Class<?>... contracts) { config.register(componentClass, contracts); return this; } /** * <p>getClasses.</p> * * @return a {@link java.util.Set} object. */ public Set<Class<?>> getClasses() { return config.getClasses(); } /** * <p>register.</p> * * @param component a {@link java.lang.Object} object. * @param contracts a {@link java.lang.Class} object. * @return a {@link ameba.core.Application} object. */ public Application register(Object component, Class<?>... contracts) { config.register(component, contracts); return this; } /** * <p>isRegistered.</p> * * @param componentClass a {@link java.lang.Class} object. * @return a boolean. */ public boolean isRegistered(Class<?> componentClass) { return config.isRegistered(componentClass); } /** * <p>registerResources.</p> * * @param resources a {@link java.util.Set} object. * @return a {@link ameba.core.Application} object. */ public Application registerResources(Set<Resource> resources) { config.registerResources(resources); return this; } /** * <p>isEnabled.</p> * * @param feature a {@link javax.ws.rs.core.Feature} object. * @return a boolean. */ public boolean isEnabled(Feature feature) { return config.isEnabled(feature); } /** * <p>getContracts.</p> * * @param componentClass a {@link java.lang.Class} object. * @return a {@link java.util.Map} object. */ public Map<Class<?>, Integer> getContracts(Class<?> componentClass) { return config.getContracts(componentClass); } /** * <p>getProperty.</p> * * @param name a {@link java.lang.String} object. * @return a {@link java.lang.Object} object. */ public Object getProperty(String name) { return config.getProperty(name); } /** * <p>addProperties.</p> * * @param properties a {@link java.util.Map} object. * @return a {@link ameba.core.Application} object. */ public Application addProperties(Map<String, Object> properties) { config.addProperties(properties); return this; } /** * <p>registerFinder.</p> * * @param resourceFinder a {@link org.glassfish.jersey.server.ResourceFinder} object. * @return a {@link ameba.core.Application} object. */ public Application registerFinder(ResourceFinder resourceFinder) { config.registerFinder(resourceFinder); return this; } /** * <p>isInitialized.</p> * * @return a boolean. */ public boolean isInitialized() { return initialized; } /** * <p>register.</p> * * @param componentClass a {@link java.lang.Class} object. * @param bindingPriority a int. * @return a {@link ameba.core.Application} object. */ public Application register(Class<?> componentClass, int bindingPriority) { config.register(componentClass, bindingPriority); return this; } /** * <p>registerResources.</p> * * @param resources a {@link org.glassfish.jersey.server.model.Resource} object. * @return a {@link ameba.core.Application} object. */ public Application registerResources(Resource... resources) { config.registerResources(resources); return this; } /** * <p>getRuntimeType.</p> * * @return a {@link javax.ws.rs.RuntimeType} object. */ public RuntimeType getRuntimeType() { return config.getRuntimeType(); } /** * <p>isRegistered.</p> * * @param component a {@link java.lang.Object} object. * @return a boolean. */ public boolean isRegistered(Object component) { return config.isRegistered(component); } /** * <p>getProperties.</p> * * @return a {@link java.util.Map} object. */ public Map<String, Object> getProperties() { return config.getProperties(); } /** * <p>setProperties.</p> * * @param properties a {@link java.util.Map} object. * @return a {@link ameba.core.Application} object. */ public Application setProperties(Map<String, ?> properties) { config.setProperties(properties); return this; } /** * <p>property.</p> * * @param name a {@link java.lang.String} object. * @param value a {@link java.lang.Object} object. * @return a {@link ameba.core.Application} object. */ public Application property(String name, Object value) { config.property(name, value); return this; } /** * <p>registerInstances.</p> * * @param instances a {@link java.util.Set} object. * @return a {@link ameba.core.Application} object. */ public Application registerInstances(Set<Object> instances) { config.registerInstances(instances); return this; } /** * <p>getInstances.</p> * * @return a {@link java.util.Set} object. */ public Set<Object> getInstances() { return config.getInstances(); } /** * <p>register.</p> * * @param componentClass a {@link java.lang.Class} object. * @param contracts a {@link java.util.Map} object. * @return a {@link ameba.core.Application} object. */ public Application register(Class<?> componentClass, Map<Class<?>, Integer> contracts) { config.register(componentClass, contracts); return this; } /** * <p>registerClasses.</p> * * @param classes a {@link java.util.Set} object. * @return a {@link ameba.core.Application} object. */ public Application registerClasses(Set<Class<?>> classes) { config.registerClasses(classes); return this; } /** * <p>isEnabled.</p> * * @param featureClass a {@link java.lang.Class} object. * @return a boolean. */ public boolean isEnabled(Class<? extends Feature> featureClass) { return config.isEnabled(featureClass); } /** * <p>isProperty.</p> * * @param name a {@link java.lang.String} object. * @return a boolean. */ public boolean isProperty(String name) { return config.isProperty(name); } /** * <p>Getter for the field <code>configFiles</code>.</p> * * @return an array of {@link java.lang.String} objects. */ public String[] getConfigFiles() { return configFiles; } /** * <p>Getter for the field <code>mode</code>.</p> * * @return a {@link ameba.core.Application.Mode} object. */ public Mode getMode() { return mode; } /** * <p>Getter for the field <code>applicationVersion</code>.</p> * * @return a {@link java.lang.CharSequence} object. */ public CharSequence getApplicationVersion() { return applicationVersion; } /** * <p>getConnectors.</p> * * @return a {@link java.util.List} object. */ public List<Connector> getConnectors() { return container.getConnectors(); } /** * <p>Getter for the field <code>container</code>.</p> * * @return a {@link ameba.container.Container} object. */ public Container getContainer() { return container; } /** * <p>isJmxEnabled.</p> * * @return a boolean. */ public boolean isJmxEnabled() { return jmxEnabled; } /** * 设置日志器 */ private void configureLogger(Properties properties) { //set logback config file URL loggerConfigFile = getResource("conf/logback_" + getMode().name().toLowerCase() + ".groovy"); LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory(); context.reset(); context.putObject("properties", properties); if (loggerConfigFile != null) { GafferUtil.runGafferConfiguratorOn(context, this, loggerConfigFile); } URL userLoggerConfigFile = getResource(StringUtils.defaultIfBlank( properties.getProperty("logger.config.file"), "conf/" + DEFAULT_LOGBACK_CONF)); if (userLoggerConfigFile != null) { GafferUtil.runGafferConfiguratorOn(context, this, userLoggerConfigFile); } if (ids != null && ids.length > 0) { String confDir = "conf/log/"; String[] logConf = DEFAULT_LOGBACK_CONF.split("\\."); String logConfPrefix = logConf[0]; String logConfSuffix = "." + logConf[1]; for (String id : ids) { URL configFile = getResource(confDir + logConfPrefix + "_" + id + logConfSuffix); if (configFile != null) GafferUtil.runGafferConfiguratorOn(context, this, configFile); } } //java.util.logging.Logger proxy java.util.logging.Logger rootLogger = LogManager.getLogManager().getLogger(""); Handler[] handlers = rootLogger.getHandlers(); for (Handler handler : handlers) { rootLogger.removeHandler(handler); } SLF4JBridgeHandler.install(); rootLogger.setLevel(Level.ALL); String appPackage = properties.getProperty("app.package"); if (StringUtils.isBlank(appPackage)) { logger.warn(Messages.get("warn.app.package.not.config")); } } /** * <p>Getter for the field <code>srcProperties</code>.</p> * * @return a {@link java.util.Map} object. */ public Map<String, Object> getSrcProperties() { return srcProperties; } /** * <p>Getter for the field <code>excludes</code>.</p> * * @return a {@link java.util.Set} object. */ public Set<String> getExcludes() { return excludes; } /** * <p>Getter for the field <code>addons</code>.</p> * * @return a {@link java.util.Set} object. */ public Set<Addon> getAddons() { return addons; } public enum Mode { DEV, PRODUCT, TEST; public boolean isDev() { return this == DEV; } public boolean isProd() { return this == PRODUCT; } public boolean isTest() { return this == TEST; } } public static final class Props extends LinkedProperties { @Override public synchronized Object put(Object key, Object value) { if (value instanceof String) { String str = (String) value; str = str.trim(); if (str.startsWith("$") && !str.startsWith("$$")) { str = str.substring(1); value = System.getProperty(str); if (StringUtils.isBlank((CharSequence) value)) { value = System.getenv(str); } } } return super.put(key, value); } } @Provider protected static class SysEventListener implements ApplicationEventListener { @Inject protected ServiceLocator serviceLocator; @Override public void onEvent(ApplicationEvent event) { SystemEventBus.publish(new ameba.core.event.ApplicationEvent(event)); } @Override public RequestEventListener onRequest(org.glassfish.jersey.server.monitoring.RequestEvent requestEvent) { AmebaFeature.publishEvent(new RequestEvent(requestEvent)); return event -> AmebaFeature.publishEvent(new RequestEvent(event)); } } private class SortEntry implements Comparable<SortEntry> { Integer sortPriority; String className; String name; String key; private SortEntry(Integer sortPriority, String className, String name) { this.sortPriority = sortPriority; this.className = className; this.name = name; } private SortEntry(Integer sortPriority, String className, String name, String key) { this.sortPriority = sortPriority; this.className = className; this.name = name; this.key = key; } @Override public int compareTo(SortEntry entry) { if (this.className.equals(entry.className)) { return 0; } int index = Integer.compare(this.sortPriority, entry.sortPriority); if (index == 0) return 1; return index; } } private class FeatureEntry extends SortEntry { int diPriority; private FeatureEntry(int diPriority, Integer sortPriority, String className, String name) { super(sortPriority, className, name); this.diPriority = diPriority; } } public class UnknownVersion implements CharSequence { String version = "Unknown"; @Override public int length() { return version.length(); } @Override public char charAt(int index) { return version.charAt(index); } @Override public CharSequence subSequence(int start, int end) { return version.subSequence(start, end); } @Override public String toString() { return version; } } }