/*******************************************************************************
* Copyright © 2012-2015 eBay Software Foundation
* This program is dual licensed under the MIT and Apache 2.0 licenses.
* Please see LICENSE for more information.
*******************************************************************************/
/**
*
*/
package com.ebay.jetstream.config;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.support.AbstractBeanDefinitionReader;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.context.ApplicationContext;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import com.ebay.jetstream.config.mongo.MongoDbResource;
import com.ebay.jetstream.spring.beans.factory.support.UpdateableListableBeanFactory;
import com.ebay.jetstream.spring.context.support.AbstractUpdateableApplicationContext;
import com.ebay.jetstream.util.CommonUtils;
/**
*
*/
public class Configuration extends AbstractUpdateableApplicationContext {
protected static final Logger LOGGER = LoggerFactory.getLogger(Configuration.class.getName());
/**
* @param clazz
* the class whose package should be searched
* @param name
* the name of the context container, or null. If null, the class name itself is used as container name.
* @return the Spring form of the context container in the given class location.
*/
public static String getClasspathContext(Class<?> clazz, String name) {
String path = "classpath:" + clazz.getCanonicalName().replace('.', '/');
return name == null ? path + ".xml" : path.substring(0, path.lastIndexOf('/') + 1) + name;
}
/**
* Looks for classpath contexts matching the class name or any super class or interface.
*
* @param clazz
* the class leaf to look for contexts
* @return all files matching the class name but ending in .xml for all impls and interfaces
*/
public static List<String> getClasspathContexts(Class<?> clazz) {
List<String> names = new ArrayList<String>();
for (Class<?> current = clazz; current != null; current = current.getSuperclass())
checkClass(names, current);
for (Class<?> current : clazz.getInterfaces())
checkClass(names, current);
return names;
}
/**
* Returns all context files at the given path, across multiple data stores: files, svn/dav, etc.
*
* @param path
* the directory path to look for context files.
* @return all files ending in .xml at the given path.
*/
public static List<String> getContexts(String path) {
try {
ConfigDataSource cds = ConfigUtils.getConfigDataSource(path);
List<String> names = new ArrayList<String>(cds.getStreamLocations());
for (int i = names.size() - 1; i >= 0; i--)
if (!names.get(i).toUpperCase().endsWith(".XML"))
names.remove(i);
return names;
}
catch (Throwable t) {
throw CommonUtils.runtimeException(t);
}
}
public static List<String> getMongoContexts() {
List<String> mongo = new ArrayList<String>();
String mongoInfo = ConfigUtils.getPropOrEnv("MONGO_HOME");
//if (!CommonUtils.isEmptyTrimmed(mongoInfo))
if(mongoInfo != null && !CommonUtils.isEmptyTrimmed(mongoInfo)) {
mongo.add(mongoInfo);
} else {
mongo.add("mongo:null");
}
return mongo;
}
/**
* Returns the ldap urls to get configuration information from. Currently only one ldap url is returned, and only if
* the LDAPINFORMATION property or env var is populated.
*
* @return the list of ldap urls
*/
public static List<String> getLdapContexts(ApplicationInformation ai) {
List<String> ldap = new ArrayList<String>();
String ldapInfo = ConfigUtils.getPropOrEnv("LDAPINFORMATION");
if (ldapInfo != null) {
int indexOfAt = ldapInfo.indexOf('@');
if (indexOfAt != -1 && ai.containsKey("ldapHostPort") && ldapInfo.charAt(indexOfAt + 1) == '/') {
ldapInfo = ldapInfo.substring(0, indexOfAt + 1) + ai.get("ldapHostPort") + ldapInfo.substring(indexOfAt + 1);
}
else if (indexOfAt != -1 && !ai.containsKey("ldapHostPort") && ldapInfo.charAt(indexOfAt + 1) == '/') {
throw new RuntimeException("Ldap host/port not set");
}
if (!ldapInfo.contains("application=") && ai.containsKey("applicationName"))
ldapInfo = addToUrlEnd(ldapInfo, "application=" + ai.get("applicationName"));
if (!ldapInfo.contains("version=") && ai.containsKey("configVersion"))
ldapInfo = addToUrlEnd(ldapInfo, "version=" + ai.get("configVersion"));
if (!ldapInfo.contains("scope="))
ldapInfo = addToUrlEnd(ldapInfo, "scope=" + ai.get("scope"));
}
if (!CommonUtils.isEmptyTrimmed(ldapInfo))
ldap.add(ldapInfo);
return ldap;
}
private static String addToUrlEnd(String begin, String string) {
return begin + (begin.charAt(begin.length() - 1) == '/' ? string : "," + string);
}
private static ApplicationInformation checkAppInfo(ApplicationInformation ai) {
if (ai == null)
throw new NullPointerException("missing ApplicationInformation");
return ai;
}
private static void checkClass(List<String> names, Class<?> clazz) {
if (clazz.getResource(clazz.getSimpleName() + ".xml") != null)
names.add(getClasspathContext(clazz, null));
}
private final ApplicationInformation m_applicationInformation;
private final String[] m_configLocations;
private Resource[] m_configResources;
public Configuration(ApplicationInformation appInfo, String[] configLocations) {
this(null, checkAppInfo(appInfo), configLocations);
}
public Configuration(String[] configLocations) {
this(null, null, configLocations);
}
private Configuration(ApplicationContext parent, ApplicationInformation appInfo, String[] configLocations) {
super(parent);
LOGGER.info("Initializing " + getClass().getSimpleName() + " for application " + appInfo + ", context containers:");
for (String cl : configLocations)
LOGGER.info(" " + cl);
m_applicationInformation = appInfo;
m_configLocations = configLocations;
getConfigResources(); // Initialize config resources
refresh();
// Ldap listens to configuration events as well as broadcast messages
for (Resource resource : getConfigResources()) {
if (resource instanceof MongoDbResource) {
((MongoDbResource) resource).listen(appInfo);
}
}
}
public ApplicationInformation getApplicationInformation() {
return m_applicationInformation;
}
public Resource[] getConfigResources() {
if (m_configResources == null) {
String[] configLocations = m_configLocations;
List<Resource> resources = new ArrayList<Resource>();
for (String configLocation : configLocations)
try {
for (Resource resource : getResources(configLocation))
resources.add(resource);
}
catch (IOException e) {
LOGGER.error("Failed to load " + configLocation + ": " + e);
}
m_configResources = resources.toArray(new Resource[] {});
}
return m_configResources;
}
@Override
public void refresh() throws BeansException, IllegalStateException {
try {
super.refresh();
}
finally {
}
}
@Override
protected Resource getResourceByPath(String path) {
if (m_configResources != null)
for (Resource r : m_configResources)
try {
URL url = r.getURL();
if (url != null && path.equals(url.toExternalForm()))
return r;
}
catch (IOException e) { // NOPMD
// Ignore
}
try {
if (path.startsWith("http:") || path.startsWith("https:"))
return new UrlResource(path);
if (path.startsWith("mongo:") || path.startsWith("mongos:"))
return new MongoDbResource(this, path);
if (path.startsWith("classpath:"))
return new ClassPathResource(path, Configuration.class);
return new FileSystemResource(path);
}
catch (Throwable t) {
LOGGER.error( "Failed to load " + path + ": " + t);
throw CommonUtils.runtimeException(t);
}
}
@Override
protected void loadBeanDefinitions(UpdateableListableBeanFactory beanFactory) throws IOException, BeansException {
AbstractBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
for (Resource resource : getConfigResources()) {
if (resource instanceof MongoDbResource) {
((MongoDbResource) resource).loadBeanDefinitions(beanFactory);
}
else {
beanDefinitionReader.loadBeanDefinitions(resource);
}
}
}
@Override
protected void onRefresh() throws BeansException {
super.onRefresh();
ApplicationInformation ai = getApplicationInformation();
if (ai != null)
ai.onRefresh(this);
}
}