Java tutorial
/* * Copyright 2012 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.sprintapi.api.http.servlet; import java.io.IOException; import java.io.InputStream; import java.util.HashMap; import java.util.Locale; import java.util.Map; import javax.servlet.GenericServlet; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.io.CopyUtils; import org.sprintapi.api.Context; import org.sprintapi.api.error.ApiErrorException; import org.sprintapi.api.http.HttpContext; import org.sprintapi.api.http.HttpRequest; import org.sprintapi.api.http.header.adapter.HttpDateAdapter; import org.sprintapi.api.http.header.adapter.HttpHeaderAdapter; import org.sprintapi.api.http.header.adapter.HttpLocationAdapter; import org.sprintapi.api.http.header.adapter.accept.HttpAcceptAdapter; import org.sprintapi.api.http.header.adapter.accept.HttpAcceptCharsetAdapter; import org.sprintapi.api.http.header.adapter.accept.HttpAcceptLanguageAdapter; import org.sprintapi.api.http.header.adapter.accept.HttpAllowAdapter; import org.sprintapi.api.http.header.adapter.lang.HttpContentLanguageAdapter; import org.sprintapi.api.http.header.adapter.media.HttpMediaTypeAdapter; import org.sprintapi.api.http.method.HttpMethod; import org.sprintapi.api.http.uri.HttpRequestUrl; import org.sprintapi.api.impl.ResponseHolder; import org.sprintapi.api.meta.ContentMetadata; import org.sprintapi.api.meta.Metadata; import org.sprintapi.api.meta.RequestMetadata; import org.sprintapi.api.meta.ResponseMetadata; import org.sprintapi.api.meta.impl.ResponseMetadataImpl; import org.sprintapi.api.request.Request; import org.sprintapi.api.response.Response; import org.sprintapi.api.response.StatusCode; import org.sprintapi.api.service.ApiService; public abstract class HttpApiServlet extends GenericServlet { private static final long serialVersionUID = 5919203442935383094L; protected static final String POWERED_BY_VALUE = "API-Machine/1.0.0"; private Map<String, HttpMethod> methods; private Map<String, HttpHeaderAdapter<?>> requestMetaAdapters; private Map<String, HttpHeaderAdapter<?>> responseMetaAdapters; private Map<String, HttpHeaderAdapter<?>> contentMetaAdapters; private HttpServletConfig config; public HttpApiServlet() { super(); this.methods = new HashMap<String, HttpMethod>(); this.requestMetaAdapters = new HashMap<String, HttpHeaderAdapter<?>>(); this.responseMetaAdapters = new HashMap<String, HttpHeaderAdapter<?>>(); this.contentMetaAdapters = new HashMap<String, HttpHeaderAdapter<?>>(); } protected abstract ApiService<?, ?> getHttpDispatcher(); protected abstract ApiService<?, ?> getContextDispatcher(); @Override public void init(ServletConfig servletConfig) throws ServletException { super.init(servletConfig); this.config = new HttpServletConfig(servletConfig); setHttpMethod(HttpMethod.GET); setHttpMethod(HttpMethod.DELETE); setHttpMethod(HttpMethod.HEAD); setHttpMethod(HttpMethod.OPTIONS); setHttpMethod(HttpMethod.POST); setHttpMethod(HttpMethod.PUT); setHttpMethod(HttpMethod.TRACE); // request headers setRequestHeaderAdapter(RequestMetadata.ACCEPT_NAME, new HttpAcceptAdapter(InputStream.class)); setRequestHeaderAdapter(RequestMetadata.ACCEPT_LANGUAGE_NAME, new HttpAcceptLanguageAdapter()); setRequestHeaderAdapter(RequestMetadata.ACCEPT_CHARSET_NAME, new HttpAcceptCharsetAdapter()); setRequestHeaderAdapter(RequestMetadata.DATE_NAME, new HttpDateAdapter()); // response headers setResponseHeaderAdapter(ResponseMetadata.DATE_NAME, new HttpDateAdapter()); setResponseHeaderAdapter(ResponseMetadata.LOCATION_NAME, new HttpLocationAdapter()); // entity headers setContentHeaderAdapter(ContentMetadata.ALLOW_NAME, new HttpAllowAdapter()); setContentHeaderAdapter(ContentMetadata.CONTENT_TYPE_NAME, new HttpMediaTypeAdapter()); setContentHeaderAdapter(ContentMetadata.CONTENT_LANGUAGE_NAME, new HttpContentLanguageAdapter()); } protected void setHttpMethod(HttpMethod method) { methods.put(method.name(), method); } protected void setRequestHeaderAdapter(final String name, final HttpHeaderAdapter<?> adapter) { requestMetaAdapters.put(name.toLowerCase(Locale.US), adapter); } protected void setContentHeaderAdapter(final String name, final HttpHeaderAdapter<?> adapter) { contentMetaAdapters.put(name.toLowerCase(Locale.US), adapter); } protected void setResponseHeaderAdapter(final String name, final HttpHeaderAdapter<?> adapter) { responseMetaAdapters.put(name.toLowerCase(Locale.US), adapter); } protected void service(Request request, Response response, Context context) throws ApiErrorException, IOException { getHttpDispatcher().dispatch(request, response, context); } protected void doService(HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws ApiErrorException, IOException { // Append signature if (config.isPoweredByAllowed()) { httpResponse.setHeader(Metadata.X_POWERED_BY_NAME, POWERED_BY_VALUE); } if (httpRequest.getMethod() == null) { throw new ApiErrorException(StatusCode.BAD_REQUEST, "The request is malformed. Requested method is missing."); } if (!methods.containsKey(httpRequest.getMethod().toUpperCase())) { throw new ApiErrorException(StatusCode.NOT_IMPLEMENTED, "The requested method '" + httpRequest.getMethod() + "' is not supported."); } // Wrap request final HttpRequest request = new HttpRequest(httpRequest, methods.get(httpRequest.getMethod().toUpperCase()), requestMetaAdapters, contentMetaAdapters); // Server options request? (OPTIONS *) if (request.getUri().isAsterisk()) { if (HttpMethod.OPTIONS.name().equalsIgnoreCase(httpRequest.getMethod())) { throw new ApiErrorException(StatusCode.NOT_IMPLEMENTED, "Not Implemented."); } throw new ApiErrorException(StatusCode.BAD_REQUEST, "The request is malformed."); } // Create response ResponseHolder<?> response = new ResponseHolder<Object>(); response.setMetadata(new ResponseMetadataImpl()); HttpContext<?, ?> context = HttpContext.getInstance(httpRequest); context.setDispatcher(getContextDispatcher()); service((Request) request, (Response) response, (Context) context); if (response.getStatus() == null) { response.setStatus(StatusCode.OK); } // Set response status httpResponse.setStatus((response.getStatus() != null) ? response.getStatus().getCode() : 0); // Write response headers if (response.getMetadata() != null) { writeHeaders(responseMetaAdapters, httpResponse, request, response.getMetadata(), context); } // Finish if there is no content to write if (response.getContent() == null) { return; } // Write content headers if (response.getContent().getMetadata() != null) { writeHeaders(contentMetaAdapters, httpResponse, request, response.getContent().getMetadata(), context); } // Write response content length // if (context.getResponseContext().getContentType() != null) { // httpResponse.setContentType(context.getResponseContext().getContentType().toString()); // } // Write response content body if (response.getContent().getBody() != null) { CopyUtils.copy((InputStream) response.getContent().getBody(), httpResponse.getOutputStream()); } } @Override public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException { HttpServletRequest nativeHttpRequest = null; HttpServletResponse nativeHttpResponse = null; try { nativeHttpRequest = (HttpServletRequest) servletRequest; nativeHttpResponse = (HttpServletResponse) servletResponse; } catch (ClassCastException e) { throw new ServletException("non-HTTP request or response"); } if ((nativeHttpRequest == null) || (nativeHttpResponse == null)) { throw new ServletException("non-HTTP request or response"); } try { doService(nativeHttpRequest, nativeHttpResponse); } catch (ApiErrorException e) { handleError(e, nativeHttpResponse); } catch (Exception e) { handleError(new ApiErrorException(HttpRequestUrl.getInstance(nativeHttpRequest), StatusCode.INTERNAL_SERVER_ERROR, e), nativeHttpResponse); } } protected void handleError(ApiErrorException ex, HttpServletResponse httpResponse) { httpResponse.setStatus(ex.getStatusCode().getCode()); httpResponse.setContentType("text/plain; charset=utf-8"); httpResponse.setHeader(ContentMetadata.CONTENT_LANGUAGE_NAME, "en"); try { httpResponse.getWriter().append(ex.getMessage()).close(); } catch (IOException e) { log(ex.getMessage(), ex.getCause()); } if (StatusCode.Category.ServerError.equals(ex.getStatusCode().getCategory())) { log(ex.getMessage(), ex.getCause()); } } protected static void writeHeaders(Map<String, HttpHeaderAdapter<?>> adapters, HttpServletResponse httpResponse, Request request, Metadata metadata, Context context) throws ApiErrorException { if ((metadata == null) || (metadata.names() == null)) { return; } for (String meta : metadata.names()) { Object value = metadata.get(meta); if (value != null) { String httpValue = value.toString(); HttpHeaderAdapter metaAdapter = (HttpHeaderAdapter<?>) (adapters.containsKey( meta.toLowerCase(Locale.US)) ? adapters.get(meta.toLowerCase(Locale.US)) : null); if (metaAdapter != null) { httpValue = metaAdapter.write(value); } httpResponse.setHeader(meta, httpValue); } } } }