Java tutorial
/* * Copyright 2015 52North Initiative for Geospatial Open Source * Software GmbH * * 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.n52.iceland.service; import java.io.IOException; import java.lang.reflect.Method; import java.util.Collections; import java.util.Enumeration; import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; import javax.inject.Inject; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.n52.iceland.binding.Binding; import org.n52.iceland.binding.BindingRepository; import org.n52.iceland.event.ServiceEventBus; import org.n52.iceland.event.events.ExceptionEvent; import org.n52.iceland.event.events.IncomingRequestEvent; import org.n52.iceland.event.events.OutgoingResponseEvent; import org.n52.iceland.exception.HTTPException; import org.n52.iceland.util.http.HTTPHeaders; import org.n52.iceland.util.http.HTTPMethods; import org.n52.iceland.util.http.HTTPStatus; import org.n52.iceland.util.http.MediaType; import org.n52.iceland.util.http.MediaTypes; import com.google.common.base.Stopwatch; /** * The servlet of the Service which receives the incoming HttpPost and HttpGet * requests and sends the operation result documents to the client TODO review * exception handling * * @since 1.0.0 */ @Controller @RequestMapping(value = "/service", consumes = "*/*", produces = "*/*") public class Service extends HttpServlet { private static final long serialVersionUID = -2103692310137045855L; private static final Logger LOGGER = LoggerFactory.getLogger(Service.class); public static final String BINDING_DELETE_METHOD = "doDeleteOperation"; public static final String BINDING_PUT_METHOD = "doPutOperation"; public static final String BINDING_POST_METHOD = "doPostOperation"; public static final String BINDING_GET_METHOD = "doGetOperation"; private static final AtomicLong counter = new AtomicLong(0); @Inject private transient BindingRepository bindingRepository; @Inject private transient ServiceEventBus serviceEventBus; private long logRequest(HttpServletRequest request) { long count = counter.incrementAndGet(); this.serviceEventBus.submit(new IncomingRequestEvent(request, count)); if (LOGGER.isDebugEnabled()) { Enumeration<?> headerNames = request.getHeaderNames(); StringBuilder headers = new StringBuilder(); while (headerNames.hasMoreElements()) { String name = (String) headerNames.nextElement(); headers.append("> ").append(name).append(": ").append(request.getHeader(name)).append("\n"); } LOGGER.debug("Incoming request No. {}:\n> [{} {} {}] from {} {}\n{}", count, request.getMethod(), request.getRequestURI(), request.getProtocol(), request.getRemoteAddr(), request.getRemoteHost(), headers); } return count; } private void logResponse(HttpServletRequest request, HttpServletResponse response, long count, Stopwatch stopwatch) { long elapsed = stopwatch.stop().elapsed(TimeUnit.MILLISECONDS); this.serviceEventBus.submit(new OutgoingResponseEvent(request, response, count, elapsed)); LOGGER.debug("Outgoing response for request No. {} is committed = {} (took {} ms)", count, response.isCommitted(), elapsed); } @Override @Deprecated public void doDelete(HttpServletRequest request, HttpServletResponse response) throws IOException { delete(request, response); } @RequestMapping(method = RequestMethod.DELETE) public void delete(HttpServletRequest request, HttpServletResponse response) throws IOException { Stopwatch stopwatch = Stopwatch.createStarted(); long currentCount = logRequest(request); try { getBinding(request).doDeleteOperation(request, response); } catch (HTTPException exception) { onHttpException(request, response, exception); } finally { logResponse(request, response, currentCount, stopwatch); } } @Deprecated @Override public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { get(request, response); } @RequestMapping(method = RequestMethod.GET) public void get(HttpServletRequest request, HttpServletResponse response) throws IOException { Stopwatch stopwatch = Stopwatch.createStarted(); long currentCount = logRequest(request); try { getBinding(request).doGetOperation(request, response); } catch (HTTPException exception) { onHttpException(request, response, exception); } finally { logResponse(request, response, currentCount, stopwatch); } } @Deprecated @Override public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { post(request, response); } @RequestMapping(method = RequestMethod.POST) public void post(HttpServletRequest request, HttpServletResponse response) throws IOException { Stopwatch stopwatch = Stopwatch.createStarted(); long currentCount = logRequest(request); try { getBinding(request).doPostOperation(request, response); } catch (HTTPException exception) { onHttpException(request, response, exception); } finally { logResponse(request, response, currentCount, stopwatch); } } @Deprecated @Override public void doPut(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { put(request, response); } @RequestMapping(method = RequestMethod.PUT) public void put(HttpServletRequest request, HttpServletResponse response) throws IOException { Stopwatch stopwatch = Stopwatch.createStarted(); long currentCount = logRequest(request); try { getBinding(request).doPutOperation(request, response); } catch (HTTPException exception) { onHttpException(request, response, exception); } finally { logResponse(request, response, currentCount, stopwatch); } } @Deprecated @Override public void doOptions(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { options(request, response); } @RequestMapping(method = RequestMethod.OPTIONS) private void options(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { Stopwatch stopwatch = Stopwatch.createStarted(); long currentCount = logRequest(request); Binding binding = null; try { binding = getBinding(request); binding.doOptionsOperation(request, response); } catch (HTTPException exception) { if (exception.getStatus() == HTTPStatus.METHOD_NOT_ALLOWED && binding != null) { doDefaultOptions(binding, request, response); } else { onHttpException(request, response, exception); } } finally { logResponse(request, response, currentCount, stopwatch); } } /** * Get the implementation of {@link Binding} that is registered for the * given <code>request</code>. * * @param request * URL pattern from request URL * * @return The implementation of {@link Binding} that is registered for the * given <code>urlPattern</code>. * * * @throws HTTPException If the URL pattern or ContentType is not supported * by this service. */ private Binding getBinding(HttpServletRequest request) throws HTTPException { final String requestURI = request.getPathInfo(); if (requestURI == null || requestURI.isEmpty() || requestURI.equals("/")) { MediaType contentType = getContentType(request); // strip of the parameters to get rid of things like encoding Binding binding = this.bindingRepository.getBinding(contentType.withoutParameters()); if (binding == null) { if (contentType.equals(MediaTypes.APPLICATION_KVP)) { throw new HTTPException(HTTPStatus.METHOD_NOT_ALLOWED); } else { throw new HTTPException(HTTPStatus.UNSUPPORTED_MEDIA_TYPE); } } else { return binding; } } for (String prefix : this.bindingRepository.getAllBindingsByPath().keySet()) { if (requestURI.startsWith(prefix)) { return this.bindingRepository.getBinding(prefix); } } throw new HTTPException(HTTPStatus.NOT_FOUND); } private MediaType getContentType(HttpServletRequest request) throws HTTPException { if (request.getContentType() == null) { // default to KVP for GET requests if (request.getMethod().equals(HTTPMethods.GET)) { return MediaTypes.APPLICATION_KVP; } else { throw new HTTPException(HTTPStatus.BAD_REQUEST); } } else { try { return MediaType.parse(request.getContentType()); } catch (IllegalArgumentException e) { throw new HTTPException(HTTPStatus.BAD_REQUEST, e); } } } protected void onHttpException(HttpServletRequest request, HttpServletResponse response, HTTPException exception) throws IOException { this.serviceEventBus.submit(new ExceptionEvent(exception)); response.sendError(exception.getStatus().getCode(), exception.getMessage()); } protected void doDefaultOptions(Binding binding, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { Set<String> methods = getDeclaredBindingMethods(binding.getClass()); StringBuilder allow = new StringBuilder(); if (methods.contains(BINDING_GET_METHOD)) { allow.append(HTTPMethods.GET); allow.append(", "); allow.append(HTTPMethods.HEAD); } if (methods.contains(BINDING_POST_METHOD)) { if (allow.length() != 0) { allow.append(", "); } allow.append(HTTPMethods.POST); } if (methods.contains(BINDING_PUT_METHOD)) { if (allow.length() != 0) { allow.append(", "); } allow.append(HTTPMethods.PUT); } if (methods.contains(BINDING_DELETE_METHOD)) { if (allow.length() != 0) { allow.append(", "); } allow.append(HTTPMethods.DELETE); } if (allow.length() != 0) { allow.append(", "); } allow.append(HTTPMethods.TRACE); allow.append(", "); allow.append(HTTPMethods.OPTIONS); response.setHeader(HTTPHeaders.ALLOW, allow.toString()); } private Set<String> getDeclaredBindingMethods(Class<?> c) { if (c.equals(Binding.class)) { return Collections.emptySet(); } else { Set<String> parent = getDeclaredBindingMethods(c.getSuperclass()); for (Method m : c.getDeclaredMethods()) { parent.add(m.getName()); } return parent; } } }