package com.google.sitebricks; import java.io.IOException; import java.io.OutputStream; import java.util.Enumeration; import java.util.Map; import java.util.Set; import javax.servlet.http.HttpServletRequest; import javax.validation.ConstraintViolation; import javax.validation.ConstraintViolationException; import javax.validation.ValidationException; import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.Multimap; import com.google.common.io.ByteStreams; import com.google.inject.Inject; import com.google.inject.Injector; import com.google.inject.Provider; import com.google.inject.Singleton; import com.google.inject.TypeLiteral; import com.google.sitebricks.client.Transport; import com.google.sitebricks.headless.Request; import com.google.sitebricks.http.Parameters; import com.google.sitebricks.validation.SitebricksValidator; /** * @author dhanji@gmail.com (Dhanji R. Prasanna) */ @Singleton class ServletRequestProvider implements Provider<Request> { private final Provider<HttpServletRequest> servletRequest; private final Injector injector; private final SitebricksValidator validator; @Inject public ServletRequestProvider(Provider<HttpServletRequest> servletRequest, Injector injector, SitebricksValidator validator) { this.servletRequest = servletRequest; this.injector = injector; this.validator = validator; } @Override public Request get() { return new Request() { HttpServletRequest servletRequest = ServletRequestProvider.this.servletRequest.get(); Multimap<String, String> matrix; Multimap<String, String> headers; Multimap<String, String> params; String method; @Override public <E> RequestRead<E> read(final Class<E> type) { return new RequestRead<E>() { E memo; @Override public E as(Class<? extends Transport> transport) { try { // Only read from the stream once. if (null == memo) { memo = injector.getInstance(transport).in(servletRequest.getInputStream(), type); } } catch (IOException e) { throw new RuntimeException("Unable to obtain input stream from servlet request" + " (was it already used or closed elsewhere?). Error:\n" + e.getMessage(), e); } return memo; } }; } @Override public <E> RequestRead<E> read(final TypeLiteral<E> type) { return new RequestRead<E>() { E memo; @Override public E as(Class<? extends Transport> transport) { try { // Only read from the stream once. if (null == memo) { memo = injector.getInstance(transport).in(servletRequest.getInputStream(), type); } } catch (IOException e) { throw new RuntimeException("Unable to obtain input stream from servlet request" + " (was it already used or closed elsewhere?). Error:\n" + e.getMessage(), e); } return memo; } }; } @Override public void readTo(OutputStream out) throws IOException { ByteStreams.copy(servletRequest.getInputStream(), out); } @Override public Multimap<String, String> headers() { if (null == headers) { readHeaders(); } return headers; } @Override public Multimap<String, String> params() { if (null == params) { readParams(); } return params; } @Override public Multimap<String, String> matrix() { if (null == matrix) { this.matrix = Parameters.readMatrix(servletRequest.getRequestURI()); } return matrix; } @Override public String matrixParam(String name) { if (null == matrix) { this.matrix = Parameters.readMatrix(servletRequest.getRequestURI()); } return Parameters.singleMatrixParam(name, matrix.get(name)); } @Override public String param(String name) { return servletRequest.getParameter(name); } @Override public String header(String name) { return servletRequest.getHeader(name); } @Override public String uri() { return servletRequest.getRequestURI(); } @Override public String path() { return servletRequest.getRequestURI().substring(servletRequest.getContextPath().length()); } @Override public String context() { return servletRequest.getContextPath(); } @Override public String method() { // This ugly hack is required because Sitebricks supports simulating PUT/DELETE requests // via browser POST and special form fields. if (method == null) { String ghostMethod = servletRequest.getParameter(HiddenMethodFilter.hiddenFieldName); method = (ghostMethod != null) ? ghostMethod : servletRequest.getMethod(); } return method; } @Override public void validate(Object object) { Set<? extends ConstraintViolation<?>> cvs = validator.validate(object); if ((cvs != null) && (! cvs.isEmpty())) { throw new ValidationException(new ConstraintViolationException((Set<ConstraintViolation<?>>) cvs)); } } private void readParams() { ImmutableMultimap.Builder<String, String> builder = ImmutableMultimap.builder(); @SuppressWarnings("unchecked") // Guaranteed by servlet spec Map<String, String[]> parameterMap = servletRequest.getParameterMap(); for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) { builder.putAll(entry.getKey(), entry.getValue()); } this.params = builder.build(); } private void readHeaders() { // Build once per request only (so do it here). ImmutableMultimap.Builder<String, String> builder = ImmutableMultimap.builder(); @SuppressWarnings("unchecked") // Guaranteed by servlet spec Enumeration<String> headerNames = servletRequest.getHeaderNames(); while (headerNames.hasMoreElements()) { String header = headerNames.nextElement(); @SuppressWarnings("unchecked") // Guaranteed by servlet spec Enumeration<String> values = servletRequest.getHeaders(header); while (values.hasMoreElements()) { builder.put(header, values.nextElement()); } } this.headers = builder.build(); } }; } }