package railo.runtime.config;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import railo.commons.collection.LinkedHashMapMaxSize;
import railo.commons.collection.MapFactory;
import railo.commons.digest.Hash;
import railo.commons.io.SystemUtil;
import railo.commons.io.res.Resource;
import railo.commons.io.res.ResourcesImpl;
import railo.commons.lang.ClassUtil;
import railo.commons.lang.PCLCollection;
import railo.commons.lang.StringUtil;
import railo.commons.lang.SystemOut;
import railo.loader.TP;
import railo.loader.engine.CFMLEngine;
import railo.loader.engine.CFMLEngineFactory;
import railo.loader.util.ExtensionFilter;
import railo.runtime.CFMLFactory;
import railo.runtime.CFMLFactoryImpl;
import railo.runtime.Mapping;
import railo.runtime.MappingImpl;
import railo.runtime.engine.CFMLEngineImpl;
import railo.runtime.engine.ThreadQueueImpl;
import railo.runtime.exp.ApplicationException;
import railo.runtime.exp.ExpressionException;
import railo.runtime.exp.PageException;
import railo.runtime.monitor.ActionMonitorCollector;
import railo.runtime.monitor.IntervallMonitor;
import railo.runtime.monitor.RequestMonitor;
import railo.runtime.net.http.ReqRspUtil;
import railo.runtime.op.Caster;
import railo.runtime.reflection.Reflector;
import railo.runtime.security.SecurityManager;
import railo.runtime.security.SecurityManagerImpl;
import railo.runtime.type.scope.Cluster;
import railo.runtime.type.scope.ClusterRemote;
import railo.runtime.type.scope.ClusterWrap;
import railo.runtime.type.util.ArrayUtil;
/**
* config server impl
*/
public final class ConfigServerImpl extends ConfigImpl implements ConfigServer {
private static final long FIVE_SECONDS = 5000;
private final CFMLEngineImpl engine;
private Map<String,CFMLFactory> initContextes;
//private Map contextes;
private SecurityManager defaultSecurityManager;
private Map<String,SecurityManager> managers=MapFactory.<String,SecurityManager>getConcurrentMap();
private String defaultPassword;
private Resource rootDir;
private URL updateLocation;
private String updateType="";
private ConfigListener configListener;
private Map<String, String> labels;
private RequestMonitor[] requestMonitors;
private IntervallMonitor[] intervallMonitors;
private ActionMonitorCollector actionMonitorCollector;
private boolean monitoringEnabled=false;
private int delay=1;
private boolean captcha=false;
private static ConfigServerImpl instance;
private String[] authKeys;
private String idPro;
private LinkedHashMapMaxSize<Long,String> previousNonces=new LinkedHashMapMaxSize<Long,String>(100);
/**
* @param engine
* @param initContextes
* @param contextes
* @param configDir
* @param configFile
*/
protected ConfigServerImpl(CFMLEngineImpl engine,Map<String,CFMLFactory> initContextes, Map<String,CFMLFactory> contextes, Resource configDir, Resource configFile) {
super(null,configDir, configFile);
this.engine=engine;
this.initContextes=initContextes;
//this.contextes=contextes;
this.rootDir=configDir;
instance=this;
}
/**
* @return the configListener
*/
public ConfigListener getConfigListener() {
return configListener;
}
/**
* @param configListener the configListener to set
*/
public void setConfigListener(ConfigListener configListener) {
this.configListener = configListener;
}
@Override
public ConfigServer getConfigServer(String password) {
return this;
}
@Override
public ConfigWeb[] getConfigWebs() {
Iterator<String> it = initContextes.keySet().iterator();
ConfigWeb[] webs=new ConfigWeb[initContextes.size()];
int index=0;
while(it.hasNext()) {
webs[index++]=((CFMLFactoryImpl)initContextes.get(it.next())).getConfig();
}
return webs;
}
@Override
public ConfigWeb getConfigWeb(String realpath) {
return getConfigWebImpl(realpath);
}
/**
* returns CongigWeb Implementtion
* @param realpath
* @return ConfigWebImpl
*/
protected ConfigWebImpl getConfigWebImpl(String realpath) {
Iterator<String> it = initContextes.keySet().iterator();
while(it.hasNext()) {
ConfigWebImpl cw=((CFMLFactoryImpl)initContextes.get(it.next())).getConfigWebImpl();
if(ReqRspUtil.getRootPath(cw.getServletContext()).equals(realpath))
return cw;
}
return null;
}
public ConfigWebImpl getConfigWebById(String id) {
Iterator<String> it = initContextes.keySet().iterator();
while(it.hasNext()) {
ConfigWebImpl cw=((CFMLFactoryImpl)initContextes.get(it.next())).getConfigWebImpl();
if(cw.getId().equals(id))
return cw;
}
return null;
}
public String getIdPro() {
if(idPro==null){
idPro = getId(getSecurityKey(),getSecurityToken(),true,null);
}
return idPro;
}
/**
* @return JspFactoryImpl array
*/
public CFMLFactoryImpl[] getJSPFactories() {
Iterator<String> it = initContextes.keySet().iterator();
CFMLFactoryImpl[] factories=new CFMLFactoryImpl[initContextes.size()];
int index=0;
while(it.hasNext()) {
factories[index++]=(CFMLFactoryImpl)initContextes.get(it.next());
}
return factories;
}
@Override
public Map<String,CFMLFactory> getJSPFactoriesAsMap() {
return initContextes;
}
@Override
public SecurityManager getSecurityManager(String id) {
Object o=managers.get(id);
if(o!=null) return (SecurityManager) o;
if(defaultSecurityManager==null) {
defaultSecurityManager = SecurityManagerImpl.getOpenSecurityManager();
}
return defaultSecurityManager.cloneSecurityManager();
}
@Override
public boolean hasIndividualSecurityManager(String id) {
return managers.containsKey(id);
}
/**
* @param defaultSecurityManager
*/
protected void setDefaultSecurityManager(SecurityManager defaultSecurityManager) {
this.defaultSecurityManager=defaultSecurityManager;
}
/**
* @param id
* @param securityManager
*/
protected void setSecurityManager(String id, SecurityManager securityManager) {
managers.put(id,securityManager);
}
/**
* @param id
*/
protected void removeSecurityManager(String id) {
managers.remove(id);
}
@Override
public SecurityManager getDefaultSecurityManager() {
return defaultSecurityManager;
}
/**
* @return Returns the defaultPassword.
*/
protected String getDefaultPassword() {
return defaultPassword;
}
/**
* @param defaultPassword The defaultPassword to set.
*/
protected void setDefaultPassword(String defaultPassword) {
this.defaultPassword = defaultPassword;
}
@Override
public CFMLEngine getCFMLEngine() {
return engine;
}
/**
* @return Returns the rootDir.
*/
public Resource getRootDirectory() {
return rootDir;
}
@Override
public String getUpdateType() {
return updateType;
}
@Override
public void setUpdateType(String updateType) {
if(!StringUtil.isEmpty(updateType))
this.updateType = updateType;
}
@Override
public URL getUpdateLocation() {
return updateLocation;
}
@Override
public void setUpdateLocation(URL updateLocation) {
this.updateLocation = updateLocation;
}
@Override
public void setUpdateLocation(String strUpdateLocation) throws MalformedURLException {
setUpdateLocation(new URL(strUpdateLocation));
}
@Override
public void setUpdateLocation(String strUpdateLocation, URL defaultValue) {
try {
setUpdateLocation(strUpdateLocation);
} catch (MalformedURLException e) {
setUpdateLocation(defaultValue);
}
}
@Override
public SecurityManager getSecurityManager() {
SecurityManagerImpl sm = (SecurityManagerImpl) getDefaultSecurityManager();//.cloneSecurityManager();
//sm.setAccess(SecurityManager.TYPE_ACCESS_READ,SecurityManager.ACCESS_PROTECTED);
//sm.setAccess(SecurityManager.TYPE_ACCESS_WRITE,SecurityManager.ACCESS_PROTECTED);
return sm;
}
/**
* @return the instance
*/
public static ConfigServerImpl getInstance() {
return instance;
}
public void setLabels(Map<String, String> labels) {
this.labels=labels;
}
public Map<String, String> getLabels() {
if(labels==null) labels=new HashMap<String, String>();
return labels;
}
private ThreadQueueImpl threadQueue;
protected void setThreadQueue(ThreadQueueImpl threadQueue) {
this.threadQueue=threadQueue;
}
public ThreadQueueImpl getThreadQueue() {
return threadQueue;
}
public RequestMonitor[] getRequestMonitors() {
return requestMonitors;
}
public RequestMonitor getRequestMonitor(String name) throws ApplicationException {
for(int i=0;i<requestMonitors.length;i++){
if(requestMonitors[i].getName().equalsIgnoreCase(name))
return requestMonitors[i];
}
throw new ApplicationException("there is no request monitor registered with name ["+name+"]");
}
protected void setRequestMonitors(RequestMonitor[] monitors) {
this.requestMonitors=monitors;;
}
public IntervallMonitor[] getIntervallMonitors() {
return intervallMonitors;
}
public IntervallMonitor getIntervallMonitor(String name) throws ApplicationException {
for(int i=0;i<intervallMonitors.length;i++){
if(intervallMonitors[i].getName().equalsIgnoreCase(name))
return intervallMonitors[i];
}
throw new ApplicationException("there is no intervall monitor registered with name ["+name+"]");
}
protected void setIntervallMonitors(IntervallMonitor[] monitors) {
this.intervallMonitors=monitors;;
}
public void setActionMonitorCollector(ActionMonitorCollector actionMonitorCollector) {
this.actionMonitorCollector=actionMonitorCollector;
}
public ActionMonitorCollector getActionMonitorCollector() {
return actionMonitorCollector;
}
public Object getActionMonitor(String name) { // FUTURE return ActionMonitor
return actionMonitorCollector.getActionMonitor(name);
}
@Override
public boolean isMonitoringEnabled() {
return monitoringEnabled;
}
protected void setMonitoringEnabled(boolean monitoringEnabled) {
this.monitoringEnabled=monitoringEnabled;;
}
protected void setLoginDelay(int delay) {
this.delay=delay;
}
protected void setLoginCaptcha(boolean captcha) {
this.captcha=captcha;
}
@Override
public int getLoginDelay() {
return delay;
}
@Override
public boolean getLoginCaptcha() {
return captcha;
}
public void reset() {
super.reset();
getThreadQueue().clear();
}
@Override
public Resource getSecurityDirectory(){
Resource cacerts=null;
// javax.net.ssl.trustStore
String trustStore = SystemUtil.getPropertyEL("javax.net.ssl.trustStore");
if(trustStore!=null){
cacerts = ResourcesImpl.getFileResourceProvider().getResource(trustStore);
}
// security/cacerts
if(cacerts==null || !cacerts.exists()) {
cacerts = getConfigDir().getRealResource("security/cacerts");
if(!cacerts.exists())cacerts.mkdirs();
}
return cacerts;
}
@Override
public void checkPermGenSpace(boolean check) {
//print.e(Runtime.getRuntime().freeMemory());
// Runtime.getRuntime().freeMemory()<200000 ||
// long pgs=SystemUtil.getFreePermGenSpaceSize();
int promille=SystemUtil.getFreePermGenSpacePromille();
// Pen Gen Space info not available
if(promille==-1) {//if(pgs==-1) {
if(countLoadedPages()>500)
shrink();
}
else if(!check || promille<50){//else if(!check || pgs<1024*1024){
SystemOut.printDate(getErrWriter(),"+Free Perm Gen Space is less than 1mb (free:"+((SystemUtil.getFreePermGenSpaceSize())/1024)+"kb), shrink all template classloaders");
// first just call GC and check if it help
System.gc();
//if(SystemUtil.getFreePermGenSpaceSize()>1024*1024)
if(SystemUtil.getFreePermGenSpacePromille()>50)
return;
shrink();
}
}
private void shrink() {
ConfigWeb[] webs = getConfigWebs();
int count=0;
for(int i=0;i<webs.length;i++){
count+=shrink((ConfigWebImpl) webs[i],false);
}
if(count==0) {
for(int i=0;i<webs.length;i++){
shrink((ConfigWebImpl) webs[i],true);
}
}
}
private static int shrink(ConfigWebImpl config, boolean force) {
int count=0;
count+=shrink(config.getMappings(),force);
count+=shrink(config.getCustomTagMappings(),force);
count+=shrink(config.getComponentMappings(),force);
count+=shrink(config.getFunctionMapping(),force);
count+=shrink(config.getServerFunctionMapping(),force);
count+=shrink(config.getTagMapping(),force);
count+=shrink(config.getServerTagMapping(),force);
//count+=shrink(config.getServerTagMapping(),force);
return count;
}
private static int shrink(Mapping[] mappings, boolean force) {
int count=0;
for(int i=0;i<mappings.length;i++){
count+=shrink(mappings[i],force);
}
return count;
}
private static int shrink(Mapping mapping, boolean force) {
try {
PCLCollection pcl = ((MappingImpl)mapping).getPCLCollection();
if(pcl!=null)return pcl.shrink(force);
}
catch (Throwable t) {
t.printStackTrace();
}
return 0;
}
public long countLoadedPages() {
long count=0;
ConfigWeb[] webs = getConfigWebs();
for(int i=0;i<webs.length;i++){
count+=_count((ConfigWebImpl) webs[i]);
}
return count;
}
private static long _count(ConfigWebImpl config) {
long count=0;
count+=_count(config.getMappings());
count+=_count(config.getCustomTagMappings());
count+=_count(config.getComponentMappings());
count+=_count(config.getFunctionMapping());
count+=_count(config.getServerFunctionMapping());
count+=_count(config.getTagMapping());
count+=_count(config.getServerTagMapping());
//count+=_count(((ConfigWebImpl)config).getServerTagMapping());
return count;
}
private static long _count(Mapping[] mappings) {
long count=0;
for(int i=0;i<mappings.length;i++){
count+=_count(mappings[i]);
}
return count;
}
private static long _count(Mapping mapping) {
PCLCollection pcl = ((MappingImpl)mapping).getPCLCollection();
return pcl==null?0:pcl.count();
}
@Override
public Cluster createClusterScope() throws PageException {
Cluster cluster=null;
try {
if(Reflector.isInstaneOf(getClusterClass(), Cluster.class)){
cluster=(Cluster) ClassUtil.loadInstance(
getClusterClass(),
ArrayUtil.OBJECT_EMPTY
);
cluster.init(this);
}
else if(Reflector.isInstaneOf(getClusterClass(), ClusterRemote.class)){
ClusterRemote cb=(ClusterRemote) ClassUtil.loadInstance(
getClusterClass(),
ArrayUtil.OBJECT_EMPTY
);
cluster=new ClusterWrap(this,cb);
//cluster.init(cs);
}
}
catch (Exception e) {
throw Caster.toPageException(e);
}
return cluster;
}
@Override
public boolean hasServerPassword() {
return hasPassword();
}
public String[] getInstalledPatches() throws PageException {
CFMLEngineFactory factory = getCFMLEngine().getCFMLEngineFactory();
try{
return factory.getInstalledPatches();
}
catch(Throwable t){
try {
return getInstalledPatchesOld(factory);
} catch (Exception e1) {
throw Caster.toPageException(e1);
}
}
}
private String[] getInstalledPatchesOld(CFMLEngineFactory factory) throws IOException {
File patchDir = new File(factory.getResourceRoot(),"patches");
if(!patchDir.exists())patchDir.mkdirs();
File[] patches=patchDir.listFiles(new ExtensionFilter(new String[]{"."+getCoreExtension()}));
List<String> list=new ArrayList<String>();
String name;
int extLen=getCoreExtension().length()+1;
for(int i=0;i<patches.length;i++) {
name=patches[i].getName();
name=name.substring(0, name.length()-extLen);
list.add(name);
}
String[] arr = list.toArray(new String[list.size()]);
Arrays.sort(arr);
return arr;
}
private String getCoreExtension() {
URL res = new TP().getClass().getResource("/core/core.rcs");
if(res!=null) return "rcs";
res = new TP().getClass().getResource("/core/core.rc");
if(res!=null) return "rc";
return "rc";
}
@Override
public boolean allowRequestTimeout() {
return engine.allowRequestTimeout();
}
private boolean fullNullSupport=false;
protected void setFullNullSupport(boolean fullNullSupport) {
this.fullNullSupport=fullNullSupport;
}
public boolean getFullNullSupport() {
return fullNullSupport;
}
public String[] getAuthenticationKeys() {
return authKeys==null?new String[0]:authKeys;
}
protected void setAuthenticationKeys(String[] authKeys) {
this.authKeys = authKeys;
}
public ConfigServer getConfigServer(String key,String nonce) {
return this;
}
public void checkAccess(String password) throws ExpressionException {
if(!hasPassword())
throw new ExpressionException("Cannot access, no password is defined");
if(!passwordEqual(password))
throw new ExpressionException("No access, password is invalid");
}
public void checkAccess(String key, long timeNonce) throws PageException {
if(previousNonces.containsKey(timeNonce)) {
long now = System.currentTimeMillis();
long diff=timeNonce>now?timeNonce-now:now-timeNonce;
if(diff>10)
throw new ApplicationException("nonce was already used, same nonce can only be used once");
}
long now = System.currentTimeMillis()+getTimeServerOffset();
if(timeNonce>(now+FIVE_SECONDS) || timeNonce<(now-FIVE_SECONDS))
throw new ApplicationException("nonce is outdated (timserver offset:"+getTimeServerOffset()+")");
previousNonces.put(timeNonce,"");
String[] keys=getAuthenticationKeys();
// check if one of the keys matching
String hash;
for(int i=0;i<keys.length;i++){
try {
hash=Hash.hash(keys[i], Caster.toString(timeNonce), Hash.ALGORITHM_SHA_256, Hash.ENCODING_HEX);
if(hash.equals(key)) return;
}
catch (NoSuchAlgorithmException e) {
throw Caster.toPageException(e);
}
}
throw new ApplicationException("No access, no matching authentication key found");
}
}