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