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