All Downloads are FREE. Search and download functionalities are using the official Maven repository.

nl.vpro.web.servlet.ConfigurationServlet Maven / Gradle / Ivy

package nl.vpro.web.servlet;

import com.fasterxml.jackson.core.JsonEncoding;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;
import org.checkerframework.checker.nullness.qual.NonNull;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Properties;
import java.util.function.Function;


/**
 * Serves a 'configuration.js' for javascript clients.
 * Expects an init param "name" with that it determines the 'environment' via
 * 
    *
  • Either it is the system property vpro.[name].env
  • *
  • If that is missing it is the application context variable vpro/[name]/env
  • *
  • If that is missing too, it is {@link Environment#PROD}
  • *
* Then it loads properties from all of: *
    *
  • The resource [name].properties
  • *
  • The resource [name].[env];properties (if exists)
  • *
  • The file /WEB-INF/classes/[name].properties (if exists)
  • *
  • The file /WEB-INF/classes/[name].[env];properties (if exists)
  • *
  • The file ${user.home}/conf/[name].properties (if exists)
  • *
  • The file ${user.home}/conf/[name].[env];properties (if exists)
  • *
* while it overrides already defined ones. *

* The result is then, together with some other properties returned in a javascript structure. * * @author Michiel Meeuwissen * @since 0.3 */ public class ConfigurationServlet extends HttpServlet { private static String ATTRIBUTE_NAME = ConfigurationServlet.class.getName() + ".configuration"; protected String name; protected static enum Environment { PROD, TEST, ACC, DEV } protected Map> systemPropMap = new HashMap<>(); public static Context getContext() throws NamingException { InitialContext context = new InitialContext(); return (Context)context.lookup("java:comp/env"); } public static Map getProperties(ServletContext context, HttpServletRequest request) { return ((Function>)context.getAttribute(ATTRIBUTE_NAME)).apply(request); } @Override public void init() { ServletConfig config = getServletConfig(); String name = config.getInitParameter("name"); if(name == null || name.length() == 0) { throw new IllegalArgumentException(String.format("Must provide a valid configuration name, got: %s", name)); } this.name = name; config.getServletContext().setAttribute(ATTRIBUTE_NAME, new Function>() { @Override public Map apply(HttpServletRequest req) { try { return ConfigurationServlet.this.getProperties(req); } catch (IOException e) { throw new RuntimeException(e); } } }); } @Override protected long getLastModified(HttpServletRequest req) { long lastModified = -1; String env = getEnvironment().toString().toLowerCase(); for(String s : new String[]{ getServletContext().getRealPath("/WEB-INF/classes/" + name + ".properties"), getServletContext().getRealPath("/WEB-INF/classes/" + name + "." + env + ".properties"), System.getProperty("user.home") + File.separator + "conf" + File.separator + name + ".properties", System.getProperty("user.home") + File.separator + "conf" + File.separator + name + "." + env + ".properties"}) { if(s != null) { File f = new File(s); if(f.canRead() && f.lastModified() > lastModified) { lastModified = f.lastModified(); } } } return lastModified; } @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { resp.setContentType("application/json"); Map props = getProperties(req); if(getLastModified(req) == -1) { resp.setDateHeader("Expires", System.currentTimeMillis() + Integer.parseInt((String)props.get("MaxAge")) * 1000); } else { resp.setHeader("Cache-Control", "max-age=" + props.get("MaxAge") + ", must-revalidate, public"); } write(req.getParameter("var"), resp.getOutputStream(), getSystem(req), getProperties(req)); } protected void write( String varName, @NonNull OutputStream outputStream, @NonNull Map system, @NonNull Map props ) throws IOException { JsonFactory jfactory = new JsonFactory(); JsonGenerator w = jfactory.createGenerator(outputStream, JsonEncoding.UTF8); if(varName != null) { outputStream.write(("var " + varName + " = ").getBytes(StandardCharsets.UTF_8)); } outputStream.flush(); w.writeStartObject(); for(Map.Entry e : system.entrySet()) { w.writeStringField(e.getKey(), e.getValue()); } //w.key("version").value(getVersion()); w.writeFieldName("configuration"); w.writeStartObject(); for(Map.Entry prop : props.entrySet()) { w.writeObjectField(prop.getKey(), prop.getValue()); } w.writeEndObject(); w.writeEndObject(); w.flush(); if(varName != null) { outputStream.write(';'); } } protected void merge(HttpServletRequest req, InputStream is, Map map) throws IOException { if(is == null) { return; } for(Map.Entry e : getProperties(is).entrySet()) { String value = e.getValue(); for(Map.Entry sp : getSystem(req).entrySet()) { value = value.replaceAll("\\$\\{" + sp.getKey() + "\\}", sp.getValue()); } map.put(e.getKey(), value); } } protected void merge(HttpServletRequest req, File file, Map map) throws IOException { if(file.canRead()) { merge(req, new FileInputStream(file), map); } } protected void mergeServletContextResource(HttpServletRequest req, String path, Map map) throws IOException { String file = getServletContext().getRealPath(path); if (file != null) { merge(req, new File(file), map); } } private String getRequestKey(HttpServletRequest req) { String scheme = req != null ? req.getHeader("X-Forwarded-Proto") : null; if (scheme == null && req != null) { scheme = req.getScheme(); } return req == null ? "NULL" : (scheme + "://" + req.getServerName() + req.getServerPort() +req.getContextPath()); } protected Map getSystem(HttpServletRequest req) throws IOException { String key = getRequestKey(req); Map systemProps = systemPropMap.get(key); if(systemProps == null) { systemProps = new LinkedHashMap<>(); systemProps.put("env", getEnvironment().toString()); if(req != null) { int port = req.getServerPort(); String scheme = req.getHeader("X-Forwarded-Proto"); if (scheme == null) { scheme = req.getScheme(); } systemProps.put("thisServer", scheme + "://" + req.getServerName() + (port == 80 ? "" : ":" + port) + req.getContextPath()); } URL u = getServletContext().getResource("/version.properties"); if(u != null) { systemProps.putAll(getProperties(u.openStream())); } systemPropMap.put(key, systemProps); } return systemProps; } protected Environment getEnvironment() { String en = System.getProperty("vpro." + name + ".env"); if(en != null) { return Environment.valueOf(en.toUpperCase()); } try { Context env = getContext(); String lookupName = env.composeName("env", "vpro/" + name); Object value = env.lookup(lookupName); if(value instanceof String) { return Environment.valueOf(((String)value).toUpperCase()); } } catch(NamingException e) { // } return Environment.PROD; } protected Map getProperties(HttpServletRequest req) throws IOException { Map res = new LinkedHashMap<>(); String env = getEnvironment().toString().toLowerCase(); merge(req, getClass().getResourceAsStream("/" + name + ".properties"), res); merge(req, getClass().getResourceAsStream("/" + name + "." + env + ".properties"), res); mergeServletContextResource(req, "/WEB-INF/classes/" + name + ".properties", res); mergeServletContextResource(req, "/WEB-INF/classes/" + name + "." + env + ".properties", res); String home = System.getProperty("user.home"); merge(req, new File(home + File.separator + "conf" + File.separator + name + ".properties"), res); merge(req, new File(home + File.separator + "conf" + File.separator + name + "." + env + ".properties"), res); return res; } /** * Reads in properties. It uses the parser of {@link Properties}, but the returned Map has predictable iteration order. * * @param is InputStream to read as properties file */ static Map getProperties(InputStream is) throws IOException { final Map keys = new LinkedHashMap<>(); if(is != null) { Properties props = new Properties() { @Override public Object put(Object key, Object value) { keys.put((String)key, (String)value); return super.put(key, value); } }; props.load(is); is.close(); } return keys; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy