
net.javapla.jawn.server.JawnServletContext Maven / Gradle / Ivy
package net.javapla.jawn.server;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.stream.Collectors;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import net.javapla.jawn.core.Response;
import net.javapla.jawn.core.api.Filter;
import net.javapla.jawn.core.configuration.JawnConfigurations;
import net.javapla.jawn.core.http.Context;
import net.javapla.jawn.core.http.Cookie;
import net.javapla.jawn.core.http.HttpMethod;
import net.javapla.jawn.core.http.Request;
import net.javapla.jawn.core.http.ResponseStream;
import net.javapla.jawn.core.http.SessionFacade;
import net.javapla.jawn.core.parsers.ParserEngineManager;
import net.javapla.jawn.core.routes.Route;
import net.javapla.jawn.core.uploads.FormItem;
import net.javapla.jawn.core.util.Constants;
import net.javapla.jawn.core.util.HttpHeaderUtil;
import net.javapla.jawn.core.util.Modes;
import net.javapla.jawn.core.util.MultiList;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import com.google.inject.Inject;
/**
* @author MTD
*/
class JawnServletContext implements Context.Internal {
private static final String X_POWERED_BY = "X-Powered-By";
private final JawnConfigurations properties;
private final ParserEngineManager parserManager;
private ServletContext servletContext;
private HttpServletRequest request;
private HttpServletResponse response;
private Route route;
private String format, language;
/**
* Holds the actual routed path used in this (request)context.
* This might differ from requestUri as routedPath is stripped from any language
*/
private String routedPath;
// servletcontext, appcontext (what the hell is the difference?)
// requestcontext - the hell, man??
@Inject
JawnServletContext(JawnConfigurations properties, ParserEngineManager parserManager) {
this.properties = properties;
this.parserManager = parserManager;
}
public void init(HttpServletRequest request, HttpServletResponse response) {
this.servletContext = request.getServletContext();
this.request = request;
this.response = response;
// Set the encoding according to the user defined
setEncoding(properties.get(Constants.DEFINED_ENCODING)); //TODO extract staticly somehow - or is it actually static *enough* as it is?
addResponseHeader(X_POWERED_BY, Constants.FRAMEWORK_NAME);
}
public void setRouteInformation(Route route, String format, String language, String routedPath) throws IllegalArgumentException {
if (route == null)
throw new IllegalArgumentException("Route could not be null");
this.route = route;
this.format = format;
this.language = language;
this.routedPath = routedPath;
}
/**
* @return An instance of the Request interface
*/
public Request createRequest() {
return new RequestConvertImpl(request, parserManager);
}
public SessionFacade getSession(boolean createIfNotExists) {
HttpSession session = request.getSession(createIfNotExists);
if (session == null) return null;
return new SessionFacadeImpl(session);
}
@Override
@SuppressWarnings("unchecked")
public void setFlash(String name, Object value) {
SessionFacade session = getSession(true);
if (session.get(Context.FLASH_SESSION_KEYWORD) == null) {
session.put(Context.FLASH_SESSION_KEYWORD, new HashMap());
}
((Map) session.get(Context.FLASH_SESSION_KEYWORD)).put(name, value);
}
//README do we need some sort of globally available context?
// public AppContext createAppContext() {
// return new AppContext(servletContext);
// }
/**
* Returns instance of {@link Route} to be used for potential conditional logic inside controller filters.
*
* @return instance of {@link Route}
*/
public Route getRoute() {
return route;
}
public String getRouteParam(String name) {
if (route == null) return null;
return route.getPathParametersEncoded(routedPath).get(name);
}
// @Override
public Map getRouteParams() {
if (route == null) return Collections.emptyMap();
return route.getPathParametersEncoded(routedPath);
}
/**
* Returns the found language of the URI - if any.
* If the languages are set in Bootstrap, only these are valid.
*
* @return
* The found language if any valid are found, or if the {lang} route parameter
* is set - else null
*/
public String getRouteLanguage() {
String routeLang = getRouteParam("lang");
if (routeLang != null) return routeLang;
return language;
}
public String getRouteFormat() {
return format;//route.getFormat();
}
/**
* Returns value of ID if one is present on a URL. Id is usually a part of a URI, such as: /controller/action/id
.
* This depends on a type of a URI, and whether controller is RESTful or not.
*
* @return ID value from URI is one exists, null if not.
*/
public String getId(){
/*String paramId = request.getParameter("id");
if(paramId != null && request.getAttribute("id") != null){
// Logger logger = LoggerFactory.getLogger(RequestUtils.class);
// logger.warn("WARNING: probably you have 'id' supplied both as a HTTP parameter, as well as in the URI. Choosing parameter over URI value.");
}
String theId;
if(paramId != null){
theId = paramId;
}else{
Object id = request.getAttribute("id");
theId = id != null ? id.toString() : null;
}
return theId;*/
return param("id");
}
/**
* Request servlet path
* @return
*/
public String path() {
return request.getServletPath();
}
/**
* Provides a context of the request - usually an app name (as seen on URL of request). Example:
* /mywebapp
*
* @return a context of the request - usually an app name (as seen on URL of request).
*/
public String contextPath() {
return request.getContextPath();
}
/**
* Differs from {@link #requestUrl()} in that this
* might have been stripped from language prefix
* @return
*/
public String getRoutedPath() {
return routedPath;
}
/**
* Returns a full URL of the request, all except a query string.
*
* @return a full URL of the request, all except a query string.
*/
public String requestUrl(){
return request.getRequestURL().toString();
}
/**
* Returns URI, or a full path of request. This does not include protocol, host or port. Just context and path.
* Examlpe: /mywebapp/controller/action/id
* @return URI, or a full path of request.
*/
public String requestUri(){
return request.getRequestURI();
}
/**
* Returns query string of the request.
*
* @return query string of the request.
*/
public String queryString(){
return request.getQueryString();
}
public String method() {
return request.getMethod();
}
public HttpMethod httpMethod() {
return HttpMethod.getMethod(this);
}
public String requestContentType() {
return request.getContentType();
}
public String getAcceptContentType() {
String contentType = request.getHeader("accept");
return HttpHeaderUtil.parseAcceptHeader(contentType);
}
/**
* Returns an instance of java.util.Map
containing parameter names as keys and parameter values as map values.
* The keys in the parameter map are of type String. The values in the parameter map are of type String array.
*
* MTD: re-mapped the return value to accommodate a different coding style
*
* @return an instance java.util.Map
containing parameter names as keys and parameter values as map values.
* The keys in the parameter map are of type String. The values in the parameter map are of type String array.
*/
public MultiList/*Map>*/ params() {
//MTD: deleted class SimpleHash as its only contribution was an unnecessary #toString()
// - for the curious, it wrote: {first: [more], of: [it]} as opposed to standard: {first=[more],of=[it]}
//#getParameterMap() is by default unmodifiable, so we need to re-map it
Map requestParams = request.getParameterMap();
MultiList params = new MultiList<>();
for (Entry entry : requestParams.entrySet()) {
params.put(entry.getKey(), entry.getValue());
}
if(getId() != null)
params.put("id", getId());
Map routeParameters = route.getPathParametersEncoded(routedPath);//requestContext/*getRequestContext()*/.getUserSegments();
for(Entry entry : routeParameters.entrySet()){
params.put(entry.getKey(), entry.getValue());
}
//MTD: added wildcard to params
// String wildCard = requestContext.getWildCardName();
// if (wildCard != null)
// params.put(wildCard, requestContext.getWildCardValue());
return params;
}
public Map getResponseHeaders() {
Collection names = response.getHeaderNames();
Map headers = new HashMap();
for (String name : names) {
headers.put(name, response.getHeader(name));
}
return headers;
}
/**
* Returns a String containing the real path for a given virtual path. For example, the path "/index.html" returns
* the absolute file path on the server's filesystem would be served by a request for
* "http://host/contextPath/index.html", where contextPath is the context path of this ServletContext.
*
* The real path returned will be in a form appropriate to the computer and operating system on which the servlet
* container is running, including the proper path separators. This method returns null if the servlet container
* cannot translate the virtual path to a real path for any reason (such as when the content is being made
* available from a .war archive).
*
*
* JavaDoc copied from:
* http://download.oracle.com/javaee/1.3/api/javax/servlet/ServletContext.html#getRealPath%28java.lang.String%29
*
*
* @param path a String specifying a virtual path
* @return a String specifying the real path, or null if the translation cannot be performed
*/
public String getRealPath(String path) {
return servletContext.getRealPath(path);
}
/**
* Returns value of routing user segment, or route wild card value, or request parameter.
* If this name represents multiple values, this call will result in {@link IllegalArgumentException}.
*
* @param name name of parameter.
* @return value of routing user segment, or route wild card value, or request parameter.
*/
public String param(String name){
/*if(name.equals("id")){
return getId();
} else */if (request.getParameter(name) != null) {
return request.getParameter(name);
// }else if(requestContext.getUserSegments().get(name) != null){
// return requestContext.getUserSegments().get(name);
// }else if(requestContext.getWildCardName() != null
// && name.equals(requestContext.getWildCardName())){
// return requestContext.getWildCardValue();
}else{
return getRouteParam(name);
}
}
/**
* Returns local host name on which request was received.
*
* @return local host name on which request was received.
*/
public String host() {
return request.getLocalName();
}
public String scheme() {
return request.getScheme();
}
public String serverName() {
return request.getServerName();
}
/**
* Returns local IP address on which request was received.
*
* @return local IP address on which request was received.
*/
public String ipAddress() {
return request.getLocalAddr();
}
/**
* Returns protocol of request, for example: HTTP/1.1.
*
* @return protocol of request
*/
public String protocol(){
return request.getProtocol();
}
/**
* Returns a request header by name.
*
* @param name name of header
* @return header value.
*/
public String requestHeader(String name){
return request.getHeader(name);
}
/**
* Returns all headers from a request keyed by header name.
*
* @return all headers from a request keyed by header name.
*/
public Map requestHeaders(){
Map headers = new HashMap();
Enumeration names = request.getHeaderNames();
while (names.hasMoreElements()) {
String name = names.nextElement();
headers.put(name, request.getHeader(name));
}
return headers;
}
public String[] requestParameterValues(String name) {
return request.getParameterValues(name);
}
/**
* Returns port on which the of the server received current request.
*
* @return port on which the of the server received current request.
*/
public int port(){
return request.getLocalPort();
}
/**
* Host name of the requesting client.
*
* @return host name of the requesting client.
*/
public String remoteHost(){
return request.getRemoteHost();
}
/**
* IP address of the requesting client.
* If the IP of the request seems to come from a local proxy,
* then the X-Forwarded-For header is returned.
*
* @return IP address of the requesting client.
*/
public String remoteIP(){
String remoteAddr = request.getRemoteAddr();
// This could be a list of proxy IPs, which the developer could
// provide via some configuration
if ("127.0.0.1".equals(remoteAddr)) remoteAddr = requestHeader("X-Forwarded-For");
return remoteAddr;
}
public String getParameter(String name) {
return request.getParameter(name);
}
public void setRequestCharacterEncoding(String encoding) throws UnsupportedEncodingException {
request.setCharacterEncoding(encoding);
}
public Cookie getCookie(String cookieName) {
javax.servlet.http.Cookie[] servletCookies = request.getCookies();
for (javax.servlet.http.Cookie cookie : servletCookies) {
if (cookie.getName().equals(cookieName)) {
return CookieHelper.fromServletCookie(cookie);
}
}
return null;
}
public boolean hasCookie(String cookieName) {
return getCookie(cookieName) != null;
}
public List getCookies() {
javax.servlet.http.Cookie[] servletCookies = request.getCookies();
List cookies = new ArrayList();
if(servletCookies != null) {
for (javax.servlet.http.Cookie servletCookie: servletCookies) {
Cookie cookie = CookieHelper.fromServletCookie(servletCookie);
cookies.add(cookie);
}
}
return cookies;
}
public Locale requestLocale() {
return request.getLocale();
}
public void responseLocale(Locale locale) {
response.setLocale(locale);
}
public Object getAttribute(String name) {
return request.getAttribute(name);
}
public T getAttribute(String name, Class clazz) {
return clazz.cast(getAttribute(name));
}
/* ********
* Upload
********* */
public boolean isRequestMultiPart() {
return ServletFileUpload.isMultipartContent(request);
}
/**
* Gets the FileItemIterator of the input.
*
* Can be used to process uploads in a streaming fashion. Check out:
* http://commons.apache.org/fileupload/streaming.html
*
* @return the FileItemIterator of the request or null if there was an
* error.
*/
public Optional> parseRequestMultiPartItems(String encoding) {
DiskFileItemFactory factory = new DiskFileItemFactory();
factory.setSizeThreshold(properties.getInt(Constants.PROPERTY_UPLOADS_MAX_SIZE/*Constants.Params.maxUploadSize.name()*/));//Configuration.getMaxUploadSize());
factory.setRepository(new File(System.getProperty("java.io.tmpdir"))); //Configuration.getTmpDir());
//README the file for tmpdir *MIGHT* need to go into Properties
ServletFileUpload upload = new ServletFileUpload(factory);
if(encoding != null)
upload.setHeaderEncoding(encoding);
upload.setFileSizeMax(properties.getInt(Constants.PROPERTY_UPLOADS_MAX_SIZE));
try {
List items = upload.parseRequest(request)
.stream()
.map(item -> new ApacheFileItemFormItem(item))
.collect(Collectors.toList());
return Optional.of(items);
} catch (FileUploadException e) {
//"Error while trying to process mulitpart file upload"
//README: perhaps some logging
}
return Optional.empty();
}
/**
* Sets an attribute value.
*
* Attributes are shared state for the duration of the request;
* useful to pass values between {@link Filter filters} and
* controllers.
*
* @see #getAttribute(String)
* @see #getAttribute(String, Class)
*/
public void setAttribute(String name, Object value) {
request.setAttribute(name, value);
}
public void setResponseStatus(int status) {
response.setStatus(status);
}
public void setResponseContentType(String type) {
response.setContentType(type);
}
public void responseContentLength(int length) {
response.setContentLength(length);
}
public void addCookie(Cookie cookie) {
response.addCookie(CookieHelper.toServletCookie(cookie));
}
public void addResponseHeader(String name, String value) {
response.addHeader(name, value);
}
public void setResponseHeader(String name, String value) {
response.setHeader(name, value);
}
public Collection responseHeaderNames() {
return response.getHeaderNames();
}
public String responseHeader(String name) {
return response.getHeader(name);
}
/**
* Use to send raw data to HTTP client.
*
* @param contentType content type
* @param headers set of headers.
* @param status status.
* @return instance of output stream to send raw data directly to HTTP client.
* @throws IOException
*/
/*public OutputStream outputStream(String contentType, Map headers, int status) {
try {
if (headers != null) {
for (String key : headers.keySet()) {
if (headers.get(key) != null)
response.addHeader(key.toString(), headers.get(key).toString());
}
}
return response.getOutputStream();
}catch(Exception e){
throw new ControllerException(e);
}
}*/
public OutputStream responseOutputStream() throws IOException {
return response.getOutputStream();
}
public PrintWriter responseWriter() throws IOException {
return response.getWriter();
}
// @Override
// public InputStream requestInputStream() throws IOException {
// return request.getInputStream();
// }
public String getResponseEncoding() {
return response.getCharacterEncoding();
}
/**
* Character encoding for response
* @param encoding
*/
public void setEncoding(String encoding) {
response.setCharacterEncoding(encoding);
}
/* ****** */
public final ResponseStream readyResponse(Response controllerResponse) {
return readyResponse(controllerResponse, true);
}
public final ResponseStream readyResponse(final Response controllerResponse, boolean handleFlash) {
// status
response.setStatus(controllerResponse.status());
// content type
if (response.getContentType() == null && controllerResponse.contentType() != null)
response.setContentType(controllerResponse.contentType());
// encoding
if (response.getCharacterEncoding() == null) { // encoding is already set in the controller
if (controllerResponse.charset() != null)
response.setCharacterEncoding(controllerResponse.charset()); // the response has an encoding
else
response.setCharacterEncoding(Constants.DEFAULT_ENCODING); // use default
}
// flash
if (handleFlash) {
SessionFacade session = getSession(false);
if (session != null && session.containsKey(FLASH_SESSION_KEYWORD)) {
Object object = session.get(FLASH_SESSION_KEYWORD);
controllerResponse.addViewObject(FLASH_KEYWORD, object);
session.remove(FLASH_SESSION_KEYWORD);
}
}
// copy headers
if (!controllerResponse.headers().isEmpty()) {
for (Entry entry : controllerResponse.headers().entrySet()) {
response.addHeader(entry.getKey(), entry.getValue());
}
}
return new ResponseStreamServlet(response);
}
@Override
public Modes mode() {
return properties.getMode();
}
@Override
public String remoteAddress() {
return null;
}
}