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:[email protected]">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 OutputStream out = resp.getOutputStream(); 178 byte[] bytes = new byte[1024 * 64]; 179 int r = in.read(bytes); 180 while (r > -1) { 181 if (r > 0) { 182 out.write(bytes, 0, r); 183 } 184 r = in.read(bytes); 185 } 186 out.flush(); 187 } finally { 188 in.close(); 189 } 190 } 191 } 192 193 protected RenderingEngine initRendering(ServletConfig config) throws ServletException { 194 RenderingEngine rendering; 195 try { 196 String v = config.getInitParameter(RenderingEngine.class.getName()); 197 if (v != null) { 198 rendering = (RenderingEngine) Utils.getClassRef(v, bundle).newInstance(); 199 } else { // default settings 200 rendering = new FreemarkerEngine(); 201 ((FreemarkerEngine) rendering).getConfiguration().setClassicCompatible(false); 202 } 203 rendering.setResourceLocator(this); 204 return rendering; 205 } catch (ReflectiveOperationException | BundleNotFoundException e) { 206 throw new ServletException(e); 207 } 208 } 209 210 protected void destroyRendering() { 211 // do nothing 212 } 213 214 protected void initContainer(ServletConfig config) throws ServletException { 215 container.init(getServletConfig()); 216 } 217 218 protected void destroyContainer() { 219 container.destroy(); 220 container = null; 221 } 222 223 protected synchronized void reloadContainer() throws ServletException { 224 // reload is not working correctly since old classes are still referenced 225 // for this to work we need a custom ResourceConfig but all fields in jersey 226 // classes are private so we cannot set it ... 227 try { 228 container.destroy(); 229 container = createServletContainer(app); 230 container.init(getServletConfig()); 231 } finally { 232 isDirty = false; 233 } 234 } 235 236 protected ServletContainer createServletContainer(ApplicationHost app) { 237 ApplicationAdapter adapter = new ApplicationAdapter(app); 238 // disable wadl since we got class loader pb in JAXB under equinox 239 adapter.getFeatures().put(ResourceConfig.FEATURE_DISABLE_WADL, Boolean.TRUE); 240 // copy all features recorded in app 241 adapter.getFeatures().putAll(app.getFeatures()); 242 return new ServletContainer(adapter); 243 } 244 245 @Override 246 public File getResourceFile(String key) { 247 return null; 248 } 249 250 @Override 251 public URL getResourceURL(String key) { 252 return ResourceContext.getContext().findEntry(key); 253 } 254 255}