/* * Copyright (c) 2006-2011 Nuxeo SA (http://nuxeo.com/) and others. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * bstefanescu */ package org.eclipse.ecr.web.jaxrs.servlet; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.URL; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.eclipse.ecr.web.jaxrs.ApplicationHost; import org.eclipse.ecr.web.jaxrs.ApplicationManager; import org.eclipse.ecr.web.jaxrs.Reloadable; import org.eclipse.ecr.web.jaxrs.Utils; import org.eclipse.ecr.web.jaxrs.servlet.config.ServletDescriptor; import org.eclipse.ecr.web.jaxrs.views.ResourceContext; import org.eclipse.ecr.web.rendering.api.RenderingEngine; import org.eclipse.ecr.web.rendering.api.ResourceLocator; import org.eclipse.ecr.web.rendering.fm.FreemarkerEngine; import org.osgi.framework.Bundle; import com.sun.jersey.spi.container.servlet.ServletContainer; /** * A hot re-loadable JAX-RS servlet. * * This servlet is building a Jersey JAX-RS Application. If you need to support * other JAX-RS containers than Jersey you need to write your own servlet. * <p> * Use it as the webengine servlet in web.xml if you want hot reload, otherwise * directly use the Jersey servlet: {@link ServletContainer}. * * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a> */ public class ApplicationServlet extends HttpServlet implements ManagedServlet, Reloadable, ResourceLocator { private static final long serialVersionUID = 1L; protected volatile boolean isDirty = false; protected Bundle bundle; protected ApplicationHost app; protected ServletContainer container; protected String resourcesPrefix; @Override public void setDescriptor(ServletDescriptor sd) { this.bundle = sd.getBundle(); } @Override public void init(ServletConfig config) throws ServletException { super.init(config); resourcesPrefix = config.getInitParameter("resources.prefix"); if (resourcesPrefix == null) { resourcesPrefix = "/skin"; } String name = config.getInitParameter("application.name"); if (name == null) { name = ApplicationManager.DEFAULT_HOST; } app = ApplicationManager.getInstance().getOrCreateApplication(name); container = new ServletContainer(app); initContainer(config); app.setRendering(initRendering(config)); app.addReloadListener(this); } @Override public void destroy() { destroyContainer(); destroyRendering(); container = null; app = null; bundle = null; resourcesPrefix = null; } @Override public synchronized void reload() { isDirty = true; } public RenderingEngine getRenderingEngine() { return app.getRendering(); } public Bundle getBundle() { return bundle; } public ServletContainer getContainer() { return container; } @Override public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String pinfo = request.getPathInfo(); if (pinfo != null && pinfo.startsWith(resourcesPrefix)) { super.service(request, response); } else { containerService(request, response); } } protected void containerService(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { if (isDirty) { reloadContainer(); } String method = request.getMethod().toUpperCase(); if (!"GET".equals(method)) { // force reading properties because jersey is consuming one // character // from the input stream - see WebComponent.isEntityPresent. request.getParameterMap(); } ResourceContext ctx = new ResourceContext(app); ctx.setRequest(request); ResourceContext.setContext(ctx); request.setAttribute(ResourceContext.class.getName(), ctx); try { container.service(request, response); } finally { ResourceContext.destroyContext(); request.removeAttribute(ResourceContext.class.getName()); } } @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String pathInfo = req.getPathInfo(); InputStream in = getServletContext().getResourceAsStream(pathInfo.substring(resourcesPrefix.length())); if (in != null) { String ctype = getServletContext().getMimeType(pathInfo); if (ctype != null) { resp.addHeader("Content-Type", ctype); } try { OutputStream out = resp.getOutputStream(); byte[] bytes = new byte[1024*64]; int r = in.read(bytes); while (r > -1) { if (r > 0) { out.write(bytes, 0, r); } r = in.read(bytes); } out.flush(); } finally { in.close(); } } } protected RenderingEngine initRendering(ServletConfig config) throws ServletException { RenderingEngine rendering; try { String v = config.getInitParameter(RenderingEngine.class.getName()); if (v != null) { rendering = (RenderingEngine)Utils.getClassRef(v, bundle).newInstance(); } else { // default settings rendering = new FreemarkerEngine(); ((FreemarkerEngine)rendering).getConfiguration().setClassicCompatible(false); } rendering.setResourceLocator(this); return rendering; } catch (Exception e) { throw new ServletException(e); } } protected void destroyRendering() { // do nothing } protected void initContainer(ServletConfig config) throws ServletException { Thread thread = Thread.currentThread(); ClassLoader cl = thread.getContextClassLoader(); thread.setContextClassLoader(ServiceClassLoader.getLoader()); try { container.init(getServletConfig()); } finally { thread.setContextClassLoader(cl); } } protected void destroyContainer() { Thread thread = Thread.currentThread(); ClassLoader cl = thread.getContextClassLoader(); thread.setContextClassLoader(ServiceClassLoader.getLoader()); try { container.destroy(); container = null; } finally { thread.setContextClassLoader(cl); } } protected synchronized void reloadContainer() throws ServletException { // reload is not working correctly since old classes are still referenced // for this to work we need a custom ResourceConfig but all fields in jersey // classes are private so we cannot set it ... //super.reload(); Thread thread = Thread.currentThread(); ClassLoader cl = thread.getContextClassLoader(); thread.setContextClassLoader(ServiceClassLoader.getLoader()); try { container.destroy(); container = new ServletContainer(app); container.init(getServletConfig()); } finally { thread.setContextClassLoader(cl); isDirty = false; } } @Override public File getResourceFile(String key) { return null; } @Override public URL getResourceURL(String key) { return ResourceContext.getContext().findEntry(key); } }