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.app.jersey; 013 014import java.io.IOException; 015import java.util.Collections; 016import java.util.Enumeration; 017import java.util.Hashtable; 018import java.util.LinkedList; 019import java.util.List; 020import java.util.NoSuchElementException; 021 022import javax.servlet.ServletConfig; 023import javax.servlet.ServletException; 024import javax.servlet.http.HttpServletRequest; 025import javax.servlet.http.HttpServletRequestWrapper; 026import javax.servlet.http.HttpServletResponse; 027import javax.ws.rs.core.HttpHeaders; 028 029import org.nuxeo.ecm.webengine.WebException; 030import org.nuxeo.ecm.webengine.jaxrs.Activator; 031import org.nuxeo.ecm.webengine.jaxrs.servlet.ApplicationServlet; 032 033/** 034 * WebEngine integration with OSGi JAX-RS model from ECR. 035 * 036 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a> 037 */ 038public class WebEngineServlet extends ApplicationServlet { 039 040 protected final String MIME_TYPE = "X-File-Type"; 041 042 private final class DefaultContentTypeRequestWrapper extends HttpServletRequestWrapper { 043 044 protected final Hashtable<String, String[]> headers; 045 046 protected final String lCONTENT_TYPE = HttpHeaders.CONTENT_TYPE.toLowerCase(); 047 048 protected final String lFILE_TYPE = MIME_TYPE.toLowerCase(); 049 050 protected DefaultContentTypeRequestWrapper(HttpServletRequest request, boolean patchCType, boolean patchMType) { 051 super(request); 052 headers = patchHeaders(request, patchCType, patchMType); 053 } 054 055 protected Hashtable<String, String[]> patchHeaders(HttpServletRequest request, boolean patchCType, 056 boolean patchMType) { 057 Hashtable<String, String[]> headers = new Hashtable<String, String[]>(); 058 // collect headers from request 059 Enumeration<String> eachNames = request.getHeaderNames(); 060 while (eachNames.hasMoreElements()) { 061 String name = eachNames.nextElement().toLowerCase(); 062 List<String> values = new LinkedList<String>(); 063 Enumeration<String> eachValues = request.getHeaders(name); 064 while (eachValues.hasMoreElements()) { 065 values.add(eachValues.nextElement()); 066 } 067 headers.put(name, values.toArray(new String[values.size()])); 068 } 069 if (patchCType) { 070 // patch content type 071 String ctype = request.getContentType(); 072 if (ctype == null || ctype.isEmpty()) { 073 String[] ctypes = new String[] { "application/octet-stream" }; 074 headers.put(lCONTENT_TYPE, ctypes); 075 } else { 076 patchContentTypes(headers.get(lCONTENT_TYPE)); 077 } 078 } 079 if (patchMType) { 080 patchContentTypes(headers.get(lFILE_TYPE)); 081 } 082 return headers; 083 } 084 085 protected void patchContentTypes(String[] ctypes) { 086 for (int index = 0; index < ctypes.length; ++index) { 087 String value = ctypes[index]; 088 if (value.isEmpty()) { 089 ctypes[index] = "application/octet-stream"; 090 } else if (!value.contains("/")) { 091 ctypes[index] = "application/".concat(value); 092 } 093 } 094 } 095 096 @Override 097 public Enumeration<String> getHeaderNames() { 098 return headers.keys(); 099 } 100 101 @Override 102 public String getHeader(String name) { 103 String lname = name.toLowerCase(); 104 if (!headers.containsKey(lname)) { 105 return null; 106 } 107 return headers.get(lname)[0]; 108 } 109 110 @Override 111 public Enumeration<String> getHeaders(final String name) { 112 final String lname = name.toLowerCase(); 113 if (!headers.containsKey(lname)) { 114 return Collections.emptyEnumeration(); 115 } 116 return new Enumeration<String>() { 117 String[] values = headers.get(lname); 118 119 int index = 0; 120 121 @Override 122 public boolean hasMoreElements() { 123 return index < values.length; 124 } 125 126 @Override 127 public String nextElement() { 128 if (index >= values.length) { 129 throw new NoSuchElementException(index + " is higher than " + values.length); 130 } 131 return values[index++]; 132 } 133 }; 134 } 135 136 } 137 138 private static final long serialVersionUID = 1L; 139 140 @Override 141 public void init(ServletConfig config) throws ServletException { 142 bundle = Activator.getInstance().getContext().getBundle(); 143 super.init(config); 144 } 145 146 @Override 147 public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 148 containerService(request, response); 149 } 150 151 @Override 152 protected void containerService(HttpServletRequest request, HttpServletResponse response) throws ServletException, 153 IOException { 154 if (isDirty) { 155 reloadContainer(); 156 } 157 String method = request.getMethod().toUpperCase(); 158 if (!"GET".equals(method)) { 159 // force reading properties because jersey is consuming one 160 // character 161 // from the input stream - see WebComponent.isEntityPresent. 162 request.getParameterMap(); 163 } 164 final String ctype = request.getHeader(HttpHeaders.CONTENT_TYPE); 165 final String mtype = request.getHeader(MIME_TYPE); 166 boolean patchCType = ctype == null || ctype.length() == 0 || !ctype.contains("/"); 167 boolean patchMMType = mtype != null && !mtype.contains("/"); 168 if (patchCType || patchMMType) { 169 request = new DefaultContentTypeRequestWrapper(request, patchCType, patchMMType); 170 } 171 container.service(request, response); 172 } 173}