001/* 002 * Copyright (c) 2006-2011 Nuxeo SA (http://nuxeo.com/) and others. 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the Eclipse Public License v1.0 006 * which accompanies this distribution, and is available at 007 * http://www.eclipse.org/legal/epl-v10.html 008 * 009 * Contributors: 010 * bstefanescu 011 */ 012package org.nuxeo.ecm.webengine.jaxrs.servlet; 013 014import java.io.File; 015import java.io.IOException; 016import java.io.InputStream; 017import java.io.OutputStream; 018import java.net.URL; 019import java.util.Enumeration; 020 021import javax.servlet.ServletConfig; 022import javax.servlet.ServletException; 023import javax.servlet.http.HttpServlet; 024import javax.servlet.http.HttpServletRequest; 025import javax.servlet.http.HttpServletResponse; 026 027import org.nuxeo.ecm.webengine.jaxrs.ApplicationHost; 028import org.nuxeo.ecm.webengine.jaxrs.ApplicationManager; 029import org.nuxeo.ecm.webengine.jaxrs.BundleNotFoundException; 030import org.nuxeo.ecm.webengine.jaxrs.Reloadable; 031import org.nuxeo.ecm.webengine.jaxrs.Utils; 032import org.nuxeo.ecm.webengine.jaxrs.servlet.config.ServletDescriptor; 033import org.nuxeo.ecm.webengine.jaxrs.views.ResourceContext; 034import org.nuxeo.ecm.platform.rendering.api.RenderingEngine; 035import org.nuxeo.ecm.platform.rendering.api.ResourceLocator; 036import org.nuxeo.ecm.platform.rendering.fm.FreemarkerEngine; 037import org.osgi.framework.Bundle; 038 039import com.sun.jersey.api.core.ApplicationAdapter; 040import com.sun.jersey.api.core.ResourceConfig; 041import com.sun.jersey.spi.container.servlet.ServletContainer; 042 043/** 044 * A hot re-loadable JAX-RS servlet. This servlet is building a Jersey JAX-RS Application. If you need to support other 045 * JAX-RS containers than Jersey you need to write your own servlet. 046 * <p> 047 * Use it as the webengine servlet in web.xml if you want hot reload, otherwise directly use the Jersey servlet: 048 * {@link ServletContainer}. 049 * 050 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a> 051 */ 052public class ApplicationServlet extends HttpServlet implements ManagedServlet, Reloadable, ResourceLocator { 053 054 private static final long serialVersionUID = 1L; 055 056 protected volatile boolean isDirty = false; 057 058 protected Bundle bundle; 059 060 protected ApplicationHost app; 061 062 protected ServletContainer container; 063 064 protected String resourcesPrefix; 065 066 @Override 067 public void setDescriptor(ServletDescriptor sd) { 068 this.bundle = sd.getBundle(); 069 } 070 071 @Override 072 public void init(ServletConfig config) throws ServletException { 073 super.init(config); 074 resourcesPrefix = config.getInitParameter("resources.prefix"); 075 if (resourcesPrefix == null) { 076 resourcesPrefix = "/skin"; 077 } 078 String name = config.getInitParameter("application.name"); 079 if (name == null) { 080 name = ApplicationManager.DEFAULT_HOST; 081 } 082 app = ApplicationManager.getInstance().getOrCreateApplication(name); 083 // use init parameters that are booleans as features 084 for (Enumeration<String> en = config.getInitParameterNames(); en.hasMoreElements();) { 085 String n = en.nextElement(); 086 String v = config.getInitParameter(n); 087 if (Boolean.TRUE.toString().equals(v) || Boolean.FALSE.toString().equals(v)) { 088 app.getFeatures().put(n, Boolean.valueOf(v)); 089 } 090 } 091 container = createServletContainer(app); 092 093 initContainer(config); 094 app.setRendering(initRendering(config)); 095 096 app.addReloadListener(this); 097 } 098 099 @Override 100 public void destroy() { 101 destroyContainer(); 102 destroyRendering(); 103 container = null; 104 app = null; 105 bundle = null; 106 resourcesPrefix = null; 107 } 108 109 @Override 110 public synchronized void reload() { 111 isDirty = true; 112 } 113 114 public RenderingEngine getRenderingEngine() { 115 return app.getRendering(); 116 } 117 118 public Bundle getBundle() { 119 return bundle; 120 } 121 122 public ServletContainer getContainer() { 123 return container; 124 } 125 126 @Override 127 public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 128 String pinfo = request.getPathInfo(); 129 if (pinfo != null && pinfo.startsWith(resourcesPrefix)) { 130 super.service(request, response); 131 } else { 132 containerService(request, response); 133 } 134 } 135 136 protected void containerService(HttpServletRequest request, HttpServletResponse response) throws ServletException, 137 IOException { 138 if (isDirty) { 139 reloadContainer(); 140 } 141 String method = request.getMethod().toUpperCase(); 142 if (!"GET".equals(method)) { 143 // force reading properties because jersey is consuming one 144 // character 145 // from the input stream - see WebComponent.isEntityPresent. 146 request.getParameterMap(); 147 } 148 ResourceContext ctx = new ResourceContext(app); 149 ctx.setRequest(request); 150 ResourceContext.setContext(ctx); 151 request.setAttribute(ResourceContext.class.getName(), ctx); 152 try { 153 container.service(request, response); 154 } finally { 155 ResourceContext.destroyContext(); 156 request.removeAttribute(ResourceContext.class.getName()); 157 } 158 } 159 160 @Override 161 protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 162 String pathInfo = req.getPathInfo(); 163 InputStream in = getServletContext().getResourceAsStream(pathInfo.substring(resourcesPrefix.length())); 164 if (in != null) { 165 String ctype = getServletContext().getMimeType(pathInfo); 166 if (ctype != null) { 167 resp.addHeader("Content-Type", ctype); 168 } 169 try { 170 OutputStream out = resp.getOutputStream(); 171 byte[] bytes = new byte[1024 * 64]; 172 int r = in.read(bytes); 173 while (r > -1) { 174 if (r > 0) { 175 out.write(bytes, 0, r); 176 } 177 r = in.read(bytes); 178 } 179 out.flush(); 180 } finally { 181 in.close(); 182 } 183 } 184 } 185 186 protected RenderingEngine initRendering(ServletConfig config) throws ServletException { 187 RenderingEngine rendering; 188 try { 189 String v = config.getInitParameter(RenderingEngine.class.getName()); 190 if (v != null) { 191 rendering = (RenderingEngine) Utils.getClassRef(v, bundle).newInstance(); 192 } else { // default settings 193 rendering = new FreemarkerEngine(); 194 ((FreemarkerEngine) rendering).getConfiguration().setClassicCompatible(false); 195 } 196 rendering.setResourceLocator(this); 197 return rendering; 198 } catch (ReflectiveOperationException | BundleNotFoundException e) { 199 throw new ServletException(e); 200 } 201 } 202 203 protected void destroyRendering() { 204 // do nothing 205 } 206 207 protected void initContainer(ServletConfig config) throws ServletException { 208 container.init(getServletConfig()); 209 } 210 211 protected void destroyContainer() { 212 container.destroy(); 213 container = null; 214 } 215 216 protected synchronized void reloadContainer() throws ServletException { 217 // reload is not working correctly since old classes are still referenced 218 // for this to work we need a custom ResourceConfig but all fields in jersey 219 // classes are private so we cannot set it ... 220 try { 221 container.destroy(); 222 container = createServletContainer(app); 223 container.init(getServletConfig()); 224 } finally { 225 isDirty = false; 226 } 227 } 228 229 protected ServletContainer createServletContainer(ApplicationHost app) { 230 ApplicationAdapter adapter = new ApplicationAdapter(app); 231 // disable wadl since we got class loader pb in JAXB under equinox 232 adapter.getFeatures().put(ResourceConfig.FEATURE_DISABLE_WADL, Boolean.TRUE); 233 // copy all features recorded in app 234 adapter.getFeatures().putAll(app.getFeatures()); 235 return new ServletContainer(adapter); 236 } 237 238 @Override 239 public File getResourceFile(String key) { 240 return null; 241 } 242 243 @Override 244 public URL getResourceURL(String key) { 245 return ResourceContext.getContext().findEntry(key); 246 } 247 248}