ContextConfig.java [src/csip] Revision: default Date:
/*
* $Id$
*
* This file is part of the Cloud Services Integration Platform (CSIP),
* a Model-as-a-Service framework, API and application suite.
*
* 2012-2022, Olaf David and others, OMSLab, Colorado State University.
*
* OMSLab licenses this file to you under the MIT license.
* See the LICENSE file in the project root for more information.
*/
package csip;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.TreeMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.servlet.ServletContext;
import javax.ws.rs.Path;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.codehaus.jettison.json.JSONArray;
import org.codehaus.jettison.json.JSONException;
import org.codehaus.jettison.json.JSONObject;
import org.glassfish.jersey.media.multipart.MultiPartFeature;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.scanner.ScannerException;
/**
*
* @author od
*/
class ContextConfig {
static final Logger LOG = Config.LOG;
//
static final String API_VERSION = "csip/2.1";
//
static final String KEY_API = "Api";
static final String KEY_CONFIG = "Config";
static final String KEY_SERVICES = "Services";
//
private Map<String, String> conf = new TreeMap<>();
private List<String> services = new ArrayList<>();
/**
* Configuration from a file.
*
* @param ctx
*/
void load(File file) {
if (file == null || !file.exists() || !file.canRead())
return;
try {
init(new FileInputStream(file), file.toString());
} catch (FileNotFoundException E) {
LOG.log(Level.WARNING, "Error", E);
}
}
/**
* Configuration from servlet init parameter.
*
* @param ctx
*/
void load(ServletContext ctx) {
if (ctx == null) {
LOG.log(Level.SEVERE, "No service context");
return;
}
Enumeration<String> params = ctx.getInitParameterNames();
while (params.hasMoreElements()) {
String key = params.nextElement();
conf.put(key, ctx.getInitParameter(key));
}
}
/**
* Configuration from bundled file within the context.
*
* @param ctx
* @param file
*/
void load(ServletContext ctx, String file) {
if (ctx == null) {
LOG.log(Level.SEVERE, "No service context for " + file);
return;
}
if (file.startsWith("/")) {
// this is a file within the context. /META-INF/config.json
InputStream is = ctx.getResourceAsStream(file);
if (is == null) {
return;
}
init(is, file);
} else {
// this is looking for a file csip-abc##1.2.3-1.2.3.json
File f = getContextFile(ctx, file);
if (f != null) {
load(f);
}
}
}
/**
* read the list from a text file "??.services" in webapps.
*
* @param c
*/
void loadServices(ServletContext c) {
try {
File serviceList = getContextFile(c, ".services");
// List to filter on.
if (serviceList == null) {
return;
}
List<String> srv = FileUtils.readLines(serviceList, "utf-8");
for (String s : srv) {
if (!s.isEmpty() && !s.trim().startsWith("#")) {
services.add(s.trim());
}
}
} catch (IOException ex) {
LOG.log(Level.SEVERE, null, ex);
}
}
/**
* Filters the services as specified in config files.
* @param c the servlet context
* @param orig the original set of service classes
*/
static void filterServices(ServletContext c, Set<Class<?>> orig) {
try {
// just to make sure it's in there, even if
// orig is not being filtered. Since it is a Set,
// the class MultiPartFeature will not be in orig
// twice.
orig.add(MultiPartFeature.class);
// Add the internal services if not already in orig.
orig.add(QueueingModelDataService.class);
orig.add(DynamicPyModelDataService.class);
ContextConfig cc = new ContextConfig();
// load services from webapp's <context>.yaml
cc.load(c, ".yaml");
// load services from webapp's <context>.services
cc.loadServices(c);
// It has to be anabled!
if (!Config.getBoolean(Config.CSIP_PUBSUB_ENABLED, false)) {
orig.remove(QueueingModelDataService.class);
}
if (!Config.getBoolean(Config.CSIP_DYNPY_ENABLED, false)) {
orig.remove(DynamicPyModelDataService.class);
}
if (cc.getServices().isEmpty()) {
// nothing to do.
return;
}
// create a lookup table 'service path' -> 'service class'
Map<String, Class<?>> smap = new HashMap<>();
orig.forEach(cl -> {
Path p = cl.getAnnotation(Path.class);
if (p != null) {
String s = p.value();
if (s != null) {
smap.put(s, cl);
}
}
});
Set<Class<?>> filtered_resources = new HashSet<>();
// add internal classes
filtered_resources.add(MultiPartFeature.class);
for (Class<?> cl : orig) {
// all internal core services
if (cl.getCanonicalName().startsWith("csip.")) {
filtered_resources.add(cl);
}
}
for (String s : cc.getServices()) {
// allow for comment lines and empty lines (properties file)
if (s != null && !s.isEmpty()) {
String service = s.trim();
// be more forgiving
if (service.startsWith("/")) {
service = service.substring(1);
}
Class<?> cl = smap.get(service);
if (cl != null) {
// you can only request model and data services to be added.
// using package conventions
if (Utils.isCsipService(cl)) {
filtered_resources.add(cl);
}
} else {
LOG.warning("service not found in context: '" + s.trim() + "', ignoring.");
}
}
}
// swap the set
orig.clear();
orig.addAll(filtered_resources);
} catch (Exception ex) {
LOG.log(Level.WARNING, "Error applying the service filter ", ex);
LOG.info("Using all services.");
}
}
private void init(InputStream is, String file) {
try {
LOG.info(" Loading settings from: " + file);
String ftype = file.substring(file.lastIndexOf('.'));
switch (ftype) {
case ".yaml":
fromYaml(is);
break;
case ".properties":
fromProperties(is);
break;
case ".json":
fromJson(is);
break;
default:
LOG.warning("Illegal configuration file: " + file);
}
} catch (ScannerException E) {
LOG.log(Level.WARNING, "Ignoring config, Error:\n" + E.getMessage());
} catch (Exception E) {
LOG.log(Level.WARNING, "Ignoring config, Error:\n" + E);
} finally {
try {
is.close();
} catch (Exception ex) {
// can be ignored.
}
}
}
/**
* This is config properties only!
*
* @param is
* @throws IOException
*/
private void fromProperties(InputStream is) throws IOException {
Properties p = new Properties();
p.load(is);
if (p.isEmpty())
return;
p.stringPropertyNames().forEach((key) -> {
conf.put(key, p.getProperty(key));
});
}
@SuppressWarnings("unchecked")
private void fromYaml(InputStream is) {
Map<String, Object> yaml = (Map) new Yaml().load(is);
if (!yaml.containsKey(KEY_API))
throw new RuntimeException("Missing Api version: 'Api: " + API_VERSION + "', ignoring.");
String apiversion = (String) yaml.get(KEY_API);
if (!apiversion.equalsIgnoreCase(API_VERSION))
throw new RuntimeException("Invalid Api version: " + apiversion + " ignoring.");
if (!yaml.containsKey(KEY_CONFIG))
throw new RuntimeException("Missing: 'Config: ...', ignoring.");
Map<Object, Object> j = (Map) yaml.get(KEY_CONFIG);
for (Map.Entry<Object, Object> entry : j.entrySet()) {
conf.put(entry.getKey().toString(), entry.getValue().toString());
}
if (yaml.containsKey(KEY_SERVICES)) {
for (Object entry : (List) yaml.get(KEY_SERVICES)) {
services.add(entry.toString());
}
}
}
private void fromJson(InputStream is) throws IOException, JSONException {
JSONObject o = new JSONObject(IOUtils.toString(is, "UTF-8"));
if (o.has(KEY_API)) {
String apiversion = o.getString(KEY_API);
if (!apiversion.equals(API_VERSION))
throw new RuntimeException("Invalid Api version: " + apiversion);
}
JSONObject c = o;
if (o.has(KEY_CONFIG))
c = o.getJSONObject(KEY_CONFIG);
Iterator<?> i = c.keys();
while (i.hasNext()) {
String k = i.next().toString();
String v = c.getString(k);
conf.put(k, v);
}
if (o.has(KEY_SERVICES)) {
JSONArray arr = o.getJSONArray(KEY_SERVICES);
for (int j = 0; j < arr.length(); j++) {
services.add(arr.getString(j));
}
}
}
private File getContextFile(ServletContext c, String ext) {
if (c == null)
return null;
String s = c.getRealPath("/WEB-INF");
if (s != null) {
File f = new File(s).getParentFile();
if (f != null && f.exists()) {
File file = new File(f.getParentFile(), f.getName() + ext);
if (file.exists() && file.canRead())
return file;
}
}
return null;
}
Map<String, String> getConfig() {
return conf;
}
List<String> getServices() {
return services;
}
}