/* * (C) Copyright 2006-2011 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 */ package org.nuxeo.ecm.webengine.jaxrs.servlet; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.URL; import java.util.Enumeration; 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.nuxeo.ecm.webengine.jaxrs.ApplicationHost; import org.nuxeo.ecm.webengine.jaxrs.ApplicationManager; import org.nuxeo.ecm.webengine.jaxrs.BundleNotFoundException; import org.nuxeo.ecm.webengine.jaxrs.Reloadable; import org.nuxeo.ecm.webengine.jaxrs.Utils; import org.nuxeo.ecm.webengine.jaxrs.servlet.config.ServletDescriptor; import org.nuxeo.ecm.webengine.jaxrs.views.ResourceContext; 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.osgi.framework.Bundle; import com.sun.jersey.api.core.ApplicationAdapter; import com.sun.jersey.api.core.ResourceConfig; 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); // use init parameters that are booleans as features for (Enumeration<String> en = config.getInitParameterNames(); en.hasMoreElements();) { String n = en.nextElement(); String v = config.getInitParameter(n); if (Boolean.TRUE.toString().equals(v) || Boolean.FALSE.toString().equals(v)) { app.getFeatures().put(n, Boolean.valueOf(v)); } } container = createServletContainer(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 (ReflectiveOperationException | BundleNotFoundException e) { throw new ServletException(e); } } protected void destroyRendering() { // do nothing } protected void initContainer(ServletConfig config) throws ServletException { container.init(getServletConfig()); } protected void destroyContainer() { container.destroy(); container = null; } 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 ... try { container.destroy(); container = createServletContainer(app); container.init(getServletConfig()); } finally { isDirty = false; } } protected ServletContainer createServletContainer(ApplicationHost app) { ApplicationAdapter adapter = new ApplicationAdapter(app); // disable wadl since we got class loader pb in JAXB under equinox adapter.getFeatures().put(ResourceConfig.FEATURE_DISABLE_WADL, Boolean.TRUE); // copy all features recorded in app adapter.getFeatures().putAll(app.getFeatures()); return new ServletContainer(adapter); } @Override public File getResourceFile(String key) { return null; } @Override public URL getResourceURL(String key) { return ResourceContext.getContext().findEntry(key); } }