001/** 002 * 003 * Copyright (c) 2014, the Railo Company Ltd. All rights reserved. 004 * 005 * This library is free software; you can redistribute it and/or 006 * modify it under the terms of the GNU Lesser General Public 007 * License as published by the Free Software Foundation; either 008 * version 2.1 of the License, or (at your option) any later version. 009 * 010 * This library is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013 * Lesser General Public License for more details. 014 * 015 * You should have received a copy of the GNU Lesser General Public 016 * License along with this library. If not, see <http://www.gnu.org/licenses/>. 017 * 018 **/ 019package lucee.runtime.gateway; 020 021import java.io.IOException; 022import java.util.HashMap; 023import java.util.Iterator; 024import java.util.Map; 025import java.util.Map.Entry; 026 027import javax.servlet.http.HttpServletRequest; 028 029import lucee.commons.io.DevNullOutputStream; 030import lucee.commons.io.log.Log; 031import lucee.commons.io.log.LogUtil; 032import lucee.commons.lang.ClassException; 033import lucee.commons.lang.ExceptionUtil; 034import lucee.commons.lang.Md5; 035import lucee.commons.lang.Pair; 036import lucee.loader.util.Util; 037import lucee.runtime.CFMLFactory; 038import lucee.runtime.Component; 039import lucee.runtime.ComponentPage; 040import lucee.runtime.PageContext; 041import lucee.runtime.PageContextImpl; 042import lucee.runtime.config.Config; 043import lucee.runtime.config.ConfigWeb; 044import lucee.runtime.config.ConfigWebImpl; 045import lucee.runtime.engine.ThreadLocalPageContext; 046import lucee.runtime.exp.ExpressionException; 047import lucee.runtime.exp.PageException; 048import lucee.runtime.exp.PageRuntimeException; 049import lucee.runtime.op.Caster; 050import lucee.runtime.thread.ThreadUtil; 051import lucee.runtime.type.Collection; 052import lucee.runtime.type.KeyImpl; 053import lucee.runtime.type.Struct; 054import lucee.runtime.type.StructImpl; 055import lucee.runtime.type.util.KeyConstants; 056 057public class GatewayEngineImpl implements GatewayEnginePro { 058 059 private static final Object OBJ = new Object(); 060 061 private static final Collection.Key AMF_FORWARD = KeyImpl.init("AMF-Forward"); 062 063 private Map<String,GatewayEntry> entries=new HashMap<String,GatewayEntry>(); 064 private ConfigWeb config; 065 private Log log; 066 067 068 public GatewayEngineImpl(ConfigWeb config){ 069 this.config=config; 070 this.log=((ConfigWebImpl)config).getLog("gateway"); 071 072 } 073 074 public void addEntries(Config config,Map<String, GatewayEntry> entries) throws ClassException, PageException,IOException { 075 Iterator<Entry<String, GatewayEntry>> it = entries.entrySet().iterator(); 076 while(it.hasNext()){ 077 addEntry(config,it.next().getValue()); 078 } 079 } 080 081 public void addEntry(Config config,GatewayEntry ge) throws ClassException, PageException,IOException { 082 String id=ge.getId().toLowerCase().trim(); 083 GatewayEntry existing=entries.get(id); 084 GatewayPro g=null; 085 086 // does not exist 087 if(existing==null) { 088 entries.put(id,load(config,ge)); 089 } 090 // exist but changed 091 else if(!existing.equals(ge)){ 092 g=existing.getGateway(); 093 if(g.getState()==GatewayPro.RUNNING) g.doStop(); 094 entries.put(id,load(config,ge)); 095 } 096 // not changed 097 //else print.out("untouched:"+id); 098 } 099 100 private GatewayEntry load(Config config,GatewayEntry ge) throws ClassException,PageException { 101 ge.createGateway(config); 102 return ge; 103 } 104 105 /** 106 * @return the entries 107 */ 108 public Map<String,GatewayEntry> getEntries() { 109 return entries; 110 } 111 112 public void remove(GatewayEntry ge) { 113 String id=ge.getId().toLowerCase().trim(); 114 GatewayEntry existing=entries.remove(id); 115 GatewayPro g=null; 116 117 // does not exist 118 if(existing!=null) { 119 g=existing.getGateway(); 120 try{ 121 if(g.getState()==GatewayPro.RUNNING) g.doStop(); 122 } 123 catch(Throwable t){ 124 ExceptionUtil.rethrowIfNecessary(t); 125 } 126 } 127 } 128 129 /** 130 * get the state of gateway 131 * @param gatewayId 132 * @return 133 * @throws PageException 134 */ 135 public int getState(String gatewayId) throws PageException { 136 return getGateway(gatewayId).getState(); 137 } 138 139 /** 140 * get helper object 141 * @param gatewayId 142 * @return 143 * @throws PageException 144 */ 145 public Object getHelper(String gatewayId) throws PageException { 146 return getGateway(gatewayId).getHelper(); 147 } 148 149 /** 150 * send the message to the gateway 151 * @param gatewayId 152 * @param data 153 * @return 154 * @throws PageException 155 */ 156 public String sendMessage(String gatewayId, Struct data) throws PageException,IOException { 157 GatewayPro g = getGateway(gatewayId); 158 if(g.getState()!=GatewayPro.RUNNING) throw new GatewayException("Gateway ["+gatewayId+"] is not running"); 159 return g.sendMessage(data); 160 } 161 162 /** 163 * start the gateway 164 * @param gatewayId 165 * @throws PageException 166 */ 167 public void start(String gatewayId) throws PageException { 168 executeThread(gatewayId,GatewayThread.START); 169 } 170 private void start(GatewayPro gateway) { 171 executeThread(gateway,GatewayThread.START); 172 } 173 174 /** 175 * stop the gateway 176 * @param gatewayId 177 * @throws PageException 178 */ 179 public void stop(String gatewayId) throws PageException { 180 executeThread(gatewayId,GatewayThread.STOP); 181 } 182 private void stop(GatewayPro gateway) { 183 executeThread(gateway,GatewayThread.STOP); 184 } 185 186 187 188 189 public void reset() { 190 Iterator<Entry<String, GatewayEntry>> it = entries.entrySet().iterator(); 191 Entry<String, GatewayEntry> entry; 192 GatewayEntry ge; 193 GatewayPro g; 194 while(it.hasNext()){ 195 entry = it.next(); 196 ge = entry.getValue(); 197 g=ge.getGateway(); 198 if(g.getState()==GatewayPro.RUNNING) { 199 try { 200 g.doStop(); 201 } catch (IOException e) { 202 log(g, LOGLEVEL_ERROR, e.getMessage()); 203 } 204 } 205 if(ge.getStartupMode()==GatewayEntry.STARTUP_MODE_AUTOMATIC) 206 start(g); 207 208 } 209 } 210 211 public synchronized void clear() { 212 Iterator<Entry<String, GatewayEntry>> it = entries.entrySet().iterator(); 213 Entry<String, GatewayEntry> entry; 214 while(it.hasNext()){ 215 entry = it.next(); 216 if(entry.getValue().getGateway().getState()==GatewayPro.RUNNING) 217 stop(entry.getValue().getGateway()); 218 } 219 entries.clear(); 220 } 221 222 /** 223 * restart the gateway 224 * @param gatewayId 225 * @throws PageException 226 */ 227 public void restart(String gatewayId) throws PageException { 228 executeThread(gatewayId,GatewayThread.RESTART); 229 } 230 231 private GatewayPro getGateway(String gatewayId) throws PageException { 232 return getGatewayEntry(gatewayId).getGateway(); 233 } 234 235 private GatewayEntry getGatewayEntry(String gatewayId) throws PageException { 236 String id=gatewayId.toLowerCase().trim(); 237 GatewayEntry ge=entries.get(id); 238 if(ge!=null) return ge; 239 240 // create list 241 Iterator<String> it = entries.keySet().iterator(); 242 StringBuilder sb=new StringBuilder(); 243 while(it.hasNext()){ 244 if(sb.length()>0) sb.append(", "); 245 sb.append(it.next()); 246 } 247 248 throw new ExpressionException("there is no gateway instance with id ["+gatewayId+"], available gateway instances are ["+sb+"]"); 249 } 250 private GatewayEntry getGatewayEntry(GatewayPro gateway) { 251 String gatewayId=gateway.getId(); 252 // it must exist, because it only can come from here 253 return entries.get(gatewayId); 254 } 255 256 private void executeThread(String gatewayId, int action) throws PageException { 257 new GatewayThread(this,getGateway(gatewayId),action).start(); 258 } 259 260 private void executeThread(GatewayPro g, int action) { 261 new GatewayThread(this,g,action).start(); 262 } 263 264 265 266 267 public static int toIntState(String state, int defaultValue) { 268 state=state.trim().toLowerCase(); 269 if("running".equals(state)) return GatewayPro.RUNNING; 270 if("started".equals(state)) return GatewayPro.RUNNING; 271 if("run".equals(state)) return GatewayPro.RUNNING; 272 273 if("failed".equals(state)) return GatewayPro.FAILED; 274 if("starting".equals(state))return GatewayPro.STARTING; 275 if("stopped".equals(state)) return GatewayPro.STOPPED; 276 if("stopping".equals(state))return GatewayPro.STOPPING; 277 278 return defaultValue; 279 } 280 281 public static String toStringState(int state, String defaultValue) { 282 if(GatewayPro.RUNNING==state) return "running"; 283 if(GatewayPro.FAILED==state) return "failed"; 284 if(GatewayPro.STOPPED==state) return "stopped"; 285 if(GatewayPro.STOPPING==state) return "stopping"; 286 if(GatewayPro.STARTING==state) return "starting"; 287 288 return defaultValue; 289 } 290 291 public boolean invokeListener(GatewayPro gateway, String method, Map data) {// FUTUTE add generic type to interface 292 return invokeListener(gateway.getId(), method, data); 293 } 294 295 public boolean invokeListener(String gatewayId, String method, Map data) {// do not add this method to loade, it can be removed with Lucee 5 296 data=GatewayUtil.toCFML(data); 297 298 GatewayEntry entry; 299 try { 300 entry = getGatewayEntry(gatewayId); 301 } 302 catch (PageException pe) { 303 throw new PageRuntimeException(pe); 304 } 305 String cfcPath = entry.getListenerCfcPath(); 306 if(!Util.isEmpty(cfcPath,true)){ 307 try { 308 if(!callOneWay(cfcPath,gatewayId, method, Caster.toStruct(data,null,false), false)) 309 log(gatewayId,LOGLEVEL_ERROR, "function ["+method+"] does not exist in cfc ["+toRequestURI(cfcPath)+"]"); 310 else 311 return true; 312 } 313 catch (PageException e) { 314 e.printStackTrace(); 315 log(gatewayId,LOGLEVEL_ERROR, e.getMessage()); 316 } 317 } 318 else 319 log(gatewayId,LOGLEVEL_ERROR, "there is no listener cfc defined"); 320 return false; 321 } 322 323 public Object callEL(String cfcPath,String id,String functionName,Struct arguments, boolean cfcPeristent, Object defaultValue) { 324 try { 325 return call(cfcPath,id,functionName, arguments,cfcPeristent, defaultValue); 326 } catch (PageException e) { 327 return defaultValue; 328 } 329 } 330 331 public boolean callOneWay(String cfcPath,String id,String functionName,Struct arguments, boolean cfcPeristent) throws PageException { 332 return call(cfcPath,id,functionName, arguments,cfcPeristent, OBJ)!=OBJ; 333 } 334 335 public Object getComponent(String cfcPath,String id) throws PageException { 336 String requestURI=toRequestURI(cfcPath); 337 338 PageContext oldPC = ThreadLocalPageContext.get(); 339 PageContextImpl pc = createPageContext(requestURI,id, "init", null, false); 340 try { 341 ThreadLocalPageContext.register(pc); 342 return getCFC(pc,requestURI); 343 } 344 finally{ 345 CFMLFactory f = config.getFactory(); 346 f.releasePageContext(pc); 347 ThreadLocalPageContext.register(oldPC); 348 } 349 } 350 351 public Object call(String cfcPath,String id,String functionName,Struct arguments, boolean cfcPeristent, Object defaultValue) throws PageException { 352 String requestURI=toRequestURI(cfcPath); 353 354 PageContext oldPC = ThreadLocalPageContext.get(); 355 PageContextImpl pc=createPageContext(requestURI,id,functionName,arguments,cfcPeristent); 356 357 try { 358 ThreadLocalPageContext.register(pc); 359 Component cfc=getCFC(pc,requestURI); 360 if(cfc.containsKey(functionName)){ 361 pc.execute(requestURI, true,false); 362 // Result 363 return pc.variablesScope().get(AMF_FORWARD,null); 364 } 365 } 366 finally{ 367 CFMLFactory f = config.getFactory(); 368 f.releasePageContext(pc); 369 ThreadLocalPageContext.register(oldPC); 370 } 371 return defaultValue; 372 } 373 374 private Component getCFC(PageContextImpl pc,String requestURI) throws PageException { 375 HttpServletRequest req = pc.getHttpServletRequest(); 376 try { 377 req.setAttribute("client", "lucee-gateway-1-0"); 378 req.setAttribute("call-type", "store-only"); 379 pc.execute(requestURI, true,false); 380 return (Component) req.getAttribute("component"); 381 } 382 finally { 383 req.removeAttribute("call-type"); 384 req.removeAttribute("component"); 385 } 386 } 387 388 private PageContextImpl createPageContext(String requestURI,String id,String functionName, Struct arguments, boolean cfcPeristent) throws PageException { 389 Struct attrs=new StructImpl(); 390 String remotePersisId; 391 try { 392 remotePersisId=Md5.getDigestAsString(requestURI+id); 393 } catch (IOException e) { 394 throw Caster.toPageException(e); 395 } 396 PageContextImpl pc = ThreadUtil.createPageContext( 397 config, 398 DevNullOutputStream.DEV_NULL_OUTPUT_STREAM, 399 "localhost", 400 requestURI, 401 "method="+functionName+(cfcPeristent?"&"+ComponentPage.REMOTE_PERSISTENT_ID+"="+remotePersisId:""), 402 null, 403 new Pair[]{new Pair<String,Object>("AMF-Forward","true")}, 404 null, 405 attrs); 406 407 pc.setRequestTimeout(999999999999999999L); 408 pc.setGatewayContext(true); 409 if(arguments!=null)attrs.setEL(KeyConstants._argumentCollection, arguments); 410 attrs.setEL("client", "lucee-gateway-1-0"); 411 return pc; 412 } 413 414 public String toRequestURI(String cfcPath) { 415 return GatewayUtil.toRequestURI(cfcPath); 416 } 417 418 @Override 419 public void log(GatewayPro gateway, int level, String message) { 420 log(gateway.getId(), level, message); 421 } 422 423 public void log(String gatewayId, int level, String message) { 424 int l=level; 425 switch(level){ 426 case LOGLEVEL_INFO:l=Log.LEVEL_INFO; 427 break; 428 case LOGLEVEL_DEBUG:l=Log.LEVEL_DEBUG; 429 break; 430 case LOGLEVEL_ERROR:l=Log.LEVEL_ERROR; 431 break; 432 case LOGLEVEL_FATAL:l=Log.LEVEL_FATAL; 433 break; 434 case LOGLEVEL_WARN:l=Log.LEVEL_WARN; 435 break; 436 case LOGLEVEL_TRACE:l=LogUtil.LEVEL_TRACE; 437 break; 438 } 439 log.log(l, "Gateway:"+gatewayId, message); 440 } 441 442 443 private Map<String, Component> persistentRemoteCFC; 444 public Component getPersistentRemoteCFC(String id) { 445 if(persistentRemoteCFC==null) persistentRemoteCFC=new HashMap<String,Component>(); 446 return persistentRemoteCFC.get(id); 447 } 448 449 public Component setPersistentRemoteCFC(String id, Component cfc) { 450 if(persistentRemoteCFC==null) persistentRemoteCFC=new HashMap<String,Component>(); 451 return persistentRemoteCFC.put(id,cfc); 452 } 453} 454