/* * (C) Copyright 2006-2008 Nuxeo SA (http://nuxeo.com/) and others. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * Contributors: * bstefanescu * * $Id$ */ package org.nuxeo.ecm.webengine; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; import java.util.HashMap; import java.util.Map; import java.util.Properties; import javax.servlet.GenericServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.io.IOUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.nuxeo.ecm.platform.rendering.api.RenderingEngine; import org.nuxeo.ecm.platform.rendering.api.ResourceLocator; import org.nuxeo.ecm.platform.rendering.fm.FreemarkerEngine; import org.nuxeo.ecm.webengine.app.WebEngineModule; import org.nuxeo.ecm.webengine.jaxrs.context.RequestContext; import org.nuxeo.ecm.webengine.loader.WebLoader; import org.nuxeo.ecm.webengine.model.Module; import org.nuxeo.ecm.webengine.model.Resource; import org.nuxeo.ecm.webengine.model.WebContext; import org.nuxeo.ecm.webengine.model.impl.ModuleConfiguration; import org.nuxeo.ecm.webengine.model.impl.ModuleManager; import org.nuxeo.ecm.webengine.scripting.ScriptFile; import org.nuxeo.ecm.webengine.scripting.Scripting; import org.nuxeo.runtime.annotations.AnnotationManager; import org.nuxeo.runtime.api.Framework; import freemarker.ext.jsp.TaglibFactory; import freemarker.ext.servlet.HttpRequestHashModel; import freemarker.ext.servlet.HttpRequestParametersHashModel; import freemarker.ext.servlet.ServletContextHashModel; /** * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a> */ public class WebEngine implements ResourceLocator { public static final String SKIN_PATH_PREFIX_KEY = "org.nuxeo.ecm.webengine.skinPathPrefix"; protected static final Map<Object, Object> mimeTypes = loadMimeTypes(); private static final Log log = LogFactory.getLog(WebEngine.class); static Map<Object, Object> loadMimeTypes() { Map<Object, Object> mimeTypes = new HashMap<Object, Object>(); Properties p = new Properties(); URL url = WebEngine.class.getClassLoader().getResource("OSGI-INF/mime.properties"); InputStream in = null; try { in = url.openStream(); p.load(in); mimeTypes.putAll(p); } catch (IOException e) { throw new RuntimeException("Failed to load mime types", e); } finally { IOUtils.closeQuietly(in); } return mimeTypes; } public static WebContext getActiveContext() { RequestContext ctx = RequestContext.getActiveContext(); if (ctx != null) { return (WebContext) ctx.getRequest().getAttribute(WebContext.class.getName()); } return null; } protected final File root; protected HashMap<String, WebEngineModule> apps; /** * moduleMgr use double-check idiom and needs to be volatile. See * http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html */ protected volatile ModuleManager moduleMgr; protected final Scripting scripting; protected final RenderingEngine rendering; protected final Map<String, Object> env; protected boolean devMode; protected final AnnotationManager annoMgr; protected final ResourceRegistry registry; protected String skinPathPrefix; protected final WebLoader webLoader; protected volatile boolean isDirty; public WebEngine(File root) { this(new EmptyRegistry(), root); } public WebEngine(ResourceRegistry registry, File root) { this.registry = registry; this.root = root; webLoader = new WebLoader(this); apps = new HashMap<String, WebEngineModule>(); scripting = new Scripting(webLoader); annoMgr = new AnnotationManager(); skinPathPrefix = Framework.getProperty(SKIN_PATH_PREFIX_KEY); if (skinPathPrefix == null) { // TODO: should put this in web.xml and not use jboss.home.dir to // test if on jboss skinPathPrefix = System.getProperty("jboss.home.dir") != null ? "/nuxeo/site/skin" : "/skin"; } env = new HashMap<String, Object>(); env.put("installDir", root); env.put("engine", "Nuxeo Web Engine"); // TODO this should be put in the MANIFEST env.put("version", "1.0.0.rc"); rendering = new FreemarkerEngine(); rendering.setResourceLocator(this); rendering.setSharedVariable("env", getEnvironment()); } /** * JSP taglib support */ public void loadJspTaglib(GenericServlet servlet) { if (rendering instanceof FreemarkerEngine) { FreemarkerEngine fm = (FreemarkerEngine) rendering; ServletContextHashModel servletContextModel = new ServletContextHashModel(servlet, fm.getObjectWrapper()); fm.setSharedVariable("Application", servletContextModel); fm.setSharedVariable("__FreeMarkerServlet.Application__", servletContextModel); fm.setSharedVariable("Application", servletContextModel); fm.setSharedVariable("__FreeMarkerServlet.Application__", servletContextModel); fm.setSharedVariable("JspTaglibs", new TaglibFactory(servlet.getServletContext())); } } public void initJspRequestSupport(GenericServlet servlet, HttpServletRequest request, HttpServletResponse response) { if (rendering instanceof FreemarkerEngine) { FreemarkerEngine fm = (FreemarkerEngine) rendering; HttpRequestHashModel requestModel = new HttpRequestHashModel(request, response, fm.getObjectWrapper()); fm.setSharedVariable("__FreeMarkerServlet.Request__", requestModel); fm.setSharedVariable("Request", requestModel); fm.setSharedVariable("RequestParameters", new HttpRequestParametersHashModel(request)); // HttpSessionHashModel sessionModel = null; // HttpSession session = request.getSession(false); // if(session != null) { // sessionModel = (HttpSessionHashModel) // session.getAttribute(ATTR_SESSION_MODEL); // if (sessionModel == null || sessionModel.isZombie()) { // sessionModel = new HttpSessionHashModel(session, wrapper); // session.setAttribute(ATTR_SESSION_MODEL, sessionModel); // if(!sessionModel.isZombie()) { // initializeSession(request, response); // } // } // } // else { // sessionModel = new HttpSessionHashModel(servlet, request, // response, fm.getObjectWrapper()); // } // sessionModel = new HttpSessionHashModel(request, response, // fm.getObjectWrapper()); // fm.setSharedVariable("Session", sessionModel); } } public WebLoader getWebLoader() { return webLoader; } public void setSkinPathPrefix(String skinPathPrefix) { this.skinPathPrefix = skinPathPrefix; } public String getSkinPathPrefix() { return skinPathPrefix; } @Deprecated public ResourceRegistry getRegistry() { return registry; } public Class<?> loadClass(String className) throws ClassNotFoundException { return webLoader.loadClass(className); } public String getMimeType(String ext) { return (String) mimeTypes.get(ext); } public AnnotationManager getAnnotationManager() { return annoMgr; } public void registerRenderingExtension(String id, Object obj) { rendering.setSharedVariable(id, obj); } public void unregisterRenderingExtension(String id) { rendering.setSharedVariable(id, null); } public Map<String, Object> getEnvironment() { return env; } public Scripting getScripting() { return scripting; } public synchronized WebEngineModule[] getApplications() { return apps.values().toArray(new WebEngineModule[apps.size()]); } public synchronized void addApplication(WebEngineModule app) { flushCache(); apps.put(app.getId(), app); } public ModuleManager getModuleManager() { if (moduleMgr == null) { // avoid synchronizing if not needed synchronized (this) { /** * the duplicate if is used avoid synchronizing when no needed. note that the this.moduleMgr member must * be set at the end of the synchronized block after the module manager is completely initialized */ if (moduleMgr == null) { ModuleManager moduleMgr = new ModuleManager(this); File deployRoot = getDeploymentDirectory(); if (deployRoot.isDirectory()) { // load modules present in deploy directory for (String name : deployRoot.list()) { String path = name + "/module.xml"; File file = new File(deployRoot, path); if (file.isFile()) { webLoader.addClassPathElement(file.getParentFile()); moduleMgr.loadModule(file); } } } for (WebEngineModule app : getApplications()) { ModuleConfiguration mc = app.getConfiguration(); moduleMgr.loadModule(mc); } // set member at the end to be sure moduleMgr is completely // initialized this.moduleMgr = moduleMgr; } } } return moduleMgr; } public Module getModule(String name, WebContext context) { ModuleConfiguration md = getModuleManager().getModule(name); if (md != null) { return md.get(context); } return null; } public File getRootDirectory() { return root; } public File getDeploymentDirectory() { return new File(root, "deploy"); } public File getModulesDirectory() { return new File(root, "modules"); } public RenderingEngine getRendering() { return rendering; } /** * Manage jax-rs root resource bindings * * @deprecated resources are deprecated - you should use a jax-rs application to declare more resources. */ @Deprecated public void addResourceBinding(ResourceBinding binding) { try { binding.resolve(this); registry.addBinding(binding); } catch (ClassNotFoundException e) { throw WebException.wrap("Failed o register binding: " + binding, e); } } /** * @deprecated resources are deprecated - you should use a jax-rs application to declare more resources. */ @Deprecated public void removeResourceBinding(ResourceBinding binding) { registry.removeBinding(binding); } /** * @deprecated resources are deprecated - you should use a jax-rs application to declare more resources. */ @Deprecated public ResourceBinding[] getBindings() { return registry.getBindings(); } public synchronized void setDirty(boolean dirty) { isDirty = dirty; } public boolean tryReload() { if (isDirty) { synchronized (this) { if (isDirty) { reload(); return true; } } } return false; } public synchronized boolean isDirty() { return isDirty; } public synchronized void flushCache() { isDirty = false; if (moduleMgr != null) { webLoader.flushCache(); moduleMgr = null; } } /** * Reloads configuration. */ public synchronized void reload() { log.info("Reloading WebEngine"); isDirty = false; webLoader.flushCache(); apps = new HashMap<String, WebEngineModule>(); if (moduleMgr != null) { // avoid synchronizing if not needed for (ModuleConfiguration mc : moduleMgr.getModules()) { mc.flushCache(); } moduleMgr = null; } } public synchronized void reloadModules() { if (moduleMgr != null) { moduleMgr.reloadModules(); } } public void start() { } public void stop() { registry.clear(); } protected ModuleConfiguration getModuleFromPath(String rootPath, String path) { path = path.substring(rootPath.length() + 1); int p = path.indexOf('/'); String moduleName = path; if (p > -1) { moduleName = path.substring(0, p); } return moduleMgr.getModule(moduleName); } /* ResourceLocator API */ @Override public URL getResourceURL(String key) { try { return new URL(key); } catch (MalformedURLException e) { return null; } } @Override public File getResourceFile(String key) { WebContext ctx = getActiveContext(); if (key.startsWith("@")) { Resource rs = ctx.getTargetObject(); if (rs != null) { return rs.getView(key.substring(1)).script().getFile(); } } else { ScriptFile file = ctx.getFile(key); if (file != null) { return file.getFile(); } } return null; } }