001/* 002 * (C) Copyright 2006-2017 Nuxeo (http://nuxeo.com/) and others. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 * 016 * Contributors: 017 * bstefanescu 018 */ 019package org.nuxeo.ecm.webengine; 020 021import java.io.File; 022import java.io.IOException; 023import java.io.InputStream; 024import java.net.MalformedURLException; 025import java.net.URL; 026import java.util.HashMap; 027import java.util.Map; 028import java.util.Properties; 029 030import javax.servlet.GenericServlet; 031import javax.servlet.http.HttpServletRequest; 032import javax.servlet.http.HttpServletResponse; 033import javax.ws.rs.core.Application; 034 035import org.apache.commons.logging.Log; 036import org.apache.commons.logging.LogFactory; 037import org.nuxeo.ecm.platform.rendering.api.RenderingEngine; 038import org.nuxeo.ecm.platform.rendering.api.ResourceLocator; 039import org.nuxeo.ecm.platform.rendering.fm.FreemarkerEngine; 040import org.nuxeo.ecm.webengine.app.WebEngineModule; 041import org.nuxeo.ecm.webengine.jaxrs.ApplicationFragment; 042import org.nuxeo.ecm.webengine.jaxrs.ApplicationHost; 043import org.nuxeo.ecm.webengine.jaxrs.ApplicationManager; 044import org.nuxeo.ecm.webengine.jaxrs.context.RequestContext; 045import org.nuxeo.ecm.webengine.loader.WebLoader; 046import org.nuxeo.ecm.webengine.model.Module; 047import org.nuxeo.ecm.webengine.model.Resource; 048import org.nuxeo.ecm.webengine.model.WebContext; 049import org.nuxeo.ecm.webengine.model.impl.ModuleConfiguration; 050import org.nuxeo.ecm.webengine.model.impl.ModuleManager; 051import org.nuxeo.ecm.webengine.scripting.ScriptFile; 052import org.nuxeo.ecm.webengine.scripting.Scripting; 053import org.nuxeo.runtime.annotations.AnnotationManager; 054import org.nuxeo.runtime.api.Framework; 055 056import freemarker.ext.jsp.TaglibFactory; 057import freemarker.ext.servlet.HttpRequestHashModel; 058import freemarker.ext.servlet.HttpRequestParametersHashModel; 059import freemarker.ext.servlet.ServletContextHashModel; 060 061/** 062 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a> 063 */ 064public class WebEngine implements ResourceLocator { 065 066 public static final String SKIN_PATH_PREFIX_KEY = "org.nuxeo.ecm.webengine.skinPathPrefix"; 067 068 protected static final Map<Object, Object> mimeTypes = loadMimeTypes(); 069 070 private static final Log log = LogFactory.getLog(WebEngine.class); 071 072 static Map<Object, Object> loadMimeTypes() { 073 Properties p = new Properties(); 074 URL url = WebEngine.class.getClassLoader().getResource("OSGI-INF/mime.properties"); 075 try (InputStream in = url.openStream()) { 076 p.load(in); 077 return new HashMap<>(p); 078 } catch (IOException e) { 079 throw new RuntimeException("Failed to load mime types", e); 080 } 081 } 082 083 public static WebContext getActiveContext() { 084 RequestContext ctx = RequestContext.getActiveContext(); 085 if (ctx != null) { 086 return (WebContext) ctx.getRequest().getAttribute(WebContext.class.getName()); 087 } 088 return null; 089 } 090 091 protected final File root; 092 093 protected HashMap<String, WebEngineModule> apps; 094 095 /** 096 * moduleMgr use double-check idiom and needs to be volatile. See 097 * http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html 098 */ 099 protected volatile ModuleManager moduleMgr; 100 101 protected final Scripting scripting; 102 103 protected final RenderingEngine rendering; 104 105 protected final Map<String, Object> env; 106 107 protected boolean devMode; 108 109 protected final AnnotationManager annoMgr; 110 111 protected final ResourceRegistry registry; 112 113 protected String skinPathPrefix; 114 115 protected final WebLoader webLoader; 116 117 protected volatile boolean isDirty; 118 119 public WebEngine(File root) { 120 this(new EmptyRegistry(), root); 121 } 122 123 public WebEngine(ResourceRegistry registry, File root) { 124 this.registry = registry; 125 this.root = root; 126 webLoader = new WebLoader(this); 127 apps = new HashMap<>(); 128 scripting = new Scripting(webLoader); 129 annoMgr = new AnnotationManager(); 130 131 skinPathPrefix = Framework.getProperty(SKIN_PATH_PREFIX_KEY); 132 if (skinPathPrefix == null) { 133 // TODO: should put this in web.xml and not use jboss.home.dir to 134 // test if on jboss 135 skinPathPrefix = System.getProperty("jboss.home.dir") != null ? "/nuxeo/site/skin" : "/skin"; 136 } 137 138 env = new HashMap<>(); 139 env.put("installDir", root); 140 env.put("engine", "Nuxeo Web Engine"); 141 // TODO this should be put in the MANIFEST 142 env.put("version", "1.0.0.rc"); 143 144 rendering = new FreemarkerEngine(); 145 rendering.setResourceLocator(this); 146 rendering.setSharedVariable("env", getEnvironment()); 147 } 148 149 /** 150 * JSP taglib support 151 */ 152 public void loadJspTaglib(GenericServlet servlet) { 153 if (rendering instanceof FreemarkerEngine) { 154 FreemarkerEngine fm = (FreemarkerEngine) rendering; 155 ServletContextHashModel servletContextModel = new ServletContextHashModel(servlet, fm.getObjectWrapper()); 156 fm.setSharedVariable("Application", servletContextModel); 157 fm.setSharedVariable("__FreeMarkerServlet.Application__", servletContextModel); 158 fm.setSharedVariable("Application", servletContextModel); 159 fm.setSharedVariable("__FreeMarkerServlet.Application__", servletContextModel); 160 fm.setSharedVariable("JspTaglibs", new TaglibFactory(servlet.getServletContext())); 161 } 162 } 163 164 public void initJspRequestSupport(GenericServlet servlet, HttpServletRequest request, 165 HttpServletResponse response) { 166 if (rendering instanceof FreemarkerEngine) { 167 FreemarkerEngine fm = (FreemarkerEngine) rendering; 168 HttpRequestHashModel requestModel = new HttpRequestHashModel(request, response, fm.getObjectWrapper()); 169 fm.setSharedVariable("__FreeMarkerServlet.Request__", requestModel); 170 fm.setSharedVariable("Request", requestModel); 171 fm.setSharedVariable("RequestParameters", new HttpRequestParametersHashModel(request)); 172 173 // HttpSessionHashModel sessionModel = null; 174 // HttpSession session = request.getSession(false); 175 // if(session != null) { 176 // sessionModel = (HttpSessionHashModel) 177 // session.getAttribute(ATTR_SESSION_MODEL); 178 // if (sessionModel == null || sessionModel.isZombie()) { 179 // sessionModel = new HttpSessionHashModel(session, wrapper); 180 // session.setAttribute(ATTR_SESSION_MODEL, sessionModel); 181 // if(!sessionModel.isZombie()) { 182 // initializeSession(request, response); 183 // } 184 // } 185 // } 186 // else { 187 // sessionModel = new HttpSessionHashModel(servlet, request, 188 // response, fm.getObjectWrapper()); 189 // } 190 // sessionModel = new HttpSessionHashModel(request, response, 191 // fm.getObjectWrapper()); 192 // fm.setSharedVariable("Session", sessionModel); 193 } 194 } 195 196 public WebLoader getWebLoader() { 197 return webLoader; 198 } 199 200 public void setSkinPathPrefix(String skinPathPrefix) { 201 this.skinPathPrefix = skinPathPrefix; 202 } 203 204 public String getSkinPathPrefix() { 205 return skinPathPrefix; 206 } 207 208 @Deprecated 209 public ResourceRegistry getRegistry() { 210 return registry; 211 } 212 213 public Class<?> loadClass(String className) throws ClassNotFoundException { 214 return webLoader.loadClass(className); 215 } 216 217 public String getMimeType(String ext) { 218 return (String) mimeTypes.get(ext); 219 } 220 221 public AnnotationManager getAnnotationManager() { 222 return annoMgr; 223 } 224 225 public void registerRenderingExtension(String id, Object obj) { 226 rendering.setSharedVariable(id, obj); 227 } 228 229 public void unregisterRenderingExtension(String id) { 230 rendering.setSharedVariable(id, null); 231 } 232 233 public Map<String, Object> getEnvironment() { 234 return env; 235 } 236 237 public Scripting getScripting() { 238 return scripting; 239 } 240 241 public synchronized WebEngineModule[] getApplications() { 242 return apps.values().toArray(new WebEngineModule[apps.size()]); 243 } 244 245 public synchronized void addApplication(WebEngineModule app) { 246 flushCache(); 247 apps.put(app.getId(), app); 248 } 249 250 public ModuleManager getModuleManager() { 251 if (moduleMgr == null) { // avoid synchronizing if not needed 252 synchronized (this) { 253 /** 254 * the duplicate if is used avoid synchronizing when no needed. note that the this.moduleMgr member must 255 * be set at the end of the synchronized block after the module manager is completely initialized 256 */ 257 if (moduleMgr == null) { 258 ModuleManager moduleMgr = new ModuleManager(this); 259 File deployRoot = getDeploymentDirectory(); 260 if (deployRoot.isDirectory()) { 261 // load modules present in deploy directory 262 for (String name : deployRoot.list()) { 263 String path = name + "/module.xml"; 264 File file = new File(deployRoot, path); 265 if (file.isFile()) { 266 webLoader.addClassPathElement(file.getParentFile()); 267 moduleMgr.loadModule(file); 268 } 269 } 270 } 271 for (WebEngineModule app : getApplications()) { 272 ModuleConfiguration mc = app.getConfiguration(); 273 moduleMgr.loadModule(mc); 274 } 275 // set member at the end to be sure moduleMgr is completely 276 // initialized 277 this.moduleMgr = moduleMgr; 278 } 279 } 280 } 281 return moduleMgr; 282 } 283 284 public Module getModule(String name, WebContext context) { 285 ModuleConfiguration md = getModuleManager().getModule(name); 286 if (md != null) { 287 return md.get(context); 288 } 289 return null; 290 } 291 292 public File getRootDirectory() { 293 return root; 294 } 295 296 public File getDeploymentDirectory() { 297 return new File(root, "deploy"); 298 } 299 300 public File getModulesDirectory() { 301 return new File(root, "modules"); 302 } 303 304 public RenderingEngine getRendering() { 305 return rendering; 306 } 307 308 /** 309 * Manage jax-rs root resource bindings 310 * 311 * @deprecated resources are deprecated - you should use a jax-rs application to declare more resources. 312 */ 313 @Deprecated 314 public void addResourceBinding(ResourceBinding binding) { 315 try { 316 binding.resolve(this); 317 registry.addBinding(binding); 318 } catch (ClassNotFoundException e) { 319 throw WebException.wrap("Failed o register binding: " + binding, e); 320 } 321 } 322 323 /** 324 * @deprecated resources are deprecated - you should use a jax-rs application to declare more resources. 325 */ 326 @Deprecated 327 public void removeResourceBinding(ResourceBinding binding) { 328 registry.removeBinding(binding); 329 } 330 331 /** 332 * @deprecated resources are deprecated - you should use a jax-rs application to declare more resources. 333 */ 334 @Deprecated 335 public ResourceBinding[] getBindings() { 336 return registry.getBindings(); 337 } 338 339 public synchronized void setDirty(boolean dirty) { 340 isDirty = dirty; 341 } 342 343 public boolean tryReload() { 344 if (isDirty) { 345 synchronized (this) { 346 if (isDirty) { 347 reload(); 348 return true; 349 } 350 } 351 } 352 return false; 353 } 354 355 public synchronized boolean isDirty() { 356 return isDirty; 357 } 358 359 public synchronized void flushCache() { 360 isDirty = false; 361 if (moduleMgr != null) { 362 webLoader.flushCache(); 363 moduleMgr = null; 364 } 365 } 366 367 /** 368 * Reloads configuration. 369 */ 370 public synchronized void reload() { 371 log.info("Reloading WebEngine"); 372 isDirty = false; 373 webLoader.flushCache(); 374 apps = new HashMap<>(); 375 if (moduleMgr != null) { // avoid synchronizing if not needed 376 for (ModuleConfiguration mc : moduleMgr.getModules()) { 377 mc.flushCache(); 378 } 379 moduleMgr = null; 380 } 381 } 382 383 public synchronized void reloadModules() { 384 if (moduleMgr != null) { 385 moduleMgr.reloadModules(); 386 } 387 } 388 389 public void start() { 390 // reconnect to the application manager and collect available web engine modules. 391 // This must be done after a component manager restart 392 // On the first start (i.e. at runtime booting) this will 393 // never find available web engine modules to connect with 394 // This will found something only after a component manager restart 395 ApplicationHost[] hosts = ApplicationManager.getInstance().getApplications(); 396 for (ApplicationHost host : hosts) { 397 for (ApplicationFragment fragment : host.getApplications()) { 398 Application app = fragment.getApplication(); 399 if (app instanceof WebEngineModule) { 400 addApplication((WebEngineModule) app); 401 } 402 } 403 } 404 } 405 406 public void stop() { 407 registry.clear(); 408 moduleMgr = null; 409 } 410 411 protected ModuleConfiguration getModuleFromPath(String rootPath, String path) { 412 path = path.substring(rootPath.length() + 1); 413 int p = path.indexOf('/'); 414 String moduleName = path; 415 if (p > -1) { 416 moduleName = path.substring(0, p); 417 } 418 return moduleMgr.getModule(moduleName); 419 } 420 421 /* ResourceLocator API */ 422 423 @Override 424 public URL getResourceURL(String key) { 425 try { 426 return new URL(key); 427 } catch (MalformedURLException e) { 428 return null; 429 } 430 } 431 432 @Override 433 public File getResourceFile(String key) { 434 WebContext ctx = getActiveContext(); 435 if (key.startsWith("@")) { 436 Resource rs = ctx.getTargetObject(); 437 if (rs != null) { 438 return rs.getView(key.substring(1)).script().getFile(); 439 } 440 } else { 441 ScriptFile file = ctx.getFile(key); 442 if (file != null) { 443 return file.getFile(); 444 } 445 } 446 return null; 447 } 448 449}