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}