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